代码挑战画 魔法圣诞树

news2024/11/16 16:55:30

一、前言

本文会基于C# GDI+技术 从零到一 实现一颗 魔法圣诞树!源码和素材在文末全部都有!


二、魔法圣诞树

对于用代码画圣诞树,网上各种编程语言像python、css、java、c/c++我们都有见到过了,那么在绘图方面,还有一位实力强劲的隐藏高手,那就C#语言,它的GDI+技术也可以称的上是笑傲江湖,但网上鲜见C#代码画的圣诞树,所以今天我就使用 C# 代码 来 展示一下 它的独特魅力,挑战的是画一颗带魔法的圣诞树:树会自动成长,树上挂件会不断变换,就像有魔法一样~


三、效果展示

请添加图片描述


四、实现步骤

  • 画圣诞树轮廓

    • 画树轮廓的算法:从树顶画起,从树干为中心,左右对称,采用循环一行一行画方块,方块从上至少依次递增。
      在这里插入图片描述
    • 画树根:简单的画i,j两层循环画方块搞定
  • 画圣诞树的星星

    • 大家应该都会手绘星星,那就是知道了路径,所以我们就根据路径(path)画即可,GDI+的画路径方法还是非常强大的.
      在这里插入图片描述
  • 画树左边线和右边线

    • 这个开始是采用直角三角形,但看上去会太直了,所以改为边线处理,采用多条折线达到雪压青松的效果.
      在这里插入图片描述
      在这里插入图片描述
      如果不加白边线,是不是差点意思?
      在这里插入图片描述
  • 画树上的小装饰挂件

    • 这是一个小亮点,树上挂件以现有的圣诞精美图片为准,采用高质量图片透明背景处理,效果非常不错.
    • 这是准备的32张图片:
      在这里插入图片描述
    • 这是把图片画上去的效果:
      在这里插入图片描述
  • 画背景图

    • 这也是一个小亮点,背景图做了透明度处理,看起来更突显出重点:树
  • 施魔法:让圣诞树动态生长,树上挂件不断变换

    • 采用定时刷新,通过时间算法计算树的高度实现动态生成,通过不断随机画星星和挂件实现不断变换

五、编码实现

  • 打开双缓冲
    画图为了界面没有明显的刷新,我们都需要开启双缓冲,在构造函数开启即可:

    this.DoubleBuffered = true;
    
  • 画笔抗锯齿
    画图为了线条没有锯齿状,我们都需要设置绘制模式为抗锯齿,在OnPaint中设置:

	 Graphics g = e.Graphics;
     g.SmoothingMode = SmoothingMode.AntiAlias;
  • 画圣诞树轮廓

DrawTreeLayer:画整颗树的“一段/一截”的方法,一般圣诞树有3/5/7段这样,这里根据start和end控制循环次数,这里采用的画刷就是单色的SolidBrush

private void DrawTreeLayer(Graphics g, int start, int end, ref int x, ref int y)
{
    using (Brush brush = new SolidBrush(Color.FromArgb(9, 124, 37)))
    {
        int outSize = rectSize + border;
        bool lastFillImage = false;
        for (int i = start; i <= end; i++)
        {
            for (int j = 0; j < (i * 2 - 1); j++)
            {
                if (j == 0)
                {
                    // 画最左边
                    DrawTreeLeft(g, brush, x, y, rectSize, rectSize);
                }
                else if (j == i * 2 - 2)
                {
                    // 画最右边
                    DrawTreeRight(g, brush, x, y, rectSize, rectSize);
                }
                else
                {
                    // 画树上的小装饰挂件
                    g.FillRectangle(brush, x, y, rectSize, rectSize);
                    if (lastFillImage || i == start)
                    {
                        lastFillImage = false;
                    }
                    else
                    {
                        lastFillImage = DrawGift(g, brush, x, y, rectSize, rectSize);
                    }
                }
                x += outSize;
            }
            x = startX - i * outSize;
            y += outSize;
        }
    }
}

实现了画一段,我们再把每一段画出来就是一整颗树,在OnPaint里指定实现,这里的全量变量level代表段数

int x = startX;
int y = startY;
int outSize = rectSize + border;
for (int i = 4; i < 3 + level; i++)
{
    // 一层比一层低的设置
    int start = 2 + i - 4;
    int end = i;

    x = startX - (start - 1) * (rectSize + border);
    DrawTreeLayer(g, start, end, ref x, ref y);
}
x = startX - (rectSize + border);
DrwaRoot(g, ref x, ref y);

画树根的方法,和画树干的方块的画刷相同SolidBrush

// 画树根
private void DrwaRoot(Graphics g, ref int x, ref int y)
{
    using (Brush brush = new SolidBrush(Color.FromArgb(131, 78, 0)))
    {
        int outSize = rectSize + border;
        for (int i = 0; i < rootHeight; i++)
        {
            for (int j = 0; j < rootWidth; j++)
            {
                g.FillRectangle(brush, x, y, rectSize, rectSize);
                x += outSize;
            }
            x = startX - outSize;
            y += outSize;
        }
    }
}
  • 画圣诞树的星星
    大家应该都会手绘星星,那就是知道了路径,所以我们就根据路径(path)画即可,GDI+的画路径方法还是非常强大的.
    利用GDI+的路径(GraphicsPath)画出的一颗小星星,这里通过starColors 和 curStarColorIndex 控制颜色每次刷新变换。
    这里小细节是:星星不只是填充背景色,还加了白色外边框达到和树边一样的效果,看起来更和谐。为了画出边框,采用了Inflate方法:即先外放大画边框,画完再缩小归位,不影响后续画笔
// 画星星
Color[] starColors = new Color[] { Color.Yellow, Color.Cyan, ColorTranslator.FromHtml("#FFDF00") };
int curStarColorIndex = 0;
private void DrawStar(Graphics g, Point center, float angle, int radius)
{
    PointF[] points = new PointF[]
    {
        new PointF(center.X, center.Y - radius),
        new PointF((float)(center.X + radius * Math.Sin(72 * Math.PI / 180)), (float)(center.Y - radius * Math.Cos(72 * Math.PI / 180))),
        new PointF((float)(center.X + radius * Math.Sin(36 * Math.PI / 180)), (float)(center.Y + radius * Math.Cos(36* Math.PI / 180))),
        new PointF((float)(center.X - radius * Math.Sin(36 * Math.PI / 180)),(float)( center.Y + radius * Math.Cos(36 * Math.PI / 180))),
        new PointF((float)(center.X - radius * Math.Sin(72 * Math.PI / 180)), (float)(center.Y - radius * Math.Cos(72 * Math.PI / 180))),
    };
    GraphicsPath path = new GraphicsPath(FillMode.Winding);
    path.AddLine(points[0], points[2]);
    path.AddLine(points[2], points[4]);
    path.AddLine(points[4], points[1]);
    path.AddLine(points[1], points[3]);
    path.AddLine(points[3], points[0]);
    path.CloseFigure();

    g.RotateTransform(angle);
    // 画白边框
    using (Pen pen = new Pen(Color.White, 6f))
    {
        path.GetBounds().Inflate(6, 6);
        g.DrawPath(pen, path);
        path.GetBounds().Inflate(-6, -6);
    }
    // 填充色轮换
    using (Brush brush = new SolidBrush(starColors[curStarColorIndex]))
    {
        g.FillPath(brush, path);
    }
    int nextStarColorIndex = (curStarColorIndex == starColors.Length - 1) ? 0 : (curStarColorIndex + 1);
    curStarColorIndex = nextStarColorIndex;
}
  • 画树左边线和右边线
    这里有个小细节,就是为了看起来更有层次感,所以对左边线和右边线,也做了处理,开始是单纯的画直角三角形,但是太直了,所以改为画多边形效果就好很多,像雪压青松的效果~

    • FillPolygon 是填充多边线颜色
    • 画白边为什么用DrawLines呢?因为我只画外边,不折合
// 画树左边
private void DrawTreeLeft(Graphics g, Brush brush, int x, int y, int width, int height)
{
    PointF point1 = new PointF(x + width, y);
    PointF point2 = new PointF(x + z12, y + height - z12);
    PointF point3 = new PointF(x - z16, y + height);
    PointF point4 = new PointF(x + width, y + height);
    PointF[] fillPts = { point1, point2, point3, point4 };
    g.FillPolygon(brush, fillPts);
    // 画白边框
    PointF[] borderPts = { point1, point2, point3 };
    using (Pen pen = new Pen(Color.White, 3f))
    {
        g.DrawLines(pen, borderPts);
    }
}

// 画树右边  
private void DrawTreeRight(Graphics g, Brush brush, int x, int y, int width, int height)
{
    PointF point1 = new PointF(x, y);
    PointF point2 = new PointF(x, y + height);
    PointF point3 = new PointF(x + width + z16, y + height);
    PointF point4 = new PointF(x + width - z12, y + height - z12);
    PointF[] pntArr = { point1, point2, point3, point4 };
    g.FillPolygon(brush, pntArr);
    // 画白边框
    PointF[] borderPts = { point1, point4, point3 };
    using (Pen pen = new Pen(Color.White, 3f))
    {
        g.DrawLines(pen, borderPts);
    }
}
  • 画树上的小装饰挂件
    因为树上挂件很多,最开始是想全用GDI+技术来画,画了几个发现效果不多,所以就弄了32张png小图片,直接画图片,但这里也有一个小细节,png背景是白色,如果原样画图片,会很不和谐,所以需要把白色变透明,请看代码:

    • 加载32张png小图片,你可以把你想加的放到iconfont目录即可:
    string[] files = Directory.GetFiles("iconfont\\");
    foreach (string file in files)
    {
        Image img = Image.FromFile(file);
        Bitmap bmp = new Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
        using (Graphics g = Graphics.FromImage(bmp))
        {
            g.DrawImage(img, 0, 0, img.Width, img.Height);
        }
        bmp.MakeTransparent(Color.White);
        bitmapList.Add(bmp);
    }
    
    this.backImage = Image.FromFile("backgroud\\backgroud.jpg");
    
    • 画树上的小装饰挂件方法:
      这里也有一个小细节:Random 默认会以当前时间为种子,所以在同一时间,循环数字相同,也就没什么效果,看起来都一样,所以我们要变化Random的种子,也就是我每次用Guid生成一个全新的。
      DrawImage采用的像素画法.
    // 画树上的小装饰挂件
    private bool DrawGift(Graphics g, Brush brush, int x, int y, int width, int height)
     {
         byte[] buffer = Guid.NewGuid().ToByteArray();
         int iSeed = BitConverter.ToInt32(buffer, 0);
         Random random = new Random(iSeed);
         int i = random.Next(bitmapList.Count * 2);
         if (i < bitmapList.Count)
         {
             Rectangle destRect = new Rectangle(x, y, width, height);
             Rectangle srcRect = new Rectangle(0, 0, bitmapList[i].Width, bitmapList[i].Height);
    
             g.DrawImage(bitmapList[i], destRect, srcRect, GraphicsUnit.Pixel);
             return true;
         }
         return false;
     }
    
  • 画背景图
    那么这么魔法的圣诞树,当然要配上圣诞老人的图片,这里也有一个小细节,如何把背景图片模糊化,这样才好突显树的效果,我这里是做了透明度处理,通过指定DrawImageImageAttributes

    this.backImage = Image.FromFile("backgroud\\backgroud.jpg");
    // 画背景图片带透明度
    using (ImageAttributes attributes = GetAlphaImgAttr(50))
    {
        Rectangle destRect = new Rectangle(0, 0, this.Width, this.Height);
        g.DrawImage(this.backImage, destRect, 0, 0, this.backImage.Width, this.backImage.Height, GraphicsUnit.Pixel, attributes);
    }
    
    • 关键代码获取一个带有透明度的ImageAttributes
    public ImageAttributes GetAlphaImgAttr(int opcity)
    {
        if (opcity < 0 || opcity > 100)
        {
            throw new ArgumentOutOfRangeException("opcity 值为 0~100");
        }
        //颜色矩阵
        float[][] matrixItems =
         {
              new float[]{
                   1,0,0,0,0},
                          new float[]{
                   0,1,0,0,0},
                          new float[]{
                   0,0,1,0,0},
                          new float[]{
                   0,0,0,(float)opcity / 100,0},
                          new float[]{
                   0,0,0,0,1}
         };
        ColorMatrix colorMatrix = new ColorMatrix(matrixItems);
        ImageAttributes imageAtt = new ImageAttributes();
        imageAtt.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
        return imageAtt;
    }
    
  • 施魔法:让圣诞树动态生长,树上挂件不断变换
    ok, 动态效果是通过timer定时器刷新实现的,1秒一刷新3秒自动成长,长到8秒停止,就这么简单~

    // 当前刷新次数
    int curRefreshCount = 0;
    // 成长阀值
    int growThreshold = 9;
    private void timer1_Tick(object sender, EventArgs e)
    {
        this.Refresh();
        curRefreshCount++;
        // 刷新次数超过growThreshold长一次高度
        if (curRefreshCount >= growThreshold)
        {
            curRefreshCount = 0;
            if (level >= 8)
            {
                //level = 3;
                this.startY = 100 + (8 - level) * 3 * rectSize;
            }
            else
            {
                this.level++;
                this.startY -= 3 * rectSize;
            }
        }
    }
    

全部源代码

打包下载地址:https://download.csdn.net/download/scm_2008/87342631

最后祝大家Merry Christmas~

大家有什么好的建议或想法,欢迎评论区讨论.
创作不易,求关注,点赞,收藏,谢谢~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/113622.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FastDDS(6)核心库综述

Fast DDS(前身为Fast RTPS)是DDS规范的高效高性能实现,DDS规范是一种用于分布式应用软件的以数据为中心的通信中间件(DCPS)。本次回顾Fast DDS的体系结构、操作和关键特性。 架构 Fast DDS的架构如下图所示,其中可以看到具有以下不同环境的层模型。 Application layer应…

人员工装未穿戴识别检测 opencv

人员工装未穿戴识别检测基于OpenCvyolo计算机视觉深度学习技术对现场画面中人员行为着装穿戴实时监测识别&#xff0c;发现不按要求着装违规行为立即抓拍存档同步后台。OpenCV-Python使用Numpy&#xff0c;这是一个高度优化的数据库操作库&#xff0c;具有MATLAB风格的语法。所…

RabbitMQ 第一天 基础 1 MQ的基本概念 1.4 MQ 的劣势 1.5 常见的MQ 产品

RabbitMQ 【黑马程序员RabbitMQ全套教程&#xff0c;rabbitmq消息中间件到实战】 文章目录RabbitMQ第一天 基础1 MQ的基本概念1.4 MQ 的劣势1.4.1 MQ 的劣势1.4.2 小结1.5 常见的MQ 产品第一天 基础 1 MQ的基本概念 1.4 MQ 的劣势 1.4.1 MQ 的劣势 从远程调用 到 利用 MQ 作…

css实现九宫格

首先是实现九宫格的样式&#xff0c;对每一行进行偏移&#xff0c;当鼠标放上去会使他们形成一张图片。 html <div class"img_container"><div class"img1"></div><div class"img1"></div><div class"i…

2022年,来者犹可追

始料未及的是&#xff0c; 疫情持续到了2022年。好在“大疫不过三年”&#xff0c;只不过是结束来的同样措不及防&#xff0c;全家的一次高烧免疫&#xff0c;没有朋友圈中的云淡风轻&#xff0c;冷暖自知&#xff0c;希望明年能够拥有平安喜乐的时光。回首这一年&#xff0c;“…

kotlin与java实现混编基础看这篇就够了

前几年一直关注安卓&#xff0c;想换个方向&#xff0c;奔着移动端大步向前&#xff0c;由于比较懒就一直停留在想法&#xff0c;这不今天勤快点&#xff0c;动手搞了一个基础的java和kotlin混编&#xff0c;和大家总结分享一下。 首先需要了解什么事kotlin&#xff0c;kotlin…

如何使用腾讯云轻量应用服务器挂载 CFS 文件系统

文件存储&#xff08;Cloud File Storage&#xff0c;CFS&#xff09;提供了可扩展的共享文件存储服务&#xff0c;可与腾讯云云服务器 、容器、批量计算、轻量应用服务器等服务搭配使用。CFS 提供了标准的 NFS 及 CIFS/SMB 文件系统访问协议&#xff0c;可为计算服务提供共享的…

【Unity】【Pico】手柄摇杆控制第一人称移动和旋转

【Unity】【Pico】手柄摇杆控制第一人称移动和旋转 背景&#xff1a;开发影院系统 环境&#xff1a;Unity2021.3、PicoNeo3ProEye 描述&#xff1a;已经在Unity项目中实现第一人称WASD移动和鼠标旋转&#xff08;代码见我的其他博文&#xff09; 需求&#xff1a;希望项目在Pi…

Cobalt Strike Beacon 初探

背景 RTO I 的课程结束了&#xff0c;Cobalt Strike 算是会用了。然后继上一篇文章之后&#xff0c;我还没有机会用 Cobalt Strike Beacon 做一下 Windows Defender Bypass。之后会写。 另外&#xff0c;我也想问一下我自己&#xff0c;Cobalt Strike 里面最基本的 payload -…

Springboot+Netty实现基于天翼物联网平台CTWing(AIOT)终端TCP协议(透传模式)-应用订阅端(北向应用)

之前实现了使用SpringbootNetty基于天翼物联网平台CTWing(AIOT)终端TCP协议(透传模式)-设备终端&#xff08;南向设备&#xff09;&#xff0c;模拟设备发送的数据给到了AIOT平台&#xff0c;那么在第三方应用也需要去订阅AIOT平台的数据&#xff0c;以及对设备进行下发指令(消…

FastGithub的下载和使用

前言 github访问很不稳定&#xff0c;时断时续&#xff0c;有时候根本打不开&#xff01; 下载 方式一&#xff1a;官方地址下载&#xff08;有及时更新&#xff09; FastGithub1.1.7下载、FastGithub2.1.4_windows、FastGithub2.1.4_Linux、 更多 方式二&#xff1a;本地上传…

[编程语言][C++][Qt]单独添加UI文件

单独添加UI文件问题描述解决方案1. 添加UI文件2. 与对应的界面类进行关联3. 修改UI文件4. 设置界面类读取UI文件总结问题描述 不知什么原因&#xff0c;Qt Creator并不是很完美很智能。当先写好界面类的头文件和源代码文件后&#xff0c;我们再添加用于可视化界面设计的UI文件…

美国顶级在线教育平台泄露22TB数据

©网络研究院 事件发生时&#xff0c;属于美国“三大”教育出版商之一的麦格劳希尔教育(McGraw Hill) 的两个配置错误的 AWS S3 存储桶在没有任何安全认证的情况下暴露在外。 vpnMentor 的网络安全研究人员发现了几个配置错误的 Amazon Web Services (AWS) S3 存储桶&…

RV1126笔记二十一:车辆颜色识别

若该文为原创文章,转载请注明原文出处。 一、介绍 在学习RV1126的过程中,测试了yolov5可以实现物体检测,物体目标识别等功能,Rock-X也自带了车牌识别功能,具体可以了解下正点原子的资料,里面有详细的介绍,这里介绍一个如何识别车辆颜色。只是提供一个思路,效果不是很…

RabbitMQ 第一天 基础 4 RabbitMQ 的工作模式 4.4 Topic 通配符模式 4.5 工作模式总结

RabbitMQ 【黑马程序员RabbitMQ全套教程&#xff0c;rabbitmq消息中间件到实战】 文章目录RabbitMQ第一天 基础4 RabbitMQ 的工作模式4.4 Topic 通配符模式4.4.1 模式说明4.4.2 代码编写4.4.3 小结4.5 工作模式总结第一天 基础 4 RabbitMQ 的工作模式 4.4 Topic 通配符模式 …

人工智能期末复习:人工神经网络(详细笔记和练习题)

文章目录1.概述2.基本单元-神经元3.激活函数3.1.阶跃函数3.2.Sigmoid函数3.3.TanH函数3.4.ReLU函数3.5.Softplus函数4.多层前馈神经网络5.损失函数5.1.均方误差5.2.交叉熵6.调参方法6.1.梯度下降法1.概述 神经网络定义&#xff1a;神经网络是具有适应性的简单单元组成的广泛并…

vue3 ant design vue——修改table表格的默认样式(css样式穿透)(一)调整table表格每行(row)行高过高问题

vue3 antd项目实战——修改ant design vue table组件的默认样式&#xff08;调整每行行高&#xff09;知识调用场景复现实际操作解决a-table表格padding过宽知识调用 文章中可能会用到的知识链接vue3ant design vuets实战【ant-design-vue组件库引入】css样式穿透&#xff08;…

基于 Traefik 的 ForwardAuth 配置

前言 Traefik 是一个现代的 HTTP 反向代理和负载均衡器&#xff0c;使部署微服务变得容易。 Traefik 可以与现有的多种基础设施组件&#xff08;Docker、Swarm 模式、Kubernetes、Marathon、Consul、Etcd、Rancher、Amazon ECS...&#xff09;集成&#xff0c;并自动和动态地…

【移动安全】—apk反编译基础及静态分析

作者名&#xff1a;Demo不是emo 主页面链接&#xff1a;主页传送门 创作初心&#xff1a;舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座…

逛逛JVM的“后花园“: 让我来戏弄戏弄字节吧

开篇瞎哔哔 这篇文章不打算放在任何一个系列里面&#xff0c;纯粹是个人对这方面比较感兴趣才写的&#xff0c;在日常的工作中&#xff0c;也不会用到关于这块的知识&#xff0c;但是&#xff0c;我希望如果有小伙伴和我一样&#xff0c;想对字节码杠一杠的&#xff0c;那么这…