Como eu tingiria uma imagem programaticamente no iOS?

87

Eu gostaria de colorir uma imagem com uma referência de cor. Os resultados devem ser semelhantes ao modo de mesclagem Multiply no Photoshop, onde os brancos seriam substituídos por matiz :

texto alternativo

Eu estarei mudando o valor da cor continuamente.

Acompanhamento: Eu colocaria o código para fazer isso no método drawRect: do meu ImageView, certo?

Como sempre, um trecho de código ajudaria muito no meu entendimento, ao contrário de um link.

Atualização: Subclassificação de um UIImageView com o código sugerido por Ramin .

Eu coloquei isso em viewDidLoad: do meu controlador de visualização:

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

Eu vejo a imagem, mas não está sendo tingida. Também tentei carregar outras imagens, definindo a imagem no IB e chamando setNeedsDisplay: no meu controlador de visualização.

Update : drawRect: não está sendo chamado.

Atualização final: encontrei um projeto antigo que tinha um imageView configurado corretamente para que eu pudesse testar o código do Ramin e funciona perfeitamente!

Atualização final, final:

Para aqueles que estão aprendendo sobre Core Graphics, aqui está a coisa mais simples que poderia funcionar.

Em seu UIView subclasse:

- (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
fonte
Eu adicionei o snippet antes de postar em algum código drawRect antigo que eu tinha e funcionou bem. Pode querer tentar sem a chamada [super viewDidLoad] (ou movê-lo para cima). Verifique também para ter certeza de que quem está alocando este objeto está alocando a versão derivada e não o UIImageView básico (ou seja, se ele está carregado em um bico, o alocador, etc.).
Ramin
Outra sugestão se o acima não funcionar: em vez de criar uma subclasse de UIImageView, uma subclasse de UIView simples e adicionar overlayColor e uma propriedade UIImage chamada 'imagem' que você pode definir. Em seguida, coloque o drawRect lá. O código drawRect não se importa se o valor 'image' vem de UIImageView ou de uma propriedade que você definiu.
Ramin
Isso não falhará se a imagem estiver com alfa? E se eu quiser colorir apenas a parte desenhada da imagem? (Assim como UIButton faz?)
Sunil Chauhan

Respostas:

45

Primeiro, você deseja criar uma subclasse de UIImageView e substituir o método drawRect. Sua classe precisa de uma propriedade UIColor (vamos chamá-la de overlayColor) para conter a cor de mesclagem e um configurador personalizado que força um redesenho quando a cor muda. Algo assim:

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

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

No método drawRect, você desejará desenhar a imagem primeiro e, em seguida, sobrepô-la com um retângulo preenchido com a cor desejada, juntamente com o modo de mesclagem adequado, algo como este:

- (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);
}

Normalmente, para otimizar o desenho, você restringiria o desenho real apenas à área passada para drawRect, mas como a imagem de fundo deve ser redesenhada cada vez que a cor muda, é provável que tudo precise ser atualizado.

Para usá-lo, crie uma instância do objeto e defina a imagepropriedade (herdada de UIImageView) para a imagem e overlayColorum valor UIColor (os níveis de mesclagem podem ser ajustados alterando o valor alfa da cor que você passa).

Ramin
fonte
Sugestões de acompanhamento anexadas à solicitação original (acima).
Ramin
deve adicionar CGContextSaveGState (contexto); antes de alterar o modo de mesclagem ou obter um underflow gstack.
willc2
D'oh, obrigado. Erro ao copiar / colar. Eu consertei no trecho de código.
Ramin
10
(Como também afirmado em outra resposta aqui) Considerações especiais A classe UIImageView é otimizada para desenhar suas imagens na tela. UIImageView não chamará drawRect: uma subclasse. Se a sua subclasse precisa de código de desenho personalizado, é recomendável usar UIView como classe base. Isto significa que você não pode subclasse UIImageView e esperam drawRect a ser chamado, em tudo .
Jonny
Olá, infelizmente tentei o código e não funcionou: /. Veja a resposta abaixo por @mpstx, que diz que drawRect não será chamado de um UIImageView e, em vez disso, você deve usar um UIView (com um UIImage) que funcionou bem para mim.
jklp
77

No iOS7, eles introduziram a propriedade tintColor em UIImageView e renderingMode em UIImage. Para colorir um UIImage no iOS7, tudo que você precisa fazer é:

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
fonte
18
Esta é definitivamente a melhor e mais fácil maneira de usar o iOS 7 se esse for o comportamento de tonalidade que você está procurando. Mas esse código aplica uma tonalidade como se você estivesse usando um modo de mesclagem "Overlay" 100% opaco no Photoshop, não o modo de mesclagem "Multiply" que a pergunta original estava procurando.
smileyborg de
26

Eu queria tingir uma imagem com alfa e criei a seguinte classe. Por favor, deixe-me saber se você encontrar algum problema com isso.

Eu nomeei minha classe CSTintedImageViewe ela herda de UIViewuma vez UIImageViewque não chama o drawRect:método, como mencionado nas respostas anteriores. Eu configurei um inicializador designado semelhante ao encontrado na UIImageViewclasse.

Uso:

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
Stunner
fonte
2
Obrigado. Esta foi a primeira solução nesta questão que realmente suportou a transparência da imagem de forma adequada.
theTRON
Quando tento usar essa resposta, meu fundo (a área que deveria ser transparente) é preto. Você sabe o que posso estar fazendo de errado aqui?
livingtech
Deixa pra lá! (Eu tinha uma cor de fundo definida no storyboard! D'oh!) Esta solução é incrível !!!
livingtech
2
Essa solução funciona bem, embora eu tenha obtido melhores resultados ao renderizar a imagem primeiro e, em seguida, preencher a cor na parte superior com kCGBlendModeColor.
Jeremy Fuller
o método drawRect: é realmente a "carne" dessa solução. O resto é uma questão de gosto / estilo. Obrigado! Funcionou para mim!
ferradura7
26

Apenas um rápido esclarecimento (após algumas pesquisas sobre o assunto). O documento da Apple aqui afirma claramente que:

A UIImageViewclasse é otimizada para desenhar suas imagens para a exibição. UIImageViewnão chama o drawRect:método de suas subclasses. Se sua subclasse precisa incluir código de desenho personalizado, você deve criar uma subclasse da UIViewclasse.

então nem perca tempo tentando sobrescrever esse método em uma UIImageViewsubclasse. Em UIViewvez disso, comece com .

Mattorb
fonte
9
Oh, como eu gostaria de saber disso há seis meses. Eles devem colocar o aviso no tipo de 72 pontos, vermelho com a etiqueta intermitente.
willc2
Eu sei que você quer dizer ... Eu perdi meio dia brincando com isso.
mattorb 01 de
obrigado @mpstx ... Coloque também essa linha de documentação na resposta entre aspas ... A Apple deveria ter feito o mesmo na documentação ... :-)
Krishnabhadra
5

Isso pode ser muito útil: PhotoshopFramework é uma biblioteca poderosa para manipular imagens em Objective-C. Ele foi desenvolvido para trazer as mesmas funcionalidades que os usuários do Adobe Photoshop estão familiarizados. Exemplos: definir cores usando RGB 0-255, aplicar filtros de mistura, transformações ...

É código aberto, aqui está o link do projeto: https://sourceforge.net/projects/photoshopframew/

Equipe de Desenvolvimento SEQOY
fonte
2
Parece interessante, mas como você mudará o nome quando os advogados da Adobe descobrirem?
Kristopher Johnson,
1
Descobriremos mais tarde. Não é um produto comercial de qualquer maneira, é uma espécie de "codinome". Também é uma biblioteca interna.
SEQOY Development Team,
+1 por sugerir uma biblioteca (mesmo que seja uma pequena autopromoção), e não dar passos para reinventar a roda.
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
fonte
Eu precisava colorir um UIImage, mas não usá-lo dentro de um UIImageView, mas como uma imagem de plano de fundo para um UINavigationBar, e seu código fez o truque. Obrigado.
ncerezo
3

Aqui está outra maneira de implementar o tingimento de imagem, especialmente se você já estiver usando o QuartzCore para outra coisa. Esta foi a minha resposta para uma pergunta semelhante .

Importar QuartzCore:

#import <QuartzCore/QuartzCore.h>

Crie CALayer transparente e adicione-o como uma subcamada para a imagem que você deseja colorir:

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

Adicione QuartzCore à lista de frameworks de seus projetos (se ainda não estiver lá), caso contrário, você obterá erros de compilador como este:

Undefined symbols for architecture i386: "_OBJC_CLASS_$_CALayer"
Lekksi
fonte
2

Para aqueles de vocês que tentam criar uma subclasse de uma classe UIImageView e ficam presos em "drawRect: não está sendo chamado", observe que você deve criar uma subclasse de uma classe UIView, porque para classes UIImageView, o método "drawRect:" não é chamado. Leia mais aqui: drawRect não sendo chamado em minha subclasse de UIImageView

Pierre
fonte
0

A única coisa que posso pensar seria em criar uma visualização retangular quase transparente com a cor desejada e colocá-la sobre a visualização da imagem adicionando-a como uma subvisualização. Não tenho certeza se isso realmente tingirá a imagem da maneira que você imagina, porém, não tenho certeza de como você invadiria uma imagem e seletivamente substituir certas cores por outras ... parece muito ambicioso para mim.

Por exemplo:

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];

Documentação para 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
fonte
Core Graphics parece ser capaz de aplicar modos de dobra. Como, é a questão.
willc2
0

Além disso, você pode querer considerar o armazenamento em cache da imagem composta para desempenho e apenas renderizá-la em drawRect :, em seguida, atualizá-la se um sinalizador sujo estiver realmente sujo. Embora você possa alterá-lo com frequência, pode haver casos em que os empates estão chegando e você não está sujo, então você pode simplesmente atualizar do cache. Se a memória é mais um problema do que o desempenho, você pode ignorar isso :)

Steve Riggins
fonte
Isso é o que o CALayer básico faz por você automaticamente. Não há necessidade de armazenar em cache o desenho da vista manualmente.
Dennis Munsie
0

Para 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)
yazh
fonte
0

Fiz macros para este propósito:

#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];\
}

Para usar, basta ligar:

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];

Ao remover a tinta, basta chamar:

removeTint(yourView);
Albert Renshaw
fonte