博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
.net下灰度模式图像在创建Graphics时出现:无法从带有索引像素格式的图像创建graphics对象 问题的解决方案。...
阅读量:5953 次
发布时间:2019-06-19

本文共 12227 字,大约阅读时间需要 40 分钟。

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

     

     心细的朋友可以在测试中会发现,通过这种方式绘制的颜色可能和指定的颜色有所不同,比如上面我们要求绘制白色的椭圆,但是实际绘制的颜色是RGB(252,252,252)的,但是并不是所有的颜色都有误差,引起这个的原因估计还是GDI+的内部的一些机制上的问题吧。

   工程完整代码:

      希望朋友们喜欢我的文章。

 

*******基本上我不提供源代码,但是我会尽量用文字把对应的算法描述清楚或提供参考文档**********

*******因为靠自己的努力和实践写出来的效果才真正是自己的东西,人一定要靠自己**************

********作者: laviewpbt   时间: 2013.7.5   联系QQ:  33184777  转载请保留本行信息*******

 

你可能感兴趣的文章
PLSQL用DBMS_JOB建立作业
查看>>
实战 SQL Server 2008 数据库误删除数据的恢复
查看>>
从函数调用来思考多态
查看>>
[Oracle]如果误删了某个数据文件,又没有被备份,能否恢复?
查看>>
Access访问错误集锦:Access关键字
查看>>
iostat命令详解
查看>>
女子监狱第一季/全集Orange Is the New Black迅雷下载
查看>>
vc中运行外部程序的方法
查看>>
[51单片机] SPI nRF24L01无线 [可以放在2个单片机里实现通信]
查看>>
面试题:在O(1)空间复杂度范围内对一个数组中前后连段有序数组进行归并排序...
查看>>
用ajax技术实现无闪烁定时刷新页面
查看>>
子进程清理
查看>>
HDU1022 Train Problem I
查看>>
微软已停止对Vista RTM(SP0)的服务支持
查看>>
Spring依赖检查
查看>>
第 72 章 FAQ
查看>>
Activity 切换 动画
查看>>
[LeetCode] Sum of Left Leaves 左子叶之和
查看>>
[LeetCode] Find Median from Data Stream
查看>>
3.6. Pure-FTPd + LDAP + MySQL + PGSQL + Virtual-Users + Quota
查看>>