¿Cómo teñiría una imagen mediante programación en iOS?

87

Me gustaría teñir una imagen con una referencia de color. Los resultados deberían verse como el modo de fusión Multiplicar en Photoshop, donde los blancos se reemplazarían con tinte :

texto alternativo

Cambiaré el valor del color continuamente.

Seguimiento: pondría el código para hacer esto en el método drawRect: de mi ImageView, ¿verdad?

Como siempre, un fragmento de código sería de gran ayuda para mi comprensión, a diferencia de un enlace.

Actualización: Subclasificación de un UIImageView con el código que sugirió Ramin .

Pongo esto en viewDidLoad: de mi controlador de vista:

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

Veo la imagen, pero no está teñida. También intenté cargar otras imágenes, configurar la imagen en IB y llamar a setNeedsDisplay: en mi controlador de vista.

Actualización : drawRect: no se está llamando.

Actualización final: encontré un proyecto antiguo que tenía un imageView configurado correctamente para poder probar el código de Ramin y ¡funciona como un encanto!

Actualización final, final:

Para aquellos de ustedes que recién están aprendiendo sobre Core Graphics, aquí está lo más simple que podría funcionar.

En su UIView subclasificada:

- (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
fuente
Agregué el fragmento antes de publicarlo en un código antiguo de drawRect que tenía y funcionó bien. Es posible que desee probarlo sin la llamada [super viewDidLoad] (o moverlo arriba). También verifique dos veces para asegurarse de que quien esté asignando este objeto esté asignando la versión derivada, no vanilla UIImageView (es decir, si se carga en una plumilla, el asignador, etc.).
Ramin
Otra sugerencia si lo anterior no funciona: en lugar de subclasificar la subclase de UIImageView, agregue una UIView simple y agregue overlayColor y una propiedad de UIImage llamada 'imagen' que puede establecer. Luego coloque drawRect allí. Al código de drawRect no le importa si el valor de 'imagen' proviene de UIImageView o de una propiedad que ha definido.
Ramin
¿No fallará esto si la imagen está con alfa? ¿Qué pasa si quisiera teñir solo la parte dibujada de la imagen? (¿Al igual que UIButton?)
Sunil Chauhan

Respuestas:

45

Primero querrá subclasificar UIImageView y anular el método drawRect. Su clase necesita una propiedad UIColor (llamémosla overlayColor) para mantener el color de mezcla y un setter personalizado que fuerza un redibujo cuando cambia el color. Algo como esto:

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

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

En el método drawRect, primero querrá dibujar la imagen y luego superponerla con un rectángulo relleno con el color que desea junto con el modo de fusión adecuado, algo como esto:

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

Por lo general, para optimizar el dibujo, restringiría el dibujo real solo al área pasada a drawRect, pero como la imagen de fondo debe volver a dibujarse cada vez que cambia el color, es probable que sea necesario actualizar todo.

Para usarlo, cree una instancia del objeto y luego establezca la imagepropiedad (heredada de UIImageView) en la imagen y overlayColoren un valor de UIColor (los niveles de mezcla se pueden ajustar cambiando el valor alfa del color que transmite).

Ramin
fuente
Sugerencias de seguimiento adjuntas a la solicitud original (arriba).
Ramin
debería agregar CGContextSaveGState (contexto); antes de cambiar el modo de fusión o obtendrá un subdesbordamiento de gstack.
willc2
Oh, gracias. Error de copiar / pegar. Lo he arreglado en el fragmento de código.
Ramin
10
(Como también se indica en otra respuesta aquí) Consideraciones especiales La clase UIImageView está optimizada para dibujar sus imágenes en la pantalla. UIImageView no llamará a drawRect: una subclase. Si su subclase necesita un código de dibujo personalizado, se recomienda que utilice UIView como clase base. Esto significa que no puede subclase UIImageView y esperar que se llame a drawRect, en absoluto .
Jonny
Hola, desafortunadamente probé el código y no funcionó: /. Vea la respuesta a continuación por @mpstx, que dice que drawRect no será llamado desde un UIImageView y, en su lugar, debería usar un UIView (con un UIImage) que funcionó bien para mí.
jklp
77

En iOS7, han introducido la propiedad tintColor en UIImageView y renderingMode en UIImage. Para teñir un UIImage en iOS7, todo lo que tienes que hacer es:

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
fuente
18
Esta es definitivamente la mejor y más fácil forma de usar iOS 7 si este es el comportamiento de tinte que está buscando. Pero este código aplica un tinte como si estuviera usando un modo de fusión "Superposición" 100% opaco en Photoshop, no el modo de fusión "Multiplicar" que estaba buscando la pregunta original.
smileyborg
26

Quería teñir una imagen con alfa y creé la siguiente clase. Por favor, avíseme si encuentra algún problema con él.

He nombrado mi clase CSTintedImageViewy la hereda UIViewya UIImageViewque no llama al drawRect:método, como se mencionó en respuestas anteriores. He configurado un inicializador designado similar al que se encuentra en la UIImageViewclase.

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
Persona maravillosa
fuente
2
Gracias. Esta fue la primera solución en esta pregunta que realmente admitió la transparencia de la imagen correctamente.
theTRON
Cuando trato de usar esta respuesta, mi fondo (el área que debería ser transparente) es negro. ¿Sabes qué podría estar haciendo mal aquí?
livingtech
¡No importa! (¡Tenía un color de fondo establecido en el guión gráfico! ¡D'oh!) ¡¡¡Esta solución es increíble !!!
livingtech
2
Esta solución funciona bien, aunque obtuve mejores resultados al renderizar la imagen primero y luego llenar el color en la parte superior con kCGBlendModeColor.
Jeremy Fuller
el método drawRect: es realmente la "carne" de esta solución. El resto es cuestión de gusto / estilo. ¡Gracias! ¡Trabajó para mi!
herradura 7
26

Solo una aclaración rápida (después de investigar un poco sobre este tema). El documento de Apple aquí establece claramente que:

La UIImageViewclase está optimizada para dibujar sus imágenes en la pantalla. UIImageViewno llama al drawRect:método de sus subclases. Si su subclase necesita incluir un código de dibujo personalizado, debe subclasificar la UIViewclase en su lugar.

así que ni siquiera pierda el tiempo intentando anular ese método en una UIImageViewsubclase. Comience con en su UIViewlugar.

mattorb
fuente
9
Oh, cómo desearía saber esto hace seis meses. Deben poner la advertencia en letra de 72 puntos, roja con la etiqueta intermitente.
willc2
Sé que quieres decir ... que perdí medio día jugando con eso.
mattorb
gracias @mpstx ... También ponga esa línea de documentación en la respuesta entre comillas ... Apple debería haber hecho lo mismo en la documentación ... :-)
Krishnabhadra
5

Esto podría ser muy útil: PhotoshopFramework es una poderosa biblioteca para manipular imágenes en Objective-C. Esto fue desarrollado para traer las mismas funcionalidades que los usuarios de Adobe Photoshop conocen. Ejemplos: Establecer colores usando RGB 0-255, aplicar filtros de mezcla, transformaciones ...

Es de código abierto, aquí está el enlace del proyecto: https://sourceforge.net/projects/photoshopframew/

Equipo de desarrollo de SEQOY
fuente
2
Parece interesante, pero ¿a qué cambiará el nombre cuando los abogados de Adobe se enteren?
Kristopher Johnson
1
Lo averiguaremos más tarde. No es un producto comercial de todos modos, es una especie de "nombre en clave". También es una biblioteca interna.
Equipo de desarrollo de SEQOY
+1 por sugerir una biblioteca (incluso si es un poco de autopromoción) y no dar pasos para reinventar la rueda.
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
fuente
Necesitaba teñir un UIImage, pero no usarlo dentro de un UIImageView, sino como imagen de fondo para un UINavigationBar, y su código funcionó. Gracias.
ncerezo
3

Aquí hay otra forma de implementar el tinte de imagen, especialmente si ya está utilizando QuartzCore para otra cosa. Esta fue mi respuesta a una pregunta similar .

Importar QuartzCore:

#import <QuartzCore/QuartzCore.h>

Cree CALayer transparente y agréguelo como una subcapa para la imagen que desea teñir:

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

Agregue QuartzCore a la lista de Framework de su proyecto (si aún no está allí), de lo contrario, obtendrá errores de compilación como este:

Undefined symbols for architecture i386: "_OBJC_CLASS_$_CALayer"
lekksi
fuente
2

Para aquellos de ustedes que intentan subclasificar una clase UIImageView y se quedan atascados en "drawRect: no se está llamando", tenga en cuenta que debe subclasificar una clase UIView en su lugar, porque para las clases UIImageView, el método "drawRect:" no se llama. Lea más aquí: no se llama a drawRect en mi subclase de UIImageView

pierre
fuente
0

Lo único que se me ocurre es crear una vista rectangular mayormente transparente con el color deseado y colocarla sobre la vista de la imagen agregándola como una subvista. Sin embargo, no estoy seguro de si esto realmente teñirá la imagen de la manera que imagina, no estoy seguro de cómo piratear una imagen y reemplazar selectivamente ciertos colores con otros ... me suena bastante ambicioso.

Por ejemplo:

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

Documentación 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
fuente
Core Graphics parece poder aplicar modos de flexión. Cómo, es la pregunta.
willc2
0

También es posible que desee considerar almacenar en caché la imagen compuesta para el rendimiento y simplemente renderizarla en drawRect :, luego actualizarla si una bandera sucia está realmente sucia. Si bien es posible que lo cambie con frecuencia, puede haber casos en los que ingresen extracciones y no esté sucio, por lo que simplemente puede actualizar desde la caché. Si la memoria es más un problema que el rendimiento, puede ignorar esto :)

Steve Riggins
fuente
Eso es lo que el CALayer subyacente hace automáticamente por usted. No es necesario almacenar en caché el dibujo de la 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
fuente
0

Hice 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, simplemente llame a:

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

Al quitar el tinte simplemente llame a:

removeTint(yourView);
Albert Renshaw
fuente