3

GDI+ 不支持CF_DIBV5格式化,但是当几个应用程序将透明图像放到剪贴板时,它们使用CF_DIBV5(格式 17)格式来维护 Alpha 通道。

.NET Framework 无法处理CF_DIBV5格式,因此 .NET 应用程序无法将 Alpha 图像放入/从剪贴板中检索。

是否有任何 c# 代码支持CF_DIBV5从剪贴板进行位图转换?

4

2 回答 2

6

对不起!我正在回答我自己的问题:

[StructLayout(LayoutKind.Sequential)]
public struct BITMAPV5HEADER
{
  public uint bV5Size;
  public int bV5Width;
  public int bV5Height;
  public UInt16 bV5Planes;
  public UInt16 bV5BitCount;
  public uint bV5Compression;
  public uint bV5SizeImage;
  public int bV5XPelsPerMeter;
  public int bV5YPelsPerMeter;
  public UInt16 bV5ClrUsed;
  public UInt16 bV5ClrImportant;
  public UInt16 bV5RedMask;
  public UInt16 bV5GreenMask;
  public UInt16 bV5BlueMask;
  public UInt16 bV5AlphaMask;
  public UInt16 bV5CSType;
  public IntPtr bV5Endpoints;
  public UInt16 bV5GammaRed;
  public UInt16 bV5GammaGreen;
  public UInt16 bV5GammaBlue;
  public UInt16 bV5Intent;
  public UInt16 bV5ProfileData;
  public UInt16 bV5ProfileSize;
  public UInt16 bV5Reserved;
}        
public static Bitmap CF_DIBV5ToBitmap(byte[] data)
{
    GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
    var bmi = (BITMAPV5HEADER)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(BITMAPV5HEADER));
    Bitmap bitmap = new Bitmap((int)bmi.bV5Width, (int)bmi.bV5Height, -
                               (int)(bmi.bV5SizeImage / bmi.bV5Height), PixelFormat.Format32bppArgb,
                               new IntPtr(handle.AddrOfPinnedObject().ToInt32()
                               + bmi.bV5Size + (bmi.bV5Height - 1) 
                               * (int)(bmi.bV5SizeImage / bmi.bV5Height)));
    handle.Free();
    return bitmap;
}   
于 2013-01-15T10:22:44.867 回答
0

这个解决方案对我来说很好用:从剪贴板复制到剪贴板会丢失图像透明度

    public static Image GetImageFromClipboard()
    {
        Image img = null;
        DataObject clipboard = Clipboard.GetDataObject() as DataObject;
        if (clipboard != null)
        {
            img = GetClipboardImage(clipboard);
        }

        return img;
    }

    /// <summary>
    /// Retrieves an image from the given clipboard data object, in the order PNG, DIB, Bitmap, Image object.
    /// </summary>
    /// <param name="retrievedData">The clipboard data.</param>
    /// <returns>The extracted image, or null if no supported image type was found.</returns>
    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"))
        {
            MemoryStream png_stream = retrievedData.GetData("PNG") as MemoryStream;
            if (png_stream != null)
                using (Bitmap bm = new Bitmap(png_stream))
                    clipboardimage = CloneImage(bm);
        }
        if (clipboardimage == null && retrievedData.GetDataPresent(DataFormats.Dib))
        {
            MemoryStream dib = retrievedData.GetData(DataFormats.Dib) as MemoryStream;
            if (dib != null)
                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;
    }

    private static Bitmap CloneImage(Bitmap bm)
    {
        int stride;
        return BuildImage(GetImageData(bm, out stride), bm.Width, bm.Height, stride, bm.PixelFormat, null, null);
    }

    /// <summary>
    /// Copies the given image to the clipboard as PNG, DIB and standard Bitmap format.
    /// </summary>
    /// <param name="image">Image to put on the clipboard.</param>
    /// <param name="imageNoTr">Optional specifically nontransparent version of the image to put on the clipboard.</param>
    /// <param name="data">Clipboard data object to put the image into. Might already contain other stuff. Leave null to create a new one.</param>
    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);
        }
    }

    /// <summary>
    /// 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.
    /// </summary>
    /// <param name="image">Image to convert to DIB</param>
    /// <returns>The image converted to DIB, in bytes.</returns>
    public static Byte[] ConvertToDib(Image image)
    {
        Byte[] bm32bData;
        Int32 width = image.Width;
        Int32 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.Format32bppArgb))
        {
            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);
            Int32 stride;
            bm32bData = GetImageData(bm32b, out stride);
        }
        // BITMAPINFOHEADER struct for DIB.
        Int32 hdrSize = 0x28;
        Byte[] fullImage = new Byte[hdrSize + 12 + bm32bData.Length];
        //Int32 biSize;
        WriteIntToByteArray(fullImage, 0x00, 4, true, (UInt32)hdrSize);
        //Int32 biWidth;
        WriteIntToByteArray(fullImage, 0x04, 4, true, (UInt32)width);
        //Int32 biHeight;
        WriteIntToByteArray(fullImage, 0x08, 4, true, (UInt32)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, (UInt32)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;
    }

    public static Bitmap ImageFromClipboardDib(Byte[] dibBytes)
    {
        if (dibBytes == null || dibBytes.Length < 4)
            return null;
        try
        {
            Int32 headerSize = (Int32)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);
            Int32 imageIndex = headerSize;
            Int32 width = (Int32)ReadIntFromByteArray(header, 0x04, 4, true);
            Int32 height = (Int32)ReadIntFromByteArray(header, 0x08, 4, true);
            Int16 planes = (Int16)ReadIntFromByteArray(header, 0x0C, 2, true);
            Int16 bitCount = (Int16)ReadIntFromByteArray(header, 0x0E, 2, true);
            //Compression: 0 = RGB; 3 = BITFIELDS.
            Int32 compression = (Int32)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.
            Int32 stride = (((((bitCount * width) + 7) / 8) + 3) / 4) * 4;
            if (compression == 3)
            {
                UInt32 redMask = ReadIntFromByteArray(dibBytes, headerSize + 0, 4, true);
                UInt32 greenMask = ReadIntFromByteArray(dibBytes, headerSize + 4, 4, true);
                UInt32 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 (Int32 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;
        }
    }

    /// <summary>
    /// Gets the raw bytes from an image.
    /// </summary>
    /// <param name="sourceImage">The image to get the bytes from.</param>
    /// <param name="stride">Stride of the retrieved image data.</param>
    /// <returns>The raw bytes of the image</returns>
    public static Byte[] GetImageData(Bitmap sourceImage, out Int32 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;
    }

    /// <summary>
    /// Creates a bitmap based on data, width, height, stride and pixel format.
    /// </summary>
    /// <param name="sourceData">Byte array of raw source data</param>
    /// <param name="width">Width of the image</param>
    /// <param name="height">Height of the image</param>
    /// <param name="stride">Scanline length inside the data</param>
    /// <param name="pixelFormat">Pixel format</param>
    /// <param name="palette">Color palette</param>
    /// <param name="defaultColor">Default color to fill in on the palette if the given colors don't fully fill it.</param>
    /// <returns>The new image</returns>
    public static Bitmap BuildImage(Byte[] sourceData, Int32 width, Int32 height, Int32 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);
        CopyToMemory(targetData.Scan0, sourceData, 0, sourceData.Length, stride, targetData.Stride);
        newImage.UnlockBits(targetData);
        // For indexed images, set the palette.
        if ((pixelFormat & PixelFormat.Indexed) != 0 && palette != null)
        {
            ColorPalette pal = newImage.Palette;
            for (Int32 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;
    }
    public static void CopyToMemory(IntPtr target, Byte[] bytes, Int32 startPos, Int32 length, Int32 origStride, Int32 targetStride)
    {
        Int32 sourcePos = startPos;
        IntPtr destPos = target;
        Int32 minStride = Math.Min(origStride, targetStride);
        while (length >= targetStride)
        {
            Marshal.Copy(bytes, sourcePos, destPos, minStride);
            length -= origStride;
            sourcePos += origStride;
            destPos = new IntPtr(destPos.ToInt64() + targetStride);
        }
        if (length > 0)
        {
            Marshal.Copy(bytes, sourcePos, destPos, length);
        }
    }

    public static void WriteIntToByteArray(Byte[] data, Int32 startIndex, Int32 bytes, Boolean littleEndian, UInt32 value)
    {
        Int32 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 (Int32 index = 0; index < bytes; index++)
        {
            Int32 offs = startIndex + (littleEndian ? index : lastByte - index);
            data[offs] = (Byte)(value >> (8 * index) & 0xFF);
        }
    }

    public static UInt32 ReadIntFromByteArray(Byte[] data, Int32 startIndex, Int32 bytes, Boolean littleEndian)
    {
        Int32 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 + ".");
        UInt32 value = 0;
        for (Int32 index = 0; index < bytes; index++)
        {
            Int32 offs = startIndex + (littleEndian ? index : lastByte - index);
            value += (UInt32)(data[offs] << (8 * index));
        }
        return value;
    }
于 2017-12-25T20:39:19.323 回答