Come posso colorare un'immagine a livello di programmazione su iOS?


87

Vorrei colorare un'immagine con un riferimento di colore. I risultati dovrebbero assomigliare al metodo di fusione Moltiplica in Photoshop, dove i bianchi sarebbero stati sostituiti con la tinta :

testo alternativo

Cambierò continuamente il valore del colore.

Follow up: metterei il codice per farlo nel metodo drawRect: del mio ImageView, giusto?

Come sempre, uno snippet di codice sarebbe di grande aiuto nella mia comprensione, al contrario di un collegamento.

Aggiornamento: creazione di sottoclassi di una UIImageView con il codice suggerito da Ramin .

Lo metto in viewDidLoad: del mio controller di visualizzazione:

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

Vedo l'immagine, ma non viene colorata. Ho anche provato a caricare altre immagini, impostare l'immagine in IB e chiamare setNeedsDisplay: nel mio controller di visualizzazione.

Aggiornamento : drawRect: non viene chiamato.

Aggiornamento finale: ho trovato un vecchio progetto che aveva un imageView impostato correttamente in modo da poter testare il codice di Ramin e funziona a meraviglia!

Ultimo aggiornamento finale:

Per quelli di voi che stanno appena imparando a conoscere Core Graphics, ecco la cosa più semplice che potrebbe funzionare.

Nella tua sottoclasse 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
}

Ho aggiunto lo snippet prima di postare su un vecchio codice drawRect che avevo in giro e ha funzionato bene. Potrebbe volerlo provare senza la chiamata [super viewDidLoad] (o spostarlo sopra). Inoltre ricontrolla per assicurarti che chiunque stia allocando questo oggetto stia allocando la versione derivata e non UIImageView vanilla (cioè se è caricata in un pennino, l'allocatore, ecc.).
Ramin

Un altro suggerimento se quanto sopra non funziona: invece di creare una sottoclasse UIImageView una semplice UIView e aggiungere sia overlayColor che una proprietà UIImage chiamata 'image' che puoi impostare. Quindi metti il ​​drawRect lì dentro. Al codice drawRect non interessa se il valore "image" proviene da UIImageView o da una proprietà che hai definito.
Ramin

Non fallirà se l'immagine è con alpha? E se volessi colorare solo la parte disegnata dell'immagine? (Proprio come fa UIButton?)
Sunil Chauhan

Risposte:


45

Per prima cosa dovrai creare una sottoclasse UIImageView e sovrascrivere il metodo drawRect. La tua classe ha bisogno di una proprietà UIColor (chiamiamola overlayColor) per contenere il colore di fusione e un setter personalizzato che forza un ridisegno quando il colore cambia. Qualcosa come questo:

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

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

Nel metodo drawRect dovrai prima disegnare l'immagine, quindi sovrapporla con un rettangolo riempito con il colore che desideri insieme alla modalità di fusione corretta, qualcosa del genere:

- (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 per ottimizzare il disegno restringeresti il ​​disegno effettivo solo all'area passata a drawRect, ma poiché l'immagine di sfondo deve essere ridisegnata ogni volta che il colore cambia, è probabile che l'intera cosa necessiti di essere rinfrescata.

Per usarlo, crea un'istanza dell'oggetto, quindi imposta la imageproprietà (ereditata da UIImageView) sull'immagine e overlayColorsu un valore UIColor (i livelli di fusione possono essere regolati modificando il valore alfa del colore trasmesso).


Suggerimenti di follow-up allegati alla richiesta originale (sopra).
Ramin

dovrebbe aggiungere CGContextSaveGState (contesto); prima di cambiare la modalità di fusione o ottieni un underflow di gstack.
willc2

D'oh, grazie. Errore di copia / incolla. L'ho risolto nello snippet di codice.
Ramin

10
(Come affermato anche in un'altra risposta qui) Considerazioni speciali La classe UIImageView è ottimizzata per disegnare le sue immagini sul display. UIImageView non chiamerà drawRect: una sottoclasse. Se la tua sottoclasse necessita di codice di disegno personalizzato, si consiglia di utilizzare UIView come classe base. Questo significa che non si può sottoclasse UIImageView e si aspettano drawRect ad essere chiamati, a tutti .
Jonny

Ciao, purtroppo ho provato il codice e non ha funzionato: /. Vedi la risposta sotto di @mpstx, che dice che drawRect non verrà chiamato da un UIImageView e invece, dovresti usare un UIView (con un UIImage) che ha funzionato bene per me.
jklp

77

In iOS7, hanno introdotto la proprietà tintColor su UIImageView e renderingMode su UIImage. Per colorare un UIImage su iOS7, tutto ciò che devi fare è:

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

18
Questo è sicuramente il modo migliore e più semplice per andare su iOS 7 se questo è il comportamento della tinta che stai cercando. Ma questo codice applica una tinta come se si stesse utilizzando un metodo di fusione "Sovrapposizione" opaco al 100% in Photoshop, non il metodo di fusione "Moltiplica" che la domanda originale stava cercando.
smileyborg

26

Volevo colorare un'immagine con alfa e ho creato la seguente classe. Per favore fatemi sapere se trovate problemi con esso.

Ho chiamato la mia classe CSTintedImageViewed eredita da UIViewpoiché UIImageViewnon chiama il drawRect:metodo, come menzionato nelle risposte precedenti. Ho impostato un inizializzatore designato simile a quello trovato nella UIImageViewclasse.

Utilizzo:

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

2
Grazie. Questa è stata la prima soluzione a questa domanda che effettivamente supportava correttamente la trasparenza dell'immagine.
theTRON

Quando provo a usare questa risposta, il mio sfondo (l'area che dovrebbe essere trasparente) è invece nero. Sai cosa potrei fare di sbagliato qui?
livingtech

Non importa! (Avevo un colore di sfondo impostato nello storyboard! D'oh!) Questa soluzione è fantastica !!!
livingtech

2
Questa soluzione funziona bene, anche se ho ottenuto risultati migliori eseguendo prima il rendering dell'immagine, quindi riempiendo il colore in alto con kCGBlendModeColor.
Jeremy Fuller

il metodo drawRect: è davvero la "carne" di questa soluzione. Il resto è una questione di gusto / stile. Grazie! Ha funzionato per me!
ferro di cavallo 7

26

Solo una rapida precisazione (dopo qualche ricerca su questo argomento). Il documento Apple qui afferma chiaramente che:

La UIImageViewclasse è ottimizzata per disegnare le sue immagini sul display. UIImageViewnon chiama il drawRect:metodo delle sue sottoclassi. Se la tua sottoclasse deve includere codice di disegno personalizzato, dovresti invece sottoclassare la UIViewclasse.

quindi non perdere nemmeno tempo a tentare di sovrascrivere quel metodo in una UIImageViewsottoclasse. Inizia UIViewinvece con .


9
Oh, come vorrei saperlo sei mesi fa. Dovrebbero mettere l'avviso in un tipo di 72 punti, rosso con il tag lampeggiante.
willc2

So che intendi ... ho perso mezza giornata a scherzarci sopra.
mattorb

grazie @mpstx ... Metti anche quella riga di documentazione nella risposta tra virgolette ... Apple avrebbe dovuto fare lo stesso nella documentazione ... :-)
Krishnabhadra

5

Questo potrebbe essere molto utile: PhotoshopFramework è una potente libreria per manipolare le immagini su Objective-C. Questo è stato sviluppato per portare le stesse funzionalità che gli utenti di Adobe Photoshop conoscono. Esempi: impostare i colori utilizzando RGB 0-255, applicare filtri di fusione, trasformazioni ...

È open source, ecco il link del progetto: https://sourceforge.net/projects/photoshopframew/


2
Sembra interessante, ma come cambierete il nome quando gli avvocati di Adobe lo scopriranno?
Kristopher Johnson,

1
Lo scopriremo più tardi. Non è comunque un prodotto commerciale, è una sorta di "nome in codice". Inoltre è una libreria interna.
SEQOY Development Team,

+1 per aver suggerito una libreria (anche se è un po 'di autopromozione) e non aver dato passaggi per reinventare la ruota.
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

Avevo bisogno di colorare un UIImage, ma non per usarlo all'interno di un UIImageView, ma come immagine di sfondo per un UINavigationBar, e il tuo codice ha fatto il trucco. Grazie.
ncerezo

3

Ecco un altro modo per implementare la colorazione dell'immagine, soprattutto se stai già utilizzando QuartzCore per qualcos'altro. Questa è stata la mia risposta a una domanda simile .

Importa QuartzCore:

#import <QuartzCore/QuartzCore.h>

Crea un CALayer trasparente e aggiungilo come sottolivello per l'immagine che desideri colorare:

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

Aggiungi QuartzCore all'elenco Framework dei tuoi progetti (se non è già presente), altrimenti otterrai errori del compilatore come questo:

Undefined symbols for architecture i386: "_OBJC_CLASS_$_CALayer"


0

L'unica cosa a cui riesco a pensare sarebbe creare una vista rettangolare per lo più trasparente con il colore desiderato e posizionarla sulla vista dell'immagine aggiungendola come vista secondaria. Non sono sicuro che questo colorerà davvero l'immagine nel modo in cui immagini, tuttavia, non sono sicuro di come potresti hackerare un'immagine e sostituire selettivamente determinati colori con altri ... mi sembra piuttosto ambizioso.

Per esempio:

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

Documentazione per 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. 

Core Graphics sembra essere in grado di applicare modalità di piegatura. Come, è la domanda.
willc2

0

Inoltre potresti prendere in considerazione la memorizzazione nella cache dell'immagine composta per le prestazioni e il semplice rendering in drawRect :, quindi aggiornarla se un flag sporco è effettivamente sporco. Anche se potresti cambiarlo spesso, potrebbero esserci casi in cui arrivano i pareggi e non sei sporco, quindi puoi semplicemente aggiornare dalla cache. Se la memoria è più un problema che le prestazioni, puoi ignorarlo :)


Questo è ciò che il CALayer sottostante fa automaticamente per te. Non è necessario memorizzare manualmente il disegno della vista nella cache.
Dennis Munsie


0

Per 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)

0

Ho creato delle macro per questo scopo:

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

Per utilizzare, è sufficiente chiamare:

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

Quando rimuovi la tinta chiama semplicemente:

removeTint(yourView);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.