ImageMagick Gravity 指定

# はじめに

ImageMagick の Gravity は画像を合成、連結、分割などする際にどの方向に寄せるかの機能です。例えばベース画像の上に文字や画像を載せる際のレイアウトを調整できます。-gravity オプションで指示します。

```shell
% convert granite: -gravity Center -pointsize 20 -annotate 0 Center  Center.png
% convert granite: rose: -gravity South -composite  South.png
```

|Center.png|South.png|
|---|---|
|![Center.png](https://qiita-image-store.s3.amazonaws.com/0/1798/7d165a2c-94b0-d916-ef7b-e42c143cb6d1.png) | ![South.png](https://qiita-image-store.s3.amazonaws.com/0/1798/e18aa041-cbe4-f605-5115-f3293da295fb.png) |


# gravity type (direction)

-list gravity で一覧が出せます。

```shell
% convert -list gravity
None
Center
East
Forget
NorthEast
North
NorthWest
SouthEast
South
SouthWest
West
```
地図で使われる方角の表現ですね。

| Gravity type (direction)|
|---|
|![image.png](https://qiita-image-store.s3.amazonaws.com/0/1798/eb18ace6-34b8-d0c1-2fb9-d9d2266fda68.png)|

尚、None は未指定、Forget は取り消しの意味合いですが、どちらも内部的な数値は 0 でデフォルト扱いの動作になります。

なお、+gravity で None 相当です。Forget じゃないのか。。(しつこいですが、どっちでも動作は変わりません)

- MagickWand/mogrify.c

```c
if (LocaleCompare("gravity",option+1) == 0)
  {
    if (*option == '+')
      {
        draw_info->gravity=UndefinedGravity;
        break;
      }
    draw_info->gravity=(GravityType) ParseCommandOption(
      MagickGravityOptions,MagickFalse,argv[i+1]);
    break;
  }
```


# コマンド例

## annotate (文字入れ)

```shell
% for g in NorthWest North NorthEast West Center East SouthWest South SouthEast ; 
    do convert granite: -gravity $g -pointsize 20 -annotate 0 $g  $g.png ;
  done
```
|   | West | Center | East |
|---|---|---|---|
|North | ![NorthWest.png](https://qiita-image-store.s3.amazonaws.com/0/1798/d183360f-6d45-6b1e-f0aa-4f841459f33f.png) | ![North.png](https://qiita-image-store.s3.amazonaws.com/0/1798/5da965af-ae16-0437-369b-4509a0e1a29f.png) | ![NorthEast.png](https://qiita-image-store.s3.amazonaws.com/0/1798/dd32768b-2558-c2ee-50c3-22e06770366a.png) |
| Center | ![West.png](https://qiita-image-store.s3.amazonaws.com/0/1798/40b6b5ca-e937-9211-e275-d5f93cea054d.png) | ![Center.png](https://qiita-image-store.s3.amazonaws.com/0/1798/7d165a2c-94b0-d916-ef7b-e42c143cb6d1.png) |  ![East.png](https://qiita-image-store.s3.amazonaws.com/0/1798/fe44cc33-a5f4-2a0f-9bb0-e50ee83389cc.png) | 
|South|![SouthWest.png](https://qiita-image-store.s3.amazonaws.com/0/1798/bfdcbe6c-da3b-06eb-0f25-22f1ecdcefd0.png) | ![South.png](https://qiita-image-store.s3.amazonaws.com/0/1798/fc97c057-784c-5907-327a-7b2298e0036c.png) | ![SouthEast.png](https://qiita-image-store.s3.amazonaws.com/0/1798/8bc502ad-a3c6-8ac0-0129-6769669261c2.png) |


## composite (画像合成)

```shell
% for g in NorthWest North NorthEast West Center East SouthWest South SouthEast ;
      do convert granite: rose: -gravity $g -composite  $g.png ;
  done
```

| |West | Center | East |
|---|---|---|---|
|North|![NorthWest.png](https://qiita-image-store.s3.amazonaws.com/0/1798/2456c7a0-a42b-9a83-869c-be235a141e0e.png) | ![North.png](https://qiita-image-store.s3.amazonaws.com/0/1798/9ba78498-f5f4-f5f7-e339-e15745ac8909.png) | ![NorthEast.png](https://qiita-image-store.s3.amazonaws.com/0/1798/ea7a5dfc-4c4d-4a1b-a385-dc73fc0f22bd.png) |
| Center |![West.png](https://qiita-image-store.s3.amazonaws.com/0/1798/758f2c80-0756-0404-fae5-fc4015c3bb1b.png) | ![Center.png](https://qiita-image-store.s3.amazonaws.com/0/1798/56420e27-5017-25ba-d36e-4bfab5d300fc.png) | ![East.png](https://qiita-image-store.s3.amazonaws.com/0/1798/e233467e-792f-8818-9dbf-cf480b4bab44.png)|
|South|  ![SouthWest.png](https://qiita-image-store.s3.amazonaws.com/0/1798/7f6e40f6-8a20-ce59-1b77-5090b4fd754b.png) | ![South.png](https://qiita-image-store.s3.amazonaws.com/0/1798/3e35113d-8ee1-76d1-af21-8d414ec45310.png) | ![SouthEast.png](https://qiita-image-store.s3.amazonaws.com/0/1798/fcf4ad91-4506-36cb-dd6c-cb911a5089f6.png) |

## append

```shell
% for g in West Center East ;
      do convert -gravity $g -append rose: granite:  $g.png ;
  done
```
|West|Center|East|
|---|---|---|
| ![West.png](https://qiita-image-store.s3.amazonaws.com/0/1798/74356d45-0499-b8e0-e936-59090e0492dd.png) | ![Center.png](https://qiita-image-store.s3.amazonaws.com/0/1798/22d83624-5771-e95e-878e-43b89c861ec5.png) | ![East.png](https://qiita-image-store.s3.amazonaws.com/0/1798/81fc20b3-33ad-24b9-d481-1f6dedfad8a8.png) |

-append は横に繋げるので、NorthWest, West, SouthWest のような**縦方向**の違いは影響せずに同じ結果になります。

```shell
% for g in North Center South ;
      do convert -gravity $g +append rose: granite:  $g.png ;
  done
```
|North|Center|South|
|---|---|---|
| ![North.png](https://qiita-image-store.s3.amazonaws.com/0/1798/ce825f17-53fe-8678-e1ae-4d484204fbda.png) | ![Center.png](https://qiita-image-store.s3.amazonaws.com/0/1798/fcfcb398-928b-0e1a-03ed-c7aa75f78b73.png) | ![South.png](https://qiita-image-store.s3.amazonaws.com/0/1798/006bc33b-29d7-cf2f-cb89-5c392f29e7c8.png) |

+append は横に繋げるので、NorthWest, North, NorthEast のような**横方向**の違いは影響せずに同じ結果になります。

## crop

別記事にあります。

- ImageMagick の画像 crop
  - https://qiita.com/yoya/items/62879e6e03d5a70eed09

# オフセット

Gravity で寄せた場所から更にオフセット指定でずらす事が出来ます。

```shell
% for g in NorthWest North NorthEast West Center East SouthWest South SouthEast ;
      do convert granite: -gravity $g -pointsize 20 -annotate +20+20 $g  $g.png ;
  done
```

| ![NorthWest.png](https://qiita-image-store.s3.amazonaws.com/0/1798/fc2525d5-3be6-2a71-3136-6b1457ca3c53.png) | ![North.png](https://qiita-image-store.s3.amazonaws.com/0/1798/829aea23-dfbd-23d8-af7a-dd2506a90376.png) | ![NorthEast.png](https://qiita-image-store.s3.amazonaws.com/0/1798/abbd6669-2b2a-9ecf-7479-02b59448ca9f.png) |
|---|---|---|
| ![West.png](https://qiita-image-store.s3.amazonaws.com/0/1798/af7d053c-40c8-584e-ba69-0708211170dd.png) | ![Center.png](https://qiita-image-store.s3.amazonaws.com/0/1798/c1bc0add-053a-4ce9-5f5a-720411413a01.png) | ![East.png](https://qiita-image-store.s3.amazonaws.com/0/1798/00027a96-8662-2dcf-0047-f91a9ba43c8a.png) |
| ![SouthWest.png](https://qiita-image-store.s3.amazonaws.com/0/1798/7c348734-82cc-7b2c-b106-fbe1b7bfa580.png) | ![South.png](https://qiita-image-store.s3.amazonaws.com/0/1798/418b3108-ff6b-076b-6584-fae538167a29.png) | ![SouthEast.png](https://qiita-image-store.s3.amazonaws.com/0/1798/f3f12311-92cc-e8a4-027d-4a47b2df2d9e.png) |

いまいち分かりにくいので、オフセット無しの文字を薄字で入れて、更にズレた方向を赤い矢印で示してみます。

```shell
% for g in NorthWest North NorthEast West Center East SouthWest South SouthEast ;
      do convert granite: -gravity $g -pointsize 20 -annotate +20+20 $g \
                                         -fill gray -annotate 0 $g  $g.png ;
  done
```

| | → | → | ← |
|---|---|---|---|
|↓| ![NorthWest.png](https://qiita-image-store.s3.amazonaws.com/0/1798/47be304a-8f37-ccbd-f718-4fcf6183cc11.png) | ![North.png](https://qiita-image-store.s3.amazonaws.com/0/1798/bfbe8211-59f6-fe0a-e2b6-fba48db685a0.png) | ![NorthEast.png](https://qiita-image-store.s3.amazonaws.com/0/1798/6ade9656-0d1f-f7d1-b2c1-833354f9de13.png) |
|↓| ![West.png](https://qiita-image-store.s3.amazonaws.com/0/1798/01e0705b-d742-83cf-e766-aa71044adab8.png) | ![Center.png](https://qiita-image-store.s3.amazonaws.com/0/1798/fbc131c8-bc77-6e7e-466a-418693682ab7.png) | ![East.png](https://qiita-image-store.s3.amazonaws.com/0/1798/3d013af8-aa28-ea92-6dfc-d97c74381893.png)
|↑| ![SouthWest.png](https://qiita-image-store.s3.amazonaws.com/0/1798/d5dba8f2-7148-d981-9886-61946b41cb77.png) | ![South.png](https://qiita-image-store.s3.amazonaws.com/0/1798/737c8ab7-88b3-fe28-5b7a-a2c0de40f3c1.png) | ![SouthEast.png](https://qiita-image-store.s3.amazonaws.com/0/1798/5c2e1fa8-1b21-77c6-4bdd-9b7e5e13b64f.png) |

つまり、Gravity が左、上、中央によっている時はオフセットが + に働き、右、下の場合は - に働きます。端っこからどのくらい開けるかを何となく指定できるので慣れると便利です。

# 該当コード


まずは横方向を合わせて、次の縦方向を合わせる。それだけです。
〜EastGravity では width - x 、 South〜Gravity は height - y してるように、この２つは逆転します。つまり端っこからどの程度空けるかの計算をしています。

```c
MagickExport void GravityAdjustGeometry(const size_t width,
  const size_t height,const GravityType gravity,RectangleInfo *region)
{
  if (region->height == 0)
    region->height=height;
  if (region->width == 0)
    region->width=width;
  switch (gravity)
  {
    case NorthEastGravity:
    case EastGravity:
    case SouthEastGravity:
    {
      region->x=(ssize_t) (width-region->width-region->x);
      break;
    }
    case NorthGravity:
    case SouthGravity:
    case CenterGravity:
    {
      region->x+=(ssize_t) (width/2-region->width/2);
      break;
    }
    case ForgetGravity:
    case NorthWestGravity:
    case WestGravity:
    case SouthWestGravity:
    default:
      break;
  }
  switch (gravity)
  {
    case SouthWestGravity:
    case SouthGravity:
    case SouthEastGravity:
    {
      region->y=(ssize_t) (height-region->height-region->y);
      break;
    }
＜略＞
```

# Forget のうんちく

None があるのに Forget があるのが不自然に見えるので、そのうんちく話です。
実は、大昔の ImageMagick が X11 Window System にべったりだった頃の名残りです。

- 参考) https://ja.wikipedia.org/wiki/X_Window_System

今現在 Gravity タイプは ImageMagick の geometry.h で以下のように定義されます。

```c
typedef enum
{
  UndefinedGravity,
  ForgetGravity = 0,
  NorthWestGravity = 1,
  NorthGravity = 2,
  NorthEastGravity = 3,
  WestGravity = 4,
  CenterGravity = 5,
  EastGravity = 6,
  SouthWestGravity = 7,
  SouthGravity = 8,
  SouthEastGravity = 9
} GravityType;
```

ImageMagick-3 系までは、この enum は存在せず、Xlib の定義を参照していました。
当時は X11R4 が主流で、以下のヘッダが該当します。

- https://github.com/yoya/x.org/blob/master/X11R4/mit/include/X.h#L361

```c
| /* Bit Gravity */ |
|:--|
|  |
| #define ForgetGravity |  | 0 |
| #define NorthWestGravity | 1 |
| #define NorthGravity |  | 2 |
| #define NorthEastGravity | 3 |
| #define WestGravity |  | 4 |
| #define CenterGravity |  | 5 |
| #define EastGravity |  | 6 |
| #define SouthWestGravity | 7 |
| #define SouthGravity |  | 8 |
| #define SouthEastGravity | 9 |
| #define StaticGravity |  | 10 |
```

ImageMagick-4 系で Windows 対応した時に、自前のヘッダ(当時は classify.h)で定義するようになりました。
尚、同じ名前を使っているので X11 のヘッダを include すると衝突しそうですが、ForgetGravity が define されてたら 〜Gravity を #undef するという力技で対処してます。

```c
#if defined(ForgetGravity)
#undef ForgetGravity
#undef NorthWestGravity
#undef NorthGravity
#undef NorthEastGravity
#undef WestGravity
#undef CenterGravity
#undef EastGravity
#undef SouthWestGravity
#undef SouthGravity
#undef SouthEastGravity
#endif
```

この 〜Gravity は X Window System で Window をどう配置するかの属性に使われるもので、このシステムが当時のオブジェクト指向のはしりだった事を考えると、ForgetGravity は命令としてのメッセージ性を高める為の命名だと思います。

# 参考

- https://imagemagick.org/script/command-line-options.php#gravity
- http://www.imagemagick.org/Usage/annotating/#gravity