如何提取UIImage或CGImageRef的alpha通道并将其转换为可与CGImageMaskCreate一起使用的掩码?
例如:
基本上,给定任何图像,我不关心图像内部的颜色。我想要的只是创建一个代表alpha通道的灰度图像。然后可以使用该图像来掩盖其他图像。
当您向UIBarButtonItem提供图标图像时,其示例行为就是这样。根据Apple文档,它声明:
条形图上显示的图像来自此图像。如果此图像太大而无法放在条形图上,则会缩放以适合该图像。通常,工具栏和导航栏图像的大小为20 x 20磅。源图像中的alpha值用于创建图像 - 忽略不透明值。
UIBarButtonItem采用任何图像并仅查看alpha,而不是图像的颜色。
答案 0 :(得分:11)
要按条形按钮项目的方式对图标进行着色,您不需要传统的蒙版,您需要蒙版的反转 - 一个原始图像中不透明像素占据最终着色的图形,而不是其他方式。
这是实现这一目标的一种方法。拍摄原始RBGA图像,并按以下方式处理:
E.g。
#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
等中的掩码,等等。
答案 1 :(得分:3)
Ben Zotto的解决方案是正确的,但依靠CGImage
为我们工作,有一种方法可以做到这一点,没有数学或局部复杂性。
以下解决方案使用Swift(v3)通过反转现有图像的 alpha通道从图像创建蒙版。源图像中的透明像素将变为不透明,部分透明像素将被反转为按比例或多或少透明。
此解决方案的唯一要求是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!)
一起使用。
这是我的基本图像&#34; Mask Image&#34;在透明背景上的不透明红色:
为了演示通过上述算法运行时会发生什么,这里有一个示例,它只是将结果图像渲染到绿色背景上。
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
,这可能会受益于该库中的矢量处理优化。我还没有尝试过它)。
答案 2 :(得分:1)
我尝试了 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);