Recent

Author Topic: [SOLVED] Drawing a RED rectangle on a TPicture.Bitmap is shown in GRAY (WIN)  (Read 1563 times)

Hartmut

  • Hero Member
  • *****
  • Posts: 891
I have an issue which only occurs on Windows, not on Linux. When I draw a red rectangle on a TPicture.Bitmap and show this Bitmap on a TPaintBox, then the picture itself is correct, but on Windows the rectangle has a gray color. When I set TForm1.Color to any value (see line 83), then this value will be the color of my rectangle.

I'm still a beginner to Graphics. I could imagine that this issue has to do with Transparency-Mode. So I tried to disable this mode (see line 60), but this did not help. And I noticed, that this mode is still active on Windows (although I disabled it), while it is off on Linux (see line 61).

This is my code:

Code: Pascal  [Select][+][-]
  1. {demonstrates that a red rectangular is shown in grey = TForm.Color}
  2.  
  3. unit Unit1;
  4.  
  5. {$mode objfpc}{$H+}
  6. {$IFNDEF LINUX} {$apptype console} {$ENDIF} {neccessary for writeln}
  7.  
  8. interface
  9.  
  10. uses
  11.  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls;
  12.  
  13. const PictureFile = 'demo1.jpg'; {picture file to show}
  14.  
  15. type
  16.  
  17.  { TForm1 }
  18.  
  19.  TForm1 = class(TForm)
  20.   PaintBox1: TPaintBox;
  21.  
  22.   procedure loadAndShowPicture(fspec: string);
  23.   procedure apply_Edits(BM: TBitmap);
  24.   procedure PaintBox1Paint(Sender: TObject);
  25.   procedure initialize;
  26.   procedure FormActivate(Sender: TObject);
  27.   procedure FormDestroy(Sender: TObject);
  28.  
  29.  private
  30.   Picture1: TPicture; {contains the loaded picture file}
  31.   BM_valid: boolean;  {is the Bitmap for Event 'PaintBox1Paint' valid?}
  32.  public
  33.  end;
  34.  
  35. var
  36.  Form1: TForm1;
  37.  
  38. implementation
  39.  
  40. {$R *.lfm}
  41.  
  42. procedure TForm1.loadAndShowPicture(fspec: string);
  43.    {loads and shows picture file 'fspec'}
  44.    begin
  45.    BM_valid:=false;              {disables Event PaintBox1Paint()}
  46.    Picture1.LoadFromFile(fspec);
  47.    self.SetBounds(20,20,Picture1.Width,Picture1.Height);
  48.  
  49.    apply_Edits(Picture1.Bitmap); {draws a RED rectangle}
  50.    BM_valid:=true;               {enables Event PaintBox1Paint()}
  51.    PaintBox1.Invalidate;
  52.    end; {loadAndShowPicture}
  53.  
  54. procedure TForm1.apply_Edits(BM: TBitmap);
  55.    {in real world: draws some rectangles in different colors and can make
  56.     other changes to the Bitmap 'BM'}
  57.    var CV: TCanvas;
  58.    begin
  59.    CV:=BM.Canvas;
  60. BM.Transparent:=false; // did not help on Windows
  61. writeln('BM.Transparent=', BM.Transparent); // => TRUE on WIN / FALSE on Linux
  62.    CV.Pen.Color:=clRed;
  63.    CV.Pen.Width:=3;
  64.    CV.Brush.Style:=bsClear;
  65.    CV.Rectangle(100,100,200,200);
  66.    end; {apply_Edits}
  67.  
  68. procedure TForm1.PaintBox1Paint(Sender: TObject);
  69.    {Event "OnPaint" of PaintBox1}
  70.    begin
  71.    if BM_valid then {only if the Bitmap for this Event is valid}
  72.       PaintBox1.Canvas.Draw(0,0,Picture1.Bitmap);
  73.    end;
  74.  
  75. procedure TForm1.initialize;
  76.    {does the complete initialization}
  77.    begin
  78.    BM_valid:=false; {disables Event PaintBox1Paint()}
  79.    Picture1:=TPicture.Create;
  80.    PaintBox1.Align:=alClient;
  81.  
  82.    if ParamCount > 0 then
  83.       self.Color:=clLime; // influences the wrong rectangular color
  84.    end; {initialize}
  85.  
  86. procedure TForm1.FormActivate(Sender: TObject);
  87.    const first: boolean = true;
  88.    begin
  89.    if not first then exit; {run the following code only once}
  90.  
  91.    first:=false;
  92.    initialize; {does the complete initialization}
  93.    loadAndShowPicture(PictureFile); {loads and shows a picture file}
  94.    end;
  95.  
  96. procedure TForm1.FormDestroy(Sender: TObject);
  97.    begin
  98.    Picture1.Free;
  99.    end;
  100.  
  101. end.

I attached a compilable demo with a demo picture file.

Versions:
 - Windows 7 and 10 both with Lazarus 2.0.10 and 3.6.0
 - Linux Ubuntu 22.04 with Lazarus 2.0.10 and 3.4.0

Can somebody help? Thanks in advance.
« Last Edit: November 22, 2024, 12:34:38 pm by Hartmut »

Dzandaa

  • Sr. Member
  • ****
  • Posts: 404
  • From C# to Lazarus
Hi,

Just a quick test with BGRABitmap:

B->
Regards,
Dzandaa

Hartmut

  • Hero Member
  • *****
  • Posts: 891
Thank you Dzandaa for your post. But it uses BGRABitmap and does not solve my issue, which is part of a bigger program, which does not use BGRABitmap and I don't want to change the horse in the home stretch of the race.

LV

  • Full Member
  • ***
  • Posts: 189
Maybe:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.PaintBox1Paint(Sender: TObject);
  2. {Event "OnPaint" of PaintBox1}
  3. begin
  4.   if BM_valid then {only if the Bitmap for this Event is valid}
  5.     PaintBox1.Canvas.Draw(0, 0, Picture1.Bitmap);
  6.  
  7.   // add here
  8.   PaintBox1.Canvas.Pen.Color := clRed;
  9.   PaintBox1.Canvas.Pen.Width := 3;
  10.   PaintBox1.Canvas.Brush.Style := bsClear;
  11.   PaintBox1.Canvas.Rectangle(100, 100, 200, 200);
  12. end;
  13.  

and see the screenshot (on Windows)   ;)
« Last Edit: November 19, 2024, 05:41:51 pm by LV »

wp

  • Hero Member
  • *****
  • Posts: 12516
Yes, you probably only want to draw on the Canvas of the Paintbox and keep the loaded bitmap untouched like LV showed you.

But if you really want to draw on the bitmap itself (and maybe later save it together with your drawing) you must know that a bitmap too easily gets the 32-bit pixelformat with the transparency channel (alpha) as 4th bit. When you now draw on this canvas in clRed you are using a 4-byte number with the alpha byte zero (clRed = $000000FF). This means that the color is considered to be transparent, and thus you see the background of the paintbox.

In order to avoid this you must make sure that your Picture has a 24bit canvas. Maybe there are better ways to do this, but this works:
- Draw the image on a bitmap created with a pf24bit pixelformat.
- Assign this image to the Picture.Bitmap

Code: Pascal  [Select][+][-]
  1. procedure TForm1.loadAndShowPicture(fspec: string);
  2.    {loads and shows picture file 'fspec'}
  3. var
  4.   bmp: TBitmap;
  5. begin
  6.   BM_valid:=false;              {disables Event PaintBox1Paint()}
  7.   Picture1.LoadFromFile(fspec);
  8.  
  9.   // Make sure that the Picture.Bitmap does not have pixelformat pf32Bit.
  10.   bmp := TBitmap.Create;
  11.   try
  12.     bmp.PixelFormat := pf24bit;
  13.     bmp.SetSize(Picture1.Width, Picture1.Height);
  14.     bmp.Canvas.Draw(0, 0, Picture1.Bitmap);
  15.     Picture1.Bitmap.Assign(bmp);
  16.   finally
  17.     bmp.Free;
  18.   end;
  19.  
  20.   self.SetBounds(20,20,Picture1.Width,Picture1.Height);
  21.  
  22.   apply_Edits(Picture1.Bitmap); {draws a RED rectangle}
  23.   BM_valid:=true;               {enables Event PaintBox1Paint()}
  24.   PaintBox1.Invalidate;
  25. end; {loadAndShowPicture}
« Last Edit: November 19, 2024, 05:54:23 pm by wp »

Hartmut

  • Hero Member
  • *****
  • Posts: 891
Thank you LV for your post, but this trick is not applicable in my real world program, where multpile changes to the original picture are possible (incl. cropping and rotating and scaling) and for speed and scrolling reasons I need 1 Bitmap with the complete changed picture, which then can easily be displayed in Event 'PaintBox1Paint'.



Thank you very much wp for this explanations and your suggestion. Maybe I have underestimated the issue and abbreviated my real world program too drastically to this demo.

In my real world program there are 2 more Bitmaps which "connect" Picture1.Bitmap and the PaintBox:
 - Picture1.Bitmap always contains the original picture and is only changed in the moment, when and if changes have to be written to disk (this works, even on Windows)
 - from Picture1.Bitmap the original picture is copied to a temporary Bitmap 'BM0', in which several changes can be made (drawing colored rectangles, cropping and rotating incl. an undo feature)
 - this temporary Bitmap 'BM0' is then copied and scaled by a selectable scaling factor to another Bitmap 'BM3', which is used in Event 'PaintBox1Paint' to draw it (incl. scrolling on a TScrollBox). This Bitmap 'BM3' is mainly for displaying and scrolling speed.

I will see, how I can apply your suggestion to this "chain" in my real world program and report then. Maybe tomorrow. Because applying all changes to Picture1.Bitmap before saving works (even on Windows!), my "only problem" is the chain from Picture1.Bitmap => temporary Bitmap 'BM0' => Bitmap 'BM3' => PaintBox.

Do you have an idea why this issue only occurs on Windows and not on Linux?

wp

  • Hero Member
  • *****
  • Posts: 12516
If I understand your requirements correctly then the intermediate bitmap on which you draw, BM0, must be 24 bits per pixel. I did not mention, though, that your input image must not be alpha-channel transparent because its transparency would be removed when going to 24 bpp. In this case you could do all the drawing (and scaling) in a TLazIntfImage (https://wiki.freepascal.org/Developing_with_Graphics#Working_with_TLazIntfImage,_TRawImage_and_TLazCanvas). But, to be honest, I think needing some more or less complex image processing is a good argument for switching to TBGRABitmap.

Do you have an idea why this issue only occurs on Windows and not on Linux?
When you insert the following code after loading the jpeg image
Code: Pascal  [Select][+][-]
  1.    Picture1.LoadFromFile(fspec);
  2.    Caption := Format('%d bpp, Depth=%d', [
  3.      Picture1.Bitmap.RawImage.Description.BitsPerPixel,
  4.      Picture1.Bitmap.RawImage.Description.Depth
  5.    ]);
and run this in both Windows and Linux you'll see that both of them reserve 32 bits for a pixel, but Linux evaluates only 24 of them (Depth = 24), i.e. Linux ignores the alpha channel which would be included in the ignored 8 bits. However, when you load an alpha-transparent png image, Linux will also switch to Depth=32, and you will have the same issue as in Windows.

Hartmut

  • Hero Member
  • *****
  • Posts: 891
As you had said, I only had to set the PixelFormat of Bitmap 'BM0' to pf24bit.
My old code was:
Code: Pascal  [Select][+][-]
  1. BM0:=TBitmap.Create;
  2. BM0.Assign(Picture1.Bitmap);
  3. apply_Edits(BM0);
  4. ...

I changed it to:
Code: Pascal  [Select][+][-]
  1. BM0:=TBitmap.Create;
  2. BM0.PixelFormat:=pf24bit;
  3. BM0.SetSize(Picture1.Width,Picture1.Height);
  4. BM0.Canvas.Draw(0,0,Picture1.Bitmap);
  5. apply_Edits(BM0);
  6. ...

This works on Windows and Linux pefectly with all JPG and PNG files I could test. With GIF and ICO files sometimes the background color gets wrong, but this is not a problem for me, because this files are not writeable via Picture1.SaveToFile. Now I know that this could be solved by switching to TLazIntfImage or TBGRABitmap.

I checked your link https://wiki.freepascal.org/Developing_with_Graphics#Working_with_TLazIntfImage,_TRawImage_and_TLazCanvas but did not understand much, because there too many things are referenced which I don't know.

I can confirm that Picture1.Bitmap.RawImage.Description.Depth on Windows is 32 while on Linux it is 24.

One last question: does Picture1.Bitmap.RawImage.Description.BitsPerPixel contain always the number of bits, which are stored in the picture file, which was loaded via Picture1.LoadFromFile? Or where do I find this value?

Again thank you very much wp for your valuable help!

wp

  • Hero Member
  • *****
  • Posts: 12516
An excellent description of FPImage has been written by Michael Van Canneyt: https://www.freepascal.org/~michael/articles/canvas/canvas.pdf

It does not cover TRawImageDescription, though, which describes the arrangement and meaning of the individual bits of each pixel. For example, when you CTRL+click on the "Description" in "Picture1.Bitmap.RawImage.Description", the IDE will open the declaration of TRawImageDescription, and you will see lots of comments explaining the elements of this record:
Code: Pascal  [Select][+][-]
  1. // in GraphType
  2.  
  3. type
  4.   TRawImageDescription = object
  5.     Format: TRawImageColorFormat;
  6.     Width: cardinal;
  7.     Height: cardinal;
  8.     Depth: Byte; // used bits per pixel
  9.     BitOrder: TRawImageBitOrder;
  10.     ByteOrder: TRawImageByteOrder;
  11.     LineOrder: TRawImageLineOrder;
  12.     LineEnd: TRawImageLineEnd;
  13.     BitsPerPixel: Byte; // bits per pixel. can be greater than Depth.
  14.     RedPrec: Byte;      // red or gray precision. bits for red
  15.     RedShift: Byte;     // bitshift. Direction: from least to most significant
  16.     GreenPrec: Byte;
  17.     GreenShift: Byte;
  18.     BluePrec: Byte;
  19.     BlueShift: Byte;
  20.     AlphaPrec: Byte;
  21.     AlphaShift: Byte;
  22.  
  23.     // The next values are only valid, if there is a mask (MaskBitsPerPixel > 0)
  24.     // Masks are always separate with a depth of 1 bpp. One pixel can occupy
  25.     // one byte at most
  26.     // a value of 1 means that pixel is masked
  27.     // a value of 0 means the pixel value is shown
  28.     MaskBitsPerPixel: Byte; // bits per mask pixel, usually 1, 0 when no mask
  29.     MaskShift: Byte;        // the shift (=position) of the mask bit
  30.     MaskLineEnd: TRawImageLineEnd;
  31.     MaskBitOrder: TRawImageBitOrder;
  32.  
  33.     // The next values are only valid, if there is a palette (PaletteColorCount > 0)
  34.     PaletteColorCount: Word;   // entries in color palette. 0 when no palette.
  35.     PaletteBitsPerIndex: Byte; // bits per palette index, this can be larger than the colors used
  36.     PaletteShift: Byte;        // bitshift. Direction: from least to most significant
  37.     PaletteLineEnd: TRawImageLineEnd;
  38.     PaletteBitOrder: TRawImageBitOrder;
  39.     PaletteByteOrder: TRawImageByteOrder;
  40.    
  41.     // don't use a constructor here, it will break compatibility with a record
  42.     procedure Init;
  43.  
  44.     // 1-bit mono format
  45.     procedure Init_BPP1(AWidth, AHeight: integer);
  46.  
  47.     // 16-bits formats
  48.     procedure Init_BPP16_R5G6B5(AWidth, AHeight: integer);
  49.  
  50.     // Formats in RGB order
  51.     procedure Init_BPP24_R8G8B8_BIO_TTB(AWidth, AHeight: integer);
  52.     procedure Init_BPP24_R8G8B8_BIO_TTB_UpsideDown(AWidth, AHeight: integer);
  53.     procedure Init_BPP32_A8R8G8B8_BIO_TTB(AWidth, AHeight: integer);
  54.     procedure Init_BPP32_R8G8B8A8_BIO_TTB(AWidth, AHeight: integer);
  55.  
  56.     // Formats in Windows pixels order: BGR
  57.     procedure Init_BPP24_B8G8R8_BIO_TTB(AWidth, AHeight: integer);
  58.     procedure Init_BPP24_B8G8R8_M1_BIO_TTB(AWidth, AHeight: integer);
  59.     procedure Init_BPP32_B8G8R8_BIO_TTB(AWidth, AHeight: integer);
  60.     procedure Init_BPP32_B8G8R8_M1_BIO_TTB(AWidth, AHeight: integer);
  61.     procedure Init_BPP32_B8G8R8A8_BIO_TTB(AWidth, AHeight: integer);
  62.     procedure Init_BPP32_B8G8R8A8_M1_BIO_TTB(AWidth, AHeight: integer);
  63.  
  64.     function GetDescriptionFromMask: TRawImageDescription;
  65.     function GetDescriptionFromAlpha: TRawImageDescription;
  66.  
  67.     //returns indices of channels in four-element array
  68.     procedure GetRGBIndices(out Ridx, Gidx, Bidx, Aidx:Byte);
  69.  
  70.     function BytesPerLine: PtrUInt;
  71.     function BitsPerLine: PtrUInt;
  72.     function MaskBytesPerLine: PtrUInt;
  73.     function MaskBitsPerLine: PtrUInt;
  74.  
  75.     function AsString: string;
  76.     function IsEqual(ADesc: TRawImageDescription): Boolean;
  77.   end;
  78.   PRawImageDescription = ^TRawImageDescription;
  79.  

Here, BitsPerPixel is to be understood as the number of bits occupied in memory by each pixel. Depth, on the other hand, is the number of bits used from the bits per pixel for displaying the image - the comment says that Depth can be smaller than BitsPerPixel. Reserving 32 bpp for a depth of 24bpp has the advantage that memory can be scanned faster if pixels are arranged in 4-byte-steps.

Hartmut

  • Hero Member
  • *****
  • Posts: 891
Thanks wp for this additional informations. I read the PDF from Michael Van Canneyt and added it to my list of Graphic documentation links.

I checked type 'TRawImageDescription' and it contains more comments than many sources of the LCL have.

What I did not find and still would like to know: how can I query the number of bits per pixel, which are stored in the picture file, which was loaded via Picture1.LoadFromFile? Does somebody know this?

wp

  • Hero Member
  • *****
  • Posts: 12516
Read the file headers. But be aware that upon loading the graphics unit may convert the pixelformat to anything appropriate. Therefore my question: What do you need this information for?

Hartmut

  • Hero Member
  • *****
  • Posts: 891
I only wanted to display this value just for curiosity together with other properties of the picture file. I thought this value would exist somewhere in class 'TPicture'. But from your answer I understand that this is not the case. It's not worth to spend the effort to analyze the file headers of each possible file type.

 

TinyPortal © 2005-2018