提问



UIImage在缩放时总是变得模糊不清。如果让它保持清晰,我该怎么办?


- (UIImage *)rescaleImageToSize:(CGSize)size {
    CGRect rect = CGRectMake(0.0, 0.0, size.width, size.height);
    UIGraphicsBeginImageContext(rect.size);
    [self drawInRect:rect];  // scales image to rect
    UIImage *resImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return resImage;
}

最佳参考


舍入



首先,确保在缩放之前缩小尺寸。drawInRect:可以模糊在这种情况下可用的图像。要舍入到最接近的整数值:


size.width = truncf(size.width);
size.height = truncf(size.height);


对于某些任务,您可能需要向下舍入(floorf)或向上舍入(ceilf)。


CILanczosScaleTransform不可用



然后,忽略我之前对CILanczosScaleTransform的建议。虽然Core Image的部分版本在iOS 5.0中可用,但Lanczos缩放不是。如果它确实可用,请使用它。对于在Mac OS上工作的人来说,可以使用它。


vImage Scaling



但是,vImage中有一个高质量的缩放算法。以下图片显示了使用它的方法(vImageScaledImage)如何与不同的上下文插值选项进行比较。另请注意这些选项在不同缩放级别下的行为方式有何不同。[8]


在此图中,它保留了最多的细节:
[9]


在这张照片上,比较左下方的叶子:
[10]


在这张照片上,比较右下方的纹理:
[11]


不要在像素艺术上使用它;它会创建奇怪的缩放工件:
[12]


虽然它在某些图像上具有有趣的舍入效果:
[13]


效果



毫不奇怪,kCGImageInterpolationHigh是最慢的标准图像插值选项。这里实现的vImageScaledImage更慢。为了将分形图像缩小到原始尺寸的一半,它花费了UIImageInterpolationHigh的110%的时间。缩小到四分之一,花费了340%的时间。


如果你在模拟器中运行它,你可能会想;在那里,它可以比kCGImageInterpolationHigh快得多。据推测,vImage多核优化使其在桌面上具有相对优势。


代码



// Method: vImageScaledImage:(UIImage*) sourceImage withSize:(CGSize) destSize
// Returns even better scaling than drawing to a context with kCGInterpolationHigh.
// This employs the vImage routines in Accelerate.framework.
// For more information about vImage, see https://developer.apple.com/library/mac/#documentation/performance/Conceptual/vImage/Introduction/Introduction.html#//apple_ref/doc/uid/TP30001001-CH201-TPXREF101
// Large quantities of memory are manually allocated and (hopefully) freed here.  Test your application for leaks before and after using this method.
- (UIImage*) vImageScaledImage:(UIImage*) sourceImage withSize:(CGSize) destSize;
{
    UIImage *destImage = nil;

    if (sourceImage)
    {
        // First, convert the UIImage to an array of bytes, in the format expected by vImage.
        // Thanks: http://stackoverflow.com/a/1262893/1318452
        CGImageRef sourceRef = [sourceImage CGImage];
        NSUInteger sourceWidth = CGImageGetWidth(sourceRef);
        NSUInteger sourceHeight = CGImageGetHeight(sourceRef);
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        unsigned char *sourceData = (unsigned char*) calloc(sourceHeight * sourceWidth * 4, sizeof(unsigned char));
        NSUInteger bytesPerPixel = 4;
        NSUInteger sourceBytesPerRow = bytesPerPixel * sourceWidth;
        NSUInteger bitsPerComponent = 8;
        CGContextRef context = CGBitmapContextCreate(sourceData, sourceWidth, sourceHeight,
                                                     bitsPerComponent, sourceBytesPerRow, colorSpace,
                                                     kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big);
        CGContextDrawImage(context, CGRectMake(0, 0, sourceWidth, sourceHeight), sourceRef);
        CGContextRelease(context);

        // We now have the source data.  Construct a pixel array
        NSUInteger destWidth = (NSUInteger) destSize.width;
        NSUInteger destHeight = (NSUInteger) destSize.height;
        NSUInteger destBytesPerRow = bytesPerPixel * destWidth;
        unsigned char *destData = (unsigned char*) calloc(destHeight * destWidth * 4, sizeof(unsigned char));

        // Now create vImage structures for the two pixel arrays.
        // Thanks: https://github.com/dhoerl/PhotoScrollerNetwork
        vImage_Buffer src = {
            .data = sourceData,
            .height = sourceHeight,
            .width = sourceWidth,
            .rowBytes = sourceBytesPerRow
        };

        vImage_Buffer dest = {
            .data = destData,
            .height = destHeight,
            .width = destWidth,
            .rowBytes = destBytesPerRow
        };

        // Carry out the scaling.
        vImage_Error err = vImageScale_ARGB8888 (
                                                 &src,
                                                 &dest,
                                                 NULL,
                                                 kvImageHighQualityResampling 
                                                 );

        // The source bytes are no longer needed.
        free(sourceData);

        // Convert the destination bytes to a UIImage.
        CGContextRef destContext = CGBitmapContextCreate(destData, destWidth, destHeight,
                                                         bitsPerComponent, destBytesPerRow, colorSpace,
                                                         kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big);
        CGImageRef destRef = CGBitmapContextCreateImage(destContext);

        // Store the result.
        destImage = [UIImage imageWithCGImage:destRef];

        // Free up the remaining memory.
        CGImageRelease(destRef);

        CGColorSpaceRelease(colorSpace);
        CGContextRelease(destContext);

        // The destination bytes are no longer needed.
        free(destData);

        if (err != kvImageNoError)
        {
            NSString *errorReason = [NSString stringWithFormat:@"vImageScale returned error code %d", err];
            NSDictionary *errorInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                                       sourceImage, @"sourceImage", 
                                       [NSValue valueWithCGSize:destSize], @"destSize",
                                       nil];

            NSException *exception = [NSException exceptionWithName:@"HighQualityImageScalingFailureException" reason:errorReason userInfo:errorInfo];

            @throw exception;
        }
    }
    return destImage;
}

其它参考1


我已经用VImage尝试了@Dondragmer的答案,但结果的质量还不够好(我用1/10的比例缩小图像尺寸)。


这个解决方案对我有用:UIImage的drawInrect:平滑图像


基本上,它只是说在视网膜显示器上你需要用视网膜参数创建图形上下文:


UIGraphicsBeginImageContextWithOptions(size, NO, 2.0f);

其它参考2


试试看:


CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);


在绘图之前获得高质量的插值。

其它参考3


在放大图像的特殊情况下,想要保留任何尖角,您需要关闭图像插值。如果您的图像是像素艺术,这就是您想要的。在大多数其他情况下,它不是。


UIGraphicsBeginImageContext(rect.size);

// Turn off interpolation to keep the scaled image from being blurred.
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);

[self drawInRect:rect];  // scales image to rect
UIImage *resImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();


如果你缩小尺寸,只需用kCGInterpolationHigh绘图也不是最好的选择。 这很好,但CILanczosScaleTransform稍微清晰。 [15]


 从iOS 5.1开始,CILanczosScaleTransform不可用。请参阅我的其他答案,了解您可以使用的高质量缩放程序。