#region License Information (GPL v3) /* ShareX - A program that allows you to take screenshots and share any file type Copyright (c) 2007-2022 ShareX Team 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Optionally you can also view the license at . */ #endregion License Information (GPL v3) using System; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; using System.Windows.Forms; namespace ShareX.HelpersLib { internal class ClipboardHelpersEx { // Source: https://stackoverflow.com/a/46424800/264877 /// /// Copies the given image to the clipboard as PNG, DIB and standard Bitmap format. /// /// Image to put on the clipboard. /// Optional specifically nontransparent version of the image to put on the clipboard. /// Clipboard data object to put the image into. Might already contain other stuff. Leave null to create a new one. public static void SetClipboardImage(Bitmap image, Bitmap imageNoTr, DataObject data) { Clipboard.Clear(); if (data == null) data = new DataObject(); if (imageNoTr == null) imageNoTr = image; using (MemoryStream pngMemStream = new MemoryStream()) using (MemoryStream dibMemStream = new MemoryStream()) { // As standard bitmap, without transparency support data.SetData(DataFormats.Bitmap, true, imageNoTr); // As PNG. Gimp will prefer this over the other two. image.Save(pngMemStream, ImageFormat.Png); data.SetData("PNG", false, pngMemStream); // As DIB. This is (wrongly) accepted as ARGB by many applications. byte[] dibData = ConvertToDib(image); dibMemStream.Write(dibData, 0, dibData.Length); data.SetData(DataFormats.Dib, false, dibMemStream); // The 'copy=true' argument means the MemoryStreams can be safely disposed after the operation. Clipboard.SetDataObject(data, true); } } /// /// 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 (Bitmap bm32b = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppPArgb)) { using (Graphics gr = Graphics.FromImage(bm32b)) gr.DrawImage(image, new Rectangle(0, 0, bm32b.Width, bm32b.Height)); // Bitmap format has its lines reversed. bm32b.RotateFlip(RotateFlipType.Rotate180FlipX); bm32bData = GetImageData(bm32b, out int stride); } // BITMAPINFOHEADER struct for DIB. int hdrSize = 0x28; byte[] fullImage = new byte[hdrSize + 12 + bm32bData.Length]; //Int32 biSize; WriteIntToByteArray(fullImage, 0x00, 4, true, (uint)hdrSize); //Int32 biWidth; WriteIntToByteArray(fullImage, 0x04, 4, true, (uint)width); //Int32 biHeight; WriteIntToByteArray(fullImage, 0x08, 4, true, (uint)height); //Int16 biPlanes; WriteIntToByteArray(fullImage, 0x0C, 2, true, 1); //Int16 biBitCount; WriteIntToByteArray(fullImage, 0x0E, 2, true, 32); //BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS; WriteIntToByteArray(fullImage, 0x10, 4, true, 3); //Int32 biSizeImage; WriteIntToByteArray(fullImage, 0x14, 4, true, (uint)bm32bData.Length); // These are all 0. Since .net clears new arrays, don't bother writing them. //Int32 biXPelsPerMeter = 0; //Int32 biYPelsPerMeter = 0; //Int32 biClrUsed = 0; //Int32 biClrImportant = 0; // The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values. WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000); WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00); WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF); Array.Copy(bm32bData, 0, fullImage, hdrSize + 12, bm32bData.Length); return fullImage; } /// /// Retrieves an image from the given clipboard data object, in the order PNG, DIB, Bitmap, Image object. /// /// The clipboard data. /// The extracted image, or null if no supported image type was found. public static Bitmap GetClipboardImage(DataObject retrievedData) { Bitmap clipboardimage = null; // Order: try PNG, move on to try 32-bit ARGB DIB, then try the normal Bitmap and Image types. if (retrievedData.GetDataPresent("PNG", false) && retrievedData.GetData("PNG", false) is MemoryStream pngStream) { using (Bitmap bm = new Bitmap(pngStream)) { clipboardimage = CloneImage(bm); } } if (clipboardimage == null && retrievedData.GetDataPresent(DataFormats.Dib, false) && retrievedData.GetData(DataFormats.Dib, false) is MemoryStream dib) { clipboardimage = ImageFromClipboardDib(dib.ToArray()); } if (clipboardimage == null && retrievedData.GetDataPresent(DataFormats.Bitmap)) clipboardimage = new Bitmap(retrievedData.GetData(DataFormats.Bitmap) as Image); if (clipboardimage == null && retrievedData.GetDataPresent(typeof(Image))) clipboardimage = new Bitmap(retrievedData.GetData(typeof(Image)) as Image); return clipboardimage; } public static Bitmap ImageFromClipboardDib(byte[] dibBytes) { if (dibBytes == null || dibBytes.Length < 4) return null; try { int headerSize = (int)ReadIntFromByteArray(dibBytes, 0, 4, true); // Only supporting 40-byte DIB from clipboard if (headerSize != 40) return null; byte[] header = new byte[40]; Array.Copy(dibBytes, header, 40); int imageIndex = headerSize; int width = (int)ReadIntFromByteArray(header, 0x04, 4, true); int height = (int)ReadIntFromByteArray(header, 0x08, 4, true); short planes = (short)ReadIntFromByteArray(header, 0x0C, 2, true); short bitCount = (short)ReadIntFromByteArray(header, 0x0E, 2, true); //Compression: 0 = RGB; 3 = BITFIELDS. int compression = (int)ReadIntFromByteArray(header, 0x10, 4, true); // Not dealing with non-standard formats. if (planes != 1 || (compression != 0 && compression != 3)) return null; PixelFormat fmt; switch (bitCount) { case 32: fmt = PixelFormat.Format32bppRgb; break; case 24: fmt = PixelFormat.Format24bppRgb; break; case 16: fmt = PixelFormat.Format16bppRgb555; break; default: return null; } if (compression == 3) imageIndex += 12; if (dibBytes.Length < imageIndex) return null; byte[] image = new byte[dibBytes.Length - imageIndex]; Array.Copy(dibBytes, imageIndex, image, 0, image.Length); // Classic stride: fit within blocks of 4 bytes. int stride = (((((bitCount * width) + 7) / 8) + 3) / 4) * 4; if (compression == 3) { uint redMask = ReadIntFromByteArray(dibBytes, headerSize + 0, 4, true); uint greenMask = ReadIntFromByteArray(dibBytes, headerSize + 4, 4, true); uint blueMask = ReadIntFromByteArray(dibBytes, headerSize + 8, 4, true); // Fix for the undocumented use of 32bppARGB disguised as BITFIELDS. Despite lacking an alpha bit field, // the alpha bytes are still filled in, without any header indication of alpha usage. // Pure 32-bit RGB: check if a switch to ARGB can be made by checking for non-zero alpha. // Admitted, this may give a mess if the alpha bits simply aren't cleared, but why the hell wouldn't it use 24bpp then? if (bitCount == 32 && redMask == 0xFF0000 && greenMask == 0x00FF00 && blueMask == 0x0000FF) { // Stride is always a multiple of 4; no need to take it into account for 32bpp. for (int pix = 3; pix < image.Length; pix += 4) { // 0 can mean transparent, but can also mean the alpha isn't filled in, so only check for non-zero alpha, // which would indicate there is actual data in the alpha bytes. if (image[pix] == 0) continue; fmt = PixelFormat.Format32bppPArgb; break; } } else // Could be supported with a system that parses the colour masks, // but I don't think the clipboard ever uses these anyway. return null; } Bitmap bitmap = BuildImage(image, width, height, stride, fmt, null, null); // This is bmp; reverse image lines. bitmap.RotateFlip(RotateFlipType.Rotate180FlipX); return bitmap; } catch { return null; } } public static void WriteIntToByteArray(byte[] data, int startIndex, int bytes, bool littleEndian, uint value) { int lastByte = bytes - 1; if (data.Length < startIndex + bytes) throw new ArgumentOutOfRangeException("startIndex", "Data array is too small to write a " + bytes + "-byte value at offset " + startIndex + "."); for (int index = 0; index < bytes; index++) { int offs = startIndex + (littleEndian ? index : lastByte - index); data[offs] = (byte)(value >> (8 * index) & 0xFF); } } public static uint ReadIntFromByteArray(byte[] data, int startIndex, int bytes, bool littleEndian) { int lastByte = bytes - 1; if (data.Length < startIndex + bytes) throw new ArgumentOutOfRangeException("startIndex", "Data array is too small to read a " + bytes + "-byte value at offset " + startIndex + "."); uint value = 0; for (int index = 0; index < bytes; index++) { int offs = startIndex + (littleEndian ? index : lastByte - index); value += (uint)(data[offs] << (8 * index)); } return value; } /// /// 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; } /// /// Creates a bitmap based on data, width, height, stride and pixel format. /// /// Byte array of raw source data /// Width of the image /// Height of the image /// Scanline length inside the data /// Pixel format /// Color palette /// Default color to fill in on the palette if the given colors don't fully fill it. /// The new image public static Bitmap BuildImage(byte[] sourceData, int width, int height, int stride, PixelFormat pixelFormat, Color[] palette, Color? defaultColor) { Bitmap newImage = new Bitmap(width, height, pixelFormat); BitmapData targetData = newImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, newImage.PixelFormat); int newDataWidth = ((Image.GetPixelFormatSize(pixelFormat) * width) + 7) / 8; // Compensate for possible negative stride on BMP format. bool isFlipped = stride < 0; stride = Math.Abs(stride); // Cache these to avoid unnecessary getter calls. int targetStride = targetData.Stride; long scan0 = targetData.Scan0.ToInt64(); for (int y = 0; y < height; y++) Marshal.Copy(sourceData, y * stride, new IntPtr(scan0 + (y * targetStride)), newDataWidth); newImage.UnlockBits(targetData); // Fix negative stride on BMP format. if (isFlipped) newImage.RotateFlip(RotateFlipType.Rotate180FlipX); // For indexed images, set the palette. if ((pixelFormat & PixelFormat.Indexed) != 0 && palette != null) { ColorPalette pal = newImage.Palette; for (int i = 0; i < pal.Entries.Length; i++) { if (i < palette.Length) pal.Entries[i] = palette[i]; else if (defaultColor.HasValue) pal.Entries[i] = defaultColor.Value; else break; } newImage.Palette = pal; } return newImage; } /// /// Clones an image object to free it from any backing resources. /// Code taken from http://stackoverflow.com/a/3661892/ with some extra fixes. /// /// The image to clone /// The cloned image public static Bitmap CloneImage(Bitmap sourceImage) { Rectangle rect = new Rectangle(0, 0, sourceImage.Width, sourceImage.Height); Bitmap targetImage = new Bitmap(rect.Width, rect.Height, sourceImage.PixelFormat); targetImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution); BitmapData sourceData = sourceImage.LockBits(rect, ImageLockMode.ReadOnly, sourceImage.PixelFormat); BitmapData targetData = targetImage.LockBits(rect, ImageLockMode.WriteOnly, targetImage.PixelFormat); int actualDataWidth = ((Image.GetPixelFormatSize(sourceImage.PixelFormat) * rect.Width) + 7) / 8; int h = sourceImage.Height; int origStride = sourceData.Stride; bool isFlipped = origStride < 0; origStride = Math.Abs(origStride); // Fix for negative stride in BMP format. int targetStride = targetData.Stride; byte[] imageData = new byte[actualDataWidth]; IntPtr sourcePos = sourceData.Scan0; IntPtr destPos = targetData.Scan0; // Copy line by line, skipping by stride but copying actual data width for (int y = 0; y < h; y++) { Marshal.Copy(sourcePos, imageData, 0, actualDataWidth); Marshal.Copy(imageData, 0, destPos, actualDataWidth); sourcePos = new IntPtr(sourcePos.ToInt64() + origStride); destPos = new IntPtr(destPos.ToInt64() + targetStride); } targetImage.UnlockBits(targetData); sourceImage.UnlockBits(sourceData); // Fix for negative stride on BMP format. if (isFlipped) targetImage.RotateFlip(RotateFlipType.Rotate180FlipX); // For indexed images, restore the palette. This is not linking to a referenced // object in the original image; the getter of Palette creates a new object when called. if ((sourceImage.PixelFormat & PixelFormat.Indexed) != 0) targetImage.Palette = sourceImage.Palette; // Restore DPI settings targetImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution); return targetImage; } public static Bitmap DIBV5ToBitmap(byte[] data) { GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); BITMAPV5HEADER bmi = (BITMAPV5HEADER)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(BITMAPV5HEADER)); int stride = -(int)(bmi.bV5SizeImage / bmi.bV5Height); long offset = bmi.bV5Size + ((bmi.bV5Height - 1) * (int)(bmi.bV5SizeImage / bmi.bV5Height)); if (bmi.bV5Compression == (uint)BitmapCompressionMode.BI_BITFIELDS) { offset += 12; } IntPtr scan0 = new IntPtr(handle.AddrOfPinnedObject().ToInt64() + offset); Bitmap bitmap = new Bitmap(bmi.bV5Width, bmi.bV5Height, stride, PixelFormat.Format32bppPArgb, scan0); handle.Free(); return bitmap; } } }