Forum > Graphics

Why does this simple TImage canvas rectangle not save properly?

(1/1)

QuinnMartin:
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  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---  s1 := 'c:\something\source.jpg';  Image1.Picture.LoadFromFile(s1);  with Image1.Canvas do  // Draw rectangle overlay    begin      brush.style := bsSolid;      brush.color := $0000AA;      Rectangle(200,200,300,300);    end;  Image1.Picture.SaveToFile('c:\something\destination.jpg'); 

circular:
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  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---  bmp := TBitmap.Create;  bmp.LoadFromFile(s1);  with bmp.Canvas do  // Draw rectangle overlay  begin      brush.style := bsSolid;      brush.color := $0000AA;      Rectangle(200,200,300,300);  end;  Image1.Picture.Assign(bmp);  bmp.Free;

QuinnMartin:
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:
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  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure TForm1.Button1Click(Sender: TObject);var  jpg: TJpegImage;  bmp: TBitmap;begin  bmp := TBitmap.Create;  try    bmp.PixelFormat := pf24bit;    jpg := TJpegImage.Create;    try      // Load the image      jpg.LoadfromFile(FN);      // Paint it on a 24bpp bitmap      bmp.SetSize(jpg.Width, jpg.Height);      bmp.Canvas.Draw(0, 0, jpg);    finally      jpg.Free;    end;    // Draw the rectangle on the 24bpp bitmap    with bmp.Canvas do    begin      Brush.Style := bsSolid;      Brush.Color := $0000AA;      Rectangle(200,200,300,300);    end;    // Show the bitmap in the Image component    Image1.Picture.Assign(bmp);  finally    bmp.Free;  end;end;
* Alternatively you can use a TLazIntfImage which gives access to the alpha channel:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---uses  IntfGraphics, LazCanvas; procedure TForm1.Button2Click(Sender: TObject);var  jpg: TJpegImage;  img: TLazIntfImage;  cnv: TLazCanvas;begin  jpg := TJpegImage.Create;  try    jpg.LoadFromFile(FN);    img := jpg.CreateIntfImage;    try      cnv := TLazCanvas.Create(img);      try        cnv.Brush.FPColor := TColorToFPColor($0000AA);  // This color has max alpha        cnv.FillRect(200, 200, 300, 300);      finally        cnv.Free;      end;      jpg.LoadFromIntfImage(img);      Image1.Picture.Assign(jpg);    finally      img.Free;    end;  finally    jpg.Free;  end;end;

Navigation

[0] Message Index

Go to full version