Write to Image.Picture.JPEG.Canvas lazarus / 39-Graphics ---------------------------------------------------------------------- LacaK · 2015-09-16 17:56 I have on form TImage where is loaded JPEG image (TJPEGImage). I want write to this image. I use for example: Code: [Select]with Image1.Picture.JPEG.Canvas do begin     Pen.Color:=clRed;     Line(0,30,50,80); end; Line is written, but in "transparent" (gray) color. I am not able set other color. Same result when I use for example: Code: [Select]for y:=10 to 50 do       for x:=10 to 50 do begin         //Canvas.Pixels[x,y] := clBlue;         Canvas.Colors[x,y] := TColorToFPColor(clBlue); end; But when I load Bitmap (TBitmap) above works as expected. Where should be problem ? ---------------------------------------------------------------------- taazz · 2015-09-16 18:24 2 questions. 1) why would you try to use a jpeg canvas at all? Simple copy it to a bitmap and draw there. 2) what is the pixel format of the jpeg? Something tells me its either a palette based image or something exotic like 16 or 12 bit. ---------------------------------------------------------------------- rvk · 2015-09-16 18:27 How about this: Code: [Select]  with Image1.Canvas do   begin     Pen.Color := clRed;     Line(0, 30, 50, 80);   end; Or do you need to save the changed image to jpg? ---------------------------------------------------------------------- LacaK · 2015-09-16 18:44 JPEG is normal no specific. Look at attached project please. Run and press "Button2" When I assign Image1.Picture.Bitmap to temporary TBitmap and write there, still does not work ... I need write to Image1.Picture.JPEG.Canvas because when I write only to Image1.Canvas changes are gone when image is scrolled out of visible area and then back ... ---------------------------------------------------------------------- rvk · 2015-09-16 19:19 The fun part is if you make a fourth button and do this: Code: [Select]procedure TForm1.Button4Click(Sender: TObject); begin   with Image1.Picture.Jpeg.Canvas do   begin     Pen.Color := clRed;     Line(0, 30, 50, 80);   end;   Image1.Picture.Jpeg.SaveToFile('c:\temp\test.jpg');   Image1.Picture.Jpeg.LoadFromFile('c:\temp\test.jpg'); end; You do get a red line ---------------------------------------------------------------------- LacaK · 2015-09-16 20:14 Quote from: rvk on September 16, 2015, 09:19:19 pmThe fun part is if you make a fourth button and do this: Code: [Select]procedure TForm1.Button4Click(Sender: TObject); begin   with Image1.Picture.Jpeg.Canvas do   begin     Pen.Color := clRed;     Line(0, 30, 50, 80);   end;   Image1.Picture.Jpeg.SaveToFile('c:\temp\test.jpg');   Image1.Picture.Jpeg.LoadFromFile('c:\temp\test.jpg'); end; You do get a red line  Yes, I noticed it also ... but original question remains : why Button2 does not write red line ? ---------------------------------------------------------------------- derek.john.evans · 2015-09-16 20:26 This is a PixelFormat issue. A lot of JPEG & PNG's are loaded as pf32bit. TCanvas doesn't work with pf32bit. (ie: alpha channels) you need to convert the TBitmap to pf24bit. 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 BitmapMake24Bit(const ABitmap: TBitmap);var  LTempBitmap: TBitmap;begin  if ABitmap.PixelFormat <> pf24bit then begin    LTempBitmap := TBitmap.Create;    try      LTempBitmap.PixelFormat := pf24bit;      LTempBitmap.SetSize(ABitmap.Width, ABitmap.Height);    LTempBitmap.Canvas.Draw(0, 0, ABitmap);      ABitmap.PixelFormat := pf24bit;      ABitmap.Canvas.Draw(0, 0, LTempBitmap);    finally      FreeAndNil(LTempBitmap);    end;  end;end; Example Use: 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.LoadFromFile('MyPicture.jpg');  BitmapMake24Bit(Image1.Picture.Bitmap);  Image1.Picture.Bitmap.Canvas.Pen.Color := clRed;  Image1.Picture.Bitmap.Canvas.Line(0, 0, 100, 100);   Or, start using TBGRABitmap if you want to draw to a 32 bit bitmap. ---------------------------------------------------------------------- derek.john.evans · 2015-09-16 20:31 Note: The above code doesnt handle transparent PNG's, (which have alpha channels) To handle those, you need to set the background of a pf24bit TBitmap to a color (eg: clWhite), and then draw the PNG onto that. The alpha channel will blend partially transparent pixels into clWhite. Download a PNG like this to play with: http://img3.wikia.nocookie.net/__cb20130203061323/disney/images/6/6b/D onald_Duck_transparent.png ---------------------------------------------------------------------- LacaK · 2015-09-17 07:31 Thak you very much. Now it works. It is documented somewhere, that TBitmap must be pf24bit to write to its canvas ? I have read for example: http://wiki.freepascal.org/Developing_with_Graphics but there is no such note. ---------------------------------------------------------------------- rvk · 2015-09-17 08:36 Quote from: LacaK on September 17, 2015, 09:31:01 amIt is documented somewhere, that TBitmap must be pf24bit to write to its canvas ? I have read for example: http://wiki.freepascal.org/Developing_with_Graphics but there is no such note. You may add a note yourself under this header: http://wiki.freepascal.org/Developing_with_Graphics#Painting_on_the_bi tmap_of_a_TImage ---------------------------------------------------------------------- LacaK · 2015-09-17 08:52 Yes I know, but I would leave it for somebody more experienced with graphics ;-) ---------------------------------------------------------------------- taazz · 2015-09-17 09:55 is that a linux/gtk only thing? If memory serves me right I have already used 32bit bitmaps on windows under win32 and qt widgeset for a port of the rmklever progress bar, setting successfully the pixel transparency and color. I do not remember to use the tcolortoFPColor function though I simple used an rgbQuad record to manipulate the color and I used direct data access(delphi scanline equivalent) of the fpimage. In any case I think the canvas.pixels should be able to handle 32bit images with no problem and the various drawing methods should call the native widgetset drawing procedures gdi in win32, qt canvas methods etc, which should all support 32bit colors. ---------------------------------------------------------------------- LacaK · 2015-09-17 18:19 Quote from: taazz on September 17, 2015, 11:55:41 amIn any case I think the canvas.pixels should be able to handle 32bit images with no problem and the various drawing methods should call the native widgetset drawing procedures gdi in win32, qt canvas methods etc, which should all support 32bit colors. Then it is probably bug ? I am on Windows. ---------------------------------------------------------------------- taazz · 2015-09-17 18:26 Quote from: LacaK on September 17, 2015, 08:19:41 pmQuote from: taazz on September 17, 2015, 11:55:41 amIn any case I think the canvas.pixels should be able to handle 32bit images with no problem and the various drawing methods should call the native widgetset drawing procedures gdi in win32, qt canvas methods etc, which should all support 32bit colors. Then it is probably bug ? I am on Windows. Give me some time to run a couple of tests against pure win api. My focused was on porting the component instead of testing the 32bit support so I might have overlooked something. But as a first impression I would say either a bug or a case of "cross platform uniformity". ---------------------------------------------------------------------- derek.john.evans · 2015-09-18 00:00 Quote from: taazz on September 17, 2015, 08:26:35 pmGive me some time to run a couple of tests against pure win api. My focused was on porting the component instead of testing the 32bit support so I might have overlooked something. But as a first impression I would say either a bug or a case of "cross platform uniformity". Arrrr. I think I found the problem.  I'm wrong (again) 32bit drawing _is_ working, but GDI functions set the alpha to 0. Drawing a 32bit TBitmap always uses the alpha channel, which means the GDI drawings are 100% transparent, which explains the gray (TForm) color. Solutions Ive seen involve manually setting the alpha values of the rawimage, which means you loose the alpha of a transparent PNG. I dont know how Delphi solved this one. Maybe they dont treat 32bit TBitmaps as always being transparent? ** EDIT ** Btw: Delphi doesn't clear a TBitmap to black when you change PixelFormat, and when loading a JPEG, you get a 24bit image. Not a 32bit. I guess, I would always draw a JPEG into a 24bit image, and "flattern" 32bit PNG to 24bit. I dont recall 32bit TBitmap editing in Delphi being overly flexable, so, I guess the TBGRABitmap advice still is good. ---------------------------------------------------------------------- derek.john.evans · 2015-09-18 00:54 More information: The code that is the issue is in "win32winapi.inc". ie: TWin32WidgetSet.StretchMaskBlt(). I didn't realize how much preprocessing of 32bit TBitmap's was taking place here! Unfortunately, it looks like the idea that a 32bit bitmap is always treated as "has alpha" is hard coded. 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 function IsAlphaBitmap(ABitmap: HBITMAP): Boolean;var  Info: Windows.BITMAP;begin  FillChar(Info, SizeOf(Info), 0);  Result := (GetObject(ABitmap, SizeOf(Info), @Info) <> 0)        and (Info.bmBitsPixel = 32);end;   Which means, if you want to keep a JPEG as 32bit, and draw to it via TCanvas, you need to use a Windows function to display it. 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   Windows.StretchBlt(Canvas.Handle, 0, 0, FBitmap.Width, FBitmap.Height,    FBitmap.Canvas.Handle, 0, 0, FBitmap.Width, FBitmap.Height, Canvas.CopyMode);     Would be nice if TWin32WidgetSet.StretchBlt() and/or TWin32WidgetSet.StretchMaskBlt() provided a means to draw non-alpha 32bit images. ---------------------------------------------------------------------- derek.john.evans · 2015-09-18 02:00 Sorry everyone. One last post on this topic. A cool soluton just struck me! If we in increase the alpha channel of the TBitmap before editing: 0 becomes 1, 255 becomes 0. We then draw using TCanvas. All the GDI commands set the alpha to 0. Then we decrease the alpha afterwards, and 1 becomes 0, 0 becomes 255. 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 // We must turn off range checks.{$RANGECHECKS OFF} procedure BitmapBeginUpdate(const ABitmap: TBitmap);var  LStart, LEnd: PByte;begin  if ABitmap.PixelFormat = pf32bit then begin    ABitmap.BeginUpdate;    LStart := ABitmap.RawImage.Data;    LEnd := @LStart[ABitmap.RawImage.DataSize];    while LStart < LEnd do begin    Inc(LStart[3]);      Inc(LStart, 4);    end;  end;end; procedure BitmapEndUpdate(const ABitmap: TBitmap);var  LStart, LEnd: PByte;begin  if ABitmap.PixelFormat = pf32bit then begin    LStart := ABitmap.RawImage.Data;    LEnd := @LStart[ABitmap.RawImage.DataSize];    while LStart < LEnd do begin      Dec(LStart[3]);      Inc(LStart, 4);    end;    ABitmap.EndUpdate;  end;end;   Use 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   Image1.Picture.LoadFromFile('test.png');   BitmapBeginUpdate(Image1.Picture.Bitmap);  try    with Image1.Picture.Bitmap do begin      Canvas.Pen.Color := clGreen;      Canvas.Line(0, 0, 100, 100);      Canvas.Pen.Color := clWhite;      Canvas.Brush.Color := clRed;      Canvas.Ellipse(50, 50, 400, 400);    Canvas.Font.Quality := fqNonAntialiased;      Canvas.Font.Color := clBlue;      Canvas.Font.Height := 40;         Canvas.Brush.Style := bsClear;      Canvas.TextOut(0, 0, 'Hello There');    end;  finally  BitmapEndUpdate(Image1.Picture.Bitmap);  end; Works with both JPEG's and transparent PNG's. ie: The original alpha channel is maintained! Cool stuff. ---------------------------------------------------------------------- taazz · 2015-09-18 21:47 I spend some time on the canvas and I can see that although the canvas methods probably support 32bit bitmaps properly the TBrush class converts the tcolor passed to Tfpcolor which is has a color depth of 16bit instead of 8bit that a 32bit bitmap supports and on top of that it replaces what value the reserved byte of the tcolor has with a value of 0 (opaque in windows). It seems that the internals are trying to emulate what would be described as a 64bit size instead of the most commonly used 32bit size. Something like that is supported by high end image processing application like photoshop although I have seen it used only on high quality grayscale images and only as a an intermediate step because as far as I know they can not be printed in any professional (or home) printer. Not even an offsets can reproduce it. So the "32bit is not supported" seems to be true for the color properties only. That's not a small thing that means that TBrush and TPen suddenly become unusable and as a result the canvas class also becomes unusable for 32bit bitmaps / Device contexts on windows at least, forcing me to either drop on win, qt, gtk etc api or use some other library like cairo. That's a shame really. I was hopping to be able to write a TGDIPlusCanvas and a TD2dCanvas for windows now I simple can't rely on them I have to use the TSurface metaphor that cross codebot library has introduced and avoid TCanvas at all costs. Hopefully you guys will get it sorted out in the next releases. Funny thing is that after playing around with the code to run my tests I can't reproduce the problem any more I need to re extract the original code and see the differences. ---------------------------------------------------------------------- derek.john.evans · 2015-09-18 22:12 Hi Taazz. Im unsure what you are testing. I followed the TCanvas.MoveTo/LineTo code down to the Windows Widget layer. (its not that far down). I then replaced the TCanvas code with Windows calls. eg: (CreatePen/SelectObject/MoveToEx/LineTo/DeleteObject). And got the same effect. It was only after looking on some other forums when I found someone saying that GDI commands set the alpha to zero. ie: Transparent. And, then when I relized 32bit bitmaps are always drawn with alpha blending, then it clicked! I think in Delphi, 32 bitmap arn't always drawn with alpha blending like the current Lazarus implementation. I read some stuff about this decision, but couldn't quite understand it. Maybe, its a cross platform issue. The problem with the Pen.Color/Brush.Color, is the upper 8 bits (actually 7 I think), are used for special system colours, so, even if you tried setting those, you would end up with a system mapped colour. Not, a alpha setting. Anyway, Im happy with the current situation, now I know what is going on. ---------------------------------------------------------------------- taazz · 2015-09-18 22:19 Quote from: Geepster on September 19, 2015, 12:12:40 amHi Taazz. Im unsure what you are testing. I followed the TCanvas.MoveTo/LineTo code down to the Windows Widget layer. (its not that far down). Those are not the problem. Go to the TBrush.Color property setter and look closer. There you will see the problem. And no, 0 is opaque on windows its transparent on GTK though. ---------------------------------------------------------------------- derek.john.evans · 2015-09-18 22:23 Quote from: taazz on September 19, 2015, 12:19:55 amThose are not the problem.got to the TBrush.Color property setter and look closer. There you will see the problem. And no, 0 is opaque on windows its transparent on GTK though. I dont understand. If you bypass the Lazarus Widget layer, and call the system (Windows) GDI commands, and get the same result, doesn't that mean, the problem isn't Lazarus? The original poster (LacaK) said, "I am on Windows." ---------------------------------------------------------------------- taazz · 2015-09-18 22:34 Quote from: Geepster on September 19, 2015, 12:23:07 amQuote from: taazz on September 19, 2015, 12:19:55 amThose are not the problem.got to the TBrush.Color property setter and look closer. There you will see the problem. And no, 0 is opaque on windows its transparent on GTK though. I dont understand. If you bypass the Lazarus Widget layer, and call the system (Windows) GDI commands, and get the same result, doesn't that mean, the problem isn't Lazarus? The original poster (LacaK) said, "I am on Windows." only if 1) You are 100% sure that everything set as expected ee set the pf32bit on the bitmap do not assume that it is. 2) You do not use any of the lcl classes on the win api level. That means no Tbitmap no tbrush no Tcolor no tpen nothing. Any way I haven't finished testing I'm reading for now and found the 32bit not supported reason or at least I thought. ---------------------------------------------------------------------- derek.john.evans · 2015-09-18 22:41 Yes, it was 100% Windows code. But, I'll leave you to your testing. O, and: Quote from: taazz on September 19, 2015, 12:19:55 am And no, 0 is opaque on windows its transparent on GTK though. A zero alpha is also transparent on Windows. If you look in "win32winapi.inc". ie: TWin32WidgetSet.StretchMaskBlt(). There is a little catch. If the entire image has a zero alpha, then its drawn as opaque. Ie: The code checks every alpha pixel before making the call. EG: Set the alpha to 1, and you get a non-visible bitmap. ---------------------------------------------------------------------- derek.john.evans · 2015-09-18 23:16 My view on TWin32WidgetSet.StretchMaskBlt(), is it should never have alpha blended 32bit bitmaps by default. That one discussion meant it was required to check every alpha value so that freshly made 32bit TBitmaps that are drawn to using TCanvas would be visible (ie: opaque). But, if you save out those TBitmap's to PNG's, they don't contain matching alpha channels, so, the PNG's are invisible in photo editing app's. So, it was an incomplete hack IMHO, which fails if you first load a JPEG/PNG, since the alpha channel will not be completely clear. So, you end up with transparent TCanvas drawings on JPEG/PNG's. Ideally! The Windows GDI (and other platforms) should set the alpha to 255. That would solve everything, but, Windows doesn't (by default) and Im unsure if that can be changed. I guess, the situation is compounded by JPEG's/PNG's being loaded as 32bit, even if they only require 24bits, and the fact that changing PixelFormat to 24bit clears the image to black. Fine, if you know what is happening. Really irritating if you dont. I remember first moving to Lazarus, and being totally irritated by this situation. It all felt buggy to me. But, once I understood what was happening, I learned the ways to resolve the issue. But, to a Lazarus new comer, this, all looks confusing and buggy. ---------------------------------------------------------------------- LacaK · 2015-09-19 07:49 Quote from: Geepster on September 19, 2015, 01:16:36 amBut, to a Lazarus new comer, this, all looks confusing and buggy. So can there be introduced any method or property which will set/clear alpha ? As current situation is unpleasant - writing to JPEG canvas simply does not give results as expected by most users. Is it worth report it as bug so it will not be forgotten ? (Frankly I do not understand all details about reason why it behaves as behaves) ---------------------------------------------------------------------- LacaK · 2015-11-11 19:18 Quote from: Geepster on September 16, 2015, 10:26:02 pmThis is a PixelFormat issue. A lot of JPEG & PNG's are loaded as pf32bit. TCanvas doesn't work with pf32bit. (ie: alpha channels) you need to convert the TBitmap to pf24bit. I am still fighting with this case. I load BMP file into TLazIntfImage 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   Picture := TPicture.Create;  Picture.LoadFromFile(FileName); // use TPicture when we do not know file format  FIntfImage := Picture.Bitmap.CreateIntfImage; Then I do manipulation with FIntfImage and I copy it to Bitmap: 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  Picture.Bitmap.LoadFromIntfImage(FIntfImage); Bitmap has pf32bit PixelFormat. Until this moment it seems okay, but when I later try write to:  Picture.Bitmap.Canvas I get result with "bad" color (transparency goes into play?) Can I somehow reset alpha chanel ? Btw. all pixels in FIntfImage have Color.alpha=$FFFF (alphaOpaque) Or can I somehow specify, when loading image, that I want pf24bit ? (I do not need alpha chanel) For now I use solution with temporary TBitmap, where I set PixelFormat=pf24bit and draw bitmap to it and then copy it to FIntfImage, but it does not looks to me as nice solution.