Преобразование 24bpp в 8bpp. Как я могу исправить этот метод, чтобы он возвращал растровое изображение с положительным шагом
Я ищу быстрый способ конвертировать Bitmap из 24bpp в 8bpp.
Я нашел решение на этом сайте Программист "1bpp в C#. Это работает, но результирующий шаг растрового изображения отрицателен!
How can I fix the stride of the image?
Я уже пробовал это:
Bitmap.Clone: шаг по-прежнему отрицательный
new Bitmap(b0): создает 32-битное изображение... ¬¬
РЕДАКТИРОВАТЬ: сохранить в файл, прочитать его обратно и сделать глубокую копию (Marshal.Copy или memcopy) данных пикселей, чтобы отделить растровое изображение от файла работает.. Но это "вроде" уродливо...
Код ниже:
/// <summary>
/// Copy a bitmap into a 1bpp/8bpp bitmap of the same dimensions, fast
/// </summary>
/// <param name="source">original bitmap</param>
/// <param name="bpp">1 or 8, target bpp</param>
/// <returns>a 1bpp copy of the bitmap</returns>
/// <url>http://www.wischik.com/lu/programmer/1bpp.html</url>
private static Bitmap ConvertTo1or8bppNegativeStride(Bitmap source, int bpp = 8)
{
if (bpp != 1 && bpp != 8) throw new System.ArgumentException("1 or 8", "bpp");
// Plan: built into Windows GDI is the ability to convert
// bitmaps from one format to another. Most of the time, this
// job is actually done by the graphics hardware accelerator card
// and so is extremely fast. The rest of the time, the job is done by
// very fast native code.
// We will call into this GDI functionality from C#. Our plan:
// (1) Convert our Bitmap into a GDI hbitmap (ie. copy unmanaged->managed)
// (2) Create a GDI monochrome hbitmap
// (3) Use GDI "BitBlt" function to copy from hbitmap into monochrome (as above)
// (4) Convert the monochrone hbitmap into a Bitmap (ie. copy unmanaged->managed)
int w = source.Width, h = source.Height;
IntPtr hbm = source.GetHbitmap(); // this is step (1)
//
// Step (2): create the monochrome bitmap.
// "BITMAPINFO" is an interop-struct which we define below.
// In GDI terms, it's a BITMAPHEADERINFO followed by an array of two RGBQUADs
BITMAPINFO bmi = new BITMAPINFO();
bmi.biSize = 40; // the size of the BITMAPHEADERINFO struct
bmi.biWidth = w;
bmi.biHeight = h;
bmi.biPlanes = 1; // "planes" are confusing. We always use just 1. Read MSDN for more info.
bmi.biBitCount = (short)bpp; // ie. 1bpp or 8bpp
bmi.biCompression = BI_RGB; // ie. the pixels in our RGBQUAD table are stored as RGBs, not palette indexes
bmi.biSizeImage = (uint)(((w + 7) & 0xFFFFFFF8) * h / 8);
bmi.biXPelsPerMeter = 1000000; // not really important
bmi.biYPelsPerMeter = 1000000; // not really important
// Now for the colour table.
uint ncols = (uint)1 << bpp; // 2 colours for 1bpp; 256 colours for 8bpp
bmi.biClrUsed = ncols;
bmi.biClrImportant = ncols;
bmi.cols = new uint[256]; // The structure always has fixed size 256, even if we end up using fewer colours
if (bpp == 1)
{
bmi.cols[0] = MAKERGB(0, 0, 0);
bmi.cols[1] = MAKERGB(255, 255, 255);
}
else
{
for (int i = 0; i < ncols; i++)
bmi.cols[i] = MAKERGB(i, i, i);
}
// For 8bpp we've created an palette with just greyscale colours.
// You can set up any palette you want here. Here are some possibilities:
// greyscale: for (int i=0; i<256; i++) bmi.cols[i]=MAKERGB(i,i,i);
// rainbow: bmi.biClrUsed=216; bmi.biClrImportant=216; int[] colv=new int[6]{0,51,102,153,204,255};
// for (int i=0; i<216; i++) bmi.cols[i]=MAKERGB(colv[i/36],colv[(i/6)%6],colv[i%6]);
// optimal: a difficult topic: http://en.wikipedia.org/wiki/Color_quantization
//
// Now create the indexed bitmap "hbm0"
IntPtr bits0; // not used for our purposes. It returns a pointer to the raw bits that make up the bitmap.
IntPtr hbm0 = CreateDIBSection(IntPtr.Zero, ref bmi, DIB_RGB_COLORS, out bits0, IntPtr.Zero, 0);
//
// Step (3): use GDI's BitBlt function to copy from original hbitmap into monocrhome bitmap
// GDI programming is kind of confusing... nb. The GDI equivalent of "Graphics" is called a "DC".
IntPtr sdc = GetDC(IntPtr.Zero); // First we obtain the DC for the screen
// Next, create a DC for the original hbitmap
IntPtr hdc = CreateCompatibleDC(sdc); SelectObject(hdc, hbm);
// and create a DC for the monochrome hbitmap
IntPtr hdc0 = CreateCompatibleDC(sdc); SelectObject(hdc0, hbm0);
// Now we can do the BitBlt:
BitBlt(hdc0, 0, 0, w, h, hdc, 0, 0, SRCCOPY);
// Step (4): convert this monochrome hbitmap back into a Bitmap:
Bitmap b0 = Bitmap.FromHbitmap(hbm0);
//
// Finally some cleanup.
DeleteDC(hdc);
DeleteDC(hdc0);
ReleaseDC(IntPtr.Zero, sdc);
DeleteObject(hbm);
DeleteObject(hbm0);
//It have negative stride...
return b0;
}
1 ответ
Согласно документации для структуры BITMAPINFOHEADER:
biHeight Определяет высоту растрового изображения в пикселях.
Если biHeight положителен, растровое изображение является восходящим DIB, а его источником является левый нижний угол.
Если biHeight отрицателен, растровое изображение является DIB сверху вниз, а его источником является верхний левый угол.
Если biHeight отрицателен, указывая на нисходящий DIB, biCompression должен быть или BI_RGB или BI_BITFIELDS. Нисходящие DIB не могут быть сжаты.
Поэтому, если вы измените строку в коде, который вы разместили на:
bmi.biHeight = -h;
Затем он создаст сверху вниз растровое изображение с положительным шагом.
-
Есть и другие возможности.
var newBmp = srcBmp.Clone(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), PixelFormat.Format8bppIndexed);
Для меня это создает растровое изображение с положительным шагом. Но тогда у оригинального растрового изображения есть положительный шаг. Я не знаю, что это будет делать, если я назову это на растровом изображении с отрицательным шагом.
После всего этого мне интересно, какую проблему вы действительно пытаетесь решить. Почему это важно, если растровое изображение снизу вверх или сверху вниз?