Fastest Image Reading and Writing

いろいろな画像ファイルを高速読み書き(GDI+を使用)」

BMP, JPEG, GIF, PNG, TIFF, WMF, EMF 形式の画像ファイルをGDI+で読み込み、
BMP, JPEG, GIF, PNG, TIFF いずれかの形式を指定して書き出す方法。
今回の内容をひとことで表現すれば「GDI+で何でも読み書き」。

0.準備
1.読み込み
2.書き出し
3.まとめ
4.お願いとお断り

0.Delphiを起動して画像の読み書き用Formを準備する

Delphiを起動して、新規にVCLアプリケーションを作成。次のVCLコンポーネントを載せたFormを準備する。準備ができたら、任意のフォルダにプロジェクトを保存する。

VCLコンポーネントをFormに配置したところ
VCLコンポーネントの親子関係

(1)FormにStatusBarを1つ置く。
(2)Formの上にPanelを1つ置き、Alignプロパティを「alBottom」、Heightプロパティを「60」に設定。
(3)Panel1をクリックして選択し、Panel1上にButtonを2つ、RadioGroupを1つ置く。名前はデフォルトのまま、CaptionプロパティをButton1は「読み込み」、Button2は「書き出し」、RadioGroup1は「画像の大きさ」に変更。Button2のAnchorsプロパティは下図のようにakTopとakRightのみTrueに変更する。こうすることで、Formを最大化した時、画面右側のButton2がFormの右側下隅(自然な位置)にくる。

Button2のAnchorsプロパティを変更

(4)Formをクリックして選択し、Form上にScrollBoxを1つ置き、Alignプロパティを「alClient」に設定。 ScrollBox1 をアクティブに(クリックして選択)して、ScrollBox1の上にImageを1つ置く。Imageのプロパティはデフォルト設定のまま。
(5)RadioGroup1のCaptionプロパティを「画像の大きさ」に変更し、Columns(表示するオプションボタンの列数)プロパティに「2」を設定。

RadioGroup1のプロパティを設定

さらに、RadioGroup1のItemIndexプロパティを「0」(第1番目のオプションボタンを選択した状態でプログラムが起動する)、Itemsプロパティには1行目に「リサイズ」、2行目に「オリジナル」を設定する。

先に、RadioGroup1のColumnsプロパティを「2」(列)に設定してあるので、1行目の「リサイズ」が1列目に、2行目の「オリジナル」が2列目に表示される。

RadioGroup1のプロパティを設定 ItemIndexを0、Itemsに「リサイズ」と「オリジナル」を準備する

(6)Formのどこでもよいので、OpenDialogとSaveDialogを1つずつ設置する。これらは非ビジュアルコンポーネントなので、下図のようにして非表示に設定することもできる(設置したことを忘れそうな場合は、非表示にしない方がよいと思う)。

非ビジュアルコンポーネントを非表示に設定

実行(F9)すると C:\ XXX \ プロジェクトファイルのあるフォルダ \Win32\ 内にDebugフォルダが作成されるので、ここに画像を保存する「Data」フォルダを作成する。Dataフォルダ内に、任意の画像データを準備する。

例:画像はJPEG形式で、名前は「Sample.jpg」の場合

Debugフォルダ内にDataフォルダを作成し、画像を準備
Sample.jpg

1.読み込み

読み込みボタン(Button1)をダブルクリックして、画像ファイルの読み込み手続きを記述する。画面は次のようになる。

procedure TForm1.Button1Click(Sender: TObject);
begin

end;

まず、必要な変数をvar宣言する。このプログラムでは、画像の読み書きにGDI+を利用する。他の方法に比べ、超高速な画像の読み書きが可能である。

GDI(Graphics Device Interface)は、Windowsで画面のグラフィック処理やプリンターへの出力を行う技術で、WindowsXPからその後継となるGDI+が登場し、GDIで不可能であったJPEGやPNGといった画像形式にも対応し、これにより高レベルの2Dグラフィック処理が可能になった。

GDI+を使用するために必要な変数、及び、処理時間計測に必要な変数を、それぞれ次のように宣言する。

procedure TForm1.Button1Click(Sender: TObject);
var
  //GDI+を利用する
  graphics:TGPGraphics;
  bmp:TGPBitmap;
  W,H:integer;
  //処理時間を計測
  timerStart:DWORD;
  timerEnd:DWORD;
  s:string;
begin

また、GDI+を利用するには、Winapi.GDIPAPI と Winapi.GDIPOBJ をusesに宣言する。また、画像データの保存時に必要なGUIDを自動取得するGetEncoderClsid関数を使用するので、Winapi.GDIPUTILも合わせて宣言する。

さらに、
処理時間計測用に Winapi.MMSystem 、
JPEG形式の画像を使うために Vcl.Imaging.jpeg 、
TPath.GetExtension関数でファイル(Path)名から拡張子を取得するために必要な System.IOUtils もここで同時に uses に追加しておく。

implementation

uses
  Vcl.Imaging.jpeg, Winapi.MMSystem,
  Winapi.GDIPAPI, Winapi.GDIPOBJ, Winapi.GDIPUTIL,
  System.IOUtils;

{$R *.dfm}

読み込みボタン(Button1)をクリックした際に実行される処理を、以下の通り記述する。アイコンの画像以外の、通常使用する画像ファイル(BMP, JPEG, GIF, PNG, TIFF, WMF, EMF 形式)を読み込み可能である。

procedure TForm1.Button1Click(Sender: TObject);
var
  //GDI+を利用する
  graphics:TGPGraphics;
  bmp:TGPBitmap;
  W,H:integer;
  //処理時間を計測
  timerStart:DWORD;
  timerEnd:DWORD;
  s:string;
begin

  //OpenDialogのプロパティはExecuteする前に設定
  With OpenDialog1 do begin
    //表示するファイルの種類を設定
    Filter:='画像ファイル|*.bmp;*.jpg;*.gif;*.png;*.tif;*.emf;*.wmf' +
    '|*.bmp|*.bmp' + '|*.jpg|*.jpg' + '|*.gif|*.gif' + '|*.png|*.png' +
    '|*.tif|*.tif' + '|*.emf|*.emf' + '|*.wmf|*.wmf';
    //データの読込先フォルダを指定
    InitialDir:=ExtractFilePath(Application.ExeName)+'Data';
  end;

  if not OpenDialog1.Execute then Exit; //キャンセルに対応

  //時間計測開始
  timerStart:=TimeGetTime;

  //オブジェクトを生成
  bmp:=TGPBitmap.Create(OpenDialog1.FileName);
  Image1.Picture.Bitmap.Width:=bmp.GetWidth;
  Image1.Picture.Bitmap.Height:=bmp.GetHeight;
  Image1.Picture.Bitmap.PixelFormat:=pf24bit;
  graphics:=TGPGraphics.Create(Image1.Picture.Bitmap.Canvas.Handle);

  try
    //イメージを表示
    graphics.DrawImage(bmp, 0, 0, bmp.GetWidth, bmp.GetHeight);

    //画像の大きさ
    case RadioGroup1.ItemIndex of
      0:begin
        //Image1にリサイズして表示
        Image1.Align:=alClient;
        W:=Image1.Width;
        H:=Image1.Height;
        Image1.Width:=W;
        Image1.Height:=H;
        //Imageに合わせて表示
        Image1.AutoSize:=False;
        //Imageのサイズに合わせて表示する
        Image1.Stretch:=True;
        //縦横の比率を変えずに Image のサイズに変更
        Image1.Proportional:=True;
      end;
      1:begin
        //オリジナルの大きさで表示
        Image1.Align:=alNone;
        Image1.AutoSize:=True;
        //Imageのサイズに合わせて表示する
        Image1.Stretch:=False;
        //縦横の比率を変えずに Image のサイズに変更
        Image1.Proportional:=False;
      end;
    end;

    //処理時間計測終了
    timerEnd:=TimeGetTime;

    //計算時間を表示
    s:='計算時間:'+(IntToStr(timerEnd-timerStart)+' ms');
    StatusBar1.SimpleText:=s;

  finally
    bmp.Free;
    graphics.Free;
  end;

end;

処理時間の計測結果をStatusBarに表示するために、FormのCreate時に、以下の設定を行っておく。

procedure TForm1.FormCreate(Sender: TObject);
begin
  //StatusBar1の設定(FalseだとStatusBarにテキストが表示されない)
  StatusBar1.SimplePanel:=True;
end;

ついでにFormが常に画面の中央に表示されるよう、次のコードをFormのOnShowイベントに記述する。

procedure TForm1.FormShow(Sender: TObject);
begin
  //Formを画面の中央に表示
  Left:=(Screen.Width-Width) div 2;
  Top:=(Screen.Height-Height) div 2;
end;

上書き保存(Ctrl+S)して、実行(F9)。Sample.jpgのファイルサイズは約5.72MBであるが、私のPC環境では最速137msで表示される。最後に旧来の画像読み込み方法も紹介するが、他の方法を使う気にならないくらいGDI+による読み込みはたいへん高速である。

2.書き出し

読み込み手続きの次は、書き出しの手続きを記述する。書き出しボタン(Button2)をダブルクリックして、書き出し手続きを新規に作成する。

procedure TForm1.Button2Click(Sender: TObject);
begin

end;

最初に手続き中で必要な変数をvar宣言する。

procedure TForm1.Button2Click(Sender: TObject);
var
  //usesにWinapi.GDIPAPI, Winapi.GDIPOBJが必要
  graphics:TGPGraphics;
  bmp:TGPBitmap;
  //GetEncoderClsid関数の利用とTGUIDを使用するには、
  //usesにWinapi.GDIPUTILが必要
  ImgGUID:TGUID;
  //処理時間を計測
  timerStart:DWORD;
  timerEnd:DWORD;
  s:string;
  dotExt, strExt:string; //拡張子を取得する
begin

書き出し処理のコードを記述する。

procedure TForm1.Button2Click(Sender: TObject);
var
  //usesにWinapi.GDIPAPI, Winapi.GDIPOBJが必要
  graphics:TGPGraphics;
  bmp:TGPBitmap;
  //GetEncoderClsid関数の利用とTGUIDを使用するには、
  //usesにWinapi.GDIPUTILが必要
  ImgGUID:TGUID;
  //処理時間を計測
  timerStart:DWORD;
  timerEnd:DWORD;
  s:string;
  dotExt, strExt:string; //拡張子を取得する
begin

  //OpenDialogのファイル名が空欄ならExit
  if OpenDialog1.FileName='' then
  begin
    ShowMessage('保存する画像がありません!');
    Exit;
  end;

  //SaveDialogのプロパティはExecuteする前に設定しておくこと
  With SaveDialog1 do begin
    //デフォルトのファイル名を設定
    FileName:='Test';
    //表示するファイルの種類を設定
    Filter:='画像ファイル|*.bmp;*.jpg;*.gif;*.png;*.tif' +
    '|*.bmp|*.bmp' + '|*.jpg|*.jpg' + '|*.gif|*.gif' + '|*.png|*.png' +
    '|*.tif|*.tif';
    //データの読込先フォルダを指定
    InitialDir:=ExtractFilePath(Application.ExeName)+'Data';
    //拡張子の指定がなかった場合に付加される拡張子を指定
    DefaultExt:='jpg';
    //上書き保存の確認の設定
    Options:=[ofOverWritePrompt];
  end;

  if not SaveDialog1.Execute then Exit; //キャンセルに対応

  //時間計測開始
  timerStart:=TimeGetTime;

  bmp:=TGPBitmap.Create(OpenDialog1.FileName);
  //どちらの指定でも保存可能
  //Graphics:=TGPGraphics.Create(Image1.Canvas.Handle);
  Graphics:=TGPGraphics.Create(Image1.Picture.Bitmap.Canvas.Handle);
  try

    //90°回転
    //bmp.RotateFlip(Rotate90FlipNone);

    //画像を取得
    Graphics.DrawImage(bmp,0,0);

    //拡張子を小文字に変換して取得(.XXX形式:Dotが付いている)
    dotExt:=LowerCase(TPath.GetExtension(SaveDialog1.FileName));
    //JPEG & TIFFに対応する
    if dotExt='.jpg' then begin
      strExt:='jpeg';
    end else begin
      if dotExt='.tif' then begin
        strExt:='tiff';
      end else begin
        strExt:=StringReplace(dotExt, '.', '', [rfReplaceAll, rfIgnoreCase]);
      end;
    end;

    //指定された拡張子を付けて保存
    if GetEncoderClsid('image/'+strExt, ImgGUID) >= 0 then
    begin
      bmp.Save(ChangeFileExt(SaveDialog1.FileName, dotExt), ImgGUID);
    end;

    //処理時間計測終了
    timerEnd:=TimeGetTime;
    //計算時間を表示
    s:='計算時間:'+(IntToStr(timerEnd-timerStart)+' ms');
    StatusBar1.SimpleText:=s;

  finally
    Graphics.Free;
    bmp.Free;
  end;

end;

上書き保存(Ctrl+S)して、実行(F9)。読み込みボタンをクリックして画像を読み込んでから、書き出しボタンをクリックして、保存形式(拡張子)を選択、画像ファイル名はデフォルトで「Test」が設定されているので、そのままでよければSaveDialogの「保存」ボタンをクリックして、画像をDataフォルダに保存する。

Sample.jpgを読み込んでから画面を最大化したところ
(画像の大きさも画面に追随して大きくなる)

GDI+による画像ファイルの読み込みと書き出し処理について、次のWebサイトにたいへん詳しく紹介されています。GDI+を学びたい方は必見です。

Delphiを使って、誰かの役に立つプログラムを作成している方なら、誰しもMr.XRAYさんのWebサイトに一度はお世話になっているのではないでしょうか? それくらい貴重な情報が数多く紹介されています。 私自身、これまでに何度助けていただいたことか・・・。Delphiに関する貴重な情報をずっと提供し続けてくださっているMr.XRAYさんに心から感謝申し上げます。

GDI+ 関係サンプル G040_各種の画像形式の表示と変換

URL:http://mrxray.on.coocan.jp/Delphi/GDIPlusSamples/G040_GDIPlus_SomeImageTypes.htm

参考:旧来の画像の呼び出し方法

最も普通に使われてきた(と思われる)JPEG画像の呼び出しの例。GDI+の利用でSample画像は150ms前後で読み込めていたが、こちらの方法では読み込みに1秒近くかかる。

さらに、読み込んだ画像に対して、何か作業を行う場合(例:矩形選択等)は、下のコード内でコメント化してある部分( Image1.Picture.Bitmap.Assign(Jpg); )をアクティブにして、BitmapにAssignしないと、画像加工実行時にエラーになることにも注意。

Image1.Picture.Assign(jpg); として読み込んで、
画像を加工しようとした場合はエラーになる。
implementation

uses
  Vcl.Imaging.jpeg, Winapi.MMSystem;

  //Vcl.Imaging.jpegはJPEGファイルを扱うために必要
  //Winapi.MMSystemは計算処理時間の表示用

{$R *.dfm}



procedure TForm1.Button1Click(Sender: TObject);
var
  Jpg:TJPEGImage;
  //処理時間を計測
  TStart: DWORD;
  TEnd: DWORD;
  s:string;
begin

  //画像を消去する
  Image1.Picture:=nil;

  //OpenDialogのプロパティはExecuteする前に設定
  With OpenDialog1 do begin
    //表示するファイルの種類を設定
    Filter:='JPEG Files (*.jpg, *.jpeg)|*.jpg;*.jpeg';
    //データの読込先フォルダを指定
    InitialDir:=ExtractFilePath(Application.ExeName)+'Data';
  end;

  if not OpenDialog1.Execute then Exit; //キャンセルに対応

  //オブジェクトを生成
  jpg := TJPEGImage.Create;

  try

    //時間計測開始
    TStart:=TimeGetTime;

    //ファイルから読み込み
    jpg.LoadFromFile(OpenDialog1.FileName);
    //Image1に(メモリから)表示
    Image1.Picture.Assign(jpg);
    //Image1.Picture.Bitmap.Assign(Jpg);

    //処理時間計測終了
    TEnd:=TimeGetTime;

    //計算時間を表示
    s:='計算時間:'+(IntToStr(TEnd-TStart)+' ms');
    StatusBar1.SimpleText:=s;

  finally
    //オブジェクトを破棄
    jpg.Free;
  end;

end;

3.まとめ

これまでいろいろな形式の画像を読み込んだり、書き込んだりする場合は、それぞれの画像形式に合わせてプログラムコードを用意していたが、GDI+を利用すればあらゆる場合に対応できることがわかった。かつ、処理速度も超高速で快適に使用できる。

4.お願いとお断り

このサイトの内容を利用される場合は、自己責任でお願いします。記載した内容を利用した結果、利用者および第三者に損害が発生したとしても、このサイトの管理者は一切責任を負えません。予め、ご了承ください。

【関連記事】