Как уменьшить масштаб UIImage и сделать его хрустящим/острым одновременно, а не размытым?

Мне нужно масштабировать изображение, но резко. В Photoshop, например, есть варианты уменьшения размера изображения "Bicubic Smoother" (размытый) и "Bicubic Sharper".

Является ли этот алгоритм масштабирования изображения открытым исходным кодом или документированным где-либо или предлагает SDK методы для этого?

Ответ 1

Просто использовать imageWithCGImage недостаточно. Он будет масштабироваться, но результат будет размытым и субоптимальным, будь то масштабирование вверх или вниз.

Если вы хотите получить право на псевдонимы и избавиться от "jaggies", вам нужно что-то вроде этого: http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/.

Мой рабочий тестовый код выглядит примерно так: решение Trevor с одной небольшой настройкой для работы с моими прозрачными 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;
}

Ответ 2

Для тех, кто использует 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
}

Ответ 3

Если кто-то ищет версию Swift, вот версия Swift из принятого ответа @Yar:

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
}

Ответ 4

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

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

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

Ответ 5

Для Swift 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
 }

Ответ 6

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

Ответ 7

Это расширение должно масштабировать изображение при сохранении исходного соотношения сторон. Остальная часть изображения обрезана. (Swift 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
}

}