Skip to content

Commit

Permalink
Merge pull request #18408 from ramezgerges/writablebitmap_android_inv…
Browse files Browse the repository at this point in the history
…alidation

fix(WriteableBitmap): Invalidation should cause a redrawing
  • Loading branch information
ramezgerges authored Oct 17, 2024
2 parents d4eb5f6 + 180dac7 commit 1a887b2
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 141 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<byte> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte>(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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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<byte>(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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
Loading

0 comments on commit 1a887b2

Please sign in to comment.