java.awt.image.BufferedImage.getType()が0:TYPE_CUSTOMの場合、インスタンス化時にIllegalArgumentExceptionが発生

# はじめに

## 記事を書いたきかっけ

長年運用している古いシステムにて、外部システムから送信される画像ファイル（JPEG)をリサイズして保存するという処理をしているのですが、外部システムの更改に伴いエラーが発生するようになり、その原因を調査しました。

同じ現象で困っている人はいないかもしれないですが、もし手助けになれば幸いです。

## 対象読者

java.awt.image.BufferedImageをインスタンス化しようとして、IllegalArgumentExceptionに遭遇した人（いるのかそんな人？）

## 環境

Java 1.6

Javaのバージョンが1.6の時点でお察し。クローズドな環境の塩漬けシステムです。

# 事象

処理部分のコードは以下のようなイメージです。

```java
String strFilePath = "ファイルパス";
File file = new File(strFilePath);

BufferedImage image = ImageIO.read(file);
int orgWidth = image.getWidth();
int orgHeight = image.getHeight();

// 省略しているが、ここでリサイズの割合計算
double scale = 0.5 // 実際には計算結果を代入している

int scaleWidth = (int)(orgWidth * scale);
int scaleHeight = (int)(orgHeight * scale);

BufferedImage scaleImage = new BufferedImage(scaleWidth,
		scaleHeight, image.getType()); // <=★ここでIllegalArgumentExceptionが発生
// この先、修正した画像に関する処理
```

再計算後のサイズをもとにjava.awt.image.BufferedImageをインスタンス化しようとしたときにIllegalArgumentExceptionがThrowされました。

どうやらimage typeが0だとダメみたいです。

```
java.lang.IllegalArgumentException: Unknown image type 0
```

# 原因

## 直接原因

BufferedImageのソースコードを見てみると、imageTypeが0の場合（厳密には想定した値以外の場合）はIllegalArgumentExceptionをthrowするようになっているので、これが直接の原因のようです。

```java
public BufferedImage(int width,
                         int height,
                         int imageType) {
    switch (imageType) {

// 省略 //

		default:
		    throw new IllegalArgumentException ("Unknown image type " +
		                                        imageType);
```

## 根本原因

結論としては、画像ファイルに使用されていた色空間がRGB色空間ではなく、YUV色空間になっていたことが原因でした。

BufferedImageのimageTypeは色空間や色深度などに応じて決められます。

今回のように、ImageIO.readメソッドを使用してBufferedImageを生成する場合、com.sun.imageio.plugins.jpeg.JPEGImageReaderクラスでimageTypeの元となる情報が決定されます。

JPEGImageReaderクラスでは、imageTypeを決定するために色空間を利用していますが、これはネイティブコードから設定されることになっており、

```java
     /**
     * Set by setImageData native code callback.  A modified
     * IJG+NIFTY colorspace code.
     */
    private int colorSpaceCode;
```

今回エラーとなったケースではcolorSpaceCode = 7(JPEG.JCS_YCbCrA)が設定されていました。

（ネイティブコードでどのように判断されるのかは不明・・・。）

com.sun.imageio.plugins.jpeg.JPEGImageReader.getImageTypesOnThread(int)では、設定されたcolorSpaceCodeに応じて、Java内でどのような色空間にするのかを判定しますが、サポートされていない色空間なのでRGBにしとくよ、ということでRGBとみなされていました。

```java
				case JPEG.JCS_YCbCrA:  // Default is to convert to RGBA
            // As there is no YCbCr ColorSpace, we can't support
            // the raw type.
            list.add(getImageType(JPEG.JCS_RGBA));
            break;
        }
```

com.sun.imageio.plugins.jpeg.JPEGImageReader.produce()にて、RGB値のオフセットがきまる際にオフセットがJPEG.JCS_RGBAとして設定されることになります。

```java
protected ImageTypeSpecifier produce() {
        switch (csCode) {
            case JPEG.JCS_GRAYSCALE:
                return ImageTypeSpecifier.createFromBufferedImageType
                        (BufferedImage.TYPE_BYTE_GRAY);
            case JPEG.JCS_RGB:
                return ImageTypeSpecifier.createInterleaved(JPEG.JCS.sRGB,
                        JPEG.bOffsRGB,
                        DataBuffer.TYPE_BYTE,
                        false,
                        false);
            case JPEG.JCS_RGBA:
                return ImageTypeSpecifier.createPacked(JPEG.JCS.sRGB,
                        0xff000000,
                        0x00ff0000,
                        0x0000ff00,
                        0x000000ff,
                        DataBuffer.TYPE_INT,
                        false);
```

結果として、java.awt.image.BufferedImage.BufferedImage(ColorModel, WritableRaster, boolean, Hashtable<?, ?>)

でのimageTypeの判定時にどの条件にもあてはまらなかったことで、imageTypeがTYPE_CUSTOM(0)となっていました。

## 対策

今回の場合は外部システムから送信される画像の種類が1種類だったために、imageTypeが0の場合に変わりの値を設定することで回避しました。

これが、どのような画像が送信されるかわからないようなオープンなシステムの場合はまた別の回避策が必要そうです。

# さいごに

普段あまり気にしない色空間の違いにより、まさかこんなことが起きるとは思ってもみませんでした。

JPEGフォーマットや色空間の違いについて知ることができたので勉強にはなったが、もうこんな思いはしたくないものです。