Wie würde ich ein Bild programmgesteuert unter iOS tönen?

87

Ich möchte ein Bild mit einer Farbreferenz tönen. Die Ergebnisse sollten wie im Mischmodus "Multiplizieren" in Photoshop aussehen, bei dem Weiß durch Farbton ersetzt wird :

Alt-Text

Ich werde den Farbwert kontinuierlich ändern.

Follow-up: Ich würde den Code dafür in die drawRect: -Methode meines ImageView einfügen, oder?

Wie immer würde ein Code-Snippet im Gegensatz zu einem Link mein Verständnis erheblich verbessern.

Update: Unterklasse einer UIImageView mit dem von Ramin vorgeschlagenen Code .

Ich habe dies in viewDidLoad: meines View Controllers eingefügt:

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

Ich sehe das Bild, aber es wird nicht getönt. Ich habe auch versucht, andere Bilder zu laden, das Bild in IB festzulegen und setNeedsDisplay: in meinem View Controller aufzurufen.

Update : drawRect: wird nicht aufgerufen.

Letzte Aktualisierung:Letztes Ich habe ein altes Projekt gefunden, in dem imageView richtig eingerichtet war, damit ich Ramins Code testen konnte, und es funktioniert wie ein Zauber!

Endgültiges, endgültiges Update:

Für diejenigen unter Ihnen, die gerade etwas über Core Graphics lernen, ist hier die einfachste Sache, die möglicherweise funktionieren könnte.

In Ihrer untergeordneten 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
quelle
Ich habe das Snippet hinzugefügt, bevor ich es in einen alten drawRect-Code gepostet habe, den ich herumgesessen habe, und es hat gut funktioniert. Vielleicht möchten Sie es ohne den Aufruf [super viewDidLoad] versuchen (oder nach oben verschieben). Überprüfen Sie außerdem noch einmal, ob derjenige, der dieses Objekt zuweist, die abgeleitete Version und nicht Vanilla UIImageView zuweist (dh, wenn es in eine Schreibfeder, den Allokator usw. geladen wird).
Ramin
Ein weiterer Vorschlag, wenn das oben Gesagte nicht funktioniert: Anstatt UIImageView in eine Unterklasse zu unterteilen, fügen Sie eine einfache UIView hinzu und fügen Sie sowohl overlayColor als auch eine UIImage-Eigenschaft namens 'image' hinzu, die Sie festlegen können. Dann setzen Sie den drawRect dort ein. Dem drawRect-Code ist es egal, ob der Wert 'image' von UIImageView oder von einer von Ihnen definierten Eigenschaft stammt.
Ramin
Wird dies nicht scheitern, wenn das Bild mit Alpha ist? Was ist, wenn ich nur einen gezeichneten Teil des Bildes tönen möchte? (Genau wie UIButton?)
Sunil Chauhan

Antworten:

45

Zuerst möchten Sie UIImageView unterordnen und die drawRect-Methode überschreiben. Ihre Klasse benötigt eine UIColor-Eigenschaft (nennen wir sie overlayColor), um die Mischfarbe zu speichern, und einen benutzerdefinierten Setter, der ein Neuzeichnen erzwingt, wenn sich die Farbe ändert. Etwas wie das:

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

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

Bei der drawRect-Methode möchten Sie zuerst das Bild zeichnen und es dann mit einem Rechteck überlagern, das mit der gewünschten Farbe gefüllt ist, zusammen mit dem richtigen Mischmodus.

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

Um die Zeichnung zu optimieren, beschränken Sie die eigentliche Zeichnung normalerweise nur auf den Bereich, der an drawRect übergeben wurde. Da das Hintergrundbild jedoch jedes Mal neu gezeichnet werden muss, wenn sich die Farbe ändert, muss das Ganze wahrscheinlich aktualisiert werden.

Um es zu verwenden, erstellen Sie eine Instanz des Objekts und setzen Sie die imageEigenschaft (von UIImageView geerbt) auf das Bild und overlayColoreinen UIColor-Wert (die Mischungsstufen können durch Ändern des Alpha-Werts der Farbe angepasst werden, die Sie weitergeben).

Ramin
quelle
Follow-up-Vorschläge an die ursprüngliche Anfrage (oben) angehängt.
Ramin
sollte CGContextSaveGState (Kontext) hinzufügen; bevor Sie den Mischmodus ändern oder ein Gstack-Unterlauf auftritt.
Willc2
D'oh, danke. Kopier- / Einfügefehler. Ich habe es im Code-Snippet behoben.
Ramin
10
(Wie auch in einer anderen Antwort hier angegeben) Besondere Überlegungen Die UIImageView-Klasse ist optimiert, um ihre Bilder auf die Anzeige zu zeichnen. UIImageView ruft nicht drawRect auf: eine Unterklasse. Wenn Ihre Unterklasse benutzerdefinierten Zeichnungscode benötigt, wird empfohlen, UIView als Basisklasse zu verwenden. Das heißt , Sie nicht UIImageView Unterklasse und erwarten drawRect genannt, werden überhaupt .
Jonny
Hallo, leider habe ich den Code ausprobiert und es hat nicht funktioniert: /. Siehe die Antwort unten von @mpstx, die besagt, dass drawRect nicht von einer UIImageView aufgerufen wird. Stattdessen sollten Sie eine UIView (mit einem UIImage) verwenden, die für mich gut funktioniert hat.
jklp
76

In iOS7 haben sie die Eigenschaft tintColor in UIImageView und renderingMode in UIImage eingeführt. Um ein UIImage unter iOS7 zu tönen, müssen Sie lediglich Folgendes tun:

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
quelle
18
Dies ist definitiv der beste und einfachste Weg für iOS 7, wenn dies das gewünschte Farbverhalten ist. Dieser Code wendet jedoch einen Farbton an, als ob Sie in Photoshop einen 100% undurchsichtigen Mischmodus "Überlagern" verwenden würden, nicht den Mischmodus "Multiplizieren", nach dem die ursprüngliche Frage gesucht hat.
Smileyborg
26

Ich wollte ein Bild mit Alpha tönen und habe die folgende Klasse erstellt. Bitte lassen Sie mich wissen, wenn Sie Probleme damit haben.

Ich habe meine Klasse benannt CSTintedImageViewund sie erbt von UIViewda UIImageViewruft die drawRect:Methode nicht auf, wie in früheren Antworten erwähnt. Ich habe einen bestimmten Initialisierer eingestellt, der dem im ähneltUIImageView Klasse .

Verwendung:

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
quelle
2
Danke dir. Dies war die erste Lösung in dieser Frage, die die Bildtransparenz tatsächlich richtig unterstützte.
TheTRON
Wenn ich versuche, diese Antwort zu verwenden, ist mein Hintergrund (der Bereich, der transparent sein sollte) stattdessen schwarz. Weißt du was ich hier falsch machen könnte?
Livingtech
Keine Ursache! (Ich hatte eine Hintergrundfarbe im Storyboard! D'oh!) Diese Lösung ist fantastisch !!!
Livingtech
2
Diese Lösung funktioniert gut, obwohl ich bessere Ergebnisse erzielt habe, indem ich zuerst das Bild gerendert und dann die Farbe oben mit kCGBlendModeColor gefüllt habe.
Jeremy Fuller
Die drawRect: -Methode ist wirklich das "Fleisch" dieser Lösung. Der Rest ist eine Frage des Geschmacks / Stils. Vielen Dank! Hat für mich gearbeitet!
Hufeisen7
26

Nur eine kurze Klarstellung (nach einigen Recherchen zu diesem Thema). Das Apple-Dokument hier stellt klar fest, dass:

Die UIImageViewKlasse ist optimiert, um ihre Bilder auf die Anzeige zu zeichnen. UIImageViewruft die drawRect:Methode seiner Unterklassen nicht auf. Wenn Ihre Unterklasse benutzerdefinierten Zeichnungscode enthalten muss, sollten Sie UIViewstattdessen die Klasse unterklassifizieren .

Verschwenden Sie also nicht einmal Zeit damit, diese Methode in einer UIImageViewUnterklasse zu überschreiben . Beginnen Sie UIViewstattdessen mit.

mattorb
quelle
9
Oh, wie gerne hätte ich das vor sechs Monaten gewusst. Sie sollten die Warnung in 72-Punkt-Typ setzen, rot mit dem Blink-Tag.
Willc2
Ich weiß, dass du meinst ... Ich habe einen halben Tag damit verloren.
Mattorb
danke @mpstx ... Setzen Sie auch diese Dokumentationszeile in die Antwort in Anführungszeichen ... Apple hätte das gleiche in der Dokumentation tun sollen ... :-)
Krishnabhadra
5

Dies kann sehr nützlich sein: PhotoshopFramework ist eine leistungsstarke Bibliothek zum Bearbeiten von Bildern in Objective-C. Dies wurde entwickelt, um die gleichen Funktionen bereitzustellen, mit denen Adobe Photoshop-Benutzer vertraut sind. Beispiele: Stellen Sie Farben mit RGB 0-255 ein, wenden Sie Mischfilter, Transformationen ...

Ist Open Source, hier ist der Projektlink: https://sourceforge.net/projects/photoshopframew/

SEQOY Entwicklungsteam
quelle
2
Sieht interessant aus, aber in was werden Sie den Namen ändern, wenn die Anwälte von Adobe davon erfahren?
Kristopher Johnson
1
Wir werden es später herausfinden. Ist sowieso kein kommerzielles Produkt, ist eine Art "Codename". Ist auch eine interne Bibliothek.
SEQOY Entwicklungsteam
+1, um eine Bibliothek vorzuschlagen (auch wenn es sich um eine kleine Eigenwerbung handelt) und keine Schritte zu geben, um das Rad neu zu erfinden.
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
quelle
Ich musste ein UIImage tönen, aber nicht innerhalb einer UIImageView verwenden, sondern als Hintergrundbild für eine UINavigationBar, und Ihr Code hat es geschafft. Vielen Dank.
Ncerezo
3

Hier ist eine andere Möglichkeit, das Abtönen von Bildern zu implementieren, insbesondere wenn Sie QuartzCore bereits für etwas anderes verwenden. Dies war meine Antwort auf eine ähnliche Frage .

Import QuartzCore:

#import <QuartzCore/QuartzCore.h>

Erstellen Sie eine transparente CALayer und fügen Sie sie als Unterschicht für das Bild hinzu, das Sie tönen möchten:

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

Fügen Sie QuartzCore zu Ihrer Projekt-Framework-Liste hinzu (falls es noch nicht vorhanden ist), andernfalls werden folgende Compilerfehler angezeigt:

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

Wenn Sie versuchen, eine UIImageView-Klasse zu unterklassifizieren und bei "drawRect: wird nicht aufgerufen" hängen bleiben, sollten Sie stattdessen eine UIView-Klasse unterordnen, da für UIImageView-Klassen die Methode "drawRect:" nicht aufgerufen wird. Lesen Sie hier mehr: drawRect wird in meiner Unterklasse von UIImageView nicht aufgerufen

Pierre
quelle
0

Das einzige, was ich mir vorstellen kann, wäre, eine rechteckige, größtenteils transparente Ansicht mit der gewünschten Farbe zu erstellen und sie über Ihre Bildansicht zu legen, indem Sie sie als Unteransicht hinzufügen. Ich bin mir nicht sicher, ob dies das Bild wirklich so tönen wird, wie Sie es sich vorstellen. Ich bin mir nicht sicher, wie Sie sich in ein Bild hacken und bestimmte Farben selektiv durch andere ersetzen würden ... klingt für mich ziemlich ehrgeizig.

Beispielsweise:

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

Dokumentation für 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
quelle
Core Graphics scheint in der Lage zu sein, Biegemodi anzuwenden. Wie ist die Frage.
Willc2
0

Möglicherweise möchten Sie auch das zusammengesetzte Bild für die Leistung zwischenspeichern und es einfach in drawRect: rendern. Aktualisieren Sie es dann, wenn ein fehlerhaftes Flag tatsächlich fehlerhaft ist. Während Sie es häufig ändern, kann es Fälle geben, in denen Zeichnungen eingehen und Sie nicht schmutzig sind, sodass Sie einfach aus dem Cache aktualisieren können. Wenn Speicher mehr ein Problem als Leistung ist, können Sie dies ignorieren :)

Steve Riggins
quelle
Das erledigt der zugrunde liegende CALayer automatisch für Sie. Die Ansichtszeichnung muss nicht manuell zwischengespeichert werden.
Dennis Munsie
0

Für 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
quelle
0

Zu diesem Zweck habe ich Makros erstellt:

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

Zur Verwendung rufen Sie einfach an:

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

Wenn Sie den Farbton entfernen, rufen Sie einfach an:

removeTint(yourView);
Albert Renshaw
quelle