クリップボードから画像を取得する

# NSPasteboardの基本
 - [【Apple】URL Reading Options](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/PasteboardGuide106/Articles/pbReading.html#//apple_ref/doc/uid/TP40008123-SW1)
     - `NSPasteboard`に関する全体の話。ベースとしてここを参照しています。
     - ※ただしサンプルコードはObjective-Cです。

# クリップボードからファイルのURLを取得
- 画像のタイプ（png/jpeg/tiff/gif/...）の判別には、UTIを使うと便利そうです。
    - [【Apple】Uniform Type Identifier Concepts](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/understanding_utis/understand_utis_conc/understand_utis_conc.html)
    - [UTIを調べるCLIツールをSwiftで作ったメモ書き](https://qiita.com/aryzae/items/85ce2b45a9ab909d3782)
    - [Uniform Type Identifierの概念](http://potting.syuriken.jp/potting_conv/understanding_utis_J/chapter2.html#chapter2-1)
- `URL`から`UTI`を取得する関数は以下を参考にしました。
    - [fileURLからUTI（Uniform type identifier）を取得する \- Qiita](https://qiita.com/masakihori/items/8d4af538b040c65a8871)

```swift
// ファイルパスがコピーされている場合を考える
let pasteboard = NSPasteboard.general

// NSImageの扱うUTIを指すfileURLのみ抽出する設定
// 具体的にはprint(NSImage.imageTypes)で確認できる
let classes = [NSURL.self]
let options: [NSPasteboard.ReadingOptionKey : Any] = [.urlReadingFileURLsOnly : true,
                                                      .urlReadingContentsConformToTypes : NSImage.imageTypes]

let canRead = pasteboard.canReadObject(forClasses: classes, options: options)
if canRead {
    let objectsToPaste = pasteboard.readObjects(forClasses: classes,
                                                options: options) ?? []
    
    if objectsToPaste.count > 0 {
        let fileURL = objectsToPaste[0] as! URL
        if let uti = uti(url: fileURL) {
            // 画像フォーマットを取得できる
            print(uti)  // e.g. UTI: Optional("public.png")
        }
        imageView.image = NSImage(contentsOf: fileURL)  // 出力して確認
    }
} else {
    print("クリップボードにファイルパスはありませんでした")
}

func uti(url: URL) -> String? {
    guard let r = try? url.resourceValues(forKeys: [.typeIdentifierKey]) else {
        return nil
    }
    return r.typeIdentifier
}
```

# クリップボードから画像を取得
## NSImageとして取得

- `NSImage`のコンストラクタに`NSPasteboard`のインスタンスを渡すことができます。

```swift
// 画像がクリップボードにコピーされている場合
let pasteboard = NSPasteboard.general

if let image = NSImage(pasteboard: pasteboard) {
  imageView.image = image  // 出力して確認
} else {
  print("クリップボードに画像はありませんでした")
}
```

- もし複数のクリップボードのアイテムを取得したい場合は、下記の通り`objectsToPaste`を経由します。
    -  前述の`readObjects`の`forClasses`を`NSImage`と変えています。

```swift
// 画像がクリップボードにコピーされている場合
let pasteboard = NSPasteboard.general

let classes = [NSImage.self]
let options = [NSPasteboard.ReadingOptionKey : Any]()

let canRead = pasteboard.canReadObject(forClasses: classes, options: options)
if canRead {
    let objectsToPaste = pasteboard.readObjects(forClasses: classes,
                                                options: options) ?? []

    if objectsToPaste.count > 0 {
        let image = objectsToPaste[0] as! NSImage
        imageView.image = image  // 出力して確認
    }
} else {
    print("クリップボードに画像はありませんでした")
}
```

- ただ欠点として、`NSImage`としてデータを保持してしまうと、元の画像のフォーマットを取得できません。
    - [How can i find the file type after loading NSImage withContentsOfFile](https://stackoverflow.com/questions/3590003/how-can-i-find-the-file-type-after-loading-nsimage-withcontentsoffile)
- つまり`NSImage`として読み込む前の段階で、画像のフォーマット情報を得ておく必要があります。

## Dataを経由して取得(画像のフォーマット情報も取得できる)
- `NSPasteboard`の機能をうまく利用すると、画像のフォーマット情報を取得することができます。
- [toolinbox/iPicUploader](https://github.com/toolinbox/iPicUploader)
    - ここのコードを参考にしました。サンプルプロジェクトから`Jump to Definition`を繰り返せば色々見れるはずです。
    - 今回私のコードはなるべく短くしています。きちんと書く場合は、クラスや関数の分け方を上記ソースから参考にすると良いと思います。

```swift
// クリップボードの内容を一旦全て取得する
guard let pasteboardItems = NSPasteboard.general.pasteboardItems else {
    return
}

var myImages = [MyImageInfo]()

for pasteboardItem in pasteboardItems {
    for type in pasteboardItem.types {
        if let data = pasteboardItem.data(forType: type) {
            if let image = NSImage(data: data) {
                myImages.append(MyImageInfo(image: image, uti: type))
            }
        }
    }
}

struct MyImageInfo {
    let image: NSImage
    let uti: NSPasteboard.PasteboardType
}
```

- [NSPasteboard\.PasteboardType](https://developer.apple.com/documentation/appkit/nspasteboard/pasteboardtype)
    - しかし足りないものもあって、例えばjpg(`public.jpeg`)は定数で用意されていないから自分で用意しないといけないかもです。

```swift
print(NSPasteboard.PasteboardType.png.rawValue)  // public.png
```

- StackOverflow等を検索すると、画像のバイナリの先頭数バイトを見て判断、というアイディアしか見つかりませんでした。
- （ゴリゴリ系はあまり実装したくないですね）
    - [How to identify a NSData's image format?](https://stackoverflow.com/questions/7281287/how-to-identify-a-nsdatas-image-format)
        - PNGとJPGを最初の4byteで見分ける。
    - [Finding image type from NSData or UIImage](https://stackoverflow.com/questions/4147311/finding-image-type-from-nsdata-or-uiimage)
- 実際にHex Fiendでバイナリを確認してみると以下の通り。

<img width="256" alt="image" src="https://i.imgur.com/GSkH2W3.jpg">
<img width="256" alt="image" src="https://i.imgur.com/cz6bdkK.jpg">
<img width="256" alt="image" src="https://i.imgur.com/yYI74Yu.jpg">

# デバッグ時の小ネタ
## スクリーンショットの拡張子を変更する 
- デフォルトは`png`ですが下記の通り変更可能です。

```sh
defaults write com.apple.screencapture type jpeg
```

## 現在のクリップボードを確認する
- Finderのメニューから`クリップボードを表示`から確認できます。
- 拡張子も確認できるので、デバッグ時のお供に。

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

## NSImageのUTIを確認する

```swift
let utis = NSImage.imageTypes
for uti in utis {
    print(uti)
}
```

```sh
com.adobe.pdf
com.apple.pict
com.adobe.encapsulated-postscript
public.jpeg
public.png
com.compuserve.gif
com.canon.tif-raw-image
com.adobe.raw-image
com.dxo.raw-image
com.canon.cr2-raw-image
com.canon.cr3-raw-image
com.leafamerica.raw-image
com.hasselblad.fff-raw-image
com.hasselblad.3fr-raw-image
com.nikon.raw-image
com.nikon.nrw-raw-image
com.pentax.raw-image
com.samsung.raw-image
com.sony.raw-image
com.sony.sr2-raw-image
com.sony.arw-raw-image
com.epson.raw-image
com.kodak.raw-image
public.tiff
public.jpeg-2000
com.apple.atx
org.khronos.astc
org.khronos.ktx
public.avci
public.heic
public.heics
public.heif
com.canon.crw-raw-image
com.fuji.raw-image
com.panasonic.raw-image
com.panasonic.rw2-raw-image
com.leica.raw-image
com.leica.rwl-raw-image
com.konicaminolta.raw-image
com.olympus.sr-raw-image
com.olympus.or-raw-image
com.olympus.raw-image
com.phaseone.raw-image
com.microsoft.ico
com.microsoft.bmp
com.apple.icns
com.adobe.photoshop-image
com.microsoft.cur
com.truevision.tga-image
com.ilm.openexr-image
com.sgi.sgi-image
public.radiance
public.pbm
public.mpo-image
public.pvr
com.microsoft.dds
```