c# - للصورة - كيفية جعل الخلفية شفافة في الفوتوشوب



النسخ من وإلى الحافظة يفقد شفافية الصورة (1)

لا تدعم حافظة Windows ، بشكل افتراضي ، الشفافية ، ولكن يمكنك وضع المحتوى في الحافظة في أنواع كثيرة معًا للتأكد من أن معظم التطبيقات تعثر على بعض الأنواع التي يمكنها استخدامها. للأسف ، النوع الأكثر شيوعًا (والذي يبدو أن نظام التشغيل Windows نفسه يستخدمه) هو نوع متسخ وغير موثوق به حقًا. كتبت تفسيرا صاخبا حول هذا الموضوع here .

سأفترض أنك قرأت ذلك قبل المتابعة مع إجابتي هنا ، لأنها تحتوي على معلومات الخلفية المطلوبة للجزء التالي.

الآن ، أنظف طريقة لوضع صورة على الحافظة مع دعم الشفافية هي دفق PNG ، لكنه لن يضمن أن جميع التطبيقات يمكنها لصقها. يدعم Gimp ملف PNG ، وعلى ما يبدو يفعل كذلك برامج MS Office الأحدث ، ولكن Google Chrome ، على سبيل المثال ، لا ، ولن يقبل سوى نوع DIB الفوضوي المفصل في الإجابة التي قمت بالربط بها. من ناحية أخرى ، لن تقبل Gimp أن يكون لـ DIB شفافية ، لأن منشئيها اتبعوا بالفعل مواصفات التنسيق ، وأدركوا أن التنسيق لم يكن موثوقًا به (كما هو واضح من ذلك السؤال الذي قمت بربطه).

بسبب فوضى DIB ، للأسف ، أفضل ما يمكنك فعله هو وضعه في أكبر عدد ممكن من الأنواع المدعومة بشكل عام ، بما في ذلك PNG و DIB والصورة العادية.

يتم وضع كل من PNG و DIB على الحافظة بنفس الطريقة: عن طريق وضعها في DataObject كـ MemoryStream ، ثم إعطاء الحافظة تعليمة "نسخ" عند وضعها فعليًا.

معظم هذا واضح ومباشر ، لكن DIB واحد أكثر تعقيدًا قليلاً. لاحظ أن الجزء التالي يحتوي على اثنين من المراجع إلى مجموعات الأدوات الخاصة بي. يمكن العثور على BuildImage في هذه الإجابة ، ويمكن العثور على ArrayUtils here ، ويتم تقديم ArrayUtils أدناه.

/// <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 = ImageUtils.GetImageData(bm32b, out stride);
    }
    // BITMAPINFOHEADER struct for DIB.
    Int32 hdrSize = 0x28;
    Byte[] fullImage = new Byte[hdrSize + 12 + bm32bData.Length];
    //Int32 biSize;
    ArrayUtils.WriteIntToByteArray(fullImage, 0x00, 4, true, (UInt32)hdrSize);
    //Int32 biWidth;
    ArrayUtils.WriteIntToByteArray(fullImage, 0x04, 4, true, (UInt32)width);
    //Int32 biHeight;
    ArrayUtils.WriteIntToByteArray(fullImage, 0x08, 4, true, (UInt32)height);
    //Int16 biPlanes;
    ArrayUtils.WriteIntToByteArray(fullImage, 0x0C, 2, true, 1);
    //Int16 biBitCount;
    ArrayUtils.WriteIntToByteArray(fullImage, 0x0E, 2, true, 32);
    //BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS;
    ArrayUtils.WriteIntToByteArray(fullImage, 0x10, 4, true, 3);
    //Int32 biSizeImage;
    ArrayUtils.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.
    ArrayUtils.WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000);
    ArrayUtils.WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00);
    ArrayUtils.WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF);
    Array.Copy(bm32bData, 0, fullImage, hdrSize + 12, bm32bData.Length);
    return fullImage;
}

الآن ، فيما يتعلق بإخراج صورة من الحافظة ، لاحظت أن هناك على ما يبدو اختلافًا في السلوك بين .Net 3.5 والإصدارات الأحدث ، والتي يبدو أنها تستخدم بالفعل DIB. بالنظر إلى هذا الاختلاف ، وبالنظر إلى مدى عدم موثوقية تنسيق DIB ، ستحتاج إلى التحقق يدويًا من جميع الأنواع ، ويفضل أن تبدأ بتنسيق PNG الموثوق به تمامًا.

يمكنك الحصول على DataObject من الحافظة باستخدام هذا الرمز:

DataObject retrievedData = Clipboard.GetDataObject() as DataObject;

إن وظيفة CloneImage المستخدمة هنا هي في الأساس مجرد مزيج من مجموعتي GetImageData و BuildImage ، مما يضمن إنشاء صورة جديدة دون أي موارد دعم قد تؤدي إلى الفوضى ؛ من المعروف أن كائنات الصور تتسبب في حدوث أعطال عندما تعتمد على Stream يتم التخلص منه بعد ذلك. تم نشر نسخة مضغوطة ومحسّنة منه هنا ، في سؤال يستحق القراءة حول موضوع أهمية الاستنساخ.

/// <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 = ImageUtils.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;
}

public static Bitmap ImageFromClipboardDib(Byte[] dibBytes)
{
    if (dibBytes == null || dibBytes.Length < 4)
        return null;
    try
    {
        Int32 headerSize = (Int32)ArrayUtils.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)ArrayUtils.ReadIntFromByteArray(header, 0x04, 4, true);
        Int32 height = (Int32)ArrayUtils.ReadIntFromByteArray(header, 0x08, 4, true);
        Int16 planes = (Int16)ArrayUtils.ReadIntFromByteArray(header, 0x0C, 2, true);
        Int16 bitCount = (Int16)ArrayUtils.ReadIntFromByteArray(header, 0x0E, 2, true);
        //Compression: 0 = RGB; 3 = BITFIELDS.
        Int32 compression = (Int32)ArrayUtils.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 = ArrayUtils.ReadIntFromByteArray(dibBytes, headerSize + 0, 4, true);
            UInt32 greenMask = ArrayUtils.ReadIntFromByteArray(dibBytes, headerSize + 4, 4, true);
            UInt32 blueMask = ArrayUtils.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 = ImageUtils.BuildImage(image, width, height, stride, fmt, null, null);
        // This is bmp; reverse image lines.
        bitmap.RotateFlip(RotateFlipType.Rotate180FlipX);
        return bitmap;
    }
    catch
    {
        return null;
    }
}

نظرًا لأن BitConverter يتطلب دائمًا إجراء فحص غبي على نهاية النظام ، فقد حصلت على ReadIntFromByteArray و WriteIntToByteArray في فئة ArrayUtils :

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;
}

أحاول نسخ صورة PNG شفافة إلى الحافظة والحفاظ على شفافيتها للصقها في برنامج معين يدعمها.

جربت العديد من الحلول بالفعل ولكن الخلفية انتهت دائمًا باللون الرمادي بطريقة أو بأخرى.

لذا حاولت نسخ نفس الصورة باستخدام Chrome ولصقها في البرنامج وقد نجحت. حافظت على الشفافية. إذن ، حاولت الحصول على الصورة من "الحافظة" التي قمت بنسخها باستخدام Chrome وتعيين الصورة مرة أخرى ، متوقعًا أن تظل الشفافية موجودة - لكن لا ، لم يتم الحفاظ على الشفافية على الرغم من أنني أخذت الصورة من الحافظة وقمت بتعيينها مرة أخرى.

var img = Clipboard.GetImage(); // copied using Chrome and transparency is preserved
Clipboard.SetImage(img); // transparency lost

نفس المشكلة حتى لو كنت تستخدم System.Windows.Forms.Clipboard أو حاول الحصول على DataObject بدلاً من الصورة.





clipboard