3

I have this function that takes 4.2 seconds to convert a jpg to bmp. Why it takes so long? Can I make if faster?
IrfanView loads and converts the file in only a fraction of that time.

I thought that is spends most of the time in JPG.LoadFromFile. But when I measured the time I was surprised to see it spends most of the time in BMP.Assing(JPG).

function ConvertJPG2BMP(CONST FileName: string): TBitmap;
VAR JPG: TJpegImage;
begin
 Result:= NIL;
 JPG:= TJpegImage.Create;
 TRY
   JPG.LoadFromFile(FileName);
   if  (JPG.Width > 0) AND (JPG.Width  < 32768)
   AND (JPG.Height> 0) AND (JPG.Height < 32768) then
    begin
      Result:= TBitmap.Create;
      TRY
        Result.HandleType:= bmDIB;

        // Fuji_FinePix_F550.JPG    [3200x1800] [1.44MB] 
        Result.Assign(JPG);  <--- 4 seconds!!
      EXCEPT
        FreeAndNil(Result);
      END;
    end;
 FINALLY
   FreeAndNil(JPG);
 end;
end;
Gabriel
  • 20,797
  • 27
  • 159
  • 293
  • 2
    Actual decompression occurs in the assign function. That's why its slow. – Orhan ODABAŞI Nov 19 '18 at 20:43
  • 1
    Since you are not showing your image anywhere in your application you could try to use `TBitmap` class from `FMX.Graphics` to load the image from JPG and then directly save it to BMP. Yes `FMX.Graphics.TBitmap` class can also be used in VCL application. – SilverWarior Nov 20 '18 at 11:17
  • there is this open source library for imaging. I see some ASM code in the JPG unit. I haven't tested yet: https://sourceforge.net/p/imaginglib/code/ci/default/tree/Source/ImagingJpeg.pas – Gabriel Nov 22 '18 at 08:36
  • TJpegImage.LoadFromFile does only minimal processing, but the actual, slow resource intensive conversion from JPG to BMP happens in TBitmap.Assign. In some cases, you can call [TjpegImage.DIBNeeded](https://docwiki.embarcadero.com/Libraries/Sydney/en/Vcl.Imaging.jpeg.TJPEGImage.DIBNeeded) to pre-process the BMP copy before assigning it or drawing it. Once a bitmap has been generated by TJPegImage object, the assignment to another picture object is fast since the bitmap handle is shared, the copy does not create a new instance of the bitmap. – Flavio Apr 27 '22 at 12:24

3 Answers3

6

Since I wanted to test the slightly older functions once, it is a good opportunity to do this now.

The sources used are here

These have been changed a bit in the code below.

Somewhat adapted source code of OP's function ConvertJPG2BMP() (2512 : ms)

enter image description here

function ConvertJPG2BMP(CONST FileName: string): TBitmap;
VAR
 JPG: TJpegImage;
begin
 Result:= NIL;
 JPG:= TJpegImage.Create;
 TRY
   JPG.LoadFromFile(FileName);
   if  (JPG.Width > 0) AND (JPG.Width  < 32768)
   AND (JPG.Height> 0) AND (JPG.Height < 32768) then
    begin
      Result:= TBitmap.Create;
      TRY
        Result.PixelFormat := pf24bit;
        Result.Width  := JPG.Width;
        Result.Height := JPG.Height;
        Result.HandleType:= bmDIB;
        // 2018-10-17 14.04.23.jpg    [2560x1920] [1.66MB]
        Result.Assign(JPG); 
        Result.SaveToFile('F:\ProgramFiles\Embarcadero\dtx\Projects\Bmp-DIB\JPG2BMP.bmp');
      EXCEPT
        FreeAndNil(Result);
      END;
    end;
 FINALLY
   FreeAndNil(JPG);
 end;
end;

The source for the TWICImage usage (296 : ms)

There is another class in Vcl.Graphics? called TWICImage that handles images supported by the Microsoft Imaging Component

Including BMP, GIF, ICO, JPEG, PNG, TIF and Windows Media Photo


enter image description here

procedure LoadImageFromStream(Stream: TStream; Image: TImage);
var
  wic: TWICImage;
  Bitmap: TBitmap;
begin
  Stream.Position := 0;
  wic := TWICImage.Create;
  try
    wic.LoadFromStream(Stream);
    Image.Picture.Assign(wic);
    Bitmap := TBitmap.Create;
    try
      Bitmap.PixelFormat := pf24bit;
      Bitmap.Width  := Image.Picture.Width;
      Bitmap.Height := Image.Picture.Height;
      Bitmap.Canvas.Draw(0, 0, Image.Picture.Graphic);
      Bitmap.SaveToFile('F:\ProgramFiles\Embarcadero\dtx\Projects\Bmp-DIB\TWICImage.bmp');
    finally
      Bitmap.Free;
    end;
  finally
    wic.Free;
  end;
end;

procedure RenderImage(const Filename: string);
var
  fs: TFileStream;
begin
  fs := TFileStream.Create(Filename, fmOpenRead);
  try
    LoadImageFromStream(fs, Form1.Image1);
  finally
    fs.Free;
  end;
end;

GetTickCount for all tested routines.

procedure TForm1.Button1Click(Sender: TObject);
var
MyDIB   : TBitmap;
loadStr : string;
XStart,Xend   : LongWord;
begin
loadStr := 'F:\ProgramFiles\Embarcadero\dtx\Projects\Bmp-DIB\2018-10-17 14.04.23.jpg';
XStart := GetTickCount;

if RadioGroup1.ItemIndex = 0 then MyDIB := ConvertJPG2BMP(loadStr);// ConvertJPG2BMP()
if RadioGroup1.ItemIndex = 1 then TestBmp(loadStr);
if RadioGroup1.ItemIndex = 2 then RenderImage(loadStr);// TWICImage
if RadioGroup1.ItemIndex = 3 then GetOleGraphic(loadStr);

Xend := GetTickCount;
Label1.Caption := IntToStr(xEnd-XStart) + ' : MS' ;

end;

The generated images are identical to the file size only from the function GetOleGraphic() is a smaller file produced with a worse resolution?

here the source used for the GetOleGraphic()

enter image description here

Gabriel
  • 20,797
  • 27
  • 159
  • 293
moskito-x
  • 11,832
  • 5
  • 47
  • 60
1

Here is a compact version of WIC image loader posted by moskito-x above.
Please VOTE HIS answer not mine. My answer here is only to provide the compact version and some details.

{-----------------------------------------------
  Uses TWICImage

  Advantages:  
      8+ times faster than Delphi's JPG function
      Works with: animated GIF, PNG, JPG
  
  Drawbacks: 
      Fails with JPEG2K
      No EXIF support
      Platform dependent
 -----------------------------------------------}

function LoadImageWic(CONST FileName: string): TBitmap;
VAR
   wic: TWICImage;
begin
  wic := TWICImage.Create;
  TRY
   wic.LoadFromFile(FileName);
   Result := TBitmap.Create;
   TRY
     Result.Assign(wic);
   EXCEPT
     FreeAndNil(Result);
   END;
  FINALLY
    FreeAndNil(wic);
  END;
end;
Gabriel
  • 20,797
  • 27
  • 159
  • 293
0

Just try to decompress the jpeg using our Open Source SynGDIPlus unit.

We found it much faster than the Delphi built-in jpeg.pas unit.

The latest revision can be retrieved from github.

As an alternative, you may try to use our fast Jpeg decoder using SSE2 but it doesn't handle all kind of Jpegs, and it is for Win32 only.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159
  • @Rigel SynGdiPlus (and the SSE2 decoder) are used on production for years to produce jpeg-oriented videos with great stability (in a multi-threaded server process) and much better performance than the standard jpeg.pas unit - so I guess something was wrong with your use – Arnaud Bouchez Nov 21 '18 at 13:35
  • I tried it in the past, and it was not decoding properly some jpegs (I have found 4-5 files that were not decoded properly). – Gabriel Apr 28 '22 at 10:33