Another oddity on FMX images: If you use the width of the image (about 300), then spilit it into 30 bits and write into each of the 30 10-wide slots) each with FillRect, you find that what you write into the image does not go where you expect it to. You have to assume the image width is actually a lot smaller than what the image says it is, and I can find nothing that tells you what the relationship is! If that is confusing, try looking at https://59b7770ca3fb18e4e90d-52e2682744779750b5103bbecf998085.ssl.cf2.rackcdn.com/ImageTest.zip
The zip is missing files
Corrected that - sorry! Occasionally Delphi puts files where I don’t expect them (probably my own damned fault!)
You have a zip inside your zip ![]()
Just saying, you can save quite a few lines with
var Rect := tRectF.Create(0,0,Image1.Width,Image1.Height);
I’m not familiar with fmx and the different screen, local, etc coord scaling system …
but it’s somewhere around this area.
I simplified your drawing routine to only do each incremental piece.
This isn’t fully correct … but I think it is easier to consider what the issue might be :
procedure tImageTestMainForm.Timer;
var
ChunkColor: TAlphaColor;
begin
inc(xvalue);
case xvalue of
1..10: ChunkColor := TAlphaColorRec.Blue;
11..20: ChunkColor := TAlphaColorRec.Yellow;
21..30: ChunkColor := TAlphaColorRec.Red;
else
ChunkColor := TAlphaColorRec.Beige
end;
var Scale := Image1.Scene.GetSceneScale;
var Brush := TBrush.Create(TBrushKind.Solid, ChunkColor);
var Rect := TRectF.Create(
(xvalue - 1) * wvalue / Scale,
0,
xvalue * wvalue / Scale,
Image1.Bitmap.Height
);
Memo1.Lines.Add(xvalue.ToString + ': left ' + round(Rect.Left) .ToString
+ ', right ' + round(Rect.Right).ToString);
Image1.Bitmap.Canvas.BeginScene;
Image1.Bitmap.Canvas.FillRect(Rect, 1, Brush);
Image1.Bitmap.Canvas.EndScene;
Brush.Free;
end;
Paul, many thanks - that was really helpful! I have used your code and learned from it, but still have a couple of questions.
(1) What is the idea of a “scale”? There is an Image1.Scale.X and Image1.Scale.Y, both of which have the value of 1. Then there is the Image1.Scene.GetSceneScale, which comes in at 3 point something. Using that gives a reasonable approximation of how to carve up an image into bite-sized chunks, but still not completely accurate. It seems to me that it is a very confusing, poorly documented piece of unnecessary android muddle! Is there more to it, and where is there some documentation which might explain it?
(2) The scale has to be applied in the horizontal direction, but not in the vertical, adding to the confusion! Or is there something I have failed to understand?
(3) The black bit at the right hand end of the image using the following code shows the slight mismatch despite using “scale”.
var
xvalue : integer;
wvalue : single;
initialised : boolean;
Scale : real;
procedure tImageTestMainForm.ButtonExitClick(Sender: TObject);
begin
Application.Terminate;
end;
procedure tImageTestMainForm.ButtonPressClick(Sender: TObject);
var
Rect : tRectF;
Brush : tBrush;
begin
xvalue := 0;
Timer1.Interval := 100;
Timer1.Enabled := true;
Image1.Position.X := 5;
Image1.Width := ClientWidth - 10;
Image1.Bitmap.SetSize(trunc(Image1.Width), trunc(Image1.Height));
Brush := tBrush.Create(tBrushkind.Solid, tAlphaColorRec.White);
Rect.Create(0, 0, Image1.Width, Image1.Height);
Image1.Bitmap.Canvas.BeginScene;
Image1.Bitmap.Canvas.FillRect(Rect, 1, Brush);
Image1.Bitmap.Canvas.EndScene;
Brush.Free;
end;
procedure tImageTestMainForm.FormActivate(Sender: TObject);
begin
if Initialised then exit;
Initialised := true;
xvalue := 0;
Image1.Width := ClientWidth - 20;
Image1.Bitmap.SetSize(trunc(Image1.Width), trunc(Image1.Height));
wvalue := Image1.Width / 30;
Memo1.Lines.Add('Clientwidth ' + inttostr(ClientWidth) + ', wvalue ' + floattostr(wvalue));
Memo1.Lines.Add('Image width ' + inttostr(round(Image1.Width)));
Scale := Image1.Scene.GetSceneScale;
Memo1.Lines.add('SceneScale = ' + FloatToStr(Scale));
end;
procedure tImageTestMainForm.FormCreate(Sender: TObject);
begin
Memo1.Text := 'This is to show how the width of an image needs the use of “scale” ' +
' to gel with the FillRect parameters. What a mess!';
Initialised := false;
end;
procedure tImageTestMainForm.Timer1Timer(Sender: TObject);
var
Rect : tRectF;
Brush : tBrush;
Colour : tAlphaColor;
begin
case xvalue of
0..9 : Colour := tAlphaColorRec.Blue;
10..19 : Colour := tAlphaColorRec.Yellow;
20..29 : Colour := tAlphaColorRec.Red;
else Colour := tAlphaColorRec.Black;
end;
Brush := tBrush.Create(tBrushKind.Solid, Colour);
Rect:= tRectf.Create(5 + wvalue * xvalue, 0, 5 + wvalue * (xvalue + 1), Image1.Height);
Rect.Left := Rect.Left / Scale;
Rect.Right := Rect.Right / Scale;
Memo1.Lines.Add('Right: ' + inttostr(round(Rect.Right)));
Image1.Bitmap.Canvas.BeginScene;
Image1.Bitmap.Canvas.FillRect(Rect, 1, Brush);
Image1.Bitmap.Canvas.EndScene;
xvalue := xvalue + 1;
if xvalue >= 31 then timer1.Enabled := false;
Brush.Destroy;
end;
I asked Claude to summarise it … seems ok on a brief scan …
Coordinate Systems and Scaling in Delphi FMX
FMX has several overlapping coordinate systems that can be confusing. Here’s a breakdown:
1. Physical Screen Pixels
These are the actual hardware pixels on the display. You rarely work with these directly, but they matter for:
-
Screen capture
-
Native window positioning
-
Multi-monitor scenarios
Access via Screen.DisplayCount, Screen.Displays[i].Bounds, etc.
2. Logical (Device-Independent) Pixels
FMX’s primary coordinate system. These are scaled by the platform’s DPI/scale factor.
delphi
// The scale factor for the current form/screen
Form.Handle.Scale // e.g., 1.0, 1.25, 1.5, 2.0 on Retina/HiDPI
Key point: When you set Button1.Position.X := 100, that’s 100 logical pixels, which might be 200 physical pixels on a 2x Retina display.
3. Form Coordinates
The coordinate space of the form’s client area. (0,0) is the top-left corner inside the form’s borders.
-
Form.ClientWidth,Form.ClientHeight— the usable area -
Control positions are relative to their parent, not the form directly
4. Control/Component Local Coordinates
Each control has its own local coordinate system where (0,0) is its top-left corner.
delphi
// A control's own dimensions
Control.Width, Control.Height
// Position within parent (logical pixels)
Control.Position.X, Control.Position.Y
// Bounds in parent coordinates
Control.BoundsRect
5. Absolute (Scene) Coordinates
Coordinates relative to the root form/scene. Convert between systems using:
delphi
// Local point → Form/Scene coordinates
AbsPoint := Control.LocalToAbsolute(PointF(0, 0));
// Form/Scene coordinates → Local
LocalPoint := Control.AbsoluteToLocal(AbsPoint);
6. Screen Coordinates
Actual position on the screen (in logical pixels, but relative to screen origin):
delphi
// Form-relative → Screen
ScreenPt := Form.ClientToScreen(ClientPt);
// Screen → Form-relative
ClientPt := Form.ScreenToClient(ScreenPt);
7. The Scale Property (Per-Control Scaling)
Each control has its own Scale property that applies an additional transform:
delphi
Button1.Scale.X := 1.5; // 150% width
Button1.Scale.Y := 1.5; // 150% height
This is on top of the system DPI scaling and affects hit-testing and child positions.
8. Canvas/Drawing Coordinates
When painting in OnPaint or custom controls, you’re working in the control’s local space, but the canvas may have transforms applied:
delphi
procedure TMyControl.Paint;
begin
// (0,0) is top-left of the control
// Width/Height define the drawable area
Canvas.Fill.Color := TAlphaColors.Red;
Canvas.FillRect(RectF(0, 0, Width, Height), 0, 0, [], 1.0);
end;
Common Conversion Scenarios
| From | To | Method |
|---|---|---|
| Control local | Parent | Position.Point |
| Control local | Form/Scene | LocalToAbsolute() |
| Form/Scene | Control local | AbsoluteToLocal() |
| Form client | Screen | Form.ClientToScreen() |
| Screen | Form client | Form.ScreenToClient() |
| Logical | Physical | Ă— Handle.Scale |
| Physical | Logical | Ă· Handle.Scale |
Practical Gotchas
-
Mouse events give coordinates in the control’s local space (already accounting for DPI and control scale)
-
Bitmaps are in physical pixels — when drawing to a bitmap at HiDPI, multiply dimensions by scale factor
-
TLayout/TScaledLayout—TScaledLayoutprovides an additional scaling layer to make designs resolution-independent -
Margins/Padding are in logical pixels
-
AbsoluteRectgives you the control’s bounds in scene coordinates (useful for overlays)
Thanks - that will take a bit of digesting!