• 【学习笔记】Windows GDI绘图(九)Graphics详解(上)



    蓦然回首,写了这么多篇关于GDI+绘图的笔记,也一直在使用的Graphics对象,既然还没深入了解Graphics,在此补上吧。
    (注意,本文中示例的方法都是在OnPaint事件中运行)
    全文图像
    Graphics详解上

    Graphics 定义

    public sealed class Graphics : MarshalByRefObject, IDisposable, System.Drawing.IDeviceContext
    

    Graphics 类提供用于将对象绘制到显示设备的方法。 与 Graphics 特定设备上下文关联。

    创建Graphics对象的方法

    • 通过对继承自 System.Windows.Forms.Control的对象调用 Control.CreateGraphics 方法
    • 通过处理控件的事件Control.Paint并访问 Graphics 类的 属性来获取 Graphics 对象System.Windows.Forms.PaintEventArgs
    • 使用 FromImage 方法从图像创建 Graphics 对象

    通过Graphics绘制不同的形状、线条、图像和文字等

    • 提供DrawLine、 DrawArc、 DrawClosedCurve、DrawCurve、DrawEllipse、DrawPie、
      DrawPolygon、DrawBezier和 DrawRectangle等方法
    • 提供DrawImage、DrawImageUnscale、DrawIcon和DrawString等方法

    通过Graphics操作对象坐标

    操作Transform或执行相关矩阵变换

    Graphics属性

    Clip(裁切/绘制区域)

    原型:

    public System.Drawing.Region Clip { get; set; }
    

    作用:获取或设置Grahics的裁切区域

    var region1 = new Region(new Rectangle(105, 100, 200, 30));
    var region2 = new Region(new Rectangle(315, 100, 215, 30));
    //合并两个区域
    region1.Union(region2);
    
    //设置裁切(绘制区域)
    e.Graphics.Clip = region1;
    
    // 填充裁切区域
    e.Graphics.FillRegion(Brushes.LightGreen, e.Graphics.Clip);
    
    //演示裁切区域 
    e.Graphics.DrawString("裁切区域外的文字是看不到的", new Font("黑体",
        28.0F, FontStyle.Regular), Brushes.Black, 90F, 90F);
    

    设置两个裁切区域并填充颜色,将要输出的文字一部分在区域内,一部分在区域外,显示其效果。
    Clip

    ClipBounds获取裁切区域矩形范围

    原型:

    public System.Drawing.RectangleF ClipBounds { get; }
    

    作用:获取Graphics的裁切区域的外包矩形。生成的矩形单位用PageUnit决定。
    注意,当裁切区域是无限的时,返回的矩形是一个无意义的大矩形。需要确定区域是否为无限的,可通过IsInfinite判断。

    int offset = 20;
    if (e.Graphics.Clip.IsInfinite(e.Graphics))
    {
        DrawString(e, $"Clip为无限区域", ref offset);
    }
    else
    {
        //获取裁切区域
        var rect = e.Graphics.ClipBounds;
        DrawString(e, $"ClipBounds:({rect.X},{rect.Y},{rect.Width},{rect.Height})", ref offset);
        var rect2 = e.ClipRectangle;
    
        DrawString(e, $"ClipRectangle:({rect2.X},{rect2.Y},{rect2.Width},{rect2.Height})", ref offset);
        //默认的裁切区域就是控件的可见范围
        e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(127,Color.LightGreen)), 
            new RectangleF(rect.X + 10, rect.Y + 10, rect.Width - 20, rect.Height - 20));
    }
    

    判断裁切区域是否为无限的,否则获取Graphics的裁切区域外接矩形,并四周各减去10像素后,填充。
    ClipBounds

    CompositiongMode合成方式

    原型:

    public System.Drawing.Drawing2D.CompositingMode CompositingMode { get; set; }
    

    CompositingMode枚举

    SourceCopy指定渲染颜色时,会覆盖背景颜色
    SourceOver会与背景颜色混合,混合由正在渲染的颜色的Alpha分量决定
    作用:获取或设置绘制图像时的合成方式(默认是SourceOver)。合成模式确定源图像中的像素是否覆盖或与背景像素组合。
    注意:当TextRenderingHint为ClearTypeGridFit时,不能设为SourceCopy。
    Graphics g = e.Graphics;
    Rectangle rect = new Rectangle(150, 150, 200, 200);
    
    // 设置背景颜色
    g.Clear(Color.White);
    
    // 使用半透明颜色绘制重叠矩形
    using (Brush redBrush = new SolidBrush(Color.FromArgb(128, Color.Red)))         
    using (Brush greenBrush = new SolidBrush(Color.FromArgb(128, Color.Green)))
    using (Brush blueBrush = new SolidBrush(Color.FromArgb(128, Color.Blue)))
    {
        var dy = 100f;
        var dx = (float)(dy / Math.Sqrt(3));
        // 设置合成模式为 SourceOver
        g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
        // 绘制三个圆填充红、绿、蓝
        g.FillEllipse(redBrush, rect);
        g.TranslateTransform(-dx, dy);
        g.FillEllipse(greenBrush, rect);
        g.TranslateTransform(2 * dx, 0);
        g.FillEllipse(blueBrush, rect);
    
    
        g.ResetTransform();
        g.TranslateTransform(360, 0);
        // 设置合成模式为 SourceCopy
        g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
        // 绘制三个圆填充红、绿、蓝
        g.FillEllipse(redBrush, rect);
        g.TranslateTransform(-dx, dy);
        g.FillEllipse(greenBrush, rect);
        g.TranslateTransform(2 * dx, 0);
        g.FillEllipse(blueBrush, rect);
    }
    

    使用不同的CompositingMode模式,绘制三原色红、绿、蓝的叠加效果。原本想实现三色光的效果,目前还没找到方法。
    CompositingMode示例

    CompositingQuality渲染质量

    原型:

    public System.Drawing.Drawing2D.CompositingQuality CompositingQuality { get; set; }
    

    作用:获取或设置合成图像的渲染质量
    CompositingQuality枚举

    说明
    AssumeLinear假设线性值
    Default默认质量
    GammaCorrected使用Gamma校正
    HighQuality高质量、低速度
    HighSpeed高速度、低质量
    Invalid无效质量

    当使用GammaCorredted时,用Gamma=2.2对图像数据进行转换。

    Graphics g = e.Graphics;
    Rectangle rect = new Rectangle(150, 150, 200, 200);
    
    // 设置背景颜色
    g.Clear(Color.White);
    
    var width = 256;
    var height = 256;
    using (var bmp = new Bitmap("AIWoman.png"))
    {
        var values = Enum.GetValues(typeof(CompositingQuality)) as CompositingQuality[];
        values = values.Where(z => z != CompositingQuality.Invalid).ToArray();
    
        for (int i = 0; i < values.Length; i++)
        {
            var quality = values[i];
            e.Graphics.CompositingQuality = quality;
            var sw = Stopwatch.StartNew();
            var times = 0;
            for (times = 0; times < 5; times++)
            {                        
                e.Graphics.DrawImage(bmp, 20 + 280 * (i % 3), 20 + 280 * (i / 3), width, height);
            }
            sw.Stop();
            e.Graphics.DrawString($"{quality},{sw.ElapsedMilliseconds}ms/{times}次", Font, Brushes.Red,
                    new PointF(20 + 280 * (i % 3), 5 + 280 * (i / 3)));
        }
    } 
    

    用不同的CompositingQuality绘制图像
    AI美女测试图
    质量上肉眼没看出多大区别,可能放大效果明显。(速度仅供参考)
    CompositingQuality示例

    DpiX和DpiY 水平、垂直分辨率

    原型:

    public float DpiX { get; }
    public float DpiY { get; }
    

    作用:获取水平和垂直的DPI。

    InterpolationMode插值模式

    原型:

    public System.Drawing.Drawing2D.InterpolationMode InterpolationMode { get; set; }
    

    作用:获取或设置Graphis的插值模式。
    NearestNeighbor 是质量最低的模式,HighQualityBicubic 是质量最高的模式。

    Graphics g = e.Graphics;
    var width = 200;
    var height = 200;
    using (var bmp = new Bitmap("AIWoman.png"))
    {
        var values = Enum.GetValues(typeof(InterpolationMode)) as InterpolationMode[];
        values = values.Where(z => z != InterpolationMode.Invalid).ToArray();
    
        long[] elapsed = new long[values.Length];
        var col = 4;
        var count = 5;
        for (int times = count - 1; times >= 0; times--)
        {
            for (int i = 0; i < values.Length; i++)
            {
                var interpolation = values[i];
                e.Graphics.InterpolationMode = interpolation;
                var sw = Stopwatch.StartNew();
                e.Graphics.DrawImage(bmp, 20 + 220 * (i % col), 20 + 220 * (i / col), width, height);
                sw.Stop();
                elapsed[i] += sw.ElapsedMilliseconds;
                if (times == 0)
                {
                    e.Graphics.DrawString($"{interpolation},{elapsed[i]}ms/{count}次", Font, Brushes.Red,
                                            new PointF(20 + 220 * (i % col), 5 + 220 * (i / col)));
                }
            }
        }
    }
    e.Graphics.DrawString($"DpiX={e.Graphics.DpiX},DpiY={e.Graphics.DpiY}", Font, Brushes.Red, new PointF(100, 500));
    

    用不同的InterpolationMode绘制图像,(速度仅供参考)
    InterpolationMode

    PageScale页面缩放

    原型:

    public float PageScale { get; set; }
    

    作用:获取或设置世界单位和页面单位之间的缩放。
    如果PageUnit为默认的Display时,修改PageScale后,绘制的图形没有缩放?!

    using(var bmp=new Bitmap("AIWoman.png"))
    {
        //如果PageUnit为默认的Display的话,PageScale不生效?
        e.Graphics.PageUnit = GraphicsUnit.Pixel;
        // 创建矩形
        Rectangle rectangle1 = new Rectangle(20, 20, 50, 100);
    
        // 绘制矩形
        e.Graphics.DrawRectangle(Pens.Blue, rectangle1);
    
        //绘制图像
        e.Graphics.DrawImage(bmp, 100, 0, 200, 200);
    
        // 修改PageScale 
        e.Graphics.PageScale = 2.0F;
        // 原点向右、向下各移10像素(实际是
        e.Graphics.TranslateTransform(10.0F, 15.0F);
    
        // 再次绘制矩形,实际的设备坐标为(60,70,100,200)
        e.Graphics.DrawRectangle(Pens.Red, rectangle1);
    
        //绘制图像
        e.Graphics.DrawImage(bmp, 100, 0, 200, 200);
    
        // Set the page scale and origin back to their original values.
        e.Graphics.PageScale = 1.0F;
        e.Graphics.ResetTransform();
    
        SolidBrush transparentBrush = new SolidBrush(Color.FromArgb(50,
            Color.Yellow));
    
        // 修改PageScale与TranslateTransform后的变换计算方法
        // x = (10 + 20) * 2
        // y = (15 + 20) * 2
        // Width = 50 * 2
        // Length = 100 * 2
        Rectangle newRectangle = new Rectangle(60, 70, 100, 200);
    
        //填充一个半透的矩形,与上面第二次绘制的矩形位置一致
        e.Graphics.FillRectangle(transparentBrush, newRectangle);
    }     
    

    注意修改PageScale后,绘制坐标与内容是如何计算得到的。

    PageScale

    1. 使用 PageScale 进行缩放:
    • 如果您希望以简单的方式实现缩放功能,并且不需要对图像进行更复杂的变换操作,那么可以使用 PageScale 属性。
    • 当用户滚动鼠标时,您可以根据滚动的方向和幅度来调整 PageScale 的值,从而实现图像的缩放效果。
    • 这种方式简单直观,适合于一般的图像浏览控件,但可能无法满足复杂的缩放需求。
    1. 使用 Matrix 进行缩放:
    • 如果您希望更灵活地控制图像的缩放效果,并且可能需要对图像进行更复杂的变换操作,那么可以使用 Matrix 类。
    • 当用户滚动鼠标时,您可以根据滚动的方向和幅度来创建一个缩放变换矩阵,并将其应用于绘图操作,从而实现图像的缩放效果。
    • 这种方式更灵活,可以实现各种复杂的缩放效果,但相对来说实现起来可能会稍微复杂一些。

    PixelOffsetMode像素偏移模式

    原型:

    public System.Drawing.Drawing2D.PixelOffsetMode PixelOffsetMode { get; set; }
    

    作用:获取或设置渲染图形时像素如何偏移

    说明
    Default使用默认的像素偏移模式。在绘制过程中,GDI+ 会自动选择合适的像素偏移模式,以保证绘制效果良好。
    HighSpeed采用高速模式进行像素偏移。这种模式下,GDI+ 不会进行像素的微调,而是采用更快的绘制方式。适用于速度要求较高的场景,但可能会影响绘制质量。
    HighQuality采用高质量模式进行像素偏移。这种模式下,GDI+ 会对像素进行微调,以提高绘制的质量和精度。适用于需要更高绘制质量的场景,但可能会影响绘制速度。
    Half采用半像素偏移模式。在此模式下,GDI+ 会将绘制的图形偏移半个像素,以获得更加平滑的图形边缘效果。
    None不偏移

    肉眼没看出多大的区别。
    PixelOffsetMode

    RenderingOrigin绘制原点

    原型:

    public System.Drawing.Point RenderingOrigin { get; set; }
    

    作用:用于抖动或填充画笔的渲染原点。
    测试过程中,没有生效,不知哪里方法不对。

    SmoothingMode平滑方式

    原型:

    public System.Drawing.Drawing2D.SmoothingMode SmoothingMode { get; set; }
    

    作用:获取或设置渲染质量。
    SmoothingMode枚举
    Default、None和HighSpeed是等效的,不启用抗锯齿。
    AntiAlias和HighQuality是等效的,启用抗锯齿。
    SmoothingMode对文本渲染无效。文本渲染需要设置TextRenderingHint。

    // 设置背景颜色
    e.Graphics.Clear(Color.White);
    var pt1 = new Point(100, 100);
    var pt2 = new Point(600, 550);
    var values = Enum.GetValues(typeof(SmoothingMode)) as SmoothingMode[];
    values = values.Where(z => z != SmoothingMode.Invalid).ToArray();
    
    for (int i = 0; i < values.Length; i++)
    {
        var val = values[i];
        e.Graphics.SmoothingMode = val;
        e.Graphics.DrawLine(Pens.Red, pt1, pt2);
        e.Graphics.DrawString($"{val}", Font, Brushes.Red, pt1);
        e.Graphics.TranslateTransform(30,15);
    }
    
    

    绘制不同的平滑模式的线段。
    SmoothingMode

    TextContrast 文本Gamma校正值 和 TextRenderingHint文本渲染方式

    原型:

    public int TextContrast { get; set; }
    public System.Drawing.Text.TextRenderingHint TextRenderingHint { get; set; }
    

    TextContrast 作用:获取或设置渲染文本的Gamma校正值。值的范围0到12,默认值为4。
    TextRenderingHint 作用:获取或设置渲染文本的方式。

    1. TextRenderingHint.SystemDefault

      说明:使用系统默认的文本渲染设置。这通常是一个折中的选择,基于操作系统的当前设置。
      用途:适用于大多数情况下,使用操作系统提供的默认设置。

    2. TextRenderingHint.SingleBitPerPixelGridFit

      说明:使用单色(每像素1位)的文本渲染,并且文本像素对齐到字符网格。这提供了清晰的文本但没有抗锯齿。
      用途:适用于需要非常清晰和锐利文本的环境,例如某些嵌入式系统或低分辨率显示器。

    3. TextRenderingHint.SingleBitPerPixel

      说明:使用单色(每像素1位)的文本渲染,不进行字符网格对齐。文本渲染相对较快,但质量较低。
      用途:当性能优先于文本质量时,例如绘制大量文本或在资源受限的环境中。

    4. TextRenderingHint.AntiAliasGridFit

      说明:使用抗锯齿和字符网格对齐。这提供了高质量的文本渲染,但可能会牺牲一些性能。
      用途:适用于需要高质量文本显示的场景,例如桌面应用程序的用户界面。

    5. TextRenderingHint.AntiAlias

      说明:使用抗锯齿,但不进行字符网格对齐。比 AntiAliasGridFit 提供稍低的文本质量,但性能更好。
      用途:当需要较好的文本质量,但对字符网格对齐要求不高时。

    6. TextRenderingHint.ClearTypeGridFit

      说明:使用 ClearType 技术进行文本渲染,并进行字符网格对齐。ClearType 提供了最高的文本清晰度,特别是在 LCD 显示器上。
      用途:适用于高分辨率和 LCD 显示器,提供最佳的文本渲染质量。

    e.Graphics.Clear(Color.White);
    e.Graphics.ScaleTransform(4.5f,4.5f);
    Font myFont = new Font(FontFamily.GenericSansSerif, 10,
        FontStyle.Regular);
    var pt1 = new Point(5, 5);
    var values = Enum.GetValues(typeof(TextRenderingHint)) as TextRenderingHint[];
    
    for (int i = 0; i < values.Length; i++)
    {
        var val = values[i];
        e.Graphics.TextRenderingHint = val;
        e.Graphics.TextContrast = i * 2;
        e.Graphics.DrawString($"{e.Graphics.TextContrast},{val}", myFont, Brushes.Red, pt1);
        e.Graphics.TranslateTransform(0, 20);
    }
    

    TextRenderingHint

    Transform变换矩阵

    原型:

    public System.Drawing.Drawing2D.Matrix Transform { get; set; }
    

    作用:设置或获取Graphics的世界坐标系统的变换矩阵副本。用于将世界坐标映射到页面坐标。获取属性后需要调用Dispose()方法。

    VisibleClipBounds可见裁切外接矩形

    原型:

    public System.Drawing.RectangleF VisibleClipBounds { get; }
    

    作用:获取可见的裁切区域外接矩形。

    e.Graphics.Clear(Color.White);
    var rect = new Rectangle(50, 50, 200, 100);
    
    var matrix = e.Graphics.Transform;
    matrix.Translate(50, 60, MatrixOrder.Append);//这里是副本,进行的平移对原Transform无效
    e.Graphics.DrawRectangle(Pens.Red, rect);
    var vRect = e.Graphics.VisibleClipBounds;
    int offset = 20;
    //这里VisiableClipBounds左上角是(0,0)
    DrawString(e,$"VisibleClipBounds:({vRect.X},{vRect.Y},{vRect.Width},{vRect.Height})",ref offset);
    
    e.Graphics.Transform = matrix;//重新设置后生效
    e.Graphics.DrawRectangle(Pens.Green, rect);
    vRect = e.Graphics.VisibleClipBounds;
    //因平移了(50,60),这里VisibleClipBounds的左上角是(-50,-60),★
    DrawString(e, $"VisibleClipBounds:({vRect.X},{vRect.Y},{vRect.Width},{vRect.Height})", ref offset);
    
    e.Graphics.ScaleTransform(2, 2);
    
    vRect = e.Graphics.VisibleClipBounds;
    //因平移了(50,60),这里VisibleClipBounds的左上角是(-50,-60),★
    DrawString(e, $"VisibleClipBounds:({vRect.X},{vRect.Y},{vRect.Width},{vRect.Height})", ref offset);
    
    var newRect = new Rectangle(-20, -20, 200, 100);
    e.Graphics.DrawRectangle(Pens.LightSkyBlue, newRect);
    
    matrix.Dispose();//需Dispose()释放
    
    

    注意矩阵变换后的VisiableClipBounds
    变换后的VisibleClipBounds

    https://learn.microsoft.com/en-us/dotnet/api/system.drawing.graphics?view=netframework-4.8.1

  • 相关阅读:
    Airtest自定义启动器支持批量运行脚本,并兼容在AirtestIDE中使用
    ll、chmod 命令
    【python爬虫】10.指挥浏览器自动工作(selenium)
    .net 6或5调用webservice自定义头。(金碟里的SessionId的传递)
    list类型常用命令及其底层数据结构
    FileOutputStream(字节输出流)
    算法leetcode|79. 单词搜索(rust重拳出击)
    第3章 初识SqlSugarCore之ConfigureOptions注入实现
    Node.js | 详解 JWT 登录验证 的工作原理
    BI业务分析思维:生产制造供应链订单交付优化分析三种方式
  • 原文地址:https://blog.csdn.net/TyroneKing/article/details/139279683