【问题标题】:Crop UIImage to alpha将 UIImage 裁剪为 alpha
【发布时间】:2011-09-25 04:55:42
【问题描述】:

我有一个相当大的、几乎全屏的图像,我将在 iPad 上显示它。图像大约 80% 透明。我需要在客户端确定不透明像素的边界框,然后裁剪到该边界框。

扫描 StackOverflow 上的其他问题并阅读一些 CoreGraphics 文档,我想我可以通过以下方式完成:

CGBitmapContextCreate(...) // Use this to render the image to a byte array

 ..
   - iterate through this byte array to find the bounding box
 ..

CGImageCreateWithImageInRect(image, boundingRect);

这似乎非常低效且笨拙。我可以用 CGImage 蒙版或利用设备的图形加速来做这件事吗?

【问题讨论】:

  • 你在设备上试过了吗?我敢打赌它会比你想象的要快。
  • 确实如此——一旦我真正坐下来实施它,处理时间比我想象的要快得多!

标签: ios ipad ios4 core-graphics cgimage


【解决方案1】:

感谢 user404709 的辛勤工作。 下面的代码还处理视网膜图像并释放 CFDataRef。

- (UIImage *)trimmedImage {

    CGImageRef inImage = self.CGImage;
    CFDataRef m_DataRef;
    m_DataRef = CGDataProviderCopyData(CGImageGetDataProvider(inImage));

    UInt8 * m_PixelBuf = (UInt8 *) CFDataGetBytePtr(m_DataRef);

    size_t width = CGImageGetWidth(inImage);
    size_t height = CGImageGetHeight(inImage);

    CGPoint top,left,right,bottom;

    BOOL breakOut = NO;
    for (int x = 0;breakOut==NO && x < width; x++) {
        for (int y = 0; y < height; y++) {
            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                left = CGPointMake(x, y);
                breakOut = YES;
                break;
            }
        }
    }

    breakOut = NO;
    for (int y = 0;breakOut==NO && y < height; y++) {

        for (int x = 0; x < width; x++) {

            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                top = CGPointMake(x, y);
                breakOut = YES;
                break;
            }

        }
    }

    breakOut = NO;
    for (int y = height-1;breakOut==NO && y >= 0; y--) {

        for (int x = width-1; x >= 0; x--) {

            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                bottom = CGPointMake(x, y);
                breakOut = YES;
                break;
            }

        }
    }

    breakOut = NO;
    for (int x = width-1;breakOut==NO && x >= 0; x--) {

        for (int y = height-1; y >= 0; y--) {

            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                right = CGPointMake(x, y);
                breakOut = YES;
                break;
            }

        }
    }


    CGFloat scale = self.scale;

    CGRect cropRect = CGRectMake(left.x / scale, top.y/scale, (right.x - left.x)/scale, (bottom.y - top.y) / scale);
    UIGraphicsBeginImageContextWithOptions( cropRect.size,
                                           NO,
                                           scale);
    [self drawAtPoint:CGPointMake(-cropRect.origin.x, -cropRect.origin.y)
           blendMode:kCGBlendModeCopy
               alpha:1.];
    UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    CFRelease(m_DataRef);
    return croppedImage;
}

【讨论】:

  • 我刚刚在我的程序中实现了这个——这正是我所需要的!但是我在第一个 if (m_PixelBuf[loc + 3] != 0) { 行上得到了 EXC_BAD_ACCESS。任何想法为什么?谢谢。
  • @Smikey,是否正在尝试在从相机捕获的图像上使用它?如果是这样,根据 user1702121 的 [stackoverflow.com/a/12614015/1541893],“从捕获的样本缓冲区创建的上下文中的CGBitmapContextGetData() 不会返回它的位图”。他确实提供了解决方案。
【解决方案2】:

我在 UImage 上创建了一个类别,如果有人需要它会这样做...

+ (UIImage *)cropTransparencyFromImage:(UIImage *)img {

    CGImageRef inImage = img.CGImage;           
    CFDataRef m_DataRef;  
    m_DataRef = CGDataProviderCopyData(CGImageGetDataProvider(inImage));  
    UInt8 * m_PixelBuf = (UInt8 *) CFDataGetBytePtr(m_DataRef);  

    int width = img.size.width;
    int height = img.size.height;

    CGPoint top,left,right,bottom;

    BOOL breakOut = NO;
    for (int x = 0;breakOut==NO && x < width; x++) {
        for (int y = 0; y < height; y++) {
            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                left = CGPointMake(x, y);
                breakOut = YES;
                break;
            }
        }
    }

    breakOut = NO;
    for (int y = 0;breakOut==NO && y < height; y++) {

        for (int x = 0; x < width; x++) {

            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                top = CGPointMake(x, y);
                breakOut = YES;
                break;
            }

        }
    }

    breakOut = NO;
    for (int y = height-1;breakOut==NO && y >= 0; y--) {

        for (int x = width-1; x >= 0; x--) {

            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                bottom = CGPointMake(x, y);
                breakOut = YES;
                break;
            }

        }
    }

    breakOut = NO;
    for (int x = width-1;breakOut==NO && x >= 0; x--) {

        for (int y = height-1; y >= 0; y--) {

            int loc = x + (y * width);
            loc *= 4;
            if (m_PixelBuf[loc + 3] != 0) {
                right = CGPointMake(x, y);
                breakOut = YES;
                break;
            }

        }
    }


    CGRect cropRect = CGRectMake(left.x, top.y, right.x - left.x, bottom.y - top.y);

    UIGraphicsBeginImageContextWithOptions( cropRect.size,
                                           NO,
                                           0.);
    [img drawAtPoint:CGPointMake(-cropRect.origin.x, -cropRect.origin.y)
           blendMode:kCGBlendModeCopy
               alpha:1.];
    UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return croppedImage;
}

【讨论】:

  • 感谢这个脚本。我在我的项目中使用它,但我注意到内存泄漏问题。我认为我们需要调用“CFRelease(m_DataRef);”在“返回croppedImage;”之前
  • 感谢 user404 的有用回复。在您检索到点后,您知道是否有办法对其应用变换?
  • 只是重复我的评论以防其他人正在观看:我刚刚在我的程序中实现了这个 - 这正是我所需要的!但是我在第一个 if (m_PixelBuf[loc + 3] != 0) { 行上得到了 EXC_BAD_ACCESS。任何想法为什么?谢谢
  • @foolishBoy 发现 left,right,top 和 bottom 不正确,当图像小于裁剪矩形时会导致一些黑边。 - i.stack.imgur.com/sFxlr.jpg - i.stack.imgur.com/JOz1Z.jpg
【解决方案3】:

没有巧妙的作弊方法来绕过让设备完成工作,但有一些方法可以加快任务速度,或尽量减少对用户界面的影响。

首先,考虑加速这项任务的需要。通过这个字节数组的简单迭代可能会足够快。如果应用程序每次运行只计算一次,或者响应用户的选择(两次选择之间至少需要几秒钟),则可能无需投资优化此任务。

如果在图像可用后一段时间内不需要边界框,则可以在单独的线程中启动此迭代。这样计算就不会阻塞主界面线程。 Grand Central Dispatch 可能会更轻松地使用单独的线程来完成此任务。

如果必须加速任务,也许这是视频图像的实时处理,那么数据的并行处理可能会有所帮助。 Accelerate 框架可能有助于对数据设置 SIMD 计算。或者,要真正通过此迭代获得性能,使用 NEON SIMD 操作的 ARM 汇编语言代码可以通过大量的开发工作获得很好的结果。

最后的选择是研究更好的算法。在检测图像中的特征方面有大量的工作。边缘检测算法可能比通过字节数组的简单迭代更快。也许苹果将来会在 Core Graphics 中添加边缘检测功能,可以应用于这种情况。 Apple 实现的图像处理功能可能与这种情况不完全匹配,但应优化 Apple 的实现以使用 iPad 的 SIMD 或 GPU 功能,从而获得更好的整体性能。

【讨论】:

    猜你喜欢
    • 2013-07-27
    • 2010-09-14
    • 2012-01-03
    • 2017-06-13
    • 2011-10-06
    • 2017-11-10
    • 2018-07-08
    • 2011-02-07
    相关资源
    最近更新 更多