From 9623669c41e23640a22de2c16fa76ba5b64e1992 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 17 Dec 2025 11:36:14 +0000 Subject: [PATCH] Win32 - export bitmap in more clipboard formats (#20209) * win32 - export bitmap in more clipboard formats * addressed review * use compatible bitmap for ole gdi copy * change bitmap intent to colormetric --------- Co-authored-by: Julien Lebosquain --- .../Avalonia.Win32/ClipboardFormatRegistry.cs | 26 +- .../DataTransferToOleDataObjectWrapper.cs | 38 ++- .../Interop/UnmanagedMethods.cs | 100 +++++++- .../Avalonia.Win32/OleDataObjectHelper.cs | 230 ++++++++++++++++-- .../OleDataObjectToDataTransferWrapper.cs | 10 +- 5 files changed, 356 insertions(+), 48 deletions(-) diff --git a/src/Windows/Avalonia.Win32/ClipboardFormatRegistry.cs b/src/Windows/Avalonia.Win32/ClipboardFormatRegistry.cs index 0d9430e19c..ace769b383 100644 --- a/src/Windows/Avalonia.Win32/ClipboardFormatRegistry.cs +++ b/src/Windows/Avalonia.Win32/ClipboardFormatRegistry.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using Avalonia.Input; +using Avalonia.Media.Imaging; using Avalonia.Utilities; using Avalonia.Win32.Interop; @@ -12,17 +13,28 @@ namespace Avalonia.Win32 private const int MaxFormatNameLength = 260; private const string AppPrefix = "avn-app-fmt:"; public const string PngFormatMimeType = "image/png"; - public const string JpegFormatMimeType = "image/jpeg"; + public const string PngFormatSystemType = "PNG"; public const string BitmapFormat = "CF_BITMAP"; public const string DibFormat = "CF_DIB"; + public const string DibV5Format = "CF_DIBV5"; private static readonly List<(DataFormat Format, ushort Id)> s_formats = []; + public static DataFormat PngSystemDataFormat = DataFormat.FromSystemName(PngFormatSystemType, AppPrefix); + public static DataFormat PngMimeDataFormat = DataFormat.FromSystemName(PngFormatMimeType, AppPrefix); + public static DataFormat HBitmapDataFormat = DataFormat.FromSystemName(BitmapFormat, AppPrefix); + public static DataFormat DibDataFormat = DataFormat.FromSystemName(DibFormat, AppPrefix); + public static DataFormat DibV5DataFormat = DataFormat.FromSystemName(DibV5Format, AppPrefix); + + public static DataFormat[] ImageFormats = [PngMimeDataFormat, PngSystemDataFormat, DibDataFormat, DibV5DataFormat, HBitmapDataFormat]; + static ClipboardFormatRegistry() { AddDataFormat(DataFormat.Text, (ushort)UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT); AddDataFormat(DataFormat.Text, (ushort)UnmanagedMethods.ClipboardFormat.CF_TEXT); AddDataFormat(DataFormat.File, (ushort)UnmanagedMethods.ClipboardFormat.CF_HDROP); - AddDataFormat(DataFormat.Bitmap, (ushort)UnmanagedMethods.ClipboardFormat.CF_DIB); + AddDataFormat(DibDataFormat, (ushort)UnmanagedMethods.ClipboardFormat.CF_DIB); + AddDataFormat(DibV5DataFormat, (ushort)UnmanagedMethods.ClipboardFormat.CF_DIBV5); + AddDataFormat(HBitmapDataFormat, (ushort)UnmanagedMethods.ClipboardFormat.CF_BITMAP); } private static void AddDataFormat(DataFormat format, ushort id) @@ -62,24 +74,18 @@ namespace Avalonia.Win32 if (DataFormat.Bitmap.Equals(format)) { (DataFormat, ushort)? pngFormat = null; - (DataFormat, ushort)? jpgFormat = null; (DataFormat, ushort)? dibFormat = null; - (DataFormat, ushort)? bitFormat = null; foreach (var currentFormat in s_formats) { if (currentFormat.Id == (ushort)UnmanagedMethods.ClipboardFormat.CF_DIB) dibFormat = currentFormat; - else if (currentFormat.Id == (ushort)UnmanagedMethods.ClipboardFormat.CF_BITMAP) - bitFormat = currentFormat; else if (currentFormat.Format.Identifier == PngFormatMimeType) pngFormat = currentFormat; - else if (currentFormat.Format.Identifier == JpegFormatMimeType) - jpgFormat = currentFormat; } - var imageFormatId = dibFormat?.Item2 ?? bitFormat?.Item2 ?? pngFormat?.Item2 ?? jpgFormat?.Item2 ?? 0; + var imageFormatId = pngFormat?.Item2 ?? dibFormat?.Item2 ?? 0; - if(imageFormatId != 0) + if (imageFormatId != 0) { return imageFormatId; } diff --git a/src/Windows/Avalonia.Win32/DataTransferToOleDataObjectWrapper.cs b/src/Windows/Avalonia.Win32/DataTransferToOleDataObjectWrapper.cs index c2769157c1..672899cb9f 100644 --- a/src/Windows/Avalonia.Win32/DataTransferToOleDataObjectWrapper.cs +++ b/src/Windows/Avalonia.Win32/DataTransferToOleDataObjectWrapper.cs @@ -6,7 +6,6 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using Avalonia.Input; using Avalonia.MicroCom; -using Avalonia.Platform.Storage; using static Avalonia.Win32.Interop.UnmanagedMethods; using FORMATETC = Avalonia.Win32.Interop.UnmanagedMethods.FORMATETC; using STGMEDIUM = Avalonia.Win32.Interop.UnmanagedMethods.STGMEDIUM; @@ -33,7 +32,23 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer) public FormatEnumerator(IReadOnlyList dataFormats) { - _formats = dataFormats.Select(OleDataObjectHelper.ToFormatEtc).ToArray(); + List formats = new List(); + + if (dataFormats.Contains(DataFormat.Bitmap)) + { + // We add extra formats for bitmaps + formats.Add(OleDataObjectHelper.ToFormatEtc(ClipboardFormatRegistry.PngMimeDataFormat)); + formats.Add(OleDataObjectHelper.ToFormatEtc(ClipboardFormatRegistry.PngSystemDataFormat)); + formats.Add(OleDataObjectHelper.ToFormatEtc(ClipboardFormatRegistry.DibDataFormat)); + formats.Add(OleDataObjectHelper.ToFormatEtc(ClipboardFormatRegistry.DibV5DataFormat)); + formats.Add(OleDataObjectHelper.ToFormatEtc(ClipboardFormatRegistry.HBitmapDataFormat, true)); + } + else + { + formats.AddRange(dataFormats.Select(x => OleDataObjectHelper.ToFormatEtc(x))); + } + + _formats = formats.ToArray(); _current = 0; } @@ -113,9 +128,18 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer) if (!ValidateFormat(format, out var result, out var dataFormat)) return result; - *medium = default; - medium->tymed = TYMED.TYMED_HGLOBAL; - return OleDataObjectHelper.WriteDataToHGlobal(DataTransfer, dataFormat, ref medium->unionmember); + if (format->tymed == TYMED.TYMED_GDI) + { + *medium = default; + medium->tymed = TYMED.TYMED_GDI; + return OleDataObjectHelper.WriteDataToGdi(DataTransfer, dataFormat, ref medium->unionmember); + } + else + { + *medium = default; + medium->tymed = TYMED.TYMED_HGLOBAL; + return OleDataObjectHelper.WriteDataToHGlobal(DataTransfer, dataFormat, ref medium->unionmember); + } } unsafe uint Win32Com.IDataObject.GetDataHere(FORMATETC* format, STGMEDIUM* medium) @@ -142,7 +166,7 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer) { dataFormat = null; - if (!format->tymed.HasAllFlags(TYMED.TYMED_HGLOBAL)) + if (!(format->tymed.HasAllFlags(TYMED.TYMED_HGLOBAL) || format->cfFormat == (ushort)ClipboardFormat.CF_BITMAP && format->tymed == TYMED.TYMED_GDI)) { result = DV_E_TYMED; dataFormat = null; @@ -162,7 +186,7 @@ internal class DataTransferToOleDataObjectWrapper(IDataTransfer dataTransfer) } dataFormat = ClipboardFormatRegistry.GetFormatById(format->cfFormat); - if (!DataTransfer.Contains(dataFormat)) + if (!DataTransfer.Contains(dataFormat) && !ClipboardFormatRegistry.ImageFormats.Contains(dataFormat)) { result = DV_E_FORMATETC; return false; diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index f2f28bbc78..bb2bf7f2d2 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -895,6 +895,23 @@ namespace Avalonia.Win32.Interop BI_PNG = 5 } + public enum BitmapColorSpace : uint + { + LCS_CALIBRATED_RGB = 0, + LCS_sRGB = 0x73524742, + LCS_WINDOWS_COLOR_SPACE = 0x57696E20, + PROFILE_LINKED = 0x4C494E4B, + PROFILE_EMBEDDED = 0x4D424544 + } + + public enum BitmapIntent : uint + { + LCS_GM_ABS_COLORIMETRIC = 1, + LCS_GM_BUSINESS = 2, + LCS_GM_GRAPHICS = 4, + LCS_GM_IMAGES = 8, + } + public enum DIBColorTable { DIB_RGB_COLORS = 0, /* color table in RGBs */ @@ -1101,7 +1118,7 @@ namespace Avalonia.Win32.Interop public int biHeight; public ushort biPlanes; public ushort biBitCount; - public uint biCompression; + public BitmapCompressionMode biCompression; public uint biSizeImage; public int biXPelsPerMeter; public int biYPelsPerMeter; @@ -1114,6 +1131,56 @@ namespace Avalonia.Win32.Interop } } + [StructLayout(LayoutKind.Sequential)] + public struct BITMAPV5HEADER + { + public uint bV5Size; + public int bV5Width; + public int bV5Height; + public ushort bV5Planes; + public ushort bV5BitCount; + public BitmapCompressionMode bV5Compression; + public uint bV5SizeImage; + public int bV5XPelsPerMeter; + public int bV5YPelsPerMeter; + public uint bV5ClrUsed; + public uint bV5ClrImportant; + public uint bV5RedMask; + public uint bV5GreenMask; + public uint bV5BlueMask; + public uint bV5AlphaMask; + public BitmapColorSpace bV5CSType; + public CIEXYZTRIPLE bV5Endpoints; + public uint bV5GammaRed; + public uint bV5GammaGreen; + public uint bV5GammaBlue; + public BitmapIntent bV5Intent; + public uint bV5ProfileData; + public uint bV5ProfileSize; + public uint bV5Reserved; + + public void Init() + { + bV5Size = (uint)sizeof(BITMAPV5HEADER); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CIEXYZTRIPLE + { + public CIEXYZ ciexyzRed; + public CIEXYZ ciexyzGreen; + public CIEXYZ ciexyzBlue; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct CIEXYZ + { + public int ciexyzX; + public int ciexyzY; + public int ciexyzZ; + } + [StructLayout(LayoutKind.Sequential)] public struct BITMAPINFO { @@ -1134,9 +1201,10 @@ namespace Avalonia.Win32.Interop public uint biClrUsed; public uint biClrImportant; //} + public uint biRedMask; + public uint biGreenMask; + public uint biBlueMask; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] - public uint[] cols; } [StructLayout(LayoutKind.Sequential)] @@ -1208,6 +1276,15 @@ namespace Avalonia.Win32.Interop uint uStartScan, uint cScanLines, IntPtr lpvBits, [In] ref BITMAPINFO lpbmi, uint fuColorUse); + [DllImport("gdi32.dll")] + public static extern int SetDIBits(IntPtr hdc, IntPtr hbm, uint start, uint cLines, IntPtr lpBits, in BITMAPINFO lpbmi, uint fuColorUse); + + [DllImport("gdi32.dll")] + public static extern int SetDIBits(IntPtr hdc, IntPtr hbm, uint start, uint cLines, IntPtr lpBits, in BITMAPINFOHEADER lpbmi, uint fuColorUse); + + [DllImport("gdi32.dll")] + public static extern int SetDIBits(IntPtr hdc, IntPtr hbm, uint start, uint cLines, IntPtr lpBits, in BITMAPV5HEADER lpbmi, uint fuColorUse); + [DllImport("gdi32.dll", SetLastError = false, ExactSpelling = true)] public static extern IntPtr CreateRectRgn(int x1, int y1, int x2, int y2); @@ -1697,13 +1774,25 @@ namespace Avalonia.Win32.Interop [DllImport("gdi32.dll")] public static extern int SetDIBitsToDevice(IntPtr hdc, int XDest, int YDest, uint dwWidth, uint dwHeight, int XSrc, int YSrc, uint uStartScan, uint cScanLines, - IntPtr lpvBits, [In] ref BITMAPINFOHEADER lpbmi, uint fuColorUse); + IntPtr lpvBits, [In] ref BITMAPINFOHEADER lpbmi, DIBColorTable fuColorUse); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CloseHandle(IntPtr hObject); [DllImport("gdi32.dll")] - public static extern IntPtr CreateDIBSection(IntPtr hDC, ref BITMAPINFOHEADER pBitmapInfo, int un, out IntPtr lplpVoid, IntPtr handle, int dw); + public static extern IntPtr CreateDIBSection(IntPtr hDC, ref BITMAPINFOHEADER pBitmapInfo, DIBColorTable usage, out IntPtr lplpVoid, IntPtr handle, int dw); + [DllImport("gdi32.dll")] + public static extern IntPtr CreateDIBSection(IntPtr hDC, in BITMAPV5HEADER pBitmapInfo, DIBColorTable usage, out IntPtr lplpVoid, IntPtr handle, int dw); + [DllImport("gdi32.dll")] + public static extern IntPtr CreateDIBitmap(IntPtr hDC, in BITMAPV5HEADER pBitmapInfo, int flInit, IntPtr lplpVoid, in BITMAPINFO pbmi, DIBColorTable iUsage); + [DllImport("gdi32.dll")] + public static extern bool GdiFlush(); + + [DllImport("gdi32.dll")] + public static extern bool BitBlt(IntPtr hDC, int x, int y, int cx, int cy, IntPtr hdcSrc, int x1, int y1, uint rop); + + [DllImport("gdi32.dll")] + public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int cx, int cy); [DllImport("gdi32.dll")] public static extern IntPtr CreateBitmap(int width, int height, int planes, int bitCount, IntPtr data); [DllImport("gdi32.dll")] @@ -2230,6 +2319,7 @@ namespace Avalonia.Win32.Interop /// A handle to type HDROP that identifies a list of files. /// CF_HDROP = 15, + CF_DIBV5 = 17, } public struct MSG diff --git a/src/Windows/Avalonia.Win32/OleDataObjectHelper.cs b/src/Windows/Avalonia.Win32/OleDataObjectHelper.cs index cdc3f8ce3e..73edab2d02 100644 --- a/src/Windows/Avalonia.Win32/OleDataObjectHelper.cs +++ b/src/Windows/Avalonia.Win32/OleDataObjectHelper.cs @@ -7,10 +7,10 @@ using System.Runtime.InteropServices.ComTypes; using Avalonia.Input; using Avalonia.Logging; using Avalonia.Media.Imaging; +using Avalonia.Platform; using Avalonia.Platform.Storage; using Avalonia.Platform.Storage.FileIO; using Avalonia.Utilities; -using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; using FORMATETC = Avalonia.Win32.Interop.UnmanagedMethods.FORMATETC; using STGMEDIUM = Avalonia.Win32.Interop.UnmanagedMethods.STGMEDIUM; @@ -24,14 +24,14 @@ internal static class OleDataObjectHelper { private const int SRCCOPY = 0x00CC0020; - public static FORMATETC ToFormatEtc(this DataFormat format) + public static FORMATETC ToFormatEtc(this DataFormat format, bool isGdi = false) => new() { cfFormat = ClipboardFormatRegistry.GetFormatId(format), dwAspect = DVASPECT.DVASPECT_CONTENT, ptd = IntPtr.Zero, lindex = -1, - tymed = TYMED.TYMED_HGLOBAL + tymed = isGdi ? TYMED.TYMED_GDI : TYMED.TYMED_HGLOBAL }; public static unsafe object? TryGet(this Win32Com.IDataObject oleDataObject, DataFormat format) @@ -327,42 +327,91 @@ internal static class OleDataObjectHelper return WriteFileNamesToHGlobal(ref hGlobal, fileNames); } - if (DataFormat.Bitmap.Equals(format)) + if (ClipboardFormatRegistry.DibDataFormat.Equals(format) + || ClipboardFormatRegistry.DibV5DataFormat.Equals(format)) { var bitmap = dataTransfer.TryGetValue(DataFormat.Bitmap); if (bitmap != null) { + bool isV5 = ClipboardFormatRegistry.DibV5DataFormat.Equals(format); var pixelSize = bitmap.PixelSize; + var bpp = bitmap.Format?.BitsPerPixel ?? 0; var stride = ((bitmap.Format?.BitsPerPixel ?? 0) / 8) * pixelSize.Width; var buffer = new byte[stride * pixelSize.Height]; fixed (byte* bytes = buffer) { bitmap.CopyPixels(new PixelRect(pixelSize), (IntPtr)bytes, buffer.Length, stride); - var infoHeader = new BITMAPINFOHEADER() + if (!isV5) { - biSizeImage = (uint)buffer.Length, - biWidth = pixelSize.Width, - biHeight = -pixelSize.Height, - biBitCount = (ushort)(bitmap.Format?.BitsPerPixel ?? 0), - biPlanes = 1, - biCompression = (uint)BitmapCompressionMode.BI_RGB - }; - infoHeader.Init(); - - var imageData = new byte[infoHeader.biSize + infoHeader.biSizeImage]; - - fixed (byte* image = imageData) + var infoHeader = new BITMAPINFOHEADER() + { + biSizeImage = (uint)buffer.Length, + biWidth = pixelSize.Width, + biHeight = -pixelSize.Height, + biBitCount = (ushort)bpp, + biPlanes = 1, + biCompression = BitmapCompressionMode.BI_RGB, + }; + infoHeader.Init(); + + var imageData = new byte[infoHeader.biSize + infoHeader.biSizeImage]; + + fixed (byte* image = imageData) + { + Marshal.StructureToPtr(infoHeader, (IntPtr)image, false); + new Span(bytes, buffer.Length).CopyTo(new Span((image + infoHeader.biSize), buffer.Length)); + + return WriteBytesToHGlobal(ref hGlobal, imageData); + } + } + else { - Marshal.StructureToPtr(infoHeader, (IntPtr)image, false); - new Span(bytes, buffer.Length).CopyTo(new Span((image + infoHeader.biSize), buffer.Length)); - - return WriteBytesToHGlobal(ref hGlobal, imageData); + var infoHeader = new BITMAPV5HEADER() + { + bV5Width = pixelSize.Width, + bV5Height = -pixelSize.Height, + bV5Planes = 1, + bV5BitCount = (ushort)bpp, + bV5Compression = bpp > 16 ? BitmapCompressionMode.BI_BITFIELDS : BitmapCompressionMode.BI_RGB, + bV5SizeImage = (uint)buffer.Length, + bV5RedMask = GetRedMask(bitmap), + bV5BlueMask = GetBlueMask(bitmap), + bV5GreenMask = GetGreenMask(bitmap), + bV5AlphaMask = GetAlphaMask(bitmap), + bV5CSType = BitmapColorSpace.LCS_sRGB, + bV5Intent = BitmapIntent.LCS_GM_ABS_COLORIMETRIC + }; + infoHeader.Init(); + + var imageData = new byte[infoHeader.bV5Size + infoHeader.bV5SizeImage]; + + fixed (byte* image = imageData) + { + Marshal.StructureToPtr(infoHeader, (IntPtr)image, false); + new Span(bytes, buffer.Length).CopyTo(new Span((image + infoHeader.bV5Size), buffer.Length)); + + return WriteBytesToHGlobal(ref hGlobal, imageData); + } } } } } + if (ClipboardFormatRegistry.PngSystemDataFormat.Equals(format) + || ClipboardFormatRegistry.PngMimeDataFormat.Equals(format)) + { + var bitmap = dataTransfer.TryGetValue(DataFormat.Bitmap); + if (bitmap != null) + { + using var stream = new MemoryStream(); + bitmap.Save(stream); + + return WriteBytesToHGlobal(ref hGlobal, stream.ToArray().AsSpan()); + } + return DV_E_FORMATETC; + } + if (format is DataFormat stringFormat) { return dataTransfer.TryGetValue(stringFormat) is { } stringValue ? @@ -383,6 +432,145 @@ internal static class OleDataObjectHelper return DV_E_FORMATETC; } + private static uint GetAlphaMask(Bitmap? bitmap) + { + return bitmap?.Format?.FormatEnum switch + { + PixelFormatEnum.Rgba8888 => 0xff000000, + PixelFormatEnum.Bgra8888 => 0xff000000, + PixelFormatEnum.Rgb565 => 0, + _ => throw new NotSupportedException() + }; + } + + private static uint GetGreenMask(Bitmap? bitmap) + { + return bitmap?.Format?.FormatEnum switch + { + PixelFormatEnum.Rgba8888 => 0x0000ff00, + PixelFormatEnum.Bgra8888 => 0x0000ff00, + PixelFormatEnum.Rgb565 => 0b0000011111100000, + _ => throw new NotSupportedException() + }; + } + + private static uint GetBlueMask(Bitmap? bitmap) + { + return bitmap?.Format?.FormatEnum switch + { + PixelFormatEnum.Rgba8888 => 0x00ff0000, + PixelFormatEnum.Bgra8888 => 0x000000ff, + PixelFormatEnum.Rgb565 => 0b1111100000000000, + _ => throw new NotSupportedException() + }; + } + + private static uint GetRedMask(Bitmap? bitmap) + { + return bitmap?.Format?.FormatEnum switch + { + PixelFormatEnum.Rgba8888 => 0x000000ff, + PixelFormatEnum.Bgra8888 => 0x00ff0000, + PixelFormatEnum.Rgb565 => 0b0000000000011111, + _ => throw new NotSupportedException() + }; + } + + public unsafe static uint WriteDataToGdi(IDataTransfer dataTransfer, DataFormat format, ref IntPtr hGlobalBitmap) + { + if (ClipboardFormatRegistry.HBitmapDataFormat.Equals(format)) + { + var bitmap = dataTransfer.TryGetValue(DataFormat.Bitmap); + if (bitmap != null) + { + var pixelSize = bitmap.PixelSize; + var bpp = bitmap.Format?.BitsPerPixel ?? 0; + var stride = (bpp / 8) * pixelSize.Width; + var buffer = new byte[stride * pixelSize.Height]; + fixed (byte* bytes = buffer) + { + bitmap.CopyPixels(new PixelRect(pixelSize), (IntPtr)bytes, buffer.Length, stride); + + IntPtr hdc = IntPtr.Zero, compatDc = IntPtr.Zero, desDc = IntPtr.Zero, hbitmap = IntPtr.Zero, section = IntPtr.Zero; + try + { + hdc = GetDC(IntPtr.Zero); + if (hdc == IntPtr.Zero) + return DV_E_FORMATETC; + + compatDc = CreateCompatibleDC(hdc); + if (compatDc == IntPtr.Zero) + return DV_E_FORMATETC; + + desDc = CreateCompatibleDC(hdc); + if (desDc == IntPtr.Zero) + return DV_E_FORMATETC; + + var bitmapInfoHeader = new BITMAPV5HEADER() + { + bV5Width = pixelSize.Width, + bV5Height = -pixelSize.Height, + bV5Planes = 1, + bV5BitCount = (ushort)bpp, + bV5Compression = BitmapCompressionMode.BI_BITFIELDS, + bV5SizeImage = (uint)buffer.Length, + bV5RedMask = GetRedMask(bitmap), + bV5BlueMask = GetBlueMask(bitmap), + bV5GreenMask = GetGreenMask(bitmap), + bV5AlphaMask = GetAlphaMask(bitmap), + bV5CSType = BitmapColorSpace.LCS_sRGB, + bV5Intent = BitmapIntent.LCS_GM_ABS_COLORIMETRIC, + }; + + bitmapInfoHeader.Init(); + + section = CreateDIBSection(compatDc, bitmapInfoHeader, 0, out var lbBits, IntPtr.Zero, 0); + if (section == IntPtr.Zero) + return DV_E_FORMATETC; + + SelectObject(compatDc, section); + + Marshal.Copy(buffer, 0, lbBits, buffer.Length); + + hbitmap = CreateCompatibleBitmap(desDc, pixelSize.Width, pixelSize.Height); + + SelectObject(desDc, hbitmap); + + if (!BitBlt(desDc, 0, 0, pixelSize.Width, pixelSize.Height, compatDc, 0, 0, SRCCOPY)) + { + return DV_E_FORMATETC; + } + + hGlobalBitmap = hbitmap; + + GdiFlush(); + + return (uint)HRESULT.S_OK; + } + finally + { + SelectObject(compatDc, IntPtr.Zero); + SelectObject(desDc, IntPtr.Zero); + + if (desDc != IntPtr.Zero) + ReleaseDC(IntPtr.Zero, desDc); + + if (compatDc != IntPtr.Zero) + ReleaseDC(IntPtr.Zero, compatDc); + + if (hdc != IntPtr.Zero) + ReleaseDC(IntPtr.Zero, hdc); + } + } + } + } + + Logger.TryGet(LogEventLevel.Warning, LogArea.Win32Platform) + ?.Log(null, "Unsupported gdi data format {Format}", format); + + return DV_E_FORMATETC; + } + private static unsafe uint WriteStringToHGlobal(ref IntPtr hGlobal, string data) { var requiredSize = (data.Length + 1) * sizeof(char); diff --git a/src/Windows/Avalonia.Win32/OleDataObjectToDataTransferWrapper.cs b/src/Windows/Avalonia.Win32/OleDataObjectToDataTransferWrapper.cs index f52dab1885..e3aebfbc94 100644 --- a/src/Windows/Avalonia.Win32/OleDataObjectToDataTransferWrapper.cs +++ b/src/Windows/Avalonia.Win32/OleDataObjectToDataTransferWrapper.cs @@ -32,21 +32,21 @@ internal sealed class OleDataObjectToDataTransferWrapper(Win32Com.IDataObject ol while (Next(enumFormat) is { } format) formats.Add(format); - bool hasSupportedFormat = false; + bool hasSupportedImageFormat = false; foreach (var format in formats) { if (format.Identifier is ClipboardFormatRegistry.DibFormat or ClipboardFormatRegistry.BitmapFormat - or ClipboardFormatRegistry.PngFormatMimeType - or ClipboardFormatRegistry.JpegFormatMimeType) + or ClipboardFormatRegistry.PngFormatMimeType + or ClipboardFormatRegistry.PngFormatSystemType) { - hasSupportedFormat = true; + hasSupportedImageFormat = true; break; } } - if (hasSupportedFormat) + if (hasSupportedImageFormat) { formats.Add(DataFormat.Bitmap); }