Recent

Author Topic: Why does this simple TImage canvas rectangle not save properly?  (Read 821 times)

QuinnMartin

  • New Member
  • *
  • Posts: 28
Why does this simple TImage canvas rectangle not save properly?
« on: February 25, 2023, 07:00:04 pm »
I have this test source code that is supposed to load a JPG image, draw a rectangle on top of the image, then save the output.  When I run it, it loads the JPG properly, correctly draws the rectangle onscreen, and saves the JPG properly, but the rectangle does not appear in the saved image as it appeared on the screen.

I have a different version of this code where the rectangle actually vanishes from the on-screen image when the SaveToFile line is executed!  I really don't understand what is going on.  If I try drawing the rectangle to Image1.picture.bitmap.canvas instead of Image1.canvas, it doesn't draw the rectangle correctly and I just get a gray, opaque area where the rectangle should be.

I'm probably being an idiot but I need some pointers on what I am missing here.  I tried searching the Internet for help but I can't find anything except significantly more complicated examples.

Code: Pascal  [Select][+][-]
  1.   s1 := 'c:\something\source.jpg';
  2.   Image1.Picture.LoadFromFile(s1);
  3.   with Image1.Canvas do  // Draw rectangle overlay
  4.     begin
  5.       brush.style := bsSolid;
  6.       brush.color := $0000AA;
  7.       Rectangle(200,200,300,300);
  8.     end;
  9.   Image1.Picture.SaveToFile('c:\something\destination.jpg');
  10.  
« Last Edit: February 25, 2023, 07:02:39 pm by QuinnMartin »

circular

  • Hero Member
  • *****
  • Posts: 4220
    • Personal webpage
Re: Why does this simple TImage canvas rectangle not save properly?
« Reply #1 on: February 26, 2023, 10:31:16 am »
TImage can be confusing, so relax and take a deep breath.

TImage is a visual component, so its Canvas property is about its surface on the form. It is not the same as the bitmap it contains.

So it is indeed expected that the rectangle would not be included in the saved image. Also, if for some reason TImage thinks it has changed, it will refresh and erase anything drawn on its canvas.

Normally drawing on Image1.Picture.Bitmap.Canvas should work. This seems a bug to me. Maybe you can avoid this by first loading the image into a TBitmap and then assign it image to Image1.Picture:
Code: Pascal  [Select][+][-]
  1.   bmp := TBitmap.Create;
  2.   bmp.LoadFromFile(s1);
  3.   with bmp.Canvas do  // Draw rectangle overlay
  4.   begin
  5.       brush.style := bsSolid;
  6.       brush.color := $0000AA;
  7.       Rectangle(200,200,300,300);
  8.   end;
  9.   Image1.Picture.Assign(bmp);
  10.   bmp.Free;
Conscience is the debugger of the mind

QuinnMartin

  • New Member
  • *
  • Posts: 28
Re: Why does this simple TImage canvas rectangle not save properly?
« Reply #2 on: March 01, 2023, 11:21:01 pm »
Thanks, I tried your code exactly as written but for some reason it's (incorrectly) plotting a gray rectangle on screen but saving a red rectangle (correctly) to the file.

Any idea why Lazarus is inconsistent with how it handles the bitmaps?  There is no reason it should be drawing a gray rectangle, we obviously set the brush to $0000AA.

wp

  • Hero Member
  • *****
  • Posts: 11916
Re: Why does this simple TImage canvas rectangle not save properly?
« Reply #3 on: March 02, 2023, 12:48:16 am »
In my tests, the bitmap (or jpeg) has 32 bits per pixel after reading the data file. Look at the color value $0000AA: this is a 24-bit value. In 32 bits it would be $000000AA, i.e. the highest byte is 0. But this is the alpha channel. Alpha = 0 means: you paint a fully transparent rectangle over the image. The gray of the rectangle that you see is the gray of the form's background - just give the form a different color and you will understand.

The problem is that the TColor type of the graphics unit is not designed to support 32bpp colors, it uses the 4th byte to distinguish system colors. Therefore, the idea to use the color $FF0000AA (alpha channel = $FF) is not successful.

There are two ways (and probably some more) to fix this issue:

* Create an intermediate 24-bpp bitmap and paint the image on it. This removes the alpha channel.
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   jpg: TJpegImage;
  4.   bmp: TBitmap;
  5. begin
  6.   bmp := TBitmap.Create;
  7.   try
  8.     bmp.PixelFormat := pf24bit;
  9.     jpg := TJpegImage.Create;
  10.     try
  11.       // Load the image
  12.       jpg.LoadfromFile(FN);
  13.       // Paint it on a 24bpp bitmap
  14.       bmp.SetSize(jpg.Width, jpg.Height);
  15.       bmp.Canvas.Draw(0, 0, jpg);
  16.     finally
  17.       jpg.Free;
  18.     end;
  19.     // Draw the rectangle on the 24bpp bitmap
  20.     with bmp.Canvas do
  21.     begin
  22.       Brush.Style := bsSolid;
  23.       Brush.Color := $0000AA;
  24.       Rectangle(200,200,300,300);
  25.     end;
  26.     // Show the bitmap in the Image component
  27.     Image1.Picture.Assign(bmp);
  28.   finally
  29.     bmp.Free;
  30.   end;
  31. end;

* Alternatively you can use a TLazIntfImage which gives access to the alpha channel:
Code: Pascal  [Select][+][-]
  1. uses
  2.   IntfGraphics, LazCanvas;
  3.  
  4. procedure TForm1.Button2Click(Sender: TObject);
  5. var
  6.   jpg: TJpegImage;
  7.   img: TLazIntfImage;
  8.   cnv: TLazCanvas;
  9. begin
  10.   jpg := TJpegImage.Create;
  11.   try
  12.     jpg.LoadFromFile(FN);
  13.     img := jpg.CreateIntfImage;
  14.     try
  15.       cnv := TLazCanvas.Create(img);
  16.       try
  17.         cnv.Brush.FPColor := TColorToFPColor($0000AA);  // This color has max alpha
  18.         cnv.FillRect(200, 200, 300, 300);
  19.       finally
  20.         cnv.Free;
  21.       end;
  22.       jpg.LoadFromIntfImage(img);
  23.       Image1.Picture.Assign(jpg);
  24.     finally
  25.       img.Free;
  26.     end;
  27.   finally
  28.     jpg.Free;
  29.   end;
  30. end;

 

TinyPortal © 2005-2018