【问题标题】:Retrieving a pixel alpha value for a UIImage检索 UIImage 的像素 alpha 值
【发布时间】:2010-11-05 18:47:21
【问题描述】:

我目前正在尝试获取 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 的坐标计算给定 Alpha 通道的数组索引。

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 的评论——我只画了一次图像,然后继续使用相同的字节数组。 @Nikolai Ruhe 也建议单像素绘制,但表示如果我不需要多次绘制图像,但需要重复查找 alpha,我的数组方法会更快。
  • 在使用此问题和此处的另一个问题以解决类似问题后的快速更新(多年后):CGContextDrawImage 上的 rect 参数用于控制“边界框在用户空间中的位置和尺寸在其中绘制图像。”因此,如果您将该矩形的大小设为 1x1,它会在绘制之前将整个图像缩小到 1x1。为了获得正确的像素,您需要像 Nikolai 的回答那样使用 CGContextTranslateCTM,并使矩形的大小与源图像相同以防止缩放。
  • 您好,我正在尝试使用您发布的代码,但无法获取“rawData”、“bytePerPixel”变量的数据类型。你能告诉我答案吗?

标签: iphone uikit core-graphics quartz-graphics


【解决方案1】:

是的,CGContext 的 y 轴指向上方,而在 UIKit 中它指向下方。 See the docs.

阅读代码后编辑:

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

CGContextSetBlendMode(context, kCGBlendModeCopy);

三思而后行:

您可以通过构建尽可能小的 CGBitmapContext(1x1 像素?也许是 8x8?试一试)并在绘制之前将上下文转换到您想要的位置来更有效地进行查找更多

CGContextTranslateCTM(context, xOffset, yOffset);

【讨论】:

  • CGImageRef 代表一个静态图像。一旦我有了字节数组,我就把 CGImage 扔掉,然后反复使用字节数组进行 alpha 查找。您的优化是否适用于这种情况?
  • 不,当然你需要图像来重复绘制它。对于少数查找,我的方法可能更有效。如果您要进行大量查找,请坚持使用您的代码。
  • 我也在尝试这个(我正在做很多查找——每次点击一个)。它在模拟器上运行良好,但在 iPhone 4 上却不行。坐标看起来不错(左下角是 0,0),但命中测试却是一团糟。见stackoverflow.com/questions/7506248/…
【解决方案2】:

我是否需要转换 UIKit 和 Core Graphics 之间的坐标 - 即:y 轴是否倒置?

这是可能的。在CGImage中,像素数据的英文阅读顺序是:从左到右,从上到下。因此,数组中的第一个像素是左上角;第二个像素是顶行左起一个;等等

假设您有这个权利,您还应该确保您查看的是像素内的正确组件。也许您期待 RGBA 但要求 ARGB,反之亦然。或者,也许你的字节顺序错误(我不知道 iPhone 的字节序是什么)。

还是我误解了预乘的 alpha 值?

听起来不像。

对于那些不知道的人:预乘意味着颜色分量被alpha预乘;无论颜色分量是否被它预乘,alpha 分量都是相同的。您可以通过将颜色分量除以 alpha 来反转(取消预乘)。

【讨论】:

    【解决方案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

    希望这有帮助!

    【讨论】:

    • 这很有帮助。请记住,在这里,您的 BytesPerRow 可能不是 width*bytesPerPixel。为了优化,它可能被填充到 16 字节边界。在遍历 rawData 时,如果不考虑这一点,最终将使用填充字节作为像素数据。
    • 这也意味着您分配的 rawData 可能太小而无法容纳位图,并且可能存在缓冲区溢出。
    • 我想知道这是否就是为什么我无法让它在 iPhone 上运行,但它在模拟器上运行良好? stackoverflow.com/questions/7506248/…
    【解决方案4】:

    如果您只需要单个点的 alpha 值,那么您只需要一个 alpha-only 单点缓冲区。我相信这应该足够了:

    // 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;
    

    我认为这样可以解决翻转问题。

    【讨论】:

    • 出于某种原因,我不得不为CGContextDrawImage(... 使用以下行:CGContextDrawImage(context, CGRectMake(-point.x, -(image.size.height-point.y), CGImageGetWidth(cgim), CGImageGetHeight(cgim)), cgim);
    • @MattLeff - 如果您的图像被翻转,这是非常有意义的,由于 Core Graphics(y 原点位于底部)和 UIKit(y 原点位于顶部)之间的阻抗不匹配,这很容易发生)。
    • 这不能也用于提取所有其他值以创建完整的 UIColor 吗?
    • Swift 解决方案怎么样?
    • @BartłomiejSemańczyk 你的意思是stackoverflow.com/a/52743565/341994 吗?
    猜你喜欢
    • 2011-02-05
    • 1970-01-01
    • 1970-01-01
    • 2013-03-19
    • 2017-04-26
    • 1970-01-01
    • 1970-01-01
    • 2014-07-16
    • 1970-01-01
    相关资源
    最近更新 更多