UIImage: Змініть розмір, а потім обріжте


187

Я буквально цілими днями б'є моє обличчя, і хоча постійно відчуваю, що перебуваю на краю одкровення, просто не можу досягти своєї мети.

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

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

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


7
Посилання перервано на ваше зображення.

1
Одне з них, якщо нас не шокує, - це те, чому Apple не додала його до UIImagePickerController - його занадто затято важко ;-)
Тим

Відповіді:


246

Мені потрібно було те саме - у моєму випадку вибрати розмір, який підходить колись масштабованому, а потім обрізати кожен кінець, щоб відповідати решті на ширину. (Я працюю в ландшафті, тому, можливо, не помічав жодних недоліків у портретному режимі.) Ось мій код - це частина категерії на 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 ..
Сорін Антохій

5
Проблема з поганим exec виникає, оскільки функції UIImage не є безпечними для потоків. Ось чому це деякі проблеми, а інколи
Ерік

1
Цей код працював для мене чудово, але він був розмитим на сітківці. Комбінуючи цей код з коментарем нижче зробив все ідеально: stackoverflow.com/questions/603907 / ...
MaxGabriel

23
Використовуйте UIGraphicsBeginImageContextWithOptions (targetSize, YES, 0.0); щоб зображення було приємним і на сітківці.
Борут Томазін

1
Це працює для мене в режимі «Портрет», але не в пейзажному режимі. Висота розтягнута в ландшафті. Будь-яка ідея чому?
Даррен

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. Дякую.
Бренден

10
Я виявив, що розмір зображення на пристрої сітківки виглядає розмитим. Щоб зберегти ясність, я змінив перший рядок на наступне: UIGraphicsBeginImageContextWithOptions(newSize, 1.0f, 0.0f);. (Описано тут: stackoverflow.com/questions/4334233 / ... )
johngraham

Він обертає зображення - але не обрізає його належним чином! Чому у неї стільки голосів?
Dejell

18
+ (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;
}

Я пропоную цю. Хіба вона не красуня? ;)


1
Це добре, якщо ви видалите спочатку, якщо оператор виступає як 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
Дуже красиво виглядаю :) Мені подобається ІнтерполяціяКачество тут
Костянтин Соколінський

Не працює з певними зображеннями, не вдається: CGContextSetInterpolationQuality: недійсний контекст 0x0 помилка
RaffAl

6

Це версія відповіді Джейн Продаж у Swift. Ура!

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
}

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

2

Версія Xamarin.iOS для прийнятої відповіді про те, як змінити розмір, а потім обрізати UIImage (аспект заповнення) , наведено нижче

    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;
    }

2

Я швидко перетворив посібник Сема Вірха, і він спрацював добре для мене, хоча в кінцевому зображенні є дуже незначне «шприц», яке я не зміг вирішити.

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, це на його веб-сайті.


2

Я виявив, що Swift 3, опублікований Євгенієм Канвець, не рівномірно масштабує зображення.

Ось моя версія Swift 4, яка не стискає зображення:

static func resizedCroppedImage(image: UIImage, newSize:CGSize) -> UIImage? {

    // This function returns a newImage, based on image
    // - image is scaled uniformaly to fit into a rect of size newSize
    // - if the newSize rect is of a different aspect ratio from the source image
    //     the new image is cropped to be in the center of the source image
    //     (the excess source image is removed)

    var ratio: CGFloat = 0
    var delta: CGFloat = 0
    var drawRect = CGRect()

    if newSize.width > newSize.height {

        ratio = newSize.width / image.size.width
        delta = (ratio * image.size.height) - newSize.height
        drawRect = CGRect(x: 0, y: -delta / 2, width: newSize.width, height: newSize.height + delta)

    } else {

        ratio = newSize.height / image.size.height
        delta = (ratio * image.size.width) - newSize.width
        drawRect = CGRect(x: -delta / 2, y: 0, width: newSize.width + delta, height: newSize.height)

    }

    UIGraphicsBeginImageContextWithOptions(newSize, true, 0.0)
    image.draw(in: drawRect)
    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return newImage
} 

Мені довелося встановити масштабування на 1,0 дюйма, UIGraphicsBeginImageContextWithOptionsале інші працювали чудово. Дякую!
maxmzd

1

Наступний простий код працював для мене.

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

Ваш код добре працює для UIImageViewоб'єктів. Однак це питання стосується масштабування самого UIImageоб’єкта.
Рікстер

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);

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

введіть тут опис зображення

Якщо ви хочете масштабувати зображення, але зберегти його кадр (пропорції), зателефонуйте таким чином:

[yourImage imageScale:2.0f cropForSize:CGSizeZero];

0

Це питання, здається, було покладено спокою, але в моєму прагненні рішення, яке я міг би легше зрозуміти (і написане в Swift), я прийшов до цього (також опублікував на сторінці: Як обрізати 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
    }
}

Є кілька речей, які я вважаю заплутаними, окремі проблеми з обрізанням та зміною розміру. Обрізання обробляється з початком прямої прямої лінії, яку ви передаєте малюваннюInRect, а масштабування обробляється за розміром. У моєму випадку мені потрібно було співвідносити розмір прямої обрізки на джерелі, до моєї вихідної прямої з тим же співвідношенням сторін. Тоді коефіцієнт масштабу - це вихід / вхід, і це потрібно застосувати до drawRect (передано до drawInRect).

Одне застереження полягає в тому, що цей підхід ефективно передбачає, що зображення, яке ви малюєте, більше, ніж контекст зображення. Я цього не перевіряв, але думаю, що ви можете використовувати цей код для обробки обрізання / масштабування, але чітко визначаючи параметр масштабу як згаданий параметр масштабу. За замовчуванням UIKit застосовує множник на основі роздільної здатності екрана.

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


0

Швидка версія:

    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
Приємна маленька функція, але я впевнений, що це просто розтягує зображення на певний розмір, не підтримує співвідношення сторін і обрізання ...
William T.

Так, ви маєте рацію, це лише для щасливого зображення до параметричного розміру.
Роман Барзичак

0

Ось Swift 3 версія посібника по швидкому переводу Сем Вірха, розміщена Вільямом Т.

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
    }

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.