欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.net下灰度模式图像在创建Graphics时出现:无法从带有索引像素格式的图像创建graphics对象 问题的解决方案。

程序员文章站 2023-08-25 23:39:12
    在.net下,如果你加载了一副8位的灰度图像,然后想向其中绘制一些线条、或者填充一些矩形、椭圆等,都需要先创建grahpics.fromimage创建grahp...

    在.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();