ImageMagick7 の negate(ネガポジ変換)で透明になる件

英訳) http://blog.awm.jp/2018/11/18/im7negate/

ImageMagick 7 系で -negate オプションを使うと画像が消える時があります。先に言っておくと不具合ではなく意図した挙動です。

# 実動作

透明度無しの RGB 画像であれば問題ありません。

```
% convert rose: rose.png
% identify -verbose rose.png | grep Type
  Type: TrueColor
% convert rose.png -negate rose-negate.png
```
|rose.png | rose-negate.png|
|---|---|
| ![rose.png](https://qiita-image-store.s3.amazonaws.com/0/1798/b1a18dc4-f93a-fafc-fbe1-702f92d064e8.png) | ![rose-negate.png](https://qiita-image-store.s3.amazonaws.com/0/1798/80db1b57-90a3-1943-1b23-7a3ddec4db92.png) |

透明度付きの RGBA 画像だとご覧の有様です。

```
% convert rose: -matte rose-matte.png
% identify -verbose rose-matte.png | grep Type
  Type: TrueColorAlpha
% convert rose-matte.png -negate rose-matte-negate.png
```
|rose-matte.png | rose-matte-negate.png|
|---|---|
|![rose-matte.png](https://qiita-image-store.s3.amazonaws.com/0/1798/4ffb35d9-eb32-bfaa-d3e3-dfdeb4a3be38.png) | ![rose-matte-negate.png](https://qiita-image-store.s3.amazonaws.com/0/1798/5d05d622-c60a-25f7-af0e-c278c7df3b9e.png) |

これは、alpha値も A:255(不透明) から A:0(透明) に negate されるからです。

尚、ImageMagick 6 では問題ありません。

```
% convert6 rose-matte.png -negate rose-matte-negate6.png
```
|rose-matte.png | rose-matte-negate6.png|
|---|---|
|![rose-matte.png](https://qiita-image-store.s3.amazonaws.com/0/1798/4ffb35d9-eb32-bfaa-d3e3-dfdeb4a3be38.png) | ![rose-RGBA-negate6.png](https://qiita-image-store.s3.amazonaws.com/0/1798/2efab081-51b5-3724-36e4-db2ecf640855.png)

# 期待される使い方

-channel RGB を指定する事で、RGB だけ negate 、A を素通りさせる事が出来ます。
蛇足ですが、ImageMagick6 でも -channel RGBA を指定して ImageMagick7 のような挙動に出来ます。

```
% convert rose-matte.png -channel RGB -negate rose-matte-negate-rgb.png
% convert6 rose-matte.png -channel RGBA -negate rose-matte-negate-rgba6.png
```

|rose-matte-negate-rgb.png|rose-matte-negate-rgba6.png|
|---|---|
| ![rose-matte-negate-rgb.png](https://qiita-image-store.s3.amazonaws.com/0/1798/956aca1b-ecce-7a29-28fd-919d886ae6b8.png) | ![rose-matte-negate-rgba6.png](https://qiita-image-store.s3.amazonaws.com/0/1798/35a550f2-4ece-da76-9b5c-c4748e674583.png) |

また、A 以外すべての色成分を指定する事も出来るようです。(こちらはマニュアルの記載を見つけてないので仕様かどうかは不明)

```
% convert rose-matte.png -channel 'All,!A' -negate rose-matte-negate-all-A.png
```

|rose-matte-negate-all-A.png|
|---|
|![rose-matte-negate-all-A.png](https://qiita-image-store.s3.amazonaws.com/0/1798/9e48114f-ba9b-755b-230f-4e921b3f0fc9.png)|

# 調査

Negate を実行するコードです。

- ImageMagick-7 MagickCore/enhance.c

```
MagickExport MagickBooleanType NegateImage(Image *image,
  const MagickBooleanType grayscale,ExceptionInfo *exception)
＜略＞
          for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
          {
            PixelChannel channel = GetPixelChannelChannel(image,j);
            PixelTrait traits = GetPixelChannelTraits(image,channel);
            if ((traits & UpdatePixelTrait) == 0)
              continue;
            q[j]=QuantumRange-q[j];
          }
```

- ImageMagick6 magick/enhance.c

```
MagickExport MagickBooleanType NegateImage(Image *image,
  const MagickBooleanType grayscale)
{
  MagickBooleanType
    status;

  status=NegateImageChannel(image,DefaultChannels,grayscale);
  return(status);
}

MagickExport MagickBooleanType NegateImageChannel(Image *image,
  const ChannelType channel,const MagickBooleanType grayscale)
{
＜略＞
          if ((channel & RedChannel) != 0)
            SetPixelRed(q,QuantumRange-GetPixelRed(q));
          if ((channel & GreenChannel) != 0)
            SetPixelGreen(q,QuantumRange-GetPixelGreen(q));
          if ((channel & BlueChannel) != 0)
            SetPixelBlue(q,QuantumRange-GetPixelBlue(q));
          if ((channel & OpacityChannel) != 0)
            SetPixelOpacity(q,QuantumRange-GetPixelOpacity(q));
          if (((channel & IndexChannel) != 0) &&
              (image->colorspace == CMYKColorspace))
            SetPixelIndex(indexes+x,QuantumRange-GetPixelIndex(indexes+x));
          q++;
```

これらの動作に影響する DefaultChannels の定義が ImageMagick7 になって変わりました。

- ImageMagick7 MagickCore/pixel.h

```
  AllChannels = 0x7ffffff,
  DefaultChannels = AllChannels
```

- ImageMagick6 magick/magick-type.h

```
  DefaultChannels = ((AllChannels | SyncChannels) &~ OpacityChannel)
```

つまり、ImageMagick6 は DefaultChannels が "RGB,Sync" で、A を含めませんが、ImageMagick7 では AllChannels に定義が変更されて、RGBA 全部デフォルトの処理対象に含むようになりました。

# 考察

デフォルトで negate で alpha 値も処理するのは porting マニュアルにある通り、意図した挙動です。

- https://imagemagick.org/script/porting.php#cli

```
Most algorithms update the red, green, blue, black (for CMYK), and alpha channels. Most operators will blend alpha the other color channels, but other operators (and situations) may require this blending to be disabled, and is currently done by removing alpha from the active channels via -channel option. (e.g. convert castle.gif -channel RGB -negate castle.png).
```

画像処理アルゴリズムの多くが RGBA ４つ平等に扱った方が都合が良いらしく(本当？)、ImageMagick7 では DefaultChannels を A を含めた定義に変更しました。今回の negate のように都合の悪い事もありますが、全体的にはこの方が良いという判断でしょう。

ただし、ImageMagick7 の挙動はドキュメント的に齟齬が残っています。

- https://imagemagick.org/script/command-line-options.php#negate

```
The red, green, and blue intensities of an image are negated.
```
alpha 値を intensities(色成分の強さ)に含むとは考えにくいですが、特に明記されずに微妙なところです。
ただし、color_mods の説明だと実際の動作と明らかに違います。

- https://www.imagemagick.org/Usage/color_mods/#negate

```
by default ignores the alpha channel.
```
これは ImageMagick6 の挙動ですね。

# 最後に

porting 以外のマニュアルにも注意書きが欲しいので、公式サイドに求めていくのが良さそうです。