diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ImageTests/WriteableBitmap_MultiInvalidate.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ImageTests/WriteableBitmap_MultiInvalidate.xaml.cs index 8cd3f8ab1e9d..2313d3a7f623 100644 --- a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ImageTests/WriteableBitmap_MultiInvalidate.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Controls/ImageTests/WriteableBitmap_MultiInvalidate.xaml.cs @@ -1,5 +1,7 @@ using System; +using System.IO; using System.Reflection; +using System.Runtime.InteropServices.WindowsRuntime; using Uno.UI.Samples.Controls; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -24,26 +26,18 @@ public WriteableBitmap_MultiInvalidate() private void UpdateSource(object sender, RoutedEventArgs e) { var randomizer = new Random(_seed++); - if (_bitmap.PixelBuffer is Windows.Storage.Streams.Buffer buffer - && buffer.GetType().GetField("_data", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(buffer) is Memory data) + var stream = _bitmap.PixelBuffer.AsStream(); + var length = _bitmap.PixelBuffer.Length; + for (var i = 0; i < length; i++) { - var span = data.Span; - for (var i = 0; i < data.Length; i++) + if (i % 4 == 3) { - if (i % 4 == 3) - { - // Alpha channel - span[i] = 255; - } - else - { - span[i] = (byte)randomizer.Next(256); - } + stream.WriteByte(255); + } + else + { + stream.WriteByte((byte)randomizer.Next(256)); } - } - else - { - throw new InvalidOperationException("Could not access _data field in Buffer type."); } // This request to the image to redraw the buffer diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media_Imaging/Given_BitmapSource.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media_Imaging/Given_BitmapSource.cs index 9380e7d0684f..f1b3bccd0e64 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media_Imaging/Given_BitmapSource.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media_Imaging/Given_BitmapSource.cs @@ -133,119 +133,6 @@ public void When_SetSourceAsync_Stream_Then_StreamClonedSynchronously() } #endif - [TestMethod] - [RunsOnUIThread] - public async Task When_WriteableBitmap_Assigned_With_Data_Present() - { - if (!ApiInformation.IsTypePresent("Microsoft.UI.Xaml.Media.Imaging.RenderTargetBitmap")) - { - Assert.Inconclusive(); // System.NotImplementedException: RenderTargetBitmap is not supported on this platform.; - } - - var wb = new WriteableBitmap(20, 20); - - var parent = new Border() - { - Width = 50, - Height = 50, - Background = new SolidColorBrush(Colors.Blue), - }; - - var rect = new Rectangle - { - Width = 20, - Height = 20, - VerticalAlignment = VerticalAlignment.Center, - HorizontalAlignment = HorizontalAlignment.Center, - }; - - parent.Child = rect; - - WindowHelper.WindowContent = parent; - - await WindowHelper.WaitForIdle(); - await WindowHelper.WaitForLoaded(rect); - - var bgraPixelData = Enumerable.Repeat(255, (int)wb.PixelBuffer.Length).ToArray(); - - using (Stream stream = wb.PixelBuffer.AsStream()) - { - stream.Write(bgraPixelData, 0, bgraPixelData.Length); - } - - rect.Fill = new ImageBrush - { - ImageSource = wb - }; - - await WindowHelper.WaitForIdle(); - - var snapshot = await UITestHelper.ScreenShot(parent); - var coords = parent.GetRelativeCoords(rect); - await WindowHelper.WaitForIdle(); - - ImageAssert.DoesNotHaveColorInRectangle( - snapshot, new System.Drawing.Rectangle((int)coords.X, (int)coords.Y, (int)coords.Width, (int)coords.Height), Colors.Blue); - } - - [TestMethod] - [RunsOnUIThread] -#if __WASM__ - [Ignore("https://github.com/unoplatform/uno/issues/12445")] -#endif - public async Task When_WriteableBitmap_SetSource_Should_Update_PixelWidth_And_PixelHeight() - { - if (!ApiInformation.IsTypePresent("Microsoft.UI.Xaml.Media.Imaging.RenderTargetBitmap")) - { - Assert.Inconclusive(); // System.NotImplementedException: RenderTargetBitmap is not supported on this platform.; - } - - var writeableBitmap = new WriteableBitmap(1, 1); - var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/ingredient3.png")); - using (var stream = await file.OpenReadAsync()) - { - await writeableBitmap.SetSourceAsync(stream); - } - - Assert.AreEqual(147, writeableBitmap.PixelWidth); - Assert.AreEqual(147, writeableBitmap.PixelHeight); - - var parent = new Border() - { - Width = 147, - Height = 147, - }; - - var rect = new Rectangle - { - Width = 147, - Height = 147, - }; - - parent.Child = rect; - - WindowHelper.WindowContent = parent; - - await WindowHelper.WaitForIdle(); - await WindowHelper.WaitForLoaded(rect); - - rect.Fill = new ImageBrush - { - ImageSource = writeableBitmap - }; - - await WindowHelper.WaitForIdle(); - - var renderer = new RenderTargetBitmap(); - - await renderer.RenderAsync(parent); - var snapshot = await RawBitmap.From(renderer, rect); - -#if !__IOS__ && !__MACOS__ // https://github.com/unoplatform/uno/issues/12705 - ImageAssert.HasColorAt(snapshot, 1, 1, Color.FromArgb(0xFF, 0xFA, 0xB8, 0x63), tolerance: 5); -#endif - } - [TestMethod] [RunsOnUIThread] public async Task When_ImageBrush_Source_Changes() diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media_Imaging/Given_WriteableBitmap.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media_Imaging/Given_WriteableBitmap.cs new file mode 100644 index 000000000000..bb8e6bdc53d1 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media_Imaging/Given_WriteableBitmap.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Markup; +using Microsoft.UI.Xaml.Media.Imaging; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Runtime.InteropServices.WindowsRuntime; +using Private.Infrastructure; +using Windows.Storage; +using static Private.Infrastructure.TestServices; +using Microsoft.UI.Xaml.Shapes; +using Microsoft.UI.Xaml.Media; +using Windows.UI; +using Uno.UI.RuntimeTests.Helpers; +using Uno.UI.RuntimeTests.Extensions; +using Windows.Foundation.Metadata; +using Microsoft.UI.Xaml; +using System.Linq; + +namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Media_Imaging +{ + [TestClass] + [RunsOnUIThread] + public class Given_WriteableBitmap + { + [TestMethod] +#if __IOS__ + [Ignore("fails")] +#endif + public async Task When_Invalidated() + { + if (!ApiInformation.IsTypePresent("Microsoft.UI.Xaml.Media.Imaging.RenderTargetBitmap")) + { + Assert.Inconclusive(); // System.NotImplementedException: RenderTargetBitmap is not supported on this platform.; + } + + var seed = 42; + var bitmap = new WriteableBitmap(200, 200); + var border = new Border + { + Width = 200, + Height = 200, + Background = new ImageBrush { ImageSource = bitmap } + }; + + UpdateSource(); + + await UITestHelper.Load(border); + + var snapshot1 = await UITestHelper.ScreenShot(border); + + UpdateSource(); + await WindowHelper.WaitForIdle(); + + var snapshot2 = await UITestHelper.ScreenShot(border); + await ImageAssert.AreNotEqualAsync(snapshot1, snapshot2); + + void UpdateSource() + { + var randomizer = new Random(seed++); + var stream = bitmap.PixelBuffer.AsStream(); + var length = bitmap.PixelBuffer.Length; + for (var i = 0; i < length; i++) + { + if (i % 4 == 3) + { + stream.WriteByte(255); + } + else + { + stream.WriteByte((byte)randomizer.Next(256)); + } + } + + bitmap.Invalidate(); + } + } + + [TestMethod] + [RunsOnUIThread] + public async Task When_WriteableBitmap_Assigned_With_Data_Present() + { + if (!ApiInformation.IsTypePresent("Microsoft.UI.Xaml.Media.Imaging.RenderTargetBitmap")) + { + Assert.Inconclusive(); // System.NotImplementedException: RenderTargetBitmap is not supported on this platform.; + } + + var wb = new WriteableBitmap(20, 20); + + var parent = new Border() + { + Width = 50, + Height = 50, + Background = new SolidColorBrush(Colors.Blue), + }; + + var rect = new Rectangle + { + Width = 20, + Height = 20, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, + }; + + parent.Child = rect; + + WindowHelper.WindowContent = parent; + + await WindowHelper.WaitForIdle(); + await WindowHelper.WaitForLoaded(rect); + + var bgraPixelData = Enumerable.Repeat(255, (int)wb.PixelBuffer.Length).ToArray(); + + using (Stream stream = wb.PixelBuffer.AsStream()) + { + stream.Write(bgraPixelData, 0, bgraPixelData.Length); + } + + rect.Fill = new ImageBrush + { + ImageSource = wb + }; + + await WindowHelper.WaitForIdle(); + + var snapshot = await UITestHelper.ScreenShot(parent); + var coords = parent.GetRelativeCoords(rect); + await WindowHelper.WaitForIdle(); + + ImageAssert.DoesNotHaveColorInRectangle( + snapshot, new System.Drawing.Rectangle((int)coords.X, (int)coords.Y, (int)coords.Width, (int)coords.Height), Colors.Blue); + } + + [TestMethod] + [RunsOnUIThread] +#if __WASM__ + [Ignore("https://github.com/unoplatform/uno/issues/12445")] +#endif + public async Task When_WriteableBitmap_SetSource_Should_Update_PixelWidth_And_PixelHeight() + { + if (!ApiInformation.IsTypePresent("Microsoft.UI.Xaml.Media.Imaging.RenderTargetBitmap")) + { + Assert.Inconclusive(); // System.NotImplementedException: RenderTargetBitmap is not supported on this platform.; + } + + var writeableBitmap = new WriteableBitmap(1, 1); + var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/ingredient3.png")); + using (var stream = await file.OpenReadAsync()) + { + await writeableBitmap.SetSourceAsync(stream); + } + + Assert.AreEqual(147, writeableBitmap.PixelWidth); + Assert.AreEqual(147, writeableBitmap.PixelHeight); + + var parent = new Border() + { + Width = 147, + Height = 147, + }; + + var rect = new Rectangle + { + Width = 147, + Height = 147, + }; + + parent.Child = rect; + + WindowHelper.WindowContent = parent; + + await WindowHelper.WaitForIdle(); + await WindowHelper.WaitForLoaded(rect); + + rect.Fill = new ImageBrush + { + ImageSource = writeableBitmap + }; + + await WindowHelper.WaitForIdle(); + + var renderer = new RenderTargetBitmap(); + + await renderer.RenderAsync(parent); + var snapshot = await RawBitmap.From(renderer, rect); + +#if !__IOS__ && !__MACOS__ // https://github.com/unoplatform/uno/issues/12705 + ImageAssert.HasColorAt(snapshot, 1, 1, Color.FromArgb(0xFF, 0xFA, 0xB8, 0x63), tolerance: 5); +#endif + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.Android.cs b/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.Android.cs index 6e4836fa197b..c3ea30d794a3 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.Android.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.Android.cs @@ -37,13 +37,13 @@ partial class BorderLayerRenderer private static ImageSource? GetBackgroundImageSource(BorderLayerState? state) => (state?.Background as ImageBrush)?.ImageSource; - partial void UpdatePlatform() + partial void UpdatePlatform(bool forceUpdate) { var drawArea = new Rect(default, _owner.LayoutSlotWithMarginsAndAlignments.Size.LogicalToPhysicalPixels()); var newState = new BorderLayerState(drawArea.Size, _borderInfoProvider); var previousLayoutState = _currentState; - if (newState.Equals(previousLayoutState)) + if (newState.Equals(previousLayoutState) && !forceUpdate) { return; } @@ -261,8 +261,8 @@ private IDisposable InnerCreateLayers( // because even though the brush instance is the same, there are additional properties // that BorderLayerState tracks on Android. This is not ideal and we should avoid it by refactoring // this file to handle brush changes on the same brush instance on its own instead. - Brush.SetupBrushChanged(_currentState.Background, background, ref _backgroundChanged, () => Update(), false); - Brush.SetupBrushChanged(_currentState.BorderBrush, borderBrush, ref _borderChanged, () => Update(), false); + Brush.SetupBrushChanged(_currentState.Background, background, ref _backgroundChanged, () => Update(true), false); + Brush.SetupBrushChanged(_currentState.BorderBrush, borderBrush, ref _borderChanged, () => Update(true), false); return disposables; } diff --git a/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.cs b/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.cs index 4c3fd72c8064..cff356cf5daf 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.cs @@ -42,11 +42,11 @@ public BorderLayerRenderer(FrameworkElement owner) /// /// Updates the border. /// - internal void Update() + internal void Update(bool forceUpdate = false) { if (_owner.IsLoaded) { - UpdatePlatform(); + UpdatePlatform(forceUpdate); } } @@ -59,7 +59,7 @@ internal void Clear() _currentState = default; } - partial void UpdatePlatform(); + partial void UpdatePlatform(bool forceUpdate); partial void ClearPlatform(); } diff --git a/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.iOSmacOS.cs b/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.iOSmacOS.cs index 110301f0569d..572645fe47ec 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.iOSmacOS.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.iOSmacOS.cs @@ -60,7 +60,7 @@ internal CGPath BoundsPath /// The border brush /// The corner radius /// An updated BoundsPath if the layer has been created or updated; null if there is no change. - partial void UpdatePlatform() + partial void UpdatePlatform(bool forceUpdate) { // Bounds is captured to avoid calling twice calls below. var bounds = _owner.Bounds; @@ -72,7 +72,7 @@ partial void UpdatePlatform() _borderInfoProvider.BorderBrush, _borderInfoProvider.BorderThickness, _borderInfoProvider.CornerRadius); - if (!newState.Equals(_currentState)) + if (!newState.Equals(_currentState) || forceUpdate) { #if __MACOS__ _owner.WantsLayer = true; diff --git a/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.wasm.cs b/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.wasm.cs index 99d5c35baf10..fd3d8daeaf86 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.wasm.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Border/BorderLayerRenderer.wasm.cs @@ -21,7 +21,7 @@ partial class BorderLayerRenderer private Action _backgroundChanged; private Action _borderChanged; - partial void UpdatePlatform() + partial void UpdatePlatform(bool forceUpdate) { var newState = new BorderLayerState( new Size(_owner.RenderSize.Width, _owner.RenderSize.Height), @@ -31,7 +31,7 @@ partial void UpdatePlatform() _borderInfoProvider.BorderThickness, _borderInfoProvider.CornerRadius); var previousLayoutState = _currentState; - if (!newState.Equals(previousLayoutState)) + if (!newState.Equals(previousLayoutState) || forceUpdate) { if (previousLayoutState.Background != newState.Background && _owner is FrameworkElement fwElt) {