diff --git a/Tests/Perspex.RenderTests/Media/ImageBrushTests.cs b/Tests/Perspex.RenderTests/Media/ImageBrushTests.cs
new file mode 100644
index 0000000000..21a3047529
--- /dev/null
+++ b/Tests/Perspex.RenderTests/Media/ImageBrushTests.cs
@@ -0,0 +1,395 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Perspex.Controls;
+using Perspex.Controls.Shapes;
+using Perspex.Layout;
+using Perspex.Media;
+using Perspex.Media.Imaging;
+using Xunit;
+
+#if PERSPEX_CAIRO
+namespace Perspex.Cairo.RenderTests.Media
+#else
+namespace Perspex.Direct2D1.RenderTests.Media
+#endif
+{
+ public class ImageBrushTests : TestBase
+ {
+ public ImageBrushTests()
+ : base(@"Media\ImageBrush")
+ {
+ }
+
+ private string BitmapPath
+ {
+ get { return System.IO.Path.Combine(OutputPath, "github_icon.png"); }
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "ImageBrush not yet implemented on Cairo")]
+#else
+ [Fact]
+#endif
+ public void ImageBrush_NoStretch_NoTile_Alignment_TopLeft()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Rectangle
+ {
+ Fill = new ImageBrush
+ {
+ Stretch = Stretch.None,
+ TileMode = TileMode.None,
+ AlignmentX = AlignmentX.Left,
+ AlignmentY = AlignmentY.Top,
+ Source = new Bitmap(BitmapPath),
+ }
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "ImageBrush not yet implemented on Cairo")]
+#else
+ [Fact]
+#endif
+ public void ImageBrush_NoStretch_NoTile_Alignment_Center()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Rectangle
+ {
+ Fill = new ImageBrush
+ {
+ Stretch = Stretch.None,
+ TileMode = TileMode.None,
+ AlignmentX = AlignmentX.Center,
+ AlignmentY = AlignmentY.Center,
+ Source = new Bitmap(BitmapPath),
+ }
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "ImageBrush not yet implemented on Cairo")]
+#else
+ [Fact]
+#endif
+ public void ImageBrush_NoStretch_NoTile_Alignment_BottomRight()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Rectangle
+ {
+ Fill = new ImageBrush
+ {
+ Stretch = Stretch.None,
+ TileMode = TileMode.None,
+ AlignmentX = AlignmentX.Right,
+ AlignmentY = AlignmentY.Bottom,
+ Source = new Bitmap(BitmapPath),
+ }
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "ImageBrush not yet implemented on Cairo")]
+#else
+ [Fact]
+#endif
+ public void ImageBrush_Fill_NoTile()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 920,
+ Height = 920,
+ Child = new Rectangle
+ {
+ Fill = new ImageBrush
+ {
+ Stretch = Stretch.Fill,
+ TileMode = TileMode.None,
+ Source = new Bitmap(BitmapPath),
+ }
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "ImageBrush not yet implemented on Cairo")]
+#else
+ [Fact]
+#endif
+ public void ImageBrush_Uniform_NoTile()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 300,
+ Height = 200,
+ Child = new Rectangle
+ {
+ Fill = new ImageBrush
+ {
+ Stretch = Stretch.Uniform,
+ TileMode = TileMode.None,
+ Source = new Bitmap(BitmapPath),
+ }
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "ImageBrush not yet implemented on Cairo")]
+#else
+ [Fact]
+#endif
+ public void ImageBrush_UniformToFill_NoTile()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 300,
+ Height = 200,
+ Child = new Rectangle
+ {
+ Fill = new ImageBrush
+ {
+ Stretch = Stretch.UniformToFill,
+ TileMode = TileMode.None,
+ Source = new Bitmap(BitmapPath),
+ }
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "ImageBrush not yet implemented on Cairo")]
+#else
+ [Fact]
+#endif
+ public void ImageBrush_NoStretch_NoTile_BottomRightQuarterSource()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Rectangle
+ {
+ Fill = new ImageBrush
+ {
+ Stretch = Stretch.None,
+ TileMode = TileMode.None,
+ SourceRect = new RelativeRect(250, 250, 250, 250, RelativeUnit.Absolute),
+ Source = new Bitmap(BitmapPath),
+ }
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "ImageBrush not yet implemented on Cairo")]
+#else
+ [Fact]
+#endif
+ public void ImageBrush_NoStretch_NoTile_BottomRightQuarterDest()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Rectangle
+ {
+ Fill = new ImageBrush
+ {
+ Stretch = Stretch.None,
+ TileMode = TileMode.None,
+ DestinationRect = new RelativeRect(92, 92, 92, 92, RelativeUnit.Absolute),
+ Source = new Bitmap(BitmapPath),
+ }
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "ImageBrush not yet implemented on Cairo")]
+#else
+ [Fact]
+#endif
+ public void ImageBrush_NoStretch_NoTile_BottomRightQuarterSource_BottomRightQuarterDest()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Rectangle
+ {
+ Fill = new ImageBrush
+ {
+ Stretch = Stretch.None,
+ TileMode = TileMode.None,
+ SourceRect = new RelativeRect(0.5, 0.5, 0.5, 0.5, RelativeUnit.Relative),
+ DestinationRect = new RelativeRect(0.5, 0.5, 0.5, 0.5, RelativeUnit.Relative),
+ Source = new Bitmap(BitmapPath),
+ }
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "ImageBrush not yet implemented on Cairo")]
+#else
+ [Fact]
+#endif
+ public void ImageBrush_NoStretch_Tile_BottomRightQuarterSource_CenterQuarterDest()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Rectangle
+ {
+ Fill = new ImageBrush
+ {
+ Stretch = Stretch.None,
+ TileMode = TileMode.Tile,
+ SourceRect = new RelativeRect(0.5, 0.5, 0.5, 0.5, RelativeUnit.Relative),
+ DestinationRect = new RelativeRect(0.25, 0.25, 0.5, 0.5, RelativeUnit.Relative),
+ Source = new Bitmap(BitmapPath),
+ }
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "ImageBrush not yet implemented on Cairo")]
+#else
+ [Fact]
+#endif
+ public void ImageBrush_NoStretch_FlipX_TopLeftDest()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Rectangle
+ {
+ Fill = new ImageBrush
+ {
+ Stretch = Stretch.None,
+ TileMode = TileMode.FlipX,
+ DestinationRect = new RelativeRect(0, 0, 0.5, 0.5, RelativeUnit.Relative),
+ Source = new Bitmap(BitmapPath),
+ }
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "ImageBrush not yet implemented on Cairo")]
+#else
+ [Fact]
+#endif
+ public void ImageBrush_NoStretch_FlipY_TopLeftDest()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Rectangle
+ {
+ Fill = new ImageBrush
+ {
+ Stretch = Stretch.None,
+ TileMode = TileMode.FlipY,
+ DestinationRect = new RelativeRect(0, 0, 0.5, 0.5, RelativeUnit.Relative),
+ Source = new Bitmap(BitmapPath),
+ }
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+
+#if PERSPEX_CAIRO
+ [Fact(Skip = "ImageBrush not yet implemented on Cairo")]
+#else
+ [Fact]
+#endif
+ public void ImageBrush_NoStretch_FlipXY_TopLeftDest()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 200,
+ Height = 200,
+ Child = new Rectangle
+ {
+ Fill = new ImageBrush
+ {
+ Stretch = Stretch.None,
+ TileMode = TileMode.FlipXY,
+ DestinationRect = new RelativeRect(0, 0, 0.5, 0.5, RelativeUnit.Relative),
+ Source = new Bitmap(BitmapPath),
+ }
+ }
+ };
+
+ RenderToFile(target);
+ CompareImages();
+ }
+ }
+}
diff --git a/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Fill_NoTile.expected.png b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Fill_NoTile.expected.png
new file mode 100644
index 0000000000..73d147bf77
Binary files /dev/null and b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Fill_NoTile.expected.png differ
diff --git a/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_FlipXY_TopLeftDest.expected.png b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_FlipXY_TopLeftDest.expected.png
new file mode 100644
index 0000000000..f99f56ec14
Binary files /dev/null and b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_FlipXY_TopLeftDest.expected.png differ
diff --git a/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_FlipX_TopLeftDest.expected.png b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_FlipX_TopLeftDest.expected.png
new file mode 100644
index 0000000000..4d89f995c4
Binary files /dev/null and b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_FlipX_TopLeftDest.expected.png differ
diff --git a/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_FlipY_TopLeftDest.expected.png b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_FlipY_TopLeftDest.expected.png
new file mode 100644
index 0000000000..75c53f0c28
Binary files /dev/null and b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_FlipY_TopLeftDest.expected.png differ
diff --git a/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_Alignment_BottomRight.expected.png b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_Alignment_BottomRight.expected.png
new file mode 100644
index 0000000000..a13b34ba2d
Binary files /dev/null and b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_Alignment_BottomRight.expected.png differ
diff --git a/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_Alignment_Center.expected.png b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_Alignment_Center.expected.png
new file mode 100644
index 0000000000..14704f751c
Binary files /dev/null and b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_Alignment_Center.expected.png differ
diff --git a/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_Alignment_TopLeft.expected.png b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_Alignment_TopLeft.expected.png
new file mode 100644
index 0000000000..7ebac13d30
Binary files /dev/null and b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_Alignment_TopLeft.expected.png differ
diff --git a/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_BottomRightQuarterDest.expected.png b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_BottomRightQuarterDest.expected.png
new file mode 100644
index 0000000000..dd41cb98c3
Binary files /dev/null and b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_BottomRightQuarterDest.expected.png differ
diff --git a/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_BottomRightQuarterSource.expected.png b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_BottomRightQuarterSource.expected.png
new file mode 100644
index 0000000000..ab9f2b76e3
Binary files /dev/null and b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_BottomRightQuarterSource.expected.png differ
diff --git a/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_BottomRightQuarterSource_BottomRightQuarterDest.expected.png b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_BottomRightQuarterSource_BottomRightQuarterDest.expected.png
new file mode 100644
index 0000000000..fea4a2a8e9
Binary files /dev/null and b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_NoTile_BottomRightQuarterSource_BottomRightQuarterDest.expected.png differ
diff --git a/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_Tile_BottomRightQuarterSource_CenterQuarterDest.expected.png b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_Tile_BottomRightQuarterSource_CenterQuarterDest.expected.png
new file mode 100644
index 0000000000..afd39c2360
Binary files /dev/null and b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_NoStretch_Tile_BottomRightQuarterSource_CenterQuarterDest.expected.png differ
diff --git a/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_UniformToFill_NoTile.expected.png b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_UniformToFill_NoTile.expected.png
new file mode 100644
index 0000000000..e173fa6cee
Binary files /dev/null and b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_UniformToFill_NoTile.expected.png differ
diff --git a/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Uniform_NoTile.expected.png b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Uniform_NoTile.expected.png
new file mode 100644
index 0000000000..de73af8170
Binary files /dev/null and b/Tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Uniform_NoTile.expected.png differ
diff --git a/Tests/TestFiles/Direct2D1/Media/ImageBrush/github_icon.png b/Tests/TestFiles/Direct2D1/Media/ImageBrush/github_icon.png
new file mode 100644
index 0000000000..cd053c5fe1
Binary files /dev/null and b/Tests/TestFiles/Direct2D1/Media/ImageBrush/github_icon.png differ
diff --git a/src/Perspex.SceneGraph/Media/ImageBush.cs b/src/Perspex.SceneGraph/Media/ImageBush.cs
new file mode 100644
index 0000000000..1b3fd07d28
--- /dev/null
+++ b/src/Perspex.SceneGraph/Media/ImageBush.cs
@@ -0,0 +1,44 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Perspex.Media.Imaging;
+
+namespace Perspex.Media
+{
+ ///
+ /// Paints an area with an .
+ ///
+ public class ImageBrush : TileBrush
+ {
+ ///
+ /// Defines the property.
+ ///
+ public static readonly PerspexProperty SourceProperty =
+ PerspexProperty.Register("Source");
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ImageBrush()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The image to draw.
+ public ImageBrush(IBitmap source)
+ {
+ Source = source;
+ }
+
+ ///
+ /// Gets or sets the image to draw.
+ ///
+ public IBitmap Source
+ {
+ get { return GetValue(SourceProperty); }
+ set { SetValue(SourceProperty, value); }
+ }
+ }
+}
diff --git a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj
index 660cbe38d7..fac8cf2924 100644
--- a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj
+++ b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj
@@ -64,11 +64,11 @@
-
+
@@ -97,6 +97,7 @@
+
diff --git a/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs b/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs
index 53f5ff3d5c..d0c2e4c254 100644
--- a/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs
+++ b/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs
@@ -303,6 +303,7 @@ namespace Perspex.Direct2D1.Media
{
var solidColorBrush = brush as Perspex.Media.SolidColorBrush;
var linearGradientBrush = brush as Perspex.Media.LinearGradientBrush;
+ var imageBrush = brush as ImageBrush;
var visualBrush = brush as VisualBrush;
if (solidColorBrush != null)
@@ -313,6 +314,10 @@ namespace Perspex.Direct2D1.Media
{
return new LinearGradientBrushImpl(linearGradientBrush, _renderTarget, destinationSize);
}
+ else if (imageBrush != null)
+ {
+ return new ImageBrushImpl(imageBrush, _renderTarget, destinationSize);
+ }
else if (visualBrush != null)
{
return new VisualBrushImpl(visualBrush, _renderTarget, destinationSize);
diff --git a/src/Windows/Perspex.Direct2D1/Media/ImageBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/ImageBrushImpl.cs
new file mode 100644
index 0000000000..c1d4c0e0b2
--- /dev/null
+++ b/src/Windows/Perspex.Direct2D1/Media/ImageBrushImpl.cs
@@ -0,0 +1,93 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using Perspex.Media;
+using SharpDX.Direct2D1;
+
+namespace Perspex.Direct2D1.Media
+{
+ public class ImageBrushImpl : TileBrushImpl
+ {
+ public ImageBrushImpl(
+ ImageBrush brush,
+ RenderTarget target,
+ Size targetSize)
+ {
+ if (brush.Source == null)
+ {
+ return;
+ }
+
+ var image = ((BitmapImpl)brush.Source.PlatformImpl).GetDirect2DBitmap(target);
+ var imageSize = new Size(brush.Source.PixelWidth, brush.Source.PixelHeight);
+ var tileMode = brush.TileMode;
+ var sourceRect = brush.SourceRect.ToPixels(imageSize);
+ var destinationRect = brush.DestinationRect.ToPixels(targetSize);
+ var scale = brush.Stretch.CalculateScaling(destinationRect.Size, sourceRect.Size);
+ var translate = CalculateTranslate(brush, sourceRect, destinationRect, scale);
+ var intermediateSize = CalculateIntermediateSize(tileMode, targetSize, destinationRect.Size);
+ var brtOpts = CompatibleRenderTargetOptions.None;
+
+ // TODO: There are times where we don't need to draw an intermediate bitmap. Identify
+ // them and directly use 'image' in those cases.
+ using (var intermediate = new BitmapRenderTarget(target, brtOpts, intermediateSize))
+ {
+ SharpDX.RectangleF drawRect;
+
+ intermediate.BeginDraw();
+ intermediate.Transform = CalculateIntermediateTransform(
+ tileMode,
+ sourceRect,
+ destinationRect,
+ scale,
+ translate,
+ out drawRect);
+ intermediate.PushAxisAlignedClip(drawRect, AntialiasMode.Aliased);
+ intermediate.DrawBitmap(image, 1, BitmapInterpolationMode.Linear);
+ intermediate.PopAxisAlignedClip();
+ intermediate.EndDraw();
+
+ this.PlatformBrush = new BitmapBrush(
+ target,
+ intermediate.Bitmap,
+ GetBitmapBrushProperties(brush),
+ GetBrushProperties(brush, destinationRect));
+ }
+ }
+
+ private BitmapBrush CreateDirectBrush(
+ ImageBrush brush,
+ RenderTarget target,
+ Bitmap image,
+ Rect sourceRect,
+ Rect destinationRect)
+ {
+ var tileMode = brush.TileMode;
+ var scale = brush.Stretch.CalculateScaling(destinationRect.Size, sourceRect.Size);
+ var translate = CalculateTranslate(brush, sourceRect, destinationRect, scale);
+ var transform = Matrix.CreateTranslation(-sourceRect.Position) *
+ Matrix.CreateScale(scale) *
+ Matrix.CreateTranslation(translate);
+
+ var opts = new BrushProperties
+ {
+ Transform = transform.ToDirect2D(),
+ Opacity = (float)brush.Opacity,
+ };
+
+ var bitmapOpts = new BitmapBrushProperties
+ {
+ ExtendModeX = GetExtendModeX(tileMode),
+ ExtendModeY = GetExtendModeY(tileMode),
+ };
+
+ return new BitmapBrush(target, image, bitmapOpts, opts);
+ }
+
+ private BitmapBrush CreateIndirectBrush()
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs
new file mode 100644
index 0000000000..edd31ae0b6
--- /dev/null
+++ b/src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs
@@ -0,0 +1,135 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using Perspex.Layout;
+using Perspex.Media;
+using SharpDX;
+using SharpDX.Direct2D1;
+
+namespace Perspex.Direct2D1.Media
+{
+ public class TileBrushImpl : BrushImpl
+ {
+ ///
+ /// Calculates a translate based on a , a source and destination
+ /// rectangle and a scale.
+ ///
+ /// The brush.
+ /// The source rectangle.
+ /// The destination rectangle.
+ /// The scale factor.
+ /// A vector with the X and Y translate.
+ protected static Vector CalculateTranslate(
+ TileBrush brush,
+ Rect sourceRect,
+ Rect destinationRect,
+ Vector scale)
+ {
+ var x = 0.0;
+ var y = 0.0;
+ var size = sourceRect.Size * scale;
+
+ switch (brush.AlignmentX)
+ {
+ case AlignmentX.Center:
+ x += (destinationRect.Width - size.Width) / 2;
+ break;
+ case AlignmentX.Right:
+ x += destinationRect.Width - size.Width;
+ break;
+ }
+
+ switch (brush.AlignmentY)
+ {
+ case AlignmentY.Center:
+ y += (destinationRect.Height - size.Height) / 2;
+ break;
+ case AlignmentY.Bottom:
+ y += destinationRect.Height - size.Height;
+ break;
+ }
+
+ return new Vector(x, y);
+ }
+
+ protected static Size2F CalculateIntermediateSize(
+ TileMode tileMode,
+ Size targetSize,
+ Size destinationSize)
+ {
+ var result = tileMode == TileMode.None ? targetSize : destinationSize;
+ return result.ToSharpDX();
+ }
+
+ protected static Matrix3x2 CalculateIntermediateTransform(
+ TileMode tileMode,
+ Rect sourceRect,
+ Rect destinationRect,
+ Vector scale,
+ Vector translate,
+ out SharpDX.RectangleF drawRect)
+ {
+ var transform = Matrix.CreateTranslation(-sourceRect.Position) *
+ Matrix.CreateScale(scale) *
+ Matrix.CreateTranslation(translate);
+ Rect dr;
+
+ if (tileMode == TileMode.None)
+ {
+ dr = destinationRect;
+ transform *= Matrix.CreateTranslation(destinationRect.Position);
+ }
+ else
+ {
+ dr = new Rect(destinationRect.Size);
+ }
+
+ drawRect = (dr * -transform).ToSharpDX();
+
+ return transform.ToDirect2D();
+ }
+
+ protected static BrushProperties GetBrushProperties(TileBrush brush, Rect destinationRect)
+ {
+ var tileMode = brush.TileMode;
+
+ return new BrushProperties
+ {
+ Opacity = (float)brush.Opacity,
+ Transform = brush.TileMode != TileMode.None ?
+ SharpDX.Matrix3x2.Translation(
+ (float)destinationRect.X,
+ (float)destinationRect.Y) :
+ SharpDX.Matrix3x2.Identity,
+ };
+ }
+
+ protected static BitmapBrushProperties GetBitmapBrushProperties(TileBrush brush)
+ {
+ var tileMode = brush.TileMode;
+
+ return new BitmapBrushProperties
+ {
+ ExtendModeX = GetExtendModeX(tileMode),
+ ExtendModeY = GetExtendModeY(tileMode),
+ };
+ }
+
+ protected static ExtendMode GetExtendModeX(TileMode tileMode)
+ {
+ return (tileMode & TileMode.FlipX) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap;
+ }
+
+ protected static ExtendMode GetExtendModeY(TileMode tileMode)
+ {
+ return (tileMode & TileMode.FlipY) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap;
+ }
+
+ public override void Dispose()
+ {
+ ((BitmapBrush)PlatformBrush)?.Bitmap.Dispose();
+ base.Dispose();
+ }
+ }
+}
diff --git a/src/Windows/Perspex.Direct2D1/Media/VisualBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/VisualBrushImpl.cs
index f7e12187f6..2e4622b5c0 100644
--- a/src/Windows/Perspex.Direct2D1/Media/VisualBrushImpl.cs
+++ b/src/Windows/Perspex.Direct2D1/Media/VisualBrushImpl.cs
@@ -8,7 +8,7 @@ using SharpDX.Direct2D1;
namespace Perspex.Direct2D1.Media
{
- public class VisualBrushImpl : BrushImpl
+ public class VisualBrushImpl : TileBrushImpl
{
public VisualBrushImpl(
VisualBrush brush,
@@ -59,8 +59,8 @@ namespace Perspex.Direct2D1.Media
renderer.Render(visual, null, transform, drawRect);
var result = new BitmapBrush(brt, brt.Bitmap);
- result.ExtendModeX = (brush.TileMode & TileMode.FlipX) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap;
- result.ExtendModeY = (brush.TileMode & TileMode.FlipY) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap;
+ result.ExtendModeX = GetExtendModeX(brush.TileMode);
+ result.ExtendModeY = GetExtendModeY(brush.TileMode);
if (brush.TileMode != TileMode.None)
{
@@ -72,44 +72,5 @@ namespace Perspex.Direct2D1.Media
PlatformBrush = result;
}
}
-
- private static Vector CalculateTranslate(
- VisualBrush brush,
- Rect sourceRect,
- Rect destinationRect,
- Vector scale)
- {
- var x = 0.0;
- var y = 0.0;
- var size = sourceRect.Size * scale;
-
- switch (brush.AlignmentX)
- {
- case AlignmentX.Center:
- x += (destinationRect.Width - size.Width) / 2;
- break;
- case AlignmentX.Right:
- x += destinationRect.Width - size.Width;
- break;
- }
-
- switch (brush.AlignmentY)
- {
- case AlignmentY.Center:
- y += (destinationRect.Height - size.Height) / 2;
- break;
- case AlignmentY.Bottom:
- y += destinationRect.Height - size.Height;
- break;
- }
-
- return new Vector(x, y);
- }
-
- public override void Dispose()
- {
- ((BitmapBrush)PlatformBrush)?.Bitmap.Dispose();
- base.Dispose();
- }
}
}
diff --git a/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj b/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj
index 922f67391b..0fa382e99d 100644
--- a/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj
+++ b/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj
@@ -82,6 +82,8 @@
+
+
diff --git a/tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj b/tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj
index 6a8b105dc5..7956643dca 100644
--- a/tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj
+++ b/tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj
@@ -75,6 +75,7 @@
+
diff --git a/tests/Perspex.RenderTests/TestBase.cs b/tests/Perspex.RenderTests/TestBase.cs
index 36a34610a2..46083fc5b3 100644
Binary files a/tests/Perspex.RenderTests/TestBase.cs and b/tests/Perspex.RenderTests/TestBase.cs differ