Как я могу скопировать данные пикселей из растрового изображения с отрицательным шагом?

Я искал самый быстрый способ конвертировать растровое изображение в 8bpp. Я нашел 2 способа:

1.

        public static System.Drawing.Image ConvertTo8bpp(Bitmap oldbmp)
    {
        using (var ms = new MemoryStream())
        {
            oldbmp.Save(ms, ImageFormat.Gif);
            ms.Position = 0;
            return System.Drawing.Image.FromStream(ms);
        }
    }

2. http://www.wischik.com/lu/programmer/1bpp.html

Но: 1. Результат очень низкого качества (плохая палитра).

и 2 дает мне растровое изображение с отрицательным шагом, когда я пытаюсь зафиксировать биты и скопировать данные в массив байтов, я получаю исключение: Попытка чтения или записи защищенной памяти. Часто это указывает на то, что другая память повреждена.

        BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);

        this.stride = bmpData.Stride;
        this.bytesPerPixel = GetBytesPerPixel(bmp.PixelFormat);
        int length = bmpData.Stride * bmp.Height;
        if (this.stride < 0)
            this.data = new byte[-length];
        else
            this.data = new byte[length];
        Marshal.Copy(bmpData.Scan0, data, 0, length);

        //Unlock the bitmap
        bmp.UnlockBits(bmpData);

Как сделать так, чтобы 2 давал положительный результат? Или как я могу копировать данные, используя локбиты отрицательного шага??


person Pedro77    schedule 26.07.2011    source источник
comment
В первом фрагменте есть ошибка, использование using приводит к разрыву растрового изображения.   -  person Hans Passant    schedule 29.04.2012


Ответы (5)


Копируйте по одной строке за раз, вычисляя начальный указатель для строки как ((byte*)scan0 + (y * stride)). Код будет одинаковым как для положительного, так и для отрицательного шага.

person Rick Brewster    schedule 28.04.2012

Проблема здесь в том, что Scan0 указывает на начало первой строки сканирования, а не на начало первого байта данных. В восходящем растровом изображении первая строка сканирования находится в Stride байтах от конца данных растрового изображения.

Когда вы вызываете Marshal.Copy для копирования данных из Scan0, он пытается скопировать (Height*Stride) байт, начиная с позиции ((Height-1)*Stride). Ясно, что это убежит в сорняки.

Если вы просто хотите скопировать растровые данные, вам нужно вычислить начальный адрес с помощью Scan0 - (Height-1)*Stride. Это запустит вас в начале данных растрового изображения. Вы можете передать этот вычисленный адрес Marshal.Copy.

Если вы хотите скопировать строки сканирования по порядку (т. е. вверху, рядом, рядом, ... внизу), вам нужно копировать строку за раз: копировать Stride байтов из Scan0, затем добавить Stride (что отрицательно), скопируйте эту строку и т. д. У Рика Брюстера был правильный ответ: https://stackoverflow.com/a/10360753/56778

person Jim Mischel    schedule 14.06.2013
comment
хорошо хорошо, это отвечает на этот вопрос. Но я не хочу копировать построчно... Я хочу, чтобы отрицательный шаг никогда не случался. Вот почему я создал новый вопрос (stackoverflow.com/questions/17115555/) - person Pedro77; 14.06.2013
comment
(Я работаю на C++, а не на C#, но я предполагаю, что это все еще полезно.) LockBits сам может скопировать данные для вас, если вы включите бит ImageLockModeUserInputBuf в качестве одного из флагов. Чтобы это работало, шаг должен быть отрицательным, а Scan0 должен быть data - (Height - 1) * Stride. (Обратите внимание, что минус фактически увеличивает указатель, начиная с Stride < 0.) - person Tyler; 28.05.2014

Я не знаю, почему в Bitmap, созданном методом FromHbitmap, есть что-то странное, но я знаю, что вы можете исправить это, используя Bitmap bmpClone = (Bitmap)bmp.Clone(); и выполняя LockBits на bmpClone.

Кроме того, я обнаружил, что если вы используете bmp.Clone(), вы не можете Dispose() для bmp, пока не закончите с клоном.

Это также работает и позволяет вам избавиться от отрицательного изображения шага раньше, чем позже:

        Bitmap bmp = null;
        using (Bitmap bmpT = CopyToBpp(bmpO, 1))
        {
            bmp = new Bitmap(bmpT);
        }
person JamieSee    schedule 26.04.2012
comment
Да.. Это возможное решение. - person Pedro77; 27.04.2012
comment
По памяти, я думаю, это должно помочь. Вы должны клонировать исходное растровое изображение. Раньше я записывал результаты напрямую с помощью небезопасного кода из одного растрового изображения в новое растровое изображение с желаемым форматом пикселей и т. д. - person Zac; 03.05.2012
comment
Я вернулся к этой проблеме, и нет, теперь это решение, потому что оно приведет к изображению с разрешением 32 бита на пиксель!! Суть в том, чтобы перейти с 24bpp на 8bpp! - person Pedro77; 14.06.2013

Из документации C# по BitmapData: шаг — это ширина одной строки пикселей (линии сканирования), округленная до четырехбайтовой границы. Если шаг положительный, растровое изображение располагается сверху вниз. Если шаг отрицательный, растровое изображение отображается снизу вверх

person Patrick Hughes    schedule 26.07.2011
comment
Шаг, который вы записываете в this.stride, является абсолютным значением свойства bmpData.Stride. Старые API-интерфейсы Windows плохо относились к добавлению дополнительной информации в подобные данные. - person Patrick Hughes; 26.07.2011
comment
Почему решение 2 превращает шаг в отрицательный?? - person Pedro77; 26.07.2011
comment
Установка старшего бита целого числа - это просто способ Microsoft добавить, что я перевернутый флаг в структуру растрового изображения, не добавляя больше места. BMP очень старые, и раньше очень важно было экономить место. С тех пор растровому коду приходится беспокоиться об этом. - person Patrick Hughes; 26.07.2011
comment
Хорошо, и как я могу скопировать данные пикселей из этого растрового изображения в массив байтов? - person Pedro77; 26.07.2011
comment
Я не знаю, как проголосовать за это как ответ, но это не отвечает на мой вопрос. Только ctrl+c ctrl+v определения шага, которое я уже знаю! - person Pedro77; 28.07.2011

Я предполагаю, что исключение, которое вы получаете, связано с

this.data = new byte[-length];

А затем пытается скопировать данные в массив байтов отрицательного размера (я не понимаю, как это вообще компилируется...).

person Ben F    schedule 03.05.2012
comment
Это не так. Посмотрите еще раз на его код. Он пытается применить -length, чтобы изменить отрицательное значение на положительное, только если результирующая длина отрицательного шага меньше 0. - person JamieSee; 03.05.2012