And because it is so much fun, I wrote a completely general program to test it.

You can define how many resolution combinations you want to go through and what maximum value you want to start with and it goes through all possible combinations and gives you results after how many cycles a start-end pair has settled down and what was the maximum difference.

E.g. for the most typical use-case of 2 different resolutions and max scaling of 200% and max starting value of 2000, the results are:

-- Input --
Possible resolutions: 100%, 125%, 150%, 175%, 200%
UniqueCombinations: True
ResolutionSteps: 2
MaxValue: 2000
-- Results --
Count: 40020
Error count after the cycle #1: 5615 (14,03%)
No errors after cycle #2
Maximal error value (difference between start and end value): 1 (5,88% from value 17 to 18 for combination 10)

So not really bad, after the 1st cycle the value always settles down and the maximal error is 1px, which is ~6% of the start value.

---------

For 3 different resolutions (100%, 125%, 200%), the maximum error value is 2px after max 10 resolution changes:

-- Input --
Possible resolutions: 100%, 125%, 200%
UniqueCombinations: False
ResolutionSteps: 10
MaxValue: 500
-- Results --
Count: 29583048
Error count after the cycle #1: 6805918 (23,01%)
No errors after cycle #2
Maximal error value (difference between start and end value): 2 (9,52% from value 21 to 19 for combination 2000000001)

----------

But even after 8 resolution steps (using 5 different resolutions), the error is not that bad:

-- Input --
Possible resolutions: 100%, 125%, 150%, 175%, 200%
UniqueCombinations: False
ResolutionSteps: 8
MaxValue: 500
-- Results --
Count: 195702624
Error count after the cycle #1: 82854224 (42,34%)
Error count after the cycle #2: 9545914 (4,88%)
Error count after the cycle #3: 184165 (0,09%)
No errors after cycle #4
Maximal error value (difference between start and end value): 5 (26,32% from value 19 to 14 for combination 32401201)

---------

To sum it up, for 3 and less monitor DPI resolutions, the maximum error is 2px, which is marginal and it is there only for the very small value of 19px.

When the number of different resolutions increases (in the above example 5 different resolutions), the error gets higher, but still is acceptable and the higher the starting value, the lower the rounding error. And honestly, who has 5 monitors and every one with a different resolution and moves a window between all of them, so that it bothers him?

My conclusion is that it really is not worth it to introduce a floating-point precision sizes within the LCL. Yes, there would be some gain in scaling precision but only for a lot of different DPI values (more than 3) and the gain would not be striking. On the other hand, the effort needed would be very high, especially due to forwards compatibility of the LFM format.

Ondrej

Program code:

program TestScaling;
uses Math, SysUtils;
const
  //DefResolutions: array[0..2] of Double = (1.00, 1.25, 2.00);
  DefResolutions: array[0..4] of Double = (1.00, 1.25, 1.50, 1.75, 2.00);
  //DefResolutions: array[0..8] of Double = (1.00, 1.25, 1.50, 1.75, 2.00, 2.50, 3.00, 3.50, 4.00); // test even more resolutions - up to 400%   MaxDiffFromValue = 16; // do not test values smaller than this for MaxDiff* function Scale(const aValue: Integer; const aFromResolution, aToResolution: Double): Integer;
begin
  Result := Round(aValue / aFromResolution * aToResolution);
end;
function ScaleCycle(const V: Integer; R: TArray<Integer>): Integer;
var
  I: Integer;
begin
  Result := V;
  for I := 0 to High(R)-1 do
    Result := Scale(Result, DefResolutions[R[I]], DefResolutions[R[I+1]]);
  Result := Scale(Result, DefResolutions[R[High(R)]], DefResolutions[R[0]]);
end;
function NextCombination(var R: TArray<Integer>): Boolean;
var
  I: Integer;
begin
  for I := High(R) downto 0 do
  begin
    if R[I]<High(DefResolutions) then
    begin
      Inc(R[I]);
      Exit(True);
    end;
    R[I] := 0;
  end;
  Result := False;
end;
function CombinationIsUnique(const R: TArray<Integer>): Boolean;
var
  I, L: Integer;
begin
  for I := 0 to High(R) do
    for L := I+1 to High(R) do
      if R[I]=R[L] then
        Exit(False);
  Result := True;
end;
function WriteCombination(const R: TArray<Integer>): string;
var
  I: Integer;
begin
  Result := '';
  for I := 0 to High(R) do
    Result := Result + IntToStr(R[I]);
end;
function WriteResolutions(const R: array of Double): string;
var
  I: Integer;
begin
  Result := '';
  for I := 0 to High(R) do
  begin
    if Result<>'' then
      Result := Result + ', ';
    Result := Result + IntToStr(Round(R[I]*100))+'%';
  end;
end;
var
  Resolutions: array of Integer;
  I, F, RefValue, Cycle, ResolutionSteps, MaxValue, StartValue, MaxDiff, MaxDiffStartValue, MaxDiffEndValue: Integer;
  Count: Int64;
  MaxDiffRatio: Double;
  Errors: array of Int64;
  MaxDiffCombination: string;
  UniqueCombinationsC: Char;
  UniqueCombinations: Boolean;
begin
  try
    Write('Do you want to test only unique combinations? (y/n): ');
    ReadLn(UniqueCombinationsC);
    UniqueCombinations := LowerCase(UniqueCombinationsC)='y';
    if UniqueCombinations then
      Write(Format('Input how many resolutions do you want to cycle through (min = 2, max = %d): ', [Length(DefResolutions)]))
    else
      Write('Input how many resolutions do you want to cycle through (min = 2): ');
    ReadLn(ResolutionSteps);
    if ResolutionSteps<2 then
      raise EInOutError.Create('Invalid resolution count');
    if UniqueCombinations and (ResolutionSteps>Length(DefResolutions)) then
      raise EInOutError.CreateFmt('Invalid resolution count (in case of unique combintations, the resolution count must be max "%d"', [Length(DefResolutions)]);

    Write('Input the maximum value you want to test (e.g. 1000): ');
    ReadLn(MaxValue);
    if MaxValue<1 then
      raise EInOutError.Create('Invalid maximum value');
    Resolutions := nil;
    SetLength(Resolutions, ResolutionSteps);
    for I := 0 to High(Resolutions) do
      Resolutions[I] := 0;

    Count := 0;
    MaxDiff := 0;
    MaxDiffRatio := 0;
    MaxDiffStartValue := 0;
    MaxDiffEndValue := 0;
    MaxDiffCombination := '';
    Errors := nil;
    while NextCombination(Resolutions) do
    begin
      if UniqueCombinations and not CombinationIsUnique(Resolutions) then
        continue;
      Write('Combination: ');
      Writeln(WriteCombination(Resolutions));
      for I := 0 to MaxValue do
      begin
        Inc(Count);
        StartValue := I;
        RefValue := I;
        F := ScaleCycle(RefValue, Resolutions);
        Cycle := 0;
        while not SameValue(F, RefValue) do
        begin
          // compute MaxDiff - but only for values above MaxDiffFromValue
          if (StartValue>=MaxDiffFromValue)
          and (MaxDiff<Abs(F-StartValue)) then
          begin
            MaxDiff := Abs(F-StartValue);
            MaxDiffRatio := Abs(F-StartValue) / StartValue;
            MaxDiffStartValue := StartValue;
            MaxDiffEndValue := F;
            MaxDiffCombination := WriteCombination(Resolutions);
          end;
          RefValue := F;
          if High(Errors)<Cycle then
          begin
            SetLength(Errors, Cycle+1);
            Errors[Cycle] := 0;
          end;
          Inc(Errors[Cycle]);
          Inc(Cycle);
          F := ScaleCycle(F, Resolutions);
        end;
      end;
    end;

    Writeln('end.');
    Writeln;
    Writeln('-- Input --');
    Writeln('Possible resolutions: ', WriteResolutions(DefResolutions));
    Writeln('UniqueCombinations: ', BoolToStr(UniqueCombinations, True));
    Writeln('ResolutionSteps: ', ResolutionSteps);
    Writeln('MaxValue: ', MaxValue);
    Writeln('-- Results --');
    Writeln('Count: ', Count);
    for Cycle := 0 to High(Errors) do
      Writeln(Format('Error count after the cycle #%d: %d (%.2f%%)', [Cycle+1, Errors[Cycle], Errors[Cycle] / Count * 100]));
    Writeln(Format('No errors after cycle #%d', [Length(Errors)+1]));
    Writeln(Format('Maximal error value (difference between start and end value): %d (%.2f%% from value %d to %d for combination %s) ', [MaxDiff, MaxDiffRatio*100, MaxDiffStartValue, MaxDiffEndValue, MaxDiffCombination]));
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

--
_______________________________________________
lazarus mailing list
lazarus@lists.lazarus-ide.org
https://lists.lazarus-ide.org/listinfo/lazarus

Reply via email to