From b07d340f8a9ffbfbc4c9370dda213cac0ac55bd5 Mon Sep 17 00:00:00 2001 From: Veeno Date: Sat, 30 May 2026 23:27:32 +0200 Subject: [PATCH 1/5] Implement lookup-table-based rectangle approximation. --- RLBotCS/ManagerTools/RectUtil.cs | 119 ++++++++++++++++++++++ RLBotCS/ManagerTools/Rendering.cs | 30 +----- RLBotCSTests/ManagerTools/RectUtilTest.cs | 46 +++++++++ 3 files changed, 167 insertions(+), 28 deletions(-) create mode 100644 RLBotCS/ManagerTools/RectUtil.cs create mode 100644 RLBotCSTests/ManagerTools/RectUtilTest.cs diff --git a/RLBotCS/ManagerTools/RectUtil.cs b/RLBotCS/ManagerTools/RectUtil.cs new file mode 100644 index 0000000..01d76ca --- /dev/null +++ b/RLBotCS/ManagerTools/RectUtil.cs @@ -0,0 +1,119 @@ +using System.Collections.Immutable; + +namespace RLBotCS.ManagerTools; + +public static class RectUtil +{ + /// + /// Defines the maximum represented aspect ratio (Limit/1) + /// as well as the granularity of stored ratios.
+ /// Number of entries and the total resulting size in memory + /// for different Limit values are as follows: + /// + /// ┌───────┬─────────┬────────────┐ + /// │ Limit │ entries │ size │ + /// ├───────┼─────────┼────────────┤ + /// │ 512 │ 1174 │ 9.17 kiB │ + /// │ 1024 │ 2563 │ 20.02 kiB │ + /// │ 2048 │ 5555 │ 43.4 kiB │ + /// │ 4096 │ 11977 │ 93.57 kiB │ + /// │ 8192 │ 25673 │ 200.57 kiB │ + /// │ 16384 │ 54778 │ 427.95 kiB │ + /// └───────┴─────────┴────────────┘ + /// + ///
+ public const ushort Limit = 4096; + + private const float HalfPrecisionRangeHigh = 4096f; + private const float HalfPrecisionRangeLow = 1.0f / HalfPrecisionRangeHigh; + + private static readonly ImmutableArray ratios; + private static readonly ImmutableArray<(ushort, ushort)> rects; + + static RectUtil() + { + SortedDictionary dictionary = []; + + static int Gcd(int a, int b) + { + // Greatest common divisor by Euclidean algorithm https://stackoverflow.com/a/41766138 + while (a != 0 && b != 0) + { + if (a > b) + a %= b; + else + b %= a; + } + + return a | b; + } + + for (ushort a = 1; a <= Limit; ++a) + for (ushort b = 1; b <= a && a * b <= Limit; ++b) + if (Gcd(a, b) == 1) + dictionary.Add((float)a / b, (a, b)); + + ratios = [.. dictionary.Keys]; + rects = [.. dictionary.Values]; + } + + private static float GeoMean(float a, float b) + { + if (a >= HalfPrecisionRangeHigh || b >= HalfPrecisionRangeHigh || + a <= HalfPrecisionRangeLow || b <= HalfPrecisionRangeLow) + return MathF.Sqrt(a) * MathF.Sqrt(b); + return MathF.Sqrt(a * b); + } + + private static bool LessThanGeoMean(float x, (float a, float b) m) + { + if (x >= HalfPrecisionRangeHigh || m.a >= HalfPrecisionRangeHigh || m.b >= HalfPrecisionRangeHigh) + return x < MathF.Sqrt(m.a) * MathF.Sqrt(m.b); + return x * x < m.a * m.b; + } + + private static (ushort, ushort) Find(float value) + { + int higherIdx = ratios.BinarySearch(value); + + if (higherIdx >= 0) + return rects[higherIdx]; + + higherIdx = ~higherIdx; + + // No need to handle this because value >= 1.0 == ratios.First() + //if (higherIdx == 0) + // return rects.First(); + + if (higherIdx == ratios.Length) + return rects.Last(); + + int lowerIdx = higherIdx - 1; + return rects[LessThanGeoMean(value, (ratios[lowerIdx], ratios[higherIdx])) ? lowerIdx : higherIdx]; + } + + private static (ushort cols, ushort rows) Find(float width, float height) + { + if (width >= height) + return Find(width / height); + + (ushort rows, ushort cols) = Find(height / width); + return (cols, rows); + } + + /// + /// Approximates the rectangle width×height with cols×rows + /// rectangles with dimensions elementWidth×elementHeight scaled by scale. + /// + public static (ushort cols, ushort rows, float scale) ApproximateRect(int width, int height, int elementWidth, int elementHeight) + { + float elementsInWidth = (float)width / elementWidth; + float elementsInHeight = (float)height / elementHeight; + (ushort cols, ushort rows) = Find(elementsInWidth, elementsInHeight); + + // Ideal horizontal and vertical scale are + // ((float)width / cols) / elementWidth == ((float)width / elementWidth) / cols == elementsInWidth / cols + // ((float)height / rows) / elementHeight == ((float)height / elementHeight) / rows == elementsInHeight / rows + return (cols, rows, GeoMean(elementsInWidth / cols, elementsInHeight / rows)); + } +} diff --git a/RLBotCS/ManagerTools/Rendering.cs b/RLBotCS/ManagerTools/Rendering.cs index 8305e62..097e279 100644 --- a/RLBotCS/ManagerTools/Rendering.cs +++ b/RLBotCS/ManagerTools/Rendering.cs @@ -137,35 +137,9 @@ private ushort SendRect3D(Rect3DT rect3Dt, GameState gameState) /// The rectangle string and the font scaling private (string, float) MakeFakeRectangleString(int width, int height) { - int Gcd(int a, int b) - { - // Greatest common divisor by Euclidean algorithm https://stackoverflow.com/a/41766138 - while (a != 0 && b != 0) - { - if (a > b) - a %= b; - else - b %= a; - } - - return a | b; - } - - int gcd = Gcd(width, height); - int cols = (width / gcd) * (FontHeightPixels / FontWidthPixels); - int rows = height / gcd; - float scale = gcd / (float)FontHeightPixels; - - if (cols + rows > RectangleStringMaxLength) - { - // The width-height ratio has resulting in a very long string. - // TODO: Consider an approximate solution as backup. Do we ever hit this case though? - Logger.LogWarning( - "A rendered rectangle requires more characters than budget allows. Consider different width-height ratio." - ); - } + (ushort cols, ushort rows, float scale) = RectUtil.ApproximateRect(width, height, FontWidthPixels, FontHeightPixels); - StringBuilder str = new StringBuilder(cols + rows); + StringBuilder str = new(cols + rows); for (int c = 0; c < cols; c++) { str.Append(' '); diff --git a/RLBotCSTests/ManagerTools/RectUtilTest.cs b/RLBotCSTests/ManagerTools/RectUtilTest.cs new file mode 100644 index 0000000..29811a6 --- /dev/null +++ b/RLBotCSTests/ManagerTools/RectUtilTest.cs @@ -0,0 +1,46 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RLBotCS.ManagerTools; + +namespace RLBotCSTests.ManagerTools; + +[TestClass] +public class RectUtilTest +{ + const int TestUpTo = 64; + + [TestMethod] + public void ApproximateRectTest() + { + for (int i = 1; i <= TestUpTo; ++i) + { + for(int j = 1; j <= TestUpTo; ++j) + { + (ushort cols, ushort rows, float scale) = RectUtil.ApproximateRect(i, i, j, j); + Assert.AreEqual(1, cols); + Assert.AreEqual(1, rows); + // Slightly iffy, but it passes. + Assert.AreEqual((float)i / j, scale); + } + } + + for (int i = 1; i <= TestUpTo; ++i) + { + float iMin = i * 0.95f; + float iMax = i * 1.05f; + for (int j = 1; j <= TestUpTo; ++j) + { + float jMin = j * 0.95f; + float jMax = j * 1.05f; + for (int k = 1; k <= TestUpTo; ++k) + { + for (int l = 1; l <= TestUpTo; ++l) + { + (ushort cols, ushort rows, float scale) = RectUtil.ApproximateRect(i, j, k, l); + Assert.IsInRange(iMin, iMax, k * scale * cols); + Assert.IsInRange(jMin, jMax, l * scale * rows); + } + } + } + } + } +} From 213bead7fe5ff7da85251574434d2aa872e23a8d Mon Sep 17 00:00:00 2001 From: Veeno Date: Sat, 30 May 2026 23:59:39 +0200 Subject: [PATCH 2/5] Fix formatting. --- RLBotCS/ManagerTools/RectUtil.cs | 33 +++++++++++++++++------ RLBotCS/ManagerTools/Rendering.cs | 7 ++++- RLBotCSTests/ManagerTools/RectUtilTest.cs | 9 +++++-- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/RLBotCS/ManagerTools/RectUtil.cs b/RLBotCS/ManagerTools/RectUtil.cs index 01d76ca..ccef620 100644 --- a/RLBotCS/ManagerTools/RectUtil.cs +++ b/RLBotCS/ManagerTools/RectUtil.cs @@ -49,9 +49,9 @@ static int Gcd(int a, int b) } for (ushort a = 1; a <= Limit; ++a) - for (ushort b = 1; b <= a && a * b <= Limit; ++b) - if (Gcd(a, b) == 1) - dictionary.Add((float)a / b, (a, b)); + for (ushort b = 1; b <= a && a * b <= Limit; ++b) + if (Gcd(a, b) == 1) + dictionary.Add((float)a / b, (a, b)); ratios = [.. dictionary.Keys]; rects = [.. dictionary.Values]; @@ -59,15 +59,23 @@ static int Gcd(int a, int b) private static float GeoMean(float a, float b) { - if (a >= HalfPrecisionRangeHigh || b >= HalfPrecisionRangeHigh || - a <= HalfPrecisionRangeLow || b <= HalfPrecisionRangeLow) + if ( + a >= HalfPrecisionRangeHigh + || b >= HalfPrecisionRangeHigh + || a <= HalfPrecisionRangeLow + || b <= HalfPrecisionRangeLow + ) return MathF.Sqrt(a) * MathF.Sqrt(b); return MathF.Sqrt(a * b); } private static bool LessThanGeoMean(float x, (float a, float b) m) { - if (x >= HalfPrecisionRangeHigh || m.a >= HalfPrecisionRangeHigh || m.b >= HalfPrecisionRangeHigh) + if ( + x >= HalfPrecisionRangeHigh + || m.a >= HalfPrecisionRangeHigh + || m.b >= HalfPrecisionRangeHigh + ) return x < MathF.Sqrt(m.a) * MathF.Sqrt(m.b); return x * x < m.a * m.b; } @@ -89,7 +97,11 @@ private static (ushort, ushort) Find(float value) return rects.Last(); int lowerIdx = higherIdx - 1; - return rects[LessThanGeoMean(value, (ratios[lowerIdx], ratios[higherIdx])) ? lowerIdx : higherIdx]; + return rects[ + LessThanGeoMean(value, (ratios[lowerIdx], ratios[higherIdx])) + ? lowerIdx + : higherIdx + ]; } private static (ushort cols, ushort rows) Find(float width, float height) @@ -105,7 +117,12 @@ private static (ushort cols, ushort rows) Find(float width, float height) /// Approximates the rectangle width×height with cols×rows /// rectangles with dimensions elementWidth×elementHeight scaled by scale. /// - public static (ushort cols, ushort rows, float scale) ApproximateRect(int width, int height, int elementWidth, int elementHeight) + public static (ushort cols, ushort rows, float scale) ApproximateRect( + int width, + int height, + int elementWidth, + int elementHeight + ) { float elementsInWidth = (float)width / elementWidth; float elementsInHeight = (float)height / elementHeight; diff --git a/RLBotCS/ManagerTools/Rendering.cs b/RLBotCS/ManagerTools/Rendering.cs index 097e279..629747b 100644 --- a/RLBotCS/ManagerTools/Rendering.cs +++ b/RLBotCS/ManagerTools/Rendering.cs @@ -137,7 +137,12 @@ private ushort SendRect3D(Rect3DT rect3Dt, GameState gameState) /// The rectangle string and the font scaling private (string, float) MakeFakeRectangleString(int width, int height) { - (ushort cols, ushort rows, float scale) = RectUtil.ApproximateRect(width, height, FontWidthPixels, FontHeightPixels); + (ushort cols, ushort rows, float scale) = RectUtil.ApproximateRect( + width, + height, + FontWidthPixels, + FontHeightPixels + ); StringBuilder str = new(cols + rows); for (int c = 0; c < cols; c++) diff --git a/RLBotCSTests/ManagerTools/RectUtilTest.cs b/RLBotCSTests/ManagerTools/RectUtilTest.cs index 29811a6..6e94965 100644 --- a/RLBotCSTests/ManagerTools/RectUtilTest.cs +++ b/RLBotCSTests/ManagerTools/RectUtilTest.cs @@ -13,7 +13,7 @@ public void ApproximateRectTest() { for (int i = 1; i <= TestUpTo; ++i) { - for(int j = 1; j <= TestUpTo; ++j) + for (int j = 1; j <= TestUpTo; ++j) { (ushort cols, ushort rows, float scale) = RectUtil.ApproximateRect(i, i, j, j); Assert.AreEqual(1, cols); @@ -35,7 +35,12 @@ public void ApproximateRectTest() { for (int l = 1; l <= TestUpTo; ++l) { - (ushort cols, ushort rows, float scale) = RectUtil.ApproximateRect(i, j, k, l); + (ushort cols, ushort rows, float scale) = RectUtil.ApproximateRect( + i, + j, + k, + l + ); Assert.IsInRange(iMin, iMax, k * scale * cols); Assert.IsInRange(jMin, jMax, l * scale * rows); } From 7a047f28a58c8bdeb4652dfb0fc12c0b39f1693d Mon Sep 17 00:00:00 2001 From: Veeno Date: Sun, 31 May 2026 01:31:15 +0200 Subject: [PATCH 3/5] Optimize RectUtil.LessThanGeoMean (x is always between m.a and m.b). --- RLBotCS/ManagerTools/RectUtil.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/RLBotCS/ManagerTools/RectUtil.cs b/RLBotCS/ManagerTools/RectUtil.cs index ccef620..d612e26 100644 --- a/RLBotCS/ManagerTools/RectUtil.cs +++ b/RLBotCS/ManagerTools/RectUtil.cs @@ -71,11 +71,7 @@ private static float GeoMean(float a, float b) private static bool LessThanGeoMean(float x, (float a, float b) m) { - if ( - x >= HalfPrecisionRangeHigh - || m.a >= HalfPrecisionRangeHigh - || m.b >= HalfPrecisionRangeHigh - ) + if (m.a >= HalfPrecisionRangeHigh || m.b >= HalfPrecisionRangeHigh) return x < MathF.Sqrt(m.a) * MathF.Sqrt(m.b); return x * x < m.a * m.b; } From b0e284d6bbccaf9bffe6345d5bc18d5937187f42 Mon Sep 17 00:00:00 2001 From: Veeno Date: Sun, 31 May 2026 01:37:32 +0200 Subject: [PATCH 4/5] Optimize RectUtil.LessThanGeoMean (m.b is always greater than m.a). --- RLBotCS/ManagerTools/RectUtil.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RLBotCS/ManagerTools/RectUtil.cs b/RLBotCS/ManagerTools/RectUtil.cs index d612e26..aa61b06 100644 --- a/RLBotCS/ManagerTools/RectUtil.cs +++ b/RLBotCS/ManagerTools/RectUtil.cs @@ -71,7 +71,7 @@ private static float GeoMean(float a, float b) private static bool LessThanGeoMean(float x, (float a, float b) m) { - if (m.a >= HalfPrecisionRangeHigh || m.b >= HalfPrecisionRangeHigh) + if (m.b >= HalfPrecisionRangeHigh) return x < MathF.Sqrt(m.a) * MathF.Sqrt(m.b); return x * x < m.a * m.b; } From d40fa38badc511ec7e8242589c825324bf85de5e Mon Sep 17 00:00:00 2001 From: Veeno Date: Mon, 1 Jun 2026 20:53:10 +0200 Subject: [PATCH 5/5] Refactor RectUtil value generation strategy. --- RLBotCS/ManagerTools/RectUtil.cs | 81 +++++++++++------------ RLBotCS/ManagerTools/Rendering.cs | 10 +-- RLBotCSTests/ManagerTools/RectUtilTest.cs | 22 +++--- 3 files changed, 53 insertions(+), 60 deletions(-) diff --git a/RLBotCS/ManagerTools/RectUtil.cs b/RLBotCS/ManagerTools/RectUtil.cs index aa61b06..df3c26b 100644 --- a/RLBotCS/ManagerTools/RectUtil.cs +++ b/RLBotCS/ManagerTools/RectUtil.cs @@ -5,24 +5,14 @@ namespace RLBotCS.ManagerTools; public static class RectUtil { /// - /// Defines the maximum represented aspect ratio (Limit/1) - /// as well as the granularity of stored ratios.
- /// Number of entries and the total resulting size in memory - /// for different Limit values are as follows: - /// - /// ┌───────┬─────────┬────────────┐ - /// │ Limit │ entries │ size │ - /// ├───────┼─────────┼────────────┤ - /// │ 512 │ 1174 │ 9.17 kiB │ - /// │ 1024 │ 2563 │ 20.02 kiB │ - /// │ 2048 │ 5555 │ 43.4 kiB │ - /// │ 4096 │ 11977 │ 93.57 kiB │ - /// │ 8192 │ 25673 │ 200.57 kiB │ - /// │ 16384 │ 54778 │ 427.95 kiB │ - /// └───────┴─────────┴────────────┘ - /// + /// The maximum number of subdivisions of the [0,1] interval to store. + /// The maximum possible resulting number of entries is ⌈MaxSubdivisions/2⌉, + /// but only those whose sum of the numerator and denominator does + /// not excede Rendering.RectangleStringMaxLength are included, + /// so ideally this should be a highly composite number.
+ /// For 55440, there are 17635 entries, corresponding to 137.77 kiB of memory. ///
- public const ushort Limit = 4096; + private const ushort MaxSubdivisions = 55440; private const float HalfPrecisionRangeHigh = 4096f; private const float HalfPrecisionRangeLow = 1.0f / HalfPrecisionRangeHigh; @@ -32,8 +22,6 @@ public static class RectUtil static RectUtil() { - SortedDictionary dictionary = []; - static int Gcd(int a, int b) { // Greatest common divisor by Euclidean algorithm https://stackoverflow.com/a/41766138 @@ -48,10 +36,16 @@ static int Gcd(int a, int b) return a | b; } - for (ushort a = 1; a <= Limit; ++a) - for (ushort b = 1; b <= a && a * b <= Limit; ++b) - if (Gcd(a, b) == 1) - dictionary.Add((float)a / b, (a, b)); + SortedDictionary dictionary = []; + float fMaxSubdivisions = MaxSubdivisions; + for (ushort i = MaxSubdivisions / 2 + MaxSubdivisions % 2; i <= MaxSubdivisions; ++i) + { + ushort gcd = (ushort)Gcd(i, MaxSubdivisions); + ushort num = (ushort)(i / gcd); + ushort den = (ushort)(MaxSubdivisions / gcd); + if (num + den <= Rendering.RectangleStringMaxLength) + dictionary.Add(i / fMaxSubdivisions, (num, den)); + } ratios = [.. dictionary.Keys]; rects = [.. dictionary.Values]; @@ -69,14 +63,7 @@ private static float GeoMean(float a, float b) return MathF.Sqrt(a * b); } - private static bool LessThanGeoMean(float x, (float a, float b) m) - { - if (m.b >= HalfPrecisionRangeHigh) - return x < MathF.Sqrt(m.a) * MathF.Sqrt(m.b); - return x * x < m.a * m.b; - } - - private static (ushort, ushort) Find(float value) + private static (ushort, ushort) FindImpl(float value) { int higherIdx = ratios.BinarySearch(value); @@ -85,24 +72,30 @@ private static (ushort, ushort) Find(float value) higherIdx = ~higherIdx; - // No need to handle this because value >= 1.0 == ratios.First() + // No need to handle this because value >= 0.5 == ratios.First() //if (higherIdx == 0) // return rects.First(); - if (higherIdx == ratios.Length) - return rects.Last(); + // No need to handle this because value <= 1.0 == ratios.Last() + //if (higherIdx == ratios.Length) + // return rects.Last(); int lowerIdx = higherIdx - 1; - return rects[ - LessThanGeoMean(value, (ratios[lowerIdx], ratios[higherIdx])) - ? lowerIdx - : higherIdx - ]; + return rects[value * 2 < ratios[lowerIdx] + ratios[higherIdx] ? lowerIdx : higherIdx]; + } + + private static (ushort, ushort) Find(float value) + { + if (value >= 0.5) + return FindImpl(value); + + (ushort num, ushort den) = FindImpl(1f - value); + return ((ushort)(den - num), den); } private static (ushort cols, ushort rows) Find(float width, float height) { - if (width >= height) + if (width <= height) return Find(width / height); (ushort rows, ushort cols) = Find(height / width); @@ -114,10 +107,10 @@ private static (ushort cols, ushort rows) Find(float width, float height) /// rectangles with dimensions elementWidth×elementHeight scaled by scale. /// public static (ushort cols, ushort rows, float scale) ApproximateRect( - int width, - int height, - int elementWidth, - int elementHeight + uint width, + uint height, + uint elementWidth, + uint elementHeight ) { float elementsInWidth = (float)width / elementWidth; diff --git a/RLBotCS/ManagerTools/Rendering.cs b/RLBotCS/ManagerTools/Rendering.cs index 629747b..a2514bd 100644 --- a/RLBotCS/ManagerTools/Rendering.cs +++ b/RLBotCS/ManagerTools/Rendering.cs @@ -94,8 +94,8 @@ private ushort SendRect2D(Rect2DT rect2Dt) // Fake a filled rectangle using a string with colored background var (text, scale) = MakeFakeRectangleString( - (int)Math.Abs(rect2Dt.Width * ResolutionWidthPixels), - (int)Math.Abs(rect2Dt.Height * ResolutionHeightPixels) + (uint)Math.Abs(rect2Dt.Width * ResolutionWidthPixels), + (uint)Math.Abs(rect2Dt.Height * ResolutionHeightPixels) ); return _renderingCommandQueue.AddText2D( @@ -114,8 +114,8 @@ private ushort SendRect3D(Rect3DT rect3Dt, GameState gameState) { // Fake a filled rectangle using a string with colored background var (text, scale) = MakeFakeRectangleString( - (int)Math.Abs(rect3Dt.Width * ResolutionWidthPixels), - (int)Math.Abs(rect3Dt.Height * ResolutionHeightPixels) + (uint)Math.Abs(rect3Dt.Width * ResolutionWidthPixels), + (uint)Math.Abs(rect3Dt.Height * ResolutionHeightPixels) ); return _renderingCommandQueue.AddText3D( @@ -135,7 +135,7 @@ private ushort SendRect3D(Rect3DT rect3Dt, GameState gameState) /// for rectangle rendering. /// /// The rectangle string and the font scaling - private (string, float) MakeFakeRectangleString(int width, int height) + private (string, float) MakeFakeRectangleString(uint width, uint height) { (ushort cols, ushort rows, float scale) = RectUtil.ApproximateRect( width, diff --git a/RLBotCSTests/ManagerTools/RectUtilTest.cs b/RLBotCSTests/ManagerTools/RectUtilTest.cs index 6e94965..fe93db8 100644 --- a/RLBotCSTests/ManagerTools/RectUtilTest.cs +++ b/RLBotCSTests/ManagerTools/RectUtilTest.cs @@ -6,14 +6,14 @@ namespace RLBotCSTests.ManagerTools; [TestClass] public class RectUtilTest { - const int TestUpTo = 64; + const uint TestUpTo = 64; [TestMethod] public void ApproximateRectTest() { - for (int i = 1; i <= TestUpTo; ++i) + for (uint i = 1; i <= TestUpTo; ++i) { - for (int j = 1; j <= TestUpTo; ++j) + for (uint j = 1; j <= TestUpTo; ++j) { (ushort cols, ushort rows, float scale) = RectUtil.ApproximateRect(i, i, j, j); Assert.AreEqual(1, cols); @@ -23,17 +23,17 @@ public void ApproximateRectTest() } } - for (int i = 1; i <= TestUpTo; ++i) + for (uint i = 1; i <= TestUpTo; ++i) { - float iMin = i * 0.95f; - float iMax = i * 1.05f; - for (int j = 1; j <= TestUpTo; ++j) + float iMin = i * 0.96f; + float iMax = i * 1.04f; + for (uint j = 1; j <= TestUpTo; ++j) { - float jMin = j * 0.95f; - float jMax = j * 1.05f; - for (int k = 1; k <= TestUpTo; ++k) + float jMin = j * 0.96f; + float jMax = j * 1.04f; + for (uint k = 1; k <= TestUpTo; ++k) { - for (int l = 1; l <= TestUpTo; ++l) + for (uint l = 1; l <= TestUpTo; ++l) { (ushort cols, ushort rows, float scale) = RectUtil.ApproximateRect( i,