.net下灰度模式图像在创建Graphics时出现:无法从带有索引像素格式的图像创建graphics对象 问题的解决方案。
在.net下,如果你加载了一副8位的灰度图像,然后想向其中绘制一些线条、或者填充一些矩形、椭圆等,都需要先创建grahpics.fromimage创建grahphics对象,而此时会出现:无法从带有索引像素格式的图像创建graphics对象 这个错误,让我们的后续工作无法完成。本文叙述了一种另外的方法来实现它。
我们通过reflector发编译.net framework的相关函数后发现,fromimage的实现过程如下:
public static graphics fromimage(image image)
{
if (image == null)
{
throw new argumentnullexception("image");
}
if ((image.pixelformat & pixelformat.indexed) != pixelformat.undefined)
{
throw new exception(sr.getstring("gdipluscannotcreategraphicsfromindexedpixelformat"));
}
intptr zero = intptr.zero;
int status = safenativemethods.gdip.gdipgetimagegraphicscontext(new handleref(image, image.nativeimage), out zero);
if (status != 0)
{
throw safenativemethods.gdip.statusexception(status);
}
return new graphics(zero) { backingimage = image };
}
而在msdn中,对gdipgetimagegraphicscontext函数的描述有如下部分:
this constructor also fails if the image uses one of the following pixel formats:
pixelformatundefined
pixelformatdontcare
pixelformat1bppindexed
pixelformat4bppindexed
pixelformat8bppindexed
pixelformat16bppgrayscale
pixelformat16bppargb1555
因此,.net是判断当图像为索引模式时,直接返回错误,而不是通过判断gdipgetimagegraphicscontext的返回值来实现的。
针对这个事实,我们其实觉得也无可厚非,graphics对象是用来干什么的,是用来向对应的image中添加线条,路径、实体图形、图像数据等的,而普通的索引图像,其矩阵的内容并不是实际的颜色值,而只是个索引,真正的颜色值在调色板中,因此,一些绘制的过程用在索引图像上存在着众多的不适。
但是有个特列,那就是灰度图像,严格的说,灰度图像完全符合索引图像的格式,可以认为是索引图像的一种特例。但是我也可以认为他不属于索引图像一类:即他的图像数据总的值可以认为就是其颜色值,我们可以抛开其调色板中的数据。所以在photoshop中把索引模式和灰度模式作为两个模式来对待。
真是有这个特殊性,一些画线、填充路径等等的过程应该可以在灰度图像中予以实现,单gdi+为了规避过多的判断,未对该模式进行特殊处理。
但是,在一些特殊的场合,对灰度进行上述操作很有用途和意义。比如:在高级的图像设计中,有着选区的概念,而选区的实质上就是一副灰度图像,如果我们创建一个椭圆选区,设计上就是在灰度图像上填充了一个椭圆。如果能借助gdi+提供的优质的抗锯齿填充模式加上丰富*的填充函数,那么就可以创建出多种多样的选区了。可.net的一个无法创建graphics让我们此路不通。
有没有办法呢,其实也是有的,熟悉gdi+平板化api的人还知道有gdipcreatefromhdc函数,该函数可以从hdc中创建graphics。因此我的想法就是利用gdi的方式创建位图对象吗,然后从gdi的hdc中创建对应的graphics。经过实践,这种方法是可以行的。
为此,我用gdi结合gdi+的方式创建了一个graybitmap类,该类的主要代码如下:
unsafe class graybitmap
{
#region gdiapi
private const int dib_rgb_colors = 0;
private const int bi_rgb = 0;
[structlayout(layoutkind.sequential, pack = 1)]
private struct rgbquad
{
internal byte blue;
internal byte green;
internal byte red;
internal byte reserved;
}
[structlayout(layoutkind.sequential, pack = 1)]
private struct bitmapinfoheader
{
internal uint size;
internal int width;
internal int height;
internal ushort planes;
internal ushort bitcount;
internal uint compression;
internal uint sizeimage;
internal int xpelspermeter;
internal int ypelspermeter;
internal uint clrused;
internal uint clrimportant;
}
[structlayout(layoutkind.sequential, pack = 1)]
private struct bitmapinfo
{
internal bitmapinfoheader header;
[marshalas(unmanagedtype.byvalarray, sizeconst = 256)]
internal rgbquad[] palette;
}
[structlayout(layoutkind.sequential)]
internal struct logpalette
{
internal ushort palversion;
internal ushort palnumentries;
[marshalas(unmanagedtype.byvalarray, sizeconst = 0x400)]
internal byte[] palpalentry;
}
[dllimport("user32.dll", setlasterror = true)]
private extern static intptr getdc(intptr hwnd);
[dllimport("user32.dll", setlasterror = true)]
private extern static int releasedc(intptr hwnd, intptr hdc);
[dllimport("gdi32.dll", setlasterror = true)]
private extern static intptr createcompatibledc(intptr hdc);
[dllimport("gdi32.dll", setlasterror = true)]
private static extern uint setdibcolortable(intptr hdc, int un1, int un2, rgbquad[] pcrgbquad);
[dllimport("gdi32.dll", setlasterror = true)]
private static extern intptr createdibsection(intptr hdc, ref bitmapinfo bmpinfo, uint iusage, out byte* ppvbits, intptr hsection, uint dwoffset);
[dllimport("gdi32.dll", setlasterror = true)]
private extern static boolean deletedc(intptr hdc);
[dllimport("gdi32.dll", setlasterror = true)]
private extern static intptr selectobject(intptr hdc, intptr object);
[dllimport("gdi32.dll", setlasterror = true)]
private static extern bool deleteobject(intptr object);
#endregion
#region privatevariable
private int m_width = 0;
private int m_height = 0;
private int m_stride = 0;
private intptr m_hdc = intptr.zero;
private graphics m_graphics = null;
private intptr m_handle = intptr.zero;
private byte* m_pointer = null;
private bitmap m_bitmap = null;
private bool disposed = false;
#endregion
#region property
public int width { get { return m_width; } }
public int height { get { return m_height; } }
public int stride { get { return m_stride; } }
public intptr handle { get { return m_handle; } }
public intptr hdc { get { return m_hdc; } }
public graphics graphics { get { return m_graphics; } }
public byte* pointer { get { return m_pointer; } }
public bitmap bitmap { get { return m_bitmap; } }
#endregion
#region constructor
public graybitmap(int width, int height)
{
allocatebitmap(width, height);
}
public graybitmap(string filename)
{
bitmap bmp = (bitmap)bitmap.fromfile(filename);
if (isgraybitmap(bmp) == false)
{
bmp.dispose();
throw new exception("wrong pixelformat");
}
else
{
allocatebitmap(bmp.width, bmp.height);
bitmapdata bmpdata = new bitmapdata();
bmpdata.scan0 = (intptr)m_pointer;
bmpdata.stride = m_stride; // 把image对象的数据拷贝到dibseciton中去
bmp.lockbits(new rectangle(0, 0, bmp.width, bmp.height), imagelockmode.readwrite | imagelockmode.userinputbuffer, bmp.pixelformat, bmpdata);
bmp.unlockbits(bmpdata);
bmp.dispose();
}
}
public graybitmap(bitmap bmp)
{
if (isgraybitmap(bmp) == false)
throw new exception("wrong pixelformat");
else
{
allocatebitmap(bmp.width, bmp.height);
bitmapdata bmpdata = new bitmapdata();
bmpdata.scan0 = (intptr)m_pointer;
bmpdata.stride = m_stride; // 把image对象的数据拷贝到dibseciton中去
bmp.lockbits(new rectangle(0, 0, bmp.width, bmp.height), imagelockmode.readwrite | imagelockmode.userinputbuffer, bmp.pixelformat, bmpdata);
bmp.unlockbits(bmpdata);
}
}
~graybitmap()
{
dispose();
}
#endregion
#region publicmethod
public static graybitmap fromfile(string filename)
{
graybitmap bmp = new graybitmap(filename);
return bmp;
}
public void dispose()
{
dispose(true);
}
protected void dispose(bool suppress = true)
{
if (disposed == false)
{
disposed = true;
if (m_hdc != intptr.zero) deletedc(m_hdc); m_hdc = intptr.zero;
if (m_graphics != null) m_graphics.dispose(); m_graphics = null;
if (m_bitmap != null) m_bitmap.dispose(); m_bitmap = null;
if (m_handle != intptr.zero) deleteobject(m_handle); m_handle = intptr.zero;
m_width = 0; m_height = 0; m_stride = 0; m_pointer = null;
if (suppress == true) gc.suppressfinalize(this);
}
}
#endregion
#region privatemethod
private void allocatebitmap(int width, int height)
{
if (width <= 0) throw new argumentoutofrangeexception("width", width, "width must be >=0");
if (height <= 0) throw new argumentoutofrangeexception("height", height, "height must be >=0");
bitmapinfo bmpinfo = new bitmapinfo();
bmpinfo.header.size = (uint)sizeof(bitmapinfoheader);
bmpinfo.header.width = width;
bmpinfo.header.height = -height; // 为了和gdi对象的坐标(起点坐标在左上角),建立一个倒序的dib
bmpinfo.header.bitcount = (ushort)8; ;
bmpinfo.header.planes = 1;
bmpinfo.header.compression = bi_rgb; // 创建dibsection必须用不压缩的格式
bmpinfo.header.xpelspermeter = 0; // createdibsection does not use the bitmapinfoheader parameters bixpelspermeter or biypelspermeter and will not provide resolution information in the bitmapinfo structure.
bmpinfo.header.ypelspermeter = 0;
bmpinfo.header.clrused = 0;
bmpinfo.header.sizeimage = 0;
bmpinfo.header.clrimportant = 0;
bmpinfo.header.sizeimage = 0;
bmpinfo.palette = new rgbquad[256];
for (int x = 0; x <256 ; x++) // for (byte x=0;x<=255;x++) 用这个代码试试,呵呵
{
bmpinfo.palette[x].red = (byte)x;
bmpinfo.palette[x].green = (byte)x;
bmpinfo.palette[x].blue = (byte)x;
bmpinfo.palette[x].reserved = 255;
}
intptr screecdc = getdc(intptr.zero);
m_hdc = createcompatibledc(screecdc);
releasedc(intptr.zero, screecdc);
m_handle = createdibsection(hdc, ref bmpinfo, dib_rgb_colors, out m_pointer, intptr.zero, 0);
if (m_handle == intptr.zero)
{
deletedc(m_hdc);
m_hdc = intptr.zero;
throw new outofmemoryexception("createdibsection function failed,this may be caused by user input too large size of image.");
}
else
{
selectobject(m_hdc, m_handle);
setdibcolortable(m_hdc, 0, 256, bmpinfo.palette);
m_width = width; m_height = height; m_stride = (int)((m_width + 3) & 0xfffffffc);
m_graphics = graphics.fromhdc(m_hdc);
m_bitmap = new bitmap(m_width, m_height, m_stride, pixelformat.format8bppindexed, (intptr)m_pointer);
colorpalette pal = m_bitmap.palette;
for (int x = 0; x < 256; x++) pal.entries[x] = color.fromargb(255, x, x, x); // 设置灰度图像的调色板
m_bitmap.palette = pal;
}
}
private bool isgraybitmap(bitmap bmp)
{
bool isgray;
if (bmp.pixelformat == pixelformat.format8bppindexed)
{
isgray = true;
if (bmp.palette.entries.length != 256)
isgray = false;
else
{
for (int x = 0; x < bmp.palette.entries.length; x++)
{
if (bmp.palette.entries[x].r != bmp.palette.entries[x].g || bmp.palette.entries[x].r != bmp.palette.entries[x].b || bmp.palette.entries[x].b != bmp.palette.entries[x].g)
{
isgray = false;
break;
}
}
}
}
else
{
isgray = false;
}
return isgray;
}
#endregion
}
正如上面所述,我们用gdi的方式(createdibsection)创建灰度图像,然后从hdc中创建graphics,从而可以顺利的调用graphics的任何绘制函数了。
比如填充椭圆:
solidbrush sb = new solidbrush(color.fromargb(255, 255, 255, 255));
bmp.graphics.smoothingmode = system.drawing.drawing2d.smoothingmode.antialias;
bmp.graphics.fillellipse(sb, new rectangle(100, 100, 200, 300));
sb.dispose();
canvas.invalidate();