ImageMagick の Canvas / Page について

# はじめに

PNG や GIF 画像で identify を実行すると、よく分からない座標が表示される件です。JPEG では問題になりません。
なお、このエントリは実質 +repage の紹介記事となります。

```shell:
% identify crop.png
crop.png PNG 200x200 640x480+100+100 8-bit sRGB 183c 4344B 0.000u 0:00.000
```

ImageMagick はこの 640x480 を (Layer) Canvas サイズ、200x200 は (Image) Page サイズと呼びます。
(canvas は書き込み作業途中の image データを表す事もあって多義的なので注意)

| Canvas / Page のイメージ図 |
|---|
| ![スライド2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/ff93f072-48d2-56b4-70a8-b5051b361307.png) |

ImageMagick で crop を繰り返すと意図しない動きをしたり、GIF アニメーションの resize でたまに画像が乱れたりといった罠があります。一応、不具合ではありません。ちょっと直感的な動きじゃないだけで整合性は取れた挙動です。

# サンプル画像

ImageMagick 埋め込み画像の logo: をサンプルにします。

```shell:
% magick convert logo: logo.png
% magick identify logo.png
logo.png PNG 640x480 640x480+0+0 8-bit sRGB 256c 27434B 0.000u 0:00.000
```

| logo.png |
|---|
|![logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/44252aa0-02e8-4826-236e-751f8ea64c79.png)|

ImageMagick 7 系の magick コマンドでサンプル画像を処理していきますが、magick コマンドを省略してそれに続く identify や convert を使えば ImageMagick 6 系でも動作します。

# crop

例として、logo 画像をオフセット付きで crop してみます。

```shell:
% magick convert logo.png -crop 200x200+100+100 crop.png
```

| logo.png | crop.png |
|---|---|
|![logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/44252aa0-02e8-4826-236e-751f8ea64c79.png)| ![crop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/6571c9d6-1350-9403-8080-cc65d398987f.png) |

![crop-process.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/3b78cf1c-c5b7-386e-ebad-ba40b1672e24.png)

上記のような crop 操作で、PNG や GIF 画像に出力すると、元の画像の大きさと、そのどの部分を Crop したかの情報が残ります。

```shell:
% magick identify crop.png
crop.png PNG 200x200 640x480+100+100 8-bit sRGB 183c 4344B 0.000u 0:00.000
```

ImageMagick 的に、元の画像の大きさは Canvas、 crop 済みの実画像は Page と呼びます。
GIF だと Screen と Image、 に対応します。

# -flatten と +rapage

-flatten を使うと、元の画像配置を復元できます。

```shell:
% magick convert crop.png -flatten crop-flatten.png
% magick identify crop-flatten.png
crop-flatten.png PNG 640x480 640x480+0+0 8-bit sRGB 183c 5717B 0.000u 0:00.000
```

| crop-flatten.png |
|---|
|![crop-flatten.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/10bd099c-c304-7147-b4f5-ecb485aaa6a3.png) |

なお、実画像 (real image) は crop 済みなので、消えた部分は戻せません。
実用上、この余計な情報を無くしたい事が多いので、その為に +repage がお勧めです。

```shell:
% magick convert crop.png +repage crop+repage.png
% magick identify crop+repage.png
crop+repage.png PNG 200x200 200x200+0+0 8-bit sRGB 183c 4316B 0.000u 0:00.000
```

| crop+repage.png|
|---|
| ![crop+repage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/6b9ae99c-6b72-5df8-9761-9664c6ca9e4d.png) |

# crop と page の罠

| crop.png | crop+repage.png|
|---|---|
| ![crop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/6571c9d6-1350-9403-8080-cc65d398987f.png)| ![crop+repage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/6b9ae99c-6b72-5df8-9761-9664c6ca9e4d.png) |

単に -crop した画像と +repage 済みの画像とは、画像ビューア上で区別できない事が多いです。

```shell:
% magick identify crop.png  crop+repage.png
crop.png PNG 200x200 640x480+100+100 8-bit sRGB 183c 4344B 0.000u 0:00.000
crop+repage.png PNG 200x200 200x200+0+0 8-bit sRGB 183c 4316B 0.000u 0:00.000
```

この　(+repage 無しの) -crop しただけの crop.png 画像を更に -crop すると、以下のような罠にハマります。

```shell:
% magick convert crop.png -crop 50x50+0+0 crop-crop.png
convert: geometry does not contain image ("50x50+0+0") `crop.png' @ warning/transform.c/CropImage/596.
```

これは画像が存在しない場所を選択したからです。
Canvas 内の 100x100 から右下に画像が展開されているので、50x50 だとそこまで届かない為です。

![スライド3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/2d077dba-df29-4570-bb43-c090b9d61746.png)

厄介な事に画像は生成されます。非表示枠の 1x1 最小サイズの画像です。

|crop-crop.png|
|---|
| ![crop-crop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/46ee697b-f032-cbae-896e-ada496bc6df0.png) |


```shell:
% magick identify crop-crop.png
crop-crop.png PNG 1x1 640x480-1-1 8-bit sRGB 352B 0.000u 0:00.000
```


+repage を使うか、+repage 済みの画像であれば大丈夫です。

```shell:
% magick convert crop+repage.png -crop 50x50+0+0 crop-+repage-crop.png
```

| crop-+repage-crop.png |
|---|
| ![crop-+repage-crop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/87135b86-edaf-23e2-0d12-2e15bb035c02.png) |

```shell:
% magick identify crop-+repage-crop.png
% crop-+repage-crop.png PNG 50x50 200x200+0+0 8-bit sRGB 55c 919B 0.000u 0:00.000
```

更なる crop 処理で混乱しないように、こちらも +repage する事をお勧めします。


```shell:
% magick convert crop-+repage-crop.png +repage crop-+repage-crop+repage.png
% crop-+repage-crop+repage.png PNG 50x50 50x50+0+0 8-bit sRGB 55c 919B 0.000u 0:00.000
```

取り込んだ PNG が Screen と Page が異なるか画像ビューアの見た目で判断出来ないので、以下のように +repage を実行すると安全です。

- -crop を実行したらその後に +repage を実行する。(クロップした場所をあえて保存したいケースは別)
- ImageMagick で crop されてないと確信出来ない PNG 読み込み時に +repage を実行する

公式にもこう書かれています。

- https://www.imagemagick.org/Usage/crop/#crop_repage

> Always use "+repage" after any 'crop' like operation.Unless you actually need to preserve that info.
> (訳) "crop" などの操作の後には、常に "+repage" を使用してください。実際にその情報を保存する必要がある場合を除きます。

# oFFs, vpAg, caNv チャンク

本来は、JPEG と同じく PNG もこの罠と無縁のはずですが、MNG (APNG以前のPNGアニメーション仕様) で oFFs が必要になり、 2005年に ImageMagick は PNG 公式仕様の oFFs, vpAg チャンクに対応して Canvas 座標の保存を行うようになりました。その後、oFFs を使う他のアプリケーション(GIMP)と折り合いがつかなかった為、2017年に oFFs, vpAg に保存するのは辞めて、独自チャンク caNv の利用に踏み切ったようです。

- PHP の ImageMagick で作成した PNG 画像にオフセットが設定されてしまった場合の対応方法
   - https://logicalerror.seesaa.net/article/419467791.html

対応の具体的な時期は以下のような流れです。

- ImageMagick-6.2.4-7 〜 6.2.5-5 (2005年9月〜11月頃) のどこかで oFFs, vpAg 対応。
- ImageMagick-6.9.7-6 (2017年1月28日) で caNv 対応。
- ImageMagick-6.9.9-4 (2017年7月29日) で oFFs, vpAg 書き込みを廃止

-  ImageMagick-6.9.7-6差分
   - https://yoya.hatenadiary.jp/entry/20170128/im
- ImageMagick-6.9.9-4差分
   - https://yoya.hatenadiary.jp/entry/20170729/im

# GIF アニメーションの罠

GIF にも Canvas / Page の概念があり、同様の罠があります。特に GIF アニメーションの変換で躓きがちです。

- https://twitter.com/yoya/status/1193156106706534401

GIF アニメーションは最適化の一環として2コマ目以降を差分フレームとして持つ事があるので、その場合、内部的に Canvas より小さな Page として差分画像を持つ為、そのまま -resize 等の処理を行うと画像が崩れます。

以下のように -coalesce で GIF アニメーションの最適化を解き、処理を行い、その後 -layers optimize で最適化し直す事で対処可能です。

```shell:
% magick convert anim.gif -coalesce -resize 50% -layers optimize output.gif
```

# 参考 URL

- https://imagemagick.org/script/command-line-options.php#crop
- [ImageMagick の画像 Crop](https://qiita.com/yoya/items/62879e6e03d5a70eed09)
- [ImageMagick で GIF 処理](https://qiita.com/yoya/items/6bacfe84cd49237aea27)