Skip to content

Commit

Permalink
feat: RenderTargetBitmap
Browse files Browse the repository at this point in the history
  • Loading branch information
workgroupengineering committed Jul 8, 2022
1 parent 7848d0e commit ba33ee0
Show file tree
Hide file tree
Showing 13 changed files with 425 additions and 55 deletions.
8 changes: 0 additions & 8 deletions src/Uno.UI.Composition/Composition/Compositor.skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,6 @@ internal void RenderVisual(SKSurface surface, Visual visual)

visual.Render(surface);

if (visual is ContainerVisual containerVisual)
{
var children = containerVisual.GetChildrenInRenderOrder();
for (var i = 0; i < children.Count; i++)
{
RenderVisual(surface, children[i]);
}
}

surface.Canvas.Restore();
}
Expand Down
14 changes: 13 additions & 1 deletion src/Uno.UI.Composition/Composition/ContainerVisual.skia.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#nullable enable

using SkiaSharp;
using System.Collections.Generic;
using System.Linq;

Expand Down Expand Up @@ -38,4 +38,16 @@ internal void ResetRenderOrder()
}
IsChildrenRenderOrderDirty = false;
}

internal override void Render(SKSurface surface)
{
var compositor = this.Compositor;
var children = GetChildrenInRenderOrder();
var childrenCount = children.Count;
for (int i = 0; i < childrenCount; i++)
{
compositor.RenderVisual(surface, children[i]);
}
}

}
107 changes: 87 additions & 20 deletions src/Uno.UI/UI/Xaml/Media/Imaging/RenderTargetBitmap.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,119 @@

using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Numerics;
using Android.Graphics;
using Uno.UI;
using Windows.Foundation;
using Java.Nio;
using Android.Views;

namespace Windows.UI.Xaml.Media.Imaging
{
partial class RenderTargetBitmap
{
/// <summary>
/// Forces all descendent views to render using the software renderer instead
/// of the hardware renderer.
/// </summary>
/// <remarks>
/// This allows for harware-only surfaces, like overlays, to be visible in the
/// the screenshots.
/// </remarks>
private static void SetSoftwareRendering(View nativeView, bool enabled)
{
var layerType = enabled ? LayerType.Software : LayerType.Hardware;

nativeView.SetLayerType(layerType, null);

if (nativeView is ViewGroup viewGroup)
{
foreach (var child in viewGroup.EnumerateAllChildren(maxDepth: 1024))
{
child.SetLayerType(layerType, null);
}
}
}
/// <inheritdoc />
private protected override bool IsSourceReady => _buffer != null;

/// <inheritdoc />
private protected override bool TryOpenSourceSync(int? targetWidth, int? targetHeight, [NotNullWhen(true)] out Bitmap? image)
{
image = BitmapFactory.DecodeByteArray(_buffer, 0, _buffer.Length);
image = default;
if (_buffer is null)
{
return false;
}
image = BitmapFactory.DecodeByteArray(_buffer, 0, _bufferSize);
return image != null;
}

private static byte[] RenderAsPng(UIElement element, Size? scaledSize = null)
private (int ByteCount, int Width, int Height) RenderAsBgra8_Premul(UIElement? element, ref byte[]? buffer, Size? scaledSize = null)
{
var logical = element.ActualSize.ToSize();

var byteCount = 0;
Bitmap? bitmap = default;
UIElement elementToRender = element
?? XamlRoot.Current.Content
?? throw new global::System.NullReferenceException();

// In UWP RenderTargetBitmap use logical size
// if using logical size to render the element there are generate glycth.
// to avoid this layout the control on Physical and after scale to logical
var logical = elementToRender.ActualSize.ToSize();
var physical = logical.LogicalToPhysicalPixels();
var bitmap = Bitmap.CreateBitmap((int)physical.Width, (int)physical.Height, Bitmap.Config.Argb8888!)
?? throw new InvalidOperationException("Failed to create target native bitmap.");
var canvas = new Canvas(bitmap);
if (physical.IsEmpty)
{
return (0, 0, 0);
}
try
{
SetSoftwareRendering(elementToRender, true);
bitmap = Bitmap.CreateBitmap((int)physical.Width, (int)physical.Height, Bitmap.Config.Argb8888!, true)
?? throw new InvalidOperationException("Failed to create target native bitmap.");
using var canvas = new Canvas(bitmap);
{
// Make sure the element has been Layouted.
Uno.UI.UnoViewGroup.TryFastRequestLayout(element, false);
// Render on the canvas
canvas.DrawColor(Colors.Transparent, PorterDuff.Mode.Clear!);
elementToRender.Draw(canvas);
}

var targetSize = scaledSize ?? logical;
if (targetSize is { } && targetSize != physical)
{
using var oldbitmap = bitmap;
bitmap = Bitmap.CreateScaledBitmap(oldbitmap, (int)targetSize.Width, (int)targetSize.Height, false)
?? throw new InvalidOperationException("Failed to scaled native bitmap to the requested size.");
}

// Make sure the element has been Layouted
element.Layout(0, 0, (int)physical.Width, (int)physical.Height);
byteCount = bitmap.ByteCount;
using var byteArray = ByteBuffer.Allocate(byteCount);
bitmap.CopyPixelsToBuffer(byteArray);
if (byteArray is null)
{
return (0,0,0);
}

// Render on the canvas
canvas.DrawColor(Colors.Transparent);
element.Draw(canvas);
EnsureBuffer(ref buffer, byteCount);

if (scaledSize is {} targetSize)
byteArray.Rewind();
//This is called for ensure correct byte order
var ordered = byteArray.Order(ByteOrder.BigEndian!);
ordered.Get(buffer!, 0, byteCount);
//Android store Argb8888 as rgba8888
SwapRB(ref buffer!, byteCount);
return (byteCount, bitmap.Width, bitmap.Height);
}
finally
{
bitmap = Bitmap.CreateScaledBitmap(bitmap, (int)targetSize.Width, (int)targetSize.Height, false)
?? throw new InvalidOperationException("Failed to scaled native bitmap to the requested size.");
bitmap?.Dispose();
SetSoftwareRendering(elementToRender, false);
}


using var stream = new MemoryStream();
bitmap.Compress(Bitmap.CompressFormat.Png, 100, stream);

return stream.ToArray();
}
}
}
69 changes: 51 additions & 18 deletions src/Uno.UI/UI/Xaml/Media/Imaging/RenderTargetBitmap.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#if !__IOS__ && !__ANDROID__
#nullable enable
#if !__IOS__ && !__ANDROID__ && !__SKIA__ && !__MACOS__
#define NOT_IMPLEMENTED
#endif

Expand All @@ -11,14 +12,29 @@
using Uno.Extensions;
using Uno.Foundation.Logging;
using Buffer = Windows.Storage.Streams.Buffer;
using System.Buffers;

namespace Windows.UI.Xaml.Media.Imaging
{
#if NOT_IMPLEMENTED
[global::Uno.NotImplemented()]
#endif
public partial class RenderTargetBitmap
public partial class RenderTargetBitmap: IDisposable
{
private static void Swap(ref byte a, ref byte b)
{
(a, b) = (b, a);
}

[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
private static void SwapRB(ref byte[] buffer, int byteCount)
{
for (int i = 0; i < byteCount; i += 4)
{
//Swap R and B chanal
Swap(ref buffer![i], ref buffer![i + 2]);
}
}
#if NOT_IMPLEMENTED
internal const bool IsImplemented = false;
#else
Expand Down Expand Up @@ -57,24 +73,21 @@ public int PixelHeight
{
get => (int)GetValue(PixelHeightProperty);
private set => SetValue(PixelHeightProperty, value);
}
}
#endregion

private byte[] _buffer;
private byte[]? _buffer;
private int _bufferSize = 0;

#if NOT_IMPLEMENTED
[global::Uno.NotImplemented()]
#endif
public IAsyncAction RenderAsync(UIElement element, int scaledWidth, int scaledHeight)
public IAsyncAction RenderAsync(UIElement? element, int scaledWidth, int scaledHeight)
=> AsyncAction.FromTask(async ct =>
{
try
{
_buffer = RenderAsPng(element, new Size(scaledWidth, scaledHeight));

PixelWidth = scaledWidth;
PixelHeight = scaledHeight;

(_bufferSize, PixelWidth, PixelHeight) = RenderAsBgra8_Premul(element, ref _buffer, new Size(scaledWidth, scaledHeight));
#if __WASM__ || __SKIA__
InvalidateSource();
#endif
Expand All @@ -88,16 +101,12 @@ public IAsyncAction RenderAsync(UIElement element, int scaledWidth, int scaledHe
#if NOT_IMPLEMENTED
[global::Uno.NotImplemented()]
#endif
public IAsyncAction RenderAsync(UIElement element)
public IAsyncAction RenderAsync(UIElement? element)
=> AsyncAction.FromTask(async ct =>
{
try
{
_buffer = RenderAsPng(element);

PixelWidth = (int)element.ActualSize.X;
PixelHeight = (int)element.ActualSize.Y;

(_bufferSize, PixelWidth, PixelHeight) = RenderAsBgra8_Premul(element, ref _buffer);
#if __WASM__ || __SKIA__
InvalidateSource();
#endif
Expand All @@ -114,12 +123,36 @@ public IAsyncAction RenderAsync(UIElement element)
public IAsyncOperation<IBuffer> GetPixelsAsync()
=> AsyncOperation<IBuffer>.FromTask(async (op, ct) =>
{
return new Buffer(_buffer);
if (_buffer is null)
{
return new Buffer(Array.Empty<byte>());
}
return new Buffer(_buffer.AsMemory().Slice(0,_bufferSize));
});

#if NOT_IMPLEMENTED
private static byte[] RenderAsPng(UIElement element, Size? scaledSize = null)
private (int ByteCount,int Width, int Height) RenderAsBgra8_Premul(UIElement? element, ref byte[]? buffer, Size? scaledSize = null)
=> throw new NotImplementedException("RenderTargetBitmap is not supported on this platform.");
#endif

private static void EnsureBuffer(ref byte[]? buffer, int length)
{
if (buffer is null)
{
buffer = ArrayPool<byte>.Shared.Rent(length);
}
else if (buffer.Length < length)
{
ArrayPool<byte>.Shared.Return(buffer);
buffer = ArrayPool<byte>.Shared.Rent(length);
}
}
void IDisposable.Dispose()
{
if (_buffer is { })
{
ArrayPool<byte>.Shared.Return(_buffer);
}
}
}
}
Loading

0 comments on commit ba33ee0

Please sign in to comment.