From 4c3c9aa99fd1b755fb5024829fcbc9afdd3f04a0 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Fri, 30 Jan 2026 09:55:40 +0000 Subject: [PATCH] Add AlphaFormat to ILockedFramebuffer (#20556) * Add AlphaFormat to ILockedFramebuffer * Update API suppressions --- api/Avalonia.nupkg.xml | 72 +++++++++++++++++++ .../SkiaPlatform/AndroidFramebuffer.cs | 7 +- src/Avalonia.Base/Media/Imaging/Bitmap.cs | 14 ++-- .../Media/Imaging/WriteableBitmap.cs | 4 +- .../Platform/ILockedFramebuffer.cs | 5 +- .../Platform/IReadableBitmapImpl.cs | 11 +-- .../Platform/IWriteableBitmapImpl.cs | 4 +- .../Platform/LockedFramebuffer.cs | 4 +- .../Platform/RetainedFramebuffer.cs | 14 ++-- .../RemoteServerTopLevelImpl.Framebuffer.cs | 1 + src/Avalonia.Native/DeferredFramebuffer.cs | 1 + src/Avalonia.X11/X11CursorFactory.cs | 2 +- src/Avalonia.X11/X11FramebufferSurface.cs | 2 +- src/Avalonia.X11/X11IconLoader.cs | 2 +- .../Rendering/BrowserSoftwareRenderTarget.cs | 4 +- .../HeadlessPlatformRenderInterface.cs | 6 +- .../Avalonia.Headless/HeadlessWindowImpl.cs | 1 + .../Output/FbDevBackBuffer.cs | 11 ++- .../Avalonia.Skia/FramebufferRenderTarget.cs | 2 +- src/Skia/Avalonia.Skia/ImmutableBitmap.cs | 6 +- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 2 + .../Avalonia.Win32/FramebufferManager.cs | 2 +- .../Avalonia.Win32/Interop/Win32Icon.cs | 14 ++-- .../Composition/DirectFbCompositionTests.cs | 2 +- .../Avalonia.RenderTests/Media/BitmapTests.cs | 7 +- .../CompositorTestServices.cs | 2 +- 26 files changed, 144 insertions(+), 58 deletions(-) diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index 4319e8343e..3858eaa6ff 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -19,6 +19,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Platform.IReadableBitmapWithAlphaImpl + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 T:Avalonia.Utilities.StringTokenizer @@ -55,6 +61,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Platform.IReadableBitmapWithAlphaImpl + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0001 T:Avalonia.Utilities.StringTokenizer @@ -127,6 +139,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Media.Imaging.Bitmap.CopyPixels(Avalonia.Platform.ILockedFramebuffer,Avalonia.Platform.AlphaFormat) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Media.StreamGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection) @@ -229,6 +247,12 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Platform.LockedFramebuffer.#ctor(System.IntPtr,Avalonia.PixelSize,System.Int32,Avalonia.Vector,Avalonia.Platform.PixelFormat,System.Action) + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0002 M:Avalonia.Visuals.Platform.PathGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection) @@ -481,6 +505,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Media.Imaging.Bitmap.CopyPixels(Avalonia.Platform.ILockedFramebuffer,Avalonia.Platform.AlphaFormat) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Media.StreamGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection) @@ -583,6 +613,12 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0002 + M:Avalonia.Platform.LockedFramebuffer.#ctor(System.IntPtr,Avalonia.PixelSize,System.Int32,Avalonia.Vector,Avalonia.Platform.PixelFormat,System.Action) + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0002 M:Avalonia.Visuals.Platform.PathGeometryContext.ArcTo(Avalonia.Point,Avalonia.Size,System.Double,System.Boolean,Avalonia.Media.SweepDirection) @@ -853,6 +889,18 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0006 + P:Avalonia.Platform.ILockedFramebuffer.AlphaFormat + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0006 + P:Avalonia.Platform.IReadableBitmapImpl.AlphaFormat + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0006 M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer) @@ -1003,6 +1051,18 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0006 + P:Avalonia.Platform.ILockedFramebuffer.AlphaFormat + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0006 + P:Avalonia.Platform.IReadableBitmapImpl.AlphaFormat + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0006 M:Avalonia.OpenGL.IGlExternalSemaphore.SignalTimelineSemaphore(Avalonia.OpenGL.IGlExternalImageTexture,System.UInt64) @@ -1087,12 +1147,24 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0008 + T:Avalonia.Platform.IWriteableBitmapImpl + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0008 T:Avalonia.Media.StreamGeometryContext baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0008 + T:Avalonia.Platform.IWriteableBitmapImpl + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0009 T:Avalonia.Platform.Screen diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs index 7a0d9793a3..87710127aa 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs @@ -25,10 +25,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform Size = new PixelSize(rc.right, rc.bottom); ANativeWindow_lock(_window, &buffer, &rc); - Format = buffer.format == AndroidPixelFormat.WINDOW_FORMAT_RGB_565 - ? PixelFormat.Rgb565 : PixelFormat.Rgba8888; + (Format, AlphaFormat, RowBytes) = buffer.format == AndroidPixelFormat.WINDOW_FORMAT_RGB_565 ? + (PixelFormat.Rgb565, AlphaFormat.Opaque, buffer.stride * 2) : + (PixelFormat.Rgba8888, AlphaFormat.Premul, buffer.stride * 4); - RowBytes = buffer.stride * (Format == PixelFormat.Rgb565 ? 2 : 4); Address = buffer.bits; Dpi = new Vector(96, 96) * scaling; @@ -46,6 +46,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform public int RowBytes { get; } public Vector Dpi { get; } public PixelFormat Format { get; } + public AlphaFormat AlphaFormat { get; } [DllImport("android")] internal static extern IntPtr ANativeWindow_fromSurface(IntPtr jniEnv, IntPtr handle); diff --git a/src/Avalonia.Base/Media/Imaging/Bitmap.cs b/src/Avalonia.Base/Media/Imaging/Bitmap.cs index 2a1ce15feb..9c0c20d170 100644 --- a/src/Avalonia.Base/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/Bitmap.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.CompilerServices; using Avalonia.Platform; @@ -177,7 +176,7 @@ namespace Avalonia.Media.Imaging public virtual PixelFormat? Format => (PlatformImpl.Item as IReadableBitmapImpl)?.Format; - public virtual AlphaFormat? AlphaFormat => (PlatformImpl.Item as IReadableBitmapWithAlphaImpl)?.AlphaFormat; + public virtual AlphaFormat? AlphaFormat => (PlatformImpl.Item as IReadableBitmapImpl)?.AlphaFormat; private protected unsafe void CopyPixelsCore(PixelRect sourceRect, IntPtr buffer, int bufferSize, int stride, ILockedFramebuffer fb) @@ -237,16 +236,15 @@ namespace Avalonia.Media.Imaging /// Copies pixels to the target buffer and transcodes the pixel and alpha format if needed. /// /// The target buffer. - /// The alpha format. /// - public void CopyPixels(ILockedFramebuffer buffer, AlphaFormat alphaFormat) + public void CopyPixels(ILockedFramebuffer buffer) { - if (PlatformImpl.Item is not IReadableBitmapWithAlphaImpl readable || readable.Format == null || readable.AlphaFormat == null) + if (PlatformImpl.Item is not IReadableBitmapImpl readable || readable.Format == null || readable.AlphaFormat == null) { throw new NotSupportedException("CopyPixels is not supported for this bitmap type"); } - if (buffer.Format != readable.Format || alphaFormat != readable.AlphaFormat) + if (buffer.Format != readable.Format || buffer.AlphaFormat != readable.AlphaFormat) { using (var fb = readable.Lock()) { @@ -255,11 +253,11 @@ namespace Avalonia.Media.Imaging fb.Size, fb.RowBytes, fb.Format, - readable.AlphaFormat.Value, + fb.AlphaFormat, buffer.Address, buffer.RowBytes, buffer.Format, - alphaFormat); + buffer.AlphaFormat); } } else diff --git a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs index 435e51009d..f2b3b4d0c7 100644 --- a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs @@ -71,10 +71,10 @@ namespace Avalonia.Media.Imaging return new LockedFramebuffer(_pixelFormatMemory.Address, _pixelFormatMemory.Size, _pixelFormatMemory.RowBytes, - Dpi, _pixelFormatMemory.Format, () => + Dpi, _pixelFormatMemory.Format, _pixelFormatMemory.AlphaFormat, () => { using var inner = ((IWriteableBitmapImpl)PlatformImpl.Item).Lock(); - _pixelFormatMemory.CopyToRgba(Platform.AlphaFormat.Unpremul, inner.Address, inner.RowBytes); + _pixelFormatMemory.CopyToRgba(inner.AlphaFormat, inner.Address, inner.RowBytes); }); } diff --git a/src/Avalonia.Base/Platform/ILockedFramebuffer.cs b/src/Avalonia.Base/Platform/ILockedFramebuffer.cs index f963b77cd9..a73b339fcc 100644 --- a/src/Avalonia.Base/Platform/ILockedFramebuffer.cs +++ b/src/Avalonia.Base/Platform/ILockedFramebuffer.cs @@ -29,6 +29,9 @@ namespace Avalonia.Platform /// PixelFormat Format { get; } - //TODO12: Add AlphaFormat + /// + /// Gets the alpha format. + /// + AlphaFormat AlphaFormat { get; } } } diff --git a/src/Avalonia.Base/Platform/IReadableBitmapImpl.cs b/src/Avalonia.Base/Platform/IReadableBitmapImpl.cs index d5a0c765cc..6332d302af 100644 --- a/src/Avalonia.Base/Platform/IReadableBitmapImpl.cs +++ b/src/Avalonia.Base/Platform/IReadableBitmapImpl.cs @@ -2,15 +2,10 @@ using Avalonia.Metadata; namespace Avalonia.Platform; -public interface IReadableBitmapImpl +[PrivateApi] +public interface IReadableBitmapImpl : IBitmapImpl { PixelFormat? Format { get; } - ILockedFramebuffer Lock(); -} - -//TODO12: Remove me once we can change IReadableBitmapImpl -[Unstable] -public interface IReadableBitmapWithAlphaImpl : IReadableBitmapImpl -{ AlphaFormat? AlphaFormat { get; } + ILockedFramebuffer Lock(); } diff --git a/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs b/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs index 685491a326..185b116c9a 100644 --- a/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs +++ b/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs @@ -5,8 +5,8 @@ namespace Avalonia.Platform /// /// Defines the platform-specific interface for a . /// - [Unstable] - public interface IWriteableBitmapImpl : IBitmapImpl, IReadableBitmapWithAlphaImpl + [PrivateApi] + public interface IWriteableBitmapImpl : IBitmapImpl, IReadableBitmapImpl { } } diff --git a/src/Avalonia.Base/Platform/LockedFramebuffer.cs b/src/Avalonia.Base/Platform/LockedFramebuffer.cs index b9094d9e14..8924947662 100644 --- a/src/Avalonia.Base/Platform/LockedFramebuffer.cs +++ b/src/Avalonia.Base/Platform/LockedFramebuffer.cs @@ -7,7 +7,7 @@ namespace Avalonia.Platform private readonly Action? _onDispose; public LockedFramebuffer(IntPtr address, PixelSize size, int rowBytes, Vector dpi, PixelFormat format, - Action? onDispose) + AlphaFormat alphaFormat, Action? onDispose) { _onDispose = onDispose; Address = address; @@ -15,6 +15,7 @@ namespace Avalonia.Platform RowBytes = rowBytes; Dpi = dpi; Format = format; + AlphaFormat = alphaFormat; } public IntPtr Address { get; } @@ -22,6 +23,7 @@ namespace Avalonia.Platform public int RowBytes { get; } public Vector Dpi { get; } public PixelFormat Format { get; } + public AlphaFormat AlphaFormat { get; } public void Dispose() { diff --git a/src/Avalonia.Base/Platform/RetainedFramebuffer.cs b/src/Avalonia.Base/Platform/RetainedFramebuffer.cs index e5ca1070dc..e1983afde1 100644 --- a/src/Avalonia.Base/Platform/RetainedFramebuffer.cs +++ b/src/Avalonia.Base/Platform/RetainedFramebuffer.cs @@ -1,6 +1,4 @@ using System; -using System.Runtime.InteropServices; -using Avalonia.Metadata; using Avalonia.Platform.Internal; namespace Avalonia.Platform; @@ -10,6 +8,7 @@ internal class RetainedFramebuffer : IDisposable public PixelSize Size { get; } public int RowBytes { get; } public PixelFormat Format { get; } + public AlphaFormat AlphaFormat { get; } public IntPtr Address => _blob?.Address ?? throw new ObjectDisposedException(nameof(RetainedFramebuffer)); private UnmanagedBlob? _blob; @@ -17,13 +16,13 @@ internal class RetainedFramebuffer : IDisposable ? format : throw new ArgumentOutOfRangeException(nameof(format)); - public RetainedFramebuffer(PixelSize size, PixelFormat format) : this(size, ValidateKnownFormat(format), - format.BitsPerPixel / 8 * size.Width) + public RetainedFramebuffer(PixelSize size, PixelFormat format, AlphaFormat alphaFormat) + : this(size, ValidateKnownFormat(format), alphaFormat, format.BitsPerPixel / 8 * size.Width) { } - public RetainedFramebuffer(PixelSize size, PixelFormat format, int rowBytes) + public RetainedFramebuffer(PixelSize size, PixelFormat format, AlphaFormat alphaFormat, int rowBytes) { if (size.Width <= 0 || size.Height <= 0) throw new ArgumentOutOfRangeException(nameof(size)); @@ -32,6 +31,7 @@ internal class RetainedFramebuffer : IDisposable Size = size; RowBytes = rowBytes; Format = format; + AlphaFormat = alphaFormat; _blob = new UnmanagedBlob(RowBytes * size.Height); } @@ -39,7 +39,7 @@ internal class RetainedFramebuffer : IDisposable { if (_blob == null) throw new ObjectDisposedException(nameof(RetainedFramebuffer)); - return new LockedFramebuffer(_blob.Address, Size, RowBytes, dpi, Format, () => + return new LockedFramebuffer(_blob.Address, Size, RowBytes, dpi, Format, AlphaFormat, () => { blit(this); GC.KeepAlive(this); @@ -51,4 +51,4 @@ internal class RetainedFramebuffer : IDisposable _blob?.Dispose(); _blob = null; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs index 593adfb225..cc7d7608ff 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs @@ -76,6 +76,7 @@ namespace Avalonia.Controls.Remote.Server Stride, new Vector(_dpi, _dpi), new PlatformPixelFormat((PixelFormatEnum)Format), + Format == ProtocolPixelFormat.Rgb565 ? AlphaFormat.Opaque : AlphaFormat.Premul, () => { handle.Free(); diff --git a/src/Avalonia.Native/DeferredFramebuffer.cs b/src/Avalonia.Native/DeferredFramebuffer.cs index fd3f3ce676..a352fc2e8b 100644 --- a/src/Avalonia.Native/DeferredFramebuffer.cs +++ b/src/Avalonia.Native/DeferredFramebuffer.cs @@ -29,6 +29,7 @@ namespace Avalonia.Native public int RowBytes { get; set; } public Vector Dpi { get; set; } public PixelFormat Format { get; set; } + public AlphaFormat AlphaFormat { get; set; } public void Dispose() { diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index 2af06960bc..c6be968bfd 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -133,7 +133,7 @@ namespace Avalonia.X11 return new LockedFramebuffer( _blob.Address + Marshal.SizeOf(), _pixelSize, _pixelSize.Width * 4, - new Vector(96, 96), PixelFormat.Bgra8888, null); + new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul, null); } public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock); diff --git a/src/Avalonia.X11/X11FramebufferSurface.cs b/src/Avalonia.X11/X11FramebufferSurface.cs index 5b01ce91de..4efd7f072d 100644 --- a/src/Avalonia.X11/X11FramebufferSurface.cs +++ b/src/Avalonia.X11/X11FramebufferSurface.cs @@ -61,7 +61,7 @@ namespace Avalonia.X11 { _fb?.Dispose(); _fb = null; - _fb = new RetainedFramebuffer(new PixelSize(width, height), PixelFormat.Bgra8888); + _fb = new RetainedFramebuffer(new PixelSize(width, height), PixelFormat.Bgra8888, AlphaFormat.Premul); } properties = new FramebufferLockProperties(framebufferValid); diff --git a/src/Avalonia.X11/X11IconLoader.cs b/src/Avalonia.X11/X11IconLoader.cs index bde6de6614..1cfe009062 100644 --- a/src/Avalonia.X11/X11IconLoader.cs +++ b/src/Avalonia.X11/X11IconLoader.cs @@ -83,7 +83,7 @@ namespace Avalonia.X11 { var h = GCHandle.Alloc(_bdata, GCHandleType.Pinned); return new LockedFramebuffer(h.AddrOfPinnedObject(), new PixelSize(_width, _height), _width * 4, - new Vector(96, 96), PixelFormat.Bgra8888, + new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul, () => h.Free()); } diff --git a/src/Browser/Avalonia.Browser/Rendering/BrowserSoftwareRenderTarget.cs b/src/Browser/Avalonia.Browser/Rendering/BrowserSoftwareRenderTarget.cs index b3395e87d9..6a0fb61f70 100644 --- a/src/Browser/Avalonia.Browser/Rendering/BrowserSoftwareRenderTarget.cs +++ b/src/Browser/Avalonia.Browser/Rendering/BrowserSoftwareRenderTarget.cs @@ -47,7 +47,7 @@ partial class BrowserSoftwareRenderTarget : BrowserRenderTarget, IFramebufferPla { _fb?.Dispose(); _fb = null; - _fb = new RetainedFramebuffer(size, PixelFormat.Rgba8888); + _fb = new RetainedFramebuffer(size, PixelFormat.Rgba8888, AlphaFormat.Premul); } return _fb.Lock(new Vector(scaling * 96, scaling * 96), _parent._blit); @@ -66,4 +66,4 @@ partial class BrowserSoftwareRenderTarget : BrowserRenderTarget, IFramebufferPla { PutPixelData(Js, fb.Address.ToInt32(), fb.Size.Width * fb.Size.Height * 4, fb.Size.Width, fb.Size.Height); } -} \ No newline at end of file +} diff --git a/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 0a29b9a5b8..05d835c57c 100644 --- a/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -414,8 +414,8 @@ namespace Avalonia.Headless public Vector Dpi { get; } public PixelSize PixelSize { get; } - public PixelFormat? Format { get; } - public AlphaFormat? AlphaFormat { get; } + public PixelFormat? Format => PixelFormat.Rgba8888; + public AlphaFormat? AlphaFormat => Platform.AlphaFormat.Premul; public int Version { get; set; } public void Save(string fileName, int? quality = null) @@ -434,7 +434,7 @@ namespace Avalonia.Headless Version++; var mem = Marshal.AllocHGlobal(PixelSize.Width * PixelSize.Height * 4); return new LockedFramebuffer(mem, PixelSize, PixelSize.Width * 4, Dpi, PixelFormat.Rgba8888, - () => Marshal.FreeHGlobal(mem)); + Platform.AlphaFormat.Premul, () => Marshal.FreeHGlobal(mem)); } } diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index e4138bee66..8f80e22138 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -200,6 +200,7 @@ namespace Avalonia.Headless public int RowBytes => _fb.RowBytes; public Vector Dpi => _fb.Dpi; public PixelFormat Format => _fb.Format; + public AlphaFormat AlphaFormat => _fb.AlphaFormat; } public ILockedFramebuffer Lock() diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs index 97b730c160..f9df24ed69 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs @@ -101,12 +101,17 @@ namespace Avalonia.LinuxFramebuffer.Output public static LockedFramebuffer LockFb(IntPtr address, fb_var_screeninfo varInfo, fb_fix_screeninfo fixedInfo, Vector dpi, Action? dispose) { + var (format, alphaFormat) = varInfo switch + { + { bits_per_pixel: 16 } => (PixelFormat.Rgb565, AlphaFormat.Opaque), + { bits_per_pixel: 32, blue.offset: 16 } => (PixelFormat.Rgba8888, AlphaFormat.Premul), + _ => (PixelFormat.Bgra8888, AlphaFormat.Premul) + }; + return new LockedFramebuffer(address, new PixelSize((int)varInfo.xres, (int)varInfo.yres), (int)fixedInfo.line_length, dpi, - varInfo.bits_per_pixel == 16 ? PixelFormat.Rgb565 - : varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 - : PixelFormat.Bgra8888, dispose); + format, alphaFormat, dispose); } private void BlitToDevice() diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index 318cdac22c..46be609ec0 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -67,7 +67,7 @@ namespace Avalonia.Skia var framebuffer = _renderTargetWithProperties?.Lock(out lockProperties) ?? _renderTarget.Lock(); var framebufferImageInfo = new SKImageInfo(framebuffer.Size.Width, framebuffer.Size.Height, framebuffer.Format.ToSkColorType(), - framebuffer.Format == PixelFormat.Rgb565 ? SKAlphaType.Opaque : SKAlphaType.Premul); + framebuffer.AlphaFormat.ToSkAlphaType()); CreateSurface(framebufferImageInfo, framebuffer); _hadConversionShim |= _conversionShim != null; diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index 0372f25047..44408a5333 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -10,7 +10,7 @@ namespace Avalonia.Skia /// /// Immutable Skia bitmap. /// - internal class ImmutableBitmap : IDrawableBitmapImpl, IReadableBitmapWithAlphaImpl + internal class ImmutableBitmap : IDrawableBitmapImpl, IReadableBitmapImpl { private readonly SKImage _image; private readonly SKBitmap? _bitmap; @@ -195,7 +195,9 @@ namespace Avalonia.Skia if (_bitmap.ColorType.ToAvalonia() is not { } format) throw new NotSupportedException($"Unsupported format {_bitmap.ColorType}"); - return new LockedFramebuffer(_bitmap.GetPixels(), PixelSize, _bitmap.RowBytes, Dpi, format, null); + var alphaFormat = _bitmap.AlphaType.ToAlphaFormat(); + + return new LockedFramebuffer(_bitmap.GetPixels(), PixelSize, _bitmap.RowBytes, Dpi, format, alphaFormat, null); } } } diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 7d8b247ad4..f57f84b168 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -238,6 +238,8 @@ namespace Avalonia.Skia public Vector Dpi => _parent.Dpi; /// public PixelFormat Format => _bitmap.ColorType.ToPixelFormat(); + + public AlphaFormat AlphaFormat => _bitmap.AlphaType.ToAlphaFormat(); } } } diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs index 5c8e84aaa6..7c11277c57 100644 --- a/src/Windows/Avalonia.Win32/FramebufferManager.cs +++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs @@ -49,7 +49,7 @@ namespace Avalonia.Win32 return fb = new LockedFramebuffer( framebufferData.Data.Address, framebufferData.Size, framebufferData.RowBytes, - GetCurrentDpi(), s_format, _onDisposeAction); + GetCurrentDpi(), s_format, AlphaFormat.Premul, _onDisposeAction); } finally { diff --git a/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs b/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs index 57da43da92..1fe301526b 100644 --- a/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs +++ b/src/Windows/Avalonia.Win32/Interop/Win32Icon.cs @@ -96,14 +96,14 @@ internal class Win32Icon : IDisposable static IntPtr CreateHBitmap(Bitmap source) { - using var fb = AllocFramebuffer(source.PixelSize, PixelFormats.Bgra8888); - source.CopyPixels(fb, AlphaFormat.Unpremul); + using var fb = AllocFramebuffer(source.PixelSize, PixelFormats.Bgra8888, AlphaFormat.Unpremul); + source.CopyPixels(fb); return UnmanagedMethods.CreateBitmap(source.PixelSize.Width, source.PixelSize.Height, 1, 32, fb.Address); } static unsafe IntPtr AlphaToMask(Bitmap source) { - using var alphaMaskBuffer = AllocFramebuffer(source.PixelSize, PixelFormats.BlackWhite); + using var alphaMaskBuffer = AllocFramebuffer(source.PixelSize, PixelFormats.BlackWhite, AlphaFormat.Opaque); var height = alphaMaskBuffer.Size.Height; var width = alphaMaskBuffer.Size.Width; @@ -114,8 +114,8 @@ internal class Win32Icon : IDisposable } else { - using var argbBuffer = AllocFramebuffer(source.PixelSize, PixelFormat.Bgra8888); - source.CopyPixels(argbBuffer, AlphaFormat.Unpremul); + using var argbBuffer = AllocFramebuffer(source.PixelSize, PixelFormat.Bgra8888, AlphaFormat.Unpremul); + source.CopyPixels(argbBuffer); var pSource = (byte*)argbBuffer.Address; var pDest = (byte*)alphaMaskBuffer.Address; @@ -140,7 +140,7 @@ internal class Win32Icon : IDisposable return UnmanagedMethods.CreateBitmap(width, height, 1, 1, alphaMaskBuffer.Address); } - static LockedFramebuffer AllocFramebuffer(PixelSize size, PixelFormat format) + static LockedFramebuffer AllocFramebuffer(PixelSize size, PixelFormat format, AlphaFormat alphaFormat) { if (size.Width < 1 || size.Height < 1) throw new ArgumentOutOfRangeException(); @@ -149,7 +149,7 @@ internal class Win32Icon : IDisposable var data = Marshal.AllocHGlobal(size.Height * stride); if (data == IntPtr.Zero) throw new OutOfMemoryException(); - return new LockedFramebuffer(data, size, stride, new Vector(96, 96), format, + return new LockedFramebuffer(data, size, stride, new Vector(96, 96), format, alphaFormat, () => Marshal.FreeHGlobal(data)); } diff --git a/tests/Avalonia.RenderTests/Composition/DirectFbCompositionTests.cs b/tests/Avalonia.RenderTests/Composition/DirectFbCompositionTests.cs index 1a8d7d9272..3a48bafaad 100644 --- a/tests/Avalonia.RenderTests/Composition/DirectFbCompositionTests.cs +++ b/tests/Avalonia.RenderTests/Composition/DirectFbCompositionTests.cs @@ -83,7 +83,7 @@ public class DirectFbCompositionTests : TestBase SKBitmap fb = new SKBitmap(200, 200, SKColorType.Rgba8888, SKAlphaType.Premul); ILockedFramebuffer LockFb() => new LockedFramebuffer(fb.GetAddress(0, 0), new(fb.Width, fb.Height), - fb.RowBytes, new Vector(96, 96), PixelFormat.Rgba8888, null); + fb.RowBytes, new Vector(96, 96), PixelFormat.Rgba8888, AlphaFormat.Premul, null); bool previousFrameIsRetained = false; IFramebufferRenderTarget rt = advertised diff --git a/tests/Avalonia.RenderTests/Media/BitmapTests.cs b/tests/Avalonia.RenderTests/Media/BitmapTests.cs index 0f849a0400..54e336a6f4 100644 --- a/tests/Avalonia.RenderTests/Media/BitmapTests.cs +++ b/tests/Avalonia.RenderTests/Media/BitmapTests.cs @@ -25,10 +25,11 @@ namespace Avalonia.Skia.RenderTests class Framebuffer : ILockedFramebuffer, IFramebufferPlatformSurface { - public Framebuffer(PixelFormat fmt, PixelSize size) + public Framebuffer(PixelFormat fmt, AlphaFormat alphaFormat, PixelSize size) { Format = fmt; var bpp = fmt == PixelFormat.Rgb565 ? 2 : 4; + AlphaFormat = alphaFormat; Size = size; RowBytes = bpp * size.Width; Address = Marshal.AllocHGlobal(size.Height * RowBytes); @@ -40,6 +41,8 @@ namespace Avalonia.Skia.RenderTests public PixelFormat Format { get; } + public AlphaFormat AlphaFormat { get; } + public PixelSize Size { get; } public int RowBytes { get; } @@ -64,7 +67,7 @@ namespace Avalonia.Skia.RenderTests { var fmt = new PixelFormat(fmte); var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt; - var fb = new Framebuffer(fmt, new PixelSize(80, 80)); + var fb = new Framebuffer(fmt, AlphaFormat.Premul, new PixelSize(80, 80)); var r = AvaloniaLocator.Current.GetRequiredService(); using(var cpuContext = r.CreateBackendContext(null)) using (var target = cpuContext.CreateRenderTarget(new object[] { fb })) diff --git a/tests/Avalonia.UnitTests/CompositorTestServices.cs b/tests/Avalonia.UnitTests/CompositorTestServices.cs index ec481328b7..96b3248ed1 100644 --- a/tests/Avalonia.UnitTests/CompositorTestServices.cs +++ b/tests/Avalonia.UnitTests/CompositorTestServices.cs @@ -174,7 +174,7 @@ public class CompositorTestServices : IDisposable { var ptr = Marshal.AllocHGlobal(128); return new LockedFramebuffer(ptr, new PixelSize(1, 1), 4, new Vector(96, 96), - PixelFormat.Rgba8888, () => Marshal.FreeHGlobal(ptr)); + PixelFormat.Rgba8888, AlphaFormat.Premul, () => Marshal.FreeHGlobal(ptr)); } public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);