diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index 43c8fbcbb4..f6f11aa9ad 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -63,10 +63,10 @@ namespace Avalonia.Controls Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize); Size scaledSize = sourceSize * scale; Rect destRect = viewPort - .CenterIn(new Rect(scaledSize)) + .CenterRect(new Rect(scaledSize)) .Intersect(viewPort); Rect sourceRect = new Rect(sourceSize) - .CenterIn(new Rect(destRect.Size / scale)); + .CenterRect(new Rect(destRect.Size / scale)); context.DrawImage(source, 1, sourceRect, destRect); } diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj index 11860f3ea3..35aaada8b8 100644 --- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj +++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj @@ -135,6 +135,7 @@ + diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index dec1c79df8..0132c5e8a3 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -238,7 +238,7 @@ namespace Avalonia /// /// The rectangle to center. /// The centered rectangle. - public Rect CenterIn(Rect rect) + public Rect CenterRect(Rect rect) { return new Rect( _x + ((_width - rect._width) / 2), diff --git a/src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs b/src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs new file mode 100644 index 0000000000..5a8adc5c45 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs @@ -0,0 +1,193 @@ +// 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 Avalonia.Media; + +namespace Avalonia.Rendering.Utilities +{ + public class TileBrushCalculator + { + private readonly Size _imageSize; + private readonly Rect _drawRect; + + public bool IsValid { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The brush to be rendered. + /// The size of the content of the tile brush. + /// The size of the control to which the brush is being rendered. + public TileBrushCalculator(TileBrush brush, Size contentSize, Size targetSize) + : this( + brush.TileMode, + brush.Stretch, + brush.AlignmentX, + brush.AlignmentY, + brush.SourceRect, + brush.DestinationRect, + contentSize, + targetSize) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The brush's tile mode. + /// The brush's stretch. + /// The brush's horizontal alignment. + /// The brush's vertical alignment. + /// The brush's source rect + /// The brush's destination rect. + /// The size of the content of the tile brush. + /// The size of the control to which the brush is being rendered. + public TileBrushCalculator( + TileMode tileMode, + Stretch stretch, + AlignmentX alignmentX, + AlignmentY alignmentY, + RelativeRect sourceRect, + RelativeRect destinationRect, + Size contentSize, + Size targetSize) + { + _imageSize = contentSize; + + SourceRect = sourceRect.ToPixels(_imageSize); + DestinationRect = destinationRect.ToPixels(targetSize); + + var scale = stretch.CalculateScaling(DestinationRect.Size, SourceRect.Size); + var translate = CalculateTranslate(alignmentX, alignmentY, SourceRect, DestinationRect, scale); + + IntermediateSize = tileMode == TileMode.None ? targetSize : DestinationRect.Size; + IntermediateTransform = CalculateIntermediateTransform( + tileMode, + SourceRect, + DestinationRect, + scale, + translate, + out _drawRect); + } + + /// + /// Gets the rectangle on the destination control to which content should be rendered. + /// + /// + /// If of the brush is repeating then this is describes rectangle + /// of a single repeat of the tiled content. + /// + public Rect DestinationRect { get; } + + /// + /// Gets the clip rectangle on the intermediate image with which the brush content should be + /// drawn when is true. + /// + public Rect IntermediateClip => _drawRect; + + /// + /// Gets the size of the intermediate image that should be created when + /// is true. + /// + public Size IntermediateSize { get; } + + /// + /// Gets the transform to be used when rendering to the intermediate image when + /// is true. + /// + public Matrix IntermediateTransform { get; } + + /// + /// Gets a value indicating whether an intermediate image should be created in order to + /// render the tile brush. + /// + /// + /// Intermediate images are required when a brush's is not repeating + /// but the source and destination aspect ratios are unequal, as all of the currently + /// supported rendering backends do not support non-tiled image brushes. + /// + public bool NeedsIntermediate + { + get + { + if (IntermediateTransform != Matrix.Identity) + return true; + if (SourceRect.Position != default(Point)) + return true; + if (SourceRect.Size.AspectRatio == _imageSize.AspectRatio) + return false; + if ((int)SourceRect.Width != _imageSize.Width || + (int)SourceRect.Height != _imageSize.Height) + return true; + return false; + } + } + + /// + /// Gets the area of the source content to be rendered. + /// + public Rect SourceRect { get; } + + public static Vector CalculateTranslate( + AlignmentX alignmentX, + AlignmentY alignmentY, + Rect sourceRect, + Rect destinationRect, + Vector scale) + { + var x = 0.0; + var y = 0.0; + var size = sourceRect.Size * scale; + + switch (alignmentX) + { + case AlignmentX.Center: + x += (destinationRect.Width - size.Width) / 2; + break; + case AlignmentX.Right: + x += destinationRect.Width - size.Width; + break; + } + + switch (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 static Matrix CalculateIntermediateTransform( + TileMode tileMode, + Rect sourceRect, + Rect destinationRect, + Vector scale, + Vector translate, + out Rect 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; + + return transform; + } + } +} diff --git a/src/Avalonia.Visuals/Size.cs b/src/Avalonia.Visuals/Size.cs index a11411c3ee..6ad87c6120 100644 --- a/src/Avalonia.Visuals/Size.cs +++ b/src/Avalonia.Visuals/Size.cs @@ -43,6 +43,11 @@ namespace Avalonia _height = height; } + /// + /// Gets the aspect ratio of the size. + /// + public double AspectRatio => _width / _height; + /// /// Gets the width. /// @@ -97,6 +102,17 @@ namespace Avalonia return new Size(size._width / scale.X, size._height / scale.Y); } + /// + /// Divides a size by another size to produce a scaling factor. + /// + /// The first size + /// The second size. + /// The scaled size. + public static Vector operator /(Size left, Size right) + { + return new Vector(left._width / right._width, left._height / right._height); + } + /// /// Scales a size. /// diff --git a/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs b/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs index 5cb2c4d494..afe9969f0e 100644 --- a/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs +++ b/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs @@ -317,10 +317,6 @@ namespace Avalonia.Cairo.Media { impl = new ImageBrushImpl(imageBrush, destinationSize); } - else if (visualBrush != null) - { - impl = new VisualBrushImpl(visualBrush, destinationSize); - } else { impl = new SolidColorBrushImpl(null, opacityOverride); diff --git a/src/Gtk/Avalonia.Cairo/Media/TileBrushes.cs b/src/Gtk/Avalonia.Cairo/Media/TileBrushes.cs index e5aa022913..ee80b9fdf3 100644 --- a/src/Gtk/Avalonia.Cairo/Media/TileBrushes.cs +++ b/src/Gtk/Avalonia.Cairo/Media/TileBrushes.cs @@ -16,40 +16,40 @@ namespace Avalonia.Cairo.Media { public static SurfacePattern CreateTileBrush(TileBrush brush, Size targetSize) { - var helper = new TileBrushImplHelper(brush, targetSize); - if (!helper.IsValid) - return null; + throw new NotImplementedException(); + //// var helper = new TileBrushImplHelper(brush, targetSize); + //// if (!helper.IsValid) + //// return null; - using (var intermediate = new ImageSurface(Format.ARGB32, (int)helper.IntermediateSize.Width, (int)helper.IntermediateSize.Height)) - using (var ctx = new RenderTarget(intermediate).CreateDrawingContext()) - { - helper.DrawIntermediate(new Avalonia.Media.DrawingContext(ctx)); + ////using (var intermediate = new ImageSurface(Format.ARGB32, (int)helper.IntermediateSize.Width, (int)helper.IntermediateSize.Height)) + //// using (var ctx = new RenderTarget(intermediate).CreateDrawingContext()) + //// { + //// helper.DrawIntermediate(new Avalonia.Media.DrawingContext(ctx)); - var result = new SurfacePattern(intermediate); + //// var result = new SurfacePattern(intermediate); - if ((brush.TileMode & TileMode.FlipXY) != 0) - { - // TODO: Currently always FlipXY as that's all cairo supports natively. - // Support separate FlipX and FlipY by drawing flipped images to intermediate - // surface. - result.Extend = Extend.Reflect; - } - else - { - result.Extend = Extend.Repeat; - } + //// if ((brush.TileMode & TileMode.FlipXY) != 0) + //// { + //// // TODO: Currently always FlipXY as that's all cairo supports natively. + //// // Support separate FlipX and FlipY by drawing flipped images to intermediate + //// // surface. + //// result.Extend = Extend.Reflect; + //// } + //// else + //// { + //// result.Extend = Extend.Repeat; + //// } - if (brush.TileMode != TileMode.None) - { - var matrix = result.Matrix; - matrix.InitTranslate(-helper.DestinationRect.X, -helper.DestinationRect.Y); - result.Matrix = matrix; - } + //// if (brush.TileMode != TileMode.None) + //// { + //// var matrix = result.Matrix; + //// matrix.InitTranslate(-helper.DestinationRect.X, -helper.DestinationRect.Y); + //// result.Matrix = matrix; + //// } - return result; - } + //// return result; + //// } } - - + } } diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj index 877c4290a4..7f286f4f88 100644 --- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj +++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj @@ -88,7 +88,7 @@ - + diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index fa937ccb2f..2c841bedb5 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -332,11 +332,11 @@ namespace Avalonia.Direct2D1.Media } else if (imageBrush != null) { - return new TileBrushImpl(imageBrush, _renderTarget, destinationSize); - } - else if (visualBrush != null) - { - return new TileBrushImpl(visualBrush, _renderTarget, destinationSize); + return new ImageBrushImpl( + imageBrush, + _renderTarget, + (BitmapImpl)imageBrush.Source.PlatformImpl, + destinationSize); } else { diff --git a/src/Windows/Avalonia.Direct2D1/Media/TileBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs similarity index 51% rename from src/Windows/Avalonia.Direct2D1/Media/TileBrushImpl.cs rename to src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs index e567018264..90fe5675ce 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/TileBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs @@ -3,50 +3,46 @@ using System; using Avalonia.Media; -using Avalonia.RenderHelpers; +using Avalonia.Rendering.Utilities; using SharpDX.Direct2D1; namespace Avalonia.Direct2D1.Media { - public sealed class TileBrushImpl : BrushImpl + public sealed class ImageBrushImpl : BrushImpl { - public TileBrushImpl( + public ImageBrushImpl( TileBrush brush, SharpDX.Direct2D1.RenderTarget target, + BitmapImpl bitmap, Size targetSize) { - var helper = new TileBrushImplHelper(brush, targetSize); - if (!helper.IsValid) - return; + var calc = new TileBrushCalculator(brush, new Size(bitmap.PixelWidth, bitmap.PixelHeight), targetSize); - using (var intermediate = new BitmapRenderTarget(target, CompatibleRenderTargetOptions.None, helper.IntermediateSize.ToSharpDX())) + if (!calc.NeedsIntermediate) { - using (var ctx = new RenderTarget(intermediate).CreateDrawingContext()) - { - intermediate.Clear(null); - helper.DrawIntermediate(new DrawingContext(ctx)); - } - PlatformBrush = new BitmapBrush( target, - intermediate.Bitmap, + bitmap.GetDirect2DBitmap(target), GetBitmapBrushProperties(brush), - GetBrushProperties(brush, helper.DestinationRect)); + GetBrushProperties(brush, calc.DestinationRect)); + } + else + { + using (var intermediate = RenderIntermediate(target, bitmap, calc)) + { + PlatformBrush = new BitmapBrush( + target, + intermediate.Bitmap, + GetBitmapBrushProperties(brush), + GetBrushProperties(brush, calc.DestinationRect)); + } } } - private static BrushProperties GetBrushProperties(TileBrush brush, Rect destinationRect) + public override void Dispose() { - var tileTransform = - brush.TileMode != TileMode.None ? - Matrix.CreateTranslation(destinationRect.X, destinationRect.Y) : - Matrix.Identity; - - return new BrushProperties - { - Opacity = (float)brush.Opacity, - Transform = tileTransform.ToDirect2D(), - }; + ((BitmapBrush)PlatformBrush)?.Bitmap.Dispose(); + base.Dispose(); } private static BitmapBrushProperties GetBitmapBrushProperties(TileBrush brush) @@ -60,6 +56,20 @@ namespace Avalonia.Direct2D1.Media }; } + private static BrushProperties GetBrushProperties(TileBrush brush, Rect destinationRect) + { + var tileTransform = + brush.TileMode != TileMode.None ? + Matrix.CreateTranslation(destinationRect.X, destinationRect.Y) : + Matrix.Identity; + + return new BrushProperties + { + Opacity = (float)brush.Opacity, + Transform = tileTransform.ToDirect2D(), + }; + } + private static ExtendMode GetExtendModeX(TileMode tileMode) { return (tileMode & TileMode.FlipX) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap; @@ -70,10 +80,28 @@ namespace Avalonia.Direct2D1.Media return (tileMode & TileMode.FlipY) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap; } - public override void Dispose() + private BitmapRenderTarget RenderIntermediate( + SharpDX.Direct2D1.RenderTarget target, + BitmapImpl bitmap, + TileBrushCalculator calc) { - ((BitmapBrush)PlatformBrush)?.Bitmap.Dispose(); - base.Dispose(); + var result = new BitmapRenderTarget( + target, + CompatibleRenderTargetOptions.None, + calc.IntermediateSize.ToSharpDX()); + + using (var context = new RenderTarget(result).CreateDrawingContext()) + { + var rect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight); + + context.Clear(Colors.Transparent); + context.PushClip(calc.IntermediateClip); + context.Transform = calc.IntermediateTransform; + context.DrawImage(bitmap, 1, rect, rect); + context.PopClip(); + } + + return result; } } } diff --git a/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v2.ncrunchproject b/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v2.ncrunchproject index 6e89ef281e..ad599eafcf 100644 --- a/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v2.ncrunchproject +++ b/tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v2.ncrunchproject @@ -23,5 +23,70 @@ AutoDetect STA x86 + + + Avalonia.Cairo.RenderTests.Controls.BorderTests + + + Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_Fill_NoTile + + + Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_FlipXY_TopLeftDest + + + Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_NoTile_Alignment_BottomRight + + + Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_NoTile_Alignment_Center + + + Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_NoTile_BottomRightQuarterDest + + + Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_NoTile_BottomRightQuarterSource + + + Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_NoTile_BottomRightQuarterSource_BottomRightQuarterDest + + + Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_Tile_BottomRightQuarterSource_CenterQuarterDest + + + Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_Uniform_NoTile + + + Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_UniformToFill_NoTile + + + Avalonia.Cairo.RenderTests.Controls.ImageTests + + + Avalonia.Cairo.RenderTests.GeometryClippingTests + + + Avalonia.Cairo.RenderTests.Media.LinearGradientBrushTests + + + Avalonia.Cairo.RenderTests.Media.VisualBrushTests + + + Avalonia.Cairo.RenderTests.OpacityMaskTests + + + Avalonia.Cairo.RenderTests.Shapes.EllipseTests + + + Avalonia.Cairo.RenderTests.Shapes.LineTests + + + Avalonia.Cairo.RenderTests.Shapes.PathTests + + + Avalonia.Cairo.RenderTests.Shapes.RectangleTests + + + Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_NoTile_Alignment_TopLeft + + AbnormalReferenceResolution \ No newline at end of file diff --git a/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj b/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj index c7a8739404..51a33f8f29 100644 --- a/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj +++ b/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj @@ -88,6 +88,7 @@ + diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/Utilities/TileBrushCalculatorTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/Utilities/TileBrushCalculatorTests.cs new file mode 100644 index 0000000000..8d70b45842 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/Utilities/TileBrushCalculatorTests.cs @@ -0,0 +1,139 @@ +using System; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Rendering.Utilities; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Rendering.Utilities +{ + public class TileBrushCalculatorTests + { + [Fact] + public void NoTile_Fill_1x() + { + var result = new TileBrushCalculator( + TileMode.None, + Stretch.Fill, + AlignmentX.Center, + AlignmentY.Center, + RelativeRect.Fill, + RelativeRect.Fill, + new Size(100, 100), + new Size(100, 100)); + + Assert.False(result.NeedsIntermediate); + Assert.Equal(new Rect(0, 0, 100, 100), result.SourceRect); + Assert.Equal(new Rect(0, 0, 100, 100), result.DestinationRect); + } + + [Fact] + public void NoTile_Fill_2x() + { + var result = new TileBrushCalculator( + TileMode.None, + Stretch.Fill, + AlignmentX.Center, + AlignmentY.Center, + RelativeRect.Fill, + RelativeRect.Fill, + new Size(100, 100), + new Size(200, 200)); + + // TODO: This doesn't need an intermediate render target. + Assert.True(result.NeedsIntermediate); + Assert.Equal(new Rect(0, 0, 100, 100), result.SourceRect); + Assert.Equal(new Rect(0, 0, 200, 200), result.DestinationRect); + } + + [Fact] + public void NoTile_Uniform_CenterHoriz() + { + var result = new TileBrushCalculator( + TileMode.None, + Stretch.Uniform, + AlignmentX.Center, + AlignmentY.Center, + RelativeRect.Fill, + RelativeRect.Fill, + new Size(100, 100), + new Size(200, 100)); + + Assert.True(result.NeedsIntermediate); + Assert.Equal(new Rect(0, 0, 100, 100), result.SourceRect); + Assert.Equal(new Rect(0, 0, 200, 100), result.DestinationRect); + Assert.Equal(new Size(200, 100), result.IntermediateSize); + Assert.Equal(Matrix.CreateTranslation(50, 0), result.IntermediateTransform); + } + + [Fact] + public void NoTile_Uniform_CenterVert() + { + var result = new TileBrushCalculator( + TileMode.None, + Stretch.Uniform, + AlignmentX.Center, + AlignmentY.Center, + RelativeRect.Fill, + RelativeRect.Fill, + new Size(100, 100), + new Size(100, 200)); + + Assert.True(result.NeedsIntermediate); + Assert.Equal(new Rect(0, 0, 100, 100), result.SourceRect); + Assert.Equal(new Rect(0, 0, 100, 200), result.DestinationRect); + Assert.Equal(new Size(100, 200), result.IntermediateSize); + Assert.Equal(Matrix.CreateTranslation(0, 50), result.IntermediateTransform); + } + + [Fact] + public void NoTile_NoStretch_BottomRightQuarterDest() + { + var result = new TileBrushCalculator( + TileMode.None, + Stretch.None, + AlignmentX.Center, + AlignmentY.Center, + RelativeRect.Fill, + new RelativeRect(0.5, 0.5, 0.5, 0.5, RelativeUnit.Relative), + new Size(800, 800), + new Size(400, 400)); + + Assert.True(result.NeedsIntermediate); + Assert.Equal(new Rect(0, 0, 800, 800), result.SourceRect); + Assert.Equal(new Rect(200, 200, 200, 200), result.DestinationRect); + Assert.Equal(new Size(400, 400), result.IntermediateSize); + Assert.Equal(new Rect(200, 200, 200, 200), result.IntermediateClip); + Assert.Equal(Matrix.CreateTranslation(-100, -100), result.IntermediateTransform); + } + + [Fact] + public void Tile_NoStretch_BottomRightQuarterSource_CenterQuarterDest() + { + var result = new TileBrushCalculator( + TileMode.Tile, + Stretch.None, + AlignmentX.Center, + AlignmentY.Center, + new RelativeRect(0.5, 0.5, 0.5, 0.5, RelativeUnit.Relative), + new RelativeRect(0.25, 0.25, 0.5, 0.5, RelativeUnit.Relative), + new Size(800, 800), + new Size(400, 400)); + + var b = new VisualBrush + { + TileMode = TileMode.Tile, + Stretch = Stretch.None, + 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), + Visual = new Border { Width = 400, Height = 400 }, + }; + + Assert.True(result.NeedsIntermediate); + Assert.Equal(new Rect(400, 400, 400, 400), result.SourceRect); + Assert.Equal(new Rect(100, 100, 200, 200), result.DestinationRect); + Assert.Equal(new Size(200, 200), result.IntermediateSize); + Assert.Equal(new Rect(0, 0, 200, 200), result.IntermediateClip); + Assert.Equal(Matrix.CreateTranslation(-500, -500), result.IntermediateTransform); + } + } +}