【Swift】画像 生 Data は UIImage や NSImage で変更されることを知る

例えば、WEB上の下のような

「imgur で削除された」ことを表す PNG 画像 のデータは、

以下のようにして Data を取得できますよね。


let data = try! Data(
  contentsOf: URL(string: "https://i.imgur.com/removed.png")!
)

それを一度、UIKit UIImage に適用した、としても、

それは再び同じ Data 型 として同じものが抽出できるイメージでいました。


👉 UIImage | Apple Developer Documentation
👉 Data | Apple Developer Documentation

しかし、同じものが抽出できません。

適用前と後で抽出したものが違うようです。


let data = try! Data(
  contentsOf: URL(string: "https://i.imgur.com/removed.png")!
)
let uiImage = UIImage(data: data)!
let data2 = uiImage.pngData()!

print(data == data2) // false
print(data, data2)   // 503 bytes 1134 bytes

なんでや、何が違うんや。

 

🤔 Base64Encoding

テキスト化して見てみます。

違いの量は分かります。


let data = try! Data(
  contentsOf: URL(string: "https://i.imgur.com/removed.png")!
)
let uiImage = UIImage(data: data)!
let data2 = uiImage.pngData()!

print(
  data,
  data.base64EncodedString()
)
print(
  data2,
  data2.base64EncodedString()
)

// 503 bytes 
// iVBORw0KGgoAAAANSUhEUgAAAKEAAABRAQMAAACADVTsAAAABlBMVEUiIiL///9ehyAxAAABrElEQVR4Xu3QL2/bQBgG8NdRlrnMNqxu1eVAahCQVAEF03STbsuBSFVZYEBBoJ2RjZ0Hljuy6IZaUlUlpfsKRUmZP4JTNJixkEm7nJu/Mxlot0l7JJOfXj06P/D3xvkBQH/lqoEC7WVvzqM0k/f4+Gat2nt7ppqeCjCbiJX6HmN7vnca4LLc0BljH/yZ0ZejDQXGlA9GmYSthoumVw1wZ6PByxjrpxmeZq0hbMcDXPCHGVB4hHCAkgUKrrNSulawelPRCH37mu4fR1EdZYPwnTA6UZoQfteoMSmPCFVcgYmUmmCuPMKkIAtNFjqS+hWyOo+MzmVsb12NS1aFazThe1Ztr2qYBklWvcPKCKG+TA/MGwjqDcI4n1Pko+1E5KM9TRz75fGB0qWv1Vlq/Bo9Gzqo3oqu7g991G1bVQmp8IQcdeRtEGpyxoVVB5eNLob0qS6xpaJc5+J7Wx+wkwct5SoSn2vCOORKrHZk0lC69tAbm4a2g0grEuknvd9tb61XhqK8hz+d/xG/cft5fD0dvxA7qsLrj+EXWqBugRbeHl6qcbCr4Ba+7Tn88/kJk4CIztd1IrIAAAAASUVORK5CYII=

// 1134 bytes 
// iVBORw0KGgoAAAANSUhEUgAAAKEAAABRCAMAAACNHTadAAAABlBMVEUiIiL///9ehyAxAAAAhGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAEgAAAABAAAASAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAoaADAAQAAAABAAAAUQAAAACmJMYkAAAACXBIWXMAAAsTAAALEwEAmpwYAAADfklEQVRoBe1Xi3bqMAyD///pa1mS4zTAxh6s200Oi21ZftQtYb1c9toT2BPYE3jlBK5cl+tSdEUWyquAbOVE/azXrQ6vbDJmSgrE9QKLiBxhJME80kAqTSWMZIaMWWu/D8ng7CR7Ql9Y2BMNhR+JuZWkiV2i4pVC6QB/ZKEG+2nyFtIaLT54arK71ce4siQJfVrMHca9SaDXpZ6O6qbxChuXhSbISGeRn24uA7KhVkVZOhK6zI6SeG+Gh4CP9eYSIXtl6AsSoJ7V+Tkk0fFOw3gF2JlpP7JlQ5U67yXSGCmpYR2+y+pQ32Xd2mwj+otYhNuZ8Cu2LDoKHczh+BltfeZP1uDPjGVXfdkE4nm7/8jd9yz93aA2qKlL5FvAw9iHzjnzY+oNLw+tPIVxbvkIA9NHY0psCYKUzhqpPXawiqODRgfzDRioMjIhbFEBeA1e8pvJaGdBskQoscOMJRUWVIqS5DUU6uDMI2E4CbW3WGcV1D1M2RCrvdbSIYuYKgkRq6xQpBtO/9jSG8M1awQABKrRDwcywpHOUoUcokQzPaSjlN2eUQUl+ypGgNKbMDMgfg4c+BtdnBY1/J0nNEWLaapT3IgXa4iWOcBuwZwQOBsn9QU5Rs0x8jIz94R0O/Hww/YN0HeBXvvcE6lIMtNHLlXLjOIlG3pGufvWg0qScntX2tvOGX2COgd+znpnWc3tc7V29J7Ar53AW1+Ut/yPLhyxI96a5aPIZ3yfzTfirVne70Lncf8BqGPT/zj5FNYRS/9w+phHER/kTpshk1G/wE4TlKp4o09cQvyZApUIuEAPyPAPpzRGIrxIzMXdiW2FtKqAjFy2RoJPYaI1J9RY8svRzIaQ6fpr4JLGsQ6cpbzKw6vyQGm5VMYFz/laJFAs1ibRZoJp2E1mWSN27syWiBBYkk2ECmsgtiSbOCSgJ/ZGWSw44b+/WjRII4OsFVFE5iV9eg5ZCg4Qi0WmsmXAhCSPocvuu0EHshKBndaKTLXRSYuCiiWUlWmMbEDrKEiyfRn7DZvb+obUX5CSz/0XJNop9gT2BPYE9gT2BPYE9gT2BPYE/sYEzv0P/O+Ycb5k4V1Mil7c/MKnN0F7SXvtlWVj+d5ZvUUD1BOWHrz6nK1D93neDj3bM3SIhw+3Ervvfj2BP3aX+zOlrjp0Nj1HeLamdj97AnsCewL/+QT+AeEGA83c9VTUAAAAAElFTkSuQmCC

全然違いますね。

「PNGフォーマット」を表す先頭部分は同じなことも分かります。

さらに、

Xcode ウインドウからデスクトップ上や好きな位置に、

コピペで画像ファイルを簡単に作成できるので便利です。


# コピーして
❯ pbpaste | base64 --decode > 503.png

# さらにコピーして
❯ pbpaste | base64 --decode > 1134.png

❯ ls -al
-rw-r--r--@  1 chanzmao  staff    503 Oct 11 10:50 503.png
-rw-r--r--@  1 chanzmao  staff   1134 Oct 11 10:51 1134.png

これで2つの処理のタイミングでの Data を利用した画像ファイルが作成できました。

また、xxd でも16進数を使って似たようなことができそうです。


❯ xxd 503.png | head
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
00000010: 0000 00a1 0000 0051 0103 0000 0080 0d54  .......Q.......T
00000020: ec00 0000 0650 4c54 4522 2222 ffff ff5e  .....PLTE"""...^
00000030: 8720 3100 0001 ac49 4441 5478 5eed d02f  . 1....IDATx^../
00000040: 6fdb 4018 06f0 d751 96b9 cc36 ac6e d5e5  o.@....Q...6.n..
00000050: 406a 1090 5401 05d3 7493 6ecb 8148 5559  @j..T...t.n..HUY
00000060: 6040 41a0 9d91 8d9d 0796 3bb2 e886 5a52  `@A.......;...ZR
00000070: 5525 a5fb 0a45 4999 3f82 5334 98b1 9049  U%...EI.?.S4...I
00000080: bb9c 9bbf 3319 68b7 497b 2493 9f5e 3d3a  ....3.h.I{$..^=:
00000090: 3ff0 f7c6 f901 407f e5aa 8102 ed65 6fce  ?.....@......eo.


❯ xxd original.png > original.hex
❯ xxd -r original.hex > revert.png

👉️ xxdコマンドで画像データを16進数変換してiOSライブラリ内に画像データを内包する

 

🤔 file コマンド

先に作成した2つのファイルを file コマンドで見てみます。


❯ file 503.png 1134.png
503.png:  PNG image data, 161 x 81, 1-bit colormap, non-interlaced
1134.png: PNG image data, 161 x 81, 8-bit colormap, non-interlaced

少し違うことが分かります。


1-bit colormap
8-bit colormap

PNGファイルのプロファイル的な何かでしょうか。

 

🤔 sips コマンド

macOS 標準のコマンドらしいです。

2つの実行結果を diff に入力します。


❯ diff --side-by-side <(sips -g all 503.png) <(sips -g all 1134.png)
~/Desktop/503.png            | ~/Desktop/1134.png
  pixelWidth: 161                pixelWidth: 161
  pixelHeight: 81                pixelHeight: 81
  typeIdentifier: public.png     typeIdentifier: public.png
  format: png                    format: png
  formatOptions: default         formatOptions: default
  dpiWidth: 72.000               dpiWidth: 72.000
  dpiHeight: 72.000              dpiHeight: 72.000
  samplesPerPixel: 3             samplesPerPixel: 3
  bitsPerSample: 1           |   bitsPerSample: 8
  hasAlpha: no                   hasAlpha: no
  space: RGB                     space: RGB

以下の部分が違います。


bitsPerSample: 1	           |	   bitsPerSample: 8

( 現在、記事更新中 ... )


SwiftUI・UIKit・AppKitでの画像処理の煩わしさを解消するためのヒント

図を作ってみました。

Appleの異なる画像処理フレームワーク(SwiftUI、UIKit、AppKit)間で画像データをやり取りする際のフロー図を示しています。

この図が示しているように、iOS や macOS で画像を扱う場合、複数の異なるフレームワーク(SwiftUI、UIKit、AppKit)間での画像データのやり取りが必要となることがよくあります。

しかし、それぞれのフレームワークは異なる画像型(UIKit では UIImage、AppKit では NSImage など)を使っているため、これらを統一して操作するのは煩雑です。

処理の煩わしさは以下の点にあります:

 

🧑🏻‍💻 異なる画像型の存在

UIKit、AppKit、Core Graphics などの各フレームワークはそれぞれ独自の画像データ型(UIImageNSImageCGImage など)を使います。

異なる画像型を相互に変換する必要があり、そのための変換処理が増えてしまいます。

 

🧑🏻‍💻 変換処理の多さ

SwiftUI の Image コンポーネントに画像を渡すには、UIKit の UIImage や AppKit の NSImage などの形式に適切に変換する必要があります。

例えば、UIImage を SwiftUI に渡すために Image(uiImage:) を使う必要があり、AppKit の NSImage なら Image(nsImage:) を使います。このように、変換手順が異なり、統一感に欠けます。

 

🧑🏻‍💻 データ型の制約と互換性

画像データをファイル形式(例えば、PNG や JPEG)に変換したり、ネットワークで送信する場合、Data 型を使う必要があります。

そのため、 UIImage.pngData()NSBitmapImageRep.representation(using:properties:) のような変換処理を追加で行わなければなりません。

こうした一連の変換処理は単純な画像表示のために多くの余分なコードを必要とし、開発者にとって煩わしいと感じる要因です。

 

🧑🏻‍💻 フレームワーク間の相違点

UIKit は iOS 向け、AppKitは macOS 向けのフレームワークなので、同じアプリをクロスプラットフォームで開発する際に、これらのフレームワーク間の違いに対応しなければならず、異なる API や処理方法に習熟する必要があります。

総じて、このような画像の処理は、単純に画像を表示・変換・送信したいだけであっても多くの手順が必要となるため、効率的でないことが多いです。

これが処理の煩わしさにつながっています。

 

🧑🏻‍💻 SwiftUI

Image(uiImage:) または Image(nsImage:) を使って、それぞれ UIKit の UIImage または AppKit の NSImage を表示できます。

ImageRenderer(content:) を使って、 UIImage または NSImage を作成することが可能です。

 

🧑🏻‍💻 UIKit の UIImage

SwiftUI の Image や AppKit の NSImage との間で画像の相互変換が可能です。

UIImagepngData() でPNGフォーマットのデータに変換できますし、 UIImage(data:) でデータから画像を生成できます。

 

🧑🏻‍💻 AppKit の NSImage

AppKit では NSImage.cgImage() で Core Graphics の CGImage を取得でき、NSBitmapImageRep クラスを使って画像表現の変換が行われます。

NSBitmapImageRep を通じて、NSImage は、PNGなどの形式でエクスポート可能です。

 

🧑🏻‍💻 Core GraphicsのCGImage

UIKit の UIImage や AppKitの NSImage と互換性があり、それらを通じて画像を描画・変換する基盤を提供します。

 

🧑🏻‍💻 Foundation の Data

UIImageNSImage から変換したデータ(例:PNG)を保持するデータ型。

これにより、画像データをファイルとして保存したり、ネットワークを通じて送信したりできます。

 

🧑🏻‍💻 したかったこと

AsyncImage 内での画像データの比較です。



302 リダイレクト後の画像を確認しています。

 

🧑🏻‍💻 まとめ

SwiftUI、UIKit、AppKit での画像処理は、それぞれのフレームワークが異なる画像形式を扱っているため、煩雑に感じることが多いです。

しかし、正しい変換手法を使い、CGImage などを活用すれば互換性を確保できます。

さらに、最新の API を使いこなすことで、画像処理を効率的に行い、開発の手間を減らすことが可能です。

この記事で紹介したヒントを参考に、より快適な画像処理の開発を目指しましょう。


【SwiftUI】UIImage / NSImage の Image への抽象化

どちらが好きですか、以下2つのコード。

 

■ 1つ目


import SwiftUI

public extension Image {
    #if canImport(AppKit)
    init(image: NSImage) {
        self = Image(nsImage: image)
    }
    #endif

    #if canImport(UIKit)
    init(image: UIImage) {
        self = Image(uiImage: image)
    }
    #endif
}

特徴:
- 拡張機能 (extension) を使って、Imageに新しいinitイニシャライザを追加しています。
- プラットフォームごとに異なる型 (NSImageやUIImage) を直接引数に取ります。
- プラットフォーム依存の条件付きで、NSImage(macOS)またはUIImage(iOS)を使用してImageを初期化しています。

メリット:
- 各プラットフォームに対応したイニシャライザが個別に用意されており、Imageの初期化が直感的です。

デメリット
- プラットフォームごとにinitメソッドが別々に定義されているため、共通の型を扱うのが難しい。

 

■ 2つ目


#if canImport(AppKit)
import AppKit
public typealias PlatformImage = NSImage
#elseif canImport(UIKit)
import UIKit
public typealias PlatformImage = UIImage
#endif

import SwiftUI

extension Image {
    init(platformImage: PlatformImage) {
        #if canImport(UIKit)
        self = Image(uiImage: platformImage)
        #elseif canImport(AppKit)
        self = Image(nsImage: platformImage)
        #endif
    }
}

特徴:
- PlatformImageという型エイリアスを使って、macOSのNSImageとiOSのUIImageを抽象化しています。
- platformImageという共通の引数型を持つイニシャライザを追加しています。これにより、プラットフォームごとにImageを初期化しますが、型エイリアスによって共通化されています。

メリット:
- 抽象化されているため、呼び出し側のコードがプラットフォームに依存しません。つまり、共通のコードでPlatformImage型を使えば、iOSでもmacOSでも同じコードで動作します。
- 可読性が高く、メンテナンスが容易です。プラットフォームごとにメソッドを分ける必要がなく、1つのメソッドで対応しています。

デメリット:
- プラットフォームごとに異なる処理を追加する際に、多少複雑になる可能性があります。

 

■ まとめ

AIによると、

結論:どちらが良いか?

2つ目のコードの方が一般的に推奨されます。理由は、コードの抽象化によって、呼び出し側がプラットフォームに依存しない形でImageを扱うことができるためです。メンテナンス性が高く、同じコードベースで複数のプラットフォームをサポートしやすくなります。

ただし、プラットフォームごとに異なる処理が必要なケースでは、1つ目のコードの方が直感的に分かりやすい場合もあるので、状況に応じて選択が変わることがあります。

ということです。

私的には、どっちも勉強になります、としか。