2009-03-02 4 views
184

Я уже бил свое лицо в этот день буквально через несколько дней, и хотя я постоянно чувствую, что нахожусь на краю откровения, я просто не могу достичь своей цели.UIImage: Resize, затем Crop

Я заранее подумал о концептуальных этапах моего дизайна, что было бы тривиальным вопросом захватить изображение с камеры или библиотеки iPhone, уменьшить масштаб до указанной высоты, используя функцию, эквивалентную Аспект Fill вариант UIImageView (полностью в коде), а затем обведите все, что не соответствовало принятому CGRect.

Получение оригинального изображения с камеры или библиотеки было тривиальным. Я шокирован тем, насколько трудны другие два шага.

Прилагаемое изображение показывает, чего я пытаюсь достичь. Кто-нибудь, пожалуйста, будьте любезны, чтобы держать меня за руку? Каждый пример кода, который я нашел до сих пор, кажется, разбивает изображение, перевернутый, выглядит как дерьмо, вытягивается за пределы или иначе работает неправильно.

+7

Ссылка сломана к изображению. – 2012-09-12 09:58:44

+1

Одна вещь, если нас не шокирует, почему Apple не добавила ее в UIImagePickerController - ее слишком штопает сложно ;-) – Tim

ответ

244

Мне нужно было то же самое - в моем случае, выбрать размер, который подходит после масштабирования, а затем обрезать каждый конец, чтобы соответствовать остальным по ширине. (Я работаю в пейзаже, поэтому, возможно, не заметил никаких недостатков в портретном режиме.) Вот мой код - это часть катеджиора на UIImage. Размер цели в моем коде всегда установлен на весь размер экрана устройства.

@implementation UIImage (Extras) 

#pragma mark - 
#pragma mark Scale and crop image 

- (UIImage*)imageByScalingAndCroppingForSize:(CGSize)targetSize 
{ 
    UIImage *sourceImage = self; 
    UIImage *newImage = nil;  
    CGSize imageSize = sourceImage.size; 
    CGFloat width = imageSize.width; 
    CGFloat height = imageSize.height; 
    CGFloat targetWidth = targetSize.width; 
    CGFloat targetHeight = targetSize.height; 
    CGFloat scaleFactor = 0.0; 
    CGFloat scaledWidth = targetWidth; 
    CGFloat scaledHeight = targetHeight; 
    CGPoint thumbnailPoint = CGPointMake(0.0,0.0); 

    if (CGSizeEqualToSize(imageSize, targetSize) == NO) 
    { 
     CGFloat widthFactor = targetWidth/width; 
     CGFloat heightFactor = targetHeight/height; 

     if (widthFactor > heightFactor) 
     { 
      scaleFactor = widthFactor; // scale to fit height 
     } 
     else 
     { 
      scaleFactor = heightFactor; // scale to fit width 
     } 

     scaledWidth = width * scaleFactor; 
     scaledHeight = height * scaleFactor; 

     // center the image 
     if (widthFactor > heightFactor) 
     { 
      thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5; 
     } 
     else 
     { 
      if (widthFactor < heightFactor) 
      { 
       thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5; 
      } 
     } 
    } 

    UIGraphicsBeginImageContext(targetSize); // this will crop 

    CGRect thumbnailRect = CGRectZero; 
    thumbnailRect.origin = thumbnailPoint; 
    thumbnailRect.size.width = scaledWidth; 
    thumbnailRect.size.height = scaledHeight; 

    [sourceImage drawInRect:thumbnailRect]; 

    newImage = UIGraphicsGetImageFromCurrentImageContext(); 

    if(newImage == nil) 
    { 
     NSLog(@"could not scale image"); 
    } 

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

    return newImage; 
} 
+1

Любопытно, что он работает в симуляторе, но на устройстве я получаю ExecBadAccess .. –

+0

То же самое здесь. Когда я перешел на устройство, я получил прерывистые ошибки. приведенный ниже код, предоставленный Брэдом Лоусоном, работает аналогично, без ошибок. – mmc

+5

Проблема с неудачным exec происходит, потому что функции UIImage не являются потокобезопасными. Thats почему это sometemes аварии, иногда не – Erik

77

Старая почта содержит код для способа изменения размера вашего UIImage. Соответствующая часть выглядит следующим образом:

+ (UIImage*)imageWithImage:(UIImage*)image 
       scaledToSize:(CGSize)newSize; 
{ 
    UIGraphicsBeginImageContext(newSize); 
    [image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)]; 
    UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext(); 
    UIGraphicsEndImageContext(); 

    return newImage; 
} 

Насколько кадрирование, то я считаю, что если вы изменили способ, чтобы использовать другой размер для масштабирования, чем для контекста, ваше полученное изображение должно быть обрезан к границам контекста.

+3

НЕ УКАЗЫВАЕТ, почему это исправляет ориентацию изображения, но это происходит, и поэтому она исправила мою проблему с камерой не возвращая правильную ориентацию в 'originalImage'. Благодарю. – Brenden

+9

Я обнаружил, что изменение размера изображения на сетчатом устройстве показалось размытым. Чтобы сохранить ясность, я изменил первую строку следующим образом: 'UIGraphicsBeginImageContextWithOptions (newSize, 1.0f, 0.0f);'. (объясняется здесь: http://stackoverflow.com/questions/4334233/how-to-capture-uiview-to-uiimage-without-loss-of-quality-on-retina-display) – johngraham

+0

Он вращает изображение - но doesn Правильно! Почему у него так много голосов? – Dejell

17
+ (UIImage *)scaleImage:(UIImage *)image toSize:(CGSize)targetSize { 
    //If scaleFactor is not touched, no scaling will occur  
    CGFloat scaleFactor = 1.0; 

    //Deciding which factor to use to scale the image (factor = targetSize/imageSize) 
    if (image.size.width > targetSize.width || image.size.height > targetSize.height) 
     if (!((scaleFactor = (targetSize.width/image.size.width)) > (targetSize.height/image.size.height))) //scale to fit width, or 
      scaleFactor = targetSize.height/image.size.height; // scale to fit heigth. 

    UIGraphicsBeginImageContext(targetSize); 

    //Creating the rect where the scaled image is drawn in 
    CGRect rect = CGRectMake((targetSize.width - image.size.width * scaleFactor)/2, 
          (targetSize.height - image.size.height * scaleFactor)/2, 
          image.size.width * scaleFactor, image.size.height * scaleFactor); 

    //Draw the image into the rect 
    [image drawInRect:rect]; 

    //Saving the image, ending image context 
    UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext(); 
    UIGraphicsEndImageContext(); 

    return scaledImage; 
} 

Я предлагаю это. Разве она не красота? ;)

+0

Это хорошо, если вы удаляете оператор first if, он действует как AspectFill. – RaffAl

7

Здесь вы идете. Это один идеально подходит ;-)

EDIT: смотри ниже комментарий - «Не работает с определенными образами, терпит неудачу с: CGContextSetInterpolationQuality: неверная ошибка контекста 0x0»

// Resizes the image according to the given content mode, taking into account the image's orientation 
- (UIImage *)resizedImageWithContentMode:(UIViewContentMode)contentMode imageToScale:(UIImage*)imageToScale bounds:(CGSize)bounds interpolationQuality:(CGInterpolationQuality)quality { 
    //Get the size we want to scale it to 
    CGFloat horizontalRatio = bounds.width/imageToScale.size.width; 
    CGFloat verticalRatio = bounds.height/imageToScale.size.height; 
    CGFloat ratio; 

    switch (contentMode) { 
     case UIViewContentModeScaleAspectFill: 
      ratio = MAX(horizontalRatio, verticalRatio); 
      break; 

     case UIViewContentModeScaleAspectFit: 
      ratio = MIN(horizontalRatio, verticalRatio); 
      break; 

     default: 
      [NSException raise:NSInvalidArgumentException format:@"Unsupported content mode: %d", contentMode]; 
    } 

    //...and here it is 
    CGSize newSize = CGSizeMake(imageToScale.size.width * ratio, imageToScale.size.height * ratio); 


    //start scaling it 
    CGRect newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height)); 
    CGImageRef imageRef = imageToScale.CGImage; 
    CGContextRef bitmap = CGBitmapContextCreate(NULL, 
               newRect.size.width, 
               newRect.size.height, 
               CGImageGetBitsPerComponent(imageRef), 
               0, 
               CGImageGetColorSpace(imageRef), 
               CGImageGetBitmapInfo(imageRef)); 

    CGContextSetInterpolationQuality(bitmap, quality); 

    // Draw into the context; this scales the image 
    CGContextDrawImage(bitmap, newRect, imageRef); 

    // Get the resized image from the context and a UIImage 
    CGImageRef newImageRef = CGBitmapContextCreateImage(bitmap); 
    UIImage *newImage = [UIImage imageWithCGImage:newImageRef]; 

    // Clean up 
    CGContextRelease(bitmap); 
    CGImageRelease(newImageRef); 

    return newImage; 
} 
+1

Посмотрите очень красиво :) Мне нравится InterpolationQuality здесь –

+0

Не работает с определенными изображениями, не удается: CGContextSetInterpolationQuality: недопустимый контекст 0x0 error – RaffAl

2

Я изменил код Брэда Ларсона. Он будет аспект заполнять изображение в данном прямоугольнике.

-(UIImage*) scaleAndCropToSize:(CGSize)newSize; 
{ 
    float ratio = self.size.width/self.size.height; 

    UIGraphicsBeginImageContext(newSize); 

    if (ratio > 1) { 
     CGFloat newWidth = ratio * newSize.width; 
     CGFloat newHeight = newSize.height; 
     CGFloat leftMargin = (newWidth - newHeight)/2; 
     [self drawInRect:CGRectMake(-leftMargin, 0, newWidth, newHeight)]; 
    } 
    else { 
     CGFloat newWidth = newSize.width; 
     CGFloat newHeight = newSize.height/ratio; 
     CGFloat topMargin = (newHeight - newWidth)/2; 
     [self drawInRect:CGRectMake(0, -topMargin, newSize.width, newSize.height/ratio)]; 
    } 

    UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext(); 
    UIGraphicsEndImageContext(); 

    return newImage; 
} 
+1

Протестировано двумя другими изображениями, портретами и пейзажами. Это НЕ выполняет Аспект Заполнения. – RaffAl

1

Следующий простой код работал для меня.

[imageView setContentMode:UIViewContentModeScaleAspectFill]; 
[imageView setClipsToBounds:YES]; 
+0

Ваш код действительно подходит для объектов 'UIImageView'. Однако этот вопрос касается масштабирования самого объекта UIImage. – Rickster

+0

Обязательно прочитайте вопрос перед ответом. –

1
scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0,0.0,ScreenWidth,ScreenHeigth)]; 
    [scrollView setBackgroundColor:[UIColor blackColor]]; 
    [scrollView setDelegate:self]; 
    [scrollView setShowsHorizontalScrollIndicator:NO]; 
    [scrollView setShowsVerticalScrollIndicator:NO]; 
    [scrollView setMaximumZoomScale:2.0]; 
    image=[image scaleToSize:CGSizeMake(ScreenWidth, ScreenHeigth)]; 
    imageView = [[UIImageView alloc] initWithImage:image]; 
    UIImageView* imageViewBk = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background.png"]]; 
    [self.view addSubview:imageViewBk]; 
    CGRect rect; 
    rect.origin.x=0; 
    rect.origin.y=0; 
    rect.size.width = image.size.width; 
    rect.size.height = image.size.height; 

    [imageView setFrame:rect]; 

    [scrollView setContentSize:[imageView frame].size]; 
    [scrollView setMinimumZoomScale:[scrollView frame].size.width/[imageView frame].size.width]; 
    [scrollView setZoomScale:[scrollView minimumZoomScale]]; 
    [scrollView addSubview:imageView]; 

    [[self view] addSubview:scrollView]; 

, то вы можете взять снимки экрана к изображению этого

float zoomScale = 1.0/[scrollView zoomScale]; 
CGRect rect; 
rect.origin.x = [scrollView contentOffset].x * zoomScale; 
rect.origin.y = [scrollView contentOffset].y * zoomScale; 
rect.size.width = [scrollView bounds].size.width * zoomScale; 
rect.size.height = [scrollView bounds].size.height * zoomScale; 

CGImageRef cr = CGImageCreateWithImageInRect([[imageView image] CGImage], rect); 

UIImage *cropped = [UIImage imageWithCGImage:cr]; 

CGImageRelease(cr); 
2

Xamarin.версия IOS, для accepted answer on how to resize and then crop UIImage (Aspect Fill) является ниже

public static UIImage ScaleAndCropImage(UIImage sourceImage, SizeF targetSize) 
    { 
     var imageSize = sourceImage.Size; 
     UIImage newImage = null; 
     var width = imageSize.Width; 
     var height = imageSize.Height; 
     var targetWidth = targetSize.Width; 
     var targetHeight = targetSize.Height; 
     var scaleFactor = 0.0f; 
     var scaledWidth = targetWidth; 
     var scaledHeight = targetHeight; 
     var thumbnailPoint = PointF.Empty; 
     if (imageSize != targetSize) 
     { 
      var widthFactor = targetWidth/width; 
      var heightFactor = targetHeight/height; 
      if (widthFactor > heightFactor) 
      { 
       scaleFactor = widthFactor;// scale to fit height 
      } 
      else 
      { 
       scaleFactor = heightFactor;// scale to fit width 
      } 
      scaledWidth = width * scaleFactor; 
      scaledHeight = height * scaleFactor; 
      // center the image 
      if (widthFactor > heightFactor) 
      { 
       thumbnailPoint.Y = (targetHeight - scaledHeight) * 0.5f; 
      } 
      else 
      { 
       if (widthFactor < heightFactor) 
       { 
        thumbnailPoint.X = (targetWidth - scaledWidth) * 0.5f; 
       } 
      } 
     } 
     UIGraphics.BeginImageContextWithOptions(targetSize, false, 0.0f); 
     var thumbnailRect = new RectangleF(thumbnailPoint, new SizeF(scaledWidth, scaledHeight)); 
     sourceImage.Draw(thumbnailRect); 
     newImage = UIGraphics.GetImageFromCurrentImageContext(); 
     if (newImage == null) 
     { 
      Console.WriteLine("could not scale image"); 
     } 
     //pop the context to get back to the default 
     UIGraphics.EndImageContext(); 

     return newImage; 
    } 
+1

Сохранено меня время перевод. Спасибо за публикацию. – Lereveme

0
- (UIImage*)imageScale:(CGFloat)scaleFactor cropForSize:(CGSize)targetSize 
{ 
    targetSize = !targetSize.width?self.size:targetSize; 
    UIGraphicsBeginImageContext(targetSize); // this will crop 

    CGRect thumbnailRect = CGRectZero; 

    thumbnailRect.size.width = targetSize.width*scaleFactor; 
    thumbnailRect.size.height = targetSize.height*scaleFactor; 
    CGFloat xOffset = (targetSize.width- thumbnailRect.size.width)/2; 
    CGFloat yOffset = (targetSize.height- thumbnailRect.size.height)/2; 
    thumbnailRect.origin = CGPointMake(xOffset,yOffset); 

    [self drawInRect:thumbnailRect]; 

    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 

    if(newImage == nil) 
    { 
     NSLog(@"could not scale image"); 
    } 

    UIGraphicsEndImageContext(); 

    return newImage; 
} 

Ниже приведен пример работы: Левое изображение - (происхождение изображения) ; Право изображение с масштабом x2

enter image description here

Если вы хотите масштабировать изображение, но сохранить его рамку (пропорции), метод вызова таким образом:

[yourImage imageScale:2.0f cropForSize:CGSizeZero]; 
0

Swift версия:

static func imageWithImage(image:UIImage, newSize:CGSize) ->UIImage { 
    UIGraphicsBeginImageContextWithOptions(newSize, true, UIScreen.mainScreen().scale); 
    image.drawInRect(CGRectMake(0, 0, newSize.width, newSize.height)) 

    let newImage = UIGraphicsGetImageFromCurrentImageContext(); 

    UIGraphicsEndImageContext(); 
    return newImage 
} 
+1

Приятная небольшая функция, но я уверен, что это просто растягивает изображение до определенного размера, не сохраняет пропорции и обрезку ... –

+0

Да, вы имеете право, это только для того, чтобы сгладить изображение до приподнятого размера. –

0

Этот вопрос, похоже, успокоился, но в моем стремлении к решению, которое я мог бы более легко понять (и написано в Swift), я достиг этого (также опубликовано: How to crop the UIImage?)


Я хотел, чтобы иметь возможность обрезать из области на основе соотношения сторон, и масштаб до размера на основе внешней ограничивающей степени. Вот мой вариант:

import AVFoundation 
import ImageIO 

class Image { 

    class func crop(image:UIImage, crop 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, а масштабирование обрабатывается частью размера. В моем случае мне нужно было связать размер прямоугольника обрезки на источнике, с моим выходным прямоугольником с одинаковым соотношением сторон. Затем коэффициент масштабирования выводится/вводится, и это нужно применять к drawRect (передан drawInRect).

Одно из предостережений заключается в том, что этот подход эффективно предполагает, что изображение, которое вы рисуете, больше, чем контекст изображения. Я не тестировал это, но я думаю, что вы можете использовать этот код для обработки обрезки/масштабирования, но явно определяя параметр масштаба как вышеупомянутый параметр масштаба. По умолчанию UIKit применяет множитель, основанный на разрешении экрана.

Наконец, следует отметить, что этот подход UIKit выше, чем подходы CoreGraphics/Quartz и Core Image, и, похоже, справляется с проблемами ориентации изображения. Также стоит упомянуть, что он довольно быстрый, второй по сравнению с ImageIO, в соответствии с этим постом здесь: http://nshipster.com/image-resizing/

2

Я преобразовал Sam Wirch's guide to swift, и это сработало хорошо для меня, хотя в конечном изображении есть очень небольшое «squishing», что я не может решить.

func resizedCroppedImage(image: UIImage, newSize:CGSize) -> UIImage { 
    var ratio: CGFloat = 0 
    var delta: CGFloat = 0 
    var offset = CGPointZero 
    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) 
    } 
    let clipRect = CGRectMake(-offset.x, -offset.y, (ratio * image.size.width) + delta, (ratio * image.size.height) + delta) 
    UIGraphicsBeginImageContextWithOptions(newSize, true, 0.0) 
    UIRectClip(clipRect) 
    image.drawInRect(clipRect) 
    let newImage = UIGraphicsGetImageFromCurrentImageContext() 
    UIGraphicsEndImageContext() 
    return newImage 
} 

Если кто-то хочет объективную версию c, это на его сайте.

6

Это версия ответа Джейн Продажи в Свифте. Ура!

public func resizeImage(image: UIImage, size: CGSize) -> UIImage? { 
    var returnImage: UIImage? 

    var scaleFactor: CGFloat = 1.0 
    var scaledWidth = size.width 
    var scaledHeight = size.height 
    var thumbnailPoint = CGPointMake(0, 0) 

    if !CGSizeEqualToSize(image.size, size) { 
     let widthFactor = size.width/image.size.width 
     let heightFactor = size.height/image.size.height 

     if widthFactor > heightFactor { 
      scaleFactor = widthFactor 
     } else { 
      scaleFactor = heightFactor 
     } 

     scaledWidth = image.size.width * scaleFactor 
     scaledHeight = image.size.height * scaleFactor 

     if widthFactor > heightFactor { 
      thumbnailPoint.y = (size.height - scaledHeight) * 0.5 
     } else if widthFactor < heightFactor { 
      thumbnailPoint.x = (size.width - scaledWidth) * 0.5 
     } 
    } 

    UIGraphicsBeginImageContextWithOptions(size, true, 0) 

    var thumbnailRect = CGRectZero 
    thumbnailRect.origin = thumbnailPoint 
    thumbnailRect.size.width = scaledWidth 
    thumbnailRect.size.height = scaledHeight 

    image.drawInRect(thumbnailRect) 
    returnImage = UIGraphicsGetImageFromCurrentImageContext() 

    UIGraphicsEndImageContext() 

    return returnImage 
} 
0

Вот версия Swift 3 из Sam Wirch's guide to swift отправленный Уильям Т.

extension UIImage { 

    static func resizedCroppedImage(image: UIImage, newSize:CGSize) -> UIImage? { 
     var ratio: CGFloat = 0 
     var delta: CGFloat = 0 
     var offset = CGPoint.zero 

     if image.size.width > image.size.height { 
      ratio = newSize.width/image.size.width 
      delta = (ratio * image.size.width) - (ratio * image.size.height) 
      offset = CGPoint(x: delta/2, y: 0) 
     } else { 
      ratio = newSize.width/image.size.height 
      delta = (ratio * image.size.height) - (ratio * image.size.width) 
      offset = CGPoint(x: 0, y: delta/2) 
     } 

     let clipRect = CGRect(x: -offset.x, y: -offset.y, width: (ratio * image.size.width) + delta, height: (ratio * image.size.height) + delta) 
     UIGraphicsBeginImageContextWithOptions(newSize, true, 0.0) 
     UIRectClip(clipRect) 
     image.draw(in: clipRect) 
     let newImage = UIGraphicsGetImageFromCurrentImageContext() 
     UIGraphicsEndImageContext() 

     return newImage 
    } 

} 
Смежные вопросы