Delphi - Graphics32, draw multiple transparent PNG over JPG as watermak - delphi

After read Graphics32 documentation I can't find a objetive example of use layers.
I just want to compose the following Image:
Layer 1 - Background Image (In JPG) (800x600)
Layer 2 - Transparent PNG as a frame border (800x600)
Layer 3 - Transparent PNG on right bottom with 25º Rotation (90x90)
And this is the expected result:
// uses => GR32, GR32_Layers, GR32_Png, GR32_Image;
procedure TMain.Button1Click(Sender: TObject);
var
// src, dest: TPNGObject; <-- another frustrating try
// r: TRect;
bmp: TBitmap32;
png: TPortableNetworkGraphic32;
rlayer: TCustomLayer;
img1, img2, img3: TImgView32;
begin
bmp := TBitmap32.Create;
bmp.Assign(imgPreview.Picture); // TImage obj already have a JPG loaded
img1 := TImgView32.Create(nil);
img1.Bitmap := bmp;
img2 := TImgView32.Create(nil);
img2.Bitmap.LoadFromFile('C:\\layer2.png');
img3 := TImgView32.Create(nil);
img3.Bitmap.LoadFromFile('C:\\watermark.png');
rlayer := TCustomLayer.Create(nil);
rlayer.LayerCollection.Add(img1.Layers.Items[0]); // [DCC Error] Incompatible types: 'TLayerClass' and 'TCustomLayer' ????
...
How can I add a new layer to collection? And after all, how can I save this?

The LayerCollection.Add method expects to receive a value of type TLayerClass. That is, it wants to receive the class, not an instance of a class. To satisfy the compiler, pass it literally TCustomLayer; the collection will instantiate the given class itself. It will return the instance reference. See for yourself in GR32_Layers.pas.
However, you're taking the wrong approach to begin with. TBitmap32 objects don't have layers. A TImage32 component has layers, which is useful if you want to not only display multiple bitmaps layers together, but also allow the user to interact with the layers; you'd detect which layer is which with the HitTest method, as described in the layer overview. Each layer consists of one graphic; for bitmaps, you probably want to use TBitmapLayer, not just TCustomLayer.
Just to create a new bitmap, you don't need layers at all. (And as long as you're using a Delphi version that understands PNG images, I'm pretty sure you don't even need Graphics32.) Instead, just start with a blank bitmap. Paint the main bitmap where it needs to go, then paint the frame bitmap, and then paint the stamp bitmap. Finally, save the bitmap.
Unless you're actually going to display all the separate bitmaps on a form, you don't need those TImgView32 components. That component is for displaying images on the screen with scroll bars.

Related

GDI+ drawing on a TBitmap

In addition to drawing GDI+ onto a control canvas via TGPGraphics (which has been working fine), I'm also trying to draw onto a TBitmap using GDI+ as well, and then drawing that bitmap to the control canvas. However, nothing actually appears to get drawn.
The following code is within the WM_PAINT message handler, which again works for the actual control canvas, but when creating an equivalent TGPGraphics object and passing this TBitmap handle, nothing gets drawn:
FBitmapCanvas:= CreateGPCanvas(FBitmap.Handle);
try
FBitmapCanvas.DrawLine(FSomePen, P1, P2); //Same pen used to successfully draw to control canvas
finally
FreeAndNil(FBitmapCanvas);
end;
Canvas.Draw(0, 0, FBitmap); //Draw this bitmap to control canvas
CreateGPCanvas looks like so, and is used for both this bitmap and the control:
function CreateGPCanvas(const DC: HDC): TGPGraphics;
begin
Result:= TGPGraphics.Create(DC);
Result.SetInterpolationMode(InterpolationMode.InterpolationModeHighQuality);
Result.SetSmoothingMode(SmoothingMode.SmoothingModeHighQuality);
Result.SetCompositingQuality(CompositingQuality.CompositingQualityHighQuality);
end;
On the other hand, if I don't try to use the TGPGraphics and instead draw a line directly via the TBitmap.Canvas property, it works fine (but of course looks ugly because it's not GDI+). So I know the actual bitmap gets drawn correctly to the control canvas.
FBitmap.Canvas.MoveTo(P1.X, P1.Y);
FBitmap.Canvas.LineTo(P2.X, P2.Y);
What am I doing wrong here, and how do I make the TGPGraphics work on this bitmap canvas?
PS - The only reason I'm using a TBitmap at all is because what I'm actually writing needs to "remember" a portion of what was previously drawn and retain it, rather than repainting it over and over.
Just figured out the problem, and it was a silly mistake.
When creating a TGPGraphics object, instead of passing FBitmap.Handle, it should rather be FBitmap.Canvas.Handle.
You need the handle of the bitmap's canvas, not of the bitmap itself.

GIF animation TImage/Timage32

The goal is to have animated GIFs playing inside image components with good image quality even after resizing one of the image components.
TImage example
This is the resized TImage, very bad image quality but flawless animation:
// GIF = TGIFImage
// TImage = TImage
GIF.Animate := True;
TImage.Stretch := True;
TImage.Proportional := True;
TImage.Picture.Assign(GIF);
TImage32 example
This is a resized TImage32 from the graphics32 library, very good image quality but no animation at all, only the first frame ist visible:
// GIF = TGIFImage
// TImage32 = TImage32
GIF.Animate := True;
TImage32.ScaleMode := smResize;
TImage32.BitmapAlign := baCenter;
TImage32.Bitmap.Assign(GIF);
I need to have the TImage32 component play the animation or for the TImage component to have better resampling.
Graphics32 is not meant to display animations (like in a GIF) out of the box. Furthermore it does not contain a native GIF decoder. The code you show relies on the internal TPicture decoder from Delphi. A conversion to TImage32 with assign will only copy the first frame and thus it will result in a still image.
In order to display animations you need further code. As mentioned in the comments it would make sense to first copy the each frame of the GIF to a TBitmap32 instance (TImage32 has too much overhead). Than you need to perform the animation. If possible this should relate to the display refresh rate or take at least the time interval since the last drawing into account.
If for example your monitor uses 60fps and your gif contains 30 frames per second than you need to display each frame for two refresh cycles. Though, this would be an easy situation.
Things will get slightly more complicated if your gif contains 29 frames per second (for example). You need to develop an algorithm to pick the right frame for the current time.
So far I have not seen any implementation of the above for Graphics32, but it's not that complicated once you know what to do.

Drawing a TImageList glyph to a TDirect2DCanvas

I'm currently about to replace the drawing code for an old component from GDI + UniScribe to Direct2D and DirectWrite (the successors).
So far the transition was straight forward as most of the time all I need to do was to replace calls to the Canvas (class TCanvas) to a custom FDirect2DCanvas instance (class TDirect2DCanvas, from the unit Direct2D).
Unfortunately it doesn't seem that simple when trying to draw a glyph from a TImageList instance onto the FDirect2DCanvas as the draw method is only meant for TCanvas and not for the rather general TCustomCanvas (which is the ancestor of both TCanvas and TDirect2DCanvas).
A solution for this dilemma would be to draw the TImageList glyph to a temporary bitmap and draw this to the TDirect2DCanvas. However, I fear this will probably slow down the drawing performance a lot.
Has anyone so far done this so far? What options do I have?
If you look at how drawing graphic objects to TDirect2DCanvas is implemented you will find that it routes through this routine.
procedure TDirect2DCanvas.StretchDraw(const Rect: TRect; Graphic: TGraphic;
Opacity: Byte);
var
D2DBitmap: ID2D1Bitmap;
D2DRect: TD2DRectF;
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.Assign(Graphic);
D2DBitmap := CreateBitmap(Bitmap);
D2DRect.Left := Rect.Left;
D2DRect.Right := Rect.Right;
D2DRect.Top := Rect.Top;
D2DRect.Bottom := Rect.Bottom;
RenderTarget.DrawBitmap(D2DBitmap, #D2DRect, Opacity/255);
finally
Bitmap.Free;
end;
end;
Let's unpick the steps involved:
Create a temporary bitmap.
Copy the graphic into that bitmap.
Create a ID2D1Bitmap and copy the temporary bitmap into it.
Draw that ID2D1Bitmap onto the render target.
This already looks pretty inefficient. Certainly it would be galling to call this function passing in a TBitmap and have a copy made for no good reason.
This sort of thing is hard to avoid though when you try to blend two distinct graphics frameworks. Your image list is GDI based, and so bound to encounter friction when you try to send it to a Direct2D canvas. There simply is no way to pass GDI bitmaps directly to a Direct2D canvas, they have to be converted to Direct2D bitmaps first.
If performance is what matters to you then you should not be starting from an image list. That will inevitably incur costs as you extract the bitmap from the GDI image list, and then convert it into the equivalent Direct2D object, ID2D1Bitmap.
In order to achieve optimal performance, don't work with image lists. Extract each image from the image list and use TDirect2DCanvas.CreateBitmap to obtain a Direct2D bitmap, ID2D1Bitmap. Store these rather than the image list. Then when you need to draw, call DrawBitmap on RenderTarget, passing a ID2D1Bitmap.

How to draw a TBitmap32 transparently on a TBitmap?

I am trying to draw a source image (GR32 TBitmap32) that contains some fully transparent and also partially transparent pixels on a normal (TBitmap) image while preserving the transparency in the source image.
I use this:
BMP32.DrawTo(BMP.Canvas.Handle, 0, 0);
but the image is not drawn transparently.
Code:
Everything is pretty basic: I have a background bitmap (Bkg) in which I load, at application start up, an image.
Then in procedure Apply I load a second image from disk (the one with transparency) and I draw the it over the background.
var Bkg: TBitmap;
procedure TfrmPhoto.FormCreate(Sender: TObject);
begin
Bkg:= LoadGraphEx(GetAppDir+ 'bkg.bmp'); { Load bitmap from file }
{
Bkg.Transparent:= TRUE;
Bkg.PixelFormat:= pf32bit;
Bkg.TransparentColor:= clPink;
}
end;
procedure TfrmPhoto.Apply2;
VAR
Loader: TBitmap;
BMP32: tbitmap32;
begin
BMP32:= TBitmap32.Create;
TRY
Loader:= TBitmap.Create;
TRY
Loader.LoadFromFile('c:\Transparent.BMP');
BMP32.Assign(Loader);
FINALLY
FreeAndNil(Loader);
END;
{ Mix images }
BMP32.DrawTo(Bkg.Canvas.Handle, 0, 0); <----- The problem is here
imgPreview.Picture.Assign(Bkg); { This is a TImage where I see the result }
FINALLY
FreeAndNil(BMP32);
END;
end;
It worked (but with 2 disadvantages) with TransparentBlt().
I am not happy with the way TransparentBlt handles the semitransparent pixels (the edge of the rotate image). After merging the src image into the background the edges look bad.
In order to use TransparentBlt, I had to define a color (clPink) in the source image as transparent. This means that if the source image has some pink in it, the result will look really nasty (it will be treated as transparent). Let's pray for non-pink images!
If you find a way to transfer the image (while preserving transparency) directly from Bitmap32 into the background please post and I will accept your answer!
A solution (still a hack) I see here is to process everything in a TBitmap32 and then 'export' the final result as TBitmap. I will try this tomorrow.
Update:
Solution:
This is how to merge two TBitmap32 images while preserving transparency:
Dst.CombineMode:= cmBlend;
Dst.DrawMode:= dmBlend;
Src.Draw(0, 0, Dst);
Dst and Src are TBitmap32 images. It doesn't work with TBitmap.
As already suggested by yourself, I would recommend performing blending operations entirely in the TBitmap32 domain! The reason for this is the fact that it offers more choices in regards of blending and it uses MMX/SSE/SSE2 where available (on modern machines always the case). With this one can typically get a notable performance boost in contrast to letting GDI perform the blending.
In particular this is not a good idea as GDI typically does not make use of any SIMD opcodes (MMX/SSE).
It also depends on how much each part changes. For example, typically the background is rather static and need to be transfered into a TBitmpa32 only occassionally on changes (e.g. on VCL style or UI theme changes). With each transfer (GR32 <-> TBitmap and alike) an implict conversion (DIB <-> DDB) might happen, which makes the entire blending an O(n²) operation. So you'd better transfer (with an implicit conversion) to TBitmap32 just once and then perform the blending entirely in the GR32 domain.
On the contrary, if the TBitmap32 is rather static and the TBitmap (or background) changes a lot, it might probably be better to leave GR32 alone and copy the TBitmap32 to TBitmap and perform the blending with GDI. Despite the fact that the GDI blending is slightly slower, you avoid the additional copy. Otherwise your bottleneck will likely be the memory throughput, while processing speed never can touch the limits.
It is dmBlend DrawMode of TBitmap32 to turn on the transparency, but it only works between two TBitmap32 bitmaps. Do not use TBitmap32 with transparent images to draw directly on HDC, Canvas or TBitmap. Use dmBlend and DrawTo or BlockTransfer() to draw on another TBitmap32. For example, to draw transparently on a TBitmap, create an intermediary cache TBitmap32:
Copy the image from TBitmap to the cache TBitmap32;
Apply your transparent image to the cache TBitmap32 using DrawTo or BlockTransfer(), avoid using Canvas or HDC to mix two images because they lose alpha layer information;
Copy the image back from the cache TBitmap32 to your TBitmap.

Timage Transparencies on laptops in Delphi 7

WHAT I AM TRYING TO DO
I am trying to draw multiple graphics to a Timage, These graphics that i Draw consist of ordered layers with Foodfills and lines.
I use multiple buffers to ensure ordering and double buffering.
WHAT I AM DOING
procedure DrawScene();
var
ObjLength,LineLength,Filllength,Obj,lin,angle,i:integer;
Npoints : array[0..1] of Tpoint;
Objmap:TBitmap;
wholemap:TBitmap;
begin
wholemap := TBitmap.Create;
wholemap.Width:=area;
wholemap.height:=area;
ObjLength:=length(Objects);
for Obj:=0 to (ObjLength-1) do
if objects[Obj].Visible then
begin
// create object bitmap
if Objects[obj].Tag='FBOX' then
begin
Objmap := TBitmap.Create;
Objmap.Width:=area;
Objmap.height:=area;
Objmap.Transparent:=true;
Objmap.Canvas.Rectangle((objects[obj].Boundleft-4)+objects[obj].Position.x,area-((objects[obj].boundtop+4)+objects[obj].Position.y),(objects[obj].boundright+4)+objects[obj].Position.x,area-((objects[obj].boundbottom-4)+objects[obj].Position.y));
end;
//draw object
LineLength:=length(objects[Obj].Lines)-1;
angle:=objects[Obj].Rotation;
for lin:=0 to (LineLength) do
begin
for i:=0 to 1 do
begin
Npoints[i] := PointAddition(RotatePoint(objects[obj].Lines[lin].Point[i],angle),objects[obj].Position,false);
end;
Objmap:=DrawLine(Npoints[0].x,Npoints[0].y,Npoints[1].x,Npoints[1].y,objects[obj].Lines[lin].Color,Objmap);
end;
Filllength:=length(objects[Obj].Fills)-1;
for i:=0 to Filllength do
begin
Npoints[0]:=PointAddition(RotatePoint(objects[Obj].Fills[i].Point,objects[Obj].Rotation),objects[Obj].Position,false);
Objmap:=fillpoint( Npoints[0].x, Npoints[0].y,objects[Obj].Fills[i].color,Objmap);
end;
//write object to step frame
wholemap.Canvas.Draw(0,0,Objmap);
Objmap.Free;
end;
// write step frame to Visible Canvas
mainwindow.bufferim.Canvas.Draw(0,0,wholemap);
mainwindow.RobotArea.Picture.Graphic:=mainwindow.bufferim.Picture.Graphic;
wholemap.Free;
end;
WHAT I EXPECT
I expect to see each image object layered on top of one another with each image layer being the complete image for that layer.
im my example it is a robot with a flag behind it.
the flag is drawn first and then the robot.
WHAT I GET(on a pc)
on a pc i get what i expect and all appears to be correct.
WHAT I GET(on a laptop)
On a nearly every laptop and some pc's i only see the robot.
i put in some statments to see if it is drawing the flag and it does. the game can even interact with the flag in the correct manner.
further investigation showed me that it was only showing the last image drawn my "drawscene", and when images were drawn directly to the wholecanvas everthing apeared(this cannot be done for overlapping fill layers reasons)
WHAT I THINK IS HAPPENING
so what i deduced is that the Timage.transparent property of the Timage is not working or is being computed differently on some machines..
i did a test to prove this and made a canvas red. then to that canvas i drew at 0,0 i Timage with property transparent=true with just one dot in the middle to the red canvas. the resuly was a white canvas with a dot in the middle.
I am assuming and findings indicate that machines with very basic graphics drivers seem to treat null or transparent as white where as more powerful machines seem to treat null or transparent as transparent.
this seems to be a bit of a failure due to the fact that the Timage.Transparent property was true.
EDIT:
UPDATE!!!
It would appear to be that on ATI graphics cards if a colour is "null" then it is interpreted in the format of PF24bit and therefore no alpha channel and no transparency.
on Nvidia cards it takes it as PF32bit and treats null as transparent.
the obvious way to get around that woulf be to set the bitmaptype to PF32bit, but that does still not work.
I then assumed that maybe that is not enough and I should make sure that the background is SET to transparent rather than being left as null.. but there are no tools ( that I can see) inside the Timage to set colour with alpha. all canvas drawing functions require a Tcolor that is RGB 24 bit and ony a TcolorRef with RGBA 32 bit would do....
is there a way of drawing with alpha 0?
WHAT I THINK I NEED TO KNOW
How to force the Transparent property to work on all machines
or a way to make laptops not paste in transparent as white
Or a way to achieve the same result.
Anyone have any solutions or ideas?
I have been using an Graphics library (AggPas) to help with drawing graphics and one of the things I've noticed is that I always need a line Bitmap.PixelFormat = pf32bit to get it to draw transparancies.
Having said that I use TransformImage from the AggPas library to copy the Image with a transparent background to another one and AggPas only accepts pf24bit or pf32bits as Pixel formats (otherwise it doesn't attach to the bitmap)
I've seen similar behaviour on different machines in the past (a few years back).
They were caused by different video cards and drivers.
Either the NVideo or the ATI ones were getting the wrong results, but I forgot which ones.
It could be reproduced on both laptops and regular PC's.
So: what video cards and drivers do you use?
--jeroen
I would explicitly set the bitmap format to pf32bit for each bitmap you create to ensure the problem isnt converting colors from 32 to 16 bit (if thats the native video resolution of the bitmaps getting created), which might interfere with how the transparency works. Also, I've had better luck specifically setting the transparency color in the past.
Another option - perhaps a better one in the long run, is to instead set the Alpha Channel on the images (ie, use PNG files), and use the GDI function AlphaBlend() to draw the graphics.

Resources