Browse Source

Merge branch 'visualbrush'

Steven Kirk 10 years ago
parent
commit
0a0e25935b
34 changed files with 1227 additions and 76 deletions
  1. 1 1
      Perspex.sln
  2. 2 33
      src/Perspex.Controls/Image.cs
  3. 29 0
      src/Perspex.SceneGraph/Media/AlignmentX.cs
  4. 29 0
      src/Perspex.SceneGraph/Media/AlignmentY.cs
  5. 47 0
      src/Perspex.SceneGraph/Media/MediaExtensions.cs
  6. 136 0
      src/Perspex.SceneGraph/Media/TileBrush.cs
  7. 45 0
      src/Perspex.SceneGraph/Media/VisualBrush.cs
  8. 6 0
      src/Perspex.SceneGraph/Perspex.SceneGraph.csproj
  9. 10 0
      src/Perspex.SceneGraph/Point.cs
  10. 105 0
      src/Perspex.SceneGraph/RelativeRect.cs
  11. 14 1
      src/Perspex.SceneGraph/Rendering/RendererBase.cs
  12. 39 0
      src/Windows/Perspex.Direct2D1/Disposable.cs
  13. 48 19
      src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs
  14. 1 1
      src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs
  15. 112 0
      src/Windows/Perspex.Direct2D1/Media/VisualBrushImpl.cs
  16. 6 0
      src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj
  17. 0 21
      src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs
  18. 596 0
      tests/Perspex.RenderTests/Media/VisualBrushTests.cs
  19. 1 0
      tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj
  20. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Align_BottomRight.expected.png
  21. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Align_Center.expected.png
  22. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Align_TopLeft.expected.png
  23. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_DestinationRect_Absolute.expected.png
  24. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_FlipX.expected.png
  25. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_FlipXY.expected.png
  26. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_FlipY.expected.png
  27. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_SourceRect_Absolute.expected.png
  28. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_SourceRect_DestinationRect_Absolute.expected.png
  29. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_SourceRect_DestinationRect_Percent.expected.png
  30. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Stretch_Fill_Large.expected.png
  31. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Stretch_Uniform.expected.png
  32. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Stretch_UniformToFill.expected.png
  33. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Tile.expected.png
  34. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Tile_Alignment_BottomRight.expected.png

+ 1 - 1
Perspex.sln

@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 14
-VisualStudioVersion = 14.0.22823.1
+VisualStudioVersion = 14.0.23107.0
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Win32", "src\Windows\Perspex.Win32\Perspex.Win32.csproj", "{811A76CF-1CF6-440F-963B-BBE31BD72A82}"
 EndProject

+ 2 - 33
src/Perspex.Controls/Image.cs

@@ -57,7 +57,7 @@ namespace Perspex.Controls
             {
                 Rect viewPort = new Rect(this.Bounds.Size);
                 Size sourceSize = new Size(source.PixelWidth, source.PixelHeight);
-                Vector scale = CalculateScaling(this.Bounds.Size, sourceSize, this.Stretch);
+                Vector scale = this.Stretch.CalculateScaling(this.Bounds.Size, sourceSize);
                 Size scaledSize = sourceSize * scale;
                 Rect destRect = viewPort
                     .CenterIn(new Rect(scaledSize))
@@ -95,41 +95,10 @@ namespace Perspex.Controls
                     availableSize = new Size(availableSize.Width, this.Height);
                 }
 
-                scale = CalculateScaling(availableSize, new Size(width, height), this.Stretch);
+                scale = this.Stretch.CalculateScaling(availableSize, new Size(width, height));
             }
 
             return new Size(width * scale.X, height * scale.Y);
         }
-
-        /// <summary>
-        /// Calculates the scaling for the image.
-        /// </summary>
-        /// <param name="availableSize">The size available to display the image.</param>
-        /// <param name="imageSize">The pxiel size of the image.</param>
-        /// <param name="stretch">The stretch mode of the control.</param>
-        /// <returns>A vector with the X and Y scaling factors.</returns>
-        private static Vector CalculateScaling(Size availableSize, Size imageSize, Stretch stretch)
-        {
-            double scaleX = 1;
-            double scaleY = 1;
-
-            if (stretch != Stretch.None)
-            {
-                scaleX = availableSize.Width / imageSize.Width;
-                scaleY = availableSize.Height / imageSize.Height;
-
-                switch (stretch)
-                {
-                    case Stretch.Uniform:
-                        scaleX = scaleY = Math.Min(scaleX, scaleY);
-                        break;
-                    case Stretch.UniformToFill:
-                        scaleX = scaleY = Math.Max(scaleX, scaleY);
-                        break;
-                }
-            }
-
-            return new Vector(scaleX, scaleY);
-        }
     }
 }

+ 29 - 0
src/Perspex.SceneGraph/Media/AlignmentX.cs

@@ -0,0 +1,29 @@
+// -----------------------------------------------------------------------
+// <copyright file="AlignmentX.cs" company="Steven Kirk">
+// Copyright 2015 MIT Licence. See licence.md for more information.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Perspex.Media
+{
+    /// <summary>
+    /// Describes how content is positioned horizontally in a container.
+    /// </summary>
+    public enum AlignmentX
+    {
+        /// <summary>
+        /// The contents align themselves with the left of the container
+        /// </summary>
+        Left,
+
+        /// <summary>
+        /// The contents align themselves with the center of the container
+        /// </summary>
+        Center,
+
+        /// <summary>
+        /// The contents align themselves with the right of the container
+        /// </summary>
+        Right,
+    }
+}

+ 29 - 0
src/Perspex.SceneGraph/Media/AlignmentY.cs

@@ -0,0 +1,29 @@
+// -----------------------------------------------------------------------
+// <copyright file="AlignmentY.cs" company="Steven Kirk">
+// Copyright 2015 MIT Licence. See licence.md for more information.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Perspex.Media
+{
+    /// <summary>
+    /// Describes how content is positioned vertically in a container.
+    /// </summary>
+    public enum AlignmentY
+    {
+        /// <summary>
+        /// The contents align themselves with the top of the container
+        /// </summary>
+        Top,
+
+        /// <summary>
+        /// The contents align themselves with the center of the container
+        /// </summary>
+        Center,
+
+        /// <summary>
+        /// The contents align themselves with the bottom of the container
+        /// </summary>
+        Bottom,
+    }
+}

+ 47 - 0
src/Perspex.SceneGraph/Media/MediaExtensions.cs

@@ -0,0 +1,47 @@
+// -----------------------------------------------------------------------
+// <copyright file="MediaExtensions.cs" company="Steven Kirk">
+// Copyright 2015 MIT Licence. See licence.md for more information.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Perspex.Media
+{
+    using System;
+
+    /// <summary>
+    /// Provides extension methods for Perspex media.
+    /// </summary>
+    public static class MediaExtensions
+    {
+        /// <summary>
+        /// Calculates scaling based on a <see cref="Stretch"/> value.
+        /// </summary>
+        /// <param name="stretch">The stretch mode.</param>
+        /// <param name="destinationSize">The size of the destination viewport.</param>
+        /// <param name="sourceSize">The size of the source.</param>
+        /// <returns>A vector with the X and Y scaling factors.</returns>
+        public static Vector CalculateScaling(this Stretch stretch, Size destinationSize, Size sourceSize)
+        {
+            double scaleX = 1;
+            double scaleY = 1;
+
+            if (stretch != Stretch.None)
+            {
+                scaleX = destinationSize.Width / sourceSize.Width;
+                scaleY = destinationSize.Height / sourceSize.Height;
+
+                switch (stretch)
+                {
+                    case Stretch.Uniform:
+                        scaleX = scaleY = Math.Min(scaleX, scaleY);
+                        break;
+                    case Stretch.UniformToFill:
+                        scaleX = scaleY = Math.Max(scaleX, scaleY);
+                        break;
+                }
+            }
+
+            return new Vector(scaleX, scaleY);
+        }
+    }
+}

+ 136 - 0
src/Perspex.SceneGraph/Media/TileBrush.cs

@@ -0,0 +1,136 @@
+// -----------------------------------------------------------------------
+// <copyright file="TileBrush.cs" company="Steven Kirk">
+// Copyright 2015 MIT Licence. See licence.md for more information.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Perspex.Media
+{
+    /// <summary>
+    /// Describes how a <see cref="TileBrush"/> is tiled.
+    /// </summary>
+    public enum TileMode
+    {
+        /// <summary>
+        /// A single repeat of the content will be displayed.
+        /// </summary>
+        None,
+
+        /// <summary>
+        /// The content will be repeated horizontally, with alternate tiles mirrored.
+        /// </summary>
+        FlipX,
+
+        /// <summary>
+        /// The content will be repeated vertically, with alternate tiles mirrored.
+        /// </summary>
+        FlipY,
+
+        /// <summary>
+        /// The content will be repeated horizontally and vertically, with alternate tiles mirrored.
+        /// </summary>
+        FlipXY,
+
+        /// <summary>
+        /// The content will be repeated.
+        /// </summary>
+        Tile
+    }
+
+    /// <summary>
+    /// Base class for brushes which display repeating images.
+    /// </summary>
+    public abstract class TileBrush : Brush
+    {
+        /// <summary>
+        /// Defines the <see cref="AlignmentX"/> property.
+        /// </summary>
+        public static readonly PerspexProperty<AlignmentX> AlignmentXProperty =
+            PerspexProperty.Register<TileBrush, AlignmentX>(nameof(AlignmentX), AlignmentX.Center);
+
+        /// <summary>
+        /// Defines the <see cref="AlignmentY"/> property.
+        /// </summary>
+        public static readonly PerspexProperty<AlignmentY> AlignmentYProperty =
+            PerspexProperty.Register<TileBrush, AlignmentY>(nameof(AlignmentY), AlignmentY.Center);
+
+        /// <summary>
+        /// Defines the <see cref="DestinationRect"/> property.
+        /// </summary>
+        public static readonly PerspexProperty<RelativeRect> DestinationRectProperty =
+            PerspexProperty.Register<TileBrush, RelativeRect>(nameof(DestinationRect), RelativeRect.Fill);
+
+        /// <summary>
+        /// Defines the <see cref="SourceRect"/> property.
+        /// </summary>
+        public static readonly PerspexProperty<RelativeRect> SourceRectProperty =
+            PerspexProperty.Register<TileBrush, RelativeRect>(nameof(SourceRect), RelativeRect.Fill);
+
+        /// <summary>
+        /// Defines the <see cref="Stretch"/> property.
+        /// </summary>
+        public static readonly PerspexProperty<Stretch> StretchProperty =
+            PerspexProperty.Register<TileBrush, Stretch>(nameof(Stretch), Stretch.Uniform);
+
+        /// <summary>
+        /// Defines the <see cref="TileMode"/> property.
+        /// </summary>
+        public static readonly PerspexProperty<TileMode> TileModeProperty =
+            PerspexProperty.Register<TileBrush, TileMode>(nameof(TileMode));
+
+        /// <summary>
+        /// Gets or sets the horizontal alignment of a tile in the destination.
+        /// </summary>
+        public AlignmentX AlignmentX
+        {
+            get { return this.GetValue(AlignmentXProperty); }
+            set { this.SetValue(AlignmentXProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the horizontal alignment of a tile in the destination.
+        /// </summary>
+        public AlignmentY AlignmentY
+        {
+            get { return this.GetValue(AlignmentYProperty); }
+            set { this.SetValue(AlignmentYProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the rectangle on the destination in which to paint a tile.
+        /// </summary>
+        public RelativeRect DestinationRect
+        {
+            get { return this.GetValue(DestinationRectProperty); }
+            set { this.SetValue(DestinationRectProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the rectangle of the source image that will be displayed.
+        /// </summary>
+        public RelativeRect SourceRect
+        {
+            get { return this.GetValue(SourceRectProperty); }
+            set { this.SetValue(SourceRectProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets a value controlling how the source rectangle will be stretched to fill
+        /// the destination rect.
+        /// </summary>
+        public Stretch Stretch
+        {
+            get { return (Stretch)this.GetValue(StretchProperty); }
+            set { this.SetValue(StretchProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the brush's tile mode.
+        /// </summary>
+        public TileMode TileMode
+        {
+            get { return (TileMode)this.GetValue(TileModeProperty); }
+            set { this.SetValue(TileModeProperty, value); }
+        }
+    }
+}

+ 45 - 0
src/Perspex.SceneGraph/Media/VisualBrush.cs

@@ -0,0 +1,45 @@
+// -----------------------------------------------------------------------
+// <copyright file="VisualBrush.cs" company="Steven Kirk">
+// Copyright 2015 MIT Licence. See licence.md for more information.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Perspex.Media
+{
+    /// <summary>
+    /// Paints an area with an <see cref="IVisual"/>.
+    /// </summary>
+    public class VisualBrush : TileBrush
+    {
+        /// <summary>
+        /// Defines the <see cref="Visual"/> property.
+        /// </summary>
+        public static readonly PerspexProperty<IVisual> VisualProperty =
+            PerspexProperty.Register<VisualBrush, IVisual>("Visual");
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="VisualBrush"/> class.
+        /// </summary>
+        public VisualBrush()
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="VisualBrush"/> class.
+        /// </summary>
+        /// <param name="visual">The visual to draw.</param>
+        public VisualBrush(IVisual visual)
+        {
+            this.Visual = visual;
+        }
+
+        /// <summary>
+        /// Gets or sets the visual to draw.
+        /// </summary>
+        public IVisual Visual
+        {
+            get { return this.GetValue(VisualProperty); }
+            set { this.SetValue(VisualProperty, value); }
+        }
+    }
+}

+ 6 - 0
src/Perspex.SceneGraph/Perspex.SceneGraph.csproj

@@ -57,11 +57,14 @@
     <Compile Include="Animation\IPageTransition.cs" />
     <Compile Include="GlobalSuppressions.cs" />
     <Compile Include="Matrix.cs" />
+    <Compile Include="Media\AlignmentY.cs" />
+    <Compile Include="Media\AlignmentX.cs" />
     <Compile Include="Media\Brush.cs" />
     <Compile Include="Media\Brushes.cs" />
     <Compile Include="Media\BrushMappingMode.cs" />
     <Compile Include="Media\Color.cs" />
     <Compile Include="Media\Colors.cs" />
+    <Compile Include="Media\MediaExtensions.cs" />
     <Compile Include="Media\GradientBrush.cs" />
     <Compile Include="Media\GradientSpreadMethod.cs" />
     <Compile Include="Media\GradientStop.cs" />
@@ -91,6 +94,8 @@
     <Compile Include="Media\SweepDirection.cs" />
     <Compile Include="Media\TextHitTestResult.cs" />
     <Compile Include="Media\Transform.cs" />
+    <Compile Include="Media\TileBrush.cs" />
+    <Compile Include="Media\VisualBrush.cs" />
     <Compile Include="Origin.cs" />
     <Compile Include="Platform\IFormattedTextImpl.cs" />
     <Compile Include="Platform\IBitmapImpl.cs" />
@@ -102,6 +107,7 @@
     <Compile Include="Platform\IStreamGeometryImpl.cs" />
     <Compile Include="Point.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="RelativeRect.cs" />
     <Compile Include="Rect.cs" />
     <Compile Include="Rendering\IRenderRoot.cs" />
     <Compile Include="Rendering\IRenderManager.cs" />

+ 10 - 0
src/Perspex.SceneGraph/Point.cs

@@ -59,6 +59,16 @@ namespace Perspex
             return new Vector(p.x, p.y);
         }
 
+        /// <summary>
+        /// Negates a point.
+        /// </summary>
+        /// <param name="a">The point.</param>
+        /// <returns>The negated point.</returns>
+        public static Point operator -(Point a)
+        {
+            return new Point(-a.x, -a.y);
+        }
+
         /// <summary>
         /// Checks for equality between two <see cref="Point"/>s.
         /// </summary>

+ 105 - 0
src/Perspex.SceneGraph/RelativeRect.cs

@@ -0,0 +1,105 @@
+// -----------------------------------------------------------------------
+// <copyright file="RelativeRect.cs" company="Steven Kirk">
+// Copyright 2015 MIT Licence. See licence.md for more information.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Perspex
+{
+    /// <summary>
+    /// Defines a rectangle that may be defined relative to another rectangle.
+    /// </summary>
+    public struct RelativeRect
+    {
+        /// <summary>
+        /// A rectangle that represents 100% of an area.
+        /// </summary>
+        public static readonly RelativeRect Fill = new RelativeRect(0, 0, 1, 1, OriginUnit.Percent);
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RelativeRect"/> structure.
+        /// </summary>
+        /// <param name="x">The X position.</param>
+        /// <param name="y">The Y position.</param>
+        /// <param name="width">The width.</param>
+        /// <param name="height">The height.</param>
+        /// <param name="unit">The unit of the rect.</param>
+        public RelativeRect(double x, double y, double width, double height, OriginUnit unit)
+        {
+            this.Rect = new Rect(x, y, width, height);
+            this.Unit = unit;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RelativeRect"/> structure.
+        /// </summary>
+        /// <param name="rect">The rectangle.</param>
+        /// <param name="unit">The unit of the rect.</param>
+        public RelativeRect(Rect rect, OriginUnit unit)
+        {
+            this.Rect = rect;
+            this.Unit = unit;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RelativeRect"/> structure.
+        /// </summary>
+        /// <param name="size">The size of the rectangle.</param>
+        /// <param name="unit">The unit of the rect.</param>
+        public RelativeRect(Size size, OriginUnit unit)
+        {
+            this.Rect = new Rect(size);
+            this.Unit = unit;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RelativeRect"/> structure.
+        /// </summary>
+        /// <param name="position">The position of the rectangle.</param>
+        /// <param name="size">The size of the rectangle.</param>
+        /// <param name="unit">The unit of the rect.</param>
+        public RelativeRect(Point position, Size size, OriginUnit unit)
+        {
+            this.Rect = new Rect(position, size);
+            this.Unit = unit;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RelativeRect"/> structure.
+        /// </summary>
+        /// <param name="topLeft">The top left position of the rectangle.</param>
+        /// <param name="bottomRight">The bottom right position of the rectangle.</param>
+        /// <param name="unit">The unit of the rect.</param>
+        public RelativeRect(Point topLeft, Point bottomRight, OriginUnit unit)
+        {
+            this.Rect = new Rect(topLeft, bottomRight);
+            this.Unit = unit;
+        }
+
+        /// <summary>
+        /// Gets the unit of the rectangle.
+        /// </summary>
+        public OriginUnit Unit { get; }
+
+        /// <summary>
+        /// Gets the rectangle.
+        /// </summary>
+        public Rect Rect { get; }
+
+        /// <summary>
+        /// Converts a <see cref="RelativeRect"/> into pixels.
+        /// </summary>
+        /// <param name="size">The size of the visual.</param>
+        /// <returns>The origin point in pixels.</returns>
+        public Rect ToPixels(Size size)
+        {
+            return this.Unit == OriginUnit.Pixels ?
+                this.Rect :
+                new Rect(
+                    this.Rect.X * size.Width,
+                    this.Rect.Y * size.Height,
+                    this.Rect.Width * size.Width,
+                    this.Rect.Height * size.Height);
+        }
+    }
+}

+ 14 - 1
src/Perspex.SceneGraph/Rendering/RendererBase.cs

@@ -33,10 +33,23 @@ namespace Perspex.Rendering
         /// <param name="visual">The visual to render.</param>
         /// <param name="handle">An optional platform-specific handle.</param>
         public virtual void Render(IVisual visual, IPlatformHandle handle)
+        {
+            this.Render(visual, handle, Matrix.Identity);
+        }
+
+        /// <summary>
+        /// Renders the specified visual with the specified transform.
+        /// </summary>
+        /// <param name="visual">The visual to render.</param>
+        /// <param name="handle">An optional platform-specific handle.</param>
+        /// <param name="transform">The transform.</param>
+        /// <param name="clip">An optional clip rectangle.</param>
+        public virtual void Render(IVisual visual, IPlatformHandle handle, Matrix transform, Rect? clip = null)
         {
             using (var context = this.CreateDrawingContext(handle))
+            using (clip.HasValue ? context.PushClip(clip.Value) : null)
             {
-                this.Render(visual, context, Matrix.Identity, Matrix.Identity);
+                this.Render(visual, context, Matrix.Identity, transform);
             }
 
             ++this.RenderCount;

+ 39 - 0
src/Windows/Perspex.Direct2D1/Disposable.cs

@@ -0,0 +1,39 @@
+// -----------------------------------------------------------------------
+// <copyright file="Disposable.cs" company="Steven Kirk">
+// Copyright 2013 MIT Licence. See licence.md for more information.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Perspex.Direct2D1
+{
+    using System;
+
+    public class Disposable<T> : IDisposable where T : IDisposable
+    {
+        private IDisposable extra;
+
+        public Disposable(T inner)
+        {
+            this.Inner = inner;
+        }
+
+        public Disposable(T inner, IDisposable extra)
+        {
+            this.Inner = inner;
+            this.extra = extra;
+        }
+
+        public T Inner { get; }
+
+        public static implicit operator T(Disposable<T> i)
+        {
+            return i.Inner;
+        }
+
+        public void Dispose()
+        {
+            this.Inner.Dispose();
+            this.extra?.Dispose();
+        }
+    }
+}

+ 48 - 19
src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs

@@ -143,11 +143,22 @@ namespace Perspex.Direct2D1.Media
             using (var brush = this.CreateBrush(pen.Brush, rect.Size))
             using (var d2dStroke = pen.ToDirect2DStrokeStyle(this.renderTarget))
             {
-                this.renderTarget.DrawRoundedRectangle(
-                    new RoundedRectangle { Rect = rect.ToDirect2D(), RadiusX = cornerRadius, RadiusY = cornerRadius },
-                    brush.PlatformBrush,
-                    (float)pen.Thickness,
-                    d2dStroke);
+                if (cornerRadius == 0)
+                {
+                    this.renderTarget.DrawRectangle(
+                        rect.ToDirect2D(),
+                        brush.PlatformBrush,
+                        (float)pen.Thickness,
+                        d2dStroke);
+                }
+                else
+                {
+                    this.renderTarget.DrawRoundedRectangle(
+                        new RoundedRectangle { Rect = rect.ToDirect2D(), RadiusX = cornerRadius, RadiusY = cornerRadius },
+                        brush.PlatformBrush,
+                        (float)pen.Thickness,
+                        d2dStroke);
+                }
             }
         }
 
@@ -181,18 +192,25 @@ namespace Perspex.Direct2D1.Media
         {
             using (var b = this.CreateBrush(brush, rect.Size))
             {
-                this.renderTarget.FillRoundedRectangle(
-                    new RoundedRectangle
-                    {
-                        Rect = new RectangleF(
-                                (float)rect.X,
-                                (float)rect.Y,
-                                (float)rect.Width,
-                                (float)rect.Height),
-                        RadiusX = cornerRadius,
-                        RadiusY = cornerRadius
-                    },
-                    b.PlatformBrush);
+                if (cornerRadius == 0)
+                {
+                    this.renderTarget.FillRectangle(rect.ToDirect2D(), b.PlatformBrush);
+                }
+                else
+                {
+                    this.renderTarget.FillRoundedRectangle(
+                        new RoundedRectangle
+                        {
+                            Rect = new RectangleF(
+                                    (float)rect.X,
+                                    (float)rect.Y,
+                                    (float)rect.Width,
+                                    (float)rect.Height),
+                            RadiusX = cornerRadius,
+                            RadiusY = cornerRadius
+                        },
+                        b.PlatformBrush);
+                }
             }
         }
 
@@ -260,10 +278,17 @@ namespace Perspex.Direct2D1.Media
             });
         }
 
+        /// <summary>
+        /// Creates a Direct2D brush wrapper for a Perspex brush.
+        /// </summary>
+        /// <param name="brush">The perspex brush.</param>
+        /// <param name="destinationSize">The size of the brush's target area.</param>
+        /// <returns>The Direct2D brush wrapper.</returns>
         public BrushImpl CreateBrush(Perspex.Media.Brush brush, Size destinationSize)
         {
-            Perspex.Media.SolidColorBrush solidColorBrush = brush as Perspex.Media.SolidColorBrush;
-            Perspex.Media.LinearGradientBrush linearGradientBrush = brush as Perspex.Media.LinearGradientBrush;
+            var solidColorBrush = brush as Perspex.Media.SolidColorBrush;
+            var linearGradientBrush = brush as Perspex.Media.LinearGradientBrush;
+            var visualBrush = brush as Perspex.Media.VisualBrush;
 
             if (solidColorBrush != null)
             {
@@ -273,6 +298,10 @@ namespace Perspex.Direct2D1.Media
             {
                 return new LinearGradientBrushImpl(linearGradientBrush, this.renderTarget, destinationSize);
             }
+            else if (visualBrush != null)
+            {
+                return new VisualBrushImpl(visualBrush, this.renderTarget, destinationSize);
+            }
             else
             {
                 return new SolidColorBrushImpl(null, this.renderTarget);

+ 1 - 1
src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs

@@ -99,4 +99,4 @@ namespace Perspex.Direct2D1.Media
             return false;
         }
     }
-}
+}

+ 112 - 0
src/Windows/Perspex.Direct2D1/Media/VisualBrushImpl.cs

@@ -0,0 +1,112 @@
+// -----------------------------------------------------------------------
+// <copyright file="VisualBrushImpl.cs" company="Steven Kirk">
+// Copyright 2015 MIT Licence. See licence.md for more information.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Perspex.Direct2D1.Media
+{
+    using System;
+    using Perspex.Layout;
+    using Perspex.Media;
+    using SharpDX.Direct2D1;
+
+    public class VisualBrushImpl : BrushImpl
+    {
+        public VisualBrushImpl(
+            Perspex.Media.VisualBrush brush,
+            SharpDX.Direct2D1.RenderTarget target,
+            Size targetSize)
+        {
+            var visual = brush.Visual;
+            var layoutable = visual as ILayoutable;
+
+            if (layoutable?.IsArrangeValid == false)
+            {
+                layoutable.Measure(Size.Infinity);
+                layoutable.Arrange(new Rect(layoutable.DesiredSize));
+            }
+
+            var sourceRect = brush.SourceRect.ToPixels(layoutable.Bounds.Size);
+            var destinationRect = brush.DestinationRect.ToPixels(targetSize);
+            var bitmapSize = brush.TileMode == TileMode.None ? targetSize : destinationRect.Size;
+            var scale = brush.Stretch.CalculateScaling(destinationRect.Size, sourceRect.Size);
+            var translate = CalculateTranslate(brush, sourceRect, destinationRect, scale);
+            var options = CompatibleRenderTargetOptions.None;
+
+            using (var brt = new BitmapRenderTarget(target, options, bitmapSize.ToSharpDX()))
+            {
+                var renderer = new Renderer(brt);
+                var transform = Matrix.CreateTranslation(-sourceRect.Position) *
+                                Matrix.CreateScale(scale) *
+                                Matrix.CreateTranslation(translate);
+
+                Rect drawRect;
+
+                if (brush.TileMode == TileMode.None)
+                {
+                    drawRect = destinationRect;
+                    transform *= Matrix.CreateTranslation(destinationRect.Position);
+                }
+                else
+                {
+                    drawRect = new Rect(0, 0, destinationRect.Width, destinationRect.Height);
+                }
+
+                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;
+
+                if (brush.TileMode != TileMode.None)
+                {
+                    result.Transform = SharpDX.Matrix3x2.Translation(
+                        (float)destinationRect.X,
+                        (float)destinationRect.Y);
+                }
+
+                this.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)this.PlatformBrush).Bitmap.Dispose();
+            base.Dispose();
+        }
+    }
+}

+ 6 - 0
src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj

@@ -74,6 +74,7 @@
       <Link>Properties\SharedAssemblyInfo.cs</Link>
     </Compile>
     <Compile Include="Direct2D1Platform.cs" />
+    <Compile Include="Disposable.cs" />
     <Compile Include="GlobalSuppressions.cs" />
     <Compile Include="Media\BrushImpl.cs" />
     <Compile Include="Media\BrushWrapper.cs" />
@@ -82,6 +83,7 @@
     <Compile Include="Media\Imaging\BitmapImpl.cs" />
     <Compile Include="Media\LinearGradientBrushImpl.cs" />
     <Compile Include="Media\PerspexTextRenderer.cs" />
+    <Compile Include="Media\VisualBrushImpl.cs" />
     <Compile Include="Media\SolidColorBrushImpl.cs" />
     <Compile Include="Media\StreamGeometryContextImpl.cs" />
     <Compile Include="Media\GeometryImpl.cs" />
@@ -104,6 +106,10 @@
       <Project>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</Project>
       <Name>Perspex.Base</Name>
     </ProjectReference>
+    <ProjectReference Include="..\..\Perspex.Layout\Perspex.Layout.csproj">
+      <Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
+      <Name>Perspex.Layout</Name>
+    </ProjectReference>
     <ProjectReference Include="..\..\Perspex.SceneGraph\Perspex.SceneGraph.csproj">
       <Project>{EB582467-6ABB-43A1-B052-E981BA910E3A}</Project>
       <Name>Perspex.SceneGraph</Name>

+ 0 - 21
src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs

@@ -43,27 +43,6 @@ namespace Perspex.Direct2D1
             else
                 return ExtendMode.Wrap;
         }
-        /// <summary>
-        /// Converts a brush to Direct2D.
-        /// </summary>
-        /// <param name="brush">The brush to convert.</param>
-        /// <param name="target">The render target.</param>
-        /// <returns>The Direct2D brush.</returns>
-        public static SharpDX.Direct2D1.Brush ToDirect2D(this Perspex.Media.Brush brush, RenderTarget target)
-        {
-            Perspex.Media.SolidColorBrush solidColorBrush = brush as Perspex.Media.SolidColorBrush;
-
-            if (solidColorBrush != null)
-            {
-                return new SharpDX.Direct2D1.SolidColorBrush(target, solidColorBrush.Color.ToDirect2D());
-            }
-            else
-            {
-                // TODO: Implement other brushes.
-                return new SharpDX.Direct2D1.SolidColorBrush(target, new Color4());
-            }
-        }
-
         /// <summary>
         /// Converts a pen to a Direct2D stroke style.
         /// </summary>

+ 596 - 0
tests/Perspex.RenderTests/Media/VisualBrushTests.cs

@@ -0,0 +1,596 @@
+// -----------------------------------------------------------------------
+// <copyright file="VisualBrushTests.cs" company="Steven Kirk">
+// Copyright 2014 MIT Licence. See licence.md for more information.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Perspex.Direct2D1.RenderTests.Media
+{
+    using Perspex.Controls;
+    using Perspex.Controls.Shapes;
+    using Perspex.Layout;
+    using Perspex.Media;
+    using Xunit;
+
+    public class VisualBrushTests : TestBase
+    {
+        public VisualBrushTests()
+            : base(@"Media\VisualBrush")
+        {
+        }
+
+        [Fact]
+        public void VisualBrush_Align_TopLeft()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Rectangle
+                {
+                    Fill = new VisualBrush
+                    {
+                        AlignmentX = AlignmentX.Left,
+                        AlignmentY = AlignmentY.Top,
+                        Stretch = Stretch.None,
+                        Visual = new Border
+                        {
+                            Width = 92,
+                            Height = 92,
+                            Background = Brushes.Red,
+                            BorderBrush = Brushes.Black,
+                            BorderThickness = 2,
+                            Child = new TextBlock
+                            {
+                                Text = "Perspex",
+                                FontSize = 12,
+                                FontFamily = "Arial",
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center,
+                            }
+                        }
+                    }
+                }
+            };
+
+            this.RenderToFile(target);
+            this.CompareImages();
+        }
+
+        [Fact]
+        public void VisualBrush_Align_Center()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Rectangle
+                {
+                    Fill = new VisualBrush
+                    {
+                        AlignmentX = AlignmentX.Center,
+                        AlignmentY = AlignmentY.Center,
+                        Stretch = Stretch.None,
+                        Visual = new Border
+                        {
+                            Width = 92,
+                            Height = 92,
+                            Background = Brushes.Red,
+                            BorderBrush = Brushes.Black,
+                            BorderThickness = 2,
+                            Child = new TextBlock
+                            {
+                                Text = "Perspex",
+                                FontSize = 12,
+                                FontFamily = "Arial",
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center,
+                            }
+                        }
+                    }
+                }
+            };
+
+            this.RenderToFile(target);
+            this.CompareImages();
+        }
+
+        [Fact]
+        public void VisualBrush_Align_BottomRight()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Rectangle
+                {
+                    Fill = new VisualBrush
+                    {
+                        AlignmentX = AlignmentX.Right,
+                        AlignmentY = AlignmentY.Bottom,
+                        Stretch = Stretch.None,
+                        Visual = new Border
+                        {
+                            Width = 92,
+                            Height = 92,
+                            Background = Brushes.Red,
+                            BorderBrush = Brushes.Black,
+                            BorderThickness = 2,
+                            Child = new TextBlock
+                            {
+                                Text = "Perspex",
+                                FontSize = 12,
+                                FontFamily = "Arial",
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center,
+                            }
+                        }
+                    }
+                }
+            };
+
+            this.RenderToFile(target);
+            this.CompareImages();
+        }
+
+        [Fact]
+        public void VisualBrush_Stretch_Fill_Large()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 920,
+                Height = 920,
+                Child = new Rectangle
+                {
+                    Fill = new VisualBrush
+                    {
+                        Stretch = Stretch.Fill,
+                        Visual = new Border
+                        {
+                            Width = 92,
+                            Height = 92,
+                            Background = Brushes.Red,
+                            BorderBrush = Brushes.Black,
+                            BorderThickness = 2,
+                            Child = new TextBlock
+                            {
+                                Text = "Perspex",
+                                FontSize = 12,
+                                FontFamily = "Arial",
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center,
+                            }
+                        }
+                    }
+                }
+            };
+
+            this.RenderToFile(target);
+            this.CompareImages();
+        }
+
+        [Fact]
+        public void VisualBrush_Stretch_Uniform()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 920,
+                Height = 820,
+                Child = new Rectangle
+                {
+                    Fill = new VisualBrush
+                    {
+                        Stretch = Stretch.Uniform,
+                        Visual = new Border
+                        {
+                            Width = 92,
+                            Height = 92,
+                            Background = Brushes.Red,
+                            BorderBrush = Brushes.Black,
+                            BorderThickness = 2,
+                            Child = new TextBlock
+                            {
+                                Text = "Perspex",
+                                FontSize = 12,
+                                FontFamily = "Arial",
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center,
+                            }
+                        }
+                    }
+                }
+            };
+
+            this.RenderToFile(target);
+            this.CompareImages();
+        }
+
+        [Fact]
+        public void VisualBrush_Stretch_UniformToFill()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 920,
+                Height = 820,
+                Child = new Rectangle
+                {
+                    Fill = new VisualBrush
+                    {
+                        Stretch = Stretch.UniformToFill,
+                        Visual = new Border
+                        {
+                            Width = 92,
+                            Height = 92,
+                            Background = Brushes.Red,
+                            BorderBrush = Brushes.Black,
+                            BorderThickness = 2,
+                            Child = new TextBlock
+                            {
+                                Text = "Perspex",
+                                FontSize = 12,
+                                FontFamily = "Arial",
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center,
+                            }
+                        }
+                    }
+                }
+            };
+
+            this.RenderToFile(target);
+            this.CompareImages();
+        }
+
+        [Fact]
+        public void VisualBrush_SourceRect_Absolute()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Rectangle
+                {
+                    Fill = new VisualBrush
+                    {
+                        SourceRect = new RelativeRect(40, 40, 100, 100, OriginUnit.Pixels),
+                        Visual = new Border
+                        {
+                            Width = 180,
+                            Height = 180,
+                            Background = Brushes.Red,
+                            BorderBrush = Brushes.Black,
+                            BorderThickness = 2,
+                            Child = new Ellipse
+                            {
+                                Width = 100,
+                                Height = 100,
+                                Fill = Brushes.Yellow,
+                                VerticalAlignment = VerticalAlignment.Center,
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                            }
+                        }
+                    }
+                }
+            };
+
+            this.RenderToFile(target);
+            this.CompareImages();
+        }
+
+        [Fact]
+        public void VisualBrush_DestinationRect_Absolute()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Rectangle
+                {
+                    Fill = new VisualBrush
+                    {
+                        DestinationRect = new RelativeRect(92, 92, 92, 92, OriginUnit.Pixels),
+                        Visual = new Border
+                        {
+                            Width = 180,
+                            Height = 180,
+                            Background = Brushes.Red,
+                            BorderBrush = Brushes.Black,
+                            BorderThickness = 2,
+                            Child = new Ellipse
+                            {
+                                Width = 100,
+                                Height = 100,
+                                Fill = Brushes.Yellow,
+                                VerticalAlignment = VerticalAlignment.Center,
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                            }
+                        }
+                    }
+                }
+            };
+
+            this.RenderToFile(target);
+            this.CompareImages();
+        }
+
+        [Fact]
+        public void VisualBrush_SourceRect_DestinationRect_Absolute()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Rectangle
+                {
+                    Fill = new VisualBrush
+                    {
+                        SourceRect = new RelativeRect(40, 40, 100, 100, OriginUnit.Pixels),
+                        DestinationRect = new RelativeRect(92, 92, 92, 92, OriginUnit.Pixels),
+                        Visual = new Border
+                        {
+                            Width = 180,
+                            Height = 180,
+                            Background = Brushes.Red,
+                            BorderBrush = Brushes.Black,
+                            BorderThickness = 2,
+                            Child = new Ellipse
+                            {
+                                Width = 100,
+                                Height = 100,
+                                Fill = Brushes.Yellow,
+                                VerticalAlignment = VerticalAlignment.Center,
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                            }
+                        }
+                    }
+                }
+            };
+
+            this.RenderToFile(target);
+            this.CompareImages();
+        }
+
+        [Fact]
+        public void VisualBrush_SourceRect_DestinationRect_Percent()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Rectangle
+                {
+                    Fill = new VisualBrush
+                    {
+                        SourceRect = new RelativeRect(0.22, 0.22, 0.56, 0.56, OriginUnit.Percent),
+                        DestinationRect = new RelativeRect(0.5, 0.5, 0.5, 0.5, OriginUnit.Percent),
+                        Visual = new Border
+                        {
+                            Width = 180,
+                            Height = 180,
+                            Background = Brushes.Red,
+                            BorderBrush = Brushes.Black,
+                            BorderThickness = 2,
+                            Child = new Ellipse
+                            {
+                                Width = 100,
+                                Height = 100,
+                                Fill = Brushes.Yellow,
+                                VerticalAlignment = VerticalAlignment.Center,
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                            }
+                        }
+                    }
+                }
+            };
+
+            this.RenderToFile(target);
+            this.CompareImages();
+        }
+
+        [Fact]
+        public void VisualBrush_Tile()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Rectangle
+                {
+                    Fill = new VisualBrush
+                    {
+                        Stretch = Stretch.None,
+                        TileMode = TileMode.Tile,
+                        DestinationRect = new RelativeRect(0.25, 0.25, 0.5, 0.5, OriginUnit.Percent),
+                        Visual = new Border
+                        {
+                            Width = 92,
+                            Height = 92,
+                            Background = Brushes.Red,
+                            BorderBrush = Brushes.Black,
+                            BorderThickness = 2,
+                            Child = new TextBlock
+                            {
+                                Text = "Perspex",
+                                FontSize = 12,
+                                FontFamily = "Arial",
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center,
+                            }
+                        }
+                    }
+                }
+            };
+
+            this.RenderToFile(target);
+            this.CompareImages();
+        }
+
+        [Fact]
+        public void VisualBrush_Tile_Alignment_BottomRight()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Rectangle
+                {
+                    Fill = new VisualBrush
+                    {
+                        Stretch = Stretch.None,
+                        TileMode = TileMode.Tile,
+                        AlignmentX = AlignmentX.Right,
+                        AlignmentY = AlignmentY.Bottom,
+                        Visual = new Border
+                        {
+                            Width = 92,
+                            Height = 92,
+                            Background = Brushes.Red,
+                            BorderBrush = Brushes.Black,
+                            BorderThickness = 2,
+                            Child = new TextBlock
+                            {
+                                Text = "Perspex",
+                                FontSize = 12,
+                                FontFamily = "Arial",
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center,
+                            }
+                        }
+                    }
+                }
+            };
+
+            this.RenderToFile(target);
+            this.CompareImages();
+        }
+
+        [Fact]
+        public void VisualBrush_FlipX()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Rectangle
+                {
+                    Fill = new VisualBrush
+                    {
+                        Stretch = Stretch.None,
+                        TileMode = TileMode.FlipX,
+                        DestinationRect = new RelativeRect(0, 0, 0.5, 0.5, OriginUnit.Percent),
+                        Visual = new Border
+                        {
+                            Width = 92,
+                            Height = 92,
+                            Background = Brushes.Red,
+                            BorderBrush = Brushes.Black,
+                            BorderThickness = 2,
+                            Child = new TextBlock
+                            {
+                                Text = "Perspex",
+                                FontSize = 12,
+                                FontFamily = "Arial",
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center,
+                            }
+                        }
+                    }
+                }
+            };
+
+            this.RenderToFile(target);
+            this.CompareImages();
+        }
+
+        [Fact]
+        public void VisualBrush_FlipY()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Rectangle
+                {
+                    Fill = new VisualBrush
+                    {
+                        Stretch = Stretch.None,
+                        TileMode = TileMode.FlipY,
+                        DestinationRect = new RelativeRect(0, 0, 0.5, 0.5, OriginUnit.Percent),
+                        Visual = new Border
+                        {
+                            Width = 92,
+                            Height = 92,
+                            Background = Brushes.Red,
+                            BorderBrush = Brushes.Black,
+                            BorderThickness = 2,
+                            Child = new TextBlock
+                            {
+                                Text = "Perspex",
+                                FontSize = 12,
+                                FontFamily = "Arial",
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center,
+                            }
+                        }
+                    }
+                }
+            };
+
+            this.RenderToFile(target);
+            this.CompareImages();
+        }
+
+        [Fact]
+        public void VisualBrush_FlipXY()
+        {
+            Decorator target = new Decorator
+            {
+                Padding = new Thickness(8),
+                Width = 200,
+                Height = 200,
+                Child = new Rectangle
+                {
+                    Fill = new VisualBrush
+                    {
+                        Stretch = Stretch.None,
+                        TileMode = TileMode.FlipXY,
+                        DestinationRect = new RelativeRect(0, 0, 0.5, 0.5, OriginUnit.Percent),
+                        Visual = new Border
+                        {
+                            Width = 92,
+                            Height = 92,
+                            Background = Brushes.Red,
+                            BorderBrush = Brushes.Black,
+                            BorderThickness = 2,
+                            Child = new TextBlock
+                            {
+                                Text = "Perspex",
+                                FontSize = 12,
+                                FontFamily = "Arial",
+                                HorizontalAlignment = HorizontalAlignment.Center,
+                                VerticalAlignment = VerticalAlignment.Center,
+                            }
+                        }
+                    }
+                }
+            };
+
+            this.RenderToFile(target);
+            this.CompareImages();
+        }
+    }
+}

+ 1 - 0
tests/Perspex.RenderTests/Perspex.Direct2D1.RenderTests.csproj

@@ -77,6 +77,7 @@
     <Otherwise />
   </Choose>
   <ItemGroup>
+    <Compile Include="Media\VisualBrushTests.cs" />
     <Compile Include="Controls\ImageTests.cs" />
     <Compile Include="Controls\BorderTests.cs" />
     <Compile Include="GlobalSuppressions.cs" />

BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Align_BottomRight.expected.png


BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Align_Center.expected.png


BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Align_TopLeft.expected.png


BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_DestinationRect_Absolute.expected.png


BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_FlipX.expected.png


BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_FlipXY.expected.png


BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_FlipY.expected.png


BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_SourceRect_Absolute.expected.png


BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_SourceRect_DestinationRect_Absolute.expected.png


BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_SourceRect_DestinationRect_Percent.expected.png


BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Stretch_Fill_Large.expected.png


BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Stretch_Uniform.expected.png


BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Stretch_UniformToFill.expected.png


BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Tile.expected.png


BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Tile_Alignment_BottomRight.expected.png