csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
719 lines
26 KiB
719 lines
26 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
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 static Avalonia.Win32.Interop.UnmanagedMethods;
|
|
using FORMATETC = Avalonia.Win32.Interop.UnmanagedMethods.FORMATETC;
|
|
using STGMEDIUM = Avalonia.Win32.Interop.UnmanagedMethods.STGMEDIUM;
|
|
|
|
namespace Avalonia.Win32;
|
|
|
|
/// <summary>
|
|
/// Contains helper methods to read from and write to an HGlobal, for <see cref="Win32Com.IDataObject"/> interop.
|
|
/// </summary>
|
|
internal static class OleDataObjectHelper
|
|
{
|
|
private const int SRCCOPY = 0x00CC0020;
|
|
|
|
public static FORMATETC ToFormatEtc(ushort formatId)
|
|
=> new()
|
|
{
|
|
cfFormat = formatId,
|
|
dwAspect = DVASPECT.DVASPECT_CONTENT,
|
|
ptd = IntPtr.Zero,
|
|
lindex = -1,
|
|
tymed = formatId == (ushort)ClipboardFormat.CF_BITMAP ? TYMED.TYMED_GDI : TYMED.TYMED_HGLOBAL
|
|
};
|
|
|
|
public static unsafe object? TryGet(this Win32Com.IDataObject oleDataObject, DataFormat format)
|
|
{
|
|
if (TryGetContainedFormat(oleDataObject, format) is not { } formatId)
|
|
return null;
|
|
|
|
var medium = new STGMEDIUM();
|
|
var formatEtc = ToFormatEtc(formatId);
|
|
var result = oleDataObject.GetData(&formatEtc, &medium);
|
|
if (result != (uint)HRESULT.S_OK)
|
|
return null;
|
|
|
|
try
|
|
{
|
|
if (medium.unionmember != IntPtr.Zero)
|
|
{
|
|
if (medium.tymed == TYMED.TYMED_HGLOBAL)
|
|
{
|
|
var hGlobal = medium.unionmember;
|
|
return ReadDataFromHGlobal(format, hGlobal, formatEtc);
|
|
}
|
|
else if (medium.tymed == TYMED.TYMED_GDI)
|
|
{
|
|
var bitmapHandle = medium.unionmember;
|
|
return ReadDataFromGdi(bitmapHandle);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
ReleaseStgMedium(ref medium);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static ushort? TryGetContainedFormat(Win32Com.IDataObject oleDataObject, DataFormat format)
|
|
{
|
|
// Bitmap is not a real format, find the first matching platform format, if any.
|
|
if (DataFormat.Bitmap.Equals(format))
|
|
{
|
|
foreach (var imageFormat in ClipboardFormatRegistry.ImageFormats)
|
|
{
|
|
if (TryGetContainedFormatCore(oleDataObject, imageFormat) is { } formatId)
|
|
return formatId;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
return TryGetContainedFormatCore(oleDataObject, format);
|
|
|
|
static unsafe ushort? TryGetContainedFormatCore(Win32Com.IDataObject oleDataObject, DataFormat format)
|
|
{
|
|
Debug.Assert(format != DataFormat.Bitmap);
|
|
|
|
var formatId = ClipboardFormatRegistry.GetOrAddFormat(format);
|
|
var formatEtc = ToFormatEtc(formatId);
|
|
return oleDataObject.QueryGetData(&formatEtc) == (uint)HRESULT.S_OK ? formatId : null;
|
|
}
|
|
}
|
|
|
|
private static unsafe object? ReadDataFromGdi(nint bitmapHandle)
|
|
{
|
|
var bitmap = new BITMAP();
|
|
unsafe
|
|
{
|
|
var pBitmap = &bitmap;
|
|
if ((uint)GetObject(bitmapHandle, Marshal.SizeOf(bitmap), (IntPtr)pBitmap) == 0)
|
|
return null;
|
|
|
|
var bitmapInfoHeader = new BITMAPINFOHEADER()
|
|
{
|
|
biWidth = bitmap.bmWidth,
|
|
biHeight = bitmap.bmHeight,
|
|
biPlanes = bitmap.bmPlanes,
|
|
biBitCount = 32,
|
|
biCompression = 0,
|
|
biSizeImage = (uint)(bitmap.bmWidth * 4 * Math.Abs(bitmap.bmHeight))
|
|
};
|
|
|
|
bitmapInfoHeader.Init();
|
|
|
|
IntPtr destHdc = IntPtr.Zero, compatDc = IntPtr.Zero, section = IntPtr.Zero, sourceHdc = IntPtr.Zero, srcCompatHdc = IntPtr.Zero;
|
|
|
|
try
|
|
{
|
|
destHdc = GetDC(IntPtr.Zero);
|
|
if (destHdc == IntPtr.Zero)
|
|
return null;
|
|
|
|
compatDc = CreateCompatibleDC(destHdc);
|
|
if (compatDc == IntPtr.Zero)
|
|
return null;
|
|
|
|
section = CreateDIBSection(compatDc, ref bitmapInfoHeader, 0, out var lbBits, IntPtr.Zero, 0);
|
|
if (section == IntPtr.Zero)
|
|
return null;
|
|
|
|
SelectObject(compatDc, section);
|
|
sourceHdc = GetDC(IntPtr.Zero);
|
|
if (sourceHdc == IntPtr.Zero)
|
|
return null;
|
|
|
|
srcCompatHdc = CreateCompatibleDC(sourceHdc);
|
|
if (srcCompatHdc == IntPtr.Zero)
|
|
return null;
|
|
|
|
SelectObject(srcCompatHdc, bitmapHandle);
|
|
|
|
if (StretchBlt(compatDc, 0, bitmapInfoHeader.biHeight, bitmapInfoHeader.biWidth, -bitmapInfoHeader.biHeight, srcCompatHdc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, SRCCOPY) != 0)
|
|
return new Bitmap(Platform.PixelFormats.Bgra8888,
|
|
Platform.AlphaFormat.Opaque,
|
|
lbBits,
|
|
new PixelSize(bitmapInfoHeader.biWidth, bitmapInfoHeader.biHeight),
|
|
new Vector(96, 96),
|
|
bitmapInfoHeader.biWidth * 4);
|
|
}
|
|
finally
|
|
{
|
|
if (sourceHdc != IntPtr.Zero)
|
|
ReleaseDC(IntPtr.Zero, sourceHdc);
|
|
|
|
if (srcCompatHdc != IntPtr.Zero)
|
|
ReleaseDC(IntPtr.Zero, srcCompatHdc);
|
|
|
|
if (compatDc != IntPtr.Zero)
|
|
ReleaseDC(IntPtr.Zero, compatDc);
|
|
|
|
if (destHdc != IntPtr.Zero)
|
|
ReleaseDC(IntPtr.Zero, destHdc);
|
|
|
|
if (section != IntPtr.Zero)
|
|
DeleteObject(section);
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public unsafe static object? ReadDataFromHGlobal(DataFormat format, IntPtr hGlobal, FORMATETC formatEtc)
|
|
{
|
|
if (DataFormat.Text.Equals(format))
|
|
return ReadStringFromHGlobal(hGlobal);
|
|
|
|
if (DataFormat.File.Equals(format))
|
|
{
|
|
return ReadFileNamesFromHGlobal(hGlobal)
|
|
.Select(fileName => StorageProviderHelpers.TryCreateBclStorageItem(fileName) as IStorageItem)
|
|
.Where(f => f is not null)
|
|
.ToArray();
|
|
}
|
|
|
|
if (DataFormat.Bitmap.Equals(format))
|
|
{
|
|
if (formatEtc.cfFormat == (ushort)ClipboardFormat.CF_DIB)
|
|
{
|
|
var data = ReadBytesFromHGlobal(hGlobal);
|
|
fixed (byte* ptr = data)
|
|
{
|
|
var sourceHeader = Marshal.PtrToStructure<BITMAPINFOHEADER>((IntPtr)ptr);
|
|
|
|
var destHeader = new BITMAPINFOHEADER()
|
|
{
|
|
biWidth = sourceHeader.biWidth,
|
|
biHeight = sourceHeader.biHeight,
|
|
biPlanes = sourceHeader.biPlanes,
|
|
biBitCount = 32,
|
|
biCompression = BitmapCompressionMode.BI_RGB,
|
|
biSizeImage = (uint)(sourceHeader.biWidth * 4 * Math.Abs(sourceHeader.biHeight))
|
|
};
|
|
|
|
destHeader.Init();
|
|
|
|
IntPtr hdc = IntPtr.Zero, compatDc = IntPtr.Zero, section = IntPtr.Zero;
|
|
try
|
|
{
|
|
hdc = GetDC(IntPtr.Zero);
|
|
if (hdc == IntPtr.Zero)
|
|
return null;
|
|
|
|
compatDc = CreateCompatibleDC(hdc);
|
|
if (compatDc == IntPtr.Zero)
|
|
return null;
|
|
|
|
section = CreateDIBSection(compatDc, ref destHeader, 0, out var lbBits, IntPtr.Zero, 0);
|
|
if (section == IntPtr.Zero)
|
|
return null;
|
|
|
|
var extraSourceHeaderSize = GetExtraHeaderSize(sourceHeader);
|
|
|
|
SelectObject(compatDc, section);
|
|
if (StretchDIBits(compatDc,
|
|
0,
|
|
sourceHeader.biHeight - 1,
|
|
sourceHeader.biWidth,
|
|
-sourceHeader.biHeight,
|
|
0,
|
|
0,
|
|
destHeader.biWidth,
|
|
destHeader.biHeight,
|
|
(IntPtr)(ptr + (sourceHeader.biSize + extraSourceHeaderSize)),
|
|
(IntPtr)ptr,
|
|
0,
|
|
SRCCOPY
|
|
) != 0)
|
|
return new Bitmap(Platform.PixelFormats.Bgra8888,
|
|
Platform.AlphaFormat.Opaque,
|
|
lbBits,
|
|
new PixelSize(destHeader.biWidth, destHeader.biHeight),
|
|
new Vector(96, 96),
|
|
destHeader.biWidth * 4);
|
|
}
|
|
finally
|
|
{
|
|
if (section != IntPtr.Zero)
|
|
DeleteObject(section);
|
|
|
|
if (compatDc != IntPtr.Zero)
|
|
ReleaseDC(IntPtr.Zero, compatDc);
|
|
|
|
if (hdc != IntPtr.Zero)
|
|
ReleaseDC(IntPtr.Zero, hdc);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var data = ReadBytesFromHGlobal(hGlobal);
|
|
var stream = new MemoryStream(data);
|
|
return new Bitmap(stream);
|
|
}
|
|
}
|
|
|
|
if (format is DataFormat<string>)
|
|
return ReadStringFromHGlobal(hGlobal);
|
|
|
|
if (format is DataFormat<byte[]>)
|
|
return ReadBytesFromHGlobal(hGlobal);
|
|
|
|
return null;
|
|
}
|
|
|
|
private static int GetExtraHeaderSize(in BITMAPINFOHEADER header)
|
|
{
|
|
// https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
|
|
switch (header.biCompression)
|
|
{
|
|
// If biCompression equals BI_RGB and the bitmap uses 8 bpp or less, the bitmap has a color table immediately
|
|
// following the BITMAPINFOHEADER structure. The color table consists of an array of RGBQUAD values. The size
|
|
// of the array is given by the biClrUsed member.
|
|
// If biClrUsed is zero, the array contains the maximum number of colors for the given bitdepth; that is,
|
|
// 2^biBitCount colors.
|
|
case BitmapCompressionMode.BI_RGB when header.biBitCount <= 8:
|
|
return (header.biClrUsed == 0 ? 1 << header.biBitCount : (int)header.biClrUsed) * 4;
|
|
|
|
// If biCompression equals BI_BITFIELDS, the bitmap uses three DWORD color masks (red, green, and blue,
|
|
// respectively), which specify the byte layout of the pixels. The 1 bits in each mask indicate the bits for
|
|
// that color within the pixel.
|
|
case BitmapCompressionMode.BI_BITFIELDS:
|
|
return 3 * 4;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private static string? ReadStringFromHGlobal(IntPtr hGlobal)
|
|
{
|
|
var sourcePtr = GlobalLock(hGlobal);
|
|
try
|
|
{
|
|
return Marshal.PtrToStringAuto(sourcePtr);
|
|
}
|
|
finally
|
|
{
|
|
GlobalUnlock(hGlobal);
|
|
}
|
|
}
|
|
|
|
private static List<string> ReadFileNamesFromHGlobal(IntPtr hGlobal)
|
|
{
|
|
var fileCount = DragQueryFile(hGlobal, -1, null, 0);
|
|
var files = new List<string>(fileCount);
|
|
|
|
for (var i = 0; i < fileCount; i++)
|
|
{
|
|
var pathLength = DragQueryFile(hGlobal, i, null, 0);
|
|
var sb = StringBuilderCache.Acquire(pathLength + 1);
|
|
|
|
if (DragQueryFile(hGlobal, i, sb, sb.Capacity) == pathLength)
|
|
files.Add(StringBuilderCache.GetStringAndRelease(sb));
|
|
else
|
|
StringBuilderCache.Release(sb);
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
private static byte[] ReadBytesFromHGlobal(IntPtr hGlobal)
|
|
{
|
|
var source = GlobalLock(hGlobal);
|
|
try
|
|
{
|
|
var size = (int)GlobalSize(hGlobal);
|
|
var data = new byte[size];
|
|
Marshal.Copy(source, data, 0, size);
|
|
return data;
|
|
}
|
|
finally
|
|
{
|
|
GlobalUnlock(hGlobal);
|
|
}
|
|
}
|
|
|
|
public unsafe static uint WriteDataToHGlobal(IDataTransfer dataTransfer, DataFormat format, ref IntPtr hGlobal)
|
|
{
|
|
if (DataFormat.Text.Equals(format))
|
|
{
|
|
var text = dataTransfer.TryGetValue(DataFormat.Text);
|
|
return WriteStringToHGlobal(ref hGlobal, text ?? string.Empty);
|
|
}
|
|
|
|
if (DataFormat.File.Equals(format))
|
|
{
|
|
var files = dataTransfer.TryGetValues(DataFormat.File) ?? [];
|
|
|
|
IEnumerable<string> fileNames = files
|
|
.Select(StorageProviderExtensions.TryGetLocalPath)
|
|
.Where(path => path is not null)!;
|
|
|
|
return WriteFileNamesToHGlobal(ref hGlobal, fileNames);
|
|
}
|
|
|
|
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);
|
|
|
|
if (!isV5)
|
|
{
|
|
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<byte>(bytes, buffer.Length).CopyTo(new Span<byte>((image + infoHeader.biSize), buffer.Length));
|
|
|
|
return WriteBytesToHGlobal(ref hGlobal, imageData);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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<byte>(bytes, buffer.Length).CopyTo(new Span<byte>((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<string> stringFormat)
|
|
{
|
|
return dataTransfer.TryGetValue(stringFormat) is { } stringValue ?
|
|
WriteStringToHGlobal(ref hGlobal, stringValue) :
|
|
DV_E_FORMATETC;
|
|
}
|
|
|
|
if (format is DataFormat<byte[]> bytesFormat)
|
|
{
|
|
return dataTransfer.TryGetValue(bytesFormat) is { } bytes ?
|
|
WriteBytesToHGlobal(ref hGlobal, bytes.AsSpan()) :
|
|
DV_E_FORMATETC;
|
|
}
|
|
|
|
Logger.TryGet(LogEventLevel.Warning, LogArea.Win32Platform)
|
|
?.Log(null, "Unsupported data format {Format}", format);
|
|
|
|
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);
|
|
|
|
if (hGlobal == IntPtr.Zero)
|
|
hGlobal = GlobalAlloc(GlobalAllocFlags.GHND, requiredSize);
|
|
|
|
var availableSize = GlobalSize(hGlobal).ToInt64();
|
|
if (requiredSize > availableSize)
|
|
return STG_E_MEDIUMFULL;
|
|
|
|
var destPtr = GlobalLock(hGlobal);
|
|
try
|
|
{
|
|
fixed (char* sourcePtr = data)
|
|
{
|
|
Buffer.MemoryCopy(sourcePtr, (void*)destPtr, requiredSize, requiredSize);
|
|
}
|
|
|
|
return (uint)HRESULT.S_OK;
|
|
}
|
|
finally
|
|
{
|
|
GlobalUnlock(hGlobal);
|
|
}
|
|
}
|
|
|
|
private static unsafe uint WriteFileNamesToHGlobal(ref IntPtr hGlobal, IEnumerable<string> fileNames)
|
|
{
|
|
var buffer = StringBuilderCache.Acquire();
|
|
|
|
foreach (var fileName in fileNames)
|
|
{
|
|
buffer.Append(fileName);
|
|
buffer.Append('\0');
|
|
}
|
|
|
|
buffer.Append('\0');
|
|
|
|
var dropFiles = new DROPFILES
|
|
{
|
|
pFiles = (uint)sizeof(DROPFILES),
|
|
pt = default,
|
|
fNC = 0,
|
|
fWide = 1
|
|
};
|
|
|
|
var requiredSize = sizeof(DROPFILES) + buffer.Length * sizeof(char);
|
|
if (hGlobal == IntPtr.Zero)
|
|
hGlobal = GlobalAlloc(GlobalAllocFlags.GHND, requiredSize);
|
|
|
|
var availableSize = GlobalSize(hGlobal).ToInt64();
|
|
if (requiredSize > availableSize)
|
|
{
|
|
StringBuilderCache.Release(buffer);
|
|
return STG_E_MEDIUMFULL;
|
|
}
|
|
|
|
var ptr = GlobalLock(hGlobal);
|
|
try
|
|
{
|
|
var data = StringBuilderCache.GetStringAndRelease(buffer);
|
|
var destSpan = new Span<byte>((void*)ptr, requiredSize);
|
|
#if NET8_0_OR_GREATER
|
|
MemoryMarshal.Write(destSpan, in dropFiles);
|
|
#else
|
|
MemoryMarshal.Write(destSpan, ref dropFiles);
|
|
#endif
|
|
|
|
fixed (char* sourcePtr = data)
|
|
{
|
|
var sourceSpan = MemoryMarshal.AsBytes(new Span<char>(sourcePtr, data.Length));
|
|
sourceSpan.CopyTo(destSpan.Slice(sizeof(DROPFILES)));
|
|
}
|
|
|
|
return (uint)HRESULT.S_OK;
|
|
}
|
|
finally
|
|
{
|
|
GlobalUnlock(hGlobal);
|
|
}
|
|
}
|
|
|
|
private static unsafe uint WriteBytesToHGlobal(ref IntPtr hGlobal, ReadOnlySpan<byte> data)
|
|
{
|
|
var requiredSize = data.Length;
|
|
|
|
if (hGlobal == IntPtr.Zero)
|
|
hGlobal = GlobalAlloc(GlobalAllocFlags.GHND, requiredSize);
|
|
|
|
var available = GlobalSize(hGlobal).ToInt64();
|
|
if (requiredSize > available)
|
|
return STG_E_MEDIUMFULL;
|
|
|
|
var destPtr = GlobalLock(hGlobal);
|
|
try
|
|
{
|
|
data.CopyTo(new Span<byte>((void*)destPtr, requiredSize));
|
|
return (uint)HRESULT.S_OK;
|
|
}
|
|
finally
|
|
{
|
|
GlobalUnlock(hGlobal);
|
|
}
|
|
}
|
|
}
|
|
|