diff --git a/src/Greenshot.Base/Core/ClipboardHelper.cs b/src/Greenshot.Base/Core/ClipboardHelper.cs index 02a7f03f1..e87f3d1d6 100644 --- a/src/Greenshot.Base/Core/ClipboardHelper.cs +++ b/src/Greenshot.Base/Core/ClipboardHelper.cs @@ -175,7 +175,7 @@ EndSelection:<<<<<<<4 try { - // For BUG-1935 this was changed from looping ourselfs, or letting MS retry... + // For BUG-1935 this was changed from looping ourselves, or letting MS retry... Clipboard.SetDataObject(ido, copy, 15, 200); } catch (Exception clipboardSetException) @@ -866,19 +866,13 @@ EndSelection:<<<<<<<4 { if (CoreConfig.ClipboardFormats.Contains(ClipboardFormat.DIB)) { - using (MemoryStream tmpBmpStream = new MemoryStream()) - { - // Save image as BMP - SurfaceOutputSettings bmpOutputSettings = new SurfaceOutputSettings(OutputFormat.bmp, 100, false); - ImageOutput.SaveToStream(imageToSave, null, tmpBmpStream, bmpOutputSettings); - - dibStream = new MemoryStream(); - // Copy the source, but skip the "BITMAPFILEHEADER" which has a size of 14 - dibStream.Write(tmpBmpStream.GetBuffer(), BITMAPFILEHEADER_LENGTH, (int) tmpBmpStream.Length - BITMAPFILEHEADER_LENGTH); - } + // Create the stream for the clipboard + dibStream = new MemoryStream(); + var dibBytes = DibHelper.ConvertToDib(imageToSave); + dibStream.Write(dibBytes,0, dibBytes.Length); // Set the DIB to the clipboard DataObject - dataObject.SetData(DataFormats.Dib, true, dibStream); + dataObject.SetData(DataFormats.Dib, false, dibStream); } } catch (Exception dibEx) diff --git a/src/Greenshot.Base/Core/CoreConfiguration.cs b/src/Greenshot.Base/Core/CoreConfiguration.cs index 4776a3985..ead32a464 100644 --- a/src/Greenshot.Base/Core/CoreConfiguration.cs +++ b/src/Greenshot.Base/Core/CoreConfiguration.cs @@ -658,16 +658,6 @@ namespace Greenshot.Base.Core { WebRequestReadWriteTimeout = 100; } - - // Workaround for the Windows 11 clipboard issue found here: https://github.com/greenshot/greenshot/issues/348 - if (WindowsVersion.IsWindows11OrLater) - { - // If the format DIB is used, remove it and replace it with BITMAP. - if (ClipboardFormats.Contains(ClipboardFormat.DIB)) - { - ClipboardFormats = ClipboardFormats.Where(cf => cf != ClipboardFormat.DIB).Append(ClipboardFormat.BITMAP).ToList(); - } - } } /// diff --git a/src/Greenshot.Base/Core/DibHelper.cs b/src/Greenshot.Base/Core/DibHelper.cs new file mode 100644 index 000000000..70708a0b5 --- /dev/null +++ b/src/Greenshot.Base/Core/DibHelper.cs @@ -0,0 +1,85 @@ +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; +using Greenshot.Base.UnmanagedHelpers; + +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: + /// 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. + /// + /// Image to convert to DIB + /// The image converted to DIB, in bytes. + public static byte[] ConvertToDib(Image image) + { + 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()); + + bitmapInfoHeader[0].biSize = hdrSize; + bitmapInfoHeader[0].biWidth = width; + bitmapInfoHeader[0].biHeight = 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); + + + // 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; + + // These are all 0. Since .net clears new arrays, don't bother writing them. + //Int32 biClrUsed = 0; + //Int32 biClrImportant = 0; + + 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; + } + } +} diff --git a/src/Greenshot.Base/Core/ImageHelper.cs b/src/Greenshot.Base/Core/ImageHelper.cs index 60d846eac..7565694e3 100644 --- a/src/Greenshot.Base/Core/ImageHelper.cs +++ b/src/Greenshot.Base/Core/ImageHelper.cs @@ -1497,16 +1497,17 @@ namespace Greenshot.Base.Core /// /// the source bitmap as the specifications for the new bitmap /// The color to fill with, or Color.Empty to take the default depending on the pixel format - /// - public static Bitmap CreateEmptyLike(Image sourceImage, Color backgroundColor) + /// PixelFormat + /// Bitmap + public static Bitmap CreateEmptyLike(Image sourceImage, Color backgroundColor, PixelFormat? pixelFormat = null) { - PixelFormat pixelFormat = sourceImage.PixelFormat; + pixelFormat ??= sourceImage.PixelFormat; if (backgroundColor.A < 255) { pixelFormat = PixelFormat.Format32bppArgb; } - return CreateEmpty(sourceImage.Width, sourceImage.Height, pixelFormat, backgroundColor, sourceImage.HorizontalResolution, sourceImage.VerticalResolution); + return CreateEmpty(sourceImage.Width, sourceImage.Height, pixelFormat.Value, backgroundColor, sourceImage.HorizontalResolution, sourceImage.VerticalResolution); } ///