IceCold
IceCold

Reputation: 21154

How to load images from disk in background (multiple threads) [AKA: TBitmap is not thread-safe]

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

Answers (1)

bummi
bummi

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

Related Questions