How to determine Canvas dimensions by knowing only its Handle? - delphi

I would like to know width and height of canvas but I know only its HDC.
I've tried with this code:
procedure TForm92.Button1Click(Sender: TObject);
var
hBitmap: HGDIOBJ;
Header: TBitmapInfoHeader;
begin
hBitmap := GetCurrentObject(PaintBox1.Canvas.Handle, OBJ_BITMAP);
GetObject(hBitmap, sizeof(TBitmapInfoHeader), #Header);
ShowMessage(IntToStr(Header.biWidth));
end;
However it does not return me the dimensions of PaintBox1's Canvas, instead I am getting the dimensions of the form on which the PaintBox1 is placed.
What am I doing wrong?

Given only a handle to a device context, it's not generally possible to determine the dimensions of a TCanvas object associated with it. Descendants of TGraphicControl all share the DC of their parent control because only windowed controls can have device contexts, and TGraphicControl objects aren't windowed controls.
When a TGraphicControl descendant (including TPaintBox) needs a canvas, TControlCanvas calls back to the control's GetDeviceContext method. That method returns the DC handle of the parent control, but before returning, it modifies the DC a little:
Result := Parent.GetDeviceContext(WindowHandle);
SetViewportOrgEx(Result, Left, Top, nil);
IntersectClipRect(Result, 0, 0, Width, Height);
That is, it shifts the origin to match the upper left corner of the current control, and it updates the clipping region to exclude anything outside the current control's borders.
In some circumstances, you might be able to determine the canvas's dimensions by inspecting the DC's clipping region (with GetClipRgn), but that's only if the region hasn't been adjusted in any other ways. The clipping region might be smaller than the control's dimensions. (It will never be larger because of the call to IntersectClipRect shown above.)
As you can see, you need more than just the DC handle to get the information you want. Ideally, that will include a TControl reference; then you can just read its Height and Width properties to know the control's and the canvas's dimensions.

The handle of a TCanvas points to a Windows device context (DC). Assuming a display device context (rather then a printer, memory, or information DC), then the logical size of that DC is equal to the (total) screen resolution, as obtainable by GetDeviceCaps or GetSystemMetrics. (Although you can paint beyond that dimensions, output will be cut).
The maximum visible part of a display device context is limited by its associated window size, which is obtainable by WindowFromDC and GetClientRect.
The current visible part may be limited by either the current framework (such as TPaintBox from the Delphi VCL which does not have a window handle and instead depends on the framework to translate the dimensions to the control's size within the parent's DC, which is obtainable by inspecting the control's dimensions) or by the current set clipping region, which is obtainable by GetClipRgn.

Related

What does ClipRect do in Delphi

Could you please explain to me what ClipRect is in Delphi?
I read the documentation, and i did not comprehend it well.
What does the following line do?
FillRect(ClipRect);
Assuming you mean TCanvas.ClipRect, the documentation says:
Read-only property that specifies the boundaries of the clipping rectangle.
Use ClipRect to determine where the canvas needs painting. ClipRect limits the drawing region of the canvas so that any drawing that occurs at coordinates outside the ClipRect is clipped and does not appear in the image.
When handling a form's OnPaint event, the canvas's ClipRect property is set to the rectangle that needs to be painted. Portions of the image that do not overlap the ClipRect do not need to be drawn. Thus, OnPaint routines can use the value of ClipRect to optimize painting, speeding the overall performance of the application.
A clipping region ensures that all painting is limited to that region. So if you set a clipping region that only covers parts of the canvas, any painting outside the clipping region will not be performed.
The documentation links to a simple example. This also uses TCanvas.FillRect(), which fills the given rectangle with the current brush (colour or pattern).

Need help to find FireMonkey equivalent of the VCL TpaintBox.Canvas.Handle in the context of building graph

Preambule:
I'm working with Black Magic Design (BMD) Decklink input card to acquire HD video signal. They provide C++ Sample with their SDK. I've successfully translated the c++ sample into Delphi (VCL). I've also isolated the API call in a TDecklink witch I want it to be available to the Delphi community. It work very well in VCL (I can provide the TDecklnk with a demo app to use it if requested).
Now I need to acquire the signal in a FMX form (but not crosscompile to other platform than Windows). I've tried to modify the TDecklink to be usable in FMX without success.
Core Question:
In my VCL version, I pass a TPaintBox refference to my TDecklink. The TPaintBox is used by the GraphBuilder as area to display the live video.
Here is some line of code I use in the VCL version to assign the TPaintBox to the GraphBuilder:
pWnd := WindowFromDC(FpboxPreview.Canvas.Handle); //WindowFromDC retreive HWND from HDC
hr:= pIVMRWindowlessCtrl.SetVideoClippingWindow(pWnd); // set the bounds of the video to the preview window
if hr = S_OK then
begin
previewRect.Left := FpboxPreview.Left;
previewRect.Right := FpboxPreview.Width;
previewRect.Top := FpboxPreview.Top;
previewRect.Bottom := FpboxPreview.Height;
hr:= pIVMRWindowlessCtrl.SetVideoPosition(nil, #previewRect); // show the whole of the source frame in the whole of the client area of the control
hr:= pIVMRWindowlessCtrl.SetAspectRatioMode(VMR_ARMODE_LETTER_BOX); // maintain the aspect ratio of the video
hr:= pIVMRWindowlessCtrl.SetBorderColor(GetSysColor(COLOR_BTNFACE)); // set the colour of the letter or pillar boxed area
Where PWnd is a HWND
In FMX, what is the best component and parameter to use to provide what the GraphBuilder expect to receive ?
In VCL, TPaintBox is a TGraphicControl descendant that draws onto the HDC of its Parent control's HWND. When the Parent control receives a WM_PAINT message, it draws itself onto the provided HDC as needed, and then temporarily gives that same HDC to each child TGraphicControl when drawing them, clipping the HDC to each child's coordinates and rectangle accordingly. If you try to draw onto a TGraphicControl.Canvas from outside of its Parent control's WM_PAINT handler (which you should never do), TCanvas will temporarily grab the Parent control's HDC using the Win32 API GetDC() function.
Thus, this statement:
pWnd := WindowFromDC(FpboxPreview.Canvas.Handle);
Is effectively the same as this:
pWnd := FpboxPreview.Parent.Handle;
So, you are actually putting your video on the window of the TPaintBox.Parent control, not on the TPaintBox itself. If you want the video associated with its own control, consider using TPanel instead, as it is a TWinControl descendant with its own HWND.
FireMonkey, on the other hand, has no concept of TGraphicControl and TWinControl. Every control is a TControl descendant with an overridden Paint() method to handle any custom drawing onto a TCanvas that is provided by either the parent TForm or the caller of the TControl.PaintTo() method. FireMonkey does not even create an HWND for each control. Only the parent TForm has its own HWND (so it can interact with the OS). Child controls are drawn directly onto that window, adjusting the drawing coordinates and clipping rectangle accordingly as they go along (under the hood, FireMonkey uses DirectX (Windows) or OpenGL (other platforms) for all of its drawing).
So, if you really need an HWND for your video class to display on, you will have to either:
use the HWND of a TForm, which you can get by either passing its Handle property to the
FMX.Platform.Win.WindowHandleToPlatform() function (or the FMX.Platform.Win.FmxHandleToHWND() function on older FireMonkey versions):
uses
..., FMX.Platform.Win;
pWnd := WindowHandleToPlatform(Form1.Handle);
Or passing the TForm itself to the FMX.Platform.Win.FormToHWND() function:
uses
..., FMX.Platform.Win;
pWnd := FormToHWND(Form1);
use the Win32 API directly to create your own HWND as needed and then embed it inside the HWND of a TForm.
Otherwise, you will have to re-think your video UI in FireMonkey. For instance, assuming the video class can provide you with images of the video frames, you can draw them onto the TPaintBox.Canvas from within the TPaintBox.OnPaint event (which is how TPaintBox is meant to be used in the first place, in both VCL and FireMonkey). Or maybe derive your own custom TControl that pulls images from the video class in its own overridden Paint() method. I don't know what your GraphBuilder class is capable of, but BMD provides an SDK for controlling video recording/playback hardware and accessing video data (see this PDF).

How to Invalidate only part of a paintbox/bitmap to optimise its performance?

The question relates to: Drawing on a paintbox - How to keep up with mouse movements without delay?
I was going to at some point ask the question of how to repaint only part of a paintbox without invalidating the whole paintbox, which is slow when there is a lot of drawing going on or in my case when there are a lot of tiles drawn on screen.
From the link above Peter Kostov did touch on the subject briefly in one of his comments:
you can partly BitBlt the offscreen bitmap (only regions where it is changed). This will improve the performance dramatically.
I have limited graphic skills and have never really used BitBlt before but I will be reading more about it after I post this question.
With that said, I wanted to know how exactly could you determine if regions of a bitmap have changed? Does this involve something simple or is there more magic so to speak involved in determining which regions have changed?
Right now I am still painting directly to the paintbox but once I draw to the offscreen buffer bitmap my next step is to optimise the painting and the above comments sound exactly like what I need, only the determining regions that have changed has confused me slightly.
Of course if there are other ways of doing this please feel free to comment.
Thanks.
You don't have to use BitBlt() directly if you draw to an offscreen TBitmap, you can use TCanvas.CopyRect() instead (which uses StretchBlt() internally). But either way, when you need to invalidate just a portion of the TPaintBox (the portion corresponding to the section of the offscreen bitmap that you drew on), you can use InvalidateRect() directly to specify the appropriate rectangle of the TPaintBox, instead of calling TControl.Invalidate() (which calls InvalidateRect() with the lpRect parameter set to NULL). Whenever the TPaintBox.OnPaint event is triggered, InvalidateRect() will have established a clipping rectangle within the canvas, any drawing you do outside of that rectangle will be ignored. If you want to manually optimize your drawing beyond that, you can use the TCanvas.ClipRect property to determine the rectangle of the TPaintBox that needs to be drawn, and just copy that portion from your onscreen bitmap.
The only gotcha is that TPaintBox is a TGraphicControl descendant, so it does not have its own HWND that you can pass to InvalidateRect(). You would have to use its Parent.Handle HWND instead. Which means you would have to translate TPaintBox-relative coordinates into Parent-relative coordinates and vice versa when needed.
You need to take charge of the painting in order to do this:
Call InvalidateRect to invalidate portions of a window.
When handling WM_PAINT you call BeginPaint which yields a paint struct containing the rect to be painted.
All of this requires a window, and unfortunately for you, TPaintBox is not windowed. So you could use the parent control's window handle, but frankly it would be cleaner to use a windowed control.
You could use my windowed paint control from this question as a starting point: How could I fade in/out a TImage? Use the ClipRect of the control's canvas when painting to determine the part of the canvas that needs re-painting.

Fill background by tiling with bitmap

In my FMX Application I have to fill the entire background area of my form using a Bitmap pattern.
The reason why I'm thinking to use this pattern is because the form is freely resizable by the user and whether I set a fixed image the stretch causes the loss of quality of the background.
Is there a way to use a small Bitmap pattern I can repeat (X and Y) to fill the whole form area according to the resizing?
The effect you are looking for is a Tile.
See TTilerEffect and FireMonkey_Image_Effects for documentation.
The properties HorizontalTileCount and VerticalTileCount controls how many times a bitmap will be replicated. Since these values are floats, you need to adapt their values when the form is rescaled.
Add a TImage to the form
Make all the other controls children of it (so they appear on top).
Set the image's Align to alClient.
Load the Bitmap.
Set the WrapMode to imTile.

How to adjust GLCamera to show entire GLScene

I have a GLScene object of varying (but known) size. It is completely surrounded by a TGLDummyCube.
I want to position the GLCamera (with CameraStyle: glPerspective) so that the object is completely visible on screen. I got this running basically - the object is visible, but the distance is sometimes too far, or the object is larger than the screen and clipped.
How can I do that? I suppose that this can be done by a clever combination of camera distance and focal length, but I have not been successful so far.
This seems to be different in GLScene compared to OpenGL. I'm using GLScene and Delphi 2007.
Although varying the camera distance and focal length will change the object's visual size, it has the drawback of also changing the perspective, thus leading to a somewhat distorted view. I suggest to use the camera's SceneScale property instead.
Alas, I have no valid steps to calculate the correct value for that. In my case I have to scale to a cube with varying size, while the window size of the viewer is constant. So I placed two dummycubes at the position of the target cube, each sized to fit either the width or the height of the viewer with appropriate values for SceneScale, camera distance and FocalLength. During runtime I calculate the new SceneScale by the ratio of the target cube size in respect to the dummycube sizes. This works quite well in my case.
Edit: Here is some code I make for the calculations.
ZoomRefX and ZoomRefY are those DummyCubes
TargetDimX and TargetDimY give the size of the current object
DesignWidth and DesignHeight are the size of MyGLView at design time
DesignSceneScale is the camera's SceneScale at design time
The calculation code:
ScaleX := (ZoomRefX.CubeSize*MyGLView.Width)/(DesignWidth*TargetDimX);
ScaleY := (ZoomRefY.CubeSize*MyGLView.Height)/(DesignHeight*TargetDimY);
NewSceneScale := Min(ScaleX, ScaleY)*DesignSceneScale;
The DummyCubes ZoomRefX and ZoomRefY are sized so that they have a small margin to either the left-right or top-bottom edges of the viewing window. The are both positioned so that the front faces match. Also the target object is positioned to match its front face with those of these DummyCubes.
The formulas above allow the window size to be different from design time, but I actually didn't test this feature.
#Andreas if you've been playing with SceneScale (as you mentioned in comments) that means that you are looking for a proper way to fit object within camera view by either changing camera distance/focal length or by resizing object. If so, the easiest way to resize single object to fit the screen is to use its BoundingSphereRadius property like this:
ResizeMultiplier := 2; //play with it, it depends on your camera params
GLFreeForm1.Scale.Scale(ResizeMultiplier / GLFreeForm1.BoundingSphereRadius);
You can add GLDummyCube as root object for all other scene objects and then resize GLDummyCube with method mentioned above.

Resources