IceCold
IceCold

Reputation: 21231

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.

Upvotes: 1

Views: 604

Answers (2)

Kevin Berry
Kevin Berry

Reputation: 161

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.

Upvotes: 1

GolezTrol
GolezTrol

Reputation: 116190

I just checked the code of TJpegImage, and the hypothesis I posted in the comments seems correct.

TJpegImage keeps an internal TBitmap for the representation. When you call DIBNeeded, this bitmap is created based on the Jpeg image data.

GetBitmap (the private function that does the legwork for DIBNeeded) will first check if the bitmap is already assigned, and won't repeat the process if it is. So just calling DIBNeeded will not work in your case, since you're basically guaranteed to have this cached bitmap already.

The FreeBitmap method will free the internal bitmap, after which calling DIBNeeded will create a new one again. So I think the sequence you need is:

Jpg.Compress; // Make sure the Jpeg compressed image data is up to date
Jpg.FreeBitmap; // Clear the internal cached bitmap
Jpg.DIBNeeded; // Optional, get a new bitmap. Will happen when you assign to TBitmap.

I also mentioned JpegNeeded before, but that will do a similar thing as DIBNeeded: check if there is data, if not, call Compress. So you need to call Compress, like you did, to force this compression.

PS: TBitmap (and file formats similar to bmp), don't really know this kind of compression, so by assigning it back to the bitmap, you will have reduced image quality, but not image size. Some bitmap formats, including PNG, do compress by using (amongst others) run length encoding (RLE), which means something like spending just four bytes for saying "And now, 54 times a pixel of this color!". That kind of compression won't work really well on images with lots of jpeg artifacts (the grainy/blurry side effect of the compression), so a PNG version of the compressed Jpg might be larger than a PNG version of the original, even though the quality of the original is better as well. This is especially true for images with large areas of the same color, like screenshots and certain artwork.

Upvotes: 4

Related Questions