获取UIImage中像素的透明度值

37

我目前正在尝试获取UIImageView中像素的alpha值。我从[UIImageView image]获取了CGImage并从中创建了一个RGBA字节数组。Alpha是预乘的。

CGImageRef image = uiImage.CGImage;
NSUInteger width = CGImageGetWidth(image);
NSUInteger height = CGImageGetHeight(image);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
rawData = malloc(height * width * 4);
bytesPerPixel = 4;
bytesPerRow = bytesPerPixel * width;

NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(
    rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace,
    kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big
);
CGColorSpaceRelease(colorSpace);

CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);
CGContextRelease(context);

然еҗҺпјҢжҲ‘дҪҝз”ЁжқҘиҮӘUIImageViewзҡ„еқҗж Үи®Ўз®—з»ҷе®ҡОұйҖҡйҒ“зҡ„ж•°з»„зҙўеј•гҖӮ

int byteIndex = (bytesPerRow * uiViewPoint.y) + uiViewPoint.x * bytesPerPixel;
unsigned char alpha = rawData[byteIndex + 3];

但是我得到的值与期望的不符。对于图像中完全黑色透明区域,我得到了非零的 alpha 通道值。我需要在 UIKit 和 Core Graphics 之间转换坐标吗 - 即 y 轴是否被反转?或者我误解了预乘 alpha 值?

更新:

@Nikolai Ruhe 的建议是关键。实际上,我不需要在 UIKit 坐标和 Core Graphics 坐标之间进行转换。但是,在设置混合模式后,我的 alpha 值就是我所期望的值。

CGContextSetBlendMode(context, kCGBlendModeCopy);

嘿 teabot - 我知道这个问题已经有答案了,但我几天前也在做类似的事情。与其绘制整个图像,然后索引到字节数组中,你应该将源图像的 1px 仅绘制到 1px x 1px 的位图上下文中。你可以使用 CGContextDrawImage 中的 rect 参数来放置正确的像素,并且速度快了 1000 倍 :-) - Ben Gotow
感谢您的评论@Ben Gotow - 我只画了一次图片,然后一直使用相同的字节数组。@Nikolai Ruhe也建议使用单像素绘制,但是说如果我不需要多次绘制图像,但需要重复查找alpha,则我的数组方法会更快。 - teabot
1
几年后的一个快速更新,使用了这个和另一个类似问题的答案。CGContextDrawImage上的rect参数用于控制“在用户空间中绘制图像的边界框的位置和尺寸”。因此,如果将该矩形大小设置为1x1,则会将整个图像缩小到1x1再进行绘制。为了获得正确的像素,需要像Nikolai的回答中那样利用CGContextTranslateCTM,并将矩形的大小保持与源图像相同以防止缩放。 - roguenet
嗨,我正在尝试使用您发布的代码,但无法获取“rawData”和“bytePerPixel”变量的数据类型。您能告诉我答案吗? - Kavya Indi
4个回答

37

如果你只需要一个点的alpha值,那么你只需要使用仅包含alpha通道的单点缓冲区即可。我认为以下代码足以满足你的需求:

// assume im is a UIImage, point is the CGPoint to test
CGImageRef cgim = im.CGImage;
unsigned char pixel[1] = {0};
CGContextRef context = CGBitmapContextCreate(pixel, 
                                         1, 1, 8, 1, NULL,
                                         kCGImageAlphaOnly);
CGContextDrawImage(context, CGRectMake(-point.x, 
                                   -point.y, 
                                   CGImageGetWidth(cgim), 
                                   CGImageGetHeight(cgim)), 
               cgim);
CGContextRelease(context);
CGFloat alpha = pixel[0]/255.0;
BOOL transparent = alpha < 0.01;
如果UIImage不必每次重新创建,这将非常高效。
编辑于2011年12月8日:
一位评论者指出,在某些情况下可能会翻转图像。我一直在思考这个问题,我有点遗憾没有像这样直接使用UIImage编写代码(我认为当时我还不了解UIGraphicsPushContext):
// assume im is a UIImage, point is the CGPoint to test
unsigned char pixel[1] = {0};
CGContextRef context = CGBitmapContextCreate(pixel, 
                                             1, 1, 8, 1, NULL,
                                             kCGImageAlphaOnly);
UIGraphicsPushContext(context);
[im drawAtPoint:CGPointMake(-point.x, -point.y)];
UIGraphicsPopContext();
CGContextRelease(context);
CGFloat alpha = pixel[0]/255.0;
BOOL transparent = alpha < 0.01;

我认为那会解决翻转问题。


3
由于某些原因,我必须使用以下代码行进行 CGContextDrawImage(... 操作:CGContextDrawImage(context, CGRectMake(-point.x, -(image.size.height-point.y), CGImageGetWidth(cgim), CGImageGetHeight(cgim)), cgim); - Matthew Leffler
@MattLeff - 如果您的图像翻转了,那么这是完全有道理的,这很容易发生,因为Core Graphics(其中y原点在底部)和UIKit(其中y原点在顶部)之间的阻抗不匹配。 - matt
这个方法不也可以用来提取所有其他的值以创建完整的UIColor吗? - Kjellski
Swift 解决方案怎么样? - Bartłomiej Semańczyk
@BartłomiejSemańczyk 你是指 https://stackoverflow.com/a/52743565/341994 吗? - matt

10

是的,CGContext的y轴向上指向,而在UIKit中它指向下。 请参阅文档

在阅读代码后进行编辑:

在绘制图像之前,您还需要将混合模式设置为replace,因为您想要图像的alpha值,而不是在上下文缓冲区中的alpha值:

CGContextSetBlendMode(context, kCGBlendModeCopy);

思考后进行编辑:

您可以通过构建最小可能的CGBitmapContext(1x1像素?也许是8x8?试一下)并在绘制前将上下文转换到所需位置,从而使查找更加高效

CGContextTranslateCTM(context, xOffset, yOffset);

CGImageRef代表静态图像。一旦我有了字节数组,就会丢弃CGImage,然后重复使用字节数组进行alpha查找。您的优化在这种情况下是否有效? - teabot
不,当然你需要图像来重复绘制它。我的方法对于少量查找可能更有效。如果你正在做大量的查找,请坚持使用你的代码。 - Nikolai Ruhe
我也在尝试这个(每次点击都会进行大量查找)。它在模拟器上运作得非常好,但在 iPhone 4 上却不行。坐标看起来很正常(左下角为0,0),但命中测试却是一团糟。请参见http://stackoverflow.com/questions/7506248/alpha-detection-in-layer-ok-on-simulator-not-iphone - Joe D'Andrea

3
我在研究如何使用图像数据的alpha值而不是矩形边界框来进行精灵之间的碰撞检测时,发现了这个问题/答案。上下文是一个iPhone应用程序...我正在尝试进行上述建议的1像素绘制,但我仍然无法使其正常工作,但我发现了一种更容易的方法,即使用图像本身的数据创建CGContextRef,并使用这里提供的辅助函数:
CGContextRef context = CGBitmapContextCreate(
                 rawData, 
                 CGImageGetWidth(cgiRef), 
                 CGImageGetHeight(cgiRef), 
                 CGImageGetBitsPerComponent(cgiRef), 
                 CGImageGetBytesPerRow(cgiRef), 
                 CGImageGetColorSpace(cgiRef),
                 kCGImageAlphaPremultipliedLast     
    );

这将绕过样例中所有丑陋的硬编码。最后一个值可以通过调用CGImageGetBitmapInfo()来检索,但在我的情况下,它返回了一张图像导致ContextCreate函数出错的值。只有特定的组合是有效的,如此文档所述:http://developer.apple.com/qa/qa2001/qa1037.html 希望这对您有所帮助!

3
这很有帮助。只需记住,这里的BytesPerRow可能不是width*bytesPerPixel。为了优化,它可能会被填充到16字节边界。当您遍历rawData时,如果您没有考虑到这一点,您将会将填充字节用作像素数据。 - mahboudz
这也意味着你分配的原始数据(rawData)可能太小,无法容纳位图,可能会发生缓冲区溢出。 - mahboudz
1
我在想这是否是为什么我在iPhone上无法正常工作,但在模拟器上可以正常工作的原因?http://stackoverflow.com/questions/7506248/alpha-detection-in-layer-ok-on-simulator-not-iphone - Joe D'Andrea

3
我需要将UIKit和Core Graphics之间的坐标进行转换 - 换句话说,y轴是否翻转?
可能需要。在CGImage中,像素数据按英语阅读顺序排列:从左到右,从上到下。因此,数组中的第一个像素是左上角,第二个像素是顶部行中距离左侧一个像素,以此类推。
假设您已经正确理解了这一点,还应确保查看像素中的正确组件。也许您期望RGBA,但请求的是ARGB,反之亦然。或者,字节顺序可能是错误的(我不知道iPhone的字节顺序是什么)。
还是我误解了预乘的alpha值吗?
听起来不是这样。
对于那些不知道的人:预乘意味着颜色分量被alpha预乘;无论颜色分量是否被alpha预乘,alpha分量都是相同的。您可以通过将颜色分量除以alpha来撤销这种操作(取消预乘)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接