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