如何裁剪UIImage?

39

我正在开发一个应用程序,其中使用像素处理图像,但在图像处理过程中需要很长时间。因此,我想裁剪UIImage(仅裁剪图像的中间部分,即去除/裁剪带边框的部分)。我已经编写了以下代码:

- (NSInteger) processImage1: (UIImage*) image
{

 CGFloat width = image.size.width;
 CGFloat height = image.size.height;
 struct pixel* pixels = (struct pixel*) calloc(1, image.size.width * image.size.height * sizeof(struct pixel));
 if (pixels != nil)
 {
  // Create a new bitmap
  CGContextRef context = CGBitmapContextCreate(
              (void*) pixels,
              image.size.width,
              image.size.height,
              8,
              image.size.width * 4,
              CGImageGetColorSpace(image.CGImage),
              kCGImageAlphaPremultipliedLast
              );
  if (context != NULL)
  {
   // Draw the image in the bitmap
   CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, image.size.width, image.size.height), image.CGImage);
   NSUInteger numberOfPixels = image.size.width * image.size.height;

   NSMutableArray *numberOfPixelsArray = [[[NSMutableArray alloc] initWithCapacity:numberOfPixelsArray] autorelease];
}

我如何裁剪UIImage中间部分(裁剪边框外的部分)?

8个回答

82

尝试像这样:

CGImageRef imageRef = CGImageCreateWithImageInRect([largeImage CGImage], cropRect);
image = [UIImage imageWithCGImage:imageRef]; 
CGImageRelease(imageRef);

注意:cropRect是一个较小的矩形,其包含图像中央部分...


抱歉...将XY坐标更改为X +(Oldwidth / 4)和y +(OldHeight / 4)...是我的错误。 - Mihir Mehta
@mihirpmehta, 我认为我需要使用像素处理从您给定的坐标中裁剪图像。但它是如何实现的? - Rajendra Bhole
不幸的是,这会保留原始的UIImage,这可能不是理想的。 - jjxtra
裁剪框 cropRect 可以是任意形状吗? - abh
如果你要裁剪UIImage,你必须考虑到UIImageOrientation。M-V和@dasdom在下面的答案中提供了更简洁的UIImage解决方案。 - CodeBrew
显示剩余4条评论

39

我正在寻找一种获取UIImage的任意矩形裁剪(即子图像)的方法。

我尝试的大多数解决方案都无法处理图像方向不是UIImageOrientationUp的情况。

例如:

http://www.hive05.com/2008/11/crop-an-image-using-the-iphone-sdk/

通常,如果您使用iPhone相机,会得到其他方向,如UIImageOrientationLeft,使用上述代码将无法获得正确的裁剪。这是因为使用CGImageRef / CGContextDrawImage与UIImage的坐标系统有所不同。

以下代码使用UI *方法(没有CGImageRef),我已经测试了上下左右取向的图像,似乎效果很好。

// 获取子图像 - (UIImage*) 从: (UIImage*) img 获取子图像: (CGRect) rect {
UIGraphicsBeginImageContext(rect.size); CGContextRef context = UIGraphicsGetCurrentContext();
// 转换绘制子图像的矩形 CGRect drawRect = CGRectMake(-rect.origin.x, -rect.origin.y, img.size.width, img.size.height);
// 剪切到图像上下文的边界 // 不是必需的,因为它会被裁剪? CGContextClipToRect(context, CGRectMake(0, 0, rect.size.width, rect.size.height));
// 绘制图像 [img drawInRect:drawRect];
// 获取图像 UIImage* subImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return subImage; }

2
请注意,您只能在主线程上运行上述代码。避免使用UIGraphicsBeginImageContext等函数的优点是可以规避此限制。 - William Denniss
1
@William Denniss 你确定吗?http://stackoverflow.com/questions/11528803/is-uigraphicsbeginimagecontext-thread-safe/11530675#11530675 - Klaas
@Klass,你是对的,自从iOS 4以来,你可以在任何线程上调用它。然而,这段代码仍然不是线程安全的,你只能在一个线程上使用UIGraphicsBeginImageContext。文档中指出:“创建基于位图的图形上下文并将其设置为当前上下文。”我认为这个“当前上下文”是全局的,因此不是线程安全的。我的应用程序同时处理多个图像,在多个线程中使用UIGraphicsBeginImageContext时会崩溃,所以我改用了线程安全的CGContextRef - William Denniss
完美!干杯,伙计! - mmsarquis

4

因为我现在需要,这里是用Swift 4编写的M-V代码:

func imageWithImage(image: UIImage, croppedTo rect: CGRect) -> UIImage {

    UIGraphicsBeginImageContext(rect.size)
    let context = UIGraphicsGetCurrentContext()

    let drawRect = CGRect(x: -rect.origin.x, y: -rect.origin.y, 
                          width: image.size.width, height: image.size.height)

    context?.clip(to: CGRect(x: 0, y: 0, 
                             width: rect.size.width, height: rect.size.height))

    image.draw(in: drawRect)

    let subImage = UIGraphicsGetImageFromCurrentImageContext()

    UIGraphicsEndImageContext()
    return subImage!
}

2

#非常简单的Swift 5版本

您不应混合使用UI和CG对象,它们有时具有非常不同的坐标空间。这可能会让您感到沮丧。

注意:self.draw(at:)

@inlinable private prefix func - (right: CGPoint) -> CGPoint
{
    return CGPoint(x: -right.x, y: -right.y)
}

extension UIImage
{
    public func cropped(to cropRect: CGRect) -> UIImage?
    {
           let renderer = UIGraphicsImageRenderer(size: cropRect.size)
        return renderer.image
        {
           _ in
           
            self.draw(at: -cropRect.origin)
        }
    }
}

2
如果您可以设置UIImageView的图像以及要在该UIImage中显示的左上角偏移量,那么最终速度将更快,从sprite纹理集中创建的图像也会少得多。也许这是可能的。这肯定会消除很多努力!
同时,我在一个实用程序类中创建了这些有用的函数,我在我的应用程序中使用它们。它从另一个UIImage的一部分创建UIImage,并提供使用标准UIImageOrientation值旋转、缩放和翻转的选项。像素缩放从原始图像保留。
我的应用程序在初始化期间创建了许多UIImages,这必然需要时间。但是某些图像直到选择某个选项卡才需要。为了给人以更快的加载外观,我可以在启动时创建一个单独的线程来生成它们,然后只需等待在选择该选项卡时完成即可。
此代码还发布在iOS中绘制图像的最有效方法
+ (UIImage*)imageByCropping:(UIImage *)imageToCrop toRect:(CGRect)aperture {
    return [ChordCalcController imageByCropping:imageToCrop toRect:aperture withOrientation:UIImageOrientationUp];
}

// Draw a full image into a crop-sized area and offset to produce a cropped, rotated image
+ (UIImage*)imageByCropping:(UIImage *)imageToCrop toRect:(CGRect)aperture withOrientation:(UIImageOrientation)orientation {

            // convert y coordinate to origin bottom-left
    CGFloat orgY = aperture.origin.y + aperture.size.height - imageToCrop.size.height,
            orgX = -aperture.origin.x,
            scaleX = 1.0,
            scaleY = 1.0,
            rot = 0.0;
    CGSize size;

    switch (orientation) {
        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
            size = CGSizeMake(aperture.size.height, aperture.size.width);
            break;
        case UIImageOrientationDown:
        case UIImageOrientationDownMirrored:
        case UIImageOrientationUp:
        case UIImageOrientationUpMirrored:
            size = aperture.size;
            break;
        default:
            assert(NO);
            return nil;
    }


    switch (orientation) {
        case UIImageOrientationRight:
            rot = 1.0 * M_PI / 2.0;
            orgY -= aperture.size.height;
            break;
        case UIImageOrientationRightMirrored:
            rot = 1.0 * M_PI / 2.0;
            scaleY = -1.0;
            break;
        case UIImageOrientationDown:
            scaleX = scaleY = -1.0;
            orgX -= aperture.size.width;
            orgY -= aperture.size.height;
            break;
        case UIImageOrientationDownMirrored:
            orgY -= aperture.size.height;
            scaleY = -1.0;
            break;
        case UIImageOrientationLeft:
            rot = 3.0 * M_PI / 2.0;
            orgX -= aperture.size.height;
            break;
        case UIImageOrientationLeftMirrored:
            rot = 3.0 * M_PI / 2.0;
            orgY -= aperture.size.height;
            orgX -= aperture.size.width;
            scaleY = -1.0;
            break;
        case UIImageOrientationUp:
            break;
        case UIImageOrientationUpMirrored:
            orgX -= aperture.size.width;
            scaleX = -1.0;
            break;
    }

    // set the draw rect to pan the image to the right spot
    CGRect drawRect = CGRectMake(orgX, orgY, imageToCrop.size.width, imageToCrop.size.height);

    // create a context for the new image
    UIGraphicsBeginImageContextWithOptions(size, NO, imageToCrop.scale);
    CGContextRef gc = UIGraphicsGetCurrentContext();

    // apply rotation and scaling
    CGContextRotateCTM(gc, rot);
    CGContextScaleCTM(gc, scaleX, scaleY);

    // draw the image to our clipped context using the offset rect
    CGContextDrawImage(gc, drawRect, imageToCrop.CGImage);

    // pull the image from our cropped context
    UIImage *cropped = UIGraphicsGetImageFromCurrentImageContext();

    // pop the context to get back to the default
    UIGraphicsEndImageContext();

    // Note: this is autoreleased
    return cropped;
}

谢谢。如果我从 AVFoundation 摄像头中拍摄了旋转图片,那么我发送图片的方向应该是什么? - Dejell

1
使用该函数。
CGContextClipToRect(context, CGRectMake(0, 0, size.width, size.height));

这是一个示例代码,用于不同的目的,但可以剪辑。
- (UIImage *)aspectFillToSize:(CGSize)size
{
    CGFloat imgAspect = self.size.width / self.size.height;
    CGFloat sizeAspect = size.width/size.height;

    CGSize scaledSize;

        if (sizeAspect > imgAspect) { // increase width, crop height
            scaledSize = CGSizeMake(size.width, size.width / imgAspect);
        } else { // increase height, crop width
            scaledSize = CGSizeMake(size.height * imgAspect, size.height);
        }
    UIGraphicsBeginImageContextWithOptions(size, NO, 0.0f);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextClipToRect(context, CGRectMake(0, 0, size.width, size.height));
    [self drawInRect:CGRectMake(0.0f, 0.0f, scaledSize.width, scaledSize.height)];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

0
如果您想要每张照片的中心裁剪,请使用@M-V的解决方案,并替换cropRect。
CGFloat height = imageTaken.size.height;
CGFloat width = imageTaken.size.width;

CGFloat newWidth = height * 9 / 16;
CGFloat newX = abs((width - newWidth)) / 2;

CGRect cropRect = CGRectMake(newX,0, newWidth ,height);

0

我想要能够根据宽高比从一个区域进行裁剪,并根据外部边界的范围进行缩放。这是我的变体:

import AVFoundation
import ImageIO

class Image {

    class func crop(image:UIImage, source:CGRect, aspect:CGSize, outputExtent:CGSize) -> UIImage {

        let sourceRect = AVMakeRectWithAspectRatioInsideRect(aspect, source)
        let targetRect = AVMakeRectWithAspectRatioInsideRect(aspect, CGRect(origin: CGPointZero, size: outputExtent))

        let opaque = true, deviceScale:CGFloat = 0.0 // use scale of device's main screen
        UIGraphicsBeginImageContextWithOptions(targetRect.size, opaque, deviceScale)

        let scale = max(
            targetRect.size.width / sourceRect.size.width,
            targetRect.size.height / sourceRect.size.height)

        let drawRect = CGRect(origin: -sourceRect.origin * scale, size: image.size * scale)
        image.drawInRect(drawRect)

        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return scaledImage
    }
}

有几件事情让我感到困惑,即裁剪和缩放的分离关注点。裁剪是通过传递给drawInRect的矩形的原点来处理的,而缩放则由大小部分处理。在我的情况下,我需要将源上的裁剪矩形的大小与相同宽高比的输出矩形相关联。然后,比例因子为输出/输入,这需要应用于传递给drawInRect的drawRect。

一个注意点是,这种方法实际上假定您绘制的图像大于图像上下文。我没有测试过,但我认为您可以使用此代码来处理裁剪/缩放,但明确定义比例参数为前述比例参数。默认情况下,UIKit会根据屏幕分辨率应用乘数。

最后,值得注意的是,这种UIKit方法比CoreGraphics / Quartz和Core Image方法更高级,并且似乎处理图像方向问题。还值得一提的是,根据这篇文章,它非常快,仅次于ImageIO:http://nshipster.com/image-resizing/


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