diff --git a/samples/ControlCatalog/Pages/CanvasPage.xaml b/samples/ControlCatalog/Pages/CanvasPage.xaml
index 10a38895a2..d6c138a4f7 100644
--- a/samples/ControlCatalog/Pages/CanvasPage.xaml
+++ b/samples/ControlCatalog/Pages/CanvasPage.xaml
@@ -11,7 +11,8 @@
-
+
+
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
index e640925b3a..10c8d36dc8 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
@@ -30,19 +30,20 @@ namespace Avalonia.Rendering.SceneGraph
Matrix Transform { get; }
///
- /// Gets the bounds for the node's geometry in global coordinates.
+ /// Gets the bounds of the node's geometry in global coordinates.
///
Rect Bounds { get; }
///
/// Gets the clip bounds for the node in global coordinates.
///
- ///
- /// This clip does not take into account parent clips, to find the absolute clip bounds
- /// it is necessary to traverse the tree.
- ///
Rect ClipBounds { get; }
+ ///
+ /// Gets the layout bounds for the node in global coordinates.
+ ///
+ Rect LayoutBounds { get; }
+
///
/// Whether the node is clipped to .
///
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
index 799380cb85..2fcea8c205 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
@@ -167,8 +167,9 @@ namespace Avalonia.Rendering.SceneGraph
using (context.PushPostTransform(m))
using (context.PushTransformContainer())
{
+ var globalBounds = bounds.TransformToAABB(contextImpl.Transform);
var clipBounds = clipToBounds ?
- bounds.TransformToAABB(contextImpl.Transform).Intersect(clip) :
+ globalBounds.Intersect(clip) :
clip;
forceRecurse = forceRecurse ||
@@ -179,6 +180,7 @@ namespace Avalonia.Rendering.SceneGraph
node.Transform = contextImpl.Transform;
node.ClipBounds = clipBounds;
node.ClipToBounds = clipToBounds;
+ node.LayoutBounds = globalBounds;
node.GeometryClip = visual.Clip?.PlatformImpl;
node.Opacity = opacity;
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
index 760cedcbf0..8000d413ea 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
@@ -58,6 +58,9 @@ namespace Avalonia.Rendering.SceneGraph
///
public Rect ClipBounds { get; set; }
+ ///
+ public Rect LayoutBounds { get; set; }
+
///
public bool ClipToBounds { get; set; }
@@ -266,7 +269,7 @@ namespace Avalonia.Rendering.SceneGraph
if (OpacityMask != null)
{
- context.PushOpacityMask(OpacityMask, ClipBounds);
+ context.PushOpacityMask(OpacityMask, LayoutBounds);
}
}
diff --git a/tests/Avalonia.RenderTests/OpacityMaskTests.cs b/tests/Avalonia.RenderTests/OpacityMaskTests.cs
index 1934654c26..4edf4daa13 100644
--- a/tests/Avalonia.RenderTests/OpacityMaskTests.cs
+++ b/tests/Avalonia.RenderTests/OpacityMaskTests.cs
@@ -55,5 +55,42 @@ namespace Avalonia.Direct2D1.RenderTests
await RenderToFile(target);
CompareImages();
}
+
+ [Fact]
+ public async Task RenderTansform_Applies_To_Opacity_Mask()
+ {
+ var target = new Canvas
+ {
+ OpacityMask = new LinearGradientBrush
+ {
+ StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
+ EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
+ GradientStops = new List
+ {
+ new GradientStop(Color.FromUInt32(0xffffffff), 0),
+ new GradientStop(Color.FromUInt32(0x00ffffff), 1)
+ }
+ },
+ RenderTransform = new RotateTransform(90),
+ Width = 76,
+ Height = 76,
+ Children =
+ {
+ new Path
+ {
+ Width = 32,
+ Height = 40,
+ [Canvas.LeftProperty] = 23,
+ [Canvas.TopProperty] = 18,
+ Stretch = Stretch.Fill,
+ Fill = Brushes.Red,
+ Data = StreamGeometry.Parse("F1 M 27,18L 23,26L 33,30L 24,38L 33,46L 23,50L 27,58L 45,58L 55,38L 45,18L 27,18 Z")
+ }
+ }
+ };
+
+ await RenderToFile(target);
+ CompareImages();
+ }
}
}
diff --git a/tests/TestFiles/Direct2D1/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png b/tests/TestFiles/Direct2D1/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png
new file mode 100644
index 0000000000..bf9775ba0b
Binary files /dev/null and b/tests/TestFiles/Direct2D1/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png differ
diff --git a/tests/TestFiles/Skia/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png b/tests/TestFiles/Skia/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png
new file mode 100644
index 0000000000..d7ea7d206f
Binary files /dev/null and b/tests/TestFiles/Skia/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png differ