Reputation: 21154
I want to quickly show some images (jpg, png, etc) as thumbnails. Because the decoding and resizing process is sloooow I to do it in one or more threads.
However, it looks like using the canvas of TBitmap and TJpeg is not multithreading-safe.
In this case, my question are:
1. How can this be done without fully rewriting the GIF/PNG/BMP/JPG library?
2. Does anybody know if Embarcadero's Gif and Png libs are also unsafe?
3. If I use Lock to lock the canvas wouldn't it ruin the performance since the resize part accesses the canvas and it takes most of the CPU cycles?
I have found this that troubles me:
David HAROUCHE wrote: That is not correct. The really confusing part is that even local TBitmap are not thread safe unless you lock them. This is because every TBitmap registers itself to the global BitmapCanvasList list in graphics.pas. And when the DC garbage collection FreeMemoryContexts()
http://www.codenewsfast.com/cnf/thread/0/permalink.thr-ng1908q2024
Upvotes: 4
Views: 3803
Reputation: 27377
Using GDI+ with CreateCompatibleDC and CreateBitmap will cover many image formats and avoid canvas thread problems.
This is only a sample implemetaion and might be modified.
GDI+ API will need three units, no installation and can be got for example from http://www.progdigy.com/
unit ScaleImageThread;
// 2013 Thomas Wassermann
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls
,GDIPAPI, GDIPOBJ, StdCtrls;
Type
TScaleImageThread=Class(TThread)
FBMP:TBitMap;
FMemDC:HDC;
FMemBMP:HBitmap;
Procedure Execute;Override;
private
Ffn:String;
FDestWidth,FDestHeight:Integer;
procedure SyncFinished;
Public
Constructor Create(aBitMap:TBitmap;const fn:String);overload;
property BMP:TBitmap read FBMP;
Property FileName:String read Ffn;
End;
implementation
{ TGDIThread }
Procedure ScaleOneImage(Const source:String;aHDC:HDC;DestWidth,DestHeight:Integer;Qual:Integer=92;WithOutMargins:Boolean=false;BgColor:TColor=ClWhite;DoNotUpScale:Boolean=false);
var
graphics : TGPGraphics;
image: TGPImage;
width, height: UINT;
faktor:Double;
destx,desty:Double;
rct:TGPRectF;
Ext:String;
begin
image:= TGPImage.Create(source);
width := image.GetWidth;
height := image.GetHeight;
if (DestWidth / width) < (DestHeight/Height) then faktor := (DestWidth / width) else faktor:= (DestHeight/Height);
destx := (DestWidth - faktor * width) / 2;
desty := (DestHeight - faktor * Height) / 2;
graphics := TGPGraphics.Create(aHDC);
graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
graphics.DrawImage(
image,
MakeRect(destx, desty , faktor * width, faktor * height), // destination rectangle
0, 0, // upper-left corner of source rectangle
width, // width of source rectangle
height, // height of source rectangle
UnitPixel);
image.Free;
graphics.Free;
end;
constructor TScaleImageThread.Create(aBitMap: TBitmap;const fn:String);
begin
inherited create(False);
Ffn :=fn;
FreeOnTerminate := true;
FBmp := aBitMap;
FMemDC := CreateCompatibleDC(FBmp.Canvas.Handle);
FMemBMP := CreateBitmap(FBmp.Width ,FBmp.Height ,1,GetDeviceCaps(FBmp.Canvas.Handle, BITSPIXEL),nil);
SelectObject(FMemDC, FMemBMP);
FDestWidth :=FBMP.Width;
FDestHeight:=FBMP.Height;
end;
procedure TScaleImageThread.Execute;
begin
inherited;
ScaleOneImage(Ffn,FMemDC,FDestWidth,FDestHeight);
Synchronize(SyncFinished);
end;
procedure TScaleImageThread.SyncFinished;
begin
BitBlt(FBmp.Canvas.Handle, 0, 0, FBmp.Width, FBmp.Height, FMemDC, 0, 0, SRCCOPY);
DeleteObject(FMemBMP);
DeleteDC (FMemDC);
end;
end.
Test of Implementation
uses ScaleImageThread;
{$R *.dfm}
procedure TForm1.ThreadTerminate(Sender: TObject);
begin
Canvas.Draw(FX, FY, TGDIThread(Sender).BMP);
TGDIThread(Sender).BMP.Free;
FX := FX + 70;
if FX > 500 then
begin
FX := 0;
FY := FY + 70;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
const
C_DIM = 64;
var
i: Integer;
Function GetNewBitMap: TBitMap;
begin
Result := TBitMap.Create;
Result.Width := C_DIM;
Result.Height := C_DIM;
end;
begin
ReportMemoryLeaksOnShutDown := true;
for i := 1 to 10 do
With TGDIThread.Create(GetNewBitMap,
'C:\temp\bild ' + intToStr(i) + '.png') do
OnTerminate := ThreadTerminate;
for i := 1 to 10 do
With TGDIThread.Create(GetNewBitMap,
'C:\Bilder\Kids' + intToStr(i) + '.jpg') do
OnTerminate := ThreadTerminate;
end;
Upvotes: 6