如何将UIImage / CGImageRef的alpha通道转换为掩码?
我怎样才能提取UIImage或CGImageRef的alpha通道,并将其转换成我可以使用CGImageMaskCreate的掩码?
例如:
基本上,给任何图像,我不关心图像内的颜色。 我想要的只是创build一个代表alpha通道的灰度图像。 这个图像可以用来掩盖其他图像。
当你提供一个图标图像的时候,这个UIBarButtonItem的一个例子是。 根据苹果文件,它指出:
显示在栏上的图像来源于该图像。 如果这张图片太大而无法放在栏上,则会缩放。 通常,工具栏和导航栏图像的大小是20 x 20点。 源图像中的alpha值用于创build图像 – 不透明值将被忽略。
该UIBarButtonItem采取任何图像,只看阿尔法,而不是图像的颜色。
要按照条形button项目的方式为图标着色,您不需要传统的蒙版,您需要蒙版的反转 – 原始图像中的不透明像素用作最终着色的反转,而不是其他方式。
这是完成这个的一个方法。 把你的原始RBGA图像,并通过以下处理:
- 将它绘制成仅用于Alpha通道的位图图像
- 如上所述,颠倒每个像素的alpha值以获得相反的行为
- 把这个倒置的alpha图像变成一个真正的蒙版
- 用它。
例如
#define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S)) // Original RGBA image CGImageRef originalMaskImage = [[UIImage imageNamed:@"masktest.png"] CGImage]; float width = CGImageGetWidth(originalMaskImage); float height = CGImageGetHeight(originalMaskImage); // Make a bitmap context that's only 1 alpha channel // WARNING: the bytes per row probably needs to be a multiple of 4 int strideLength = ROUND_UP(width * 1, 4); unsigned char * alphaData = calloc(strideLength * height, sizeof(unsigned char)); CGContextRef alphaOnlyContext = CGBitmapContextCreate(alphaData, width, height, 8, strideLength, NULL, kCGImageAlphaOnly); // Draw the RGBA image into the alpha-only context. CGContextDrawImage(alphaOnlyContext, CGRectMake(0, 0, width, height), originalMaskImage); // Walk the pixels and invert the alpha value. This lets you colorize the opaque shapes in the original image. // If you want to do a traditional mask (where the opaque values block) just get rid of these loops. for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { unsigned char val = alphaData[y*strideLength + x]; val = 255 - val; alphaData[y*strideLength + x] = val; } } CGImageRef alphaMaskImage = CGBitmapContextCreateImage(alphaOnlyContext); CGContextRelease(alphaOnlyContext); free(alphaData); // Make a mask CGImageRef finalMaskImage = CGImageMaskCreate(CGImageGetWidth(alphaMaskImage), CGImageGetHeight(alphaMaskImage), CGImageGetBitsPerComponent(alphaMaskImage), CGImageGetBitsPerPixel(alphaMaskImage), CGImageGetBytesPerRow(alphaMaskImage), CGImageGetDataProvider(alphaMaskImage), NULL, false); CGImageRelease(alphaMaskImage);
现在你可以使用finalMaskImage
作为CGContextClipToMask
等的掩码。
本Zotto的解决scheme是正确的,但有一种方法可以做到这一点,没有math或当地的复杂性,依靠CGImage
为我们做的工作。
以下解决scheme使用Swift(v3)通过反转现有图像的Alpha通道从图像创build一个蒙版。 源图像中的透明像素将变得不透明,并且部分透明的像素将被反转以成比例地或多或less透明。
这个解决scheme的唯一要求是一个CGImage
基础图像。 一个可以从UIImage.cgImage
获得最多的UIImage
。 如果您要在CGContext
自己渲染基础图像,请使用CGContext.makeImage()
生成新的CGImage
。
代码
let image: CGImage = // your image // Create a "Decode Array" which flips the alpha channel in // an image in ARGB format (premultiplied first). Adjust the // decode array as needed based on the pixel format of your // image data. // The tuples in the decode array specify how to clamp the // pixel color channel values when the image data is decoded. // // Tuple(0,1) means the value should be clamped to the range // 0 and 1. For example, a red value of 0.5888 (~150 out of // 255) would not be changed at all because 0 < 0.5888 < 1. // Tuple(1,0) flips the value, so the red value of 0.5888 // would become 1-0.5888=0.4112. We use this method to flip // the alpha channel values. let decode = [ CGFloat(1), CGFloat(0), // alpha (flipped) CGFloat(0), CGFloat(1), // red (no change) CGFloat(0), CGFloat(1), // green (no change) CGFloat(0), CGFloat(1) ] // blue (no change) // Create the mask `CGImage` by reusing the existing image data // but applying a custom decode array. let mask = CGImage(width: image.width, height: image.height, bitsPerComponent: image.bitsPerComponent, bitsPerPixel: image.bitsPerPixel, bytesPerRow: image.bytesPerRow, space: image.colorSpace!, bitmapInfo: image.bitmapInfo, provider: image.dataProvider!, decode: decode, shouldInterpolate: image.shouldInterpolate, intent: image.renderingIntent)
而已! mask
CGImage现在已经可以和context.clip(to: rect, mask: mask!)
一起使用了context.clip(to: rect, mask: mask!)
。
演示
这是我的基本图像与透明背景上的不透明红色的“面具图像”:
为了演示通过上述algorithm运行时会发生什么情况,下面是一个简单地将生成的图像呈现在绿色背景上的示例。
override func draw(_ rect: CGRect) { // Create decode array, flipping alpha channel let decode = [ CGFloat(1), CGFloat(0), CGFloat(0), CGFloat(1), CGFloat(0), CGFloat(1), CGFloat(0), CGFloat(1) ] // Create the mask `CGImage` by reusing the existing image data // but applying a custom decode array. let mask = CGImage(width: image.width, height: image.height, bitsPerComponent: image.bitsPerComponent, bitsPerPixel: image.bitsPerPixel, bytesPerRow: image.bytesPerRow, space: image.colorSpace!, bitmapInfo: image.bitmapInfo, provider: image.dataProvider!, decode: decode, shouldInterpolate: image.shouldInterpolate, intent: image.renderingIntent) let context = UIGraphicsGetCurrentContext()! // paint solid green background to highlight the transparent areas context.setFillColor(UIColor.green.cgColor) context.fill(rect) // render the mask image directly. The black areas will be masked. context.draw(mask!, in: rect) }
现在我们可以使用该图像来掩盖任何呈现的内容。 下面是一个例子,我们在上一个例子中的绿色顶部渲染了一个蒙板渐变。
override func draw(_ rect: CGRect) { let context = UIGraphicsGetCurrentContext()! // paint solid green background to highlight the transparent areas context.setFillColor(UIColor.green.cgColor) context.fill(rect) let mask: CGImage = // mask generation elided. See previous example. // Clip to the mask image context.clip(to: rect, mask: mask!) // Create a simple linear gradient let colors = [ UIColor.red.cgColor, UIColor.blue.cgColor, UIColor.orange.cgColor ] let gradient = CGGradient(colorsSpace: context.colorSpace, colors: colors as CFArray, locations: nil) // Draw the linear gradient around the clipping area context.drawLinearGradient(gradient!, start: CGPoint.zero, end: CGPoint(x: rect.size.width, y: rect.size.height), options: CGGradientDrawingOptions()) }
(注意:你也可以交换CGImage
代码来使用Accelerate Framework的vImage
,可能受益于该库中的vector处理优化,我还没有尝试过)。
我尝试了由quixoto提供的代码,但它不适合我,所以我改变了一点点。
问题是只绘制alpha通道不适合我,所以我通过首先获取原始图像的数据并在Alpha通道上工作来手动完成。
#define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S)) #import <stdlib.h> - (CGImageRef) createMaskWithImageAlpha: (CGContextRef) originalImageContext { UInt8 *data = (UInt8 *)CGBitmapContextGetData(originalImageContext); float width = CGBitmapContextGetBytesPerRow(originalImageContext) / 4; float height = CGBitmapContextGetHeight(originalImageContext); // Make a bitmap context that's only 1 alpha channel // WARNING: the bytes per row probably needs to be a multiple of 4 int strideLength = ROUND_UP(width * 1, 4); unsigned char * alphaData = (unsigned char * )calloc(strideLength * height, 1); CGContextRef alphaOnlyContext = CGBitmapContextCreate(alphaData, width, height, 8, strideLength, NULL, kCGImageAlphaOnly); // Draw the RGBA image into the alpha-only context. //CGContextDrawImage(alphaOnlyContext, CGRectMake(0, 0, width, height), originalMaskImage); // Walk the pixels and invert the alpha value. This lets you colorize the opaque shapes in the original image. // If you want to do a traditional mask (where the opaque values block) just get rid of these loops. for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { //unsigned char val = alphaData[y*strideLength + x]; unsigned char val = data[y*(int)width*4 + x*4 + 3]; val = 255 - val; alphaData[y*strideLength + x] = val; } } CGImageRef alphaMaskImage = CGBitmapContextCreateImage(alphaOnlyContext); CGContextRelease(alphaOnlyContext); free(alphaData); // Make a mask CGImageRef finalMaskImage = CGImageMaskCreate(CGImageGetWidth(alphaMaskImage), CGImageGetHeight(alphaMaskImage), CGImageGetBitsPerComponent(alphaMaskImage), CGImageGetBitsPerPixel(alphaMaskImage), CGImageGetBytesPerRow(alphaMaskImage), CGImageGetDataProvider(alphaMaskImage), NULL, false); CGImageRelease(alphaMaskImage); return finalMaskImage; }
你可以像这样调用这个函数
CGImageRef originalImage = [image CGImage]; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef bitmapContext = CGBitmapContextCreate(NULL, CGImageGetWidth(originalImage), CGImageGetHeight(originalImage), 8, CGImageGetWidth(originalImage)*4, colorSpace, kCGImageAlphaPremultipliedLast); CGContextDrawImage(bitmapContext, CGRectMake(0, 0, CGBitmapContextGetWidth(bitmapContext), CGBitmapContextGetHeight(bitmapContext)), originalImage); CGImageRef finalMaskImage = [self createMaskWithImageAlpha:bitmapContext]; //YOUR CODE HERE CGContextRelease(bitmapContext); CGImageRelease(finalMaskImage);