Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 624|回复: 0

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

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-7-21 12:05:20 | 显示全部楼层 |阅读模式

      在.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+的内部的一些机制上的问题吧。

        工程完整代码:http://files.cnblogs.com/Imageshop/GrayModeBitmap.rar

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

     

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

     

     

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-4-27 22:27 , Processed in 0.074428 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表