سؤال

بينما أحضر الصور إلى برنامجي ، أريد تحديد ما إذا كان:

  1. لديهم قناة ألفا
  2. إذا تم استخدام تلك القناة ألفا

#1 بسيط بما فيه الكفاية مع استخدام Image.IsAlphaPixelFormat. إلى عن على #2 على الرغم من ذلك ، بخلاف الحلق عبر كل بكسل ، هل هناك طريقة بسيطة يمكنني تحديد ما إذا كانت واحدة على الأقل من البكسلات تحتوي على قناة ألفا المستخدمة (أي ضبط على بعض القيمة الأخرى 255))؟ كل ما أحتاجه مرة أخرى هو منطقية ثم سأقرر ما إذا كان سيتم حفظه إلى 32 بت أو 24 بت.

تحديث: لقد اكتشفت ذلك ImageFlags.hastranslucent يجب أن توفر لي ما أبحث عنه - لسوء الحظ ، لا يعمل على الإطلاق. على سبيل المثال ، تستمر PNGs مع تنسيقات البكسل التي لديها على الأقل قناة ألفا البالغة 66 (شبه شفافة) في الإبلاغ False (الاستخدام: if((img.Flags & ImageFlags.HasTranslucent) == 4) ...;). لقد اختبرت على جميع أنواع الصور ، بما في ذلك .bmp التي لها قيمة ألفا> 0 و <255 وما زالت تقارير False. أي شخص يستخدم هذا من أي وقت مضى ومعرفة ما إذا كان يعمل حتى في GDI+؟

هل كانت مفيدة؟

المحلول

ليس عليك أن تحلق كل بكسل (حسنًا ، ولكن هذا يعتمد على الصورة). قم بإعداد لحلق على جميع وحدات البكسل ، ولكن فقط الخروج من الحلقة عندما تجد قيمة ألفا بخلاف 255 استخدم رمز الزائفة التالية:

bool hasAlpha = false;
foreach (var pixel in image)
{
    hasAlpha = pixel.Alpha != 255;
    if (hasAlpha)
    {
        break;
    }
}

سيكون عليك فقط التحقق من جميع وحدات البكسل للصور التي لا تحتوي على أي ألفا. بالنسبة للصور التي لها ألفا ، سوف يندلع هذا بسرعة كبيرة.

نصائح أخرى

لن تجد حلًا أفضل من هذا ، فقد استغرق الأمر مني ساعات لتحسين:

public bool IsAlphaBitmap(ref System.Drawing.Imaging.BitmapData BmpData)
{
    byte[] Bytes = new byte[BmpData.Height * BmpData.Stride];
    Marshal.Copy(BmpData.Scan0, Bytes, 0, Bytes.Length);
    for (p = 3; p < Bytes.Length; p += 4) {
        if (Bytes[p] != 255) return true;
    }
    return false;
}

أحصل على حل أكثر تقدماً ، استنادًا إلى إجابة chrisf:

public bool IsImageTransparent(Bitmap image,string optionalBgColorGhost)
    {
        for (int i = 0; i < image.Width; i++)
        {
            for (int j = 0; j < image.Height; j++)
            {
                var pixel = image.GetPixel(i, j);
                if (pixel.A != 255)
                    return true;
            }
        }

        //Check 4 corners to check if all of them are with the same color!
        if (!string.IsNullOrEmpty(optionalBgColorGhost))
        {
            if (image.GetPixel(0, 0).ToArgb() == GetColorFromString(optionalBgColorGhost).ToArgb())
            {
                if (image.GetPixel(image.Width - 1, 0).ToArgb() == GetColorFromString(optionalBgColorGhost).ToArgb())
                {
                    if (image.GetPixel(0, image.Height - 1).ToArgb() ==
                        GetColorFromString(optionalBgColorGhost).ToArgb())
                    {
                        if (image.GetPixel(image.Width - 1, image.Height - 1).ToArgb() ==
                            GetColorFromString(optionalBgColorGhost).ToArgb())
                        {
                            return true;
                        }
                    }
                }
            }
        }

        return false;
    }

    public static Color GetColorFromString(string colorHex)
    {
        return ColorTranslator.FromHtml(colorHex);
    }

يحتوي على سلسلة ألوان BG اختيارية لصور غير شفافة:

مثال على الاستخدام:

IsImageTransparent(new Bitmap(myImg),"#FFFFFF");

إن الجمع بين مجموعة من الطرق لأنواع مختلفة من الصور جعلني هذه الطريقة النهائية ، والتي يبدو أنها تقوم بعمل جيد لأي صورة تقوم بتفريغها فيها ، سواء كانت صورة GIF شفافة أو PNG تحتوي على قناة ألفا. بفضل إجابة إلمو لطريقة قراءة البايت السريعة.

ملاحظة جانبية: افعل ليس استعمال Image.IsAlphaPixelFormat(bitmap.PixelFormat)); ؛ يرى أن التنسيقات المليئة غير قادرة على أنه غير قادر على الفلفة ، في حين أن مثل هذه الصور يستطيع في الواقع تمتلك الشفافية. فقط ، وليس نوع "ألفا". مثل هذه الصور التي تدعم الشفافية 8 بت لها علامة Hasalpha ممكّنة ، بحيث لا يزال هناك فحص مفيد.

[ملاحظة: لقد قمت منذ ذلك الحين بتبسيط هذا المنطق بشكل كبير. انظر بلدي إجابة أخرى.]]

public static Boolean HasTransparency(Bitmap bitmap)
{
    // not an alpha-capable color format.
    if ((bitmap.Flags & (Int32)ImageFlags.HasAlpha) == 0)
        return false;
    // Indexed formats. Special case because one index on their palette is configured as THE transparent color.
    if (bitmap.PixelFormat == PixelFormat.Format8bppIndexed || bitmap.PixelFormat == PixelFormat.Format4bppIndexed)
    {
        ColorPalette pal = bitmap.Palette;
        // Find the transparent index on the palette.
        Int32 transCol = -1;
        for (int i = 0; i < pal.Entries.Length; i++)
        {
            Color col = pal.Entries[i];
            if (col.A != 255)
            {
                // Color palettes should only have one index acting as transparency. Not sure if there's a better way of getting it...
                transCol = i;
                break;
            }
        }
        // none of the entries in the palette have transparency information.
        if (transCol == -1)
            return false;
        // Check pixels for existence of the transparent index.
        Int32 colDepth = Image.GetPixelFormatSize(bitmap.PixelFormat);
        BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
        Int32 stride = data.Stride;
        Byte[] bytes = new Byte[bitmap.Height * stride];
        Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
        bitmap.UnlockBits(data);
        if (colDepth == 8)
        {
            // Last line index.
            Int32 lineMax = bitmap.Width - 1;
            for (Int32 i = 0; i < bytes.Length; i++)
            {
                // Last position to process.
                Int32 linepos = i % stride;
                // Passed last image byte of the line. Abort and go on with loop.
                if (linepos > lineMax)
                    continue;
                Byte b = bytes[i];
                if (b == transCol)
                    return true;
            }
        }
        else if (colDepth == 4)
        {
            // line size in bytes. 1-indexed for the moment.
            Int32 lineMax = bitmap.Width / 2;
            // Check if end of line ends on half a byte.
            Boolean halfByte = bitmap.Width % 2 != 0;
            // If it ends on half a byte, one more needs to be processed.
            // We subtract in the other case instead, to make it 0-indexed right away.
            if (!halfByte)
                lineMax--;
            for (Int32 i = 0; i < bytes.Length; i++)
            {
                // Last position to process.
                Int32 linepos = i % stride;
                // Passed last image byte of the line. Abort and go on with loop.
                if (linepos > lineMax)
                    continue;
                Byte b = bytes[i];
                if ((b & 0x0F) == transCol)
                    return true;
                if (halfByte && linepos == lineMax) // reached last byte of the line. If only half a byte to check on that, abort and go on with loop.
                    continue;
                if (((b & 0xF0) >> 4) == transCol)
                    return true;
            }
        }
        return false;
    }
    if (bitmap.PixelFormat == PixelFormat.Format32bppArgb || bitmap.PixelFormat == PixelFormat.Format32bppPArgb)
    {
        BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
        Byte[] bytes = new Byte[bitmap.Height * data.Stride];
        Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
        bitmap.UnlockBits(data);
        for (Int32 p = 3; p < bytes.Length; p += 4)
        {
            if (bytes[p] != 255)
                return true;
        }
        return false;
    }
    // Final "screw it all" method. This is pretty slow, but it won't ever be used, unless you
    // encounter some really esoteric types not handled above, like 16bppArgb1555 and 64bppArgb.
    for (Int32 i = 0; i < bitmap.Width; i++)
    {
        for (Int32 j = 0; j < bitmap.Height; j++)
        {
            if (bitmap.GetPixel(i, j).A != 255)
                return true;
        }
    }
    return false;
}

منذ النشر إجابتي الأولى هنا ، اكتشفت أن LockBits يمكن أن يكون الأمر في الواقع يتحول بيانات الصورة إلى تنسيق بكسل المطلوب. هذا يعني أنه ، بغض النظر عن الإدخال ، يمكنك ببساطة التحقق من البايتات "على أنها" 32 بت لكل بيانات ARGB بكسل. نظرًا لأن هذا التنسيق يحتوي على 4 بكسل ، والخطوة في إطار .NET دائما مضاعف 4 بايت, عادة جداً تصبح القضية المهمة المتمثلة في ضبط قراءة البيانات بشكل صحيح على أطوال سطر المسح غير ذي صلة. كل هذا يبسط بشكل كبير الرمز.

بالطبع ، لا تزال أول شيكتين من إجابتي الأخرى تنطبق ؛ فحص HasAlpha يعد العلم الموجود على أعلام Bitmap و Alpha على إدخالات لوحة التنسيقات المفهرسة طريقة أولية سريعة جدًا لتحديد ما إذا كانت الصورة يستطيع لديك شفافية ، قبل التبديل إلى عملية مسح البيانات الكاملة.

لقد اكتشفت أيضًا أن PNG المفهرسة مع لوحات قادرة على ألفا هو في الواقع شيء (رغم ذلك دعم ضعيف في .NET) ، لذلك فقط التحقق من لون واحد قادر على ألفا على التنسيقات المفهرسة أمر ساذج للغاية.

مع وضع كل ذلك في الاعتبار ، وعملية LINQ التي تحول فحص اللوحة إلى خط واحد ، يصبح الرمز النهائي المعدل هذا:

public static Boolean HasTransparency(Bitmap bitmap)
{
    // Not an alpha-capable color format. Note that GDI+ indexed images are alpha-capable on the palette.
    if (((ImageFlags)bitmap.Flags & ImageFlags.HasAlpha) == 0)
        return false;
    // Indexed format, and no alpha colours in the image's palette: immediate pass.
    if ((bitmap.PixelFormat & PixelFormat.Indexed) != 0 && bitmap.Palette.Entries.All(c => c.A == 255))
        return false;
    // Get the byte data 'as 32-bit ARGB'. This offers a converted version of the image data without modifying the original image.
    BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    Int32 len = bitmap.Height * data.Stride;
    Byte[] bytes = new Byte[len];
    Marshal.Copy(data.Scan0, bytes, 0, len);
    bitmap.UnlockBits(data);
    // Check the alpha bytes in the data. Since the data is little-endian, the actual byte order is [BB GG RR AA]
    for (Int32 i = 3; i < len; i += 4)
        if (bytes[i] != 255)
            return true;
    return false;
}

هذا يعمل ل أي تنسيق بكسل الإدخال ، سواء كان ذلك أو لا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top