GDI+ drawing on a TBitmap - delphi

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.

Related

Delphi - I need to pass a TPrinter canvas to a TBitmap canvas

So I've filled a canvas on my printer object and it prints perfectly. Now I need to save to a file that same canvas that's filled in the printer.canvas. I know I can save a bitmap object and I thought I could just instantiate a bitmap and assign it the printer canvas, but alas, its read-only. This is the line with the error. Anyone has any idea?
bitmapAGuardar.Canvas := Printer.Canvas;
This isn't how to do it, and in fact what you ask can't be done.
Write a function that receives a canvas as its parameter. Have that function perform the necessary drawing. When you need to print to a printer call the function passing the printer canvas. When you need to save to a bitmap, call the function passing the bitmap canvas.
You'll likely need to account for the fact that the printer canvas resolution will be much greater than the resolution you want for the bitmap.

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.

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.

stop flickering

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;

Transparency in TImage

When a new TImage is created it is transparent. After drawing objects to this image I would like to clear them. Note that I need to keep the image transparent as the TImage is being used as an overlay to another image.
Some sort of "clear" function for the TImage would be best. I think I'm missing something simple here, I just could not find any clear function within the TImage menu.
You're not really meant to draw things on a TImage control. You're meant to assign its Picture property and leave it alone. In fact, when you draw on a TImage, you're either drawing transiently by drawing on its Canvas property, or you're modifying the underlying Picture object by drawing on its canvas.
To clear a TImage, simply unassign the Picture property.
Image.Picture := nil;
To draw transient images — something you'll need to repaint whenever the window gets obscured and revealed — use a TPaintBox.

Resources