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

     

     心細的朋友可以在測試中會發現,通過這種方式繪制的顏色可能和指定的顏色有所不同,比如上面我們要求繪制白色的橢圓,但是實際繪制的顏色是RGB(252,252,252)的,但是並不是所有的顏色都有誤差,引起這個的原因估計還是GDI+的內部的一些機制上的問題吧。

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

      希望朋友們喜歡我的文章。

 

 ***************************作者: laviewpbt   時間: 2013.7.13   聯系QQ:  33184777  轉載請保留本行信息*************************

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM