7

A Swift question. Not an Obj-C question. Please don't mark it as duplicate

I am trying to write a class that generates a JPEG thumbnail on OS X. I hope I have something sensible that forms the scaled down NSImage but I'm struggling to write a JPEG file to disk.

class ImageThumbnail {
    let size = CGSize(width: 128, height: 128)
    var originalPath = ""

    init(originalPath: String){
        self.originalPath = originalPath
    }

    func generateThumbnail() {
        let url = NSURL(fileURLWithPath: originalPath)

        if let imageSource = CGImageSourceCreateWithURL(url, nil) {
            let options: [NSString: NSObject] = [
                kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height) / 2.0,
                kCGImageSourceCreateThumbnailFromImageAlways: true
            ]

            let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options)
            let scaledImage = cgImage.flatMap { NSImage(CGImage: $0, size: size) }

            // How do I write scaledImage to a JPEG file?
        }
    }
}

Example usage: ImageThumbail(originalPath: "/path/to/original").generateThumbnail()

henster
  • 157
  • 1
  • 6
  • I had seen "the duplicate" and the answers are in Obj-C (apart from the last answer which has no votes and doesn't work for me). Also it seems @JAL edited my text where I said I working in Swift. – henster Jan 26 '16 at 20:16
  • Sorry about that, I removed the "Swift newbie" message because it was considered noise. I have undeleted my answer, which differs slightly from the answer in the dupe post. Let me know if it helps you. – JAL Jan 26 '16 at 20:17
  • Do you want to take an image file and create a separate thumbnail (smaller) representation of it to store in a **separate** file, or do you want to add a thumbnail image to an existing image file by modifying and then saving that file? (The later is the way `CGImageSourceCreateThumbnailAtIndex` is intended to be used, I believe). – NSGod Jan 26 '16 at 22:57
  • I'd like to write the thumbnail to a separate file. Perhaps something like CGBitmapContextCreate and CGContextDrawImage would be more suitable? – henster Jan 31 '16 at 13:31

3 Answers3

7

First you need to get a bitmap representation of the image, get a JPEG representation of that image (or whatever format you want), and then write the binary data to a file:

if let bits = scaledImage?.representations.first as? NSBitmapImageRep {
    let data = bits.representationUsingType(.NSJPEGFileType, properties: [:])
    data?.writeToFile("/path/myImage.jpg", atomically: false)
}

NSBitmapImageFileType gives you a choice of representations:

public enum NSBitmapImageFileType : UInt {

    case NSTIFFFileType
    case NSBMPFileType
    case NSGIFFileType
    case NSJPEGFileType
    case NSPNGFileType
    case NSJPEG2000FileType
}
JAL
  • 41,701
  • 23
  • 172
  • 300
  • Thanks for un-deleting this! My scaledImage doesn't seem to have any representations. The execution doesn't enter that branch. Do I have to create a representation first? – henster Jan 26 '16 at 20:50
  • @henster I can't reproduce that issue. I've initialized an `ImageThumbnail` object with a jpg image and see that it has at least one representation: `[>]`. What kind of image are you passing in to your class? – JAL Jan 26 '16 at 20:56
  • My issue in Swift 3 / Xcode 8 is that I see indeed a `NSCGImageSnapshotRep` but it doesn't cast as `NSBitmapImageRep` – mz2 Sep 20 '16 at 23:34
2
extension NSImage {
    func imagePNGRepresentation() -> NSData? {
        if let imageTiffData = self.tiffRepresentation, let imageRep = NSBitmapImageRep(data: imageTiffData) {
            // let imageProps = [NSImageCompressionFactor: 0.9] // Tiff/Jpeg
            // let imageProps = [NSImageInterlaced: NSNumber(value: true)] // PNG
            let imageProps: [String: Any] = [:]
            let imageData = imageRep.representation(using: NSBitmapImageFileType.PNG, properties: imageProps) as NSData?
            return imageData
        }
        return nil
    }
}

if let thumbImageData = thumbimage.imagePNGRepresentation() {
    thumbImageData.write(toFile: "/path/to/thumb.png", atomically: false)
}
Atika
  • 1,560
  • 18
  • 18
1

It appears that either in Swift 3 NSCGImageSnapshotRep no longer casts as NSBitmapImageRep, or this works for only some images (I can't tell which is true as I only encountered images that do not cast to NSBitmapImageRep in my case when loading some JPEG data into an NSImage, then trying to encode a JPEG representation again).

This slight alteration to another answer works for me in Swift 3 + macOS Sierra SDK in Xcode 8:

let cgImage = img.cgImage(forProposedRect: nil, context: nil, hints: nil)!
let bitmapRep = NSBitmapImageRep(cgImage: cgImage)
let jpegData = bitmapRep.representation(using: NSBitmapImageFileType.JPEG, properties: [:])!

(Please excuse the forced unwraps, just there for brevity – definitely optionals to check in production code given current SDK has introduced optionality here.)

Community
  • 1
  • 1
mz2
  • 4,672
  • 1
  • 27
  • 47