如何将UIImage/CGImageRef的alpha通道转换为掩码?

Nat*_*ner 14 core-graphics uikit uibarbuttonitem ios

如何提取UIImage或CGImageRef的alpha通道并将其转换为可以与CGImageMaskCreate一起使用的掩码?

例如:

示例图片

基本上,给定任何图像,我不关心图像内部的颜色.我想要的只是创建一个代表alpha通道的灰度图像.然后可以使用该图像来掩盖其他图像.

当您为其提供图标图像时,此示例的行为是在UIBarButtonItem中.根据Apple文档,它声明:

条形图上显示的图像来自此图像.如果此图像太大而无法放在条形图上,则会缩放以适合该图像.通常,工具栏和导航栏图像的大小为20 x 20磅.源图像中的alpha值用于创建图像 - 忽略不透明值.

UIBarButtonItem采用任何图像并仅查看alpha,而不是图像的颜色.

Ben*_*tto 11

要按照条形按钮项目的方式对图标进行着色,您不需要传统的蒙版,您需要蒙版的反转 - 一个原始图像中的不透明像素占据最终着色的图形,而不是相反的方式.

这是实现这一目标的一种方法.拍摄原始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);
Run Code Online (Sandbox Code Playgroud)

现在你可以finalMaskImage用作CGContextClipToMask等等的面具,等等.


Ada*_*lan 5

Ben Zotto 的解决方案是正确的,但是有一种方法可以通过依靠CGImage为我们完成工作而无需数学或局部复杂性来完成此操作。

以下解决方案使用 Swift (v3) 通过反转现有图像的Alpha 通道来从图像创建蒙版。源图像中的透明像素将变得不透明,部分透明的像素将反转为按比例或多或少透明。

此解决方案的唯一要求是CGImage基础映像。UIImage.cgImage可以从for a most s中获得一个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)
Run Code Online (Sandbox Code Playgroud)

就是这样!CGImagemask现在可以与context.clip(to: rect, mask: mask!).

演示

这是我的基本图像,在透明背景上带有不透明红色的“蒙版图像”: 图像将成为透明背景上带有红色文本的图像蒙版

为了演示通过上述算法运行时会发生什么,这里有一个示例,它只是在绿色背景上渲染结果图像。

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)
}
Run Code Online (Sandbox Code Playgroud)

直接渲染的蒙版图像,通过剪切区域显示背景

现在我们可以使用该图像来掩盖任何渲染的内容。这是一个示例,我们在上一个示例的绿色顶部渲染蒙版渐变。

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())
}
Run Code Online (Sandbox Code Playgroud)

最终图像显示渐变,原始图像文本被屏蔽为绿色

(注意:您还可以交换代码CGImage以使用 Accelerate Framework 的vImage,可能会受益于该库中的矢量处理优化。我还没有尝试过。)