diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/TransparentShapeShouldNotDisplayShadow.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/TransparentShapeShouldNotDisplayShadow.png new file mode 100644 index 000000000000..ba361be4aa5f Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/TransparentShapeShouldNotDisplayShadow.png differ diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue29394.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue29394.cs new file mode 100644 index 000000000000..aad6170489c9 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue29394.cs @@ -0,0 +1,68 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 29394, "On Android Shadows should not be rendered over fully transparent areas of drawn shapes", PlatformAffected.Android)] +public class Issue29394 : TestContentPage +{ + protected override void Init() + { + var verticalStackLayout = new VerticalStackLayout() + { + Padding = new Thickness(30, 0), + Spacing = 25, + VerticalOptions = LayoutOptions.Center + }; + + var graphicsView = new GraphicsView() + { + HeightRequest = 500, + WidthRequest = 400, + Drawable = new Issue29394_Drawable() + }; + var label = new Label() + { + Text = "This is a test for Shadows should not be rendered over fully transparent areas of drawn shapes.", + AutomationId = "label", + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center + }; + + verticalStackLayout.Children.Add(graphicsView); + verticalStackLayout.Children.Add(label); + Content = verticalStackLayout; + } +} + +public class Issue29394_Drawable : IDrawable +{ + Rect fillRect = new Rect(0, 0, 300, 300); + + public void Draw(ICanvas canvas, RectF dirtyRect) + { + canvas.SaveState(); + + float centerX = (dirtyRect.Width - (float)fillRect.Width) / 2; + float centerY = (dirtyRect.Height - (float)fillRect.Height) / 2; + var outerRect = new RectF(centerX, centerY, (float)fillRect.Width, (float)fillRect.Height); + + canvas.SetFillPaint(Colors.Transparent.AsPaint(), outerRect); + canvas.SetShadow(new SizeF(0, 15), 4, Color.FromArgb("#59000000")); + canvas.FillEllipse(outerRect.X, outerRect.Y, outerRect.Width, outerRect.Height); + + float arcSize = 250f; + float arcX = outerRect.X + (outerRect.Width - arcSize) / 2; + float arcY = outerRect.Y + (outerRect.Height - arcSize) / 2; + + canvas.FillColor = Colors.Blue; + canvas.FillCircle(arcX + arcSize / 2, arcY + arcSize / 2, 15); + canvas.DrawArc(arcX, arcY, arcSize, arcSize, 45, 90, true, false); + + canvas.StrokeColor = Colors.Transparent; + canvas.StrokeSize = 2; + canvas.DrawLine(0, 25, dirtyRect.Width, 25); + + canvas.FontColor = Colors.Transparent; + canvas.DrawString("Shadow should not be Rendered", dirtyRect.Width / 2, 10, HorizontalAlignment.Center); + + canvas.RestoreState(); + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/TransparentShapeShouldNotDisplayShadow.png b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/TransparentShapeShouldNotDisplayShadow.png new file mode 100644 index 000000000000..2aa08b7f9085 Binary files /dev/null and b/src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/TransparentShapeShouldNotDisplayShadow.png differ diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29394.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29394.cs new file mode 100644 index 000000000000..8c6ffb24c4a7 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29394.cs @@ -0,0 +1,19 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; +public class Issue29394 : _IssuesUITest +{ + public Issue29394(TestDevice device) : base(device) { } + + public override string Issue => "On Android Shadows should not be rendered over fully transparent areas of drawn shapes"; + + [Test] + [Category(UITestCategories.GraphicsView)] + public void TransparentShapeShouldNotDisplayShadow() + { + App.WaitForElement("label"); + VerifyScreenshot(); + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TransparentShapeShouldNotDisplayShadow.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TransparentShapeShouldNotDisplayShadow.png new file mode 100644 index 000000000000..607854ad63fe Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TransparentShapeShouldNotDisplayShadow.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/TransparentShapeShouldNotDisplayShadow.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/TransparentShapeShouldNotDisplayShadow.png new file mode 100644 index 000000000000..eefc0b7e7012 Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/TransparentShapeShouldNotDisplayShadow.png differ diff --git a/src/Graphics/src/Graphics/Platforms/Android/PlatformCanvasState.cs b/src/Graphics/src/Graphics/Platforms/Android/PlatformCanvasState.cs index 4828a7db7bf6..cd80ebdc604c 100644 --- a/src/Graphics/src/Graphics/Platforms/Android/PlatformCanvasState.cs +++ b/src/Graphics/src/Graphics/Platforms/Android/PlatformCanvasState.cs @@ -61,13 +61,27 @@ public PlatformCanvasState(PlatformCanvasState prototype) : base(prototype) public Color StrokeColor { get => _strokeColor; - set => _strokeColor = value; + set + { + if (_strokeColor != value) + { + _strokeColor = value; + UpdateShadowState(); + } + } } public Color FillColor { get => _fillColor; - set => _fillColor = value; + set + { + if (_fillColor != value) + { + _fillColor = value; + UpdateShadowState(); + } + } } public Color FontColor @@ -75,8 +89,12 @@ public Color FontColor get => _fontColor; set { - _fontColor = value; - FontPaint.Color = value != null ? _fontColor.AsColor() : global::Android.Graphics.Color.Black; + if (_fontColor != value) + { + _fontColor = value; + FontPaint.Color = value != null ? _fontColor.AsColor() : global::Android.Graphics.Color.Black; + UpdateShadowState(); + } } } @@ -352,15 +370,15 @@ public override void Dispose() public void SetShadow(float blur, float sx, float sy, global::Android.Graphics.Color color) { - FillPaint.SetShadowLayer(blur, sx, sy, color); - StrokePaint.SetShadowLayer(blur, sx, sy, color); - FontPaint.SetShadowLayer(blur, sx, sy, color); - _shadowed = true; _shadowBlur = blur; _shadowX = sx; _shadowY = sy; _shadowColor = color; + + ApplyShadow(FillPaint, FillColor.Alpha); + ApplyShadow(StrokePaint, StrokeColor.Alpha); + ApplyShadow(FontPaint, FontColor.Alpha); } public global::Android.Graphics.Paint GetShadowPaint(float sx, float sy) @@ -420,5 +438,25 @@ public void Reset(global::Android.Graphics.Paint aFontPaint, global::Android.Gra _scaleX = 1; _scaleY = 1; } + + void ApplyShadow(global::Android.Graphics.Paint paint, float alpha) + { + if (alpha > 0) + { + paint.SetShadowLayer(_shadowBlur, _shadowX, _shadowY, _shadowColor); + } + else + { + paint.ClearShadowLayer(); + } + } + + void UpdateShadowState() + { + if (_shadowed) + { + SetShadow(_shadowBlur, _shadowX, _shadowY, _shadowColor); + } + } } }