NSImage不能按比例缩放

8
我正在开发一个快速应用程序,在其中有一个方法,应该将@2x图像调整为普通图像。但问题是它没有实现:(
为什么呢?
-(BOOL)createNormalImage:(NSString*)inputRetinaImagePath {

    NSImage *inputRetinaImage = [[NSImage alloc] initWithContentsOfFile:inputRetinaImagePath];



    NSSize size = NSZeroSize;
    size.width = inputRetinaImage.size.width*0.5;
    size.height = inputRetinaImage.size.height*0.5;

    [inputRetinaImage setSize:size];


    NSLog(@"%f",inputRetinaImage.size.height);


    NSBitmapImageRep *imgRep = [[inputRetinaImage representations] objectAtIndex: 0];

    NSData *data = [imgRep representationUsingType: NSPNGFileType properties: nil];

    NSString *outputFilePath = [[inputRetinaImagePath substringToIndex:inputRetinaImagePath.length - 7] stringByAppendingString:@".png"];

    NSLog([@"Normal version file path: " stringByAppendingString:outputFilePath]);
    [data writeToFile:outputFilePath atomically: NO];
    return true;
}

以下是一个行不通的解决方案:setScalesWhenResized:。这曾经是您实现此功能的方法,但自从Snow Leopard以来已被弃用,并且在Lion中不起作用。 - Peter Hosey
你不能只是在一个较小的矩形中绘制它吗?或者传递一个NSAffineTransform作为提示? - Ramy Al Zuhouri
@RamyAlZuhouri:听起来这个应用程序的目的是降低图像的分辨率并保存结果,以便创建2x的资产并从相同的资产生成1x的资产。 - Peter Hosey
你是显式地绘制图像还是只是将其分配给图像容器? - Ramy Al Zuhouri
根据问题中的代码,都不是。调整大小后的图像从未离开该方法,既没有传递给属性、实例变量,也没有传递给其他对象。 - Peter Hosey
2个回答

11

在处理NSImage的大小属性时,必须非常小心。它并不一定指的是bitmapRepresentation的像素尺寸,例如它可能指的是显示尺寸。一个NSImage可能有多个bitmapRepresentations,用于不同的输出尺寸。

同样地,改变NSImage的大小属性对bitmapRepresentations没有任何影响。

因此,您需要确定您想要的输出图像的大小,然后使用源NSImage中的bitmapRepresentation在该大小上绘制新图像。

获取该大小取决于您如何获取输入图像以及您所知道的内容。例如,如果您确信您的输入图像只有一个bitmapImageRep,则可以使用此类型的东西(作为NSImage的类别)

  - (NSSize) pixelSize
{
    NSBitmapImageRep* bitmap = [[self representations] objectAtIndex:0];
    return NSMakeSize(bitmap.pixelsWide,bitmap.pixelsHigh);
}

即使您有多个bitmapImageReps,第一个也应该是最大的,如果这是创建Retina图像的大小,则应该是您想要的Retina大小。

当您计算出最终大小后,可以制作图像:

- (NSImage*) resizeImage:(NSImage*)sourceImage size:(NSSize)size
{

    NSRect targetFrame = NSMakeRect(0, 0, size.width, size.height);     
    NSImage* targetImage = nil;
    NSImageRep *sourceImageRep =
    [sourceImage bestRepresentationForRect:targetFrame
                                   context:nil
                                     hints:nil];

    targetImage = [[NSImage alloc] initWithSize:size];

    [targetImage lockFocus];
    [sourceImageRep drawInRect: targetFrame];
    [targetImage unlockFocus];

return targetImage; 

}

更新

这里是一个更详细的 NSImage 像素大小获取类别版本... 让我们不假设任何关于图像的事情,包括它有多少 imageReps,是否有 任何 bitmapImageReps... 这将返回它可以找到的最大像素尺寸。如果它找不到 bitMapImageRep 像素尺寸,它将使用它能够获得的任何其他东西,这很可能是边界框尺寸(由 eps 和 pdf 使用)。

NSImage+PixelSize.h

#import <Cocoa/Cocoa.h>
#import <QuartzCore/QuartzCore.h>

@interface NSImage (PixelSize)

- (NSInteger) pixelsWide;
- (NSInteger) pixelsHigh;
- (NSSize) pixelSize;

@end

NSImage+PixelSize.m

#import "NSImage+PixelSize.h"

@implementation NSImage (Extensions)

- (NSInteger) pixelsWide
{
    /*
     returns the pixel width of NSImage.
     Selects the largest bitmapRep by preference
     If there is no bitmapRep returns largest size reported by any imageRep.
     */
    NSInteger result = 0;
    NSInteger bitmapResult = 0;

    for (NSImageRep* imageRep in [self representations]) {
        if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) {
            if (imageRep.pixelsWide > bitmapResult)
                bitmapResult = imageRep.pixelsWide;
        } else {
            if (imageRep.pixelsWide > result)
                result = imageRep.pixelsWide;
        }
    }
    if (bitmapResult) result = bitmapResult;
    return result;

}

- (NSInteger) pixelsHigh
{
    /*
     returns the pixel height of NSImage.
     Selects the largest bitmapRep by preference
     If there is no bitmapRep returns largest size reported by any imageRep.
     */
    NSInteger result = 0;
    NSInteger bitmapResult = 0;

    for (NSImageRep* imageRep in [self representations]) {
        if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) {
            if (imageRep.pixelsHigh > bitmapResult)
                bitmapResult = imageRep.pixelsHigh;
        } else {
            if (imageRep.pixelsHigh > result)
                result = imageRep.pixelsHigh;
        }
    }
    if (bitmapResult) result = bitmapResult;
    return result;
}

- (NSSize) pixelSize
{
    return NSMakeSize(self.pixelsWide,self.pixelsHigh);
}

@end

在您当前的文件中,您可以使用#import "NSImage+PixelSize.h"来使其可访问。

有了这个图像类别和resize:方法,您可以修改您的方法如下:

//size.width = inputRetinaImage.size.width*0.5;
//size.height = inputRetinaImage.size.height*0.5;
size.width  = inputRetinaImage.pixelsWide*0.5;
size.height = inputRetinaImage.pixelsHigh*0.5;

//[inputRetinaImage setSize:size];
NSImage* outputImage = [self resizeImage:inputRetinaImage size:size];

//NSBitmapImageRep *imgRep = [[inputRetinaImage representations] objectAtIndex: 0];
NSBitmapImageRep *imgRep = [[outputImage representations] objectAtIndex: 0];

这应该为您解决问题(附条件:我没有在您的代码上测试过)


为什么不在所有三个地方都使用 bestRepresentationForRect:context:hints: 呢? - Peter Hosey
1
我认为这可能是个问题…BestRepresentationForRect函数建议我们知道最终想要的尺寸——但在这种情况下我们可能并不知道,因为我们要把视网膜位图的大小减半。另外,作为了解NSImage如何组成的练习,掌握各种imageRep实际上是如何相互关联的也不失为一个好主意。 - foundry
这并不是一个圆形,因为该矩形在源图像的用户空间中。如果您想要整个图像,则无论图像中任何表示的像素分辨率(或缺乏像素分辨率)或所需缩放大小或分辨率如何,该矩形都是(NSRect){ NSZeroPoint, image.size }。我确实同意了解代表的本质是好的;但是,获取第一个代表并假设它将是您想要的东西并不是达到这个目的的好方法。 - Peter Hosey
@Peter - 首先,我要感谢您授予我奖金,非常感激。我相信我的答案仍有改进的空间。关于image.size,我同意您的观点,但它绕过了这里的问题,即我们正在尝试获取像素尺寸(至少我是这样解释这个问题的)...而对rect/size的引用则涉及到显示尺寸 - 特别是,image.size不一定与图像的“原始”像素大小有任何关系。我认为这是一个值得强调的观点,因为它可能是许多混淆的根源。 - foundry
@peterhosey,在你最后的观点上 - 抓取第一个imageReps是不好的解决方案 - 我同意,这样做对源图像做出了太多的假设,这就是为什么我扩展了我的答案,提供了更详细的像素尺寸类别...我不应该那么匆忙! - foundry

2

我修改了用于缩小图像的脚本,为您提供更好的使用体验 :)

-(BOOL)createNormalImage:(NSString*)inputRetinaImagePath {

    NSImage *inputRetinaImage = [[NSImage alloc] initWithContentsOfFile:inputRetinaImagePath];

    //determine new size
    NSBitmapImageRep* bitmapImageRep = [[inputRetinaImage representations] objectAtIndex:0];
    NSSize size = NSMakeSize(bitmapImageRep.pixelsWide * 0.5,bitmapImageRep.pixelsHigh * 0.5);

    NSLog(@"size = %@", NSStringFromSize(size));

    //get CGImageRef
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)[inputRetinaImage TIFFRepresentation], NULL);
    CGImageRef oldImageRef =  CGImageSourceCreateImageAtIndex(source, 0, NULL);
    CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(oldImageRef);
    if (alphaInfo == kCGImageAlphaNone) alphaInfo = kCGImageAlphaNoneSkipLast;

    // Build a bitmap context
    CGContextRef bitmap = CGBitmapContextCreate(NULL, size.width, size.height, 8, 4 * size.width, CGImageGetColorSpace(oldImageRef), alphaInfo);

    // Draw into the context, this scales the image
    CGContextDrawImage(bitmap, CGRectMake(0, 0, size.width, size.height), oldImageRef);

    // Get an image from the context
    CGImageRef newImageRef = CGBitmapContextCreateImage(bitmap);

    //this does not work in my test.
    NSString *outputFilePath = [[inputRetinaImagePath substringToIndex:inputRetinaImagePath.length - 7] stringByAppendingString:@".png"];

    //but this does!
    NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString* docsDirectory = [paths objectAtIndex:0];
    NSString *newfileName = [docsDirectory stringByAppendingFormat:@"/%@", [outputFilePath lastPathComponent]];

    CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:newfileName];
    CGImageDestinationRef destination = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, NULL);
    CGImageDestinationAddImage(destination, newImageRef, nil);

    if (!CGImageDestinationFinalize(destination)) {
        NSLog(@"Failed to write image to %@", newfileName);
    }

    CFRelease(destination);

    return true;
}

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