ImageMagick の Geometry 仕様

# はじめに

引数で 640x480+200+100 のようにサイズや位置を指定する記法を ImageMagick では Image Geometry と呼びます。この記事では主にリサイズでの使い方を例にして紹介します。

```shell
% convert rose: rose.png
% identify rose.png
rose.png PNG 70x46 70x46+0+0 8-bit sRGB 6975B 0.000u 0:00.000
% convert rose.png -resize 100x100 rose-resized.png
% identify rose-resized.png
rose-resized.png PNG 100x66 100x66+0+0 8-bit sRGB 11687B 0.000u 0:00.000
```

|rose.png|rose-resized.png|
|---|---|
| ![rose.png](https://qiita-image-store.s3.amazonaws.com/0/1798/1ad2c326-5269-644e-3391-39f9024606a1.png) (70x46) | ![rose-resized.png](https://qiita-image-store.s3.amazonaws.com/0/1798/841f685a-a69f-7a4c-e3c6-1138b562bb54.png) (**100**x66)|

ImageMagick 初心者がはじめに引っかかるであろう -resize 100x100 を指定したのに出力画像が 100x100 サイズにならない現象を解決するヒントにもなるかもしれません。

Geometry と合わせて指定する事の多い Gravity は別エントリにしました。

- ImageMagick Gravity 指定
  - https://qiita.com/yoya/items/b6926f01b7cb90217e07

あと、ついでにリサイズの詳細はこちらです。

- ImageMagick リサイズ補間アルゴリズム
   -  https://qiita.com/yoya/items/b1590de289b623f18639

# Geometry の概要

以下の指定ができます。
- サイズ/面積/アスペクト比(←縦横比)
- オフセット
- リサイズ前後でアスペクト比が変わる場合に、どう対応するか
- その他、オプションによっては独自

画像をリサイズする際に、アスペクト比が変わると見た目が変わってしまうので、基本は**アスペクト維持**で動作するものだと考えると分かりやすいです。
アスペクト比無視での変換は例外的な使い方になります。

## general description

公式ドキュメントの表の順番で解説します。

- https://imagemagick.org/script/command-line-processing.php#geometry

| size | General description (実際の動作はオプションや設定によって変わり得ます) |
|:--|:--|
| scale% | 縦幅(height)横幅(width)まとめてパーセント指定. |
| scale-x%xscale-y% |縦幅(height)と横幅(width)を別々にパーセント指定。アスペクト比は維持しない |
| width | 横幅(width)を指定。縦幅(height)はアスペクト比を保つよう自動で決まる |
| xheight | 縦幅(height)を指定。横幅(width)はアスペクト比を保つよう自動で決まる |
| widthxheight | 縦幅(height)と横幅(width)の最大値を指定する。アスペクト比は保つ  |
| widthxheight^ | 縦幅(height)と横幅(width)の最小値を指定する。アスペクト比は保つ |
| widthxheight! | 縦幅(height)と横幅(width)ズバリのサイズになる。アスペクト比は維持しない |
| widthxheight> | 横幅(width) と縦幅(height) の引数よりも画像の大きい方を縮小する |
| widthxheight< | 横幅(width) と縦幅(height) の引数よりも画像の小さい方を拡大する |
| area@ | 指定した面積になるようリサイズする。幅(height)と横幅(width)の最小値を指定する。アスペクト比は保つ|
| x:y | x と y はアスペクト比を表す (例 3:2 = 1.5). |
| x:y^ | 指定したアスペクト比になるよう行または列を削る (外接クリップ) |
| x:y# | 指定したアスペクト比になるよう行または列を足す (内接マージン) (2021年11月13日 7.1.0-14 追加)|
| {size}{offset} | オフセットの指定 (デフォルトは +0+0). {size} は上記の形式に従う |
| {size}{+-}x{+-}y | ピクセル中の横方向と縦方向のオフセット x と y 、両方共 +- 符号が必要. オフセットは -gravity 設定に影響される。% やその他 size の演算子は影響しない。 |

## scale%

縦幅(height)横幅(width)まとめてパーセント指定します。要するに倍率指定です。

```shell
% convert rose: rose.png
% convert rose: -resize 200% rose-200p.png
% convert rose: -resize 50% rose-50p.png
% identify rose.png rose-200percent.png rose-50percent.png
rose.png PNG 70x46 70x46+0+0 8-bit sRGB 6975B 0.000u 0:00.000
rose-200p.png PNG 140x92 140x92+0+0 8-bit sRGB 19686B 0.000u 0:00.000
rose-50p.png PNG 35x23 35x23+0+0 8-bit sRGB 2213B 0.000u 0:00.009
```

|rose.png| 200% | 50% |
|---|---|---|
|![rose.png](https://qiita-image-store.s3.amazonaws.com/0/1798/4234f893-7f6d-b441-369a-11eb1f044c64.png) (70x46) | ![rose-200p.png](https://qiita-image-store.s3.amazonaws.com/0/1798/7f7ba4f1-de4e-8d16-e467-732df02060a3.png) (140x92) | ![rose-50p.png](https://qiita-image-store.s3.amazonaws.com/0/1798/2108beb3-382d-dc17-5e84-98f30fbe66f8.png) (35x23)|

## scale-x%xscale-y%

縦幅(height)と横幅(width)を別々にパーセント指定

```shell
% convert rose.png -resize 200%x300% rose-200px300p.png
% identify rose.png rose-200px300p.png
rose.png PNG 70x46 70x46+0+0 8-bit Gray 4c 502B 0.000u 0:00.000
rose-200px300p.png PNG 140x138 140x138+0+0 8-bit Gray 256c 4287B 0.000u 0:00.000
```

|rose.png | 200%x300% |
|---|---|
|![rose.png](https://qiita-image-store.s3.amazonaws.com/0/1798/4234f893-7f6d-b441-369a-11eb1f044c64.png) (70x46)|  ![rose-200px300p.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/8c34c031-18af-467a-a9a8-91c5998fdb64.png) (140x138)|

## width

横幅(width)を指定。縦幅(height)はアスペクト比を保つよう自動で決まる

```shell
% convert rose.png -resize 200 rose-200.png
% identify  rose-200.png
rose-200.png PNG 200x131 200x131+0+0 8-bit sRGB 34012B 0.000u 0:00.000
```

|rose.png| 200 |
|---|---|
|![rose.png](https://qiita-image-store.s3.amazonaws.com/0/1798/4234f893-7f6d-b441-369a-11eb1f044c64.png) (**70**x46)| ![rose-200.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/0357f350-391a-5937-37aa-96eab7288707.png) (**200**x131) |

## xheight

縦幅(height)を指定。横幅(width)はアスペクト比を保つよう自動で決まる。尚、x〜% のようにパーセント単位だと縦だけ伸ばす。

```shell
% convert rose.png -resize x200 rose-x200.png
% identify  rose-x200.png
rose-x200.png PNG 304x200 304x200+0+0 8-bit sRGB 63862B 0.000u 0:00.000
```
|rose.png | x200 |
|---|---|
|![rose.png](https://qiita-image-store.s3.amazonaws.com/0/1798/4234f893-7f6d-b441-369a-11eb1f044c64.png)  (70x<b>46</b>) | ![rose-x200.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/27b197e1-2d24-1c6a-81e2-e84784bf6236.png)(302x<b>200</b>) |


## widthxheight

縦幅(height)と横幅(width)の最大値を指定する。アスペクト比は保つ

```shell
% convert rose.png -resize 200x200 rose-200x200.png
% identify  rose-200x200.png
rose-200x200.png PNG 200x131 200x131+0+0 8-bit sRGB 34012B 0.000u 0:00.000
```
|rose.png| 200x200 |
|---|---|
|![rose.png](https://qiita-image-store.s3.amazonaws.com/0/1798/4234f893-7f6d-b441-369a-11eb1f044c64.png)  (70x46) | ![rose-200x200.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/d6604615-8cb9-a3db-e6d8-25b683d39c21.png) (**200**x131)|

## widthxheight^ 

縦幅(height)と横幅(width)の最小値を指定する。アスペクト比は保つ。

```shell
% convert rose.png -resize 200x200^ rose-200x200min.png
% identify rose-200x200min.png
rose-200x200min.png PNG 304x200 304x200+0+0 8-bit sRGB 63862B 0.000u 0:00.000
```

|rose.png| 200x200^ |
|---|---|
|![rose.png](https://qiita-image-store.s3.amazonaws.com/0/1798/4234f893-7f6d-b441-369a-11eb1f044c64.png)  (70x46) |  ![rose-200x200min.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/548ba2fa-cf68-0b4d-3c96-90f0d575ca5b.png) (304x<b>200</b>) |

## widthxheight!

縦幅(height)と横幅(width)ズバリのサイズになる。アスペクト比は維持しない。

```shell
% convert rose.png -resize '200x200!' rose-200x200fix.png
% identify  rose-200x200fix.png
rose-200x200fix.png PNG 200x200 200x200+0+0 8-bit sRGB 46490B 0.000u 0:00.000
```

|rose.png| '200x200!' |
|---|---|
|![rose.png](https://qiita-image-store.s3.amazonaws.com/0/1798/4234f893-7f6d-b441-369a-11eb1f044c64.png)  (70x46) |  ![rose-200x200fix.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/c67ae36b-4502-3480-cf13-2696f06b03c8.png) (200x200)|


## widthxheight>

横幅(width) と縦幅(height) の引数よりも画像の大きい方を縮小する。
要するに、拡大はしない。縮小のみ実行してかつアスペクト比を保つ指定です。

```shell
% convert rose.png -resize '100x100>' rose-100x100gt.png
% convert rose.png -resize '50x50>' rose-50x50gt.png
% convert rose.png -resize '80x40>' rose-80x40gt.png
% convert rose.png -resize '20x20>' rose-20x20gt.png
% identify rose-100x100gt.png rose-50x50gt.png rose-80x40gt.png rose-20x20gt.png
rose-100x100gt.png PNG 70x46 70x46+0+0 8-bit sRGB 6975B 0.000u 0:00.000
rose-50x50gt.png PNG 50x33 50x33+0+0 8-bit sRGB 3976B 0.000u 0:00.000
rose-80x40gt.png PNG 61x40 61x40+0+0 8-bit sRGB 5531B 0.000u 0:00.000
rose-20x20gt.png PNG 20x13 20x13+0+0 8-bit sRGB 256c 1311B 0.000u 0:00.000
```

|rose.png| '100x100>' | '50x50>' | '80x40>' | '20x20>' |
|---|---|---|---|---|
|![rose.png](https://qiita-image-store.s3.amazonaws.com/0/1798/4234f893-7f6d-b441-369a-11eb1f044c64.png)  (70x46) | ![rose-100x100gt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/997f7b74-fcce-6571-258c-f1702a7f4923.png) (70x46) | ![rose-50x50gt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/26089938-f275-8ed7-5009-7bf660a4186e.png) (**50**x33) | ![rose-80x40gt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/2814e89f-ec47-a6d9-5da3-5aba44b6a2ef.png) (61x40) |![rose-20x20gt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/5eef3eed-c683-3b6e-ddb7-cc00009ef392.png) (**20**x13) |


## widthxheight<

横幅(width) と縦幅(height) の引数よりも画像の小さい方を拡大する。
要するに、縮小はしない。拡大だけ実行してかつアスペクト比を保つ指定です。

```shell
% convert rose.png -resize '100x100<' rose-100x100lt.png
% convert rose.png -resize '50x50<' rose-50x50lt.png
% convert rose.png -resize '20x20<' rose-20x20lt.png
% identify rose-100x100lt.png rose-50x50lt.png rose-40x40lt.png rose-20x20lt.png
rose-100x100lt.png PNG 100x66 100x66+0+0 8-bit sRGB 11687B 0.000u 0:00.000
rose-50x50lt.png PNG 70x46 70x46+0+0 8-bit sRGB 6975B 0.000u 0:00.000
rose-40x40lt.png PNG 70x46 70x46+0+0 8-bit sRGB 6975B 0.000u 0:00.000
rose-20x20lt.png PNG 70x46 70x46+0+0 8-bit sRGB 6975B 0.000u 0:00.000
```

|rose.png| '100x100<' | '50x50<' |'40x40<' |
|---|---|---|---|
|![rose.png](https://qiita-image-store.s3.amazonaws.com/0/1798/4234f893-7f6d-b441-369a-11eb1f044c64.png)  (70x46) |  ![rose-100x100lt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/cdc2e9c0-3ccb-b27d-f553-2c0f9b9dda81.png) (**100**x66) | ![rose-50x50lt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/239f677d-fff9-f607-fd7f-98b448c93017.png) (70x46) | ![rose-40x40lt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/58d4e86d-3193-cfa4-9dc0-4f7232234927.png) (70x46) |

## area@

指定した面積になるようリサイズする。幅(height)と横幅(width)の最小値を指定する。アスペクト比は保つ

```shell
% convert rose.png -resize 10000@  rose-10000a.png
% convert rose.png -resize 100@ rose-100a.png
% identify  rose-10000a.png rose-100a.png
rose-10000a.png PNG 123x81 123x81+0+0 8-bit sRGB 16147B 0.000u 0:00.000
rose-100a.png PNG 12x8 12x8+0+0 8-bit sRGB 97c 662B 0.000u 0:00.000
```

|rose.png| 10000@ | 100@|
|---|---|---|
|![rose.png](https://qiita-image-store.s3.amazonaws.com/0/1798/4234f893-7f6d-b441-369a-11eb1f044c64.png)  (70x46) | ![rose-10000a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/46d8bdc3-fffb-2c9f-3dd5-010d6db48585.png) (123x81) | ![rose-100a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/2f063535-ca39-3a65-e3b4-d0b98d3a4f41.png) (12x8)|

```
123 x 81 = 9963 (<= 10000)
12 x 8 = 96     (<=100)
```

指定した面積か、面積を超えないギリギリのサイズで拡大縮小します。

## x:y

アスペクト比を変更する。

```shell
% convert rose.png -resize 1:1 rose-1-1.png
% convert rose.png -resize 2:1 rose-2-1.png
% identify  rose-1-1.png rose-2-1.png
rose-1-1.png PNG 46x46 46x46+0+0 8-bit sRGB 4868B 0.000u 0:00.000
rose-2-1.png PNG 70x35 70x35+0+0 8-bit sRGB 5548B 0.000u 0:00.000
```
|rose.png| 1:1 | 2:1|
|---|---|---|
|![rose.png](https://qiita-image-store.s3.amazonaws.com/0/1798/4234f893-7f6d-b441-369a-11eb1f044c64.png)  (70x46) | ![rose-1-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/1cec865d-bdaf-ba0d-0b82-fc86e489c3ed.png) (46x46) | ![rose-2-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/18847763-2131-c0a2-8941-9d8c70f6d0e0.png) (70x35) |

```
46:46 = 1:1
70:35 = 2:1
```

元の大きさを超えないよう基準の尺を選ぶようです。

### {size}{offset}

オフセットの指定 (デフォルトは +0+0). 以下の {size} は上記の形式に従う

offset の記法は {+-}x{+-}y です。{size}{+-}x{+-}y の項に説明を譲ります。


### {size}{+-}x{+-}y

ピクセル中の横方向と縦方向のオフセット x と y 、両方共 +- 符号が必要. オフセットは -gravity 設定に影響される。% やその他 size の演算子は影響しない。

region 指定の例です。特定の場所にエフェクトをかけられます。

```shell
% convert rose.png -region 40x20+20+15 -negate  rose-negate.png
% convert rose.png -region 40x20+20+15 -modulate 120,100,240 rose-modulate.png
```

|rose.png| -negate | -modulate 120,100,240 |
|---|---|---|
|![rose.png](https://qiita-image-store.s3.amazonaws.com/0/1798/4234f893-7f6d-b441-369a-11eb1f044c64.png)   | ![rose-negate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/33f295f9-a2af-8776-a4ab-990e954e2684.png) | ![rose-modulate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/f9ed459a-28c6-a280-cc2f-9fe0389dab98.png) |

尚、この offset は gravity 指定の影響を受けます。

```shell
% convert rose.png -gravity center -region 40x20 -negate  rose-nagate-center.png
% convert rose.png -gravity center -region 40x20+10+10 -negate  rose-nagate-center+10+10.png
```

|rose.png| -negate | -modulate 120,100,240 |
|---|---|---|
|![rose.png](https://qiita-image-store.s3.amazonaws.com/0/1798/4234f893-7f6d-b441-369a-11eb1f044c64.png)   | ![rose-nagate-center.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/4c0ea5a5-8b65-49ca-6c0e-848c13b014ad.png) | ![rose-nagate-center+10+10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/c8c8da93-a2d3-af58-9057-17fa02a1dd74.png) |


Gravity との関連は以下のエントリをご参照ください。

- ImageMagick Gravity 指定
  - https://qiita.com/yoya/items/b6926f01b7cb90217e07

# ソースコード

Geometry 文字列のパース関数は MagickCore/geometry.c 内に２つあります。

## ParseRegionGeometry

Geometry 文字列を width, height, x, y に分解するのに加えて、>, < @ 等の修飾からフラグを得る関数です。

```c:MagickCore/geometry.c
MagickExport MagickStatusType ParseRegionGeometry(const Image *image,
  const char *geometry,RectangleInfo *region_info,ExceptionInfo *exception)
{
  MagickStatusType
    flags;

  SetGeometry(image,region_info);
  flags=ParseMetaGeometry(geometry,&region_info->x,&region_info->y,
    &region_info->width,&region_info->height);
  if (flags == NoValue)
    (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
      "InvalidGeometry","`%s'",geometry);
  return(flags);
}
```

この通り、実質的な処理は ParseMetaGeometry 任せです。>, < @ 等の修飾からフラグに応じて、x, y, width, height を確定させるお仕事をします。

```c:MagickCore/geometry.c
MagickExport MagickStatusType ParseMetaGeometry(const char *geometry,ssize_t *x\
,
  ssize_t *y,size_t *width,size_t *height)
<略>
  flags=GetGeometry(geometry,x,y,width,height);
  if ((flags & PercentValue) != 0)
    {
      MagickStatusType
        percent_flags;

      PointInfo
        scale;

      /*
        Geometry is a percentage of the image size.
      */
      percent_flags=ParseGeometry(geometry,&geometry_info);
      scale.x=geometry_info.rho;
＜略＞
  if ((flags & AspectRatioValue) != 0)
    {
      double
        geometry_ratio,
        image_ratio;

      /*
        Geometry is a relative to image size and aspect ratio.
      */
      (void) ParseGeometry(geometry,&geometry_info);
      geometry_ratio=geometry_info.rho;
      image_ratio=(double) former_width*
```

そして、文字列の分解はGetGeometry 関数です。x, y, width, height や >, < @ 等のフラグを拾い出します。

```c:MagickCore/geometry.c
MagickExport MagickStatusType GetGeometry(const char *geometry,ssize_t *x,
  ssize_t *y,size_t *width,size_t *height)
＜略＞
  for (p=pedantic_geometry; *p != '\0'; )
  {
    if (isspace((int) ((unsigned char) *p)) != 0)
      {
        (void) CopyMagickString(p,p+1,MagickPathExtent);
        continue;
      }
    c=(int)*p;
    switch (c)
    {
      case '%':
      {
        flags|=PercentValue;
        (void) CopyMagickString(p,p+1,MagickPathExtent);
        break;
```

## ParseGeometry

ユーザ的に Geometry と言えるか微妙ですが "," で区切ったより汎用的に使える書式も内部では Geometry と呼びます。
Geometry 文字列を rho(ロー、ρ), sigma(シグマ、σ) xi(クサイ、ξ), psi(プサイ、ψ), chi(カイ、χ) の５つの変数に分離します。

```c:MagickCore/geometry.c
MagickExport MagickStatusType ParseGeometry(const char *geometry,
  GeometryInfo *geometry_info)
＜略＞
  c=sscanf(geometry,"%lf%*[ ,]%lf%*[ ,]%lf%*[ ,]%lf",&coordinate.rho,
    &coordinate.sigma,&coordinate.xi,&coordinate.psi);
  if (c == 4)
    {
      /*
        Special case: coordinate (e.g. 0,0 255,255).
      */
      geometry_info->rho=coordinate.rho;
      geometry_info->sigma=coordinate.sigma;
      geometry_info->xi=coordinate.xi;
      geometry_info->psi=coordinate.psi;
      flags|=RhoValue | SigmaValue | XiValue | PsiValue;
      return(flags);
    }
```

例えば、-modulate <brightness>,<saturation>,<hue> といったオプションは以下のように処理します。

```c:MagickCore/enhance
  flags=ParseGeometry(modulate,&geometry_info);
  percent_brightness=geometry_info.rho;
  percent_saturation=geometry_info.sigma;
  if ((flags & SigmaValue) == 0)
    percent_saturation=100.0;
  percent_hue=geometry_info.xi;
  if ((flags & XiValue) == 0)
    percent_hue=100.0;
```

# X11 (X Window system) の影響

UNIX 系の GUI システムのベースになる X Window System (以下X11 と呼ぶ) というものがありまして(今も勿論ある)。
実は ImageMagick 3 まで X11 ライブラリの XParseGeometry を使っていました。つまり、ImageMagick の Geometry 記法の元は X11 です。

```
% man XParseGeometry
XParseGeometry(3)               XLIB FUNCTIONS               

NAME
       XParseGeometry, XWMGeometry - parse window geometry
(略)
       [=][<width>{xX}<height>][{+-}<xoffset>{+-}<yoffset>]
```

ImageMagick 4 から ParseGeometry を自前実装して、convert,mogrify等の X11 を使わないコマンドはそちらを使うようになっています。(X11 を使うコマンド例: display, animate, import)

# Canvas と Page

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

上記のように画像に関するサイズが２種類表示される件は、 Geometry 解説から逸脱する為、別エントリにしました。

-  ImageMagick の Canvas / Page について
   - https://qiita.com/yoya/items/007717e7b09c9c140dae


# 参考

- https://imagemagick.org/script/command-line-processing.php#geometry