Menu

Loading jpeg from non-ui thread randomly gives a black image

2021-11-25
2021-11-26
  • Ruchira Hasaranga

    Hi, I just noticed when loading jpeg from a non-ui thread (task) causes it to decoded to black image randomly.
    Following code demonstrate the issue. This issue mostly occur when I move the mouse on the main window.

    procedure TForm1.DecodeTaskHandler;
    var
      myImage: TImage32;
    begin
      while True do begin
        myImage := TImage32.Create();
    
        if myImage.LoadFromFile('D:\small_white_image.jpg') then begin
          if myImage.Pixel[0,0] = Color32(clBlack) then MessageBox(0, 'Image decode failed','Error', 0);
        end;
    
        myImage.Free;
        sleep(10);
       end;
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      TTask.Create(DecodeTaskHandler).Start;
    end;
    
     
  • Ruchira Hasaranga

    Just found out that the bug occur within the Img32.Fmt.JPG.pas -> LoadFromStream method.
    In the following line, Canvas.Handle occasionally becomes zero when called from background thread.

    img32.CopyFromDC(Canvas.Handle, Rect(0,0, Width, Height));
    

    Then I realized the issue comes from the TJpegImage class. TJpegImage.Canvas.Handle occasionally becomes zero when called from a background thread.

    After searching more on the subject, I found following two links:
    https://en.delphipraxis.net/topic/1292-why-is-this-code-not-thread-safe-delphi-7/?do=findComment&comment=10765
    http://www.delphigroups.info/1/6/28001.html

    According to those links, we need to wrap the CopyFromDC within the Canvas.Lock and Canvas.Unlock methods.

    Here is my fix for TImageFormat_JPG.LoadFromStream method:

    function TImageFormat_JPG.LoadFromStream(stream: TStream; img32: TImage32): Boolean;
    var
      jpeg: TJpegImage;
    begin
      result := false;
      jpeg := TJpegImage.Create;
      try
        jpeg.LoadFromStream(stream);
        if jpeg.Empty then Exit;
    
        // lock only if background thread
        if GetCurrentThreadId <> System.MainThreadID then jpeg.Canvas.Lock;
    
        with TJpegImageHack(jpeg).Bitmap do begin
         img32.CopyFromDC(Canvas.Handle, Rect(0,0, Width, Height));
        end;
    
        if GetCurrentThreadId <> System.MainThreadID then jpeg.Canvas.Unlock;
    
        result := true;
      finally
        jpeg.Free;
      end;
    end;
    
     
  • Angus Johnson

    Angus Johnson - 2021-11-26

    Thank you for the very helpful feedback. I'll make the changes you suggested. Cheers.

     
Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.