From 70053f7443ce127ceafa5f88488f9885060df2c8 Mon Sep 17 00:00:00 2001 From: Nikita Krapivin Date: Sun, 13 Nov 2022 02:57:45 +0500 Subject: [PATCH] Use dibv5 bitmap format for clipboard. --- AeroShot.csproj | 1 + Dibv5.cs | 208 ++++++++++++++++++++++++++++++++++++++++++++++++ Screenshot.cs | 25 +----- 3 files changed, 211 insertions(+), 23 deletions(-) create mode 100644 Dibv5.cs diff --git a/AeroShot.csproj b/AeroShot.csproj index 303c879..4ac649d 100644 --- a/AeroShot.csproj +++ b/AeroShot.csproj @@ -45,6 +45,7 @@ + Form diff --git a/Dibv5.cs b/Dibv5.cs new file mode 100644 index 0000000..80aa21c --- /dev/null +++ b/Dibv5.cs @@ -0,0 +1,208 @@ +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Runtime.InteropServices; + +namespace AeroShot +{ + public static class Dibv5 + { + [StructLayout(LayoutKind.Sequential)] + private struct BITMAPV5HEADER + { + public uint bV5Size; + public int bV5Width; + public int bV5Height; + public ushort bV5Planes; + public ushort bV5BitCount; + public uint 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 uint bV5CSType; + // CIEXYZTRIPLE bV5Endpoints + // bV5Endpoints.ciexyzRed + public int ciexyzRedX; + public int ciexyzRedY; + public int ciexyzRedZ; + // bV5Endpoints.ciexyzGreen + public int ciexyzGreenX; + public int ciexyzGreenY; + public int ciexyzGreenZ; + // bV5Endpoints.ciexyzBlue + public int ciexyzBlueX; + public int ciexyzBlueY; + public int ciexyzBlueZ; + public uint bV5GammaRed; + public uint bV5GammaGreen; + public uint bV5GammaBlue; + public uint bV5Intent; + public uint bV5ProfileData; + public uint bV5ProfileSize; + public uint bV5Reserved; + } + + [DllImport("user32.dll")] + private static extern bool OpenClipboard(IntPtr hWndNewOwner); + + [DllImport("user32.dll")] + private static extern bool CloseClipboard(); + + [DllImport("user32.dll")] + private static extern bool EmptyClipboard(); + + [DllImport("user32.dll")] + private static extern IntPtr SetClipboardData(uint uFormat, IntPtr hMemory); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern uint RegisterClipboardFormat(string lpszFormat); + + [DllImport("kernel32.dll")] + private static extern IntPtr GlobalAlloc(uint uFlags, UIntPtr cbBytes); + + [DllImport("kernel32.dll")] + private static extern IntPtr GlobalLock(IntPtr hMemory); + + [DllImport("kernel32.dll")] + private static extern bool GlobalUnlock(IntPtr hMemory); + + private const uint GMEM_MOVEABLE = 0x0002; + + private const uint CF_DIBV5 = 17; + + private const uint BI_RGB = 0; + + private const uint LCS_GM_GRAPHICS = 0x00000002; + + private const uint LCS_WINDOWS_COLOR_SPACE = 0x57696E20; // 'Win ' in ASCII (or ' niW') + + private const uint LCS_sRGB = 0x73524742; // 'sRGB' in ASCII (or 'BGRs') + + private const int SIZEOF_BITMAPV5HEADER = 124; + + public static void SetImageAsDibv5(Image i) + { + if (i == null) + { + throw new ArgumentNullException(nameof(i), "Image cannot be null."); + } + + int w, h, stride, siz; + byte[] pixbuff, pngbuff; + + // save as raw png before doing anything... + using (var ms = new MemoryStream()) + { + i.Save(ms, ImageFormat.Png); + pngbuff = ms.ToArray(); + } + + // use 32bit Premultiplied ARGB format since this is what GDI is using internally for bitmaps w/ alpha + using (var bm = new Bitmap(i.Width, i.Height, PixelFormat.Format32bppPArgb)) + { + using (var g = Graphics.FromImage(bm)) + { + // draw + g.DrawImage(i, 0, 0); + } + + // flip Y because BITMAPV5 expects a different pixel order + bm.RotateFlip(RotateFlipType.RotateNoneFlipY); + + // lock with our current pixel format (already prepared) + { + var dat = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadOnly, bm.PixelFormat); + // copy data + w = dat.Width; + h = dat.Height; + stride = dat.Stride; + siz = stride * h; // magic! + pixbuff = new byte[siz]; + Marshal.Copy(dat.Scan0, pixbuff, 0, siz); + bm.UnlockBits(dat); + } + // unlock + } + + // copy raw pixels into a byte array with 124 extra bytes for DIBV5 info + byte[] final = new byte[SIZEOF_BITMAPV5HEADER + pixbuff.Length]; + Array.Copy(pixbuff, 0, final, SIZEOF_BITMAPV5HEADER, pixbuff.Length); + + // prepare DIBV5 + var v5hdr = new BITMAPV5HEADER(); + v5hdr.bV5Size = SIZEOF_BITMAPV5HEADER; // sizeof(BITMAPV5HEADER); in Visual C++ + v5hdr.bV5Width = w; + v5hdr.bV5Height = h; // already flipped for us, no need to negate, FIXES DISCORD/CHROME + v5hdr.bV5Planes = 1; // must be set to 1 + v5hdr.bV5BitCount = 32; // argb bit count + v5hdr.bV5Compression = BI_RGB; + v5hdr.bV5SizeImage = (uint)siz; // in bytes + v5hdr.bV5XPelsPerMeter = 1; // ???? + v5hdr.bV5YPelsPerMeter = 1; // ???? + v5hdr.bV5ClrUsed = 0; + v5hdr.bV5ClrImportant = 0; + v5hdr.bV5RedMask = 0; + v5hdr.bV5GreenMask = 0; + v5hdr.bV5BlueMask = 0; + v5hdr.bV5AlphaMask = 0xff000000; // important!! + v5hdr.bV5CSType = LCS_WINDOWS_COLOR_SPACE; // default color space. + v5hdr.ciexyzRedX = 0; + v5hdr.ciexyzRedY = 0; + v5hdr.ciexyzRedZ = 0; + v5hdr.ciexyzGreenX = 0; + v5hdr.ciexyzGreenY = 0; + v5hdr.ciexyzGreenZ = 0; + v5hdr.ciexyzBlueX = 0; + v5hdr.ciexyzBlueY = 0; + v5hdr.ciexyzBlueZ = 0; + v5hdr.bV5GammaRed = 0; + v5hdr.bV5GammaGreen = 0; + v5hdr.bV5GammaBlue = 0; + v5hdr.bV5Intent = LCS_GM_GRAPHICS; // GRAPHICS intent, raw pixels please + v5hdr.bV5ProfileData = 0; + v5hdr.bV5ProfileSize = 0; + v5hdr.bV5Reserved = 0; + + // -- UNSAFE + { + IntPtr raw = Marshal.AllocHGlobal((int)v5hdr.bV5Size); + Marshal.StructureToPtr(v5hdr, raw, false); + Marshal.Copy(raw, final, 0, (int)v5hdr.bV5Size); + Marshal.FreeHGlobal(raw); + } + // -- UNSAFE END + + if (OpenClipboard(IntPtr.Zero)) + { + if (EmptyClipboard()) + { + // -- UNSAFE + IntPtr hPng = GlobalAlloc(GMEM_MOVEABLE, new UIntPtr((uint)pngbuff.Length)); + { + IntPtr pPng = GlobalLock(hPng); + Marshal.Copy(pngbuff, 0, pPng, pngbuff.Length); + GlobalUnlock(pPng); + } + IntPtr hFinal = GlobalAlloc(GMEM_MOVEABLE, new UIntPtr((uint)final.Length)); + { + IntPtr pFinal = GlobalLock(hFinal); + Marshal.Copy(final, 0, pFinal, final.Length); + GlobalUnlock(pFinal); + } + SetClipboardData(RegisterClipboardFormat("PNG"), hPng); + SetClipboardData(CF_DIBV5, hFinal); + // handles are moved to Windows's ownership. + // -- UNSAFE END + } + CloseClipboard(); + } + } + } +} diff --git a/Screenshot.cs b/Screenshot.cs index 38d6c26..3312a22 100644 --- a/Screenshot.cs +++ b/Screenshot.cs @@ -197,30 +197,9 @@ internal static void CaptureWindow(ref ScreenshotTask data) } else { - if (data.ClipboardNotDisk && data.Background != - ScreenshotTask.BackgroundType.Transparent) - // Screenshot is already opaque, don't need to modify it - Clipboard.SetImage(s); - else if (data.ClipboardNotDisk) + if (data.ClipboardNotDisk) { - var whiteS = new Bitmap(s.Width, s.Height, PixelFormat.Format24bppRgb); - using (Graphics graphics = Graphics.FromImage(whiteS)) - { - graphics.Clear(Color.White); - graphics.DrawImage(s, 0, 0, s.Width, s.Height); - } - using (var stream = new MemoryStream()) - { - // Save screenshot in clipboard as PNG which some applications support (eg. Microsoft Office) - s.Save(stream, ImageFormat.Png); - var pngClipboardData = new DataObject("PNG", stream); - - // Add fallback for applications that don't support PNG from clipboard (eg. Photoshop or Paint) - pngClipboardData.SetData(DataFormats.Bitmap, whiteS); - Clipboard.Clear(); - Clipboard.SetDataObject(pngClipboardData, true); - } - whiteS.Dispose(); + Dibv5.SetImageAsDibv5(s); } else {