如何调整NSImage的大小?

28

我有一个大小为 WxHNSBitmapImageRep

我创建了一个 NSImage 并调用了 addRepresentation:。然后我需要调整 NSImage 的大小。

我尝试使用 setSize 方法,但它不起作用。我该怎么办?


请给我们提供一些代码以便我们进行工作。 - Asciiom
12个回答

41

编辑: 由于这个答案仍然是被接受的答案,但是没有考虑Retina屏幕,我将直接链接到更好的解决方案,该方案在线程的下面:Objective-C Swift 4


因为Paresh的方法完全正确但已经自10.8版本以后被弃用,所以我将发布适用于10.8版本的工作代码。所有功劳归功于Paresh的回答。

- (NSImage *)imageResize:(NSImage*)anImage newSize:(NSSize)newSize {
    NSImage *sourceImage = anImage;
    [sourceImage setScalesWhenResized:YES];
    
    // Report an error if the source isn't a valid image
    if (![sourceImage isValid]){
        NSLog(@"Invalid Image");
    } else {
        NSImage *smallImage = [[NSImage alloc] initWithSize: newSize];
        [smallImage lockFocus];
        [sourceImage setSize: newSize];
        [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
        [sourceImage drawAtPoint:NSZeroPoint fromRect:CGRectMake(0, 0, newSize.width, newSize.height) operation:NSCompositingOperationCopy fraction:1.0];
        [smallImage unlockFocus];
        return smallImage;
    }
    return nil;
}

4
[sourceImage setScalesWhenResized:YES]; 这段代码已经被弃用,因为它不再必要。你可以直接将其删除,这个程序仍然可以完美运行。 - user2002649
2
请注意,此代码将调整为屏幕点大小,而非像素尺寸。在Retina屏幕上,生成的图像将是所请求像素尺寸的两倍。如需在Retina屏幕上进行精确像素尺寸,请使用此方法:https://dev59.com/QGct5IYBdhLWcg3wn-xz#38442746 - Marco
[NSGraphicsContext currentContext] 对我返回了 nil。有什么想法吗? - prabhu
这难道不会有调整sourceImage大小的副作用吗?https://developer.apple.com/documentation/appkit/nsimage/1519987-size?language=objc 这可能不是调用者想要的。 - wcochran

39

Thomas Johannesmeyer使用lockFocus的回答在Retina/HiDPI屏幕上可能无法按您的意图工作:它会以屏幕本地比例的所需大小调整大小,而不是像素

  • 如果您要调整大小以在屏幕上显示,请使用该方法
  • 如果您要为具有精确像素尺寸的文件调整大小,则在Retina(2x DPI)屏幕上运行时将大两倍。

此方法从各种答案中拼凑而成,包括一些在这个相关问题中,无论当前屏幕DPI如何,都会调整大小到指定的像素尺寸

+ (NSImage *)resizedImage:(NSImage *)sourceImage toPixelDimensions:(NSSize)newSize
{
    if (! sourceImage.isValid) return nil;

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

    [NSGraphicsContext saveGraphicsState];
    [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:rep]];
    [sourceImage drawInRect:NSMakeRect(0, 0, newSize.width, newSize.height) fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
    [NSGraphicsContext restoreGraphicsState];

    NSImage *newImage = [[NSImage alloc] initWithSize:newSize];
    [newImage addRepresentation:rep];
    return newImage;
}

1
谢谢Marco,我已经寻找这个东西有一段时间了。 - Douglas Cobb
使用lockFocus()方法的建议已经过时;该方法现在已被软弃用。Marco的方法会在屏幕上生成非Retina图像,但您可以通过将NSBitmapImageRep初始化器的pixelsWidepixelsHigh参数的值添加* 2来解决这个问题。这使得这种方法在Retina屏幕上能够很好地工作。(我不知道它对于打印图像有什么作用...谁还会在2022年打印东西呢?) - Bryan

37

@Marco的答案使用Swift 4编写:

extension NSImage {
    func resized(to newSize: NSSize) -> NSImage? {
        if let bitmapRep = NSBitmapImageRep(
            bitmapDataPlanes: nil, pixelsWide: Int(newSize.width), pixelsHigh: Int(newSize.height),
            bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false,
            colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 0
        ) {
            bitmapRep.size = newSize
            NSGraphicsContext.saveGraphicsState()
            NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: bitmapRep)
            draw(in: NSRect(x: 0, y: 0, width: newSize.width, height: newSize.height), from: .zero, operation: .copy, fraction: 1.0)
            NSGraphicsContext.restoreGraphicsState()

            let resizedImage = NSImage(size: newSize)
            resizedImage.addRepresentation(bitmapRep)
            return resizedImage
        }

        return nil
    }
}

let targetSize = NSSize(width: 256.0, height: 256.0)
let newImageResized = myimage.resized(to: targetSize)

3
毋庸置疑:我现在已经测试了这里或其他地方提供的几乎所有“如何调整图像大小而不考虑分辨率”的答案,这是唯一适用于不同图像分辨率并且无论您是否拥有RetinaDisplay的方法。 - green_knight
仍然适用于macOS Catalina,Xcode 11.3,Swift 5。非常好的答案。+1 - ICL1901
为什么我使用这段代码时总是出现白色边框?这能够复制吗? - techno
谢谢。这是我找到的唯一一个能保留原始bitsPerSample并不将8位图像转换为16位图像的解决方案。虽然我必须显式调用正确的bytesPerRow和bitsPerPixel,但另一方面无法创建16位图像,因此我还必须像@ThomasJohannesmeyer的答案建议的那样绘制图像。 - Enie
对于那些不想要抗锯齿的人,在绘制语句之前添加 NSGraphicsContext.current.imageInterpolation = .none - hotdogsoup.nl
当在视网膜屏幕上产生模糊、非视网膜图像时,只需在NSBitmapRepresentation初始化器的pixelsWidepixelsHigh值后添加* 2即可。缩放后的图像将与其他答案中讨论的旧的lockFocus()缩放方法所产生的图像相匹配。 - Bryan

17

编辑:您可以使用以下函数调整图像大小:

- (NSImage *)imageResize:(NSImage*)anImage
         newSize:(NSSize)newSize 
{
 NSImage *sourceImage = anImage;
 [sourceImage setScalesWhenResized:YES];

 // Report an error if the source isn't a valid image
 if (![sourceImage isValid])
 {
    NSLog(@"Invalid Image");
 } else
 {
    NSImage *smallImage = [[[NSImage alloc] initWithSize: newSize] autorelease];
    [smallImage lockFocus];
    [sourceImage setSize: newSize];
    [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
    [sourceImage compositeToPoint:NSZeroPoint operation:NSCompositeCopy];
    [smallImage unlockFocus];
    return smallImage;
 }
 return nil;
}

其次像这样:
NSData *imageData = [yourImg  TIFFRepresentation]; // converting img into data
NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData]; // converting into BitmapImageRep 
NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:0.9] forKey:NSImageCompressionFactor]; // any number betwwen 0 to 1
imageData = [imageRep representationUsingType:NSJPEGFileType properties:imageProps]; // use NSPNGFileType if needed
NSImage *resizedImage = [[NSImage alloc] initWithData:imageData]; // image created from data

1
如果您使用的是10.8或更高版本,请使用以下代码替换compositeToPoint:operation::[sourceImage drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.]。 - senojsitruc
我不太理解你的“Secondly”代码。它本身并没有调整图像大小,只是进行格式转换...这是为什么需要的,你在哪里与你的第一个清单一起使用这个代码? - konran

16

实际上,修改任何源图像参数(例如大小)都不是必需的。以下代码片段已经使用Swift编写,但我认为您可以从中推断出Objective-C版本:

func resized(to: CGSize) -> NSImage {
    let img = NSImage(size: to)

    img.lockFocus()
    defer {
        img.unlockFocus()
    }

    if let ctx = NSGraphicsContext.current {
        ctx.imageInterpolation = .high
        draw(in: NSRect(origin: .zero, size: to),
             from: NSRect(origin: .zero, size: size),
             operation: .copy,
             fraction: 1)
    }

    return img
}

除了当 to: 设置为16x16时,图像变成32x32之外,一切工作正常。可能是视网膜计算的问题?如何纠正这个问题? - hotdogsoup.nl

5

这是Thomas Johannesmeyer的答案的Swift 4版本:

func resize(image: NSImage, w: Int, h: Int) -> NSImage {
    var destSize = NSMakeSize(CGFloat(w), CGFloat(h))
    var newImage = NSImage(size: destSize)
    newImage.lockFocus()
    image.draw(in: NSMakeRect(0, 0, destSize.width, destSize.height), from: NSMakeRect(0, 0, image.size.width, image.size.height), operation: NSCompositingOperation.sourceOver, fraction: CGFloat(1))
    newImage.unlockFocus()
    newImage.size = destSize
    return NSImage(data: newImage.tiffRepresentation!)!
}

以下是 Swift 4 版本的 Marco 的答案

func resize(image: NSImage, w: Int, h: Int) -> NSImage {
    let destSize = NSMakeSize(CGFloat(w), CGFloat(h))
    let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(destSize.width), pixelsHigh: Int(destSize.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 0)
    rep?.size = destSize
    NSGraphicsContext.saveGraphicsState()
    if let aRep = rep {
        NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: aRep)
    }
    image.draw(in: NSMakeRect(0, 0, destSize.width, destSize.height),     from: NSZeroRect, operation: NSCompositingOperation.copy, fraction: 1.0)
    NSGraphicsContext.restoreGraphicsState()
    let newImage = NSImage(size: destSize)
    if let aRep = rep {
        newImage.addRepresentation(aRep)
    }
    return newImage
}

3

下面是完整的 Swift 3 解答(修改自 @Erik Aigner 的 上述回答):

extension NSImage {
    func resizeImage(width: CGFloat, _ height: CGFloat) -> NSImage {
        let img = NSImage(size: CGSize(width:width, height:height))

        img.lockFocus()
        let ctx = NSGraphicsContext.current()
        ctx?.imageInterpolation = .high
        self.draw(in: NSMakeRect(0, 0, width, height), from: NSMakeRect(0, 0, size.width, size.height), operation: .copy, fraction: 1)
        img.unlockFocus()

        return img
    }
}

2

这里有一个Swift 3版本,保持图像比例不变,只需将minimumSize设置为您想要的最小高度或宽度:

func imageResized(image: NSImage) -> NSImage {
    let ratio = image.size.height / image.size.width

    let width: CGFloat
    let height: CGFloat
    // We keep ratio of image
    if ratio > 1 {
        width = minimumSize
        height = minimumSize * ratio
    } else {
        width = minimumSize
        height = minimumSize * (1 / ratio)
    }
    let destSize = NSSize(width: width, height: height)

    let newImage = NSImage(size: destSize)
    newImage.lockFocus()
    image.draw(in: NSRect(x: 0, y: 0, width: destSize.width, height: destSize.height), from: NSRect(x: 0, y: 0, width: image.size.width, height: image.size.height), operation: .sourceOver, fraction: 1.0)
    newImage.unlockFocus()
    newImage.size = destSize
    return NSImage(data: newImage.tiffRepresentation!)!
}

2

2020 | SWIFT 4和5:

用法:

let resizedImg = someImage.resizedCopy(w: 500.0, h:500.0)
let scaledImg = someImage.scaledCopy( sizeOfLargerSide: 1000.0)

//and bonus:
scaledImg.writePNG(toURL: someUrl )

代码:

extension NSImage {
    func scaledCopy( sizeOfLargerSide: CGFloat) ->  NSImage {
        var newW: CGFloat
        var newH: CGFloat
        var scaleFactor: CGFloat
        
        if ( self.size.width > self.size.height) {
            scaleFactor = self.size.width / sizeOfLargerSide
            newW = sizeOfLargerSide
            newH = self.size.height / scaleFactor
        }
        else{
            scaleFactor = self.size.height / sizeOfLargerSide
            newH = sizeOfLargerSide
            newW = self.size.width / scaleFactor
        }
        
        return resizedCopy(w: newW, h: newH)
    }
    
    
    func resizedCopy( w: CGFloat, h: CGFloat) -> NSImage {
        let destSize = NSMakeSize(w, h)
        let newImage = NSImage(size: destSize)
        
        newImage.lockFocus()
        
        self.draw(in: NSRect(origin: .zero, size: destSize),
                  from: NSRect(origin: .zero, size: self.size),
                  operation: .copy,
                  fraction: CGFloat(1)
        )
        
        newImage.unlockFocus()
        
        guard let data = newImage.tiffRepresentation,
              let result = NSImage(data: data)
        else { return NSImage() }
        
        return result
    }
    
    public func writePNG(toURL url: URL) {
        guard let data = tiffRepresentation,
              let rep = NSBitmapImageRep(data: data),
              let imgData = rep.representation(using: .png, properties: [.compressionFactor : NSNumber(floatLiteral: 1.0)]) else {

            Swift.print("\(self) Error Function '\(#function)' Line: \(#line) No tiff rep found for image writing to \(url)")
            return
        }

        do {
            try imgData.write(to: url)
        }catch let error {
            Swift.print("\(self) Error Function '\(#function)' Line: \(#line) \(error.localizedDescription)")
        }
    }
}


1

仅对NSBitmapImageRep进行缩放

static NSBitmapImageRep *i_scale_bitmap(const NSBitmapImageRep *bitmap, const uint32_t width, const uint32_t height)
{
    NSBitmapImageRep *new_bitmap = NULL;
    CGImageRef dest_image = NULL;
    CGColorSpaceRef space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    CGContextRef context = CGBitmapContextCreate(NULL, (size_t)width, (size_t)height, PARAM(bitsPerComponent, 8), PARAM(bytesPerRow, (size_t)(width * 4)), space, kCGImageAlphaPremultipliedLast);
    CGImageRef src_image = [bitmap CGImage];
    CGRect rect = CGRectMake((CGFloat)0.f, (CGFloat)0.f, (CGFloat)width, (CGFloat)height);
    CGContextDrawImage(context, rect, src_image);
    dest_image = CGBitmapContextCreateImage(context);
    CGContextRelease(context);
    CGColorSpaceRelease(space);
    new_bitmap = [[NSBitmapImageRep alloc] initWithCGImage:dest_image];
    CGImageRelease(dest_image);
    return new_bitmap;
}

针对基于NSBitmapImageRep缩放NSImage的方法

ImageImp *imgimp_create_scaled(const ImageImp *image, const uint32_t new_width, const uint32_t new_height)
{
    NSImage *src_image = (NSImage*)image;
    NSBitmapImageRep *src_bitmap, *dest_bitmap;
    NSImage *scaled_image = nil;
    cassert_no_null(src_image);
    cassert([[src_image representations] count] == 1);
    cassert([[[src_image representations] objectAtIndex:0] isKindOfClass:[NSBitmapImageRep class]]);
    src_bitmap = (NSBitmapImageRep*)[[(NSImage*)image representations] objectAtIndex:0];
    cassert_no_null(src_bitmap);
    dest_bitmap = i_scale_bitmap(src_bitmap, new_width, new_height);
    scaled_image = [[NSImage alloc] initWithSize:NSMakeSize((CGFloat)new_width, (CGFloat)new_height)];
    [scaled_image addRepresentation:dest_bitmap];
    cassert([scaled_image retainCount] == 1);
    [dest_bitmap release];
    return (ImageImp*)scaled_image;
}

直接在NSImage上绘制(使用[NSImage lockFocus]等)将创建一个NSCGImageSnapshotRep而不是NSBitmapImageRep。


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