Recortando un UIImage

Tengo un código que cambia el tamaño de una imagen para que pueda obtener una porción escalada del centro de la imagen. Lo uso para tomar un UIImage y devolver una representación pequeña y cuadrada de una imagen, similar a lo que se ve en la vista del álbum de la aplicación Fotos. (Sé que podría usar un UIImageView y ajustar el modo de recorte para lograr los mismos resultados, pero estas imágenes a veces se muestran en UIWebViews ).

Empecé a notar algunos lockings en este código y estoy un poco perplejo. Tengo dos teorías diferentes y me pregunto si alguna de ellas está en la base.

Teoría 1) Logré el recorte dibujando en un contexto de imagen fuera de pantalla de mi tamaño objective. Como quiero la parte central de la imagen, establecí el argumento CGRect pasado a drawInRect a algo que es más grande que los límites del contexto de mi imagen. Esperaba que fuera kosher, pero ¿estoy intentando dibujar otro recuerdo que no debería tocar?

Teoría 2) Estoy haciendo todo esto en un hilo de fondo. Sé que hay porciones de UIKit que están restringidas al hilo principal. Asumía / esperaba que dibujar fuera de pantalla no fuera uno de estos. ¿Me equivoco?

(Ah, cómo echo de NSImage's drawInRect:fromRect:operation:fraction: )

Actualización 29-05-2014: escribí esto cuando iOS 3 o algo así era algo nuevo y novedoso, estoy seguro de que hay mejores formas de hacerlo ahora, posiblemente con una función incorporada. Como muchas personas han mencionado, este método no tiene en cuenta la rotación; lea algunas respuestas adicionales y difunda un poco de amor para evitar que las respuestas a esta pregunta sean útiles para todos.

Respuesta original:

Voy a copiar / pegar mi respuesta a la misma pregunta en otro lugar:

No hay un método de clase simple para hacer esto, pero hay una función que puede usar para obtener los resultados deseados: CGImageCreateWithImageInRect(CGImageRef, CGRect) lo ayudará.

Aquí hay un breve ejemplo que lo usa:

 CGImageRef imageRef = CGImageCreateWithImageInRect([largeImage CGImage], cropRect); // or use the UIImage wherever you like [UIImageView setImage:[UIImage imageWithCGImage:imageRef]]; CGImageRelease(imageRef); 

Para recortar imágenes retina manteniendo la misma escala y orientación, use el siguiente método en una categoría UIImage (iOS 4.0 y superior):

 - (UIImage *)crop:(CGRect)rect { if (self.scale > 1.0f) { rect = CGRectMake(rect.origin.x * self.scale, rect.origin.y * self.scale, rect.size.width * self.scale, rect.size.height * self.scale); } CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, rect); UIImage *result = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation]; CGImageRelease(imageRef); return result; } 

Puedes hacer una categoría de UIImage y usarla donde necesites. En base a la respuesta de HitScans y los comentarios a continuación.

 @implementation UIImage (Crop) - (UIImage *)crop:(CGRect)rect { rect = CGRectMake(rect.origin.x*self.scale, rect.origin.y*self.scale, rect.size.width*self.scale, rect.size.height*self.scale); CGImageRef imageRef = CGImageCreateWithImageInRect([self CGImage], rect); UIImage *result = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation]; CGImageRelease(imageRef); return result; } @end 

Puedes usarlo de esta manera:

 UIImage *imageToCrop = ; CGRect cropRect = ; //for example //CGRectMake(0, 40, 320, 100); UIImage *croppedImage = [imageToCrop crop:cropRect]; 

Aquí está mi implementación de cultivo UIImage que obedece a la propiedad imageOrientation. Todas las orientaciones fueron probadas a fondo.

 inline double rad(double deg) { return deg / 180.0 * M_PI; } UIImage* UIImageCrop(UIImage* img, CGRect rect) { CGAffineTransform rectTransform; switch (img.imageOrientation) { case UIImageOrientationLeft: rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(90)), 0, -img.size.height); break; case UIImageOrientationRight: rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-90)), -img.size.width, 0); break; case UIImageOrientationDown: rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-180)), -img.size.width, -img.size.height); break; default: rectTransform = CGAffineTransformIdentity; }; rectTransform = CGAffineTransformScale(rectTransform, img.scale, img.scale); CGImageRef imageRef = CGImageCreateWithImageInRect([img CGImage], CGRectApplyAffineTransform(rect, rectTransform)); UIImage *result = [UIImage imageWithCGImage:imageRef scale:img.scale orientation:img.imageOrientation]; CGImageRelease(imageRef); return result; } 

Swift 3 versión

 func cropImage(imageToCrop:UIImage, toRect rect:CGRect) -> UIImage{ let imageRef:CGImage = imageToCrop.cgImage!.cropping(to: rect)! let cropped:UIImage = UIImage(cgImage:imageRef) return cropped } let imageTop:UIImage = UIImage(named:"one.jpg")! // add validation 

enter image description here

con la ayuda de esta función puente CGRectMake -> CGRect (créditos a esta respuesta respondidos por @rob mayoff ):

  func CGRectMake(_ x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) -> CGRect { return CGRect(x: x, y: y, width: width, height: height) } 

El uso es:

 if var image:UIImage = UIImage(named:"one.jpg"){ let croppedImage = cropImage(imageToCrop: image, toRect: CGRectMake( image.size.width/4, 0, image.size.width/2, image.size.height) ) } 

Salida:

enter image description here

Heads up: todas estas respuestas asumen un objeto de imagen CGImage CGImage.

image.CGImage puede devolver cero, si el UIImage está respaldado por un CIImage , que sería el caso si creara esta imagen usando un CIFilter .

En ese caso, es posible que tenga que dibujar la imagen en un nuevo contexto y devolver esa imagen ( lento ).

 UIImage* crop(UIImage *image, rect) { UIGraphicsBeginImageContextWithOptions(rect.size, false, [image scale]); [image drawAtPoint:CGPointMake(-rect.origin.x, -rect.origin.y)]; cropped_image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return cropped_image; } 

Ninguna de las respuestas maneja todos los problemas de escala y rotación 100% correctamente. Aquí hay una síntesis de todo lo dicho hasta ahora, actualizado a partir de iOS7 / 8. Está destinado a ser incluido como un método en una categoría en UIImage.

 - (UIImage *)croppedImageInRect:(CGRect)rect { double (^rad)(double) = ^(double deg) { return deg / 180.0 * M_PI; }; CGAffineTransform rectTransform; switch (self.imageOrientation) { case UIImageOrientationLeft: rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(90)), 0, -self.size.height); break; case UIImageOrientationRight: rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-90)), -self.size.width, 0); break; case UIImageOrientationDown: rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-180)), -self.size.width, -self.size.height); break; default: rectTransform = CGAffineTransformIdentity; }; rectTransform = CGAffineTransformScale(rectTransform, self.scale, self.scale); CGImageRef imageRef = CGImageCreateWithImageInRect([self CGImage], CGRectApplyAffineTransform(rect, rectTransform)); UIImage *result = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation]; CGImageRelease(imageRef); return result; } 
 CGSize size = [originalImage size]; int padding = 20; int pictureSize = 300; int startCroppingPosition = 100; if (size.height > size.width) { pictureSize = size.width - (2.0 * padding); startCroppingPosition = (size.height - pictureSize) / 2.0; } else { pictureSize = size.height - (2.0 * padding); startCroppingPosition = (size.width - pictureSize) / 2.0; } // WTF: Don't forget that the CGImageCreateWithImageInRect believes that // the image is 180 rotated, so x and y are inverted, same for height and width. CGRect cropRect = CGRectMake(startCroppingPosition, padding, pictureSize, pictureSize); CGImageRef imageRef = CGImageCreateWithImageInRect([originalImage CGImage], cropRect); UIImage *newImage = [UIImage imageWithCGImage:imageRef scale:1.0 orientation:originalImage.imageOrientation]; [m_photoView setImage:newImage]; CGImageRelease(imageRef); 

La mayoría de las respuestas que he visto solo se refieren a una posición de (0, 0) para (x, y). Ok, ese es un caso, pero me gustaría que mi operación de recorte esté centrada. Lo que me llevó un tiempo descifrar es la línea que sigue al comentario de la WTF.

Tomemos el caso de una imagen capturada con una orientación vertical:

  1. La altura de la imagen original es mayor que su ancho (Woo, no es sorpresa hasta ahora!)
  2. La imagen que el método CGImageCreateWithImageInRect imagina en su propio mundo no es realmente un retrato, sino un paisaje (también es por eso que si no usas el argumento de orientación en el constructor imageWithCGImage, aparecerá como 180 rotados).
  3. Entonces, debe imaginarse que es un paisaje, la posición (0, 0) es la esquina superior derecha de la imagen.

Espero que tenga sentido! Si no lo hace, pruebe con diferentes valores, verá que la lógica se invierte cuando se trata de elegir la x, y, el ancho y la altura correctos para su cropRect.

Swift Extension

 extension UIImage { func crop(var rect: CGRect) -> UIImage { rect.origin.x*=self.scale rect.origin.y*=self.scale rect.size.width*=self.scale rect.size.height*=self.scale let imageRef = CGImageCreateWithImageInRect(self.CGImage, rect) let image = UIImage(CGImage: imageRef, scale: self.scale, orientation: self.imageOrientation)! return image } } 

swift3

 extension UIImage { func crop(rect: CGRect) -> UIImage? { var scaledRect = rect scaledRect.origin.x *= scale scaledRect.origin.y *= scale scaledRect.size.width *= scale scaledRect.size.height *= scale guard let imageRef: CGImage = cgImage?.cropping(to: scaledRect) else { return nil } return UIImage(cgImage: imageRef, scale: scale, orientation: imageOrientation) } } 

Versión awolf de la respuesta de awolf , que funcionó para mí:

 public extension UIImage { func croppedImage(inRect rect: CGRect) -> UIImage { let rad: (Double) -> CGFloat = { deg in return CGFloat(deg / 180.0 * .pi) } var rectTransform: CGAffineTransform switch imageOrientation { case .left: let rotation = CGAffineTransform(rotationAngle: rad(90)) rectTransform = rotation.translatedBy(x: 0, y: -size.height) case .right: let rotation = CGAffineTransform(rotationAngle: rad(-90)) rectTransform = rotation.translatedBy(x: -size.width, y: 0) case .down: let rotation = CGAffineTransform(rotationAngle: rad(-180)) rectTransform = rotation.translatedBy(x: size.width, y: -size.height) default: rectTransform = .identity } rectTransform = rectTransform.scaledBy(x: scale, y: scale) let transformedRect = rect.applying(rectTransform) let imageRef = cgImage!.cropping(to: transformedRect)! let result = UIImage(cgImage: imageRef, scale: scale, orientation: imageOrientation) return result } } 

La mejor solución para recortar un UIImage en Swift , en términos de precisión, escalado de píxeles …:

 private func squareCropImageToSideLength(let sourceImage: UIImage, let sideLength: CGFloat) -> UIImage { // input size comes from image let inputSize: CGSize = sourceImage.size // round up side length to avoid fractional output size let sideLength: CGFloat = ceil(sideLength) // output size has sideLength for both dimensions let outputSize: CGSize = CGSizeMake(sideLength, sideLength) // calculate scale so that smaller dimension fits sideLength let scale: CGFloat = max(sideLength / inputSize.width, sideLength / inputSize.height) // scaling the image with this scale results in this output size let scaledInputSize: CGSize = CGSizeMake(inputSize.width * scale, inputSize.height * scale) // determine point in center of "canvas" let center: CGPoint = CGPointMake(outputSize.width/2.0, outputSize.height/2.0) // calculate drawing rect relative to output Size let outputRect: CGRect = CGRectMake(center.x - scaledInputSize.width/2.0, center.y - scaledInputSize.height/2.0, scaledInputSize.width, scaledInputSize.height) // begin a new bitmap context, scale 0 takes display scale UIGraphicsBeginImageContextWithOptions(outputSize, true, 0) // optional: set the interpolation quality. // For this you need to grab the underlying CGContext let ctx: CGContextRef = UIGraphicsGetCurrentContext() CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh) // draw the source image into the calculated rect sourceImage.drawInRect(outputRect) // create new image from bitmap context let outImage: UIImage = UIGraphicsGetImageFromCurrentImageContext() // clean up UIGraphicsEndImageContext() // pass back new image return outImage } 

Instrucciones utilizadas para llamar a esta función:

 let image: UIImage = UIImage(named: "Image.jpg")! let squareImage: UIImage = self.squareCropImageToSideLength(image, sideLength: 320) self.myUIImageView.image = squareImage 

Nota: la inspiración del código fuente inicial escrita en Objective-C se ha encontrado en el blog “Cocoanetics”.

Parece un poco extraño pero funciona muy bien y toma en consideración la orientación de la imagen:

 var image:UIImage = ... let img = CIImage(image: image)!.imageByCroppingToRect(rect) image = UIImage(CIImage: img, scale: 1, orientation: image.imageOrientation) 
 - (UIImage *)getSubImage:(CGRect) rect{ CGImageRef subImageRef = CGImageCreateWithImageInRect(self.CGImage, rect); CGRect smallBounds = CGRectMake(rect.origin.x, rect.origin.y, CGImageGetWidth(subImageRef), CGImageGetHeight(subImageRef)); UIGraphicsBeginImageContext(smallBounds.size); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextDrawImage(context, smallBounds, subImageRef); UIImage* smallImg = [UIImage imageWithCGImage:subImageRef]; UIGraphicsEndImageContext(); return smallImg; } 
  (UIImage *)squareImageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize { double ratio; double delta; CGPoint offset; //make a new square size, that is the resized imaged width CGSize sz = CGSizeMake(newSize.width, newSize.width); //figure out if the picture is landscape or portrait, then //calculate scale factor and offset if (image.size.width > image.size.height) { ratio = newSize.width / image.size.width; delta = (ratio*image.size.width - ratio*image.size.height); offset = CGPointMake(delta/2, 0); } else { ratio = newSize.width / image.size.height; delta = (ratio*image.size.height - ratio*image.size.width); offset = CGPointMake(0, delta/2); } //make the final clipping rect based on the calculated values CGRect clipRect = CGRectMake(-offset.x, -offset.y, (ratio * image.size.width) + delta, (ratio * image.size.height) + delta); //start a new context, with scale factor 0.0 so retina displays get //high quality image if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) { UIGraphicsBeginImageContextWithOptions(sz, YES, 0.0); } else { UIGraphicsBeginImageContext(sz); } UIRectClip(clipRect); [image drawInRect:clipRect]; UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return newImage; } 

En iOS9.2SDK, utilizo el siguiente método para convertir fotogtwigs de UIView a UIImage

 -(UIImage *)getNeedImageFrom:(UIImage*)image cropRect:(CGRect)rect { CGSize cropSize = rect.size; CGFloat widthScale = image.size.width/self.imageViewOriginal.bounds.size.width; CGFloat heightScale = image.size.height/self.imageViewOriginal.bounds.size.height; cropSize = CGSizeMake(rect.size.width*widthScale, rect.size.height*heightScale); CGPoint pointCrop = CGPointMake(rect.origin.x*widthScale, rect.origin.y*heightScale); rect = CGRectMake(pointCrop.x, pointCrop.y, cropSize.width, cropSize.height); CGImageRef subImage = CGImageCreateWithImageInRect(image.CGImage, rect); UIImage *croppedImage = [UIImage imageWithCGImage:subImage]; CGImageRelease(subImage); return croppedImage; } 

Actualización de Swift 2.0 (compatibilidad con CIImage )

Se expande de Maxim’s Answer pero funciona si su imagen también está basada en CIImage .

 public extension UIImage { func imageByCroppingToRect(rect: CGRect) -> UIImage? { if let image = CGImageCreateWithImageInRect(self.CGImage, rect) { return UIImage(CGImage: image) } else if let image = (self.CIImage)?.imageByCroppingToRect(rect) { return UIImage(CIImage: image) } return nil } } 

Aquí hay una versión actualizada de Swift 3 basada en la respuesta de Noodles

 func cropping(to rect: CGRect) -> UIImage? { if let cgCrop = cgImage?.cropping(to: rect) { return UIImage(cgImage: cgCrop) } else if let ciCrop = ciImage?.cropping(to: rect) { return UIImage(ciImage: ciCrop) } return nil } 

No estaba satisfecho con otras soluciones, ya sea porque dibujan varias veces (usando más energía de la necesaria) o tienen problemas de orientación. Aquí está lo que utilicé para una imagen recortada cuadrada a escala de una imagen de UIImage *.

 CGFloat minimumSide = fminf(image.size.width, image.size.height); CGFloat finalSquareSize = 600.; //create new drawing context for right size CGRect rect = CGRectMake(0, 0, finalSquareSize, finalSquareSize); CGFloat scalingRatio = 640.0/minimumSide; UIGraphicsBeginImageContext(rect.size); //draw [image drawInRect:CGRectMake((minimumSide - photo.size.width)*scalingRatio/2., (minimumSide - photo.size.height)*scalingRatio/2., photo.size.width*scalingRatio, photo.size.height*scalingRatio)]; UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); 

Yo uso el método a continuación.

  -(UIImage *)getNeedImageFrom:(UIImage*)image cropRect:(CGRect)rect { CGSize cropSize = rect.size; CGFloat widthScale = image.size.width/self.imageViewOriginal.bounds.size.width; CGFloat heightScale = image.size.height/self.imageViewOriginal.bounds.size.height; cropSize = CGSizeMake(rect.size.width*widthScale, rect.size.height*heightScale); CGPoint pointCrop = CGPointMake(rect.origin.x*widthScale, rect.origin.y*heightScale); rect = CGRectMake(pointCrop.x, pointCrop.y, cropSize.width, cropSize.height); CGImageRef subImage = CGImageCreateWithImageInRect(image.CGImage, rect); UIImage *croppedImage = [UIImage imageWithCGImage:subImage]; CGImageRelease(subImage); return croppedImage; 

}

Mira https://github.com/vvbogdan/BVCropPhoto

 - (UIImage *) croppedImage {
     CGFloat scale = self.sourceImage.size.width / self.scrollView.contentSize.width;

     UIImage * finalImage = nil;
     CGRect targetFrame = CGRectMake ((self.scrollView.contentInset.left + self.scrollView.contentOffset.x) * scale,
             (self.scrollView.contentInset.top + self.scrollView.contentOffset.y) * scale,
             escala self.cropSize.width *,
             escala self.cropSize.height *);

     CGImageRef contextImage = CGImageCreateWithImageInRect ([[self imageWithRotation: self.sourceImage] CGImage], targetFrame);

     if (contextImage! = NULL) {
         finalImage = [UIImage imageWithCGImage: contextImage
                                          scale: self.sourceImage.scale
                                    orientación: UIImageOrientationUp];

         CGImageRelease (contextImage);
     }

     return finalImage;
 }


 - (UIImage *) imageWithRotation: (UIImage *) image {


     if (image.imageOrientation == UIImageOrientationUp) devolver imagen;
     CGAffineTransform transform = CGAffineTransformIdentity;

     switch (image.imageOrientation) {
         caso UIImageOrientationDown:
         caso UIImageOrientationDownMirrored:
             transform = CGAffineTransformTranslate (transform, image.size.width, image.size.height);
             transform = CGAffineTransformRotate (transform, M_PI);
             descanso;

         caso UIImageOrientationLeft:
         caso UIImageOrientationLeftMirrored:
             transform = CGAffineTransformTranslate (transform, image.size.width, 0);
             transform = CGAffineTransformRotate (transform, M_PI_2);
             descanso;

         caso UIImageOrientationRight:
         caso UIImageOrientationRightMirrored:
             transform = CGAffineTransformTranslate (transform, 0, image.size.height);
             transform = CGAffineTransformRotate (transform, -M_PI_2);
             descanso;
         caso UIImageOrientationUp:
         caso UIImageOrientationUpMirrored:
             descanso;
     }

     switch (image.imageOrientation) {
         caso UIImageOrientationUpMirrored:
         caso UIImageOrientationDownMirrored:
             transform = CGAffineTransformTranslate (transform, image.size.width, 0);
             transform = CGAffineTransformScale (transform, -1, 1);
             descanso;

         caso UIImageOrientationLeftMirrored:
         caso UIImageOrientationRightMirrored:
             transform = CGAffineTransformTranslate (transform, image.size.height, 0);
             transform = CGAffineTransformScale (transform, -1, 1);
             descanso;
         caso UIImageOrientationUp:
         caso UIImageOrientationDown:
         caso UIImageOrientationLeft:
         caso UIImageOrientationRight:
             descanso;
     }

     // Ahora dibujamos el CGImage subyacente en un nuevo contexto, aplicando la transformación
     // calculado arriba.
     CGContextRef ctx = CGBitmapContextCreate (NULL, image.size.width, image.size.height,
             CGImageGetBitsPerComponent (image.CGImage), 0,
             CGImageGetColorSpace (image.CGImage),
             CGImageGetBitmapInfo (image.CGImage));
     CGContextConcatCTM (ctx, transformar);
     switch (image.imageOrientation) {
         caso UIImageOrientationLeft:
         caso UIImageOrientationLeftMirrored:
         caso UIImageOrientationRight:
         caso UIImageOrientationRightMirrored:
             // Grr ...
             CGContextDrawImage (ctx, CGRectMake (0, 0, image.size.height, image.size.width), image.CGImage);
             descanso;

         defecto:
             CGContextDrawImage (ctx, CGRectMake (0, 0, image.size.width, image.size.height), image.CGImage);
             descanso;
     }

     // Y ahora solo creamos un nuevo UIImage desde el contexto del dibujo
     CGImageRef cgimg = CGBitmapContextCreateImage (ctx);
     UIImage * img = [UIImage imageWithCGImage: cgimg];
     CGContextRelease (ctx);
     CGImageRelease (cgimg);
     devolver img;

 }

Sigue la respuesta de @Arne. Solo estoy arreglando la función Categoría. ponlo en la Categoría de UIImage.

 -(UIImage*)cropImage:(CGRect)rect{ UIGraphicsBeginImageContextWithOptions(rect.size, false, [self scale]); [self drawAtPoint:CGPointMake(-rect.origin.x, -rect.origin.y)]; UIImage* cropped_image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return cropped_image; }