Преобразование цвета в монохромный - PullRequest
0 голосов
/ 29 июня 2018

См .: Сохранить 32-битное растровое изображение как 1-битный файл .bmp в C #

Объявление № 1

    public static Bitmap BitmapTo1Bpp(Bitmap source)
    {
        int Width = source.Width; 
        int Height = source.Height; 

        Bitmap dest = new Bitmap(Width, Height, PixelFormat.Format1bppIndexed);
        BitmapData destBmpData = dest.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);

        byte[] destBytes = new byte[(Width + 7) / 8];//19 bytes

        for (int y = 0; y < Height; y++)
        {
            for (int x = 0; x < Width; x++)
            {
                Color c = source.GetPixel(x, y);

                if (x % 8 == 0)
                {
                    destBytes[x / 8] = 0;
                }
                if (c.GetBrightness() >= 0.5)
                {
                    destBytes[x / 8] |= (byte)(0x80 >> (x % 8));
                }
            }
            Marshal.Copy(destBytes, 0, (IntPtr)((long)destBmpData.Scan0 + destBmpData.Stride * y), destBytes.Length);
        }

        dest.UnlockBits(destBmpData);
        return dest;
    }

Листинг № 2

    public static Bitmap BitmapTo1Bpp222(Bitmap source)
    {
        int Width = source.Width; 
        int Height = source.Height; 

        Bitmap dest = new Bitmap(Width, Height, PixelFormat.Format1bppIndexed);
        BitmapData destBmpData = dest.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);

        int destStride = destBmpData.Stride;
        int destSize = Math.Abs(destStride) * Height;

        byte[] destBytes = new byte[destSize];

        for (int y = 0; y < Height; y++)
        {
            for (int x = 0; x < Width; x++)
            {
                Color c = source.GetPixel(x, y);

                if (x % 8 == 0)
                {
                    destBytes[x*y / 8] = 0;
                }
                if (c.GetBrightness() >= 0.5)
                {
                    destBytes[x*y / 8] |= (byte)(0x80 >> (x % 8));
                }
            } 
        } 
        Marshal.Copy(destBytes, 0, destBmpData.Scan0, destBytes.Length);
        dest.UnlockBits(destBmpData);
        return dest;
    } 

См. Положение Marshal.Copy().

Почему листинг № 1 работает, а листинг № 2 - нет?

Какая модификация может заставить Листинг №2 работать?

1 Ответ

0 голосов
/ 02 июля 2018

Оба они слишком сложны. LockBits может конвертировать данные в 1bpp. Просто откройте исходный код как 1bpp, скопируйте его данные в новый образ 1bpp, и все готово.

Я также весьма озадачен комбинацией GetPixel и LockBits. Обычно использование LockBits означает, что вы поняли, что GetPixel - это ужасно медленная трата времени, которая выполняет LockBits внутренне для каждый вызов .

public static Bitmap BitmapTo1Bpp(Bitmap source)
{
    Rectangle rect = new Rectangle(0, 0, source.Width, source.Height);
    Bitmap dest = new Bitmap(rect.Width, rect.Height, PixelFormat.Format1bppIndexed);
    dest.SetResolution(source.HorizontalResolution, source.VerticalResolution);
    BitmapData sourceData = source.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed);
    BitmapData targetData = dest.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);
    Int32 actualDataWidth = (rect.Width + 7) / 8;
    Int32 h = source.Height;
    Int32 origStride = sourceData.Stride;
    Int32 targetStride = targetData.Stride;
    // buffer for one line of image data.
    Byte[] imageData = new Byte[actualDataWidth];
    Int64 sourcePos = sourceData.Scan0.ToInt64();
    Int64 destPos = targetData.Scan0.ToInt64();
    // Copy line by line, skipping by stride but copying actual data width
    for (Int32 y = 0; y < h; y++)
    {
        Marshal.Copy(new IntPtr(sourcePos), imageData, 0, actualDataWidth);
        Marshal.Copy(imageData, 0, new IntPtr(destPos), actualDataWidth);
        sourcePos += origStride;
        destPos += targetStride;
    }
    dest.UnlockBits(targetData);
    source.UnlockBits(sourceData);
    return dest;
}

Обратите внимание, что преобразования данных в индексированные форматы следует избегать в тех случаях, когда ваш результат не равен 1 bpp для чисто черного и белого цветов. Индексированные форматы имеют палитру, и это не приведет ни к какому уменьшению до оптимизированной палитры, приближающейся к цветам изображения; он просто изменит цвета на изображении до их наиболее близкого соответствия на стандартной палитре для этой битовой глубины. Для 1bpp это просто черно-белый, что идеально, но для 4bpp и 8bpp это даст довольно плохие результаты.

Также обратите внимание, что по некоторым причинам вы не можете конвертировать из более высокого в более низкий индексированный формат пикселей; это бросит исключение. Поскольку вы можете преобразовать растровое изображение в 32-разрядное с помощью конструктора new Bitmap(Bitmap), эту проблему легко избежать, вызвав такой код:

public static Bitmap ConvertTo1Bpp(Bitmap source)
{
    PixelFormat sourcePf = source.PixelFormat;
    if ((sourcePf & PixelFormat.Indexed) == 0 || Image.GetPixelFormatSize(sourcePf) == 1)
        return BitmapTo1Bpp(source);
    using (Bitmap bm32 = new Bitmap(source))
        return BitmapTo1Bpp(bm32);
}
...