4

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

Gabriel
  • 20,797
  • 27
  • 159
  • 293
  • Load the image data within a thread into the classes but drawing to the canvas has to be synchronized with the MainThread, because the canvas is also not threadsafe ;o) – Sir Rufo Feb 10 '13 at 21:23
  • 2
    See also: [GDI handle leak using TGIFImage in a second thread](http://stackoverflow.com/questions/10231556/). – NGLN Feb 10 '13 at 21:24
  • 1
    I guess I don't really understand precisely what you are talking about. Would you be able to demonstrate the particular problem you have encountered. The term thread-safe by itself means nothing. You have to specify what threading model you use. http://blogs.msdn.com/b/ericlippert/archive/2009/10/19/what-is-this-thing-you-call-thread-safe.aspx – David Heffernan Feb 10 '13 at 21:37
  • See [my recently updated `AwImageGrid` component](http://stackoverflow.com/a/8954813/757830). – NGLN May 11 '13 at 18:11

1 Answers1

6

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;
bummi
  • 27,123
  • 14
  • 62
  • 101
  • 2
    No need for GDI+ I guess. Plain old GDI will be fine. Perhaps somebody could explain what the actual problem is here. – David Heffernan Feb 11 '13 at 08:26
  • 1
    You are right, you would be able to work threadsave with old GDI, but you will have problems in simple use of different image formats and quality of scaling would be poor. The effort in using GDI+ is just adding 3 free units to the project. The problem in TLama's solution can easy tested with the comment I added to his thread. – bummi Feb 11 '13 at 08:32
  • For rescaling I'd consider using graphics32. I still don't understand the problem. The question doesn't make it clear. – David Heffernan Feb 11 '13 at 08:46
  • @bummi, I have downloaded the [GDI plus API](http://www.progdigy.com/files/gdiplus.zip) but I can't find the units: `EXGDIPAPI, EXGDIPOBJ`. Are these units included in that package? – kobik Feb 11 '13 at 12:49
  • @kobik sorry, I did an edit, just remove the "ex" in the declaration, I'm using extended units and forgot removing. – bummi Feb 11 '13 at 12:57
  • @bummi, Where can these extended units be found? I'm just curious :) – kobik Feb 11 '13 at 13:02
  • You won't need them, included GDIPAPI, GDIPOBJ will do it. It's just own adaption. Sorry for the typo. – bummi Feb 11 '13 at 13:06
  • @bummi, Thanks. I understand what you said, but I just thought the EX units where special...! never mind :) – kobik Feb 11 '13 at 13:09