Skip to content

Commit

Permalink
[Mouse Jump] - adding tests / fixing issue with preview size rounding
Browse files Browse the repository at this point in the history
  • Loading branch information
mikeclayton committed Aug 1, 2024
1 parent 3f1c890 commit 1456662
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static IEnumerable<object[]> GetTestCases()
yield return new object[]
{
new TestCase(
previewStyle: StyleHelper.DefaultPreviewStyle,
previewStyle: StyleHelper.BezelledPreviewStyle,
screens: new List<RectangleInfo>()
{
new(0, 0, 500, 500),
Expand All @@ -65,7 +65,7 @@ public static IEnumerable<object[]> GetTestCases()
yield return new object[]
{
new TestCase(
previewStyle: StyleHelper.DefaultPreviewStyle,
previewStyle: StyleHelper.BezelledPreviewStyle,
screens: new List<RectangleInfo>()
{
new(5120, 349, 1920, 1080),
Expand Down Expand Up @@ -117,7 +117,7 @@ private static Bitmap LoadImageResource(string filename)
}

/// <summary>
/// Naive / brute force image comparison - we can optimise this later :-)
/// Naive / brute force image comparison - we can optimize this later :-)
/// </summary>
private static void AssertImagesEqual(Bitmap expected, Bitmap actual)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public TestCase(PreviewStyle previewStyle, List<RectangleInfo> screens, PointInf
public static IEnumerable<object[]> GetTestCases()
{
// happy path - single screen with 50% scaling,
// *has* a preview borders but *no* screenshot borders
// *has* a preview border but *no* screenshot borders
//
// +----------------+
// | |
Expand Down Expand Up @@ -161,7 +161,7 @@ public static IEnumerable<object[]> GetTestCases()
new(0, 0, 1024, 768),
};
var activatedLocation = new PointInfo(512, 384);
var previewLayout = new PreviewLayout(
var expectedResult = new PreviewLayout(
virtualScreen: new(0, 0, 1024, 768),
screens: screens,
activatedScreenIndex: 0,
Expand All @@ -184,7 +184,7 @@ public static IEnumerable<object[]> GetTestCases()
contentBounds: new(6, 6, 512, 384)
),
});
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, previewLayout) };
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };

// happy path - single screen with 50% scaling,
// *no* preview borders but *has* screenshot borders
Expand Down Expand Up @@ -218,7 +218,7 @@ public static IEnumerable<object[]> GetTestCases()
new(0, 0, 1024, 768),
};
activatedLocation = new PointInfo(512, 384);
previewLayout = new PreviewLayout(
expectedResult = new PreviewLayout(
virtualScreen: new(0, 0, 1024, 768),
screens: screens,
activatedScreenIndex: 0,
Expand All @@ -241,7 +241,59 @@ public static IEnumerable<object[]> GetTestCases()
contentBounds: new(6, 6, 500, 372)
),
});
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, previewLayout) };
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };

// rounding error check - single screen with 33% scaling,
// no borders, check to make sure form scales to exactly
// fill the canvas size with no rounding errors.
//
// in this test the preview width is 300 and the desktop is
// 900, so the scaling factor is 1/3, but this gets rounded
// to 0.3333333333333333333333333333, and 900 times this value
// is 299.99999999999999999999999997. if we don't scale correctly
// the resulting form width might only be 299 pixels instead of 300
//
// +----------------+
// | |
// | 0 |
// | |
// +----------------+
previewStyle = new PreviewStyle(
canvasSize: new(
width: 300,
height: 200
),
canvasStyle: BoxStyle.Empty,
screenStyle: BoxStyle.Empty);
screens = new List<RectangleInfo>
{
new(0, 0, 900, 200),
};
activatedLocation = new PointInfo(450, 100);
expectedResult = new PreviewLayout(
virtualScreen: new(0, 0, 900, 200),
screens: screens,
activatedScreenIndex: 0,
formBounds: new(300, 66.5m, 300, 67),
previewStyle: previewStyle,
previewBounds: new(
outerBounds: new(0, 0, 300, 67),
marginBounds: new(0, 0, 300, 67),
borderBounds: new(0, 0, 300, 67),
paddingBounds: new(0, 0, 300, 67),
contentBounds: new(0, 0, 300, 67)
),
screenshotBounds: new()
{
new(
outerBounds: new(0, 0, 300, 67),
marginBounds: new(0, 0, 300, 67),
borderBounds: new(0, 0, 300, 67),
paddingBounds: new(0, 0, 300, 67),
contentBounds: new(0, 0, 300, 67)
),
});
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };

// primary monitor not topmost / leftmost - if there are screens
// that are further left or higher up than the primary monitor
Expand Down Expand Up @@ -292,7 +344,7 @@ public static IEnumerable<object[]> GetTestCases()
new(0, 0, 5120, 1440),
};
activatedLocation = new(-960, 60);
previewLayout = new PreviewLayout(
expectedResult = new PreviewLayout(
virtualScreen: new(-1920, -480, 7040, 1920),
screens: screens,
activatedScreenIndex: 0,
Expand Down Expand Up @@ -322,7 +374,7 @@ public static IEnumerable<object[]> GetTestCases()
contentBounds: new(204, 60, 500, 132)
),
});
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, previewLayout) };
yield return new object[] { new TestCase(previewStyle, screens, activatedLocation, expectedResult) };
}

[TestMethod]
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -16,45 +16,49 @@ public sealed class ScaleToFitTests
{
public sealed class TestCase
{
public TestCase(SizeInfo obj, SizeInfo bounds, SizeInfo expectedResult)
public TestCase(SizeInfo source, SizeInfo bounds, SizeInfo expectedResult, decimal scalingRatio)
{
this.Obj = obj;
this.Source = source;
this.Bounds = bounds;
this.ExpectedResult = expectedResult;
this.ScalingRatio = scalingRatio;
}

public SizeInfo Obj { get; }
public SizeInfo Source { get; }

public SizeInfo Bounds { get; }

public SizeInfo ExpectedResult { get; }

public decimal ScalingRatio { get; }
}

public static IEnumerable<object[]> GetTestCases()
{
// identity tests
yield return new object[] { new TestCase(new(512, 384), new(512, 384), new(512, 384)), };
yield return new object[] { new TestCase(new(1024, 768), new(1024, 768), new(1024, 768)), };
yield return new object[] { new TestCase(new(512, 384), new(512, 384), new(512, 384), 1), };
yield return new object[] { new TestCase(new(1024, 768), new(1024, 768), new(1024, 768), 1), };

// general tests
yield return new object[] { new TestCase(new(512, 384), new(2048, 1536), new(2048, 1536)), };
yield return new object[] { new TestCase(new(2048, 1536), new(1024, 768), new(1024, 768)), };
yield return new object[] { new TestCase(new(512, 384), new(2048, 1536), new(2048, 1536), 4), };
yield return new object[] { new TestCase(new(2048, 1536), new(1024, 768), new(1024, 768), 0.5m), };

// scale to fit width
yield return new object[] { new TestCase(new(512, 384), new(2048, 3072), new(2048, 1536)), };
yield return new object[] { new TestCase(new(512, 384), new(2048, 3072), new(2048, 1536), 4), };

// scale to fit height
yield return new object[] { new TestCase(new(512, 384), new(4096, 1536), new(2048, 1536)), };
yield return new object[] { new TestCase(new(512, 384), new(4096, 1536), new(2048, 1536), 4), };
}

[TestMethod]
[DynamicData(nameof(GetTestCases), DynamicDataSourceType.Method)]
public void RunTestCases(TestCase data)
{
var actual = data.Obj.ScaleToFit(data.Bounds);
var actual = data.Source.ScaleToFit(data.Bounds, out var scalingRatio);
var expected = data.ExpectedResult;
Assert.AreEqual(expected.Width, actual.Width);
Assert.AreEqual(expected.Height, actual.Height);
Assert.AreEqual(scalingRatio, data.ScalingRatio);
}
}

Expand Down
13 changes: 5 additions & 8 deletions src/modules/MouseUtils/MouseJump.Common/Helpers/LayoutHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,13 @@ public static PreviewLayout GetPreviewLayout(
.Shrink(previewStyle.CanvasStyle.BorderStyle)
.Shrink(previewStyle.CanvasStyle.PaddingStyle);

// scale the virtual screen to fit inside the content area
var screenScalingRatio = builder.VirtualScreen.Size
.ScaleToFitRatio(maxContentSize);

// work out the actual size of the "content area" by scaling the virtual screen
// to fit inside the maximum content area while maintaining its aspect ration.
// we'll also offset it to allow for any margins, borders and padding
var contentBounds = builder.VirtualScreen.Size
.Scale(screenScalingRatio)
.Floor()
.ScaleToFit(maxContentSize, out var scalingRatio)
.Round()
.Clamp(maxContentSize)
.PlaceAt(0, 0)
.Offset(previewStyle.CanvasStyle.MarginStyle.Left, previewStyle.CanvasStyle.MarginStyle.Top)
.Offset(previewStyle.CanvasStyle.BorderStyle.Left, previewStyle.CanvasStyle.BorderStyle.Top)
Expand All @@ -85,9 +82,9 @@ public static PreviewLayout GetPreviewLayout(
screen => LayoutHelper.GetBoxBoundsFromOuterBounds(
screen
.Offset(builder.VirtualScreen.Location.ToSize().Invert())
.Scale(screenScalingRatio)
.Scale(scalingRatio)
.Offset(builder.PreviewBounds.ContentBounds.Location.ToSize())
.Truncate(),
.Round(),
previewStyle.ScreenStyle))
.ToList();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using MouseJump.Common.Models.Drawing;

namespace MouseJump.Common.Imaging;
Expand Down Expand Up @@ -33,6 +34,11 @@ public void CopyImageRegion(
RectangleInfo sourceBounds,
RectangleInfo targetBounds)
{
// prevent the background bleeding through into screen images
// (see https://github.com/mikeclayton/FancyMouse/issues/44)
targetGraphics.PixelOffsetMode = PixelOffsetMode.Half;
targetGraphics.InterpolationMode = InterpolationMode.NearestNeighbor;

targetGraphics.DrawImage(
image: this.SourceImage,
destRect: targetBounds.ToRectangle(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,15 @@ public RectangleInfo Offset(SizeInfo amount) =>
public RectangleInfo Offset(decimal dx, decimal dy) =>
new(this.X + dx, this.Y + dy, this.Width, this.Height);

public RectangleInfo Round() =>
this.Round(0);

public RectangleInfo Round(int decimals) => new(
Math.Round(this.X, decimals),
Math.Round(this.Y, decimals),
Math.Round(this.Width, decimals),
Math.Round(this.Height, decimals));

/// <summary>
/// Returns a new <see cref="RectangleInfo"/> that is a scaled version of the current rectangle.
/// The dimensions of the new rectangle are calculated by multiplying the current rectangle's dimensions by the scaling factor.
Expand Down
81 changes: 51 additions & 30 deletions src/modules/MouseUtils/MouseJump.Common/Models/Drawing/SizeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ public decimal Height
get;
}

public SizeInfo Clamp(SizeInfo max)
{
return new(
width: Math.Clamp(this.Width, 0, max.Width),
height: Math.Clamp(this.Height, 0, max.Height));
}

public SizeInfo Enlarge(BorderStyle border) =>
new(
this.Width + border.Horizontal,
Expand All @@ -45,6 +52,17 @@ public SizeInfo Enlarge(PaddingStyle padding) =>
this.Width + padding.Horizontal,
this.Height + padding.Vertical);

/// <summary>
/// Rounds down the width and height of this size to the nearest whole number.
/// </summary>
/// <returns>A new <see cref="SizeInfo"/> instance with floored dimensions.</returns>
public SizeInfo Floor()
{
return new SizeInfo(
Math.Floor(this.Width),
Math.Floor(this.Height));
}

/// <summary>
/// Calculates the intersection of this size with another size, resulting in a size that represents
/// the overlapping dimensions. Both sizes must be non-negative.
Expand All @@ -71,19 +89,6 @@ public SizeInfo Intersect(SizeInfo size)
public SizeInfo Invert() =>
new(-this.Width, -this.Height);

public SizeInfo Scale(decimal scalingFactor) => new(
this.Width * scalingFactor,
this.Height * scalingFactor);

public SizeInfo Shrink(BorderStyle border) =>
new(this.Width - border.Horizontal, this.Height - border.Vertical);

public SizeInfo Shrink(MarginStyle margin) =>
new(this.Width - margin.Horizontal, this.Height - margin.Vertical);

public SizeInfo Shrink(PaddingStyle padding) =>
new(this.Width - padding.Horizontal, this.Height - padding.Vertical);

/// <summary>
/// Creates a new <see cref="RectangleInfo"/> instance representing a rectangle with this size,
/// positioned at the specified coordinates.
Expand All @@ -94,32 +99,39 @@ public SizeInfo Shrink(PaddingStyle padding) =>
public RectangleInfo PlaceAt(decimal x, decimal y) =>
new(x, y, this.Width, this.Height);

public SizeInfo Round() =>
this.Round(0);

public SizeInfo Round(int decimals) => new(
Math.Round(this.Width, decimals),
Math.Round(this.Height, decimals));

public SizeInfo Scale(decimal scalingFactor) => new(
this.Width * scalingFactor,
this.Height * scalingFactor);

/// <summary>
/// Scales this size to fit within the bounds of another size, while maintaining the aspect ratio.
/// </summary>
/// <param name="bounds">The size to fit this size into.</param>
/// <returns>A new <see cref="SizeInfo"/> instance representing the scaled size.</returns>
public SizeInfo ScaleToFit(SizeInfo bounds)
public SizeInfo ScaleToFit(SizeInfo bounds, out decimal scalingRatio)
{
var widthRatio = bounds.Width / this.Width;
var heightRatio = bounds.Height / this.Height;
return widthRatio.CompareTo(heightRatio) switch
switch (widthRatio.CompareTo(heightRatio))
{
< 0 => new(bounds.Width, this.Height * widthRatio),
0 => bounds,
> 0 => new(this.Width * heightRatio, bounds.Height),
};
}

/// <summary>
/// Rounds down the width and height of this size to the nearest whole number.
/// </summary>
/// <returns>A new <see cref="SizeInfo"/> instance with floored dimensions.</returns>
public SizeInfo Floor()
{
return new SizeInfo(
Math.Floor(this.Width),
Math.Floor(this.Height));
case < 0:
scalingRatio = widthRatio;
return new(bounds.Width, this.Height * widthRatio);
case 0:
// widthRatio and heightRatio are the same, so just pick one
scalingRatio = widthRatio;
return bounds;
case > 0:
scalingRatio = heightRatio;
return new(this.Width * heightRatio, bounds.Height);
}
}

/// <summary>
Expand All @@ -142,6 +154,15 @@ public decimal ScaleToFitRatio(SizeInfo bounds)
return scalingRatio;
}

public SizeInfo Shrink(BorderStyle border) =>
new(this.Width - border.Horizontal, this.Height - border.Vertical);

public SizeInfo Shrink(MarginStyle margin) =>
new(this.Width - margin.Horizontal, this.Height - margin.Vertical);

public SizeInfo Shrink(PaddingStyle padding) =>
new(this.Width - padding.Horizontal, this.Height - padding.Vertical);

public Size ToSize() => new((int)this.Width, (int)this.Height);

public Point ToPoint() => new((int)this.Width, (int)this.Height);
Expand Down

0 comments on commit 1456662

Please sign in to comment.