diff --git a/src/Avalonia.Visuals/Media/PixelPoint.cs b/src/Avalonia.Visuals/Media/PixelPoint.cs
new file mode 100644
index 0000000000..995781ee9f
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/PixelPoint.cs
@@ -0,0 +1,201 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Globalization;
+using Avalonia.Utilities;
+
+namespace Avalonia
+{
+ ///
+ /// Represents a point in device pixels.
+ ///
+ public readonly struct PixelPoint
+ {
+ ///
+ /// A point representing 0,0.
+ ///
+ public static readonly PixelPoint Origin = new PixelPoint(0, 0);
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The X co-ordinate.
+ /// The Y co-ordinate.
+ public PixelPoint(int x, int y)
+ {
+ X = x;
+ Y = y;
+ }
+
+ ///
+ /// Gets the X co-ordinate.
+ ///
+ public int X { get; }
+
+ ///
+ /// Gets the Y co-ordinate.
+ ///
+ public int Y { get; }
+
+ ///
+ /// Checks for equality between two s.
+ ///
+ /// The first point.
+ /// The second point.
+ /// True if the points are equal; otherwise false.
+ public static bool operator ==(PixelPoint left, PixelPoint right)
+ {
+ return left.X == right.X && left.Y == right.Y;
+ }
+
+ ///
+ /// Checks for inequality between two s.
+ ///
+ /// The first point.
+ /// The second point.
+ /// True if the points are unequal; otherwise false.
+ public static bool operator !=(PixelPoint left, PixelPoint right)
+ {
+ return !(left == right);
+ }
+
+ ///
+ /// Parses a string.
+ ///
+ /// The string.
+ /// The .
+ public static PixelPoint Parse(string s)
+ {
+ using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelPoint"))
+ {
+ return new PixelPoint(
+ tokenizer.ReadInt32(),
+ tokenizer.ReadInt32());
+ }
+ }
+
+ ///
+ /// Checks for equality between a point and an object.
+ ///
+ /// The object.
+ ///
+ /// True if is a point that equals the current point.
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is PixelPoint other)
+ {
+ return this == other;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Returns a hash code for a .
+ ///
+ /// The hash code.
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 17;
+ hash = (hash * 23) + X.GetHashCode();
+ hash = (hash * 23) + Y.GetHashCode();
+ return hash;
+ }
+ }
+
+ ///
+ /// Returns a new with the same Y co-ordinate and the specified X co-ordinate.
+ ///
+ /// The X co-ordinate.
+ /// The new .
+ public PixelPoint WithX(int x) => new PixelPoint(x, Y);
+
+ ///
+ /// Returns a new with the same X co-ordinate and the specified Y co-ordinate.
+ ///
+ /// The Y co-ordinate.
+ /// The new .
+ public PixelPoint WithY(int y) => new PixelPoint(X, y);
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified scaling factor.
+ ///
+ /// The scaling factor.
+ /// The device-independent point.
+ public Point ToPoint(double scale) => new Point(X / scale, Y / scale);
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified scaling factor.
+ ///
+ /// The scaling factor.
+ /// The device-independent point.
+ public Point ToPoint(Vector scale) => new Point(X / scale.X, Y / scale.Y);
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified dots per inch (DPI).
+ ///
+ /// The dots per inch of the device.
+ /// The device-independent point.
+ public Point ToPointWithDpi(double dpi) => ToPoint(dpi / 96);
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified dots per inch (DPI).
+ ///
+ /// The dots per inch of the device.
+ /// The device-independent point.
+ public Point ToPointWithDpi(Vector dpi) => ToPoint(new Vector(dpi.X / 96, dpi.Y / 96));
+
+ ///
+ /// Converts a to device pixels using the specified scaling factor.
+ ///
+ /// The point.
+ /// The scaling factor.
+ /// The device-independent point.
+ public static PixelPoint FromPoint(Point point, double scale) => new PixelPoint(
+ (int)(point.X * scale),
+ (int)(point.Y * scale));
+
+ ///
+ /// Converts a to device pixels using the specified scaling factor.
+ ///
+ /// The point.
+ /// The scaling factor.
+ /// The device-independent point.
+ public static PixelPoint FromPoint(Point point, Vector scale) => new PixelPoint(
+ (int)(point.X * scale.X),
+ (int)(point.Y * scale.Y));
+
+ ///
+ /// Converts a to device pixels using the specified dots per inch (DPI).
+ ///
+ /// The point.
+ /// The dots per inch of the device.
+ /// The device-independent point.
+ public static PixelPoint FromPointWithDpi(Point point, double dpi) => FromPoint(point, dpi / 96);
+
+ ///
+ /// Converts a to device pixels using the specified dots per inch (DPI).
+ ///
+ /// The point.
+ /// The dots per inch of the device.
+ /// The device-independent point.
+ public static PixelPoint FromPointWithDpi(Point point, Vector dpi) => FromPoint(point, new Vector(dpi.X / 96, dpi.Y / 96));
+
+ ///
+ /// Returns the string representation of the point.
+ ///
+ /// The string representation of the point.
+ public override string ToString()
+ {
+ return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", X, Y);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/PixelRect.cs b/src/Avalonia.Visuals/Media/PixelRect.cs
new file mode 100644
index 0000000000..9c8e5ad1c4
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/PixelRect.cs
@@ -0,0 +1,436 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Globalization;
+using Avalonia.Utilities;
+
+namespace Avalonia
+{
+ ///
+ /// Represents a rectangle in device pixels.
+ ///
+ public readonly struct PixelRect
+ {
+ ///
+ /// An empty rectangle.
+ ///
+ public static readonly PixelRect Empty = default;
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The X position.
+ /// The Y position.
+ /// The width.
+ /// The height.
+ public PixelRect(int x, int y, int width, int height)
+ {
+ X = x;
+ Y = y;
+ Width = width;
+ Height = height;
+ }
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The size of the rectangle.
+ public PixelRect(PixelSize size)
+ {
+ X = 0;
+ Y = 0;
+ Width = size.Width;
+ Height = size.Height;
+ }
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The position of the rectangle.
+ /// The size of the rectangle.
+ public PixelRect(PixelPoint position, PixelSize size)
+ {
+ X = position.X;
+ Y = position.Y;
+ Width = size.Width;
+ Height = size.Height;
+ }
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The top left position of the rectangle.
+ /// The bottom right position of the rectangle.
+ public PixelRect(PixelPoint topLeft, PixelPoint bottomRight)
+ {
+ X = topLeft.X;
+ Y = topLeft.Y;
+ Width = bottomRight.X - topLeft.X;
+ Height = bottomRight.Y - topLeft.Y;
+ }
+
+ ///
+ /// Gets the X position.
+ ///
+ public int X { get; }
+
+ ///
+ /// Gets the Y position.
+ ///
+ public int Y { get; }
+
+ ///
+ /// Gets the width.
+ ///
+ public int Width { get; }
+
+ ///
+ /// Gets the height.
+ ///
+ public int Height { get; }
+
+ ///
+ /// Gets the position of the rectangle.
+ ///
+ public PixelPoint Position => new PixelPoint(X, Y);
+
+ ///
+ /// Gets the size of the rectangle.
+ ///
+ public PixelSize Size => new PixelSize(Width, Height);
+
+ ///
+ /// Gets the right position of the rectangle.
+ ///
+ public int Right => X + Width;
+
+ ///
+ /// Gets the bottom position of the rectangle.
+ ///
+ public int Bottom => Y + Height;
+
+ ///
+ /// Gets the top left point of the rectangle.
+ ///
+ public PixelPoint TopLeft => new PixelPoint(X, Y);
+
+ ///
+ /// Gets the top right point of the rectangle.
+ ///
+ public PixelPoint TopRight => new PixelPoint(Right, Y);
+
+ ///
+ /// Gets the bottom left point of the rectangle.
+ ///
+ public PixelPoint BottomLeft => new PixelPoint(X, Bottom);
+
+ ///
+ /// Gets the bottom right point of the rectangle.
+ ///
+ public PixelPoint BottomRight => new PixelPoint(Right, Bottom);
+
+ ///
+ /// Gets the center point of the rectangle.
+ ///
+ public PixelPoint Center => new PixelPoint(X + (Width / 2), Y + (Height / 2));
+
+ ///
+ /// Gets a value that indicates whether the rectangle is empty.
+ ///
+ public bool IsEmpty => Width == 0 && Height == 0;
+
+ ///
+ /// Checks for equality between two s.
+ ///
+ /// The first rect.
+ /// The second rect.
+ /// True if the rects are equal; otherwise false.
+ public static bool operator ==(PixelRect left, PixelRect right)
+ {
+ return left.Position == right.Position && left.Size == right.Size;
+ }
+
+ ///
+ /// Checks for inequality between two s.
+ ///
+ /// The first rect.
+ /// The second rect.
+ /// True if the rects are unequal; otherwise false.
+ public static bool operator !=(PixelRect left, PixelRect right)
+ {
+ return !(left == right);
+ }
+
+ ///
+ /// Determines whether a point in in the bounds of the rectangle.
+ ///
+ /// The point.
+ /// true if the point is in the bounds of the rectangle; otherwise false.
+ public bool Contains(PixelPoint p)
+ {
+ return p.X >= X && p.X <= Right && p.Y >= Y && p.Y <= Bottom;
+ }
+
+ ///
+ /// Determines whether the rectangle fully contains another rectangle.
+ ///
+ /// The rectangle.
+ /// true if the rectangle is fully contained; otherwise false.
+ public bool Contains(PixelRect r)
+ {
+ return Contains(r.TopLeft) && Contains(r.BottomRight);
+ }
+
+ ///
+ /// Centers another rectangle in this rectangle.
+ ///
+ /// The rectangle to center.
+ /// The centered rectangle.
+ public PixelRect CenterRect(PixelRect rect)
+ {
+ return new PixelRect(
+ X + ((Width - rect.Width) / 2),
+ Y + ((Height - rect.Height) / 2),
+ rect.Width,
+ rect.Height);
+ }
+
+ ///
+ /// Returns a boolean indicating whether the given object is equal to this rectangle.
+ ///
+ /// The object to compare against.
+ /// True if the object is equal to this rectangle; false otherwise.
+ public override bool Equals(object obj)
+ {
+ if (obj is PixelRect other)
+ {
+ return this == other;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Returns the hash code for this instance.
+ ///
+ /// The hash code.
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hash = 17;
+ hash = (hash * 23) + X.GetHashCode();
+ hash = (hash * 23) + Y.GetHashCode();
+ hash = (hash * 23) + Width.GetHashCode();
+ hash = (hash * 23) + Height.GetHashCode();
+ return hash;
+ }
+ }
+
+ ///
+ /// Gets the intersection of two rectangles.
+ ///
+ /// The other rectangle.
+ /// The intersection.
+ public PixelRect Intersect(PixelRect rect)
+ {
+ var newLeft = (rect.X > X) ? rect.X : X;
+ var newTop = (rect.Y > Y) ? rect.Y : Y;
+ var newRight = (rect.Right < Right) ? rect.Right : Right;
+ var newBottom = (rect.Bottom < Bottom) ? rect.Bottom : Bottom;
+
+ if ((newRight > newLeft) && (newBottom > newTop))
+ {
+ return new PixelRect(newLeft, newTop, newRight - newLeft, newBottom - newTop);
+ }
+ else
+ {
+ return Empty;
+ }
+ }
+
+ ///
+ /// Determines whether a rectangle intersects with this rectangle.
+ ///
+ /// The other rectangle.
+ ///
+ /// True if the specified rectangle intersects with this one; otherwise false.
+ ///
+ public bool Intersects(PixelRect rect)
+ {
+ return (rect.X < Right) && (X < rect.Right) && (rect.Y < Bottom) && (Y < rect.Bottom);
+ }
+
+ ///
+ /// Gets the union of two rectangles.
+ ///
+ /// The other rectangle.
+ /// The union.
+ public PixelRect Union(PixelRect rect)
+ {
+ if (IsEmpty)
+ {
+ return rect;
+ }
+ else if (rect.IsEmpty)
+ {
+ return this;
+ }
+ else
+ {
+ var x1 = Math.Min(X, rect.X);
+ var x2 = Math.Max(Right, rect.Right);
+ var y1 = Math.Min(Y, rect.Y);
+ var y2 = Math.Max(Bottom, rect.Bottom);
+
+ return new PixelRect(new PixelPoint(x1, y1), new PixelPoint(x2, y2));
+ }
+ }
+
+ ///
+ /// Returns a new with the specified X position.
+ ///
+ /// The x position.
+ /// The new .
+ public PixelRect WithX(int x)
+ {
+ return new PixelRect(x, Y, Width, Height);
+ }
+
+ ///
+ /// Returns a new with the specified Y position.
+ ///
+ /// The y position.
+ /// The new .
+ public PixelRect WithY(int y)
+ {
+ return new PixelRect(X, y, Width, Height);
+ }
+
+ ///
+ /// Returns a new with the specified width.
+ ///
+ /// The width.
+ /// The new .
+ public PixelRect WithWidth(int width)
+ {
+ return new PixelRect(X, Y, width, Height);
+ }
+
+ ///
+ /// Returns a new with the specified height.
+ ///
+ /// The height.
+ /// The new .
+ public PixelRect WithHeight(int height)
+ {
+ return new PixelRect(X, Y, Width, Height);
+ }
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified scaling factor.
+ ///
+ /// The scaling factor.
+ /// The device-independent rect.
+ public Rect ToRect(double scale) => new Rect(Position.ToPoint(scale), Size.ToSize(scale));
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified scaling factor.
+ ///
+ /// The scaling factor.
+ /// The device-independent rect.
+ public Rect ToRect(Vector scale) => new Rect(Position.ToPoint(scale), Size.ToSize(scale));
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified dots per inch (DPI).
+ ///
+ /// The dots per inch of the device.
+ /// The device-independent rect.
+ public Rect ToRectWithDpi(double dpi) => new Rect(Position.ToPointWithDpi(dpi), Size.ToSizeWithDpi(dpi));
+
+ ///
+ /// Converts the to a device-independent using the
+ /// specified dots per inch (DPI).
+ ///
+ /// The dots per inch of the device.
+ /// The device-independent rect.
+ public Rect ToRectWithDpi(Vector dpi) => new Rect(Position.ToPointWithDpi(dpi), Size.ToSizeWithDpi(dpi));
+
+ ///
+ /// Converts a to device pixels using the specified scaling factor.
+ ///
+ /// The rect.
+ /// The scaling factor.
+ /// The device-independent rect.
+ public static PixelRect FromRect(Rect rect, double scale) => new PixelRect(
+ PixelPoint.FromPoint(rect.Position, scale),
+ PixelSize.FromSize(rect.Size, scale));
+
+ ///
+ /// Converts a to device pixels using the specified scaling factor.
+ ///
+ /// The rect.
+ /// The scaling factor.
+ /// The device-independent point.
+ public static PixelRect FromRect(Rect rect, Vector scale) => new PixelRect(
+ PixelPoint.FromPoint(rect.Position, scale),
+ PixelSize.FromSize(rect.Size, scale));
+
+ ///
+ /// Converts a to device pixels using the specified dots per inch (DPI).
+ ///
+ /// The rect.
+ /// The dots per inch of the device.
+ /// The device-independent point.
+ public static PixelRect FromRectWithDpi(Rect rect, double dpi) => new PixelRect(
+ PixelPoint.FromPointWithDpi(rect.Position, dpi),
+ PixelSize.FromSizeWithDpi(rect.Size, dpi));
+
+ ///
+ /// Converts a to device pixels using the specified dots per inch (DPI).
+ ///
+ /// The rect.
+ /// The dots per inch of the device.
+ /// The device-independent point.
+ public static PixelRect FromRectWithDpi(Rect rect, Vector dpi) => new PixelRect(
+ PixelPoint.FromPointWithDpi(rect.Position, dpi),
+ PixelSize.FromSizeWithDpi(rect.Size, dpi));
+
+ ///
+ /// Returns the string representation of the rectangle.
+ ///
+ /// The string representation of the rectangle.
+ public override string ToString()
+ {
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}, {1}, {2}, {3}",
+ X,
+ Y,
+ Width,
+ Height);
+ }
+
+ ///
+ /// Parses a string.
+ ///
+ /// The string.
+ /// The parsed .
+ public static PixelRect Parse(string s)
+ {
+ using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelRect"))
+ {
+ return new PixelRect(
+ tokenizer.ReadInt32(),
+ tokenizer.ReadInt32(),
+ tokenizer.ReadInt32(),
+ tokenizer.ReadInt32()
+ );
+ }
+ }
+ }
+}