J'ai du code qui redimensionne une image pour pouvoir obtenir une partie mise à l'échelle du centre de l'image. Je l'utilise pour prendre une variable UIImage
et renvoyer une petite représentation carrée d'une image, similaire à celle vue dans la vue de l'album de l'application Photos. (Je sais que je pourrais utiliser UIImageView
et ajuster le mode de recadrage pour obtenir les mêmes résultats, mais ces images sont parfois affichées dans UIWebViews
).
J'ai commencé à remarquer des plantages dans ce code et je suis un peu perplexe. J'ai deux théories différentes et je me demande si l'une ou l'autre est fondée.
Théorie 1) Je réalise le recadrage en dessinant dans un contexte d'image hors écran de la taille de ma cible. Puisque je veux la partie centrale de l'image, j'ai défini l'argument CGRect
passé à drawInRect
sur une valeur plus grande que les limites du contexte de mon image. J'espérais que c'était cacher, mais est-ce que j'essaie plutôt de puiser dans une mémoire que je ne devrais pas toucher?
Théorie 2) Je fais tout cela dans un fil de fond. Je sais qu'il y a des portions d'UIKit qui sont limitées au fil principal. J'avais supposé/espéré que le passage à une vue hors écran ne faisait pas partie de celles-ci. Ai-je tort?
(Oh, comme je manque la méthode NSImage's drawInRect:fromRect:operation:fraction:
.)
Mise à jour du 28/05/14: J'ai écrit ceci lorsque iOS 3 ou plus était la nouvelle chose à la mode, je suis certain qu'il existe de meilleures façons de le faire maintenant, éventuellement intégrée. Comme beaucoup de personnes l'ont mentionné, cette méthode ne prend pas en compte la rotation; lisez quelques réponses supplémentaires et diffusez un peu d'amour positif pour que les réponses à cette question restent utiles à tous.
Réponse originale:
Je vais copier/coller ma réponse à la même question ailleurs:
Il n’existe pas de méthode de classe simple pour le faire, mais une fonction que vous pouvez utiliser pour obtenir les résultats souhaités: CGImageCreateWithImageInRect(CGImageRef, CGRect)
vous aidera.
Voici un exemple court d'utilisation:
CGImageRef imageRef = CGImageCreateWithImageInRect([largeImage CGImage], cropRect);
// or use the UIImage wherever you like
[UIImageView setImage:[UIImage imageWithCGImage:imageRef]];
CGImageRelease(imageRef);
Pour rogner des images de la rétine tout en conservant la même échelle et la même orientation, utilisez la méthode suivante dans une catégorie UIImage (iOS 4.0 et versions ultérieures):
- (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;
}
Vous pouvez créer une catégorie UIImage et l’utiliser où vous le souhaitez. Basé sur la réponse HitScans et les commentaires ci-dessous.
@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
Vous pouvez l'utiliser de cette façon:
UIImage *imageToCrop = <yourImageToCrop>;
CGRect cropRect = <areaYouWantToCrop>;
//for example
//CGRectMake(0, 40, 320, 100);
UIImage *croppedImage = [imageToCrop crop:cropRect];
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
avec l'aide de cette fonction de pont CGRectMake
-> CGRect
(crédits à cette réponse répondu par @rob mayoff
):
func CGRectMake(_ x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) -> CGRect {
return CGRect(x: x, y: y, width: width, height: height)
}
L'utilisation est:
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)
)
}
Sortie:
Voici ma mise en œuvre de recadrage UIImage qui obéit à la propriété imageOrientation. Toutes les orientations ont été minutieusement testées.
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;
}
Heads up: toutes ces réponses supposent un objet image CGImage
- backed.
image.CGImage
peut renvoyer nil, si la UIImage
est appuyée par une CIImage
, ce qui serait le cas si vous créiez cette image avec une CIFilter
.
Dans ce cas, vous devrez peut-être dessiner l'image dans un nouveau contexte et renvoyer cette image ( slow ).
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;
}
Aucune des réponses ici ne gère correctement tous les problèmes d’échelle et de rotation. Voici une synthèse de tout ce qui a été dit jusqu'à présent, à jour depuis iOS7/8. Il est censé être inclus en tant que méthode dans une catégorie sur 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 plupart des réponses que j'ai vues concernent uniquement une position de (0, 0) pour (x, y). Ok C'est un cas, mais j'aimerais que mon opération de recadrage soit centrée. Ce qui m'a pris un certain temps à comprendre, c'est la ligne qui suit le commentaire de la WTF.
Prenons le cas d'une image capturée avec une orientation portrait:
J'espère que cela a du sens! Si ce n'est pas le cas, essayez différentes valeurs et vous verrez que la logique est inversée lorsqu'il s'agit de choisir les bons paramètres x, y, largeur et hauteur pour votre cropRect.
Extension rapide
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
}
}
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)
}
}
Version rapide de la réponse de awolf
, qui a fonctionné pour moi:
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
}
}
Meilleure solution pour recadrer un UIImage dans Swift, en terme de précision, de mise à l'échelle des pixels ...:
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
}
Instructions utilisées pour appeler cette fonction:
let image: UIImage = UIImage(named: "Image.jpg")!
let squareImage: UIImage = self.squareCropImageToSideLength(image, sideLength: 320)
self.myUIImageView.image = squareImage
Remarque: l'inspiration initiale du code source écrite en Objective-C a été trouvée sur le blog "Cocoanetics".
Cela semble un peu étrange mais fonctionne très bien et prend en compte l'orientation de l'image:
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;
}
Voici une version mise à jour de Swift 3 basée sur Noodles answer
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
}
Sur iOS9.2SDK, j’utilise la méthode ci-dessous pour convertir une image de UIView à 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;
}
(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;
}
Swift 2.0 Update (compatibilité CIImage
)
Développer en dehors de Maxim's Answer mais fonctionne si votre image est aussi basée sur 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
}
}
Suivre la réponse de @Arne . I Fixant simplement la fonction de catégorie. mettez-le dans la catégorie 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;
}
J'utilise la méthode ci-dessous.
-(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;
}
Swift 5:
extension UIImage {
func cropped(rect: CGRect) -> UIImage? {
guard let cgImage = cgImage else { return nil }
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0)
let context = UIGraphicsGetCurrentContext()
context?.translateBy(x: 0.0, y: self.size.height)
context?.scaleBy(x: 1.0, y: -1.0)
context?.draw(cgImage, in: CGRect(x: rect.minX, y: rect.minY, width: self.size.width, height: self.size.height), byTiling: false)
let croppedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return croppedImage
}
}
Regardez 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, .__ self.cropSize.width * scale, self.cropSize.height * scale); CGImageRef contextImage = CGImageCreateWithImageInRect ([[self imageWithRotation: self.sourceImage] CGImage], targetFrame); if (contextImage! = NULL) { finalImage = [UIImage imageWithCGImage: contextImage scale: self.sourceImage.scale orientation: UIImageOrientationUp]; CGImageRelease (contextImage); } return finalImage; } - (UIImage *) imageWithRotation: (UIImage *) image { if (image.imageOrientation == UIImageOrientationUp) renvoie l'image; CGAffineTransform transform = CGAffineTransformIdentity; commutateur (image.imageOrientation) { case UIImageOrientationDown: case UIImageOrientationDownMirrored: transform = CGAffineTransformTranslate (transform, image.size.width, image.size.height); transform = CGAffineTransformRotate (transform, M_PI); Pause; case UIImageOrientationLeft: case UIImageOrientationLeftMirrored: transform = CGAffineTransformTranslate (transform, image.size.width, 0); transform = CGAffineTransformRotate (transform, M_PI_2); Pause; case UIImageOrientationRight: case UIImageOrientationRightMirrored: transform = CGAffineTransformTranslate (transform, 0, image.size.height); transform = CGAffineTransformRotate (transform, -M_PI_2); Pause; case UIImageOrientationUp: case UIImageOrientationUpMirrored: Pause; } commutateur (image.imageOrientation) { case UIImageOrientationUpMirrored: case UIImageOrientationDownMirrored: transform = CGAffineTransformTranslate (transform, image.size.width, 0); transform = CGAffineTransformScale (transform, -1, 1); Pause; case UIImageOrientationLeftMirrored: case UIImageOrientationRightMirrored: transform = CGAffineTransformTranslate (transform, image.size.height, 0); transform = CGAffineTransformScale (transform, -1, 1); Pause; case UIImageOrientationUp: case UIImageOrientationDown: case UIImageOrientationLeft: case UIImageOrientationRight: Pause; } // Maintenant, nous dessinons l'image CGImage sous-jacente dans un nouveau contexte, en appliquant la transformation // calculé ci-dessus . CGContextRef ctx = CGBitmapContextCreate (NULL, image.size.width, image.size.height, CGImageGetBitsPerComponent (image.CGImage), 0, CGImageGetColorSpace (image.CGImage), ); CGContextConcatCTM (ctx, transform); commutateur (image.imageOrientation) { case UIImageOrientationLeft: case UIImageOrientationLeftMirrored: case UIImageOrientationRight: case UIImageOrientationRightMirrored: // Grr ... CGContextDrawImage (ctx, CGRectMake (0, 0, image.size.height, image.size.width), image.CGImage); Pause; défaut: CGContextDrawImage (ctx, CGRectMake (0, 0, image.size.width, image.size.height), image.CGImage); Pause; } // Et maintenant, nous venons de créer une nouvelle UIImage à partir du contexte de dessin CGImageRef cgimg = CGBitmapContextCreateImage (ctx); UIImage * img = [UIImage imageWithCGImage: cgimg]; CGContextRelease (ctx); CGImageRelease (cgimg); return img; }
L'extrait de code ci-dessous peut aider.
import UIKit
extension UIImage {
func cropImage(toRect rect: CGRect) -> UIImage? {
if let imageRef = self.cgImage?.cropping(to: rect) {
return UIImage(cgImage: imageRef)
}
return nil
}
}
Je n'étais pas satisfait des autres solutions car elles prenaient plusieurs fois (consommant plus d'énergie que nécessaire) ou rencontraient des problèmes d'orientation. Voici ce que j'ai utilisé pour une image carrée redimensionnée à partir d'une image 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();