Delphiによる画像のJpeg保存

 Delphiで画像を保存する場合、種々のファイル形式で保存できますが、圧縮レベルの指定できる、Jpeg形式の保存について取り上げてみました。
単にファイル形式を変換するだけであれば、Windowsに標準でインストールされている、ペイントブラシを使用すれば簡単に変換が可能です。
 最近では、データーを保存する媒体の容量が非常に大きくなっているし、WEBの速度も上がっているので、大きな圧縮率は必要ないのかもしれませんが、それでも、画像ファイルのサイズを小さくすれば、それなりの効果がありますので、Delphiでのプログラムで圧縮レベルを指定してJpeg形式での保存を検討してみました。

 Delphiには、JPEGunit、GDI+、WinCodec の三種類の方法が組み込まれています。
Windowsのバージョンと共に、組み込まれているルーチンが増えていった為です。
プログラムの進化とともに、新しいプログラムが追加されますが、古いプログラムとの互換性を持たせるため、古いプログラムの削除ができないので、WindowsOSはどんどん容量が大きくなりました。
当然、Delphiも新しいプログラムの為の機能が追加されていますが、古い機能の削除ができない為、必要以上に容量が大きくなっています。

 圧縮レベルの設定できる方法で一番簡単なのは、TJPEGImageを使用するのが一番簡単です。
Wincodec、GDI+を使用する場合は、少々面倒です。
 GDI+の場合、プログラム例をインターネットで探すと、同時に複数の変換ができる様なプログラム例が見つかりますが、実際にプログラムを組んでみると、画像の回転は、Jpegファイルから、Jpegファイルに保存するときのみ有効で、圧縮ベルの変換は無視されます。
どの組み合わせの変換なら同時にできるのかは確認していません。
圧縮レベルを指定してJpegファイルに保存する場合は、単独の変換で指定する必要があるようです。
 Wincodecを使用する場合は、画像情報の付加が必要となります。

 上図が、圧縮レベルを変えて保存した場合の例です。
圧縮レベル30の場合は、元の画像と殆ど変わらないので省略しています。
圧縮率が大きくなると、四角のモザイクが強くなりますが、これは、8×8の領域毎に圧縮を行っている為です。

元画像 ビットマップ 256KBのファイルがJpeg圧縮すると、どれ位のファイルサイズになるかです。
画像によって、圧縮されたファイルサイズが変わりますので参考です。
圧縮レベル   ファイルサイズ
  30       5.36KB
    20       4.28KB
    10       2.89KB
   5       1.97KB

ペイントブラシの場合 23.7KBで、圧縮レベル 94 程度ですが、それでも元の画像 256KB に対して10分の1の程度のファイルサイズになります。
元の画像に対して、全く遜色ない画像となっています。

 プログラム

  プログラムの中にTSavePictureDialogはありませんが、圧縮画像表示の為に、メモリーストリームを使用しているので、ファイル名を付けて書き出せば簡単にファイルに保存できます。
メモリーストリームから保存する場合は、画像表示後に保存しないと、画像が正常に表示されない場合があります。

unit JpegMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.ExtDlgs;

type


  TForm1 = class(TForm)
    Button1: TButton;
    OpenPictureDialog1: TOpenPictureDialog;
    LabeledEdit1: TLabeledEdit;
    ScrollBox1: TScrollBox;
    ScrollBox2: TScrollBox;
    Image2: TImage;
    Image1: TImage;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    LabeledEdit2: TLabeledEdit;
    LabeledEdit3: TLabeledEdit;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses  WinCodec, ActiveX,
      Vcl.imaging.Jpeg,
      GDIPOBJ, GDIPAPI, GDIPUTIL;

{$R *.dfm}

//var
//  FImageFilePath : string;

// uses  WinCodec, ActiveX によります
procedure TForm1.Button1Click(Sender: TObject);
var
  WICImage        : TWICImage;
  Encoder         : IWICBitmapEncoder;
  Frame           : IWICBitmapFrameEncode;
  Factory         : IWICImagingFactory;
  LStream         : IWICStream;
  Palette         : IWICPalette;
  LStreamAdapter  : TStreamAdapter;
  PixelFormat     : TGUID;
  Memstm          : TMemoryStream;
  Option          : TPropBag2;
  Props           : IPropertyBag2;
  Value           : TPropVariant;
  rtn             : Integer;
  fltVal          : Extended;
begin
  val(LabeledEdit1.Text, fltVal, rtn);
  if rtn <> 0 then begin
    application.MessageBox('圧縮レベルの値に間違いがあります。', '注意', 0);
    exit;
  end;
  if (fltVal <= 0) or (fltVal > 1) then begin
    application.MessageBox('圧縮レベルの値は 0 < X <= 1 にして下さい。', '注意', 0);
    exit;
  end;
// 元画像と関連付け
//  Image2.Picture.Assign(nil);                 // Image2 関連付けの解除
  Image2.Picture.Bitmap.Height := Image1.Picture.Bitmap.Height;
  Image2.Picture.Bitmap.Width  := Image1.Picture.Bitmap.Width;

  WICImage := TWICImage.Create;
  WICImage.Assign(Image1.Picture.Bitmap);
// 出力先の作成(TMemoryStream)
  Memstm := TMemoryStream.Create;
  Memstm.Clear;
  LStreamAdapter := TStreamAdapter.Create(Memstm);
  IUnknown(LStreamAdapter)._AddRef;
  try
// CLSID_WICImagingFactory  ファクトリの生成
    if Failed(CoCreateInstance(CLSID_WICImagingFactory, nil,
        CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IUnknown, Factory)) then exit;
    Factory.CreateStream(LStream);
    LStream.InitializeFromIStream(LStreamAdapter);
// EncoderをJpeg形式用として作成
    Factory.CreateEncoder(GUID_ContainerFormatJpeg, guid_null, Encoder);
    Encoder.Initialize(LStream, WICBitmapEncoderNoCache);
// フレームを作成
    Encoder.CreateNewFrame(Frame, Props);
// jpeg Qualityを指定する
    Option.pstrName := 'ImageQuality';
    Option.dwType := 1;         // data
    Option.dwHint := 0;         // 無し
    Value.vt := VT_R4;
    Value.fltVal := fltVal;     // 0.~1
    rtn := Props.Write(1, @Option, @Value);
    if rtn = 0 then begin
//  画像データに必要な情報を付加
      Frame.Initialize(Props);
      WICImage.Handle.GetPixelFormat(PixelFormat);
      Frame.SetPixelFormat(PixelFormat);
      Frame.SetSize(WICImage.Width, WICImage.Height);
      Factory.CreatePalette(Palette);
      WICImage.Handle.CopyPalette(Palette);
      Frame.SetPalette(Palette);
      Frame.WriteSource(WICImage.Handle, nil);
      Frame.Commit;
      Encoder.Commit;
// MemoryStreamから読み込み 画像の表示
      Memstm.Position := 0;                         // 読み出し位置ストリーム先頭にセット
      WICImage.LoadFromStream(Memstm);
//      Image2.Picture.Assign(WICImage);
      Image2.Canvas.Draw(0, 0, WICImage);           // Jpeg画像の表示
//  ファイルとして保存
//      Memstm.Position := 0;
//      Memstm.SaveToFile('D:\test.jpg');
    end;
  finally
    Memstm.Free;
    IUnknown(LStreamAdapter)._Release;
    if Assigned(WICImage) then FreeAndNil(WICImage);
  end;
end;

// Uses JPEG によります
procedure TForm1.Button3Click(Sender: TObject);
var
  jp              : TJPEGImage;
  LMemoryStream   : TMemoryStream;
  Ch              : integer;
  Vale            : double;
begin
  val(LabeledEdit2.Text, Vale, ch);
  if ch <> 0 then begin
    application.MessageBox('圧縮レベルの値に間違いがあります。', '注意', 0);
    exit;
  end;
  if (Vale <= 0) or (Vale > 100) then begin
    application.MessageBox('圧縮レベルの値は 0 < X <= 100 にして下さい。', '注意', 0);
    exit;
  end;
//  Image2.Picture.Assign(nil);                   // Image2 関連付けの解除
  Image2.Picture.Bitmap.Height := Image1.Picture.Bitmap.Height;
  Image2.Picture.Bitmap.Width  := Image1.Picture.Bitmap.Width;
  jp :=TJPEGImage.Create;
  LMemoryStream := TMemoryStream.Create;
  LMemoryStream.Clear;
  try
    with jp do begin
      Assign(Image1.Picture.Bitmap);            // BitmapをJPEGに関連付け
      CompressionQuality := round(Vale);        // 圧縮率 1~100
      DIBNeeded;                                // JPEGImageから内部にビットマップを作成
      Compress;                                 // 内部ビットマップからJPEGImageを作成
      SaveToStream(LMemoryStream);              // ストリームに保存
//      SaveToFile('D:\Test.jpg' );
      LMemoryStream.Position := 0;              // 読み出し位置ストリーム先頭にセット
      LoadFromStream(LMemoryStream);
    end;
//    Image2.Picture.Assign(JP);
    Image2.Canvas.Draw(0, 0, jp);               // Jpeg画像の表示
//    LMemoryStream.Position := 0;
//    LMemoryStream.SaveToFile('D:\test.jpg');    // ファイルへ保存
  finally
    jp.free;
    LMemoryStream.Free;
  end;
end;

{
type
  DEncoderParameter = record
    Guid : TGUID;           // GUID of the parameter
    NumberOfValues : ULONG; // Number of the parameter values
    Type_ : ULONG;          // Value type, like ValueTypeLONG etc.
    Value : Pointer;        // A pointer to the parameter values
  end;

  TDEncoderParameter = DEncoderParameter;
  DEncoderParameters = record
    Count : UINT;           // Number of parameters in this structure
    Parameter : array[0..1] of TDEncoderParameter; // Parameter values
  end;

  TDEncoderParameters = DEncoderParameters;
//  PDEncoderParameters = ^TDEncoderParameters;
}

// uses GDIPOBJ, GDIPAPI, GDIPUTILによります
procedure TForm1.Button4Click(Sender: TObject);
var
  LObjWidth       : Integer;
  LObjHeight      : Integer;
  LGPImage        : TGPImage;
  LGPGraphic      : TGPGraphics;
  LEncoderParams  : TEncoderParameters;
//  LEncoderParams  : TDEncoderParameters;
  LMemoryStream, GMemoryStream  : TMemoryStream;
  LIStrema        : IStream;
  LImageGUID      : TGUID;
  FJPEGQuality    : Cardinal;
//  transformation  : TEncoderValue;
  Ch              : integer;
  Vale            : double;
begin
  val(LabeledEdit3.Text, Vale, ch);
  if ch <> 0 then begin
    application.MessageBox('圧縮レベルの値に間違いがあります。', '注意', 0);
    exit;
  end;
  if (Vale <= 0) or (Vale > 100) then begin
    application.MessageBox('圧縮レベルの値は 0 < X <= 100 にして下さい。', '注意', 0);
    exit;
  end;
  Image2.Picture.Assign(nil);                   // Image2 関連付けの解除 解除しないと描画されません
//  transformation := EncoderValueTransformRotate90;
  FJPEGQuality := round(Vale);
  // 画像ファイルからTGPImageのインスタンスを生成.幅と高さを取得
//  LGPImage    := TGPImage.Create(FImageFilePath);
  // Image1の画像をメモリストリームに保存
  GMemoryStream := TMemoryStream.Create;
  Image1.Picture.Bitmap.SaveToStream(GMemoryStream);
  // メモリストリームに保存したImage1画像からTGPImageのインスタンスを生成
  LIStrema := TStreamAdapter.Create(GMemoryStream);
  LGPImage := TGPBitmap.Create(LIStrema);
  LObjWidth   := LGPImage.GetWidth;
  LObjHeight  := LGPImage.GetHeight;
  // image2ビットマップサイズ設定
  image2.Picture.Bitmap.SetSize(LObjWidth, LObjHeight);
  // LGPGraphicの描画先をimage2に設定
  LGPGraphic := TGPGraphics.Create(image2.Canvas.Handle);
  // JPEGの品質関係の値をTEncoderParametersレコード型の各メンバーにセット
  with LEncoderParams do begin
    Count := 1;
//    Count := 2;
    Parameter[0].Guid           := EncoderQuality;
    Parameter[0].Type_          := EncoderParameterValueTypeLong;
    Parameter[0].NumberOfValues := 1;
    Parameter[0].Value          := @FJPEGQuality;
//    Parameter[1].Guid           := EncoderTransformation;
//    Parameter[1].Type_          := EncoderParameterValueTypeLong;
//    Parameter[1].NumberOfValues := 1;
//    Parameter[1].Value          := @transformation;
  end;
  // Jpeg用メモリストリームを生成
  LMemoryStream := TMemoryStream.Create;
  try
    // メモリストリームに圧縮保存
    GetEncoderClsid('image/jpeg', LImageGUID);
    LGPImage.Save(TStreamAdapter.Create(LMemoryStream), LImageGUID, @LEncoderParams);
    // ファイルに保存
//    LGPImage.Save('D:\Test.jpg', LImageGUID, @LEncoderParams);
    LGPImage.Free;                                                // LGPImage解放
    // メモリストリームに保存したJPEG画像からTGPImageのインスタンスを生成
    LIStrema := TStreamAdapter.Create(LMemoryStream);
    LGPImage := TGPBitmap.Create(LIStrema);
    LGPGraphic.DrawImage(LGPImage, 0, 0, LObjWidth, LObjHeight);  // Jpeg画像の表示
//    LMemoryStream.Position := 0;
//    LMemoryStream.SaveToFile('D:\test.jpg');    // ファイルへ保存
  finally
    LMemoryStream.Free;
    GMemoryStream.Free;
  end;
  if Assigned(LGPGraphic) then FreeAndNil(LGPGraphic);
  if Assigned(LGPImage) then FreeAndNil(LGPImage);
//  LGPGraphic.Free;
//  LGPImage.Free;
end;

// ファイルのオープン
procedure TForm1.Button2Click(Sender: TObject);
var
  WICImage        : TWICImage;
begin
// イメージ読み込み
  OpenPictureDialog1.Filter := 'イメージファイル|*.jpg;*.jpeg;*.tif;*.tiff;*.png;*.gif;*.bmp;*.wdp';
  if OpenPictureDialog1.Execute then begin                  // ファイルが指定されたら
    WICImage := TWICImage.Create;
    WICImage.LoadFromFile(OpenPictureDialog1.FileName);
  end
  else
    exit;                                                   // ファイルが選択されなかったら終了
// 入力画像の表示
  Image1.Picture.Bitmap.SetSize(WICImage.Width, WICImage.Height);
  Image1.Canvas.Draw(0, 0, WICImage);
//  Image1.Picture.Assign(WICImage);                        // Assign だとImage1.Picture.Bitmapd 使用不可

//  FImageFilePath := OpenPictureDialog1.FileName;
  Button1.Enabled := True;
  Button3.Enabled := True;
  Button4.Enabled := True;
  if Assigned(WICImage) then FreeAndNil(WICImage);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Button1.Enabled := False;
  Button3.Enabled := False;
  Button4.Enabled := False;
end;


end.

    download Delphi_jpeg.zip

画像処理一覧へ戻る

      最初に戻る