Browse Source
Don't try to render `VisualBrush`es in TileBrushImpl - visual brushes need first to be transformed into images and then rendered as an `ImageBrush`. To this end, renamed `TileBrushImpl` as `ImageBrushImpl` (in D2D backend only for now as that's what I'll get working first). Actual visual brush implementation not yet done.scenegraph-after-breakage
13 changed files with 510 additions and 71 deletions
@ -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; } |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TileBrushCalculator"/> class.
|
|||
/// </summary>
|
|||
/// <param name="brush">The brush to be rendered.</param>
|
|||
/// <param name="contentSize">The size of the content of the tile brush.</param>
|
|||
/// <param name="targetSize">The size of the control to which the brush is being rendered.</param>
|
|||
public TileBrushCalculator(TileBrush brush, Size contentSize, Size targetSize) |
|||
: this( |
|||
brush.TileMode, |
|||
brush.Stretch, |
|||
brush.AlignmentX, |
|||
brush.AlignmentY, |
|||
brush.SourceRect, |
|||
brush.DestinationRect, |
|||
contentSize, |
|||
targetSize) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TileBrushCalculator"/> class.
|
|||
/// </summary>
|
|||
/// <param name="tileMode">The brush's tile mode.</param>
|
|||
/// <param name="stretch">The brush's stretch.</param>
|
|||
/// <param name="alignmentX">The brush's horizontal alignment.</param>
|
|||
/// <param name="alignmentY">The brush's vertical alignment.</param>
|
|||
/// <param name="sourceRect">The brush's source rect</param>
|
|||
/// <param name="destinationRect">The brush's destination rect.</param>
|
|||
/// <param name="contentSize">The size of the content of the tile brush.</param>
|
|||
/// <param name="targetSize">The size of the control to which the brush is being rendered.</param>
|
|||
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); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the rectangle on the destination control to which content should be rendered.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If <see cref="TileMode"/> of the brush is repeating then this is describes rectangle
|
|||
/// of a single repeat of the tiled content.
|
|||
/// </remarks>
|
|||
public Rect DestinationRect { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the clip rectangle on the intermediate image with which the brush content should be
|
|||
/// drawn when <see cref="NeedsIntermediate"/> is true.
|
|||
/// </summary>
|
|||
public Rect IntermediateClip => _drawRect; |
|||
|
|||
/// <summary>
|
|||
/// Gets the size of the intermediate image that should be created when
|
|||
/// <see cref="NeedsIntermediate"/> is true.
|
|||
/// </summary>
|
|||
public Size IntermediateSize { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the transform to be used when rendering to the intermediate image when
|
|||
/// <see cref="NeedsIntermediate"/> is true.
|
|||
/// </summary>
|
|||
public Matrix IntermediateTransform { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether an intermediate image should be created in order to
|
|||
/// render the tile brush.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Intermediate images are required when a brush's <see cref="TileMode"/> 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.
|
|||
/// </remarks>
|
|||
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; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the area of the source content to be rendered.
|
|||
/// </summary>
|
|||
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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue