Lazarus

Programming => Graphics and Multimedia => Graphics => Topic started by: LacaK on September 16, 2015, 07:56:54 pm

Title: Write to Image.Picture.JPEG.Canvas
Post by: LacaK on September 16, 2015, 07:56:54 pm
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 ?
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: taazz on September 16, 2015, 08:24:34 pm
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.
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: rvk on September 16, 2015, 08:27:53 pm
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?
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: LacaK on September 16, 2015, 08:44:42 pm
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 ...
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: rvk on September 16, 2015, 09:19:19 pm
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  :P
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: LacaK on September 16, 2015, 10:14:14 pm
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  :P
Yes, I noticed it also ... but original question remains : why Button2 does not write red line ?
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: derek.john.evans on September 16, 2015, 10:26:02 pm
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][+][-]
  1. procedure BitmapMake24Bit(const ABitmap: TBitmap);
  2. var
  3.   LTempBitmap: TBitmap;
  4. begin
  5.   if ABitmap.PixelFormat <> pf24bit then begin
  6.     LTempBitmap := TBitmap.Create;
  7.     try
  8.       LTempBitmap.PixelFormat := pf24bit;
  9.       LTempBitmap.SetSize(ABitmap.Width, ABitmap.Height);
  10.       LTempBitmap.Canvas.Draw(0, 0, ABitmap);
  11.       ABitmap.PixelFormat := pf24bit;
  12.       ABitmap.Canvas.Draw(0, 0, LTempBitmap);
  13.     finally
  14.       FreeAndNil(LTempBitmap);
  15.     end;
  16.   end;
  17. end;  
  18.  

Example Use:
Code: Pascal  [Select][+][-]
  1.   Image1.Picture.LoadFromFile('MyPicture.jpg');
  2.   BitmapMake24Bit(Image1.Picture.Bitmap);
  3.   Image1.Picture.Bitmap.Canvas.Pen.Color := clRed;
  4.   Image1.Picture.Bitmap.Canvas.Line(0, 0, 100, 100);    
  5.  


Or, start using TBGRABitmap if you want to draw to a 32 bit bitmap.
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: derek.john.evans on September 16, 2015, 10:31:45 pm
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/Donald_Duck_transparent.png
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: LacaK on September 17, 2015, 09:31:01 am
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.
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: rvk on September 17, 2015, 10:36:25 am
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.
You may add a note yourself under this header:
http://wiki.freepascal.org/Developing_with_Graphics#Painting_on_the_bitmap_of_a_TImage
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: LacaK on September 17, 2015, 10:52:41 am
Yes I know, but I would leave it for somebody more experienced with graphics ;-)
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: taazz on September 17, 2015, 11:55:41 am
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.
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: LacaK on September 17, 2015, 08:19:41 pm
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.
Then it is probably bug ?
I am on Windows.
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: taazz on September 17, 2015, 08:26:35 pm
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.
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".
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: derek.john.evans on September 18, 2015, 02:00:17 am
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".

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.
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: derek.john.evans on September 18, 2015, 02:54:40 am
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][+][-]
  1. function IsAlphaBitmap(ABitmap: HBITMAP): Boolean;
  2. var
  3.   Info: Windows.BITMAP;
  4. begin
  5.   FillChar(Info, SizeOf(Info), 0);
  6.   Result := (GetObject(ABitmap, SizeOf(Info), @Info) <> 0)
  7.         and (Info.bmBitsPixel = 32);
  8. end;  
  9.  

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][+][-]
  1.   Windows.StretchBlt(Canvas.Handle, 0, 0, FBitmap.Width, FBitmap.Height,
  2.     FBitmap.Canvas.Handle, 0, 0, FBitmap.Width, FBitmap.Height, Canvas.CopyMode);    
  3.  

 :( Would be nice if TWin32WidgetSet.StretchBlt() and/or TWin32WidgetSet.StretchMaskBlt() provided a means to draw non-alpha 32bit images.
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: derek.john.evans on September 18, 2015, 04:00:26 am
Sorry everyone. One last post on this topic. A cool soluton just struck me! O:-)

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][+][-]
  1. // We must turn off range checks.
  2. {$RANGECHECKS OFF}
  3.  
  4. procedure BitmapBeginUpdate(const ABitmap: TBitmap);
  5. var
  6.   LStart, LEnd: PByte;
  7. begin
  8.   if ABitmap.PixelFormat = pf32bit then begin
  9.     ABitmap.BeginUpdate;
  10.     LStart := ABitmap.RawImage.Data;
  11.     LEnd := @LStart[ABitmap.RawImage.DataSize];
  12.     while LStart < LEnd do begin
  13.       Inc(LStart[3]);
  14.       Inc(LStart, 4);
  15.     end;
  16.   end;
  17. end;
  18.  
  19. procedure BitmapEndUpdate(const ABitmap: TBitmap);
  20. var
  21.   LStart, LEnd: PByte;
  22. begin
  23.   if ABitmap.PixelFormat = pf32bit then begin
  24.     LStart := ABitmap.RawImage.Data;
  25.     LEnd := @LStart[ABitmap.RawImage.DataSize];
  26.     while LStart < LEnd do begin
  27.       Dec(LStart[3]);
  28.       Inc(LStart, 4);
  29.     end;
  30.     ABitmap.EndUpdate;
  31.   end;
  32. end;  
  33.  

Use like:

Code: Pascal  [Select][+][-]
  1.   Image1.Picture.LoadFromFile('test.png');
  2.  
  3.   BitmapBeginUpdate(Image1.Picture.Bitmap);
  4.   try
  5.     with Image1.Picture.Bitmap do begin
  6.       Canvas.Pen.Color := clGreen;
  7.       Canvas.Line(0, 0, 100, 100);
  8.       Canvas.Pen.Color := clWhite;
  9.       Canvas.Brush.Color := clRed;
  10.       Canvas.Ellipse(50, 50, 400, 400);
  11.       Canvas.Font.Quality := fqNonAntialiased;
  12.       Canvas.Font.Color := clBlue;
  13.       Canvas.Font.Height := 40;  
  14.       Canvas.Brush.Style := bsClear;
  15.       Canvas.TextOut(0, 0, 'Hello There');
  16.     end;
  17.   finally
  18.     BitmapEndUpdate(Image1.Picture.Bitmap);
  19.   end;  
  20.  

Works with both JPEG's and transparent PNG's. ie: The original alpha channel is maintained! Cool stuff.
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: taazz on September 18, 2015, 11:47:17 pm
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.
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: derek.john.evans on September 19, 2015, 12:12:40 am
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.

Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: taazz on September 19, 2015, 12:19:55 am
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).

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.
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: derek.john.evans on September 19, 2015, 12:23:07 am
Those 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."

Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: taazz on September 19, 2015, 12:34:10 am
Those 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.
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: derek.john.evans on September 19, 2015, 12:41:53 am
Yes, it was 100% Windows code. But, I'll leave you to your testing.

O, and:
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.
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: derek.john.evans on September 19, 2015, 01:16:36 am
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.
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: LacaK on September 19, 2015, 09:49:52 am
But, 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)
Title: Re: Write to Image.Picture.JPEG.Canvas
Post by: LacaK on November 11, 2015, 08:18:10 pm
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.


I am still fighting with this case.
I load BMP file into TLazIntfImage like:
Code: Pascal  [Select][+][-]
  1.   Picture := TPicture.Create;
  2.   Picture.LoadFromFile(FileName); // use TPicture when we do not know file format
  3.   FIntfImage := Picture.Bitmap.CreateIntfImage;
  4.  
Then I do manipulation with FIntfImage and I copy it to Bitmap:
Code: Pascal  [Select][+][-]
  1.  Picture.Bitmap.LoadFromIntfImage(FIntfImage);
  2.  
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.
TinyPortal © 2005-2018