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.