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;
}
[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)]
public struct MINMAXINFO
{
@ -1274,16 +1248,10 @@ namespace Avalonia.Win32.Interop
uint dwWidth, uint dwHeight,
int XSrc, int YSrc,
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);
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 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)]
public static extern IntPtr CreateRectRgn(int x1, int y1, int x2, int y2);
@ -1784,7 +1752,7 @@ namespace Avalonia.Win32.Interop
[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);
public static extern IntPtr CreateDIBitmap(IntPtr hDC, in BITMAPV5HEADER pBitmapInfo, int flInit, IntPtr lplpVoid, IntPtr pbmi, DIBColorTable iUsage);
[DllImport("gdi32.dll")]
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);
[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")]
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);
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,
biHeight = bitmapInfo.biHeight,
biPlanes = bitmapInfo.biPlanes,
biWidth = sourceHeader.biWidth,
biHeight = sourceHeader.biHeight,
biPlanes = sourceHeader.biPlanes,
biBitCount = 32,
biCompression = 0,
biSizeImage = (uint)(bitmapInfo.biWidth * 4 * Math.Abs(bitmapInfo.biHeight))
biCompression = BitmapCompressionMode.BI_RGB,
biSizeImage = (uint)(sourceHeader.biWidth * 4 * Math.Abs(sourceHeader.biHeight))
};
bitmapInfoHeader.Init();
destHeader.Init();
IntPtr hdc = IntPtr.Zero, compatDc = IntPtr.Zero, section = IntPtr.Zero;
try
@ -218,31 +218,33 @@ internal static class OleDataObjectHelper
if (compatDc == IntPtr.Zero)
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)
return null;
var extraSourceHeaderSize = GetExtraHeaderSize(sourceHeader);
SelectObject(compatDc, section);
if (StretchDIBits(compatDc,
0,
bitmapInfo.biHeight,
bitmapInfo.biWidth,
-bitmapInfo.biHeight,
sourceHeader.biHeight - 1,
sourceHeader.biWidth,
-sourceHeader.biHeight,
0,
0,
bitmapInfoHeader.biWidth,
bitmapInfoHeader.biHeight,
(IntPtr)(ptr + bitmapInfo.biSize),
ref bitmapInfo,
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(bitmapInfoHeader.biWidth, bitmapInfoHeader.biHeight),
new PixelSize(destHeader.biWidth, destHeader.biHeight),
new Vector(96, 96),
bitmapInfoHeader.biWidth * 4);
destHeader.biWidth * 4);
}
finally
{
@ -274,6 +276,30 @@ internal static class OleDataObjectHelper
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);

Loading…
Cancel
Save