Incorrect behavior writing pixels[x,y] to a TImage (Bitmap.Assign bug) lazarus / 39-Graphics ---------------------------------------------------------------------- QuinnMartin · 2024-05-28 08:20 I started writing code for making a rubber band on a TImage using pixels[].  No matter what I did it wanted to draw only light gray lines.  So I created a very simple piece of test code on a pre- existing image (so it's initialized already): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TMainForm.TestButtonClick(Sender: TObject);var  x, y : integer;begin   for x := 20 to 100 do     for y := 20 to 100 do      Image1.Picture.Bitmap.Canvas.Pixels[x, y] := RGB(0, 0, 0);end;  This draws a light gray box!  Why?  I sampled the color in a paint program and get rgb=240,240,240.  It should be drawing a black box. I don't know what I am doing wrong.  Is there some other method for setting color in Lazarus I am missing?  I did try setting PixelFormat to pf24bit and pf32bit in the FormCreate event and it doesn't make any difference. ---------------------------------------------------------------------- RayoGlauco · 2024-05-28 08:40 Maybe RGB doesn't do what you want. Have you tried Graphics.RGBtoColor(0,0,0) ? Or just Image1.Picture.Bitmap.Canvas.Pixels[x, y] := clBlack ---------------------------------------------------------------------- Zvoni · 2024-05-28 08:41 https://lazarus-ccr.sourceforge.io/docs/lcl/graphics/tcolor.html Try RGBToColor instead of RGB-Function? ---------------------------------------------------------------------- Thaddy · 2024-05-28 08:44 Why are you trying to draw using direct pixel access? Use LineTo. Must faster. ---------------------------------------------------------------------- KodeZwerg · 2024-05-28 09:58 Quote from: QuinnMartin on May 28, 2024, 10:20:22 amI don't know what I am doing wrong.You do many things wrong in my opinion. Quote from: QuinnMartin on May 28, 2024, 10:20:22 amIs there some other method for setting color in Lazarus I am missing?Yes. Use TImage as display container and not draw direct inside. Always do it like: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);var  LBitmap: TBitmap;begin  // create class  LBitmap := TBitmap.Create;  try    // step 1: set size  LBitmap.SetSize(Image1.Width, Image1.Height);    // step 2: fill background    LBitmap.Canvas.Brush.Style := bsSolid;    LBitmap.Canvas.Brush.Color := clBlack;    LBitmap.Canvas.FillRect(LBitmap.Canvas.ClipRect);    // step 3: create band    LBitmap.Canvas.Brush.Color := RGBToColor($A4, $A0, $A0); // <- clMedGray    // X1 = x axis start    // Y1 = y axis start    // X2 = x axis end    // Y2 = y axis end    LBitmap.Canvas.FillRect(20, 0, 40, LBitmap.Canvas.ClipRect.Height);    // step 4: transport image to display container    Image1.Picture.Assign(LBitmap);  finally    // Free class    LBitmap.Free;  end;end; ---------------------------------------------------------------------- jamie · 2024-05-28 11:01 Quote from: QuinnMartin on May 28, 2024, 10:20:22 amI started writing code for making a rubber band on a TImage using pixels[].  No matter what I did it wanted to draw only light gray lines.  So I created a very simple piece of test code on a pre-existing image (so it's initialized already): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TMainForm.TestButtonClick(Sender: TObject);var  x, y : integer;begin   for x := 20 to 100 do     for y := 20 to 100 do      Image1.Picture.Bitmap.Canvas.Pixels[x, y] := RGB(0, 0, 0);end;  This draws a light gray box!  Why?  I sampled the color in a paint program and get rgb=240,240,240.  It should be drawing a black box. I don't know what I am doing wrong.  Is there some other method for setting color in Lazarus I am missing?  I did try setting PixelFormat to pf24bit and pf32bit in the FormCreate event and it doesn't make any difference. When I attempt to use "RGB(....)" with standard graphic units, there does not seem to be an existing function of that name? Where did you get it and did you confirm the return results of that function by simply sampling a value returned ? EDIT:   Yes, I found it, it's in the windows unit and I tested it. it returns DWORD of 0 value.   is your format in the image a 24bit or better? ---------------------------------------------------------------------- QuinnMartin · 2024-05-29 06:29 Quote from: KodeZwerg on May 28, 2024, 11:58:37 amYes. Use TImage as display container and not draw direct inside. Always do it like: I think you are getting ahead of me... my Pixel draw routine was just a test function to see what was wrong.  I was originally trying to draw a rubber band (as stated in the original message) but this wasn't working and it led me down a series of tests trying to find out what is wrong with Pixels[]. I did try addressing the container as Image1.Canvas.Pixels[x, y] := RGB(0, 0, 0);  but it makes no difference, it still draws the exact same light gray box.  I will look into the other comments about the RGB function. ---------------------------------------------------------------------- QuinnMartin · 2024-05-29 06:31 Quote from: jamie on May 28, 2024, 01:01:52 pmYes, I found it, it's in the windows unit and I tested it. it returns DWORD of 0 value.   is your format in the image a 24bit or better? Yes, my RGB function is from the Windows unit.  I do have the 24bit mode set and tried 32bit with no difference. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Image1.Picture.Bitmap.PixelFormat := pf24bit; ---------------------------------------------------------------------- QuinnMartin · 2024-05-29 06:33 Quote from: Zvoni on May 28, 2024, 10:41:26 amhttps://lazarus- ccr.sourceforge.io/docs/lcl/graphics/tcolor.html Try RGBToColor instead of RGB-Function? I tried it this way and it still draws the box as light gray. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   for x := 20 to 100 do     for y := 20 to 100 do       Image1.Picture.Bitmap.Canvas.Pixels[x, y] := RGBToColor(0, 0, 0); I did create a standalone program and pixels is working correctly, painting black like it should, so evidently this is a problem specific to that project and not a Lazarus bug.  I also notice in the faulty program, MoveTo/LineTo is also drawing gray even though I set Pen.Color to clBlack.  I am working to figure out why that particular TImage is not rendering correctly. ---------------------------------------------------------------------- Zvoni · 2024-05-29 06:40 What about: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Image1.Picture.Bitmap.Canvas.Pixels[x, y] := clBlack;Still gray? ---------------------------------------------------------------------- Thaddy · 2024-05-29 06:40 As I wrote: that is the wrong approach, use canvas lineto etc. Will add example later. ---------------------------------------------------------------------- QuinnMartin · 2024-05-29 06:49 Quote from: Thaddy on May 29, 2024, 08:40:18 amAs I wrote: that is the wrong approach, use canvas lineto etc. Will add example later. OK thanks, appreciate the quick response.  I will remember to start doing it that way. I do notice all the Canvas line draw operations draw the same light gray color on TImage in the faulty program even when pen.color is set to clBlack.  I am continuing to look around to see what the problem is. ---------------------------------------------------------------------- Zvoni · 2024-05-29 07:07 Quote from: QuinnMartin on May 29, 2024, 08:49:29 amQuote from: Thaddy on May 29, 2024, 08:40:18 amAs I wrote: that is the wrong approach, use canvas lineto etc. Will add example later. OK thanks, appreciate the quick response.  I will remember to start doing it that way. I do notice all the Canvas line draw operations draw the same light gray color on TImage in the faulty program even when pen.color is set to clBlack.  I am continuing to look around to see what the problem is. If you use Black as the Color, and you get gray as a result, it probably has to do with the opacity-factor (Alpha-Channel?) EDIT: If it is the Alpha-Channel, maybe this? https://www.freepascal.org/daily/packages/fcl- image/fpimage/tfpcolor.html https://lazarus- ccr.sourceforge.io/docs/lcl/graphics/fpcolortotcolor.html untested Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Image1.Picture.Bitmap.Canvas.Pixels[x, y] := FPColorToColor(TFPColor(0,0,0,0)); ---------------------------------------------------------------------- rvk · 2024-05-29 07:20 Quote from: QuinnMartin on May 28, 2024, 10:20:22 amI started writing code for making a rubber band on a TImage using pixels[].  No matter what I did it wanted to draw only light gray lines.  So I created a very simple piece of test code on a pre-existing image (so it's initialized already): Can you post that pre-existing image here? It could be the cause of the problems. For example if it has a weird color palet then the pixels need to be in that palet. Quote from: Thaddy on May 29, 2024, 08:40:18 amAs I wrote: that is the wrong approach, use canvas lineto etc. Will add example later. Using lineto would be more appropriate for drawing a box... but isn't the issue here. If you want to manipulate direct pixels on the canvas, the coloring should work too, and it doesn't in this case... that's the issue. Could you create your example where you manipulate pixels directly, setting a certain color for a pixel? ---------------------------------------------------------------------- Zvoni · 2024-05-29 07:23 Quote from: rvk on May 29, 2024, 09:20:04 amQuote from: QuinnMartin on May 28, 2024, 10:20:22 amI started writing code for making a rubber band on a TImage using pixels[].  No matter what I did it wanted to draw only light gray lines.  So I created a very simple piece of test code on a pre-existing image (so it's initialized already): Can you post that pre-existing image here? It could be the cause of the problems. For example if it has a weird color palet then the pixels need to be in that palet. Maybe that pre-existing Picture has a set Alpha-Channel? ---------------------------------------------------------------------- QuinnMartin · 2024-05-29 07:52 Well the pre-existing image I am working with is a 1600x800 TBGRABitmap image copied over to TImage, where I am running these tests.  Since I have a 24-bit image I figured the alpha channels would be ignored and I'd just be getting the RGB data.  The image that ends up drawn in Image1 looks perfectly correct. Here's an interesting development, though: If I clear the image with a FillRect, then black plots correctly when I draw a line.  If I don't do this and I draw a line, I get the erroneous light gray. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   // Clear bitmap first. If we don't do this then the line below plots as light gray.  Image1.Picture.Bitmap.SetSize(Image1.Width, Image1.Height);  Image1.Canvas.Brush.Color := clWhite;  Image1.Canvas.FillRect(0, 0, Image1.Width, Image1.Height);   // Draw a line.  Image1.Canvas.Pen.Color := clBlack;  Image1.Canvas.Pen.Width := 2;  Image1.Canvas.MoveTo(100, 100);  Image1.Canvas.LineTo(500, 500); Is there any possible explanation for this behavior? In case it matters, the code I have to copy TBGRABitmap to TImage is: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i bmp : TBGRABitmap; if not bmp.Empty then    begin      if Assigned(Image1) then        begin          Image1.Picture.Bitmap.Assign(bmp);        Image1.Width := bmp.Width;          Image1.Height := bmp.Height;        Image1.update;  // update immediately        end;    end; ---------------------------------------------------------------------- QuinnMartin · 2024-05-30 02:39 OK, I don't know what the problem is.  I'm still getting an incorrect pen color when drawing on top of TImage.  Here is the full test application with an attached PNG, which draws a white line when it is supposed to be black.  Surely there must be an explanation for why TImage is not drawing the correct color.  Is there a setting that I am overlooking or is my process incorrect? EDIT: Removed a dumb redundancy in the final "with" statement, just syntax. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit graphicstest2main; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls,  BGRABitmap, BGRABitmapTypes,  StdCtrls; type   { TForm1 }   TForm1 = class(TForm)    Button1: TButton;    Image1: TImage;    Label1: TLabel;    procedure Button1Click(Sender: TObject);  private   public   end; var  Form1: TForm1; implementation {$R *.lfm} { TForm1 } procedure TForm1.Button1Click(Sender: TObject);var  x, y : integer;  bmp : TBGRABitmap;  rs : integer;begin   label1.caption := 'Loading...';  label1.update;   // Load into TBGRABitmap and copy to TImage.  bmp := TBGRABitmap.Create('pngtest.png');  try     if not bmp.Empty then    begin        if Assigned(Image1) then        begin          Image1.Picture.Bitmap.PixelFormat := pf24bit;          Image1.Picture.Bitmap.Assign(bmp);          Image1.Width := bmp.Width;          Image1.Height := bmp.Height;          Image1.update;  // update immediately        end;      end;     // If you uncomment this block and let it overwrite the contents, the    // line draw will work correctly.    (*    Image1.Picture.Bitmap.SetSize(Image1.Width, Image1.Height);    Image1.Canvas.Brush.Color := clWhite;    Image1.Canvas.FillRect(0, 0, Image1.Width, Image1.Height);    *)     // Draw a line using a black color.    with Image1.picture.bitmap.canvas do    begin      Brush.Style := bsClear;      Pen.Color := clBlack;      Pen.Width := 2;      MoveTo(100,100);      LineTo(500,500);    end;  finally    bmp.free;    label1.caption := 'Idle.';  end; end; end. ---------------------------------------------------------------------- Paolo · 2024-05-30 04:51 What about you change the brush style to bsSolid ? Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Image1.Canvas.Brush.Style := bsSolid; ---------------------------------------------------------------------- QuinnMartin · 2024-05-30 05:54 Quote from: Paolo on May 30, 2024, 06:51:57 amWhat about you change the brush style to bsSolid ? Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Image1.Canvas.Brush.Style := bsSolid;  I tried it, no change, same problem. ---------------------------------------------------------------------- Thaddy · 2024-05-30 06:13 This seems a bug because it works in Delphi. ---------------------------------------------------------------------- paweld · 2024-05-30 06:15 Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);var  bmp : TBGRABitmap;  ms: TMemoryStream;begin   label1.caption := 'Loading...';  label1.update;   // Load into TBGRABitmap and copy to TImage.  bmp := TBGRABitmap.Create('d:\Downloads\pngtest.png');  try     if not bmp.Empty then      begin        if Assigned(Image1) then        begin          ms := TMemoryStream.Create;          bmp.SaveToStreamAs(ms, ifBmp);          ms.Position := 0;          Image1.Picture.LoadFromStream(ms);          ms.Free;        end;      end;     // Draw a line using a black color.    with Image1.canvas do    begin      Image1.Canvas.Brush.Style := bsClear;      Image1.Canvas.Pen.Color := clBlack;      Image1.Canvas.Pen.Width := 2;      Image1.Canvas.MoveTo(100,100);      Image1.Canvas.LineTo(500,500);    end;  finally    bmp.free;    label1.caption := 'Idle.';  end;end; ---------------------------------------------------------------------- rvk · 2024-05-30 06:59 Quote from: QuinnMartin on May 30, 2024, 04:39:54 amOK, I don't know what the problem is.  I'm still getting an incorrect pen color when drawing on top of TImage.  Here is the full test application with an attached PNG, which draws a white line when it is supposed to be black.  Surely there must be an explanation for why TImage is not drawing the correct color.  Is there a setting that I am overlooking or is my process incorrect?Why are you using TBGRABitmap ????? Just loading the image in the TImage and then writing on TImage.Picture.Bitmap.Canvas works fine. Also note: You need to use TImage.Picture.Bitmap.Canvas. You CAN'T use TImage.Canvas after you loaded an image to it !!!!!! TImage.Picture.Bitmap has 'taken over' and you need to do your changes on that Canvas ! Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button1Click(Sender: TObject);begin  label1.Caption := 'Loading...';  label1.Update;  try    Image1.Picture.LoadFromFile('c:\temp\pngtest.png');    Image1.Update;    Image1.Picture.Bitmap.Canvas.Brush.Style := bsClear;    Image1.Picture.Bitmap.Canvas.Pen.Color := clBlack;    Image1.Picture.Bitmap.Canvas.Pen.Width := 2;    Image1.Picture.Bitmap.Canvas.MoveTo(100, 100);    Image1.Picture.Bitmap.Canvas.LineTo(500, 500);  finally    label1.Caption := 'Idle.';  end;end; ---------------------------------------------------------------------- QuinnMartin · 2024-05-30 07:50 Quote from: rvk on May 30, 2024, 08:59:38 amWhy are you using TBGRABitmap ????? Just loading the image in the TImage and then writing on TImage.Picture.Bitmap.Canvas works fine. As mentioned upthread, I wrote that code to test a very specific problem.  That problem is why are the TImage canvas methods painting in some random color instead of clBlack?  Me using TBGRAbitmap is beside the point, I am using that because I stumbled into the problem that way, and now I am trying to find out why TImage is misbehaving so I can understand the problem better and figure out if TImage is bugged.  I am guessing maybe the bitmap copy from TBGRABitmap is corrupting the TImage. I switched the code as you suggested to paint to Image1.picture.bitmap.canvas and there is no change, clBlack still paints as rgb 240,240,240. Obviously I should be using something like TBGRAVirtualScreen to display the TBGRABitmap and I likely will start adapting to use that component, but I was hoping to figure out a quick fix so I could continue using TImage in my old legacy apps as a quick visual displayer for TBGRABitmap without a lot of additional coding. All my code is there upthread along with the sample PNG, anyone willing to test this will probably get the same weird results. ---------------------------------------------------------------------- QuinnMartin · 2024-05-30 08:00 Quote from: paweld on May 30, 2024, 08:15:17 am          ms := TMemoryStream.Create;           bmp.SaveToStreamAs(ms, ifBmp);           ms.Position := 0;           Image1.Picture.LoadFromStream(ms);           ms.Free; Wow, this fixes it.  Why would this TMemoryStream work and not Bitmap.Assign?  Is Bitmap.Assign hosed? ---------------------------------------------------------------------- paweld · 2024-05-30 08:23 Because when saving as Bmp stream set color palette to 24 bit, and using Assign you copy along with the alpha canal. Setting PixelFormat to 24 bit is done before assigning, so assigning overrides the previously set option, and trying to change PixelFormat after assigning doesn't give the expected results - there may be an error as @Thaddy mentioned. ---------------------------------------------------------------------- wp · 2024-05-30 10:35 Quote from: rvk on May 30, 2024, 08:59:38 amJust loading the image in the TImage and then writing on TImage.Picture.Bitmap.Canvas works fine. No, it does not, drawing a line on TImage.Picture.Bitmap.Canvas results in a gray line. The line is gray because TImage expands the loaded image to 32 bpp, i.e. the image has an alpha channel now. In case of a non-transparent jpg, all alpha values are 255 (opaque). But when you draw on such an image, e.g. black (with R=0, G=0, B=0), the canvas erases the alpha byte to zero (black = $00000000 = (red=0, green=0, blue=0, alpha=0), and alpha=0 means: transparent. You can see this, when you change the color of the form - the color of the drawn line on the image changes, too! Maybe there are other ways, but for me the simplest way to directly draw on the TImage.Picture.Bitmap.Canvas is via a LazIntfImage: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i uses  FPImage, IntfGraphics, LazCanvas; procedure TForm1.Button4Click(Sender: TObject);var  img: TLazIntfImage;  canv: TLazCanvas;begin  Image1.Picture.LoadFromFile('C:\Lazarus\lazarus- main_fpc3.2.2\images\splash_source\cheetah.jpg');  img := Image1.Picture.Bitmap.CreateIntfImage;  try    canv := TLazCanvas.Create(img);    canv.pen.FPColor := colRed;    canv.Pen.Width := 5;    canv.Line(0, 0, 100, 100);    Image1.Picture.Bitmap.LoadFromIntfImage(img);  finally    canv.Free;  img.Free;  end;end;But since an intermediate object is already needed here, and since LazIntfImage is a bit tricky sometimes, it really is the best to load the image into a temporary bitmap, do the drawing here, and then load this bitmap into the image as already suggested by KodeZwerg in the beginning. (However, I'd transfer the bitmap via stream (rather than using Assign) because this way the transparency is preserved). ---------------------------------------------------------------------- QuinnMartin · 2024-05-31 02:13 Quote from: paweld on May 30, 2024, 10:23:58 amBecause when saving as Bmp stream set color palette to 24 bit, and using Assign you copy along with the alpha canal. Setting PixelFormat to 24 bit is done before assigning, so assigning overrides the previously set option, and trying to change PixelFormat after assigning doesn't give the expected results - there may be an error as @Thaddy mentioned. Thanks all, I appreciate you all checking into this.  I thought there was some oversight on my part and I couldn't figure it out. I just went ahead and replaced TImage with TBGRAVirtualScreen and am doing all the pixel and line draws on the TBGRABitmap now, which I think is the recommended way. However I have saved a note of this TMemoryStream method and will use it if I have to pass a bitmap to TImage again. ---------------------------------------------------------------------- jipété · 2024-06-16 09:05 Hello, Quote from: wp on May 30, 2024, 12:35:32 pm... The line is gray because TImage expands the loaded image to 32 bpp, i.e. the image has an alpha channel now. in Windows, maybe ? But not in Linux, clearly not ! I"ve done the following : Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   ...  Imag e1.Picture.LoadFromFile('/usr/share/lazarus/3.4.0/images/splash_source /cheetah.jpg'); // first working line of the Button4 procedure  Image1.Picture.SaveToFile('/tmp/test32','bmp'); // second line added by me  ... in https://en.wikipedia.org/wiki/BMP_file_format#DIB_header_(bitmap_in formation_header) one can read, in the table, line 1C, Quotethe number of bits per pixel, which is the color depth of the image. Typical values are 1, 4, 8, 16, 24 and 32.If value is 24 [bpp] then one read 18 at address 1C of the bitmap file ; il value is 32 one read 20. And the value of the beginning of the generated file attached below is 18, so 24 bpp... Regards, ---------------------------------------------------------------------- jamie · 2024-06-16 18:49 Hmm has anyone looked into the PEN.MODE ? Pen.Mode can cause adverse effects if not set to pmCopy. ---------------------------------------------------------------------- jipété · 2024-06-17 06:45 Hello, Quote from: jamie on June 16, 2024, 08:49:25 pmHmm has anyone looked into the PEN.MODE ? Pen.Mode can cause adverse effects if not set to pmCopy. The problem was not here but simply the fact that for getting a 32bits result file, one must start with a 32bits source file ! Tip not reported in wp's post, which uses a .jpg file, so 24 bits.