# [Lazarus] Hi-DPI tweak of components

Ondrej Pokorny lazarus at kluug.net
Thu Jun 8 13:12:36 CEST 2023

```On 08.06.2023 12:24, Giuliano Colla wrote:
>
> Il 08/06/23 11:58, Ondrej Pokorny via lazarus ha scritto:
>> All in all, an over-complicated approach with little gain.
>
> The gain would be that you do not add up rounding errors. We can't
> have fractional pixels, of course, but we may have the exact actual
> size at design/creation time, and for each different DPI the best
> approximation to the actual size. If you switch back and forth between
> two monitors with different DPI the rounding errors remain constant,

You have to consider that for monitor DPI scaling, incrementing series
of multiplications like
A * 1.5 * 1.25 * 1.75 * 2.00 * etc * etc
never appear.

You always have a limited count of different resolutions (usually 2) and
you always switch between up- and down-scaling. Statistically the
Round() function rounds up and down equal sets of float values. So,
statistically, the errors cannot add up, but they fix themselves up with
the count of up- and down-scaling operations.

Here is my proof for switching between 2 different resolutions from

program Project1;
uses Math;
const
Resolutions: array[0..3] of Double = (1.25, 1.50, 1.75, 2.00);
var
R: Double;
I, F: Integer;
begin
for R in Resolutions do
for I := 0 to 1000 do
begin
F := I;
// scale first up and then down
F := Round(F * R);
F := Round(F / R);
if not SameValue(F, I) then
Writeln(I, ': ', F);
end;
end.

Run the program, and you will se that there is no starting value at 100%
that would differ if you scale it up and down.

-------

If you want to test scaling down and up, then there of course there will
be a discrepancy between starting and ending value after 1 down- and
up-scaling cycle because you lose resolution with the first division.
But if you do the same up/down scaling again, you will see that every
starting value ended at a well defined pair of values. The values
definitely do not grow or shrink with the count of scaling operations.

program Project1;
uses Math;
const
Resolutions: array[0..3] of Double = (1.25, 1.50, 1.75, 2.00);
var
R: Double;
I, F, ErrorCountInMiddleValue, ErrorCountAfterSecondScaling,
MiddleValue: Integer;
begin
ErrorCountInMiddleValue := 0;
ErrorCountAfterSecondScaling := 0;
for R in Resolutions do
for I := 0 to 1000 do
begin
F := I;
// first scale down, then up
F := Round(F / R);
F := Round(F * R);
MiddleValue := F;
if not SameValue(F, I) then
begin
Inc(ErrorCountInMiddleValue);
// scale first down and then up
F := Round(F / R);
F := Round(F * R);

if not SameValue(F, MiddleValue) then
begin
Writeln('Error after second scaling: ', I, ': ', F);
Inc(ErrorCountAfterSecondScaling);
end;
end;
end;
Writeln('ErrorCountInMiddleValue: ', ErrorCountInMiddleValue);
Writeln('ErrorCountAfterSecondScaling: ', ErrorCountAfterSecondScaling);
end.

As you can see, there is no starting value that would cause a constantly
incrementing or decrementing series of values. Every starting value
settles down on a well-defined pair of values after the second scaling.

I consider my arguments as proven. Your suggestion to have a better
precision units for sizes doesn't help at all.

---

And even if you found some very rare combination of scaling A -> B -> C
-> A of some value X that increments by 1px every time after the whole
scaling cycle is performed: who cares that after so many scaling
operations the window/column/... width is bigger/smaller by 1px?
As I said in the beginning, the default values should not be scaled with
this approach, they should be scaled always from a constant value at
100%/96 PPI. That means for them the rounding error problem does not
apply. It would apply only for user-defined sizes and there it does not
matter because if the size doesn't do it for the user, he can always
resize the window/column/ whatever.

Ondrej

```