Xcode 如何以正确的方式从NSImage(视网膜问题)保存PNG文件?
我正试图将每个图像保存在一个数组中,作为一个.PNG文件(也是正确的大小,由于retina mac dpi问题,没有放大),但似乎找不到解决方案。目前的解决方案似乎都不适合我。我试过每一个,但它们都会在retina.etc中将72x72文件保存为144x144 更具体地说,我正在寻找NSImage类别(是的,我在Mac环境中工作) 我试图让用户选择一个目录来保存它们,并从数组中执行图像保存,如下所示:Xcode 如何以正确的方式从NSImage(视网膜问题)保存PNG文件?,xcode,macos,save,png,retina,Xcode,Macos,Save,Png,Retina,我正试图将每个图像保存在一个数组中,作为一个.PNG文件(也是正确的大小,由于retina mac dpi问题,没有放大),但似乎找不到解决方案。目前的解决方案似乎都不适合我。我试过每一个,但它们都会在retina.etc中将72x72文件保存为144x144 更具体地说,我正在寻找NSImage类别(是的,我在Mac环境中工作) 我试图让用户选择一个目录来保存它们,并从数组中执行图像保存,如下所示: - (IBAction)saveImages:(id)sender { // Prep
- (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
我对原件中提供的答案也有疑问。通过进一步阅读,我找到了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;
}
泰德-非常感谢你的代码-我为此苦恼了好几天!它帮助我从NSImage中写入一个文件,同时将分辨率保持在72DPI,尽管我的Mac上安装了视网膜显示器。为了帮助其他希望将NSImage保存到分辨率为72 DPI的特定像素大小和类型(PNG、JPG等)的文件中的人,下面是适用于我的代码。我发现在调用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
谢谢泰德&雪桨手 对于不熟悉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)
}
我在将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")
文件路径=file:///Users/Andrew/Library/Containers/Objects-and-Such.ColourSpace/Data/Documents/TestImage.png
…适用于沙盒应用程序,如果我选择了桌面:
文件路径=file:///Users/Andrew/Library/Containers/Objects-and-Such.ColourSpace/Data/Desktop/TestImage.png
我希望这有帮助
let fileManager = FileManager.default
let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
let documentPath = documentsURL.path
let filePath = documentsURL.appendingPathComponent("TestImage.png")