Як зменшити UIImage і зробити його одночасно хрустким / різким, а не розмитим?


75

Мені потрібно зменшити зображення, але різко. Наприклад, у Photoshop є параметри зменшення розміру зображення "Bicubic Smoother" (розмито) та "Bicubic Sharper".

Цей алгоритм зменшення масштабу зображень відкритий чи десь задокументований, або SDK пропонує методи для цього?




Дивіться це питання. stackoverflow.com/questions/2658738/ ... Найголосніша відповідь - це найпростіше рішення цієї проблеми, яке я ще знайшов.
alper_k

Відповіді:


127

Простого використання imageWithCGImageнедостатньо. Масштабуватиметься, але результат буде розмитим і неоптимальним, будь то збільшення чи зменшення.

Якщо ви хочете отримати правильний псевдонім і позбутися від "джаггі", вам потрібно щось на зразок цього: http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right- шлях / .

Мій робочий тестовий код виглядає приблизно так, це рішення Тревора з одним невеликим налаштуванням для роботи з моїми прозорими PNG:

- (UIImage *)resizeImage:(UIImage*)image newSize:(CGSize)newSize {
    CGRect newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height));
    CGImageRef imageRef = image.CGImage;

    UIGraphicsBeginImageContextWithOptions(newSize, NO, 0);
    CGContextRef context = UIGraphicsGetCurrentContext();

    // Set the quality level to use when rescaling
    CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
    CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, newSize.height);

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

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

    CGImageRelease(newImageRef);
    UIGraphicsEndImageContext();    

    return newImage;
}

Дякую! Шукав цього. Це працює для PNG, але обходить Тревора transformForOrientation. Чи дійсно ця функція необхідна? Це лише для JPG з метаданими орієнтації?
pixelfreak

@pixelfreak, ти хочеш сказати, що в коді, який я представив, відсутній фрагмент, необхідний для роботи з jpeg, орієнтація яких встановлена ​​в їх метаданих? Не соромтеся редагувати мій код, якщо це не надто ускладнює його. Я просто вирвав те, що мені не потрібно
Ден Розенстарк

1
у коді Тревора він перевіряє imageOrientation(дані EXIF, я вважаю?) і робить додаткове перетворення залежно від його значення. Не впевнений, що це необхідно. Я пограю з цим.
pixelfreak

Дякую @pixelfreak, це було б чудово. Як бачите, я просто намагався зробити код простим для одного випадку використання. Потім я роблю UIImageView, який кешує різні масштабовані версії (у статичному масиві), щоб зменшити повільність цієї процедури зміни розміру, і перериває метод setFrame. Я не впевнений, що це представляє загальний інтерес :)
Dan Rosenstark

1
@Yar Це правильно. Якщо ви вже знаєте, яке тло у вас буде, наприклад, у комірці таблиці, ви можете спершу намалювати фон:[[UIColor whiteColor] set]; UIRectFill(newRect);
Holtwick

19

Для тих, хто використовує Swift, ось прийнята відповідь у Swift:

func resizeImage(image: UIImage, newSize: CGSize) -> (UIImage) {
    let newRect = CGRectIntegral(CGRectMake(0,0, newSize.width, newSize.height))
    let imageRef = image.CGImage

    UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
    let context = UIGraphicsGetCurrentContext()

    // Set the quality level to use when rescaling
    CGContextSetInterpolationQuality(context, kCGInterpolationHigh)
    let flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, newSize.height)

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

    let newImageRef = CGBitmapContextCreateImage(context) as CGImage
    let newImage = UIImage(CGImage: newImageRef)

    // Get the resized image from the context and a UIImage
    UIGraphicsEndImageContext()

    return newImage
}

Класно! тоді ви могли б зробити це і як розширення.
Dan Rosenstark,

2
Навіщо вам потрібно перевертати зображення, щоб змінити його розмір (тобто для чого це потрібно CGContextConcatCTM(context, flipVertical)?
Крашалот

5
kCGInterpolationHigh -> CGInterpolation.High у Swift 2 або ви отримуєте помилку компіляції
Crashalot

1
@Crashalot використовує CGInterpolationQuality.High замість kCGInterpolationHigh для swift 2.0
Deepak Thakur

Коли я використовую це, я втрачаю 40 Мб кожного разу, коли викликається CGContextDrawImage. Хтось мав подібну проблему? Для мене це непридатне, оскільки я намагаюся зменшити масштаб багатьох зображень і одразу закінчую пам’ять.
RowanPD

19

Якщо хтось шукає версію Swift, ось ось Swift версія прийнятої відповіді @Dan Rosenstark:

func resizeImage(image: UIImage, newHeight: CGFloat) -> UIImage {
    let scale = newHeight / image.size.height
    let newWidth = image.size.width * scale
    UIGraphicsBeginImageContext(CGSizeMake(newWidth, newHeight))
    image.drawInRect(CGRectMake(0, 0, newWidth, newHeight))
    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return newImage
}

1
дякуємо за відповідь, і ми можемо використовувати це як розширення публічного розширення UIImage {func ...}
mert

Відповідь сестри Рей у наступному посиланні для мене ідеально працює stackoverflow.com/questions/6052188/…
Діпак Тхакур,

12

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

Для масштабування можна використовувати наступний метод:

+ (UIImage *)imageWithCGImage:(CGImageRef)imageRef scale:(CGFloat)scale orientation:(UIImageOrientation)orientation

схоже застосовує трансформацію у поданні, але не змінює розмір базових даних
Cbas

12

Для Свіфта 3

func resizeImage(image: UIImage, newSize: CGSize) -> (UIImage) {

    let newRect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height).integral
    UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
    let context = UIGraphicsGetCurrentContext()

    // Set the quality level to use when rescaling
    context!.interpolationQuality = CGInterpolationQuality.default
    let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: newSize.height)

    context!.concatenate(flipVertical)
    // Draw into the context; this scales the image
    context?.draw(image.cgImage!, in: CGRect(x: 0.0,y: 0.0, width: newRect.width, height: newRect.height))

    let newImageRef = context!.makeImage()! as CGImage
    let newImage = UIImage(cgImage: newImageRef)

    // Get the resized image from the context and a UIImage
    UIGraphicsEndImageContext()

    return newImage
 }

Це трактування зображення як заповнення аспекту, чи є спосіб зробити його відповідним аспекту ?. Дякую
Coder221,

Це не працює. Коли я друкую newRect.size, це дає мені правильні зменшені розміри. Однак, якщо я друкую newImage.size, там я бачу початкові розміри image. Будь-які ідеї, чому це так? Розібрався. UIGraphicsBeginImageContextWithOptions(newSize, false, 0)запобігає масштабуванню. Параметр шкали - 0. Якщо ви використовуєте UIGraphicsBeginImageContext(newRect.size)замість цього, все добре.
Schnodderbalken

2

@YAR ваше рішення працює належним чином.

Єдина річ, яка не відповідає моїм вимогам: Змінено розмір всього зображення. Я написав метод, який зробив це як photos app on iphone. Це обчислює "довшу сторону" та відсікає "накладання", що призводить до набагато кращих результатів щодо якості зображення.

- (UIImage *)resizeImageProportionallyIntoNewSize:(CGSize)newSize;
{
    CGFloat scaleWidth = 1.0f;
    CGFloat scaleHeight = 1.0f;

    if (CGSizeEqualToSize(self.size, newSize) == NO) {

        //calculate "the longer side"
        if(self.size.width > self.size.height) {
            scaleWidth = self.size.width / self.size.height;
        } else {
            scaleHeight = self.size.height / self.size.width;
        }
    }    

    //prepare source and target image
    UIImage *sourceImage = self;
    UIImage *newImage = nil;

    // Now we create a context in newSize and draw the image out of the bounds of the context to get
    // A proportionally scaled image by cutting of the image overlay
    UIGraphicsBeginImageContext(newSize);

    //Center image point so that on each egde is a little cutoff
    CGRect thumbnailRect = CGRectZero;
    thumbnailRect.size.width  = newSize.width * scaleWidth;
    thumbnailRect.size.height = newSize.height * scaleHeight;
    thumbnailRect.origin.x = (int) (newSize.width - thumbnailRect.size.width) * 0.5;
    thumbnailRect.origin.y = (int) (newSize.height - thumbnailRect.size.height) * 0.5;

    [sourceImage drawInRect:thumbnailRect];

    newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

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

    return newImage ;
}

0

Для швидкої версії 4.2:

extension UIImage {

    func resized(By coefficient:CGFloat) -> UIImage? {

        guard coefficient >= 0 && coefficient <= 1 else {

            print("The coefficient must be a floating point number between 0 and 1")
            return nil
        }

        let newWidth = size.width * coefficient
        let newHeight = size.height * coefficient

        UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))

        draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))

        let newImage = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()

        return newImage
    }
}

@ShahbazSaleem Чи можете ви пояснити, які проблеми з якістю? Я бачу, ви прихилили відповідь Йована Станковича з тією ж "причиною".
gabriel_vincent

-1

Це розширення має масштабувати зображення, зберігаючи оригінальне співвідношення сторін. Інша частина зображення обрізана. (Свіфт 3)

extension UIImage {    
    func thumbnail(ofSize proposedSize: CGSize) -> UIImage? {

        let scale = min(size.width/proposedSize.width, size.height/proposedSize.height)

        let newSize = CGSize(width: size.width/scale, height: size.height/scale)
        let newOrigin = CGPoint(x: (proposedSize.width - newSize.width)/2, y: (proposedSize.height - newSize.height)/2)

        let thumbRect = CGRect(origin: newOrigin, size: newSize).integral

        UIGraphicsBeginImageContextWithOptions(proposedSize, false, 0)

        draw(in: thumbRect)

        let result = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()

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