Comment teinter une image par programmation sur iOS?

87

Je voudrais teinter une image avec une référence de couleur. Les résultats devraient ressembler au mode de fusion Multiplier dans Photoshop, où les blancs seraient remplacés par une teinte :

texte alternatif

Je changerai la valeur de la couleur en continu.

Suivi: je mettrais le code pour faire cela dans la méthode drawRect: de mon ImageView, non?

Comme toujours, un extrait de code aiderait grandement à ma compréhension, par opposition à un lien.

Mise à jour: sous-classer une UIImageView avec le code suggéré par Ramin .

Je mets ceci dans viewDidLoad: de mon contrôleur de vue:

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

Je vois l'image, mais elle n'est pas teintée. J'ai également essayé de charger d'autres images, de définir l'image dans IB et d'appeler setNeedsDisplay: dans mon contrôleur de vue.

Mise à jour : drawRect: n'est pas appelé.

Dernière mise à jour: j'ai trouvé un ancien projet avec une imageView correctement configurée pour que je puisse tester le code de Ramin et cela fonctionne comme un charme!

Dernière mise à jour finale:

Pour ceux d'entre vous qui viennent juste d'apprendre les Core Graphics, voici la chose la plus simple qui pourrait éventuellement fonctionner.

Dans votre UIView sous-classée:

- (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
la source
J'ai ajouté l'extrait avant de poster sur un ancien code drawRect que j'avais assis et cela a bien fonctionné. Peut vouloir l'essayer sans l'appel [super viewDidLoad] (ou le déplacer au-dessus). Vérifiez également que celui qui alloue cet objet alloue la version dérivée et non la version vanilla UIImageView (c'est-à-dire si elle est chargée dans un nib, l'allocateur, etc.).
Ramin
Une autre suggestion si ce qui précède ne fonctionne pas: au lieu de sous-classer UIImageView, sous-classez un UIView simple et ajoutez à la fois overlayColor et une propriété UIImage appelée 'image' que vous pouvez définir. Mettez ensuite le drawRect dedans. Le code drawRect ne se soucie pas de savoir si la valeur 'image' provient de UIImageView ou d'une propriété que vous avez définie.
Ramin
Cela n'échouera-t-il pas si l'image est avec alpha? Que faire si je souhaite teinter uniquement une partie dessinée de l'image? (Tout comme le fait UIButton?)
Sunil Chauhan

Réponses:

45

Vous voudrez d'abord sous-classer UIImageView et remplacer la méthode drawRect. Votre classe a besoin d'une propriété UIColor (appelons-la overlayColor) pour contenir la couleur de fusion et d'un setter personnalisé qui force un redessiner lorsque la couleur change. Quelque chose comme ça:

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

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

Dans la méthode drawRect, vous voudrez d'abord dessiner l'image, puis la superposer avec un rectangle rempli de la couleur souhaitée avec le mode de fusion approprié, quelque chose comme ceci:

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

Habituellement, pour optimiser le dessin, vous limiteriez le dessin réel à la seule zone transmise à drawRect, mais comme l'image d'arrière-plan doit être redessinée à chaque fois que la couleur change, il est probable que l'ensemble aura besoin d'être rafraîchi.

Pour l'utiliser, créez une instance de l'objet, puis définissez la imagepropriété (héritée de UIImageView) sur l'image et overlayColorsur une valeur UIColor (les niveaux de fusion peuvent être ajustés en modifiant la valeur alpha de la couleur que vous transmettez).

Ramin
la source
Suggestions de suivi jointes à la demande initiale (ci-dessus).
Ramin
devrait ajouter CGContextSaveGState (contexte); avant de changer le mode de fusion ou vous obtenez un sous-débit gstack.
willc2
D'oh, merci. Erreur de copier / coller. Je l'ai corrigé dans l'extrait de code.
Ramin
10
(Comme indiqué dans une autre réponse ici) Considérations spéciales La classe UIImageView est optimisée pour dessiner ses images sur l'écran. UIImageView n'appellera pas drawRect: une sous-classe. Si votre sous-classe a besoin d'un code de dessin personnalisé, il est recommandé d'utiliser UIView comme classe de base. Cela signifie que vous ne pouvez pas sous-classer UIImageView et vous attendre à ce que drawRect soit appelé, du tout .
Jonny
Salut, malheureusement, j'ai essayé le code et cela n'a pas fonctionné: /. Voir la réponse ci-dessous par @mpstx, qui dit que drawRect ne sera pas appelé à partir d'un UIImageView et à la place, vous devriez utiliser un UIView (avec une UIImage) qui a bien fonctionné pour moi.
jklp
77

Dans iOS7, ils ont introduit la propriété tintColor sur UIImageView et renderingMode sur UIImage. Pour teinter une UIImage sur iOS7, tout ce que vous avez à faire est:

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
la source
18
C'est certainement le moyen le meilleur et le plus simple d'utiliser iOS 7 si c'est le comportement de teinte que vous recherchez. Mais ce code applique une teinte comme si vous utilisiez un mode de fusion "Superposition" 100% opaque dans Photoshop, et non le mode de fusion "Multiplier" recherché par la question d'origine.
smileyborg
26

Je voulais teinter une image avec alpha et j'ai créé la classe suivante. S'il vous plaît laissez-moi savoir si vous rencontrez des problèmes avec lui.

J'ai nommé ma classe CSTintedImageViewet elle hérite de UIViewpuisque UIImageViewn'appelle pas la drawRect:méthode, comme mentionné dans les réponses précédentes. J'ai défini un initialiseur désigné similaire à celui trouvé dans la UIImageViewclasse.

Usage:

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
la source
2
Je vous remercie. C'était la première solution à cette question qui prenait en charge correctement la transparence de l'image.
theTRON
Lorsque j'essaie d'utiliser cette réponse, mon arrière-plan (la zone qui devrait être transparente) est noir à la place. Savez-vous ce que je pourrais faire de mal ici?
livingtech
Ça ne fait rien! (J'avais une couleur d'arrière-plan définie dans le storyboard! D'oh!) Cette solution est géniale !!!
livingtech
2
Cette solution fonctionne bien, même si j'ai obtenu de meilleurs résultats en effectuant d'abord le rendu de l'image, puis en remplissant la couleur par-dessus avec kCGBlendModeColor.
Jeremy Fuller
la méthode drawRect: est vraiment la "viande" de cette solution. Le reste est une question de goût / style. Merci! A travaillé pour moi!
horseshoe7
26

Juste une petite clarification (après quelques recherches sur ce sujet). Le doc Apple ici indique clairement que:

La UIImageViewclasse est optimisée pour dessiner ses images sur l'écran. UIImageViewn'appelle pas la drawRect:méthode de ses sous-classes. Si votre sous-classe doit inclure un code de dessin personnalisé, vous devez UIViewplutôt sous- classer la classe.

alors ne perdez même pas de temps à essayer de remplacer cette méthode dans une UIImageViewsous - classe. Commencez UIViewplutôt par.

mattorb
la source
9
Oh, comme j'aurais aimé savoir ça il y a six mois. Ils doivent mettre l'avertissement en 72 points, en rouge avec la balise clignotante.
willc2
Je sais que tu veux dire ... J'ai perdu une demi-journée à jouer avec ça.
mattorb
merci @mpstx ... Mettez également cette ligne de documentation dans la réponse entre guillemets ... Apple aurait dû faire la même chose dans la documentation ... :-)
Krishnabhadra
5

Cela pourrait être très utile: PhotoshopFramework est une bibliothèque puissante pour manipuler des images sur Objective-C. Cela a été développé pour apporter les mêmes fonctionnalités que les utilisateurs d'Adobe Photoshop sont familiers. Exemples: définir les couleurs à l'aide de RVB 0-255, appliquer des fichiers de fusion, des transformations ...

Est open source, voici le lien du projet: https://sourceforge.net/projects/photoshopframew/

Équipe de développement SEQOY
la source
2
Cela semble intéressant, mais comment allez-vous changer le nom lorsque les avocats d'Adobe le découvriront?
Kristopher Johnson
1
Nous découvrirons plus tard. N'est-ce pas un produit commercial de toute façon, c'est une sorte de "nom de code". Est également une bibliothèque interne.
Équipe de développement SEQOY
+1 pour suggérer une bibliothèque (même si c'est un peu de l'auto-promotion), et ne pas donner d'étapes pour réinventer la roue
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
la source
J'avais besoin de teinter une UIImage, mais pas de l'utiliser à l'intérieur d'une UIImageView, mais comme image d'arrière-plan pour une UINavigationBar, et votre code a fait l'affaire. Merci.
ncerezo
3

Voici une autre façon d'implémenter la teinte d'image, surtout si vous utilisez déjà QuartzCore pour autre chose. C'était ma réponse à une question similaire .

Importer QuartzCore:

#import <QuartzCore/QuartzCore.h>

Créez un CALayer transparent et ajoutez-le en tant que sous-calque pour l'image que vous souhaitez teinter:

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

Ajoutez QuartzCore à votre liste de projets Framework (si ce n'est déjà fait), sinon vous obtiendrez des erreurs de compilation comme celle-ci:

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

Pour ceux d'entre vous qui essaient de sous-classer une classe UIImageView et de rester bloqués à «drawRect: is not being called», notez que vous devez sous-classer une classe UIView à la place, car pour les classes UIImageView, la méthode «drawRect:» n'est pas appelée. En savoir plus ici: drawRect n'est pas appelé dans ma sous-classe de UIImageView

pierre
la source
0

La seule chose à laquelle je peux penser serait de créer une vue rectangulaire principalement transparente avec la couleur souhaitée et de la placer sur votre vue d'image en l'ajoutant en tant que sous-vue. Je ne suis pas sûr que cela teinte vraiment l'image comme vous l'imaginez, je ne suis pas sûr de savoir comment vous pirateriez une image et remplaceriez sélectivement certaines couleurs par d'autres ... cela me semble assez ambitieux.

Par exemple:

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

Documentation pour 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
la source
Core Graphics semble pouvoir appliquer des modes de flexion. Comment, est la question.
willc2
0

Vous pouvez également envisager de mettre en cache l'image composite pour les performances et de la restituer simplement dans drawRect :, puis de la mettre à jour si un indicateur sale est effectivement sale. Bien que vous puissiez le changer souvent, il peut y avoir des cas où des tirages arrivent et vous n'êtes pas sale, vous pouvez donc simplement actualiser à partir du cache. Si la mémoire est plus un problème que les performances, vous pouvez l'ignorer :)

Steve Riggins
la source
C'est ce que le CALayer sous-jacent fait automatiquement pour vous. Pas besoin de mettre en cache le dessin de vue manuellement.
Dennis Munsie
0

Pour 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
la source
0

J'ai fait des macros à cet effet:

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

Pour l'utiliser, il suffit d'appeler:

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

Lorsque vous supprimez la teinte, appelez simplement:

removeTint(yourView);
Albert Renshaw
la source