如何从NSImage保存PNG文件(视网膜问题)正确的方法?

时间:2014-04-24 02:15:39

标签: xcode macos save png retina

我正在尝试将数组中的每个图像保存为.PNG文件(也是正确的大小,因为视网膜mac dpi问题没有按比例放大)并且似乎无法找到解决方案。 How to save PNG file from NSImage (retina issues)的任何解决方案似乎都不适用于我。我已经尝试了每一个,他们每个人仍然会在视网膜上保存一个72x72文件144x144。等等。

更具体地说,我正在寻找一个NSImage类别(是的,我在Mac环境中工作)

我正在尝试让用户选择一个目录来保存它们并执行从数组中保存图像,如下所示:

- (IBAction)saveImages:(id)sender {
    // Prepare Images that are checked and put them in an array
    [self prepareImages];

    if ([preparedImages count] == 0) {
        NSLog(@"We have no preparedImages to save!");
        NSAlert *alert = [[NSAlert alloc] init];
        [alert setAlertStyle:NSInformationalAlertStyle];
        [alert setMessageText:NSLocalizedString(@"Error", @"Save Images Error Text")];
        [alert setInformativeText:NSLocalizedString(@"You have not selected any images to create.", @"Save Images Error Informative Text")];

        [alert beginSheetModalForWindow:self.window
                          modalDelegate:self
                        didEndSelector:@selector(testDatabaseConnectionDidEnd:returnCode:
                                                   contextInfo:)
                            contextInfo:nil];
        return;
    } else {
        NSLog(@"We have prepared %lu images.", (unsigned long)[preparedImages count]);
    }

    // Save Dialog
    // Create a File Open Dialog class.
    //NSOpenPanel* openDlg = [NSOpenPanel openPanel];
    NSSavePanel *panel = [NSSavePanel savePanel];

    // Set array of file types
    NSArray *fileTypesArray;
    fileTypesArray = [NSArray arrayWithObjects:@"jpg", @"gif", @"png", nil];

    // Enable options in the dialog.
    //[openDlg setCanChooseFiles:YES];
    //[openDlg setAllowedFileTypes:fileTypesArray];
    //[openDlg setAllowsMultipleSelection:TRUE];
    [panel setNameFieldStringValue:@"Images.png"];
    [panel setDirectoryURL:directoryPath];


    // Display the dialog box.  If the OK pressed,
    // process the files.
    [panel beginWithCompletionHandler:^(NSInteger result) {

        if (result == NSFileHandlingPanelOKButton) {
            NSLog(@"OK Button!");
            // create a file manager and grab the save panel's returned URL
            NSFileManager *manager = [NSFileManager defaultManager];
            directoryPath = [panel URL];
            [[self directoryLabel] setStringValue:[NSString stringWithFormat:@"%@", directoryPath]];

            // then copy a previous file to the new location

            // copy item at URL was self.myURL
            // copy images that are created from array to this path


            for (NSImage *image in preparedImages) {
#warning Fix Copy Item At URL to copy image from preparedImages array to save each one
                NSString *imageName = image.name;
                NSString *imagePath = [[directoryPath absoluteString] stringByAppendingPathComponent:imageName];

                //[manager copyItemAtURL:nil toURL:directoryPath error:nil];
                NSLog(@"Trying to write IMAGE: %@ to URL: %@", imageName, imagePath);
                //[image writePNGToURL:[NSURL URLWithString:imagePath] outputSizeInPixels:image.size error:nil];
                [self saveImage:image atPath:imagePath];
            }
            //[manager copyItemAtURL:nil toURL:directoryPath error:nil];


        }
    }];

    [preparedImages removeAllObjects];

    return;

}

一个用户试图通过使用此NSImage类别来回答他,但它不会为我生成任何文件或PNG。

@interface NSImage (SSWPNGAdditions)

- (BOOL)writePNGToURL:(NSURL*)URL outputSizeInPixels:(NSSize)outputSizePx error:(NSError*__autoreleasing*)error;

@end

@implementation NSImage (SSWPNGAdditions)

- (BOOL)writePNGToURL:(NSURL*)URL outputSizeInPixels:(NSSize)outputSizePx error:(NSError*__autoreleasing*)error
{
    BOOL result = YES;
    NSImage* scalingImage = [NSImage imageWithSize:[self size] flipped:[self isFlipped] drawingHandler:^BOOL(NSRect dstRect) {
        [self drawAtPoint:NSMakePoint(0.0, 0.0) fromRect:dstRect operation:NSCompositeSourceOver fraction:1.0];
        return YES;
    }];
    NSRect proposedRect = NSMakeRect(0.0, 0.0, outputSizePx.width, outputSizePx.height);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    CGContextRef cgContext = CGBitmapContextCreate(NULL, proposedRect.size.width, proposedRect.size.height, 8, 4*proposedRect.size.width, colorSpace, kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colorSpace);
    NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:NO];
    CGContextRelease(cgContext);
    CGImageRef cgImage = [scalingImage CGImageForProposedRect:&proposedRect context:context hints:nil];
    CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)(URL), kUTTypePNG, 1, NULL);
    CGImageDestinationAddImage(destination, cgImage, nil);
    if(!CGImageDestinationFinalize(destination))
    {
        NSDictionary* details = @{NSLocalizedDescriptionKey:@"Error writing PNG image"};
        [details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
        *error = [NSError errorWithDomain:@"SSWPNGAdditionsErrorDomain" code:10 userInfo:details];
        result = NO;
    }
    CFRelease(destination);
    return result;
}

@end

4 个答案:

答案 0 :(得分:9)

我在原thread中提供的答案也遇到了问题。进一步阅读让我在post由埃里卡·萨杜(Erica Sadun)找到了与没有视网膜显示器的视网膜显示器调试代码有关的问题。她创建了所需大小的位图,然后将当前绘图上下文(基于显示/视网膜受影响)替换为与新位图关联的通用图形。然后,她将原始图像渲染到位图中(使用通用图形上下文)。

我拿了她的代码并在NSImage上做了一个快速的类别,它似乎为我做了这个工作。致电后

    NSBitmapImageRep *myRep = [myImage unscaledBitmapImageRep];

你应该有一个正确(原始)尺寸的位图,无论你开始使用什么类型的物理显示。从这一点开始,您可以在未缩放的位图上调用representationUsingType:properties以获取您要写出的任何格式。

这是我的类别(标题省略)。注意 - 您可能需要公开位图初始化程序的颜色空间部分。这是适合我特定情况的价值。

-(NSBitmapImageRep *)unscaledBitmapImageRep {

    NSBitmapImageRep *rep = [[NSBitmapImageRep alloc]
                               initWithBitmapDataPlanes:NULL
                                             pixelsWide:self.size.width
                                             pixelsHigh:self.size.height
                                          bitsPerSample:8
                                        samplesPerPixel:4
                                               hasAlpha:YES
                                               isPlanar:NO
                                         colorSpaceName:NSDeviceRGBColorSpace
                                            bytesPerRow:0
                                           bitsPerPixel:0];
    rep.size = self.size;

   [NSGraphicsContext saveGraphicsState];
   [NSGraphicsContext setCurrentContext:
            [NSGraphicsContext graphicsContextWithBitmapImageRep:rep]];

    [self drawAtPoint:NSMakePoint(0, 0) 
             fromRect:NSZeroRect 
            operation:NSCompositeSourceOver 
             fraction:1.0];

    [NSGraphicsContext restoreGraphicsState];
    return rep;
}

答案 1 :(得分:5)

感谢 tad &的 SnowPaddler

对于不熟悉Cocoa并使用Swift 4的人,您可以查看Swift 2&编辑历史中的Swift 3版本:

import Cocoa

func unscaledBitmapImageRep(forImage image: NSImage) -> NSBitmapImageRep {
    guard let rep = NSBitmapImageRep(
        bitmapDataPlanes: nil,
        pixelsWide: Int(image.size.width),
        pixelsHigh: Int(image.size.height),
        bitsPerSample: 8,
        samplesPerPixel: 4,
        hasAlpha: true,
        isPlanar: false,
        colorSpaceName: .deviceRGB,
        bytesPerRow: 0,
        bitsPerPixel: 0
        ) else {
            preconditionFailure()
    }

    NSGraphicsContext.saveGraphicsState()
    NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep)
    image.draw(at: .zero, from: .zero, operation: .sourceOver, fraction: 1.0)
    NSGraphicsContext.restoreGraphicsState()

    return rep
}

func writeImage(
    image: NSImage,
    usingType type: NSBitmapImageRep.FileType,
    withSizeInPixels size: NSSize?,
    to url: URL) throws {
    if let size = size {
        image.size = size
    }
    let rep = unscaledBitmapImageRep(forImage: image)

    guard let data = rep.representation(using: type, properties: [.compressionFactor: 1.0]) else {
        preconditionFailure()
    }

    try data.write(to: url)
}

答案 2 :(得分:3)

Tad - 非常感谢您使用此代码 - 我为此感到痛苦!它帮助我从NSImage写了一个文件,同时尽管在我的Mac上安装了视网膜显示器,但仍保持72DPI的分辨率。为了其他想要将NSImage保存到具有特定像素大小和类型(PNG,JPG等)且分辨率为72 DPI的文件的人的利益,这里的代码对我有用。我发现你需要在调用unscaledBitmapImageRep之前设置图像的大小才能使其工作。

-(void)saveImage:(NSImage *)image
     AsImageType:(NSBitmapImageFileType)imageType
         forSize:(NSSize)targetSize
          atPath:(NSString *)path
{
    image.size = targetSize;

    NSBitmapImageRep * rep = [image unscaledBitmapImageRep:targetSize];

    // Write the target image out to a file
    NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:1.0] forKey:NSImageCompressionFactor];
    NSData *targetData = [rep representationUsingType:imageType properties:imageProps];
    [targetData writeToFile:path atomically: NO];

    return;
}

我还在下面列出了类别标题和.m文件的源代码。

NSImage + Scaling.h文件:

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

@interface NSImage (Scaling)

-(NSBitmapImageRep *)unscaledBitmapImageRep;

@end

NSImage + Scaling.m文件:

#import "NSImage+Scaling.h"

#pragma mark - NSImage_Scaling
@implementation NSImage (Scaling)

-(NSBitmapImageRep *)unscaledBitmapImageRep
{

    NSBitmapImageRep *rep = [[NSBitmapImageRep alloc]
                             initWithBitmapDataPlanes:NULL
                             pixelsWide:self.size.width
                             pixelsHigh:self.size.height
                             bitsPerSample:8
                             samplesPerPixel:4
                             hasAlpha:YES
                             isPlanar:NO
                             colorSpaceName:NSDeviceRGBColorSpace
                             bytesPerRow:0
                             bitsPerPixel:0];

    [NSGraphicsContext saveGraphicsState];
    [NSGraphicsContext setCurrentContext:
    [NSGraphicsContext graphicsContextWithBitmapImageRep:rep]];    

    [self drawAtPoint:NSMakePoint(0, 0)
             fromRect:NSZeroRect
            operation:NSCompositeSourceOver
             fraction:1.0];

    [NSGraphicsContext restoreGraphicsState];
    return rep;
}

@end

答案 3 :(得分:1)

在将NSImage对象保存到PNG或JPG文件时,我遇到了同样的困难,我终于明白了为什么...

首先,上面显示的代码摘录效果很好:

import Cocoa

func unscaledBitmapImageRep(forImage image: NSImage) -> NSBitmapImageRep {
    guard let rep = NSBitmapImageRep(
        bitmapDataPlanes: nil,
        pixelsWide: Int(image.size.width),
        pixelsHigh: Int(image.size.height),
        bitsPerSample: 8,
        samplesPerPixel: 4,
        hasAlpha: true,
        isPlanar: false,
        colorSpaceName: .deviceRGB,
        bytesPerRow: 0,
        bitsPerPixel: 0
    ) else {
        preconditionFailure()
    }

    NSGraphicsContext.saveGraphicsState()
    NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep)
    image.draw(at: .zero, from: .zero, operation: .sourceOver, fraction: 1.0)
    NSGraphicsContext.restoreGraphicsState()

    return rep
}

func writeImage(
    image: NSImage,
    usingType type: NSBitmapImageRep.FileType,
    withSizeInPixels size: NSSize?,
    to url: URL) throws {
    if let size = size {
        image.size = size
    }
    let rep = unscaledBitmapImageRep(forImage: image)

    guard let data = rep.representation(using: type, properties:[.compressionFactor: 1.0]) else {
    preconditionFailure()
    }

    try data.write(to: url)
}

...但是,由于我使用的是沙盒Mac App,正如您所知,这是Apple App Store发行的要求,所以我注意到在测试我的主机时必须谨慎选择目标目录初步代码。

如果我通过以下方式使用文件URL:

let fileManager = FileManager.default
let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
let documentPath = documentsURL.path
let filePath = documentsURL.appendingPathComponent("TestImage.png")

filePath = file:///Users/Andrew/Library/Containers/Objects-and-Such.ColourSpace/Data/Documents/TestImage.png

...适用于沙盒应用程序,如果选择了桌面之类的目录,则文件保存将无效:

filePath = file:///Users/Andrew/Library/Containers/Objects-and-Such.ColourSpace/Data/Desktop/TestImage.png

我希望这会有所帮助。