Recent

Author Topic: Write to Image.Picture.JPEG.Canvas  (Read 14350 times)

derek.john.evans

  • Guest
Re: Write to Image.Picture.JPEG.Canvas
« Reply #15 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.
« Last Edit: October 02, 2015, 03:05:47 am by Geepster »

derek.john.evans

  • Guest
Re: Write to Image.Picture.JPEG.Canvas
« Reply #16 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.
« Last Edit: October 02, 2015, 03:05:05 am by Geepster »

taazz

  • Hero Member
  • *****
  • Posts: 5362
Re: Write to Image.Picture.JPEG.Canvas
« Reply #17 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.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

derek.john.evans

  • Guest
Re: Write to Image.Picture.JPEG.Canvas
« Reply #18 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.

« Last Edit: September 19, 2015, 12:14:48 am by Geepster »

taazz

  • Hero Member
  • *****
  • Posts: 5362
Re: Write to Image.Picture.JPEG.Canvas
« Reply #19 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.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

derek.john.evans

  • Guest
Re: Write to Image.Picture.JPEG.Canvas
« Reply #20 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."


taazz

  • Hero Member
  • *****
  • Posts: 5362
Re: Write to Image.Picture.JPEG.Canvas
« Reply #21 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.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

derek.john.evans

  • Guest
Re: Write to Image.Picture.JPEG.Canvas
« Reply #22 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.

derek.john.evans

  • Guest
Re: Write to Image.Picture.JPEG.Canvas
« Reply #23 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.
« Last Edit: September 19, 2015, 01:31:55 am by Geepster »

LacaK

  • Hero Member
  • *****
  • Posts: 566
Re: Write to Image.Picture.JPEG.Canvas
« Reply #24 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)

LacaK

  • Hero Member
  • *****
  • Posts: 566
Re: Write to Image.Picture.JPEG.Canvas
« Reply #25 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.