Recent

Author Topic: Bitmap 16bit R5G6B5  (Read 1676 times)

wp

  • Hero Member
  • *****
  • Posts: 12459
Re: Bitmap 16bit R5G6B5
« Reply #15 on: September 17, 2024, 10:11:20 am »
You're right. I thought I had checked this case. But I think I had used a TBitmap for the reading step: the following code still produces a 24bit bitmap:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button3Click(Sender: TObject);
  2. var
  3.   bmp: TBitmap;
  4.   img: TLazIntfImage;
  5.   writer: TFPWriterBMP;
  6. begin
  7.   bmp := TBitmap.Create;
  8.   try
  9.     bmp.LoadFromFile(SRC_FILENAME);
  10.     img := bmp.CreateIntfImage;
  11.     writer := TFPWriterBMP.Create;
  12.     try
  13.       img.SaveToFile(DEST_FILENAME);
  14.     finally
  15.       writer.Free;
  16.       img.Free;
  17.     end;
  18.   finally
  19.     bmp.Free;
  20.   end;
  21. end;
Using a TFPCustomImage for the reading step, like in your code, fixes the issue. Just: what is happening that TBitmap does not work this way?

JZS

  • Full Member
  • ***
  • Posts: 205
Re: Bitmap 16bit R5G6B5
« Reply #16 on: September 17, 2024, 06:42:14 pm »
Thank you Jamie, but I couldn't get any positive result using your code, most likely I did not use it correctly, but at least I tried, and this is what I did:
Code: Pascal  [Select][+][-]
  1. procedure RGB565JamieEx(const ASrc, ADest: String);
  2. Var
  3.   A:WORD; //Simulate a 16bit color
  4.  
  5.   img: TFPMemoryImage;
  6.   reader: TFPCustomImageReader;
  7.   writer: TFPWriterBMP;
  8.   i, j: Integer;
  9.   clr: TFPColor;
  10. begin
  11.   img := TFPMemoryImage.Create(0, 0);
  12.   try
  13.     reader := TFPReaderbmp.Create;
  14.     try
  15.       img.LoadFromFile(ASrc, reader);
  16.     finally
  17.       reader.Free;
  18.     end;
  19.  
  20.     for j := 0 to img.Height - 1 do
  21.       for i := 0 to img.Width - 1 do
  22.       begin
  23.         clr := img.Colors[i, j];
  24.         A := FPColorToTColor(clr);
  25.  
  26.         With T16BitColor(A) do
  27.         Begin
  28.          Clr:= TColorToFPColor(RGBToCOlor(R,g,b));
  29.         end;
  30.         img.Colors[i, j] := clr;
  31.       end;
  32.  
  33.     writer := TFPWriterbmp.Create;
  34.     //writer.BitsPerPixel:= 16; //makes no difference
  35.     try
  36.       img.SaveToFile(ADest, writer);
  37.     finally
  38.       writer.Free;
  39.     end;
  40.   finally
  41.     img.Free;
  42.   end;
  43. end;
I use recent stable release

JZS

  • Full Member
  • ***
  • Posts: 205
Re: Bitmap 16bit R5G6B5
« Reply #17 on: September 17, 2024, 06:47:25 pm »
Thank you WP, your code gives identical result to Circular example.
I use recent stable release

TRon

  • Hero Member
  • *****
  • Posts: 3623
Re: Bitmap 16bit R5G6B5
« Reply #18 on: September 17, 2024, 06:55:23 pm »
If i remember correctly you need to set the colorformat of your new customimage, then make sure the colors get scaled correctly, then write the bmp using the required format.

I am very curious about circular's single step solution because to my knowledge things do not work that way (but do keep in mind that circular is better versed in these kind of topics). rgb565 is a special one and not your default 16-bit bmp image
This tagline is powered by AI (AI advertisement: Free Pascal the only programming language that matters)

JZS

  • Full Member
  • ***
  • Posts: 205
Re: Bitmap 16bit R5G6B5
« Reply #19 on: September 17, 2024, 06:58:02 pm »
Hi JZS,

We may have overcomplicated things here. You don't need to quantize yourself the values if you're using the image writer to do that. So in the code your propose, just keep the following:
Code: Pascal  [Select][+][-]
  1. procedure RGB565(const ASrc, ADest: String);
  2. var
  3.   img: TFPMemoryImage;
  4.   reader: TFPCustomImageReader;
  5.   writer: TFPWriterBMP;
  6. begin
  7.   img := TFPMemoryImage.Create(0, 0);
  8.   try
  9.     reader := TFPReaderbmp.Create;
  10.     try
  11.       img.LoadFromFile(ASrc, reader);
  12.     finally
  13.       reader.Free;
  14.     end;
  15.  
  16.     writer := TFPWriterbmp.Create;
  17.     writer.BitsPerPixel:= 16;
  18.     try
  19.       img.SaveToFile(ADest, writer);
  20.     finally
  21.       writer.Free;
  22.     end;
  23.   finally
  24.     img.Free;
  25.   end;
  26. end;

The manual quantization is only if you want to do something with the data as 16 bits.

Regards

Thank you Circular, your code works but am not sure if the result is the same to 16bit 565 generated image.
if I compare the generated 16bit 565 image from GIMP and the image produced with the above code the result is kinda different, if compared as bits and bytes, I might be wrong though, the outcome generally looks identical, it is only the content statistics that is different.
Now I have a question about the types of 16bit that can be generated by GIMP are R5G6B5, A1R5G5B5 and X1R5G5B5. How does GIMP do it to manipulate the 16bit result image?
I use recent stable release

circular

  • Hero Member
  • *****
  • Posts: 4356
    • Personal webpage
Re: Bitmap 16bit R5G6B5
« Reply #20 on: September 18, 2024, 12:11:53 am »
Hi JZS,

Happy to hear it works.  :)

I am not sure what is the "content statistics". Can you give me more details?

Now I have a question about the types of 16bit that can be generated by GIMP are R5G6B5, A1R5G5B5 and X1R5G5B5. How does GIMP do it to manipulate the 16bit result image?
I suppose X1R5G5B5 is 15 bits per pixel, where X1 indicate the padding.

I am not sure what you would like to know about what GIMP does. Are you referring to the way the quantization is done? Or what happens when you edit the 16bit image?
Conscience is the debugger of the mind

wp

  • Hero Member
  • *****
  • Posts: 12459
Re: Bitmap 16bit R5G6B5
« Reply #21 on: September 18, 2024, 01:16:23 am »
Just uploaded to my github a small tool to analyze and display the headers of bmp files: https://github.com/wp-xyz/bmpExplorer/tree/main

JZS

  • Full Member
  • ***
  • Posts: 205
Re: Bitmap 16bit R5G6B5
« Reply #22 on: September 18, 2024, 05:13:29 am »
Hi JZS,

Happy to hear it works.  :)

I am not sure what is the "content statistics". Can you give me more details?

I did not use benchmark tool to tell the difference so I used dirty and quick bits count/location comparison. And there were few differences.
Thanks WP for the rescue, if I use WP app "bmpExplorer" I can name few differences now, and if you like I am attaching the four bmp pictures (Parrots (Original), GIMP_16bit565, wp_16bit, and Circular_16bit) (BTW WP bitmap result image is identical with Circular result).

The differences are as follows:

GIMP:
Horizontal resolution: 72 ppi
Vertical resolution: 72 ppi

Offset to image data: 138 ($  8A)

Size of info header: 124 ($  7C)
Horizontal resolution: 2835 px/m, 72 ppi
Vertical resolution: 2835 px/m, 72 ppi


WP & Circular:
Horizontal resolution: 3 ppi
Vertical resolution: 3 ppi

Offset to image data: 66 ($  42)

Size of info header: 40 ($  28)
Horizontal resolution: 100 px/m, 3 ppi
Vertical resolution: 100 px/m, 3 ppi



I am not sure what you would like to know about what GIMP does. Are you referring to the way the quantization is done? Or what happens when you edit the 16bit image?

I asked that question since it is only one flag to set (bpp=16) and we have the 16bit 565, so I wanted to know how about the other two variations (A1 555 & x1 555), I was only curious what alteration that makes the difference. But my original goal here is to be able to generate 16bit r5g6b5 bitmaps using Lazarus. I have a hardware device that only accepts 16bit565 bitmap, which I can not have access to test with the new result until next week, meanwhile you are stuck with me looking at the variations that different tools offer  O:-)
I use recent stable release

circular

  • Hero Member
  • *****
  • Posts: 4356
    • Personal webpage
Re: Bitmap 16bit R5G6B5
« Reply #23 on: September 18, 2024, 11:29:43 am »
Hi JZS,

Ok, let's take a look at the differences between GIMP-16bit565 and Circular_16bit/wp_16bit565. First, looking at the file size, it is nearly identical, showing that the bits per pixel is roughly the same. So I presume your main objective here is reached.

The header size is different, but that probably doesn't matter. There are indeed additional fields but they are mostly set to default.

Regarding the resolution, it is an indication for printing in the header of the file. Support for resolution in TFPCustomImage was added in FPC 3.2.3, which at the time of writing this, is not yet published. You can either use trunk or use BGRABitmap for this.

With resolution support, you can match GIMP if you like by setting the relevant properties before saving:
Code: Pascal  [Select][+][-]
  1. bmp.ResolutionUnit := roPixelsPerInch;
  2. bmp.ResolutionWidth := 72;
  3. bmp.ResolutionHeight := 72;
  4. bmp.SaveToFile(...);
  5.  

Looking at the pixel data, there are some minor differences. Looking at the first values, it seems GIMP values are sometimes offset by 1 for example $A2 instead of $A1. This is probably dues to rounding in the quantization. Indeed TFPWriteBMP and TLazIntfImage truncate values. If you would like the same quantization as GIMP, you need to quantize manually before saving.

You can do a quantization with rounding like GIMP using the formula I've provided at the beginning of this thread:
Code: Pascal  [Select][+][-]
  1. var
  2.   c: TFPColor;
  3.   maxEncodedRed, encodedRed: longword;
  4. begin
  5.   ...
  6.   maxEncodedRed := 1 shl bitDepth - 1; // with bitDepth = 5, maxEncodedRed = 31
  7.   encodedRed := (c.red * maxEncodedRed + 32767) div 65535;
  8.   c.red := (encodedRed * 65535 + (maxEncodedRed shr 1)) div maxEncodedRed;
You would need to adapt this for green and blue channels as well of course.
« Last Edit: September 18, 2024, 11:33:24 am by circular »
Conscience is the debugger of the mind

wp

  • Hero Member
  • *****
  • Posts: 12459
Re: Bitmap 16bit R5G6B5
« Reply #24 on: September 18, 2024, 11:50:20 am »
The TFPWriterBMP used to write bmp files to disk has a nasty annoyance: x and y resolution are initialized with the value 100, and it probably is assumed that these are pixels per inch (ppi). But in reality Microsoft had decided to interpret the X and YResolution as pixels per meter (ppm)! To convert between ppi and ppm you must apply a correction factor 0.0254: ppi = ppm * 0.0254 - the 100 ppm in the file header therefore condense to small 3 ppi.

The writer, however, has properties XPelsPerMeter and YPelsPerMeter so that you can override the hard-coded 100. If you want 100 ppi (rather than ppm) then specify the value 100/0.054 = 3937 pixels per meter:

Code: Pascal  [Select][+][-]
  1. var
  2.   writer: TFPWriterBMP;
  3. ...
  4.   writer := TFPWriterBMP.Create;
  5.   try
  6.     writer.XPelsPerMeter := round(ppi / 0.0254);
  7.     writer.YPelsPerMeter := round(ppi / 0.0254);
  8.     writer.BitsPerPixel := 16;
  9.     Bitmap.SaveToFile(filename, writer);
  10.   finally
  11.     writer.Free;
  12.   end;

Quote
I wanted to know how about the other two variations (A1 555 & x1 555)
Looking at the FPWriterBMP source code it can be found that its BitsPerPixel here have the value 15 (rather than 16). I don't know what condition must be met so that the empty bit in A1 R5G5B5 / X1 R5G5B5 is interpreted as A=Alpha or is ignored (X).
« Last Edit: September 18, 2024, 11:58:40 am by wp »

circular

  • Hero Member
  • *****
  • Posts: 4356
    • Personal webpage
Re: Bitmap 16bit R5G6B5
« Reply #25 on: September 19, 2024, 01:24:03 pm »
Yes, wp is right, with current version of FPC 3.2.2 you would set the resolution at the level of the BMP image writer.

That raises a question about backward compatibility. I have the impression that the changes in trunk are not compatible as the new writer uses the resolution set at the level of the image. I imagine that we can fix it:
- by having an undefined state for the XPelsPerMeter / YPelsPerMeter values so that unless they are assigned, they will be overridden by the values of the image.
- by ignoring the ResolutionX / ResolutionY values at the level of the image if they are the default (equal to zero).
Conscience is the debugger of the mind

wp

  • Hero Member
  • *****
  • Posts: 12459
Re: Bitmap 16bit R5G6B5
« Reply #26 on: September 19, 2024, 04:54:32 pm »
That raises a question about backward compatibility. I have the impression that the changes in trunk are not compatible as the new writer uses the resolution set at the level of the image.
Sorry I don't understand. The FPWriterBMP only allows to set a value for the image resolution, it was hardcoded before. I don't see an issue with backward compatibility, the default resolution did not change. The problem is that the default resolution value is measured in wrong units (ppi rather than ppm). A compatibility issue would arise if the default resolution value would be changed to the real ppm value.

- by ignoring the ResolutionX / ResolutionY values at the level of the image if they are the default (equal to zero).
A bitmap can be used by any application, not just ours. Ignoring the resolution is not an option for software which calculates the image size from the resolution. And when the default resolution is zero they would even multiply the image size down to zero. -> no image displayed. And other software which draws the image into a frame of its own size, or draws a screen pixel for each image pixel, ignores the resolution value anyway.

circular

  • Hero Member
  • *****
  • Posts: 4356
    • Personal webpage
Re: Bitmap 16bit R5G6B5
« Reply #27 on: September 20, 2024, 08:43:34 am »
Hi wp,

I suppose there is a misunderstanding. I am talking about the change between FPC 3.2.2 and FPC 3.2.4, introducing ResolutionX and ResolutionY to TFPCustomImage:
https://gitlab.com/freepascal.org/fpc/source/-/commit/987d87569a82b1f42a5fd94b0d4904cf72b2ec67#c166f82bfd4854f379e3a4627adbde98b87f4718

The BMP writer was changed to use those fields and change XPelsPerMeter / YPelsPerMeter accordingly.

Regards
Conscience is the debugger of the mind

 

TinyPortal © 2005-2018