Browse Source

Windows: Fix clipboard bitmap pixel shift (#20654)

* Windows: Fix clipboard bitmap pixel shift

* Remove BITMAPINFO
pull/20449/head
Julien Lebosquain 1 month ago
committed by GitHub
parent
commit
09cf664bd1
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 40
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  2. 62
      src/Windows/Avalonia.Win32/OleDataObjectHelper.cs

40
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1181,32 +1181,6 @@ namespace Avalonia.Win32.Interop
public int ciexyzZ; public int ciexyzZ;
} }
[StructLayout(LayoutKind.Sequential)]
public struct BITMAPINFO
{
// C# cannot inlay structs in structs so must expand directly here
//
//[StructLayout(LayoutKind.Sequential)]
//public struct BITMAPINFOHEADER
//{
public uint biSize;
public int biWidth;
public int biHeight;
public ushort biPlanes;
public ushort biBitCount;
public BitmapCompressionMode biCompression;
public uint biSizeImage;
public int biXPelsPerMeter;
public int biYPelsPerMeter;
public uint biClrUsed;
public uint biClrImportant;
//}
public uint biRedMask;
public uint biGreenMask;
public uint biBlueMask;
}
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct MINMAXINFO public struct MINMAXINFO
{ {
@ -1274,16 +1248,10 @@ namespace Avalonia.Win32.Interop
uint dwWidth, uint dwHeight, uint dwWidth, uint dwHeight,
int XSrc, int YSrc, int XSrc, int YSrc,
uint uStartScan, uint cScanLines, uint uStartScan, uint cScanLines,
IntPtr lpvBits, [In] ref BITMAPINFO lpbmi, uint fuColorUse); IntPtr lpvBits, IntPtr 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")] [DllImport("gdi32.dll")]
public static extern int SetDIBits(IntPtr hdc, IntPtr hbm, uint start, uint cLines, IntPtr lpBits, in BITMAPV5HEADER lpbmi, uint fuColorUse); public static extern int SetDIBits(IntPtr hdc, IntPtr hbm, uint start, uint cLines, IntPtr lpBits, IntPtr lpbmi, uint fuColorUse);
[DllImport("gdi32.dll", SetLastError = false, ExactSpelling = true)] [DllImport("gdi32.dll", SetLastError = false, ExactSpelling = true)]
public static extern IntPtr CreateRectRgn(int x1, int y1, int x2, int y2); public static extern IntPtr CreateRectRgn(int x1, int y1, int x2, int y2);
@ -1784,7 +1752,7 @@ namespace Avalonia.Win32.Interop
[DllImport("gdi32.dll")] [DllImport("gdi32.dll")]
public static extern IntPtr CreateDIBSection(IntPtr hDC, in BITMAPV5HEADER pBitmapInfo, DIBColorTable usage, out IntPtr lplpVoid, IntPtr handle, int dw); public static extern IntPtr CreateDIBSection(IntPtr hDC, in BITMAPV5HEADER pBitmapInfo, DIBColorTable usage, out IntPtr lplpVoid, IntPtr handle, int dw);
[DllImport("gdi32.dll")] [DllImport("gdi32.dll")]
public static extern IntPtr CreateDIBitmap(IntPtr hDC, in BITMAPV5HEADER pBitmapInfo, int flInit, IntPtr lplpVoid, in BITMAPINFO pbmi, DIBColorTable iUsage); public static extern IntPtr CreateDIBitmap(IntPtr hDC, in BITMAPV5HEADER pBitmapInfo, int flInit, IntPtr lplpVoid, IntPtr pbmi, DIBColorTable iUsage);
[DllImport("gdi32.dll")] [DllImport("gdi32.dll")]
public static extern bool GdiFlush(); public static extern bool GdiFlush();
@ -1819,7 +1787,7 @@ namespace Avalonia.Win32.Interop
public static extern int DescribePixelFormat(IntPtr hdc, int iPixelFormat, int bytes, ref PixelFormatDescriptor pfd); public static extern int DescribePixelFormat(IntPtr hdc, int iPixelFormat, int bytes, ref PixelFormatDescriptor pfd);
[DllImport("gdi32.dll")] [DllImport("gdi32.dll")]
public static extern int StretchDIBits(IntPtr hdc, int xDest, int yDest, int DestWidth, int DestHeight, int xSrc, int ySrc, int SrcWidth, int SrcHeight, IntPtr lpBits, [In] ref BITMAPINFO lpbmi, uint iUsage, uint rop); public static extern int StretchDIBits(IntPtr hdc, int xDest, int yDest, int DestWidth, int DestHeight, int xSrc, int ySrc, int SrcWidth, int SrcHeight, IntPtr lpBits, IntPtr lpbmi, uint iUsage, uint rop);
[DllImport("gdi32.dll")] [DllImport("gdi32.dll")]
public static extern int StretchBlt(IntPtr hdc, int xDest, int yDest, int DestWidth, int DestHeight, IntPtr hdcSrc, int xSrc, int ySrc, int SrcWidth, int SrcHeight, uint rop); public static extern int StretchBlt(IntPtr hdc, int xDest, int yDest, int DestWidth, int DestHeight, IntPtr hdcSrc, int xSrc, int ySrc, int SrcWidth, int SrcHeight, uint rop);

62
src/Windows/Avalonia.Win32/OleDataObjectHelper.cs

@ -193,19 +193,19 @@ internal static class OleDataObjectHelper
var data = ReadBytesFromHGlobal(hGlobal); var data = ReadBytesFromHGlobal(hGlobal);
fixed (byte* ptr = data) fixed (byte* ptr = data)
{ {
var bitmapInfo = Marshal.PtrToStructure<BITMAPINFO>((IntPtr)ptr); var sourceHeader = Marshal.PtrToStructure<BITMAPINFOHEADER>((IntPtr)ptr);
var bitmapInfoHeader = new BITMAPINFOHEADER() var destHeader = new BITMAPINFOHEADER()
{ {
biWidth = bitmapInfo.biWidth, biWidth = sourceHeader.biWidth,
biHeight = bitmapInfo.biHeight, biHeight = sourceHeader.biHeight,
biPlanes = bitmapInfo.biPlanes, biPlanes = sourceHeader.biPlanes,
biBitCount = 32, biBitCount = 32,
biCompression = 0, biCompression = BitmapCompressionMode.BI_RGB,
biSizeImage = (uint)(bitmapInfo.biWidth * 4 * Math.Abs(bitmapInfo.biHeight)) biSizeImage = (uint)(sourceHeader.biWidth * 4 * Math.Abs(sourceHeader.biHeight))
}; };
bitmapInfoHeader.Init(); destHeader.Init();
IntPtr hdc = IntPtr.Zero, compatDc = IntPtr.Zero, section = IntPtr.Zero; IntPtr hdc = IntPtr.Zero, compatDc = IntPtr.Zero, section = IntPtr.Zero;
try try
@ -218,31 +218,33 @@ internal static class OleDataObjectHelper
if (compatDc == IntPtr.Zero) if (compatDc == IntPtr.Zero)
return null; return null;
section = CreateDIBSection(compatDc, ref bitmapInfoHeader, 0, out var lbBits, IntPtr.Zero, 0); section = CreateDIBSection(compatDc, ref destHeader, 0, out var lbBits, IntPtr.Zero, 0);
if (section == IntPtr.Zero) if (section == IntPtr.Zero)
return null; return null;
var extraSourceHeaderSize = GetExtraHeaderSize(sourceHeader);
SelectObject(compatDc, section); SelectObject(compatDc, section);
if (StretchDIBits(compatDc, if (StretchDIBits(compatDc,
0, 0,
bitmapInfo.biHeight, sourceHeader.biHeight - 1,
bitmapInfo.biWidth, sourceHeader.biWidth,
-bitmapInfo.biHeight, -sourceHeader.biHeight,
0, 0,
0, 0,
bitmapInfoHeader.biWidth, destHeader.biWidth,
bitmapInfoHeader.biHeight, destHeader.biHeight,
(IntPtr)(ptr + bitmapInfo.biSize), (IntPtr)(ptr + (sourceHeader.biSize + extraSourceHeaderSize)),
ref bitmapInfo, (IntPtr)ptr,
0, 0,
SRCCOPY SRCCOPY
) != 0) ) != 0)
return new Bitmap(Platform.PixelFormats.Bgra8888, return new Bitmap(Platform.PixelFormats.Bgra8888,
Platform.AlphaFormat.Opaque, Platform.AlphaFormat.Opaque,
lbBits, lbBits,
new PixelSize(bitmapInfoHeader.biWidth, bitmapInfoHeader.biHeight), new PixelSize(destHeader.biWidth, destHeader.biHeight),
new Vector(96, 96), new Vector(96, 96),
bitmapInfoHeader.biWidth * 4); destHeader.biWidth * 4);
} }
finally finally
{ {
@ -274,6 +276,30 @@ internal static class OleDataObjectHelper
return null; 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) private static string? ReadStringFromHGlobal(IntPtr hGlobal)
{ {
var sourcePtr = GlobalLock(hGlobal); var sourcePtr = GlobalLock(hGlobal);

Loading…
Cancel
Save