|
Delphi Programming / Object Pascal
[掲載 2017年08月01日] [更新 2017年08月06日] GDI+ 関係サンプル |
GDI+ 関係サンプル
G122_ぼかし画像の作成と文字列や画像の Drop Shadow |
動作確認等 |
Windows 7 U64(SP1) + Delphi XE(UP1) Pro |
|
G122_GDIPlus_DropShadow.zip [2,541 KB] 2017年08月06日版 (EXE 同梱) |
ぼかし画像の作成と文字列や画像の影 ( Drop Shadow ) の描画 |
写真等の画像全全体をぼかす,つまりぼかし画像を作成するサンプルです.
以下のページでは Delphi 2009 GDI+ Library
を使用していますが,本ページでは添付のユニット内の関数類を使用しています.このユニットにはガウスぼかし (ガウシアンブラー・Gaussian Blur) とボックスぼかし (ボックスブラー・Box Blur) 用の関数類があります.
|
これらのぼかし用の関数を使用すると,Drop Shadow と呼ばれている影付きの文字列や画像を描画することができます.
まず,ぼかし画像を作成します.この時,画像の矩形領域よりも少し広い範囲を含めてぼかし画像を作成します.拡張した領域は何も描画されていない透明なピクセルです.この状態でぼかし画像を作井すると,画像の周辺がぼかされた画像になります.
このぼかし画像を単一の色にしたマスク画像にします.一般的には「影」の部分ですから黒にすることが多いようです.この画像に元の画像を重ね描画すると影付きの画像にすることができます.
これとは逆に,まず画像のマスク画像を作成して,それをぼかし画像にする方法もあります.
マスク画像の作成については以下にも記事があります.
|
本ページのサンプルでは,ビットマップのアルファチャンネルの処理とアイコン画像の拡大表示に,以下の記事に掲載している関数類を使用しています.
|
01_Gaussian Blur または Box Blur を使用したぼかし画像の作成 |
写真等の画像を Gaussian Blur または Box Blur を使用してぼかし画像にするサンプルです.
|
|
図1
設計時の画面
- TImage.AutoSize プロパティの値は True
|
|
図2
Gaussian Blur によるぼか画像の例
|
リスト1
Gaussian Blur または Box Blur を使用したぼかし画像の作成
アルファチャンネルの処理が必要なければ PNG 画像,ビットマップ画像でもテスト可能 |
uses
plBlurGDIplus;
{$R *.DFM}
//=============================================================================
// フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
SpinEdit1.Value := 5;
end;
//=============================================================================
// 矩形領域内だけをぼかす
// 写真等のぼかし画像作成用
//
// TImageのAutoSizeプロパティの値をTrueにしている
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LGPGraphic : TGPGraphics;
LGPBitmap : TGPBitmap;
LGPBlurBitmap : TGPBitmap;
LStream : TMemoryStream;
LImgWidth : Integer;
LImgHeight : Integer;
LImgFilePath : string;
LRadius : Integer;
begin
LRadius := SpinEdit1.Value;
LImgFilePath := ExpandFileName('PenguinsB.bmp');
LStream := TMemoryStream.Create;
try
//画像をメモリストリームに読み込んで処理
LStream.LoadFromFile(LImgFilePath);
//ストリームからTGPBitmapを生成
LGPBitmap := TGPBitmap.Create(TStreamAdapter.Create(LStream, soReference));
LImgWidth := LGPBitmap.GetWidth;
LImgHeight := LGPBitmap.GetHeight;
//ぼかし画像を作成
if CheckBox1.Checked then begin
LGPBlurBitmap := BoxBlur(LGPBitmap, LRadius);
end else begin
LGPBlurBitmap := GaussianBlur(LGPBitmap, LRadius, 1);
end;
//元の画像をTImageに描画
Image1.Picture.Assign(nil);
Image1.Left := 10;
Image1.Picture.Bitmap.SetSize(LImgWidth + 20, LImgHeight + 20);
LGPGraphic := TGPGraphics.Create(Image1.Canvas.Handle);
LGPGraphic.DrawImage(LGPBitmap, 15, 15);
FreeAndNil(LGPGraphic);
//作成したぼかし画像をTImageに描画
Image2.Picture.Assign(nil);
Image2.Left := 10;
Image2.Top := Image1.Top + Image1.Height + 1;
Image2.Picture.Bitmap.SetSize(LImgWidth + 20, LImgHeight + 20);
LGPGraphic := TGPGraphics.Create(Image2.Canvas.Handle);
LGPGraphic.DrawImage(LGPBlurBitmap, 15, 15);
finally
FreeAndNil(LGPGraphic);
FreeAndNil(LGPBitmap);
FreeAndNil(LGPBlurBitmap);
FreeAndNil(LStream);
end;
end;
//=============================================================================
// CheckBox1のOnClickイベント処理
// Gaussian BlurとBox Blur処理の切り換え
//=============================================================================
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
Button1Click(nil);
end;
//=============================================================================
// SpinEdit1のOnChangeイベント処理
// ぼかし計算の範囲を変更
//=============================================================================
procedure TForm1.SpinEdit1Change(Sender: TObject);
begin
if SpinEdit1.Value < 0 then begin
SpinEdit1.Value := 0;
end else begin
Button1Click(nil);
end;
end;
02_画像のぼかし用関数を使用した影付き文字列の描画 |
影付き文字列の描画例です.
まず文字列をぼかし画像にします (文字列も画像として描画されます).そのぼかし画像のマスク画像を作成しています.ぼかし画像,つまり影の部分は半透明になっています.その画像に元の画像を重ね描画しています.これ以降も同じ手法で作成しています. |
|
図3
影付き文字の例
- 一番上が元の文字列
- 次がぼかしたマスク画像
- その画像に元の画像を重ね描画したのが一番下のの画像
|
下図は,Drop Shadow 作成用の関数の最後の引数の値を clRed にした場合です. |
//ぼかし画像を作成(実際にはマスク画像のぼかし画像)
LGPBlurBitmap := CreateGaussianBlurShadow(LGPBitmap, LRadius, 0.8, clRed);
|
図4
マスク画像の色を clRed に変更 |
リスト2
画像のぼかし用関数を使用した影付き ( Drop Shadow 付き ) 文字列の描画 |
uses
plBlurGDIplus;
{$R *.DFM}
//=============================================================================
// 影付き文字列の描画
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LGPGraphic : TGPGraphics;
LGPBitmap : TGPBitmap;
LGPBlurBitmap : TGPBitmap;
LGPDstBitmap : TGPBitmap;
LGPFont : TGPFont;
LGPBrush : TGPSolidBrush;
LFontSize : Single;
LGPColor : TGPColor;
LRadius : Integer;
begin
LFontSize := 60.0;
LGPColor := MakeColor($FF, 0, 0, 0);
LRadius := 2;
//文字列を描画するTGpBitmapを生成
LGPBitmap := TGPBitmap.Create(Image1.Width, Image1.Height);
LGPGraphic := TGPGraphics.Create(LGPBitmap);
try
//フォントと文字列を描画するブラシを設定
LGPFont := TGPFont.Create('Tahoma', LFontSize, FontStyleBold, UnitPixel);
LGPBrush := TGPSolidBrush.Create(LGPColor);
//その設定でTGPBitmapに文字列を描画
LGPGraphic.DrawString('Delphi', -1, LGPFont, MakePoint(0.0, 0.0), LGPBrush);
//ぼかし画像を作成(実際にはぼかし画像のマスク画像)
if CheckBox1.Checked then begin
LGPBlurBitmap := CreateBoxBlurShadow(LGPBitmap, LRadius, 0.5, clBlack);
end else begin
LGPBlurBitmap := CreateGaussianBlurShadow(LGPBitmap, LRadius, 0.5, clBlack);
end;
//Image1に元の画像を,Image2にぼかし画像を描画
DrawGPBitmapToTImage(Image1, LGPBitmap);
DrawGPBitmapToTImage(Image2, LGPBlurBitmap);
FreeAndNil(LGPGraphic);
//結果用のTGPBitmapを生成して,そこにぼかし画像と元の加増を重ね描画
LGPDstBitmap := TGPBitmap.Create(Image1.Width, Image1.Height);
LGPGraphic := TGPGraphics.Create(LGPDstBitmap);
LGPGraphic.DrawImage(LGPBlurBitmap, -LRadius * 2 + 5, -LRadius * 2 + 5);
LGPGraphic.DrawImage(LGPBitmap, 0, 0);
//Image3に重ね描画した結果の画像を描画
DrawGPBitmapToTImage(Image3, LGPDstBitmap);
finally
FreeAndNil(LGPGraphic);
FreeAndNil(LGPBitmap);
FreeAndNil(LGPBlurBitmap);
FreeAndNil(LGPDstBitmap);
FreeAndNil(LGPFont);
FreeAndNil(LGPBrush);
end;
end;
//=============================================================================
// CheckBox1のOnClickイベント処理
// Gaussian BlurとBox Blur処理の切り換え
//=============================================================================
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
Button1Click(nil);
end;
//-----------------------------------------------------------------------------
// 引数で指定するTImageコントロールにTGPBitmapを描画
//-----------------------------------------------------------------------------
procedure TForm1.DrawGPBitmapToTImage(ATImage: TImage; AGPBitmap: TGPBitmap);
var
LGPGraphic : TGPGraphics;
begin
ATImage.Picture.Assign(nil);
LGPGraphic := TGPGraphics.Create(ATImage.Canvas.Handle);
LGPGraphic.DrawImage(AGPBitmap, 0, 0);
end;
03_画像に半透明の Drop Shadow を描画 |
画像の背景に影を描画する例です.テストに使用したのは 32 ビットのアルファチャンネル付きのビットマップ画像で,背景は透明色です. |
|
図5
実行結果
|
Drop Shadow (影) 画像作成用の関数の不透明度と色の指定を省略すると,ぼかし画像だけを作成することができます.下図はそのぼかし画像とマスク画像です. |
|
図6
ぼかし画像
- 画像の周囲のぼかし部分は半透明
- 描画開始位置は [ほかしの半径 X 2] ピクセル分右下からとなる
- 左は元の画像
|
|
図7
ぼかし画像をマスク画像にした画像
- 不透明度の値を 1.0 にして,色の指定を clBlack にしている
- マスク画像の周囲のぼかし部分は半透明
|
下図は,画像の周囲に半透明の影を描画した例です.マスク画像のぼかし画像を少し拡大リサイズしてみました. |
|
図8
画像の周囲に影を描画
|
以上は背景が透明色の画像でしたが,背景が透明でない画像の場合は下図のようになります.
合成した画像は元の画像サイズよりも大きくなることに注意する必要があります.この画像では,マスク画像の不透明度を 0.5 にしています. |
LGPBlurBitmap := CreateGaussianBlurShadow(LGPBitmap, 3, 0.5, clBlack);
|
図9
半透明の影
|
下図は不透明度を 1.0 にした場合です.
不透明度を 1.0 にしても必ずしもピクセルのアルファ値が $FF になるとは限りません.GDI+ は画像を 32 ビットのアルファチャンネル付きのビットマップとして処理します.ぼかし画像を作成する際に使用する拡張領域のピクセルは透明色として処理されます. |
LGPBlurBitmap := CreateGaussianBlurShadow(LGPBitmap, 3, 1.0, clBlack);
|
図10
不透明度を 1.0 にした場合の影
|
リスト3
画像に半透明の Drop Shadow を描画 |
uses
plBlurGDIplus;
{$R *.DFM}
//=============================================================================
// 画像にDrop Shadow(半透明の影)を描画
//
// TImageのAutoSizeの値をTrueにしている
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LGPGraphic : TGPGraphics;
LGPBitmap : TGPBitmap;
LGPBlurBitmap : TGPBitmap;
LGPDstBitmap : TGPBitmap;
LBitmap : TBitmap;
LStream : TMemoryStream;
LImgWidth : Integer;
LImgHeight : Integer;
LImgFilePath : string;
LRadius : Integer;
begin
LRadius := 3;
//操作対象のビットマップ画像ファイル名
LImgFilePath := ExpandFileName('503_Transe.bmp');
LBitmap := TBitmap.Create;
LStream := TMemoryStream.Create;
try
//ビットマップ画像をメモリストリームに読み込んで処理
LStream.LoadFromFile(LImgFilePath);
LStream.Position :=0;
LBitmap.LoadFromStream(LStream);
//メモリストリームからTGPBitmapを生成
LGPBitmap := TGPBitmap.Create(TStreamAdapter.Create(LStream, soReference));
if LBitmap.PixelFormat = pf32bit then begin
//TGPBitmapでTBitmapのアルファチャンネルを扱えるようにする
SetAlphaValueFromTBitmap(LGPBitmap, LBitmap);
end;
LImgWidth := LGPBitmap.GetWidth;
LImgHeight := LGPBitmap.GetHeight;
//ぼかし画像を作成(実際にはぼかし画像のマスク画像)
if CheckBox1.Checked then begin
LGPBlurBitmap := CreateBoxBlurShadow(LGPBitmap, LRadius, 0.5, clBlack);
end else begin
LGPBlurBitmap := CreateGaussianBlurShadow(LGPBitmap, LRadius, 0.5, clBlack);
end;
//結果用のTGPBitmapを生成して,そこにぼかし画像と元の加増を重ね描画
LGPDstBitmap := TGPBitmap.Create(LImgWidth + 20, LImgHeight + 20);
LGPGraphic := TGPGraphics.Create(LGPDstBitmap);
LGPGraphic.DrawImage(LGPBlurBitmap, -LRadius * 2 + 5, -LRadius * 2 + 5);
LGPGraphic.DrawImage(LGPBitmap, 0, 0);
FreeAndNil(LGPGraphic);
//Image1に元の画像を描画
Image1.Picture.Assign(nil);
Image1.Picture.Bitmap.SetSize(LImgWidth + 20, LImgHeight + 20);
LGPGraphic := TGPGraphics.Create(Image1.Canvas.Handle);
LGPGraphic.DrawImage(LGPBitmap, 0, 0);
FreeAndNil(LGPGraphic);
//Image2に重ね描画した結果の画像を描画
Image2.Picture.Assign(nil);
Image2.Left := Image1.Left + Image1.Width + 10;
Image2.Picture.Bitmap.SetSize(LImgWidth + 20, LImgHeight + 20);
LGPGraphic := TGPGraphics.Create(Image2.Canvas.Handle);
LGPGraphic.DrawImage(LGPDstBitmap, 0, 0);
finally
FreeAndNil(LGPGraphic);
FreeAndNil(LBitmap);
FreeAndNil(LGPBitmap);
FreeAndNil(LGPBlurBitmap);
FreeAndNil(LGPDstBitmap);
FreeAndNil(LStream);
end;
end;
//=============================================================================
// CheckBox1のOnClickイベント処理
// Gaussian BlurとBox Blur処理の切り換え
//=============================================================================
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
Button1Click(nil);
end;
04_アイコン画像に半透明の Drop Shadow を描画 |
アイコン画像に半透明の影を描画する例です.上部の画像が実サイズの画像です.テストに使用した画像は PNG 画像ファイルです.下の左側はそれの拡大表示で,右が実行結果の拡大表示画像です.
本サンプルは実行結果の画像を PNG 画像で保存するようにしています.別名保存です. |
|
図11
実行結果 |
リスト4
アイコン画像に半透明の Drop Shadow を描画 |
uses
plBlurGDIplus;
{$R *.DFM}
//=============================================================================
// アイコン画像にDrop Shadow(半透明の影)を描画
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LGPGraphic : TGPGraphics;
LGPBitmap : TGPBitmap;
LGPBlurBitmap : TGPBitmap;
LGPDstBitmap : TGPBitmap;
LStream : TMemoryStream;
LImgWidth : Integer;
LImgHeight : Integer;
LImgFilePath : string;
LSaveFilePath : string;
LImgGUID : TGUID;
begin
//操作対象のビットマップ画像ファイル名
LImgFilePath := ExpandFileName('livejournal-old.png');
LStream := TMemoryStream.Create;
try
//画像をメモリストリームに読み込んで処理
LStream.LoadFromFile(LImgFilePath);
//メモリストリームからTGPBitmapを生成
LGPBitmap := TGPBitmap.Create(TStreamAdapter.Create(LStream, soReference));
LImgWidth := LGPBitmap.GetWidth;
LImgHeight := LGPBitmap.GetHeight;
//ぼかし画像を作成(実際にはマスク画像のぼかし画像)
if CheckBox1.Checked then begin
LGPBlurBitmap := CreateBoxBlurShadow(LGPBitmap, 1, 0.45, clBlack);
end else begin
LGPBlurBitmap := CreateGaussianBlurShadow(LGPBitmap, 1, 0.45, clBlack);
end;
//結果用のTGPBitmapを生成して,そこにぼかし画像と元の加増を重ね描画
LGPDstBitmap := TGPBitmap.Create(LImgWidth, LImgHeight);
LGPGraphic := TGPGraphics.Create(LGPDstBitmap);
LGPGraphic.DrawImage(LGPBlurBitmap, -1.5, -1.5);
LGPGraphic.DrawImage(LGPBitmap, 0, 0);
//Image1に元の画像を描画
DisplayImageAndGridLine(Image1, Image2, LGPBitmap);
//Image2に重ね描画した結果の画像を描画
Image3.Left := Image1.Left + Image1.Width + 10;
Image4.Left := Image1.Left + Image1.Width + 10;
DisplayImageAndGridLine(Image3, Image4, LGPDstBitmap);
//PNG画像として保存
if GetEncoderClsid('image/png', LImgGUID) >= 0 then begin
LSaveFilePath := ExtractFilePath(LImgFilePath)
+ ChangeFileExt(ExtractFileName(LImgFilePath), '') + '-Blur.png';
LGPDstBitmap.Save(LSaveFilePath, LImgGUID);
end;
finally
FreeAndNil(LGPGraphic);
FreeAndNil(LGPBitmap);
FreeAndNil(LGPBlurBitmap);
FreeAndNil(LGPDstBitmap);
FreeAndNil(LStream);
end;
end;
//=============================================================================
// CheckBox1のOnClickイベント処理
// Gaussian BlurとBox Blur処理の切り換え
//=============================================================================
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
Button1Click(nil);
end;
//-----------------------------------------------------------------------------
// 実サイズと拡大してグリッド表示の画像を描画
//-----------------------------------------------------------------------------
procedure TForm1.DisplayImageAndGridLine(AImageCtrl, ARealSizeImage: TImage;
TargetGPBitmap: TGPBitmap);
var
LGPGraphics : TGPGraphics;
begin
//TGPBitmapをTImageに描画
if ARealSizeImage <> nil then begin
ARealSizeImage.Picture.Assign(nil);
LGPGraphics := TGPGraphics.Create(ARealSizeImage.Canvas.Handle);
LGPGraphics.DrawImage(TargetGPBitmap, 0, 0);
LGPGraphics.Free;
end;
if AImageCtrl = nil then exit;
//ピクセルのグリッド表示
DrawPixelColorToTImage(TargetGPBitmap, AImageCtrl, 6, 32, clGray, nil, False);
end;
本ページのサンプルで使用しているユニットのコードです.下表の関数を実装しています.
|
関数名 |
機能 |
GaussianBlur |
ガウスぼかしを使用してぼかし画像を作成する関数 |
CreateGaussianBlurShadow |
ガウスぼかしのマスク画像を作成する関数 |
BoxBlur |
ボックスぼかしを使用してぼかし画像を作成する関数 |
CreateBoxBlurShadow |
ボックスぼかしのマスク画像を作成する関数 |
Box Blur のコードは,以下のリンクの記事のコメントとして掲載されているコードを筆者が GDI+1.0 で使用可能なコードに変更しています.
元のコードは Delphi 2009 GDI+ Library 用となっています.
関数名と関数内の変数名はそのまま使用していますが,マスク画像の作成をピクセルを直接操作するコードに変更しています.元のカラーマトリックスによる色の平行移動では必ずしも指定の色にならない場合がありますが,この変更により,マスク画像の色の変更が有効にっています.また,不透明度も指定した色に対する値になります.
|
unit plBlurGDIplus;
interface
uses
Windows, SysUtils, Graphics, GDIPAPI, GDIPOBJ, GDIPUTIL;
function GaussianBlur(AGPSrcBmp: TGPBitmap; ARadius: Integer;
ARepeat: integer = 1): TGPBitmap;
function CreateGaussianBlurShadow(ABitmap: TGPBitmap; ARadius: Integer;
AOpacity: Double = 1.0; AColor: TColor = clNone): TGPBitmap;
function BoxBlur(ABitmap: TGPBitmap; ARadius: Integer): TGPBitmap;
function CreateBoxBlurShadow(ABitmap: TGPBitmap; ARadius: Integer;
AOpacity: Double = 1.0; AColor: TColor = clNone): TGPBitmap;
implementation
uses
Math;
//=============================================================================
// ガウシアンぼかし(ガウスぼかし・Gaussian Blur)関数
//
// 戻り値のTGPBitmapはこの関数内で生成する
// 解放・破棄は呼出し側で実行する
//
// AGPSrcBmp : 対象のTGPBitmap(この関数内では変更処理はしない)
// ARadius : ほかし計算の半径
// ARepeat : 繰り返し回数(2以上にすると平たい感じになる)
//=============================================================================
function GaussianBlur(AGPSrcBmp: TGPBitmap; ARadius: Integer;
ARepeat: integer = 1): TGPBitmap;
//---------------------------------------------------------------------------
// 指定位置のPRGBQuadの値を取得
//---------------------------------------------------------------------------
function GetPRGBQuad(ABmpData: TBitmapData; APosY, APosX: Integer) : PRGBQuad;
begin
Result := ABmpData.Scan0;
inc(PByte(Result), APosY * ABmpData.stride + APosX);
end;
//---------------------------------------------------------------------------
var
LYRepeatCnt : Integer;
LXRepeatCnt : Integer;
LDiameter : Integer;
LImgWidth : Integer;
LImgHeight : Integer;
LPosX : Integer;
LPosY : Integer;
LPixel1 : Integer;
LPixel2 : Integer;
LCalcX : Integer;
LCalcY : Integer;
LBmpData : TBitmapData;
LpARGB : PRGBQuad;
LAlpha : array of Integer;
LRed : array of Integer;
LGreen : array of Integer;
LBlue : array of Integer;
LScanStart : Integer;
LBytesShift : Integer;
begin
Result := TGPBitmap(AGPSrcBmp.Clone);
if ARadius <= 0 then exit;
LDiameter := (ARadius * 2) + 1;
LBytesShift := SizeOf(TRGBQuad);
LImgWidth := Result.GetWidth- 1;
LImgHeight := Result.GetHeight - 1;
//TGPBitmapのビットマップデータ取得
if Result.LockBits(MakeRect(0, 0, LImgWidth, LImgHeight),
ImageLockModeWrite or ImageLockModeRead,
PixelFormat32bppARGB, LBmpData) <> Ok then exit;
//演算範囲を(幅x半径x2+1)に拡張
SetLength(LAlpha, LImgWidth + 1 + (ARadius * 2));
SetLength(LRed, LImgWidth + 1 + (ARadius * 2));
SetLength(LGreen, LImgWidth + 1 + (ARadius * 2));
SetLength(LBlue, LImgWidth + 1 + (ARadius * 2));
for LPosY := 0 to LImgHeight do begin
for LYRepeatCnt := 1 to ARepeat do begin
LXRepeatCnt := -ARadius;
while LXRepeatCnt <= LImgWidth + ARadius do begin
LCalcX := LXRepeatCnt;
if LCalcX < 0 then begin
LCalcX := 0;
end else
if LCalcX >= LImgWidth then begin
LCalcX := LImgWidth;
end;
LpARGB := GetPRGBQuad(LBmpData, LPosY, LCalcX * LBytesShift);
LPixel1 := LXRepeatCnt + ARadius;
LPixel2 := LXRepeatCnt + ARadius - 1;
LAlpha[LPixel1] := LpARGB^.rgbReserved + LAlpha[LPixel2];
LRed [LPixel1] := LpARGB^.rgbRed + LRed [LPixel2];
LGreen[LPixel1] := LpARGB^.rgbGreen + LGreen[LPixel2];
LBlue [LPixel1] := LpARGB^.rgbBlue + LBlue [LPixel2];
inc(LXRepeatCnt);
end;
for LPosX := 0 to LImgWidth do begin
LpARGB := GetPRGBQuad(LBmpData, LPosY, LPosX * LBytesShift);
LCalcX := LPosX + ARadius;
LPixel1 := LCalcX + ARadius;
LPixel2 := LCalcX - 1 - ARadius;
LpARGB^.rgbReserved := ((LAlpha[LPixel1] - LAlpha[LPixel2]) div LDiameter);
LpARGB^.rgbRed := ((LRed [LPixel1] - LRed [LPixel2]) div LDiameter);
LpARGB^.rgbGreen := ((LGreen[LPixel1] - LGreen[LPixel2]) div LDiameter);
LpARGB^.rgbBlue := ((LBlue [LPixel1] - LBlue [LPixel2]) div LDiameter);
end;
end;
end;
//演算範囲を(高さx半径x2+1)に拡張
SetLength(LAlpha, LImgHeight + 1 + (ARadius * 2));
SetLength(LRed, LImgHeight + 1 + (ARadius * 2));
SetLength(LGreen, LImgHeight + 1 + (ARadius * 2));
SetLength(LBlue, LImgHeight + 1 + (ARadius * 2));
for LPosX := 0 to LImgWidth do begin
for LYRepeatCnt := 1 to ARepeat do begin
LScanStart := 0;
LXRepeatCnt := -ARadius;
while LXRepeatCnt <= LImgHeight + ARadius do begin
if (LXRepeatCnt > 0) and (LXRepeatCnt < LImgHeight) then begin
inc(LScanStart);
end;
LpARGB := GetPRGBQuad(LBmpData, LScanStart, LPosX * LBytesShift);
LPixel1 := LXRepeatCnt + ARadius;
LPixel2 := LXRepeatCnt + ARadius - 1;
LAlpha[LPixel1] := LpARGB.rgbReserved + LAlpha[LPixel2];
LRed [LPixel1] := LpARGB.rgbRed + LRed [LPixel2];
LGreen[LPixel1] := LpARGB.rgbGreen + LGreen[LPixel2];
LBlue [LPixel1] := LpARGB.rgbBlue + LBlue [LPixel2];
inc(LXRepeatCnt);
end;
for LPosY := 0 to LImgHeight do begin
LpARGB := GetPRGBQuad(LBmpData, LPosY, LPosX * LBytesShift);
LCalcY := LPosY + ARadius;
LPixel1 := LCalcY + ARadius;
LPixel2 := LCalcY - 1 - ARadius;
LpARGB.rgbReserved := ((LAlpha[LPixel1] - LAlpha[LPixel2]) div LDiameter);
LpARGB.rgbRed := ((LRed [LPixel1] - LRed [LPixel2]) div LDiameter);
LpARGB.rgbGreen := ((LGreen[LPixel1] - LGreen[LPixel2]) div LDiameter);
LpARGB.rgbBlue := ((LBlue [LPixel1] - LBlue [LPixel2]) div LDiameter);
end;
end;
end;
Result.UnlockBits(LBmpData);
SetLength(LAlpha, 0);
SetLength(LRed, 0);
SetLength(LGreen, 0);
SetLength(LBlue, 0);
end;
//=============================================================================
// ガウスぼかしを使用してほかし画像のマスク画像を作成する関数
//
// 戻り値のTGPBitmapはこの関数内で生成する
// 解放・破棄は呼出し側で実行する
//
// ABitmap : 対象のTGPBitmap(この関数内では変更処理はしない)
// ARadius : ほかし計算の半径
// AOpacity : 不透明度(0.0 - 1.0).0.0は完全な透明
// 1.0にすると単色の画像となり半透明のピクセルは生成しない
// AColor : マスク画像の色
//
// 不透明度と色の引数を省略すると周辺部も含めてぼかし画像となる
//=============================================================================
function CreateGaussianBlurShadow(ABitmap: TGPBitmap; ARadius: Integer;
AOpacity: Double = 1.0; AColor: TColor = clNone): TGPBitmap;
var
LShadow : TGPBitmap;
LClone : TGPBitmap;
LGraphics : TGPGraphics;
LShadowData : TBitmapData;
LColor : TGPColor;
LShadowWidth : Integer;
LShadowHeight : Integer;
LScanLine : PByte;
LpRGBQuad : PRGBQuad;
LDataStride : Integer;
LXPos : integer;
LYPos : Integer;
LAlphaValue : Byte;
begin
ARadius := Max(ARadius, 0);
LClone := TGPBitmap.Create(ABitmap.GetWidth + (4 * Cardinal(ARadius)),
ABitmap.GetHeight + (4 * Cardinal(ARadius)),
PixelFormat32bppARGB);
LGraphics := TGPGraphics.Create(LClone);
//ぼかし画像はぼかし領域サイズX2ピクセル分右下から描画
//こうしないと矩形の上端と左端のほかしが切れてしまう
//ぼかし画像の実際の描画位置は呼出し側で行う
LGraphics.DrawImage(ABitmap,
MakeRect(2 * ARadius, 2 * ARadius,
ABitmap.GetWidth,
ABitmap.GetHeight),
0,
0,
ABitmap.GetWidth,
ABitmap.GetHeight,
UnitPixel);
try
LShadow := GaussianBlur(LClone, ARadius, 1);
if (AColor <> clNone) or (AOpacity <> 1.0) then begin
LShadowWidth := LShadow.GetWidth;
LShadowHeight := LShadow.GetHeight;
LShadow.LockBits(MakeRect(0, 0, LShadowWidth, LShadowHeight),
ImageLockModeRead or ImageLockModeWrite,
PixelFormat32bppARGB,
LShadowData);
LColor := ColorRefToARGB(AColor);
LScanLine := LShadowData.Scan0;
LDataStride := LShadowData.Stride;
for LYPos := 0 to LShadowHeight - 1 do begin
LpRGBQuad := PRGBQuad(LScanLine);
for LXPos := 0 to LShadowWidth - 1 do begin
if LpRGBQuad^.rgbReserved <> 0 then begin
LAlphaValue := Round(LpRGBQuad^.rgbReserved * AOpacity);
LpRGBQuad^.rgbReserved := LAlphaValue;
LpRGBQuad^.rgbRed := GetRed(LColor);
LpRGBQuad^.rgbGreen := GetGreen(LColor);
LpRGBQuad^.rgbBlue := GetBlue(LColor);
end;
Inc(LpRGBQuad);
end;
Inc(LScanLine, LDataStride);
end;
end;
Result := TGPBitmap(LShadow.Clone);
finally
LShadow.UnlockBits(LShadowData);
FreeAndNil(LClone);
FreeAndNil(LShadow);
FreeAndNil(LGraphics);
end;
end;
//-----------------------------------------------------------------------------
// Box Blurぼかし関数
// 以下のウェブページに掲載の関数をGDI+1.0用に変更したコード
// (掲載のコードはDelphi 2009 GDI+ Library 用)
// https://stackoverflow.com/questions/7364026/algorithm-for-fast-drop-shadow-in-gdi
//
// 戻り値のTGPBitmapはこの関数内で生成する
// 解放,破棄は呼出し側で行う
//
// usesにMathが必要
//-----------------------------------------------------------------------------
procedure BoxBlur_Routine(const AData1, AData2: TBitmapData; AWidth, AHeight,
ARadius: Integer);
const
CHANNELS = 4;
var
LScan1 : PByte;
LScan2 : PByte;
LSum : array [0..CHANNELS-1] of Integer;
LFirstValue : array [0..CHANNELS-1] of Integer;
LLastValue : array [0..CHANNELS-1] of Integer;
LRadius2 : Integer;
LStride : Integer;
LStart : Integer;
LChannel : Integer;
LLeft : Integer;
LRight : Integer;
LBottom : Integer;
LTop : Integer;
LRow : Integer;
LColumn : Integer;
begin
LScan1 := AData1.Scan0;
LScan2 := AData2.Scan0;
LRadius2 := (2 * ARadius) + 1;
LStride := AData1.Stride;
for LRow := 0 to AHeight - 1 do begin
LStart := LRow * LStride;
LLeft := LStart;
LRight := LStart + ARadius * CHANNELS;
for LChannel := 0 to CHANNELS - 1 do begin
LFirstValue[LChannel] := LScan1[LStart + LChannel];
LLastValue[LChannel] := LScan1[LStart + ((AWidth
- 1) * CHANNELS) + LChannel];
LSum[LChannel] := (ARadius + 1) * LFirstValue[LChannel];
end;
for LColumn := 0 to ARadius - 1 do begin
for LChannel := 0 to CHANNELS - 1 do begin
LSum[LChannel] := LSum[LChannel] + LScan1[LStart
+ (LColumn * CHANNELS) + LChannel];
end;
end;
for LColumn := 0 to ARadius do begin
for LChannel := 0 to CHANNELS - 1 do begin
LSum[LChannel] := LSum[LChannel] + LScan1[LRight
+ LChannel] - LFirstValue[LChannel];
LScan2[LStart + LChannel] := Byte(LSum[LChannel] div LRadius2);
end;
Inc(LRight, CHANNELS);
Inc(LStart, CHANNELS);
end;
for LColumn := ARadius + 1 to AWidth - ARadius - 1 do begin
for LChannel := 0 to CHANNELS - 1 do begin
LSum[LChannel] := LSum[LChannel] + LScan1[LRight + LChannel]
- LScan1[LLeft + LChannel];
LScan2[LStart + LChannel] := Byte(LSum[LChannel] div LRadius2);
end;
Inc(LLeft, CHANNELS);
Inc(LRight, CHANNELS);
Inc(LStart, CHANNELS);
end;
for LColumn := AWidth-ARadius to AWidth-1 do begin
for LChannel := 0 to CHANNELS - 1 do begin
LSum[LChannel] := LSum[LChannel] + LLastValue[LChannel]
- LScan1[LLeft + LChannel];
LScan2[LStart + LChannel] := Byte(LSum[LChannel] div LRadius2);
end;
Inc(LLeft, CHANNELS);
Inc(LStart, CHANNELS);
end;
end;
LStride := AData2.Stride;
for LColumn := 0 to AWidth - 1 do begin
LStart := LColumn * CHANNELS;
LTop := LStart;
LBottom := LStart + (ARadius * LStride);
for LChannel := 0 to CHANNELS - 1 do begin
LFirstValue[LChannel] := LScan2[LStart + LChannel];
LLastValue[LChannel] := LScan2[LStart + ((AHeight - 1) * LStride) + LChannel];
LSum[LChannel] := (ARadius + 1) * LFirstValue[LChannel];
end;
for LRow := 0 to ARadius-1 do begin
for LChannel := 0 to CHANNELS - 1 do begin
LSum[LChannel] := LSum[LChannel] + LScan2[LStart
+ (LRow * LStride) + LChannel];
end;
end;
for LRow := 0 to ARadius do begin
for LChannel := 0 to CHANNELS - 1 do begin
LSum[LChannel] := LSum[LChannel] + LScan2[LBottom + LChannel]
- LFirstValue[LChannel];
LScan1[LStart + LChannel] := Byte(LSum[LChannel] div LRadius2);
end;
Inc(LBottom, LStride);
Inc(LStart, LStride);
end;
for LRow := ARadius + 1 to AHeight - ARadius - 1 do begin
for LChannel := 0 to CHANNELS - 1 do begin
LSum[LChannel] := LSum[LChannel] + LScan2[LBottom
+ LChannel] - LScan2[LTop + LChannel];
LScan1[LStart + LChannel] := Byte(LSum[LChannel] div LRadius2);
end;
Inc(LTop, LStride);
Inc(LBottom, LStride);
Inc(LStart, LStride);
end;
for LRow := AHeight - ARadius to AHeight-1 do begin
for LChannel := 0 to CHANNELS - 1 do begin
LSum[LChannel] := LSum[LChannel] + LLastValue[LChannel]
- LScan2[LTop + LChannel];
LScan1[LStart + LChannel] := Byte(LSum[LChannel] div LRadius2);
end;
Inc(LTop, LStride);
Inc(LStart, LStride);
end;
end;
end;
//=============================================================================
// ボックスぼかし(Box Blur)を使用してほかし画像のマスク画像を作成する関数
//
// 戻り値のTGPBitmapはこの関数内で生成する
// 解放・破棄は呼出し側で実行する
//
// ABitmap : 対象のTGPBitmap(この関数内では変更処理はしない)
// ARadius : ほかし計算の半径
// AOpacity : 不透明度(0.0 - 1.0).0.0は完全な透明
// 1.0にすると単色の画像となり半透明のピクセルは生成しない
// AColor : マスク画像の色
//
// 不透明度と色の引数を省略すると周辺部も含めてぼかし画像となる
//
// usesにMathが必要
//=============================================================================
function CreateBoxBlurShadow(ABitmap: TGPBitmap; ARadius: Integer;
AOpacity: Double = 1.0; AColor: TColor = clNone): TGPBitmap;
var
LShadow : TGPBitmap;
LClone : TGPBitmap;
LGraphics : TGPGraphics;
LShadowData : TBitmapData;
LCloneData : TBitmapData;
LColor : TGPColor;
LShadowWidth : Integer;
LShadowHeight : Integer;
LCloneWidth : Integer;
LCloneHeight : Integer;
LScanLine : PByte;
LpRGBQuad : PRGBQuad;
LDataStride : Integer;
LXPos : integer;
LYPos : Integer;
LAlphaValue : Byte;
begin
ARadius := Max(ARadius, 0);
LShadow := TGPBitmap.Create(ABitmap.GetWidth + (4 * Cardinal(ARadius)),
ABitmap.GetHeight + (4 * Cardinal(ARadius)),
PixelFormat32bppARGB);
LGraphics := TGPGraphics.Create(LShadow);
//ぼかし画像はぼかし領域サイズX2ピクセル分右下から描画
//こうしないと矩形の上端と左端のほかしが切れてしまう
//ぼかし画像の実際の描画位置は呼出し側で行う
LGraphics.DrawImage(ABitmap, MakeRect(2 * ARadius, 2 * ARadius,
ABitmap.GetWidth,
ABitmap.GetHeight),
0,
0,
ABitmap.GetWidth,
ABitmap.GetHeight,
UnitPixel);
LClone := TGPBitmap(LShadow.Clone);
LShadowWidth := LShadow.GetWidth;
LShadowHeight := LShadow.GetHeight;
LShadow.LockBits(MakeRect(0, 0, LShadowWidth, LShadowHeight),
ImageLockModeRead or ImageLockModeWrite,
PixelFormat32bppARGB,
LShadowData);
LCloneWidth := LClone.GetWidth;
LCloneHeight := LClone.GetHeight;
LClone.LockBits(MakeRect(0, 0, LCloneWidth, LCloneHeight),
ImageLockModeRead or ImageLockModeWrite,
PixelFormat32bppARGB,
LCloneData);
try
BoxBlur_Routine(LShadowData, LCloneData, LShadowWidth, LShadowHeight, ARadius - 1);
BoxBlur_Routine(LShadowData, LCloneData, LShadowWidth, LShadowHeight, ARadius - 1);
BoxBlur_Routine(LShadowData, LCloneData, LShadowWidth, LShadowHeight, ARadius);
if (AColor <> clNone) or (AOpacity <> 1.0) then begin
LColor := ColorRefToARGB(AColor);
LScanLine := LShadowData.Scan0;
LDataStride := LShadowData.Stride;
for LYPos := 0 to LShadowHeight - 1 do begin
LpRGBQuad := PRGBQuad(LScanLine);
for LXPos := 0 to LShadowWidth - 1 do begin
if LpRGBQuad^.rgbReserved <> 0 then begin
LAlphaValue := Round(LpRGBQuad^.rgbReserved * AOpacity);
LpRGBQuad^.rgbReserved := LAlphaValue;
LpRGBQuad^.rgbRed := GetRed(LColor);
LpRGBQuad^.rgbGreen := GetGreen(LColor);
LpRGBQuad^.rgbBlue := GetBlue(LColor);
end;
Inc(LpRGBQuad);
end;
Inc(LScanLine, LDataStride);
end;
end;
Result := TGPBitmap(LShadow.Clone);
finally
LShadow.UnlockBits(LShadowData);
LClone.UnlockBits(LCloneData);
FreeAndNil(LClone);
FreeAndNil(LShadow);
FreeAndNil(LGraphics);
end;
end;
//=============================================================================
// Box Blurぼかし関数を使用して画像の矩形領域内のぼかし画像を作成する関数
//
// 戻り値のTGPBitmapはこの関数内で生成する
// 解放,破棄は呼出し側で行う
//
// ABitmap : 対象のTGPBitmap(この関数内では変更処理はしない)
// ARadius : ほかし計算の半径
//
// usesにMathが必要
//=============================================================================
function BoxBlur(ABitmap: TGPBitmap; ARadius: Integer): TGPBitmap;
var
LShadow : TGPBitmap;
LClone : TGPBitmap;
LGraphics : TGPGraphics;
LShadowData : TBitmapData;
LCloneData : TBitmapData;
LShadowWidth : Integer;
LShadowHeight : Integer;
LCloneWidth : Integer;
LCloneHeight : Integer;
begin
ARadius := Max(ARadius, 0);
LShadow := TGPBitmap.Create(ABitmap.GetWidth,
ABitmap.GetHeight,
PixelFormat32bppARGB);
LGraphics := TGPGraphics.Create(LShadow);
LGraphics.DrawImage(ABitmap, MakeRect(0, 0,
ABitmap.GetWidth,
ABitmap.GetHeight),
0,
0,
ABitmap.GetWidth,
ABitmap.GetHeight,
UnitPixel);
LClone := TGPBitmap(LShadow.Clone);
LShadowWidth := LShadow.GetWidth;
LShadowHeight := LShadow.GetHeight;
LShadow.LockBits(MakeRect(0, 0, LShadowWidth, LShadowHeight),
ImageLockModeRead or ImageLockModeWrite,
PixelFormat32bppARGB,
LShadowData);
LCloneWidth := LClone.GetWidth;
LCloneHeight := LClone.GetHeight;
LClone.LockBits(MakeRect(0, 0, LCloneWidth, LCloneHeight),
ImageLockModeRead or ImageLockModeWrite,
PixelFormat32bppARGB,
LCloneData);
try
BoxBlur_Routine(LShadowData, LCloneData, LShadowWidth, LShadowHeight, ARadius - 1);
BoxBlur_Routine(LShadowData, LCloneData, LShadowWidth, LShadowHeight, ARadius - 1);
BoxBlur_Routine(LShadowData, LCloneData, LShadowWidth, LShadowHeight, ARadius);
Result := TGPBitmap(LShadow.Clone);
finally
LShadow.UnlockBits(LShadowData);
LClone.UnlockBits(LCloneData);
FreeAndNil(LClone);
FreeAndNil(LShadow);
FreeAndNil(LGraphics);
end;
end;
end.
end.
|