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.
Related
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.
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.
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.
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.
I have an old program written in borland pascal and in Delphi if I use the Form1.Canvas.LineTo and MoveTo functions I get a flickering effect. Can anyone tell me how to get rid of the flickering?
Try to set DoubleBuffered to true in Form.OnCreate.
A general technique for reducing flicker in animated graphics operations is called double buffering. The idea is that you do all drawing to an offscreen bitmap, then when you've finished rendering the whole scene, copy the entire bitmap to the visible display.
The term also relates to hardware-supported techniques such as the ability to exchange the whole video display buffer with an alternate one, which is used in dedicated systems like console video games.
Although using double buffering is usually the best solution, it is not always the right solution, and definitely not the most memory saving solution. However if you only draw a part of the image, I'd go with that solution as well setting DoubleBuffered to true as mentioned in the other comments.
However if you fill the entire components area every time you draw anyway, you might want to choose a different approach. If you set the ControlStyle to csOpaque you'll avoid having the component erase the background, and thereby removing a source of the flickering, without having to double buffer. This of course requires you to draw on the entire component area, so the solution is only really suitable if you do.
In general however if memory consumption is of no importance, I'd go for the double buffering as well, I just wanted to supply you with an alternative. :)
Easy code sample on the double buffering.
Create Buffer ( TBitmap )
Draw on the Buffer canvas.
Draw the bitmap on the canvas. Form1.Canvas for example.
Buffer := TBitmap.Create;
try
Buffer.Width:=Form1.Width;
Buffer.Height:=Form1.Height;
//clearBuffer
Buffer.Canvas.FillRect(Buffer.Canvas.ClipRect);
//draw Something
Buffer.Canvas.TextOut(0,0,'Hello World');
Buffer.Canvas.Rectangle(0,1,2,3);
//drawBuffer on canvas
Form1.Canvas.Draw(0,0,Buffer);
finally
Buffer.free
end;
Double Buffering is one way but not always sufficient. Imagine you paint a shape at runtime, it depends on your repaint method. Forces the control to repaint its image on the screen could be crucial.
Call Repaint to force the control to repaint its image immediately. If the ControlStyle property includes csOpaque, the control paints itself directly.
So double buffering plus invalidate and [csOpaque] as controlstyle could be an improvement:
var Compass: TPaintBox;
procedure TForm1ForceRepaint(Sender: TObject);
{Called when display type or compass angle changes}
begin
compass.controlstyle:= [csOpaque];
while heading.value<0 do heading.Value:=heading.Value+360;
while heading.value>360 do heading.Value:=heading.Value-360;
compass.Invalidate;
end;
In my case, none of the above-mentioned solutions really worked. I have two graphics which are overlaying one on another. The change of the DoubleBuffered property has solved the flickering problem, but the background graphics was not correctly rendered.
After a small research, I have found two alternative fixes described on page https://delphi-bar.blogspot.com/2012/11/prevent-screen-refresh-and-flickering.html.
LockWindowUpdate(Handle);
try
// Code goes here
finally
LockWindowUpdate(0);
end;
As discussed for example here, it is not always a recommended way. The second option, in my case, has fixed almost completely the flicker problem:
SendMessage(Handle, WM_SETREDRAW, WPARAM(False), 0);
try
// Code goes here
finally
SendMessage(Handle, WM_SETREDRAW, WPARAM(True), 0);
RedrawWindow(Handle, nil, 0, RDW_ERASE or RDW_FRAME or RDW_INVALIDATE or RDW_ALLCHILDREN);
end;