UIImage在缩放时变得模糊,为什么?(iOS 5.0)

12

当 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;
}
4个回答

46

四舍五入

首先,在缩放之前确保您对大小进行四舍五入。 在这种情况下,drawInRect:可能会模糊图像。 要四舍五入为最接近的整数值:

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

对于某些任务,您可能希望向下取整(floorf)或向上取整(ceilf)。

CILanczosScaleTransform不可用

那么,请忽略我之前关于CILanczosScaleTransform的推荐。虽然在iOS 5.0中提供了部分Core Image功能,但是Lanczos缩放却没有。如果它变得可用,请使用它。对于在Mac OS上工作的人,它是可用的,请使用它。

vImage Scaling

但是,在vImage中有一种高质量的缩放算法。以下图片展示了使用vImageScaledImage方法与不同上下文插值选项进行比较的结果。还要注意这些选项在不同的缩放级别下表现不同。

在这张图表上,它保留了最多的线条细节: Scaling comparison on diagram

在这张照片上,比较左下角的叶子: Scaling comparison on tree photograph

在这张照片上,比较右下角的纹理: Scaling comparison on rock photograph

不要在像素艺术上使用它;它会创建奇怪的缩放伪影: Scaling comparison on pixel art, showing scaling artifacts

虽然在一些图像上它具有有趣的四舍五入效果: Scaling comparison on Space Invader

性能

毫不奇怪,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: https://dev59.com/qHRB5IYBdhLWcg3w-8A9#1262893
        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;
}

3
{PhotoScrollerNetwork} 的作者感谢代码中的归属说明: // Thanks: https://github.com/dhoerl/PhotoScrollerNetwork :-) - David H

5
我尝试了@Dondragmer的VImage答案,但结果的质量不够好(我正在将图像缩小1/10)。
不过这个解决方案对我有用:UIImage的drawInRect:平滑图像
基本上,它只是说在视网膜显示器上,您需要使用视网膜参数创建图形上下文:
UIGraphicsBeginImageContextWithOptions(size, NO, 2.0f);

1
以上和以下的方法都没用,但这个方法让我卡了2天,点赞支持原创,干杯!!! - AamirR

2
尝试使用put方法:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);

在绘制之前进行高质量插值。


@shuiyouren 这已经是最好的了。据我所知,Quartz2D使用双线性插值,因为它在实时处理中更快。质量比双三次插值低。如果你想要更高的质量,可能需要使用自己的插值算法。 - He Shiming

0
在放大图像并且想要保留锐利的角落的特殊情况下,您需要关闭图像插值。如果您的图像是像素艺术,则这正是您想要的。在大多数其他情况下,它并不适用。
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略微更清晰。

编辑:从iOS 5.1开始,CILanczosScaleTransform不再可用。请查看我的其他答案以获取可以使用的高质量缩放例程。


你能给我一个关于CILanczosScaleTransform的例子吗? - shuiyouren
@shuiyouren:我还没有给出CILanczosScaleTransform的示例,因为我不知道你是否需要它。请在原帖中附上两张图片:缩放前的图片和缩放后的图片。然后有人可能能够告诉你需要什么算法。 - Cowirrie
@shuiyouren:非常抱歉让您感到困扰。无论如何,我已经描述了需要使用CILanczosScaleTransform的情况。您能否确认这些情况适用于您的问题? - Cowirrie
我认为CILanczosScaleTransform可以解决这个问题,我尝试寻找关于如何使用CILanczosScaleTransform的指导。 - shuiyouren

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接