【问题标题】:How To Save PNG file From NSImage (retina issues) The Right Way?如何以正确的方式从 NSImage(视网膜问题)保存 PNG 文件?
【发布时间】:2014-06-09 03:07:27
【问题描述】:

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

更具体地说,我正在寻找一个 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

【问题讨论】:

    标签: xcode macos save png retina


    【解决方案1】:

    我对原始thread 中提供的答案也有疑问。进一步阅读使我找到了 Erica Sadun 撰写的post,该邮件涉及在没有视网膜显示器的情况下调试视网膜显示器的代码。她创建了一个所需大小的位图,然后用与新位图关联的通用绘图上下文(基于显示/受视网膜影响)替换当前绘图上下文。然后她将原始图像渲染到位图中(使用通用图形上下文)。

    我拿了她的代码并在 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;
    }
    

    【讨论】:

      【解决方案2】:

      我在将 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 应用程序,如您所知,这是 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

      我希望这会有所帮助。

      【讨论】:

        【解决方案3】:

        感谢tadSnowPaddler

        对于不熟悉 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)
        }
        

        【讨论】:

          【解决方案4】:

          Tad - 非常感谢您提供这段代码 - 我为此苦恼了好几天!尽管我的 Mac 上安装了视网膜显示器,但它帮助我从 NSImage 写入文件,同时将分辨率保持在 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
          

          【讨论】:

            猜你喜欢
            • 2013-07-04
            • 1970-01-01
            • 2011-10-21
            • 2014-12-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多