« 2013年12月 | トップページ | 2015年1月 »

2014年2月

Java:JPEGファイルから画像サイズのみ読み込む

【課題】
今回は,JPEGファイルから画像サイズ(縦×横ピクセル値)の情報だけを取得してみます。

【概要】
Javaのクラスを使って画像サイズを取得する方法として,ImageBufferクラスインスタンスにJPEGデータを読み込むと,サイズ取得メソッドで値を取り出すことができます。
この方法は,画像データを全部読み込んでのサイズ取得になるので,サイズ取得のみが目的の場合はメモリと処理時間を無駄に費やしてしまいます。
今回はローレベルクラスを使ってJPEGファイルから必要な情報のみ読み取ります。

●JPEGファイルの画像サイズの格納場所
まずはJPEGファイルの中身について。JPEGファイルはJFIFというフォーマットに従って作られています。JPEGのデータは複数のセグメントに分かれており,1つのセグメントは基本的に次のような構成になっています。

 サイズ
(1)マーカー2byte
(2)セグメントサイズ値2byte
(3)データ(2)の値-2byte
マーカーはそのセグメントを識別する値で,各マーカーの値には対応するマーカー名が存在します。
セグメントサイズに格納される値は,自分自身とデータサイズの合計です。マーカー値のサイズは含みません。
そのため(2)セグメントサイズの値は(3)データの実サイズより+2byte大きい値になります。
この構成の例外として,JPEGデータの最初のセグメント「SOI(0xFFD8)」はマーカーのみです。マーカーのみなので,SOIセグメントのサイズは2バイトです。
画像サイズが入っているセグメントのマーカー名はSOFです。SOFはSOF0(0xFFC0)からSOF15(0xFFCF)まであるようですが,サイズ値の格納場所は変わらないようです。
SOFで画像サイズが格納されている場所は以下になります。下記の「縦ピクセル数」と「横ピクセル数」が目的の値です。

 サイズ
(1)マーカー:SOF (SOF0〜SOF15のいずれか)2byte
(2)セグメントサイズ値2byte
(3)データ(1)(詳細不明)1byte
(2)縦ピクセル数2byte
(3)横ピクセル数2byte
(以下略)

●処理の概要
以上から,サイズ取得の処理は以下のようになります。
(1)SOIをスキップ
(2)マーカがSOF0〜SOF15のいずれかのセグメントを探す。
(2-1)マーカを参照してSOFではないセグメントだったら,そのセグメントをスキップして次のセグメントに移る。
(2-2)セグメントをスキップするときのサイズは,そのセグメントのセグメントサイズ値から決定する。
(3)マーカSOF0〜SOF15が見つかったら,セグメントサイズの2バイトとデータ部の先頭1バイトをスキップして,2バイトづつデータを取得し整数化する。

●使用するクラス
JPEGファイルを読み込むために今回使用するJavaのクラスは「FileImageInputStream」です。
ファイルのアクセスは,このクラスの以下のメソッドを使用します。
(1)readUnsignedShort() …… 2バイト読み込んで値をintで返す。ストリーム位置は2進む。
(2)skipBytes() ………… データを読み込まずにストリーム位置のみ移動する。
(3)readShort() ………… 2バイト読み込んで,値をshortで返す。ストリーム位置は2進む。

●実装
以上より,実際のコードは以下のようになります。
(2015/06/28改訂)

import java.awt.Dimension;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
 
import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.ImageInputStream;
 
public class JPEGImagesize
{
  //----------------------------------------------------------------------------------
  private static final int SOI  = 0xFFD8;
  private static final int SOF0 = 0xFFC0;
  private static final int SOF15 = 0xFFCF;
  //----------------------------------------------------------------------------------
  /**
   * JPEG画像データの縦横サイズ取得.
   * @param file JPEGファイル.
   * @return ファイルサイズ. 幅,高さの順のintの配列.
   * @throws IOException
   */
  private static Dimension getSize (
    File file)
    throws IOException
  {
    int width = 0, height = 0;
    ImageInputStream stream = null;
    try {
      stream = new FileImageInputStream(file);
 
      if (stream.readUnsignedShort() == SOI) {
        int size, kind;
        while ((kind = stream.readUnsignedShort()) < SOF0 || SOF15 < kind) {
          size = stream.readUnsignedShort() - 2;
          if (stream.skipBytes(size) < size) { break; }
        }
        if (SOF0 <= kind && kind <= SOF15) {
          size = stream.readUnsignedShort();
          stream.skipBytes(1);
          height = stream.readShort();
          width = stream.readShort();
        }
      }
    }
    catch (EOFException ex) {
      width = 0; height = 0;
    }
    finally {
      try {
        if (stream != null) { stream.close(); }
      } catch (IOException ex) { ex.printStackTrace(); }
    }
    return new Dimension(width, height);
  }
  //----------------------------------------------------------------------------------
  /**
   * (検証用)JPEGの縦横サイズを取得して表示する.
   * @param args args[0]に画像ファイルのパスが指定されている想定.
   */
  public static void main(String[] args)
  {
    try {
      if (0 < args.length) {
        File file = new File(args[0]);
        if (file.exists()) {
          Dimension size = JPEGImagesize.getSize(file);
          System.out.println("width :"+String.valueOf(size.width));
          System.out.println("height:"+String.valueOf(size.height));
        }
      }
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}
※上記コードでは,整形のため空白部分は全角スペースを使用しています。
【著作権表記】上記コードを含む本ブログのプログラムコードは,私的利用可,商用利用可,改変しての利用可です。利用の際に作者に許諾を得る必要はありません。

■関連書籍をAmazonで検索:[Java]
現場で使えるJavaライブラリ(Kindle版)



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


ノートンシリーズ新登場バナー

| | トラックバック (0)

« 2013年12月 | トップページ | 2015年1月 »