如何在iPhone中为透明的PNG图像着色?


74

我知道可以通过在矩形图像上绘制CGContextFillRect并设置混合模式来着色矩形图像。但是,我不知道如何对透明图像(例如图标)进行着色。之所以必须这样做是因为SDK本身就是在这样的标签栏上进行的。有人可以提供摘要吗?

更新:

自从我最初提出问题以来,已经为这个问题提供了很多很棒的建议。请务必通读所有答案,以找出最适合您的方法。

更新(2015年4月30日):

使用iOS 7.0,我现在可以执行以下操作,这将满足我原来的问题。但是,如果您遇到的情况更复杂,请查看所有答案。

UIImage *iconImage = [[UIImage imageNamed:@"myImageName"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];    
UIImageView *icon = [[UIImageView alloc] initWithImage:iconImage];
icon.tintColor = [UIColor redColor];

Answers:


165

更新:这是使用以下代码的Swift UIColor扩展的要点


如果您有灰度图像,并且希望白色成为着色色kCGBlendModeMultiply则是一种方法。使用这种方法,您不能使突出显示的颜色比着色的颜色浅。

相反,如果你有一张非灰度图像或者你有亮点阴影应保留,混合模式kCGBlendModeColor是要走的路。由于图像的亮度得以保留,因此白色将保持白色,而黑色将保持黑色。此模式仅用于着色-与Photoshop的Color图层混合模式相同(免责声明:结果可能会略有不同)。

请注意,无论是在iOS中还是在Photoshop中,着色alpha像素均无法正常工作-半透明的黑色像素不会保持黑色。我更新了以下答案来解决该问题,但花了很长时间才找到答案。

您也可以使用一种混合模式kCGBlendModeSourceIn/DestinationIn代替CGContextClipToMask

如果要创建一个UIImage,下面的每个代码段都可以被以下代码包围:

UIGraphicsBeginImageContextWithOptions (myIconImage.size, NO, myIconImage.scale); // for correct resolution on retina, thanks @MobileVet
CGContextRef context = UIGraphicsGetCurrentContext();

CGContextTranslateCTM(context, 0, myIconImage.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

CGRect rect = CGRectMake(0, 0, myIconImage.size.width, myIconImage.size.height);

// image drawing code here

UIImage *coloredImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

因此,这是使用着色透明图像的代码kCGBlendModeColor

// draw black background to preserve color of transparent pixels
CGContextSetBlendMode(context, kCGBlendModeNormal);
[[UIColor blackColor] setFill];
CGContextFillRect(context, rect);

// draw original image
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextDrawImage(context, rect, myIconImage.CGImage);

// tint image (loosing alpha) - the luminosity of the original image is preserved
CGContextSetBlendMode(context, kCGBlendModeColor);
[tintColor setFill];
CGContextFillRect(context, rect);

// mask by alpha values of original image
CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
CGContextDrawImage(context, rect, myIconImage.CGImage);

如果您的图像没有半透明像素,则也可以使用kCGBlendModeLuminosity以下方法:

// draw tint color
CGContextSetBlendMode(context, kCGBlendModeNormal);
[tintColor setFill];
CGContextFillRect(context, rect);

// replace luminosity of background (ignoring alpha)
CGContextSetBlendMode(context, kCGBlendModeLuminosity);
CGContextDrawImage(context, rect, myIconImage.CGImage);

// mask by alpha values of original image
CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
CGContextDrawImage(context, rect, myIconImage.CGImage);

如果您不关心亮度,因为您只获得了带有Alpha通道的图像,该图像应使用颜色进行着色,则可以以更有效的方式进行操作:

// draw tint color
CGContextSetBlendMode(context, kCGBlendModeNormal);
[tintColor setFill];
CGContextFillRect(context, rect);

// mask by alpha values of original image
CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
CGContextDrawImage(context, rect, myIconImage.CGImage);

或相反:

// draw alpha-mask
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextDrawImage(context, rect, myIconImage.CGImage);

// draw tint color, preserving alpha values of original image
CGContextSetBlendMode(context, kCGBlendModeSourceIn);
[tintColor setFill];
CGContextFillRect(context, rect);

玩得开心!


这太棒了,节省了我很多时间!我喜欢使用kCGBlendModeDestinationIn还原要处理的图像的原始Alpha的技术。比处理剪贴蒙版还容易。
Ziconic 2012年

8
这是出色而透彻的……但是,它缺少一个微小的选项来确保在Retina显示器上的输出质量– UIGraphicsBeginImageContextWithOptions(myIconImage.size,NO,[[UIScreen mainScreen] scale])。
MobileVet

或像这样设置UIGraphicsBeginImageContextWithOptions(self.size,0,self.scale)类别中的视网膜比例
HotJard 2013年

21

我使用此方法最成功,因为尝试的其他方法会导致某些颜色组合的半透明像素的颜色失真。在性能方面,这也应该更好一些。

+ (UIImage *) imageNamed:(NSString *) name withTintColor: (UIColor *) tintColor {

    UIImage *baseImage = [UIImage imageNamed:name];

    CGRect drawRect = CGRectMake(0, 0, baseImage.size.width, baseImage.size.height);

    UIGraphicsBeginImageContextWithOptions(baseImage.size, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextTranslateCTM(context, 0, baseImage.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);

    // draw original image
    CGContextSetBlendMode(context, kCGBlendModeNormal);
    CGContextDrawImage(context, drawRect, baseImage.CGImage);

    // draw color atop
    CGContextSetFillColorWithColor(context, tintColor.CGColor);
    CGContextSetBlendMode(context, kCGBlendModeSourceAtop);
    CGContextFillRect(context, drawRect);

    UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return tintedImage;
}

这似乎不适用于alpha分量小于1的颜色。您知道一种解决方法吗?
Zev Eisenberg

我从github.com/vilanovi/UIImage-Additions/blob/master/…中使用此代码进行工作CGContextSetBlendMode(context, kCGBlendModeNormal); CGContextDrawImage(context, rect, self.CGImage); CGContextSetBlendMode(context, kCGBlendModeSourceIn); [color setFill]; CGContextFillRect(context, rect);
Zev Eisenberg

18

经过四处搜寻之后,到目前为止,最好的解决方案是结合使用混合模式和剪贴蒙版来实现透明PNG的着色/着色:

CGContextSetBlendMode (context, kCGBlendModeMultiply);

CGContextDrawImage(context, rect, myIconImage.CGImage);

CGContextClipToMask(context, rect, myIconImage.CGImage);

CGContextSetFillColorWithColor(context, tintColor);

CGContextFillRect(context, rect);

在找到这个东西之前,我尝试了我能想到的所有其他东西,这是唯一有效的方法。谢谢@anna!
托德·雷曼

这个工作,但我的翻转图像,所以我用从克里夫·维埃加斯这个答案来修复:stackoverflow.com/questions/506622/...
C.李

很棒。上面的解决方案出于某种奇怪的原因为我留下了一个1像素的边框。
哥特弗里德,

16

通过使用kCGBlendModeOverlay,我可以获得非常接近Apple导航栏中色彩的结果。在本文中以出色的@fabb答案并结合@omz方法https://stackoverflow.com/a/4684876/229019我附带了此解决方案,该解决方案具有我期望的结果:

- (UIImage *)tintedImageUsingColor:(UIColor *)tintColor;
{
    UIGraphicsBeginImageContextWithOptions (self.size, NO, [[UIScreen mainScreen] scale]);

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);

    // draw original image
    [self drawInRect:rect blendMode:kCGBlendModeNormal alpha:1.0f];

    // tint image (loosing alpha). 
    // kCGBlendModeOverlay is the closest I was able to match the 
    // actual process used by apple in navigation bar 
    CGContextSetBlendMode(context, kCGBlendModeOverlay);
    [tintColor setFill];
    CGContextFillRect(context, rect);

    // mask by alpha values of original image
    [self drawInRect:rect blendMode:kCGBlendModeDestinationIn alpha:1.0f];

    UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return tintedImage;
}

这是一个使用透明度为多个灰度图像着色的示例:

渲染示例

  • 第一行是苹果工具栏,其颜色为[UIColor orangeColor]。
  • 第二行是相同的渐变色,以几种颜色着色,从清晰的颜色(=实际的渐变色)开始,以相同的橙色结束。
  • 第三个是具有透明度的简单圆圈(亚麻是背景色)
  • 第四行是复杂的暗噪纹理

4
这对我来说很棒。我只是将第一行更改为:UIGraphicsBeginImageContextWithOptions(self.size,NO,[[UIScreen mainScreen] scale]); 允许图像保留视网膜格式(如果适用)。
jwarrent

1
这将更合适:UIGraphicsBeginImageContextWithOptions(self.size,NO,self.scale);
malhal 2014年

8

您可以创建一个UIImage类别,并像这样进行操作:

- (instancetype)tintedImageWithColor:(UIColor *)tintColor {
    UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGRect rect = (CGRect){ CGPointZero, self.size };
    CGContextSetBlendMode(context, kCGBlendModeNormal);
    [self drawInRect:rect];

    CGContextSetBlendMode(context, kCGBlendModeSourceIn);
    [tintColor setFill];
    CGContextFillRect(context, rect);

    UIImage *image  = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}


3

使用iOS 7.0,您还可以执行以下操作以着色基本的UIImageView:

UIImage *iconImage = [[UIImage imageNamed:@"myImageName"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];    
UIImageView *icon = [[UIImageView alloc] initWithImage:iconImage];
icon.tintColor = [UIColor redColor];

3

请注意,在fabb接受的答案中,用于制作UIImage的“环绕”代码使我在视网膜屏幕上的图像分辨率错误。要修复,请更改:

UIGraphicsBeginImageContext(myIconImage.size);

至:

UIGraphicsBeginImageContextWithOptions(myIconImage.size, NO, 0.0);

设置为0.0的最后一个参数是scale,根据Apple文档:

“如果将值指定为0.0,则比例因子将设置为设备主屏幕的比例因子”。

没有评论的权限,编辑似乎有点不礼貌,所以我在回答中提到了这一点。以防万一有人遇到同样的问题。


2

UIImageView(或任何与此相关的视图)的背景颜色为RGBA。颜色中的Alpha可以满足您的需要,而无需发明新的东西。



1

我想在自定义UIButton子类中为图像视图添加阴影,而其他解决方案则无法满足我的需求。我需要加深“色调”图像的颜色。这是使用CoreImage更改亮度的方法。

按下按钮时带有阴影图像的自定义按钮

  1. 确保将CoreImage.framework添加到项目的库中。(将二进制文件与库链接)

  2. UIImage阴影方法

    - (UIImage *)shadeImage:(UIImage *)image {
     CIImage *inputImage = [CIImage imageWithCGImage:image.CGImage];
    
     CIContext *context = [CIContext contextWithOptions:nil];
    
     CIFilter *filter = [CIFilter filterWithName:@"CIColorControls"
                                  keysAndValues:kCIInputImageKey, inputImage,
                        @"inputBrightness", [NSNumber numberWithFloat:-.5], nil];
     CIImage *outputImage = [filter outputImage];
    
     CGImageRef cgImage = [context createCGImage:outputImage fromRect:[outputImage extent]];
     UIImage *newImage = [UIImage imageWithCGImage:cgImage scale:image.scale orientation:image.imageOrientation];
     CGImageRelease(cgImage);
    
     return newImage;
    }
    
  3. 您需要将上下文的副本存储为ivar,而不是重新创建它。


1

没有任何答案可以帮助我解决堆栈溢出问题:我们的设计师使用各种形式,各种alpha值(和“ alpha孔”)绘制UI元素。在大多数情况下,这是带有Alpha通道的32位PNG文件,其中包含黑白像素(具有所有可能的强度)。在对此类图片进行着色之后,我必须获得这种着色结果:白色像素-着色较多,而深色像素-较少。而所有这些都考虑到alpha通道。我为UIImage类别编写了此方法。也许它效率不高,但它可以作为时钟工作:)这是:

- (UIImage *)imageTintedWithColor:(UIColor *)color {
    UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);

    CGContextSetBlendMode(context, kCGBlendModeCopy);
    [color setFill];
    CGContextFillRect(context, rect);

    [self drawInRect:rect blendMode:kCGBlendModeXOR alpha:1.0];

    CGContextSetBlendMode(context, kCGBlendModeXOR);
    CGContextFillRect(context, rect);

    [self drawInRect:rect blendMode:kCGBlendModeMultiply alpha:1.0];

    UIImage *coloredImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return coloredImage;
}

0

首先,我要感谢fabb的出色解决方案,该解决方案帮助我完成了将半透明图标着色的任务。因为我需要C#(Monotouch)解决方案,所以我不得不翻译他的代码。这是我想出的。只需将其复制粘贴到您的代码中,然后添加半透明的图像并完成即可。

因此,所有信用再次归功于fabb。这只是为了启动C#用户:)

//TINT COLOR IMAGE
UIImageView iImage = new UIImageView(new RectangleF(12, 14, 24,24));
iImage.ContentMode = UIViewContentMode.ScaleAspectFit;
iImage.Image = _dataItem.Image[0] as UIImage;

UIGraphics.BeginImageContextWithOptions(iImage.Bounds.Size, false, UIScreen.MainScreen.Scale);
CGContext context = UIGraphics.GetCurrentContext();

context.TranslateCTM(0, iImage.Bounds.Size.Height);
context.ScaleCTM(1.0f, -1.0f);

RectangleF rect = new RectangleF(0,0, iImage.Bounds.Width, iImage.Bounds.Height);

// draw black background to preserve color of transparent pixels
context.SetBlendMode(CGBlendMode.Normal);
UIColor.Black.SetFill();
context.FillRect(rect);

// draw original image
context.SetBlendMode(CGBlendMode.Normal);
context.DrawImage(rect, iImage.Image.CGImage);

// tint image (loosing alpha) - the luminosity of the original image is preserved
context.SetBlendMode(CGBlendMode.Color);
UIColor.Orange.SetFill();
context.FillRect(rect);

// mask by alpha values of original image
context.SetBlendMode(CGBlendMode.DestinationIn);
context.DrawImage(rect, iImage.Image.CGImage);

UIImage coloredImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();

iImage = new UIImageView(coloredImage);
iImage.Frame = new RectangleF(12, 14, 24,24);
//END TINT COLOR IMAGE

cell.Add(iImage);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.