diff --git a/src/Greenshot.Base/Core/ClipboardHelper.cs b/src/Greenshot.Base/Core/ClipboardHelper.cs index e87f3d1d6..02911f058 100644 --- a/src/Greenshot.Base/Core/ClipboardHelper.cs +++ b/src/Greenshot.Base/Core/ClipboardHelper.cs @@ -666,8 +666,8 @@ EndSelection:<<<<<<<4 if (imageStream != null) { byte[] dibBuffer = new byte[imageStream.Length]; - imageStream.Read(dibBuffer, 0, dibBuffer.Length); - var infoHeader = BinaryStructHelper.FromByteArray(dibBuffer); + _ = imageStream.Read(dibBuffer, 0, dibBuffer.Length); + var infoHeader = BinaryStructHelper.FromByteArray(dibBuffer); if (!infoHeader.IsDibV5) { Log.InfoFormat("Using special DIB /// Set an Image to the clipboard /// This method will place images to the clipboard depending on the ClipboardFormats setting. @@ -868,7 +866,7 @@ EndSelection:<<<<<<<4 { // Create the stream for the clipboard dibStream = new MemoryStream(); - var dibBytes = DibHelper.ConvertToDib(imageToSave); + var dibBytes = ((Bitmap)imageToSave).ConvertToDib(); dibStream.Write(dibBytes,0, dibBytes.Length); // Set the DIB to the clipboard DataObject @@ -889,7 +887,7 @@ EndSelection:<<<<<<<4 dibV5Stream = new MemoryStream(); // Create the BITMAPINFOHEADER - BITMAPINFOHEADER header = new BITMAPINFOHEADER(imageToSave.Width, imageToSave.Height, 32) + var header = new BITMAPINFOHEADERV5(imageToSave.Width, imageToSave.Height, 32) { // Make sure we have BI_BITFIELDS, this seems to be normal for Format17? biCompression = BI_COMPRESSION.BI_BITFIELDS @@ -1126,7 +1124,7 @@ EndSelection:<<<<<<<4 /// public static IEnumerable GetImageFilenames(IDataObject dataObject) { - string[] dropFileNames = (string[]) dataObject.GetData(DataFormats.FileDrop); + string[] dropFileNames = (string[])dataObject.GetData(DataFormats.FileDrop); if (dropFileNames != null && dropFileNames.Length > 0) { return dropFileNames diff --git a/src/Greenshot.Base/Core/DibHelper.cs b/src/Greenshot.Base/Core/DibHelper.cs index 70708a0b5..61cb84411 100644 --- a/src/Greenshot.Base/Core/DibHelper.cs +++ b/src/Greenshot.Base/Core/DibHelper.cs @@ -1,4 +1,25 @@ -using System; +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: https://getgreenshot.org/ + * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; @@ -9,77 +30,91 @@ namespace Greenshot.Base.Core /// /// Though Greenshot implements the specs for the DIB image format, /// it seems to cause a lot of issues when using the clipboard. - /// There is some research done about the DIB on the clipboard, this code was taking from: + /// There is some research done about the DIB on the clipboard, this code is based upon the information /// here /// internal static class DibHelper { /// - /// Converts the image to Device Independent Bitmap format of type BITFIELDS. - /// This is (wrongly) accepted by many applications as containing transparency, - /// so I'm abusing it for that. + /// Converts the Bitmap to a Device Independent Bitmap format of type BITFIELDS. /// - /// Image to convert to DIB + /// Bitmap to convert to DIB /// The image converted to DIB, in bytes. - public static byte[] ConvertToDib(Image image) + public static byte[] ConvertToDib(this Bitmap sourceBitmap) { - byte[] bm32bData; - int width = image.Width; - int height = image.Height; - // Ensure image is 32bppARGB by painting it on a new 32bppARGB image. - using (var bm32b = ImageHelper.CreateEmptyLike(image, Color.Transparent, PixelFormat.Format32bppArgb)) - { - using (var graphics = Graphics.FromImage(bm32b)) - { - graphics.DrawImage(image, new Rectangle(0, 0, bm32b.Width, bm32b.Height)); - } - // Bitmap format has its lines reversed. - bm32b.RotateFlip(RotateFlipType.Rotate180FlipX); - bm32bData = GetImageData(bm32b, out var stride); - } - // BITMAPINFOHEADER struct for DIB. - uint hdrSize = 0x28; - var fullImage = new byte[hdrSize + 12 + bm32bData.Length]; - var bitmapInfoHeader = MemoryMarshal.Cast(fullImage.AsSpan()); + if (sourceBitmap == null) throw new ArgumentNullException(nameof(sourceBitmap)); - bitmapInfoHeader[0].biSize = hdrSize; - bitmapInfoHeader[0].biWidth = width; - bitmapInfoHeader[0].biHeight = height; + bool needsDisposal = false; + if (sourceBitmap.PixelFormat != PixelFormat.Format32bppArgb) + { + needsDisposal = true; + var clonedImage = ImageHelper.CreateEmptyLike(sourceBitmap, Color.Transparent, PixelFormat.Format32bppArgb); + using (var graphics = Graphics.FromImage(clonedImage)) + { + graphics.DrawImage(sourceBitmap, new Rectangle(0, 0, clonedImage.Width, clonedImage.Height)); + } + sourceBitmap = clonedImage; + } + + var bitmapSize = 4 * sourceBitmap.Width * sourceBitmap.Height; + var bitmapInfoHeaderSize = Marshal.SizeOf(typeof(BITMAPINFOHEADER)); + var bitmapInfoSize = bitmapInfoHeaderSize + 3 * Marshal.SizeOf(typeof(RGBQUAD)); + + // Create a byte [] to contain the DIB + var fullBmpBytes = new byte[bitmapInfoSize + bitmapSize]; + var fullBmpSpan = fullBmpBytes.AsSpan(); + // Cast the span to be of type BITMAPINFOHEADER so we can assign values + // TODO: in .NET 6 we could do a AsRef + var bitmapInfoHeader = MemoryMarshal.Cast(fullBmpSpan); + + bitmapInfoHeader[0].biSize = (uint)bitmapInfoHeaderSize; + bitmapInfoHeader[0].biWidth = sourceBitmap.Width; + bitmapInfoHeader[0].biHeight = sourceBitmap.Height; bitmapInfoHeader[0].biPlanes = 1; bitmapInfoHeader[0].biBitCount = 32; bitmapInfoHeader[0].biCompression = BI_COMPRESSION.BI_BITFIELDS; - bitmapInfoHeader[0].biSizeImage = (uint)bm32bData.Length; - bitmapInfoHeader[0].biXPelsPerMeter = (int)(image.HorizontalResolution * 39.3701); - bitmapInfoHeader[0].biYPelsPerMeter = (int)(image.VerticalResolution * 39.3701); + bitmapInfoHeader[0].biSizeImage = (uint)bitmapSize; + bitmapInfoHeader[0].biXPelsPerMeter = (int)(sourceBitmap.HorizontalResolution * 39.3701); + bitmapInfoHeader[0].biYPelsPerMeter = (int)(sourceBitmap.VerticalResolution * 39.3701); + // The aforementioned "BITFIELDS": color masks applied to the Int32 pixel value to get the R, G and B values. + var rgbQuads = MemoryMarshal.Cast(fullBmpSpan.Slice(Marshal.SizeOf(typeof(BITMAPINFOHEADER)))); + rgbQuads[0].rgbRed = 255; + rgbQuads[1].rgbGreen = 255; + rgbQuads[2].rgbBlue = 255; - // The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values. - bitmapInfoHeader[0].bV5RedMask = 0x00FF0000; - bitmapInfoHeader[0].bV5GreenMask = 0x0000FF00; - bitmapInfoHeader[0].bV5BlueMask = 0x000000FF; + // Now copy the lines, in reverse to the byte array + var sourceBitmapData = sourceBitmap.LockBits(new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height), ImageLockMode.ReadOnly, sourceBitmap.PixelFormat); + try + { + // Get a span for the real bitmap bytes, which starts after the header + var bitmapSpan = fullBmpSpan.Slice(bitmapInfoSize); + // Make sure we also have a span to copy from + Span bitmapSourceSpan; + unsafe + { + bitmapSourceSpan = new Span(sourceBitmapData.Scan0.ToPointer(), sourceBitmapData.Stride * sourceBitmapData.Height); + } - // These are all 0. Since .net clears new arrays, don't bother writing them. - //Int32 biClrUsed = 0; - //Int32 biClrImportant = 0; + // Loop over all the lines and copy the top line to the bottom (flipping the image) + for (int y = 0; y < sourceBitmap.Height; y++) + { + var sourceY = (sourceBitmap.Height - 1) - y; + var sourceLine = bitmapSourceSpan.Slice(sourceBitmapData.Stride * sourceY, 4 * sourceBitmap.Width); + var destinationLine = bitmapSpan.Slice(y * 4 * sourceBitmap.Width); + sourceLine.CopyTo(destinationLine); + } + } + finally + { + sourceBitmap.UnlockBits(sourceBitmapData); + } - Array.Copy(bm32bData, 0, fullImage, hdrSize + 12, bm32bData.Length); - return fullImage; - } - - /// - /// Gets the raw bytes from an image. - /// - /// The image to get the bytes from. - /// Stride of the retrieved image data. - /// The raw bytes of the image - public static byte[] GetImageData(Bitmap sourceImage, out int stride) - { - BitmapData sourceData = sourceImage.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, sourceImage.PixelFormat); - stride = sourceData.Stride; - byte[] data = new byte[stride * sourceImage.Height]; - Marshal.Copy(sourceData.Scan0, data, 0, data.Length); - sourceImage.UnlockBits(sourceData); - return data; + if (needsDisposal) + { + sourceBitmap.Dispose(); + } + return fullBmpBytes; } } } diff --git a/src/Greenshot.Base/Core/WindowCapture.cs b/src/Greenshot.Base/Core/WindowCapture.cs index 3b5bfdbe4..a6b846fd4 100644 --- a/src/Greenshot.Base/Core/WindowCapture.cs +++ b/src/Greenshot.Base/Core/WindowCapture.cs @@ -332,13 +332,13 @@ namespace Greenshot.Base.Core } // Create BITMAPINFOHEADER for CreateDIBSection - BITMAPINFOHEADER bmi = new BITMAPINFOHEADER(captureBounds.Width, captureBounds.Height, 24); + BITMAPINFOHEADERV5 bmi = new BITMAPINFOHEADERV5(captureBounds.Width, captureBounds.Height, 24); // Make sure the last error is set to 0 Win32.SetLastError(0); // create a bitmap we can copy it to, using GetDeviceCaps to get the width/height - using SafeDibSectionHandle safeDibSectionHandle = GDI32.CreateDIBSection(desktopDcHandle, ref bmi, BITMAPINFOHEADER.DIB_RGB_COLORS, out _, IntPtr.Zero, 0); + using SafeDibSectionHandle safeDibSectionHandle = GDI32.CreateDIBSection(desktopDcHandle, ref bmi, BITMAPINFOHEADERV5.DIB_RGB_COLORS, out _, IntPtr.Zero, 0); if (safeDibSectionHandle.IsInvalid) { // Get Exception before the error is lost diff --git a/src/Greenshot.Base/UnmanagedHelpers/GDI32.cs b/src/Greenshot.Base/UnmanagedHelpers/GDI32.cs index 224ab954a..3f483ff20 100644 --- a/src/Greenshot.Base/UnmanagedHelpers/GDI32.cs +++ b/src/Greenshot.Base/UnmanagedHelpers/GDI32.cs @@ -261,7 +261,7 @@ namespace Greenshot.Base.UnmanagedHelpers public static extern SafeCompatibleDCHandle CreateCompatibleDC(SafeHandle hDC); [DllImport("gdi32", SetLastError = true)] - public static extern SafeDibSectionHandle CreateDIBSection(SafeHandle hdc, ref BITMAPINFOHEADER bmi, uint Usage, out IntPtr bits, IntPtr hSection, uint dwOffset); + public static extern SafeDibSectionHandle CreateDIBSection(SafeHandle hdc, ref BITMAPINFOHEADERV5 bmi, uint usage, out IntPtr bits, IntPtr hSection, uint dwOffset); [DllImport("gdi32", SetLastError = true)] public static extern SafeRegionHandle CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect); @@ -314,6 +314,15 @@ namespace Greenshot.Base.UnmanagedHelpers } } + [StructLayout(LayoutKind.Sequential)] + public struct RGBQUAD + { + public byte rgbBlue; + public byte rgbGreen; + public byte rgbRed; + public byte rgbReserved; + } + [StructLayout(LayoutKind.Sequential)] public struct CIEXYZTRIPLE { @@ -348,15 +357,168 @@ namespace Greenshot.Base.UnmanagedHelpers [FieldOffset(28)] public int biYPelsPerMeter; [FieldOffset(32)] public uint biClrUsed; [FieldOffset(36)] public uint biClrImportant; - [FieldOffset(40)] public uint bV5RedMask; - [FieldOffset(44)] public uint bV5GreenMask; - [FieldOffset(48)] public uint bV5BlueMask; - [FieldOffset(52)] public uint bV5AlphaMask; - [FieldOffset(56)] public uint bV5CSType; - [FieldOffset(60)] public CIEXYZTRIPLE bV5Endpoints; - [FieldOffset(96)] public uint bV5GammaRed; - [FieldOffset(100)] public uint bV5GammaGreen; - [FieldOffset(104)] public uint bV5GammaBlue; + + public const int DIB_RGB_COLORS = 0; + + public BITMAPINFOHEADER(int width, int height, ushort bpp) + { + biSize = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADER)); // BITMAPINFOHEADER < DIBV4 is 40 bytes + biPlanes = 1; // Should allways be 1 + biCompression = BI_COMPRESSION.BI_RGB; + biWidth = width; + biHeight = height; + biBitCount = bpp; + biSizeImage = (uint)(width * height * (bpp >> 3)); + biXPelsPerMeter = 0; + biYPelsPerMeter = 0; + biClrUsed = 0; + biClrImportant = 0; + } + + public bool IsDibV4 + { + get + { + uint sizeOfBMI = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADERV4)); + return biSize >= sizeOfBMI; + } + } + public bool IsDibV5 + { + get + { + uint sizeOfBMI = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADERV5)); + return biSize >= sizeOfBMI; + } + } + + public uint OffsetToPixels + { + get + { + if (biCompression == BI_COMPRESSION.BI_BITFIELDS) + { + // Add 3x4 bytes for the bitfield color mask + return biSize + 3 * 4; + } + + return biSize; + } + } + } + + [StructLayout(LayoutKind.Explicit)] + public struct BITMAPINFOHEADERV4 + { + [FieldOffset(0)] public uint biSize; + [FieldOffset(4)] public int biWidth; + [FieldOffset(8)] public int biHeight; + [FieldOffset(12)] public ushort biPlanes; + [FieldOffset(14)] public ushort biBitCount; + [FieldOffset(16)] public BI_COMPRESSION biCompression; + [FieldOffset(20)] public uint biSizeImage; + [FieldOffset(24)] public int biXPelsPerMeter; + [FieldOffset(28)] public int biYPelsPerMeter; + [FieldOffset(32)] public uint biClrUsed; + [FieldOffset(36)] public uint biClrImportant; + [FieldOffset(40)] public uint bV4RedMask; + [FieldOffset(44)] public uint bV4GreenMask; + [FieldOffset(48)] public uint bV4BlueMask; + [FieldOffset(52)] public uint bV4AlphaMask; + [FieldOffset(56)] public uint bV4CSType; + [FieldOffset(60)] public CIEXYZTRIPLE bV4Endpoints; + [FieldOffset(96)] public uint bV4GammaRed; + [FieldOffset(100)] public uint bV4GammaGreen; + [FieldOffset(104)] public uint bV4GammaBlue; + + public const int DIB_RGB_COLORS = 0; + + public BITMAPINFOHEADERV4(int width, int height, ushort bpp) + { + biSize = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADERV4)); // BITMAPINFOHEADER < DIBV5 is 40 bytes + biPlanes = 1; // Should allways be 1 + biCompression = BI_COMPRESSION.BI_RGB; + biWidth = width; + biHeight = height; + biBitCount = bpp; + biSizeImage = (uint)(width * height * (bpp >> 3)); + biXPelsPerMeter = 0; + biYPelsPerMeter = 0; + biClrUsed = 0; + biClrImportant = 0; + + // V4 + bV4RedMask = (uint)255 << 16; + bV4GreenMask = (uint)255 << 8; + bV4BlueMask = 255; + bV4AlphaMask = (uint)255 << 24; + bV4CSType = 0x73524742; // LCS_sRGB + bV4Endpoints = new CIEXYZTRIPLE + { + ciexyzBlue = new CIEXYZ(0), + ciexyzGreen = new CIEXYZ(0), + ciexyzRed = new CIEXYZ(0) + }; + bV4GammaRed = 0; + bV4GammaGreen = 0; + bV4GammaBlue = 0; + } + + public bool IsDibV4 + { + get + { + uint sizeOfBMI = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADERV4)); + return biSize >= sizeOfBMI; + } + } + public bool IsDibV5 + { + get + { + uint sizeOfBMI = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADERV5)); + return biSize >= sizeOfBMI; + } + } + + public uint OffsetToPixels + { + get + { + if (biCompression == BI_COMPRESSION.BI_BITFIELDS) + { + // Add 3x4 bytes for the bitfield color mask + return biSize + 3 * 4; + } + + return biSize; + } + } + } + + [StructLayout(LayoutKind.Explicit)] + public struct BITMAPINFOHEADERV5 + { + [FieldOffset(0)] public uint biSize; + [FieldOffset(4)] public int biWidth; + [FieldOffset(8)] public int biHeight; + [FieldOffset(12)] public ushort biPlanes; + [FieldOffset(14)] public ushort biBitCount; + [FieldOffset(16)] public BI_COMPRESSION biCompression; + [FieldOffset(20)] public uint biSizeImage; + [FieldOffset(24)] public int biXPelsPerMeter; + [FieldOffset(28)] public int biYPelsPerMeter; + [FieldOffset(32)] public uint biClrUsed; + [FieldOffset(36)] public uint biClrImportant; + [FieldOffset(40)] public uint bV4RedMask; + [FieldOffset(44)] public uint bV4GreenMask; + [FieldOffset(48)] public uint bV4BlueMask; + [FieldOffset(52)] public uint bV4AlphaMask; + [FieldOffset(56)] public uint bV4CSType; + [FieldOffset(60)] public CIEXYZTRIPLE bV4Endpoints; + [FieldOffset(96)] public uint bV4GammaRed; + [FieldOffset(100)] public uint bV4GammaGreen; + [FieldOffset(104)] public uint bV4GammaBlue; [FieldOffset(108)] public uint bV5Intent; // Rendering intent for bitmap [FieldOffset(112)] public uint bV5ProfileData; [FieldOffset(116)] public uint bV5ProfileSize; @@ -364,9 +526,9 @@ namespace Greenshot.Base.UnmanagedHelpers public const int DIB_RGB_COLORS = 0; - public BITMAPINFOHEADER(int width, int height, ushort bpp) + public BITMAPINFOHEADERV5(int width, int height, ushort bpp) { - biSize = (uint) Marshal.SizeOf(typeof(BITMAPINFOHEADER)); // BITMAPINFOHEADER < DIBV5 is 40 bytes + biSize = (uint) Marshal.SizeOf(typeof(BITMAPINFOHEADERV5)); // BITMAPINFOHEADER < DIBV5 is 40 bytes biPlanes = 1; // Should allways be 1 biCompression = BI_COMPRESSION.BI_RGB; biWidth = width; @@ -378,32 +540,41 @@ namespace Greenshot.Base.UnmanagedHelpers biClrUsed = 0; biClrImportant = 0; - // V5 - bV5RedMask = (uint) 255 << 16; - bV5GreenMask = (uint) 255 << 8; - bV5BlueMask = 255; - bV5AlphaMask = (uint) 255 << 24; - bV5CSType = 0x73524742; // LCS_sRGB - bV5Endpoints = new CIEXYZTRIPLE + // V4 + bV4RedMask = (uint) 255 << 16; + bV4GreenMask = (uint) 255 << 8; + bV4BlueMask = 255; + bV4AlphaMask = (uint) 255 << 24; + bV4CSType = 0x73524742; // LCS_sRGB + bV4Endpoints = new CIEXYZTRIPLE { ciexyzBlue = new CIEXYZ(0), ciexyzGreen = new CIEXYZ(0), ciexyzRed = new CIEXYZ(0) }; - bV5GammaRed = 0; - bV5GammaGreen = 0; - bV5GammaBlue = 0; + bV4GammaRed = 0; + bV4GammaGreen = 0; + bV4GammaBlue = 0; + // V5 bV5Intent = 4; bV5ProfileData = 0; bV5ProfileSize = 0; bV5Reserved = 0; } + public bool IsDibV4 + { + get + { + uint sizeOfBMI = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADERV4)); + return biSize >= sizeOfBMI; + } + } public bool IsDibV5 { get { - uint sizeOfBMI = (uint) Marshal.SizeOf(typeof(BITMAPINFOHEADER)); + uint sizeOfBMI = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADERV5)); return biSize >= sizeOfBMI; } }