Jak programowo odcień obrazu w systemie iOS?

87

Chciałbym zabarwić obraz za pomocą odniesienia koloru. Rezultat powinien wyglądać jak tryb mieszania Pomnóż w Photoshopie, w którym biel zostałaby zastąpiona odcieniem :

tekst alternatywny

Będę zmieniać wartość koloru w sposób ciągły.

Kontynuacja: umieściłbym kod, aby to zrobić w metodzie drawRect: mojego ImageView, prawda?

Jak zawsze fragment kodu znacznie pomógłby mi w zrozumieniu, w przeciwieństwie do linku.

Aktualizacja: podklasa UIImageView z kodem sugerowanym przez Ramin .

Umieściłem to w viewDidLoad: mojego kontrolera widoku:

[self.lena setImage:[UIImage imageNamed:kImageName]];
[self.lena setOverlayColor:[UIColor blueColor]];
[super viewDidLoad];

Widzę obraz, ale nie jest zabarwiony. Próbowałem też załadować inne obrazy, ustawić obraz w IB i wywołać setNeedsDisplay: w moim kontrolerze widoku.

Aktualizacja : drawRect: nie jest wywoływany.

Ostatnia aktualizacja: znalazłem stary projekt, który miał poprawnie skonfigurowany imageView, więc mogłem przetestować kod Ramina i działa jak urok!

Ostateczna, ostateczna aktualizacja:

Dla tych, którzy dopiero uczą się o Core Graphics, oto najprostsza rzecz, która może zadziałać.

W Twojej podklasie UIView:

- (void)drawRect:(CGRect)rect {

    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextSetFillColor(context, CGColorGetComponents([UIColor colorWithRed:0.5 green:0.5 blue:0 alpha:1].CGColor)); // don't make color too saturated

    CGContextFillRect(context, rect); // draw base

    [[UIImage imageNamed:@"someImage.png"] drawInRect: rect blendMode:kCGBlendModeOverlay alpha:1.0]; // draw image
}
willc2
źródło
Dodałem fragment przed wysłaniem do starego kodu drawRect, który miałem przy sobie i działał dobrze. Może chcesz spróbować bez wywołania [super viewDidLoad] (lub przenieś to powyżej). Sprawdź również dwukrotnie, aby upewnić się, że ktokolwiek alokuje ten obiekt, przydziela wersję pochodną, ​​a nie zwykłą wersję UIImageView (tj. Jeśli jest ładowana do nib, alokatora itp.).
Ramin
Inna sugestia, jeśli powyższe nie działa: zamiast podklasy UIImageView podklasować zwykły UIView i dodać zarówno overlayColor, jak i właściwość UIImage o nazwie „image”, którą można ustawić. Następnie umieść tam drawRect. Kod drawRect nie dba o to, czy wartość „image” pochodzi z UIImageView, czy z właściwości, którą zdefiniowałeś.
Ramin
Czy to nie zawiedzie, jeśli obraz jest z alfą? A co jeśli chciałbym zabarwić tylko narysowaną część obrazu? (Tak jak UIButton?)
Sunil Chauhan

Odpowiedzi:

45

Najpierw będziesz chciał podklasę UIImageView i nadpisać metodę drawRect. Twoja klasa potrzebuje właściwości UIColor (nazwijmy ją overlayColor), aby przechowywać kolor mieszania i niestandardową metodę ustawiającą, która wymusza przerysowanie po zmianie koloru. Coś takiego:

- (void) setOverlayColor:(UIColor *)newColor {
   if (overlayColor)
     [overlayColor release];

   overlayColor = [newColor retain];
   [self setNeedsDisplay]; // fires off drawRect each time color changes
}

W metodzie drawRect będziesz chciał najpierw narysować obraz, a następnie nałożyć na niego prostokąt wypełniony wybranym kolorem wraz z odpowiednim trybem mieszania, coś takiego:

- (void) drawRect:(CGRect)area
{
  CGContextRef context = UIGraphicsGetCurrentContext();
  CGContextSaveGState(context);

  // Draw picture first
  //
  CGContextDrawImage(context, self.frame, self.image.CGImage);

  // Blend mode could be any of CGBlendMode values. Now draw filled rectangle
  // over top of image.
  //
  CGContextSetBlendMode (context, kCGBlendModeMultiply);
  CGContextSetFillColor(context, CGColorGetComponents(self.overlayColor.CGColor));      
  CGContextFillRect (context, self.bounds);
  CGContextRestoreGState(context);
}

Zwykle, aby zoptymalizować rysunek, ograniczyłbyś rzeczywisty rysunek tylko do obszaru przekazanego do drawRect, ale ponieważ obraz tła musi być przerysowywany za każdym razem, gdy zmienia się kolor, prawdopodobnie całość będzie wymagała odświeżenia.

Aby go użyć, utwórz instancję obiektu, a następnie ustaw imagewłaściwość (odziedziczoną z UIImageView) na obraz i overlayColorna wartość UIColor (poziomy mieszania można regulować, zmieniając wartość alfa przekazywanego koloru).

Ramin
źródło
Sugestie dotyczące dalszych działań dołączone do pierwotnego wniosku (powyżej).
Ramin
powinien dodać CGContextSaveGState (kontekst); przed zmianą trybu mieszania lub wystąpi niedomiar gstack.
willc2
D'oh, dzięki. Błąd kopiowania / wklejania. Naprawiłem to we fragmencie kodu.
Ramin,
10
(Jak stwierdzono również w innej odpowiedzi tutaj). Uwagi specjalne Klasa UIImageView jest zoptymalizowana pod kątem rysowania obrazów na ekranie. UIImageView nie wywoła drawRect: podklasy. Jeśli podklasa wymaga niestandardowego kodu rysunku, zaleca się użycie UIView jako klasy bazowej. Oznacza to, że nie można podklasa UIImageView i oczekują drawRect się nazywać, w ogóle .
Jonny
Cześć, niestety próbowałem kodu i nie zadziałał: /. Zobacz odpowiedź poniżej @mpstx, która mówi, że drawRect nie zostanie wywołany z UIImageView, a zamiast tego powinieneś użyć UIView (z UIImage), który działał dobrze dla mnie.
jklp
77

W iOS7 wprowadzili właściwość tintColor na UIImageView i renderingMode na UIImage. Aby zabarwić UIImage na iOS7, wszystko, co musisz zrobić, to:

UIImageView* imageView = …
UIImage* originalImage = …
UIImage* imageForRendering = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
imageView.image = imageForRendering;
imageView.tintColor = [UIColor redColor]; // or any color you want to tint it with
Toland Hon
źródło
18
Jest to zdecydowanie najlepszy i najłatwiejszy sposób na iOS 7, jeśli jest to zachowanie odcienia, którego szukasz. Ale ten kod nakłada tintę tak, jakbyś używał w Photoshopie 100% nieprzezroczystego trybu mieszania „Nakładka”, a nie trybu mieszania „Pomnóż”, którego szukało oryginalne pytanie.
smileyborg
26

Chciałem zabarwić obraz za pomocą alfa i utworzyłem następującą klasę. Daj mi znać, jeśli napotkasz jakieś problemy.

Nazwałam swoją klasę CSTintedImageViewi dziedziczy po niej, UIViewponieważ UIImageViewnie wywołuje drawRect:metody, jak wspomniano w poprzednich odpowiedziach. Ustawiłem wyznaczony inicjator podobny do tego, który można znaleźć w UIImageViewklasie.

Stosowanie:

CSTintedImageView * imageView = [[CSTintedImageView alloc] initWithImage:[UIImage imageNamed:@"image"]];
imageView.tintColor = [UIColor redColor];

CSTintedImageView.h

@interface CSTintedImageView : UIView

@property (strong, nonatomic) UIImage * image;
@property (strong, nonatomic) UIColor * tintColor;

- (id)initWithImage:(UIImage *)image;

@end

CSTintedImageView.m

#import "CSTintedImageView.h"

@implementation CSTintedImageView

@synthesize image=_image;
@synthesize tintColor=_tintColor;

- (id)initWithImage:(UIImage *)image
{
    self = [super initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)];

    if(self)
    {
        self.image = image;

        //set the view to opaque
        self.opaque = NO;
    }

    return self;
}

- (void)setTintColor:(UIColor *)color
{
    _tintColor = color;

    //update every time the tint color is set
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();    

    //resolve CG/iOS coordinate mismatch
    CGContextScaleCTM(context, 1, -1);
    CGContextTranslateCTM(context, 0, -rect.size.height);

    //set the clipping area to the image
    CGContextClipToMask(context, rect, _image.CGImage);

    //set the fill color
    CGContextSetFillColor(context, CGColorGetComponents(_tintColor.CGColor));
    CGContextFillRect(context, rect);    

    //blend mode overlay
    CGContextSetBlendMode(context, kCGBlendModeOverlay);

    //draw the image
    CGContextDrawImage(context, rect, _image.CGImage);    
}

@end
Szlagier
źródło
2
Dziękuję Ci. To było pierwsze rozwiązanie w tym pytaniu, które właściwie wspierało przezroczystość obrazu.
theTRON
Kiedy próbuję użyć tej odpowiedzi, moje tło (obszar, który powinien być przezroczysty) jest zamiast tego czarne. Czy wiesz, co mogłem tu zrobić źle?
livingtech
Nieważne! (Miałem ustawiony kolor tła w scenorysie! D'oh!) To rozwiązanie jest niesamowite !!!
livingtech
2
To rozwiązanie działa dobrze, chociaż lepsze wyniki uzyskałem renderując najpierw obraz, a następnie wypełniając kolor na górze kCGBlendModeColor.
Jeremy Fuller
metoda drawRect: jest naprawdę „mięsem” tego rozwiązania. Reszta to kwestia gustu / stylu. Dzięki! Pracował dla mnie!
horseshoe7
26

Tylko krótkie wyjaśnienie (po kilku badaniach na ten temat). Apple doc tutaj wyraźnie stwierdza, że:

UIImageViewKlasa jest zoptymalizowany rysować swoje obrazy na ekranie. UIImageViewnie wywołuje drawRect:metody swoich podklas. Jeśli podklasa wymaga uwzględnienia niestandardowego kodu rysunku, należy UIViewzamiast tego podklasować klasę.

więc nawet nie trać czasu na próby przesłonięcia tej metody w UIImageViewpodklasie. UIViewZamiast tego zacznij od .

mattorb
źródło
9
Och, jak bardzo chciałbym wiedzieć o tym sześć miesięcy temu. Powinni umieścić ostrzeżenie w 72-punktowej czcionce, czerwonej z migającą etykietą.
willc2
Wiem, że masz na myśli ... Straciłem pół dnia na zabawie.
mattorb
Dzięki @mpstx ... Również umieścić tę linię dokumentacji odpowiedź w cudzysłowie ... Apple powinno zrobić to samo w dokumentacji ... :-)
Krishnabhadra
5

Może to być bardzo przydatne: PhotoshopFramework to potężna biblioteka do manipulowania obrazami w Objective-C. Został on opracowany, aby zapewnić te same funkcje, które znają użytkownicy Adobe Photoshop. Przykłady: Ustaw kolory za pomocą RGB 0-255, zastosuj filtry mieszające, transformacje ...

Jest open source, tutaj jest link do projektu: https://sourceforge.net/projects/photoshopframew/

Zespół deweloperski SEQOY
źródło
2
Wygląda interesująco, ale na co zamierzasz zmienić nazwę, gdy dowiedzą się o tym prawnicy Adobe?
Kristopher Johnson,
1
Dowiemy się później. I tak nie jest produktem komercyjnym, to swego rodzaju „kryptonim”. Jest również biblioteką wewnętrzną.
SEQOY Development Team
+1 za zasugerowanie biblioteki (nawet jeśli jest to niewielka autopromocja) i niepodejmowanie kroków w celu ponownego wynalezienia koła.
Tim
4
UIImage * image = mySourceImage;
UIColor * color = [UIColor yellowColor];  
UIGraphicsBeginImageContext(image.size);
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height) blendMode:kCGBlendModeNormal alpha:1];
UIBezierPath * path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, image.size.width, image.size.height)];
[color setFill];
[path fillWithBlendMode:kCGBlendModeMultiply alpha:1]; //look up blending modes for your needs
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//use newImage for something
gngrwzrd
źródło
Musiałem zabarwić UIImage, ale nie użyć go wewnątrz UIImageView, ale jako obraz tła dla UINavigationBar, a twój kod załatwił sprawę. Dzięki.
ncerezo,
3

Oto inny sposób na zaimplementowanie barwienia obrazu, zwłaszcza jeśli używasz już QuartzCore do czegoś innego. To była moja odpowiedź na podobne pytanie .

Importuj QuartzCore:

#import <QuartzCore/QuartzCore.h>

Utwórz przezroczystą warstwę CAL i dodaj ją jako podwarstwę dla obrazu, który chcesz odcień:

CALayer *sublayer = [CALayer layer];
[sublayer setBackgroundColor:[UIColor whiteColor].CGColor];
[sublayer setOpacity:0.3];
[sublayer setFrame:toBeTintedImage.frame];
[toBeTintedImage.layer addSublayer:sublayer];

Dodaj QuartzCore do swojej listy projektów Framework (jeśli jeszcze go tam nie ma), w przeciwnym razie otrzymasz takie błędy kompilatora:

Undefined symbols for architecture i386: "_OBJC_CLASS_$_CALayer"
lekksi
źródło
2

Dla tych z Was, którzy próbują podklasować klasę UIImageView i utkną w „drawRect: nie jest wywoływana”, powinniście zauważyć, że zamiast tego powinniście utworzyć podklasę klasy UIView, ponieważ w przypadku klas UIImageView metoda „drawRect:” nie jest wywoływana. Przeczytaj więcej tutaj: drawRect nie jest wywoływany w mojej podklasie UIImageView

pierre
źródło
0

Jedyną rzeczą, o której mogę pomyśleć, byłoby utworzenie prostokątnego, przeważnie przezroczystego widoku o pożądanym kolorze i nałożenie go na widok obrazu, dodając go jako podwidok. Nie jestem pewien, czy to naprawdę zabarwi obraz w sposób, w jaki sobie wyobrażasz, nie jestem pewien, jak włamać się do obrazu i selektywnie zastąpić niektóre kolory innymi ... brzmi dla mnie dość ambitnie.

Na przykład:

UIImageView *yourPicture = (however you grab the image);
UIView *colorBlock = [[UIView alloc] initWithFrame:yourPicture.frame];
//Replace R G B and A with values from 0 - 1 based on your color and transparency
colorBlock.backgroundColor = [UIColor colorWithRed:R green:G blue:B alpha:A];
[yourPicture addSubView:colorBlock];

Dokumentacja dla UIColor:

colorWithRed:green:blue:alpha:

Creates and returns a color object using the specified opacity and RGB component values.

+ (UIColor *)colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha

Parameters

red    - The red component of the color object, specified as a value from 0.0 to 1.0.

green  - The green component of the color object, specified as a value from 0.0 to 1.0.

blue   - The blue component of the color object, specified as a value from 0.0 to 1.0.



alpha  - The opacity value of the color object, specified as a value from 0.0 to 1.0.

Return Value

The color object. The color information represented by this object is in the device RGB colorspace. 
Tim Bowen
źródło
Wydaje się, że Core Graphics może stosować tryby zginania. Jak brzmi pytanie.
willc2
0

Możesz także rozważyć buforowanie skomponowanego obrazu dla wydajności i po prostu renderowanie go w drawRect :, a następnie zaktualizować go, jeśli brudna flaga jest rzeczywiście brudna. Chociaż możesz to często zmieniać, mogą wystąpić przypadki, w których nadchodzą losowania i nie jesteś brudny, więc możesz po prostu odświeżyć się z pamięci podręcznej. Jeśli pamięć jest większym problemem niż wydajnością, możesz to zignorować :)

Steve Riggins
źródło
To właśnie robi dla Ciebie podstawowy CALayer. Nie ma potrzeby ręcznego buforowania rysunku widoku.
Dennis Munsie
0

Dla Swift 2.0,

    let image: UIImage! = UIGraphicsGetImageFromCurrentImageContext()

    imgView.image = imgView.image!.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)

    imgView.tintColor = UIColor(red: 51/255.0, green: 51/255.0, blue: 

51/255.0, alpha: 1.0)
tak
źródło
0

W tym celu zrobiłem makra:

#define removeTint(view) \
if ([((NSNumber *)[view.layer valueForKey:@"__hasTint"]) boolValue]) {\
for (CALayer *layer in [view.layer sublayers]) {\
if ([((NSNumber *)[layer valueForKey:@"__isTintLayer"]) boolValue]) {\
[layer removeFromSuperlayer];\
break;\
}\
}\
}

#define setTint(view, tintColor) \
{\
if ([((NSNumber *)[view.layer valueForKey:@"__hasTint"]) boolValue]) {\
removeTint(view);\
}\
[view.layer setValue:@(YES) forKey:@"__hasTint"];\
CALayer *tintLayer = [CALayer new];\
tintLayer.frame = view.bounds;\
tintLayer.backgroundColor = [tintColor CGColor];\
[tintLayer setValue:@(YES) forKey:@"__isTintLayer"];\
[view.layer addSublayer:tintLayer];\
}

Aby skorzystać, wystarczy zadzwonić:

setTint(yourView, yourUIColor);
//Note: include opacity of tint in your UIColor using the alpha channel (RGBA), e.g. [UIColor colorWithRed:0.5f green:0.0 blue:0.0 alpha:0.25f];

Aby usunąć odcień wystarczy zadzwonić:

removeTint(yourView);
Albert Renshaw
źródło