diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/LinearGradientBrushTransparentStopsShouldNotBeOpaque.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/LinearGradientBrushTransparentStopsShouldNotBeOpaque.png new file mode 100644 index 000000000000..628d1b3f01a0 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/LinearGradientBrushTransparentStopsShouldNotBeOpaque.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyLinearGradientBrushWithOpacity.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyLinearGradientBrushWithOpacity.png index 622e10e7bbf6..dae55c55ffde 100644 Binary files a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyLinearGradientBrushWithOpacity.png and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyLinearGradientBrushWithOpacity.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyLinearGradientBrushWithShadowAndOpacity.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyLinearGradientBrushWithShadowAndOpacity.png index 176341dbe032..3fb4d0fc98d8 100644 Binary files a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyLinearGradientBrushWithShadowAndOpacity.png and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyLinearGradientBrushWithShadowAndOpacity.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyLinearGradientBrushWithStrokeAndOpacity.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyLinearGradientBrushWithStrokeAndOpacity.png index 353ed235d775..ef34ab1e100a 100644 Binary files a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyLinearGradientBrushWithStrokeAndOpacity.png and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyLinearGradientBrushWithStrokeAndOpacity.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyRadialGradientBrushWithOpacity.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyRadialGradientBrushWithOpacity.png index 833ffc934747..bb4afce76101 100644 Binary files a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyRadialGradientBrushWithOpacity.png and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyRadialGradientBrushWithOpacity.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyRadialGradientBrushWithShadowAndOpacity.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyRadialGradientBrushWithShadowAndOpacity.png index a7c3b880855f..9e137e8b1d4a 100644 Binary files a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyRadialGradientBrushWithShadowAndOpacity.png and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyRadialGradientBrushWithShadowAndOpacity.png differ diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue35280.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue35280.cs new file mode 100644 index 000000000000..ee0f53ccfff8 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue35280.cs @@ -0,0 +1,128 @@ +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Shapes; +using Microsoft.Maui.Graphics; + +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 35280, "LinearGradientBrush with transparent stops renders as opaque black box on Android", PlatformAffected.Android)] +public class Issue35280 : ContentPage +{ + public Issue35280() + { + Label label = new Label + { + AutomationId = "DescriptionLabel", + Text = "The image below should have a gradient overlay that fades from black to transparent. If the image is completely covered by a black box, then the test has failed." + }; + + var border = new Border + { + Stroke = Colors.Transparent, + Margin = new Thickness(5, 0, 0, 0), + WidthRequest = 250, + StrokeShape = new RoundRectangle + { + CornerRadius = new CornerRadius(10) + }, + Content = new Grid + { + Children = + { + new Image + { + Source = "dotnet_bot.png", + Aspect = Aspect.AspectFill + }, + new VerticalStackLayout + { + VerticalOptions = LayoutOptions.End, + Padding = 10, + Background = new LinearGradientBrush + { + StartPoint = new Point(0, 1), + EndPoint = new Point(1, 0), + GradientStops = new GradientStopCollection + { + new GradientStop { Color = Colors.Black, Offset = 0.0f }, + new GradientStop { Color = Color.FromArgb("#80000000"), Offset = 0.5f }, + new GradientStop { Color = Colors.Transparent, Offset = 1.0f } + } + }, + Children = + { + new Label + { + Text = "Title", + TextColor = Colors.White + }, + new Label + { + Text = "Subtitle", + TextColor = Colors.White + } + } + } + } + } + }; + + // Radial gradient with transparent stops — exercises SetBackground(RadialGradientPaint) + var radialGradientBox = new BoxView + { + AutomationId = "RadialGradientBox", + WidthRequest = 250, + HeightRequest = 100, + Background = new RadialGradientBrush + { + GradientStops = new GradientStopCollection + { + new GradientStop { Color = Colors.Transparent, Offset = 0.0f }, + new GradientStop { Color = Color.FromArgb("#80000000"), Offset = 0.5f }, + new GradientStop { Color = Colors.Blue, Offset = 1.0f } + } + } + }; + + // Border with a LinearGradientBrush Stroke that fades to transparent — exercises SetBorderBrush(LinearGradientPaint) + var borderGradientBox = new Border + { + AutomationId = "BorderGradientBox", + WidthRequest = 250, + HeightRequest = 100, + StrokeThickness = 10, + StrokeShape = new RoundRectangle + { + CornerRadius = new CornerRadius(10) + }, + Stroke = new LinearGradientBrush + { + StartPoint = new Point(0, 0), + EndPoint = new Point(1, 1), + GradientStops = new GradientStopCollection + { + new GradientStop { Color = Colors.Purple, Offset = 0.0f }, + new GradientStop { Color = Color.FromArgb("#80000000"), Offset = 0.5f }, + new GradientStop { Color = Colors.Transparent, Offset = 1.0f } + } + }, + Content = new Label + { + Text = "Gradient border", + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center + } + }; + + Content = new VerticalStackLayout + { + Padding = 20, + Children = + { + label, + border, + radialGradientBox, + borderGradientBox + } + }; + } +} \ No newline at end of file diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35280.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35280.cs new file mode 100644 index 000000000000..1cd835f36036 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue35280.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue35280 : _IssuesUITest +{ + public Issue35280(TestDevice device) : base(device) { } + + public override string Issue => "LinearGradientBrush with transparent stops renders as opaque black box on Android"; + + [Test] + [Category(UITestCategories.Brush)] + public void LinearGradientBrushTransparentStopsShouldNotBeOpaque() + { + // Wait for the gradient container to be visible + App.WaitForElement("DescriptionLabel"); + + // The gradient has transparent/semi-transparent stops. + // With the bug (GetGradientData(1.0f)), all stops are forced fully opaque → solid black box. + // With the fix (GetGradientData(null)), per-stop alpha is preserved → the dotnet_bot.png image shows through. + VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2)); + } +} diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/LinearGradientBrushTransparentStopsShouldNotBeOpaque.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/LinearGradientBrushTransparentStopsShouldNotBeOpaque.png new file mode 100644 index 000000000000..89ceef09fa1e Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/LinearGradientBrushTransparentStopsShouldNotBeOpaque.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/LinearGradientBrushTransparentStopsShouldNotBeOpaque.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/LinearGradientBrushTransparentStopsShouldNotBeOpaque.png new file mode 100644 index 000000000000..3b65236af0b6 Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/LinearGradientBrushTransparentStopsShouldNotBeOpaque.png differ diff --git a/src/Core/src/Graphics/MauiDrawable.Android.cs b/src/Core/src/Graphics/MauiDrawable.Android.cs index 0a2df48b1bbd..da6c0ac2e197 100644 --- a/src/Core/src/Graphics/MauiDrawable.Android.cs +++ b/src/Core/src/Graphics/MauiDrawable.Android.cs @@ -112,7 +112,7 @@ public void SetBackground(LinearGradientPaint linearGradientPaint) _background = linearGradientPaint; - var gradientData = linearGradientPaint.GetGradientData(1.0f); + var gradientData = linearGradientPaint.GetGradientData(null); SetLinearGradientBackground(gradientData.X1, gradientData.Y1, gradientData.X2, gradientData.Y2, gradientData.Colors, gradientData.Offsets); } @@ -126,7 +126,7 @@ public void SetBackground(RadialGradientPaint radialGradientPaint) _background = radialGradientPaint; - var gradientData = radialGradientPaint.GetGradientData(1.0f); + var gradientData = radialGradientPaint.GetGradientData(null); SetRadialGradientBackground(gradientData.CenterX, gradientData.CenterY, gradientData.Radius, gradientData.Colors, gradientData.Offsets); } @@ -247,7 +247,7 @@ public void SetBorderBrush(LinearGradientPaint linearGradientPaint) _stroke = linearGradientPaint; - var gradientData = linearGradientPaint.GetGradientData(1.0f); + var gradientData = linearGradientPaint.GetGradientData(null); SetLinearGradientBorder(gradientData.X1, gradientData.Y1, gradientData.X2, gradientData.Y2, gradientData.Colors, gradientData.Offsets); } @@ -261,7 +261,7 @@ public void SetBorderBrush(RadialGradientPaint radialGradientPaint) _stroke = radialGradientPaint; - var gradientData = radialGradientPaint.GetGradientData(1.0f); + var gradientData = radialGradientPaint.GetGradientData(null); SetRadialGradientBorder(gradientData.CenterX, gradientData.CenterY, gradientData.Radius, gradientData.Colors, gradientData.Offsets); }