dwrbudr 10 Posted yesterday at 10:57 AM (edited) The following code with this specific type of image (8-bit grayscale PNG) produces invalid .bmp (out_2.bmp) file if TBitmap.SaveToFile/SaveToStream is used more than once. The corrupt file cannot be opened in Windows Photo Viewer, FastStone Image Viewer, etc. BmpHeaderViewer shows this for out_1.bmp File Name: out_1.bmp File Size: 52.9 KB ------------------------------------------------------------------------ Type: BM Size: 54214 bytes Reserved1: 0 Reserved2: 0 OffBits: 1078 bytes ------------------------------------------------------------------------ Size: 40 bytes Width: 143 pixels Height: 123 pixels Planes: 1 BitCount: 24 bpp Compression: RGB SizeImage: 53136 bytes XPelsPerMeter: 0 YPelsPerMeter: 0 ClrUsed: 256 ClrImportant: 0 ------------------------------------------------------------------------ And this for the corrupted out_2.bmp File Name: out_2.bmp File Size: 52.9 KB ------------------------------------------------------------------------ Type: BM Size: 55238 bytes Reserved1: 0 Reserved2: 0 OffBits: 2102 bytes ------------------------------------------------------------------------ Size: 40 bytes Width: 143 pixels Height: 123 pixels Planes: 1 BitCount: 24 bpp Compression: RGB SizeImage: 53136 bytes XPelsPerMeter: 0 YPelsPerMeter: 0 ClrUsed: 256 ClrImportant: 0 ------------------------------------------------------------------------ Gap to pixels: 1024 bytes ------------------------------------------------------------------------ Image corrupt or truncated. ======================================================================== Tested on Delphi 12.3/13.0 Is this a known issue or what is possibly wrong in VCL, is it Vcl.Graphics.Pas or PNGImage.pas AssignTo method wrong? procedure TForm69.FormCreate(Sender: TObject); var bmp: TBitmap; png: TPNGImage; begin bmp := TBitmap.Create; png := TPNGImage.create; png.LoadFromFile('in.png'); bmp.Assign(png); bmp.SaveToFile('out_1.bmp'); bmp.SaveToFile('out_2.bmp'); end; Sample project to reproduce the issue is attached. BmpTest.zip Edited yesterday at 05:12 PM by dwrbudr Share this post Link to post
David Heffernan 2482 Posted yesterday at 01:05 PM submit a report to QP 1 Share this post Link to post
Anders Melander 2114 Posted yesterday at 03:57 PM Here's the algorithm used when saving a TBitmap: Calculate Size based on HeaderSize, the number of pixels, color depth, and current value of BitmapInfoHeader.biClrUsed (number of entries in the palette). Calculate number of entries in the palette: ColorCount Adjust Size with the value of ColorCount: Size := Size + ColorCount * SizeOf(Pixel) Set BitmapFileHeader.bfSize = Size Write BitmapFileHeader to file Set BitmapInfoHeader.biClrUsed = ColorCount Write BitmapInfoHeader to file Write palette entries Write pixels BitmapFileHeader is a local variable that is nilled and initialized each time. BitmapInfoHeader is a class variable that is used to store the bitmap properties. Can you spot the bug? The first time the bitmap is saved, BitmapInfoHeader.biClrUsed=0. The second time the bitmap is saved, BitmapInfoHeader.biClrUsed=ColorCount, so the palette size is counted twice. 1 Share this post Link to post
dwrbudr 10 Posted yesterday at 06:09 PM Thanks @Anders Melander What do you think is the safest fix? Probably to call InternalGetDIBSizes passing 0 instead of FDIB.dsbmih.biClrUsed Another "fix" is to set the Bitmap.Palette := 0 after the Assign call. Strangely though after assigning 8bit grayscale PNG to bitmap, the bitmap becames 24bit with palette. Is that a common use at all? Share this post Link to post
Anders Melander 2114 Posted yesterday at 06:47 PM minutes ago, dwrbudr said: What do you think is the safest fix? Fix or work-around? The safest work-around is to not call TBitmap.SaveTo* more than once on the same bitmap: bmp.Assign(png); bmp.SaveToFile('out_1.bmp'); bmp.Assign(png); bmp.SaveToFile('out_2.bmp'); I would guess (I would need to examine the logic more than I have time for, to be sure) the quick-fix is to modify TBitmap.WriteStream so it only counts the palette size once: if (FDIB.dsbmih.biBitCount > 8) and (FDIB.dsbmih.biClrUsed = 0) then begin // optional color palette for hicolor images (non OS2) Inc(Size, ColorCount * SizeOf(TRGBQuad)); Inc(HeaderSize, ColorCount * SizeOf(TRGBQuad)); end; but to be honest, if this was my code, I would throw out the current TBitmap.WriteStream implementation and rewrite it from scratch. It's a horrible mess. 1 Share this post Link to post