'TJpegImage: Internal bitmap not updated after applying JPEG compression

I want to convert a BMP to JPG, compress that JPG and the put back the compressed JPG into the original BMP. However, it won't assign the compressed image to the BMP. I always get the orignal image into the BMP.

The code is below. To see the compression I set CompressionQuality = 1. This will literary ruin the image.

function CompressBmp2RAM(InOutBMP: TBitmap): Integer;
VAR
   Stream: TMemoryStream;
   Jpg: TJPEGImage;
begin
 Jpg:= TJPEGImage.Create;
 Stream:= TMemoryStream.Create;
 TRY
   Jpg.Assign(InOutBMP);
   Jpg.CompressionQuality:= 1;  // highest compression, lowest quality 
   Jpg.Compress;
   Jpg.SaveToStream(Stream);
   //Stream.SaveToFile('c:\out.jpg'); <---- this gives the correct (heavily compressed) image
   Result:= tmpQStream.Size;
   InOutBMP.Assign(Jpg);
   //InOutBMP.SaveToFile('c:\out.bmp'); <---- this gives the uncompressed image
 FINALLY
   FreeAndNil(Stream);
   FreeAndNil(Jpg);
 END;
end;

I have found an work around, but I still want to know why InOutBMP.Assign(Jpg) in the code above won't work.

   ...
   Stream.Position:= 0;
   Jpg.LoadFromStream(Stream);
   InOutBMP.Assign(Jpg);
   ...

To me it seems to be a bug. The JPG is not aware that the data was recompressed, so the internal bitmap is never updated. There should be some kind of internal "DirtyData" (or "HasChanged") flag.

So, what is the official solution for this? Having the JPG to reload ITS OWN DATA from an external data source (stream) seems rather a hack/temporary bug fix.

PS: Jpg.DIBNeeded won't help.



Solution 1:[1]

The internal TJPEGIMage.GetBitmap function only creates an internal bitmap based on the current jpeg image if no internal bitmap has been previously created.

Assign Image A. Do not use Canvas.
Assign Image B. Use Canvas and see Image B.
Assign Image C. Use Canvas and see Image B.
Assign Image D. Use Canvas and see Image B.
etc.

This is definitely a bug in TJPEGImage. I'm using XE7, so maybe this has been fixed by now.

My workaround to always get the correct JPEGImage.Canvas bitmap is to clear any existing internal bitmap before the assignment.

TJPEGImage = class(Vcl.Imaging.jpeg.TJPEGImage)
public
  procedure Assign(Source: TPersistent); override;
end;

procedure TJPEGImage.Assign(Source: TPersistent);
begin
  FreeBitmap;
  inherited;
end;

The code above is helpful when using one TJPEGImage to handle a lot of different images. For only a few images, creating a new TJPEGImage for each image works.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1