yajira 发表于 2011-7-9 16:57:22

在C#中使用GDI的简单总结

在C#默认支持的是GDI+库,使用GDI+库,有很丰富的函数和排版手段,可以满足大部分的要求.除非,你需要使用bitmap字体,GDI+对于字体的支持有很大限制,支持truetype字体,而对于点阵字体(栅格字体)则不再支持,但是很多字体都是这种点阵字体,这样就带来一个问题,使用不了了.而很多公司则会自己制作某些用途的字体,比如说在LED显示屏上使用,这个时候使用GDI+则不再是一个明智的选择了.此外GDI+虽然强大,但是经过测试发现效率却是低下,速度比GDI慢了不少.
但是CS的界面开发环境和编码习惯又很适合,所以比较折衷的解决方法是使用C#调用系统的gdi api函数实现编程.
首先C#环境中的color结构是AARRGGBB,而在win32的颜色结构却是AABBGGRR,所以如果不注意则会使到颜色出现偏差或者逆转.在.net环境的类库当中,在system.drawing的命名空间中有ColorTranslator.ToWin32()这个函数,来对color结构转换成win32的api使用的颜色结构.
GDI中比较重要的函数,gdi函数基本都是来之一个dll. Gdi32.dll
      
      public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

      
      public static extern bool DeleteObject(IntPtr objectHandle);


      
      public static extern bool FillRgn(IntPtr hdc, IntPtr hrgn, IntPtr hbr);

      
      public static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect,
            int nBottomRect);

      
      public static extern IntPtr CreateSolidBrush(Int32 crColor);

      
      public static extern int SetBkMode(IntPtr hdc, int iBkMode);
      public const int TRANSPARENT = 1;
      public const int OPAQUE = 2;

      
      static extern uint SetBkColor(IntPtr hdc, int crColor);

      
      static extern uint SetTextColor(IntPtr hdc, int crColor);


      
      static extern IntPtr CreateFontW(
                   Int32 nHeight,
                   Int32 nWidth,
                   Int32 nEscapement,
                   Int32 nOrientation,
                   FontWeight fnWeight,
                   Boolean fdwItalic,
                   Boolean fdwUnderline,
                   Boolean fdwStrikeOut,
                   FontCharSet fdwCharSet,
                   FontPrecision fdwOutputPrecision,
                   FontClipPrecision fdwClipPrecision,
                   FontQuality fdwQuality,
                   FontPitchAndFamily fdwPitchAndFamily,
                   String lpszFace);


      
      public static extern int GetTextFace(IntPtr hdc, int nCount,
          StringBuilder lpFaceName);

      public const Int32 LF_FACESIZE = 32;


      
      public static extern bool BitBlt(
      IntPtr hdcDest,   //    目标设备的句柄   
      int nXDest,         //    目标对象的左上角的X坐标   
      int nYDest,         //    目标对象的左上角的Y坐标   
      int nWidth,         //    目标对象的矩形的宽度   
      int nHeight,      //    目标对象的矩形的长度   
      IntPtr hdcSrc,      //    源设备的句柄   
      int nXSrc,          //    源对象的左上角的X坐标   
      int nYSrc,          //    源对象的左上角的X坐标   
      TernaryRasterOperations dwRop //    光栅的操作值   
      );

      
      public static extern bool StretchBlt(IntPtr hdcDest, int nXOriginDest, int nYOriginDest,
            int nWidthDest, int nHeightDest,
            IntPtr hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc,
            TernaryRasterOperations dwRop);

      
      public static extern bool GetTextExtentPoint(IntPtr hdc, string lpString,
            int cbString, ref Size lpSize);

      
      public static extern bool GetTextMetrics(IntPtr hdc, out TEXTMETRIC lptm);


      
      public static extern bool GetCharABCWidthsFloatW(IntPtr hdc, uint iFirstChar, uint iLastChar, ABCFloat[] lpABCF);

      
      public static extern bool TextOutW(IntPtr hdc, int nXStart, int nYStart,
            string lpString, int cbString);

      
      public static extern bool GetCharWidth32(IntPtr hdc, uint iFirstChar, uint iLastChar,
          int[] lpBuffer);

      
      public static extern int DrawText(IntPtr hdc, string lpStr, int nCount, ref Rect lpRect, dwDTFormat wFormat);


      
      static extern IntPtr CreateCompatibleDC(IntPtr hdc);

      
      static extern bool DeleteDC(IntPtr hdc);

这些都是在GDI中比较常用的函数,其中SelectObject,和DeleteObject更是关键,在GDI的使用中最容易出现的问题就是内存泄漏,很多时候是因为没有正确释放资源引起的.所以需要特别小心,在GDI中释放资源使用DeleteObject这个函数来释放.
下面来实现一些具体的函数

      /// <summary>
      /// 填充特定DC的一个区域的特定颜色
      /// </summary>
      /// <param name="hdc">给定DC</param>
      /// <param name="Rect">给定区域</param>
      /// <param name="FillColor">给定颜色</param>
      publicstatic void FillRect(IntPtr hdc, Rectangle Rect, Color FillColor)
      {            
            IntPtr fillBrush = CreateSolidBrush(ColorTranslator.ToWin32(FillColor));
            IntPtr rectR = CreateRectRgn(Rect.Left, Rect.Top, Rect.Right, Rect.Bottom);
            FillRgn(hdc, rectR, fillBrush);
            DeleteObject(rectR);
            DeleteObject(fillBrush);
      }
这个函数实现对一个区域填充一个颜色, 当中创建了画笔,创建了区域,然后最后,当然还要记得释放两者占用的资源.

创建字体函数
      public static IntPtr CreatFont(String FontName, Int32 Height, FontStyle Style)
      {
            IntPtr Result;// = IntPtr.Zero;
            FontWeight boldWeight = FontWeight.FW_NORMAL;
            Boolean Italic = false;
            Boolean Underline = false;
            Boolean Bold = false;

            if ((Style & FontStyle.Bold) != 0)
            {   
                Bold = true;
            }

            if ((Style & FontStyle.Italic) != 0)
            {
                Italic = true;
            }

            if ((Style & FontStyle.Underline) != 0)
            {
                Underline = true;
            }

            if (Bold)
            {
                boldWeight = FontWeight.FW_BOLD;
            }


            Result = CreateFontW(Height, 0, 0, 0, boldWeight, Italic, Underline, false,
                                 FontCharSet.DEFAULT_CHARSET, FontPrecision.OUT_DEFAULT_PRECIS,
                                 FontClipPrecision.CLIP_DEFAULT_PRECIS, FontQuality.DRAFT_QUALITY,
                                 FontPitchAndFamily.DEFAULT_PITCH, FontName);
            return Result;
      }
在.net中,默认的字体类,不支持点阵字体,所以要使用CreateFontW这个函数来创建自己的字体资源,其中大部分的选项都试用默认值即可.
然后就是设置画板字体,在GDI环境中,DC是带着字体的,而不像GDI+那样子是分离的,所以经常需要设置不同的字体
      public static IntPtr SetCanvasFont(IntPtr hdc, ApiFont NewFont)
      {
            IntPtr FontPtr = CreatFont(NewFont.Name, NewFont.Size, NewFont.Style);
            IntPtr OldPtr =SelectObject(hdc, FontPtr);
            DeleteObject(OldPtr);
            return OldPtr;
      }
这个函数,将DC原来的字体资源释放掉,这样的实现会带来一个新的问题,因为一般来说都需要将DC原来的字体资源再通过selectobject函数放回去,然后将新的字体资源释放掉.所以这个函数是要小心使用的.
所以就有了第二个版本
      public static IntPtr SetCanvasFontNotDelete(IntPtr hdc, ApiFont NewFont)
      {
            IntPtr FontPtr = CreatFont(NewFont.Name, NewFont.Size, NewFont.Style);
            IntPtr OldPtr = SelectObject(hdc, FontPtr);
            return OldPtr;
      }

这样子就可以手动的释放资源,但是需要特别注意,的是一定要记得释放掉字体资源.

一般的使用画图的步骤
                IntPtr pTarget = e.Graphics.GetHdc();
                IntPtr pSource = CreateCompatibleDC(pTarget);
                IntPtr pOrig = SelectObject(pSource, drawBmp.GetHbitmap());
                GDIApi.StretchBlt(pTarget, 0, 0, this.Width, this.Height, pSource, 0, 0, drawWidth, drawHeight, TernaryRasterOperations.SRCCOPY);
                IntPtr pNew = SelectObject(pSource, pOrig);
                DeleteObject(pNew);
                DeleteDC(pSource);
                e.Graphics.ReleaseHdc(pTarget);
这样子就可以将drawBmp画到e.graphics上面了.最重要的是后面释放掉资源,否则内存的泄漏速度是很厉害的.我的软件每次重画就有7M左右的泄漏.一下子从十几M的内存上升到几百M的内存占用

      
      public static extern int SetBkMode(IntPtr hdc, int iBkMode);
      public const int TRANSPARENT = 1;
      public const int OPAQUE = 2;
这个函数是设置透明度的,参数2如果为1则是透明,2则是不透明.
不透明的话,将字符串画上去的时候,会有白色的背景.一般来说设为透明.
获取字符或者字体的信息函数
      
      public static extern bool GetTextExtentPoint(IntPtr hdc, string lpString,
            int cbString, ref Size lpSize);
      
      public static extern bool GetTextMetrics(IntPtr hdc, out TEXTMETRIC lptm);
      
      public static extern bool GetCharABCWidthsFloatW(IntPtr hdc, uint iFirstChar, uint iLastChar, ABCFloat[] lpABCF);

      
      public static extern bool GetCharWidth32(IntPtr hdc, uint iFirstChar, uint iLastChar,
          int[] lpBuffer);
这几个函数都可以获取字体或者字符串占用的空间大小,而又各有区别.
GetTextExtentPoint可以方便的获取一个字符串,或者字符串的部分的长度,因为可以通过cbString这个长度来控制获取的范围.
GetTextMetrics则是获取一个字体的各种高度信息,包括height,ascent,descent,还包括字体能够表现的字符范围等等信息.
GetCharABCWidthsFloatW则是获取某段连续字符串的abcwidth信息,abcwidth信息在某些情况下,需要特别注意,否则斜体会排版得很难看.
GetCharWidth32获取一个连续的字符段的宽度信息, 但是根据实践,居然和GetTextExtentPoint获取的信息不大一致,暂时是少于实际占用的空间.使到计算出来的占用宽度实际上不足以容纳字符串的排版.


字符串的描绘
这是在gdi操作中非常重要的一部分,使用Gdi是因为需要使用特殊字体,而字体当然是针对字符串来使用的的.所以,这根本就是使用gdi的目的.
常用的字符串输出有来个函数
      
      public static extern bool TextOutW(IntPtr hdc, int nXStart, int nYStart,
            string lpString, int cbString);
   
      public static extern int DrawText(IntPtr hdc, string lpStr, int nCount, ref Rect lpRect, dwDTFormat wFormat);
TextOutW是一个比较简单的函数,适合一般的场合,只需要设置X和Y的坐标即可
DrawText,则会控制输出的空间大小,排版规则.比较适合需要精确控制的场所,又或者比如说输出阿拉伯文字的时候,要设置为右对齐.

获取系统所有的字体
由于C#不支持点阵字体,所以自然地,使用.net提供的函数,获取的安装字体列表自然是不包含点阵字体的.所以并不符合我的要求.所以还得使用系统的api函数,来获取安装字体列表.
      private Int32 EnumFontCallBack(ref ENUMLOGFONTEX lpelfe, IntPtr lpntme, int FontType, int lParam)
      {
            //Debug.WriteLine(lpelfe.elfFullName);
            if (lpelfe.elfFullName.Substring(0, 1) != "@")
            {
                if (!sysFontList.Contains(lpelfe.elfFullName))
                {
                  sysFontList.Add(lpelfe.elfFullName);
                }

            }

            return 1;
      }

            LOGFONT logfont = new LOGFONT();
            logfont.lfCharSet = FontCharSet.DEFAULT_CHARSET;
            Bitmap bmp = new Bitmap(10, 10);
            Graphics g = Graphics.FromImage(bmp);
            IntPtr hDC = g.GetHdc();
            EnumFontFamilies.EnumFontFamiliesEx(hDC, logfont, EnumFontCallBack, IntPtr.Zero, 0);
            g.ReleaseHdc(hDC);
            g.Dispose();
            bmp.Dispose();

    public class EnumFontFamilies
    {
      public const int LF_FACESIZE = 32;
      
      public delegate int EnumFontExDelegate( ref ENUMLOGFONTEX lpelfe, IntPtr lpntme, int FontType, int lParam );

      
      public static extern int EnumFontFamiliesEx( IntPtr hDC, LOGFONT logFont, EnumFontExDelegate enumFontExCallback,
             IntPtr lParam, uint dwFlags );

    }

其中EnumFontCallBack为回调函数,通过这个回调函数,可以获取到系统所有的字体,包括点阵的字体.



附录A:
GDI使用的结构和常量
   
    public struct ENUMLOGFONTEX
    {
      public LOGFONT elfLogFont;
      
      public string elfFullName;
      
      public string elfStyle;
      
      public string elfScript;
    }


   
    public class LOGFONT
    {
      public int lfHeight;
      public int lfWidth;
      public int lfEscapement;
      public int lfOrientation;
      public FontWeight lfWeight;
      
      public bool lfItalic;
      
      public bool lfUnderline;
      
      public bool lfStrikeOut;
      public FontCharSet lfCharSet;
      public FontPrecision lfOutPrecision;
      public FontClipPrecision lfClipPrecision;
      public FontQuality lfQuality;
      public FontPitchAndFamily lfPitchAndFamily;
      
      public string lfFaceName;

      public override string ToString()
      {
            StringBuilder sb = new StringBuilder();
            sb.Append("LOGFONT\n");
            sb.AppendFormat("   lfHeight: {0}\n", lfHeight);
            sb.AppendFormat("   lfWidth: {0}\n", lfWidth);
            sb.AppendFormat("   lfEscapement: {0}\n", lfEscapement);
            sb.AppendFormat("   lfOrientation: {0}\n", lfOrientation);
            sb.AppendFormat("   lfWeight: {0}\n", lfWeight);
            sb.AppendFormat("   lfItalic: {0}\n", lfItalic);
            sb.AppendFormat("   lfUnderline: {0}\n", lfUnderline);
            sb.AppendFormat("   lfStrikeOut: {0}\n", lfStrikeOut);
            sb.AppendFormat("   lfCharSet: {0}\n", lfCharSet);
            sb.AppendFormat("   lfOutPrecision: {0}\n", lfOutPrecision);
            sb.AppendFormat("   lfClipPrecision: {0}\n", lfClipPrecision);
            sb.AppendFormat("   lfQuality: {0}\n", lfQuality);
            sb.AppendFormat("   lfPitchAndFamily: {0}\n", lfPitchAndFamily);
            sb.AppendFormat("   lfFaceName: {0}\n", lfFaceName);

            return sb.ToString();
      }
    }

    public enum FontWeight : int
    {
      FW_DONTCARE = 0,
      FW_THIN = 100,
      FW_EXTRALIGHT = 200,
      FW_LIGHT = 300,
      FW_NORMAL = 400,
      FW_MEDIUM = 500,
      FW_SEMIBOLD = 600,
      FW_BOLD = 700,
      FW_EXTRABOLD = 800,
      FW_HEAVY = 900,
    }
    public enum FontCharSet : byte
    {
      ANSI_CHARSET = 0,
      DEFAULT_CHARSET = 1,
      SYMBOL_CHARSET = 2,
      SHIFTJIS_CHARSET = 128,
      HANGEUL_CHARSET = 129,
      HANGUL_CHARSET = 129,
      GB2312_CHARSET = 134,
      CHINESEBIG5_CHARSET = 136,
      OEM_CHARSET = 255,
      JOHAB_CHARSET = 130,
      HEBREW_CHARSET = 177,
      ARABIC_CHARSET = 178,
      GREEK_CHARSET = 161,
      TURKISH_CHARSET = 162,
      VIETNAMESE_CHARSET = 163,
      THAI_CHARSET = 222,
      EASTEUROPE_CHARSET = 238,
      RUSSIAN_CHARSET = 204,
      MAC_CHARSET = 77,
      BALTIC_CHARSET = 186,
    }
    public enum FontPrecision : byte
    {
      OUT_DEFAULT_PRECIS = 0,
      OUT_STRING_PRECIS = 1,
      OUT_CHARACTER_PRECIS = 2,
      OUT_STROKE_PRECIS = 3,
      OUT_TT_PRECIS = 4,
      OUT_DEVICE_PRECIS = 5,
      OUT_RASTER_PRECIS = 6,
      OUT_TT_ONLY_PRECIS = 7,
      OUT_OUTLINE_PRECIS = 8,
      OUT_SCREEN_OUTLINE_PRECIS = 9,
      OUT_PS_ONLY_PRECIS = 10,
    }
    public enum FontClipPrecision : byte
    {
      CLIP_DEFAULT_PRECIS = 0,
      CLIP_CHARACTER_PRECIS = 1,
      CLIP_STROKE_PRECIS = 2,
      CLIP_MASK = 0xf,
      CLIP_LH_ANGLES = (1 << 4),
      CLIP_TT_ALWAYS = (2 << 4),
      CLIP_DFA_DISABLE = (4 << 4),
      CLIP_EMBEDDED = (8 << 4),
    }
    public enum FontQuality : byte
    {
      DEFAULT_QUALITY = 0,
      DRAFT_QUALITY = 1,
      PROOF_QUALITY = 2,
      NONANTIALIASED_QUALITY = 3,
      ANTIALIASED_QUALITY = 4,
      CLEARTYPE_QUALITY = 5,
      CLEARTYPE_NATURAL_QUALITY = 6,
    }
   
    public enum FontPitchAndFamily : byte
    {
      DEFAULT_PITCH = 0,
      FIXED_PITCH = 1,
      VARIABLE_PITCH = 2,
      FF_DONTCARE = (0 << 4),
      FF_ROMAN = (1 << 4),
      FF_SWISS = (2 << 4),
      FF_MODERN = (3 << 4),
      FF_SCRIPT = (4 << 4),
      FF_DECORATIVE = (5 << 4),
    }

    /// <summary>
    /// Enumeration for the raster operations used in BitBlt.
    /// In C++ these are actually #define. But to use these
    /// constants with C#, a new enumeration type is defined.
    /// </summary>
    public enum TernaryRasterOperations
    {
      SRCCOPY = 0x00CC0020, /* dest = source                   */
      SRCPAINT = 0x00EE0086, /* dest = source OR dest         */
      SRCAND = 0x008800C6, /* dest = source AND dest          */
      SRCINVERT = 0x00660046, /* dest = source XOR dest          */
      SRCERASE = 0x00440328, /* dest = source AND (NOT dest )   */
      NOTSRCCOPY = 0x00330008, /* dest = (NOT source)             */
      NOTSRCERASE = 0x001100A6, /* dest = (NOT src) AND (NOT dest) */
      MERGECOPY = 0x00C000CA, /* dest = (source AND pattern)   */
      MERGEPAINT = 0x00BB0226, /* dest = (NOT source) OR dest   */
      PATCOPY = 0x00F00021, /* dest = pattern                  */
      PATPAINT = 0x00FB0A09, /* dest = DPSnoo                   */
      PATINVERT = 0x005A0049, /* dest = pattern XOR dest         */
      DSTINVERT = 0x00550009, /* dest = (NOT dest)               */
      BLACKNESS = 0x00000042, /* dest = BLACK                  */
      WHITENESS = 0x00FF0062, /* dest = WHITE                  */
    };

   
    public enum dwDTFormat : int
    {
      DT_TOP = 0, DT_LEFT = 0x00000000, DT_CENTER = 0x00000001, DT_RIGHT = 0x00000002,
      DT_VCENTER = 0x00000004, DT_BOTTOM = 0x00000008, DT_WORDBREAK = 0x00000010, DT_SINGLELINE = 0x00000020,
      DT_EXPANDTABS = 0x00000040, DT_TABSTOP = 0x00000080, DT_NOCLIP = 0x00000100, DT_EXTERNALLEADING = 0x00000200,
      DT_CALCRECT = 0x00000400, DT_NOPREFIX = 0x00000800, DT_INTERNAL = 0x00001000
    };


    public struct Rect
    {
      public int Left, Top, Right, Bottom;
      public Rect(Rectangle r)
      {
            this.Left = r.Left;
            this.Top = r.Top;
            this.Bottom = r.Bottom;
            this.Right = r.Right;
      }
    }


   
    public struct TEXTMETRIC
    {
      public Int32 tmHeight;
      public Int32 tmAscent;
      public Int32 tmDescent;
      public Int32 tmInternalLeading;
      public Int32 tmExternalLeading;
      public Int32 tmAveCharWidth;
      public Int32 tmMaxCharWidth;
      public Int32 tmWeight;
      public Int32 tmOverhang;
      public Int32 tmDigitizedAspectX;
      public Int32 tmDigitizedAspectY;
      public char tmFirstChar;
      public char tmLastChar;
      public char tmDefaultChar;
      public char tmBreakChar;
      public byte tmItalic;
      public byte tmUnderlined;
      public byte tmStruckOut;
      public byte tmPitchAndFamily;
      public byte tmCharSet;
    }

durgy 发表于 2011-7-9 18:01:49

good ,正用上,请教一下如何用setdibits,
int SetDIBits(HDC hdc, HBITMAP hbmp, UINT uStartScan, UINT cScanLines, CONST VOID *lpvBits,CONST BITMAPINFO *lpbmi, UINT fuColorUse);
BITMAPINFO 这个在C#里面不知道是用什么类型来代替

yajira 发表于 2011-7-9 21:18:32

我对这个函数没用过
根据我的经验

static extern int SetDIBits(IntPtr hdc, IntPtr hbmp, uint uStartScan, uint
   cScanLines, byte [] lpvBits, ref BITMAPINFO lpbmi, uint fuColorUse);

typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUAD          bmiColors;
} BITMAPINFO, *PBITMAPINFO;
都是将这些结构换成C#形式的结构体就好了

typedef struct tagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;

typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
LONGbiWidth;
LONGbiHeight;
WORDbiPlanes;
WORDbiBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONGbiXPelsPerMeter;
LONGbiYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

linjs137 发表于 2011-7-11 08:47:08

学了C#三年多了 gdi方面一直没机会接触, 希望楼主能发个项目实例,学习起来比较直观

durgy 发表于 2011-7-11 09:15:12

非常感谢!试试先

lxlylm 发表于 2011-7-11 12:56:10

先MARK

yongke 发表于 2022-9-28 16:47:28

MARK,后面学习一下
页: [1]
查看完整版本: 在C#中使用GDI的简单总结