NSImageのサイズを縮小してPNGファイルを出力する

# 実装

- 検索するといくつも実装例が見つかりますが、最終的に下記を参考にしました。
    - [NSImage をリサイズする。](https://ez-net.jp/article/27/DY0R3-1O/SJQQ_0rAOV6B/)
    - Swiftのバージョンが古かったため、いくつかリファクタリングしています。

```swift
extension NSImage {
    func pixelsSize() -> NSSize? {
        guard let pixelsWide = self.tiffRepresentation?.bitmap?.pixelsWide,
            let pixelsHigh = self.tiffRepresentation?.bitmap?.pixelsHigh else {
                return nil
        }
        
        return NSSize(width: pixelsWide, height: pixelsHigh)
    }
    
    func resizedImageScaseDown(to ratio: Double) -> NSImage? {
    
        if !(0.0 < ratio && ratio < 1.0) {
            return nil
        }
        
        if !self.isValid {
            return nil
        }
        
        guard let sourceBitmapRep = self.tiffRepresentation?.bitmap else {
            return nil
        }
        
        // 画像のサイズと実際の描画サイズ（ピクセル単位）の2つを使用する
        let newImageSize = NSSize(width: Double(self.size.width) * ratio,
                                  height: Double(self.size.height) * ratio)
        
        guard let pixelsSize = pixelsSize() else {
            return nil
        }
        let newPixelsSize = NSSize(width: Double(pixelsSize.width) * ratio,
                                   height: Double(pixelsSize.height) * ratio)
        
        let bitmapRep = NSBitmapImageRep(bitmapDataPlanes: nil,
                                         pixelsWide: Int(newPixelsSize.width),
                                         pixelsHigh: Int(newPixelsSize.height),
                                         bitsPerSample: sourceBitmapRep.bitsPerSample,
                                         samplesPerPixel: sourceBitmapRep.samplesPerPixel,
                                         hasAlpha: sourceBitmapRep.hasAlpha,
                                         isPlanar: sourceBitmapRep.isPlanar,
                                         colorSpaceName: sourceBitmapRep.colorSpaceName,
                                         bytesPerRow: sourceBitmapRep.bytesPerRow,
                                         bitsPerPixel: sourceBitmapRep.bitsPerPixel)!
        
        bitmapRep.size = newImageSize
        NSGraphicsContext.saveGraphicsState()
        NSGraphicsContext.current = NSGraphicsContext.init(bitmapImageRep: bitmapRep)
        self.draw(in: NSRect(x: 0, y: 0, width: newImageSize.width, height: newImageSize.height),
                  from: NSRect.zero,
                  operation: NSCompositingOperation.copy,
                  fraction: 1.0)
        NSGraphicsContext.restoreGraphicsState()
        
        let newImage = NSImage(size: newImageSize)
        newImage.addRepresentation(bitmapRep)
        
        return newImage
    }
}
```

- 下記3つの画像のサイズに関するパラメータがあります。
- 解像度に影響するので、比率を合わせるように実装しています。
    - [NSImageのサイズを取得する方法 \- 開発メモ](http://seeku.hateblo.jp/entry/2014/04/02/200246)
    - [Get the correct image width and height of an NSImage](https://stackoverflow.com/questions/11876963/get-the-correct-image-width-and-height-of-an-nsimage)
    - [NSImage備忘録](https://san-diego.hatenablog.com/entry/20071012/1192196358)

| パラメータ          | 例  |
|-----------------------|---|
| NSImage.size          | (1680, 1050)  |
| NSBitmapImageRep.size | (1680, 1050)  |
| NSBitmapImageRep.pixels | (3360, 2100)  |

- また実装を便利にするため、下記のextensionを定義しています。
    - [NSImage to NSData as PNG Swift](https://stackoverflow.com/questions/29262624/nsimage-to-nsdata-as-png-swift)

```swift
extension NSBitmapImageRep {
    func imageWithFormat(for format: NSBitmapImageRep.FileType) -> Data? {
        return representation(using: format, properties: [:])
    }
}

extension Data {
    var bitmap: NSBitmapImageRep? { NSBitmapImageRep(data: self) }
}

extension NSImage {
    var png : Data? { tiffRepresentation?.bitmap?.imageWithFormat(for: .png)  }
    var jpeg: Data? { tiffRepresentation?.bitmap?.imageWithFormat(for: .jpeg) }
    var gif : Data? { tiffRepresentation?.bitmap?.imageWithFormat(for: .gif)  }
}
```

- 呼び出し例は下記のとおりです。

```swift
guard let image = loadImage() else {
    return
}

// 50%のサイズにする
guard let resizedImage = image.resizedImageScaseDown(to: 0.5) else {
    return
}

let outputURL1 = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first!.appendingPathComponent("/images/created_image_1.png")
let outputURL2 = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first!.appendingPathComponent("/images/created_image_2.png")

if let imageData = image.png,
    let resizedImageData = resizedImage.png {
    do {
        print(imageData.count)
        print(resizedImageData.count)
        
        try imageData.write(to: outputURL1)
        try resizedImageData.write(to: outputURL2)
        
        print("PNG image saved")
    } catch {
        print(error)
    }
}
```

```swift
// MARK: - Helper Methods

func loadImage() -> NSImage? {
    let desktopPath = (NSSearchPathForDirectoriesInDomains(.desktopDirectory, .userDomainMask, true) as [String]).first
    let filePath = desktopPath?.appending("/images/desktop 1.png")
    if let filePath = filePath  {
        if let image = NSImage(contentsOfFile: filePath) {
            return image
        }
    }
    
    return nil
}
```

- 出力結果は以下の通りです。
- 縦横のサイズがそれぞれ50%になっていることが確認できます。

![-w628](https://i.imgur.com/JpCtSdx.jpg)


# 解決していない問題
- ご存じの方、お教えいただけると助かります…。

## NSImageを経由するとサイズが異なる
- 読み込み元のファイルをそのまま出力しているつもりですが、ファイルサイズに微妙な差異が見られます。
- [representation\(using:properties:\)](https://developer.apple.com/documentation/appkit/nsbitmapimagerep/1395458-representation)のpropertiesで何かしら設定しないといけない？

<img width="512" alt="image" src="https://i.imgur.com/C3KPaEo.jpg">

## ColorSyncプロファイルが異なる 

<img width="512" alt="image" src="https://i.imgur.com/4moPbht.png">