Python绘图实战:从基础四边形到Turtle、Pillow、Matplotlib多库实现

发布时间:2026/7/28 3:34:07

Python绘图实战:从基础四边形到Turtle、Pillow、Matplotlib多库实现 1. 从“画个框”到“行空绘图”为什么Python绘图值得深究最近在快手上刷到不少关于“游戏辅助科技”、“绘制满挂全图”的视频播放量动辄几千上万。抛开这些灰色地带的内容不谈这背后反映了一个挺有意思的现象大家对“绘制”这件事的热情或者说对“用代码控制图形”的需求其实一直都很旺盛。只不过有人用它来写游戏外挂而我们更愿意用它来做点正经事比如数据可视化、UI设计或者就是单纯地学习编程感受创造的乐趣。今天要聊的“用Python绘制四边形”听起来像是编程入门课里最基础的一课——不就是画个方框嘛。但恰恰是这种最基础的图形构成了我们屏幕上看到的一切复杂界面的基石。按钮是四边形窗口是四边形图片的显示区域也是四边形。在游戏开发里碰撞检测的边界框Bounding Box是四边形在数据可视化里图表的坐标轴、图例框也是四边形。所以别小看这个简单的四边形理解了它你就拿到了进入计算机图形世界的第一把钥匙。“行空绘图”这个名字听起来有点武侠小说的意境它可能指的是某个具体的Python绘图库比如一个教学库也可能是一种泛指寓意着在代码的“行空”之间绘制出想要的图形。无论具体指代什么其核心都是利用Python的某个图形库如turtle,pygame,matplotlib,PIL/Pillow, 甚至是tkinter的 Canvas的API通过指定几个关键点的坐标来定义一个四边形并把它画出来。这个过程会涉及到几个关键问题坐标系是怎样的原点在哪X轴和Y轴的方向如何指定四个顶点的位置是画空心边框还是实心填充线条的颜色、粗细、样式怎么设置如果你用的是像pygame或tkinter这类更底层的库可能还需要处理绘图表面的刷新双缓冲问题。你看一个简单的“画四边形”拆解开来就是一连串非常具体的技术选择和实践步骤。我打算在这篇文章里不局限于任何一个特定的库而是带你走一遍从原理到多种实现的完整路径。我们会从最经典的“海龟绘图”turtle开始感受那种一笔一划的编程乐趣然后切换到更面向“像素”的Pillow库学习如何在图像上作图最后我们会用功能强大的matplotlib画一个可以作为图表元素的精致四边形。每个部分我都会分享一些我踩过的坑和总结出来的小技巧。目标是看完之后你不仅能画出四边形更能理解背后“为什么这么画”并且能举一反三画出任何你想要的简单图形。2. 坐标系与图形基础一切绘图的起点在动手写代码之前我们必须先统一“语言”这个语言就是坐标系。你在纸上画一个四边形你会本能地知道哪里是左上角哪里是右下角。但在计算机里我们需要用数字精确地告诉它每个点的位置。绝大多数计算机图形系统包括我们即将用到的Python库都采用笛卡尔坐标系的一个变种屏幕坐标系。它的特点是原点 (0, 0)通常位于绘图区域的左上角。这一点和数学里常见的原点在中心或左下角不同需要特别注意。X轴水平向右延伸X坐标值逐渐增大。Y轴垂直向下延伸Y坐标值逐渐增大。这是最容易让人困惑的一点在我们熟悉的数学坐标系里Y轴是向上的但在屏幕坐标系里Y轴是向下的。你可以想象你是在从上往下“扫描”这个屏幕。假设我们有一个800像素宽、600像素高的画布。那么左上角坐标是 (0, 0)右上角是 (800, 0)左下角是 (0, 600)右下角是 (800, 600)。一个四边形就是由四个这样的 (x, y) 坐标点有序连接而成的。注意这个“有序”非常关键。你不能随意地把四个点扔给绘图函数。点的顺序决定了四边形的形状。通常我们需要按顺时针或逆时针方向依次提供顶点坐标。除了坐标我们还需要定义图形的“样式”描边 (Stroke)图形边框的线条。包括颜色如red,#FF0000,(255, 0, 0)、宽度线宽、样式实线、虚线、点线。填充 (Fill)图形内部的颜色。可以是纯色、渐变色或透明None。顶点 (Vertices)就是那四个点的坐标列表。对于凸四边形顺序连接即可对于复杂四边形如凹四边形某些绘图库可能需要特定的处理方式。理解这些基础概念后我们再来看看Python里几个主流的绘图库它们是如何封装这些概念的turtle它模拟一只海龟在画布上爬行通过forward(),left()等命令控制。画四边形更像是“走路径”通过控制海龟的移动轨迹来形成图形。它的坐标系原点默认在屏幕中心但可以设置。PIL/Pillow这是一个强大的图像处理库。它的绘图模块ImageDraw提供的是基于像素的、更“静态”的绘图方式。你告诉它在某个Image对象上从A点到B点画一条线或填充一个由若干点围成的区域。它的坐标系原点在图像左上角。matplotlib这是一个主要用于科学绘图的库但它底层的patches模块可以绘制非常精确的矢量图形。它采用标准的数学坐标系原点在左下角Y轴向上这对于需要精确对齐数据点的图表来说非常自然。它绘制的图形对象如Rectangle,Polygon具有丰富的属性可供调整。选择哪个库取决于你的目标。想趣味学习或绘制分形图案选turtle。想生成图片文件如合成头像、添加水印选Pillow。想在数据图表中插入自定义形状选matplotlib。接下来我们就逐一实战。3. 实战一用Turtle库“行走”出一个四边形turtle库是Python标准库的一部分无需安装。它最大的特点是“所见即所得”和“过程式”的绘图体验非常适合编程初学者和可视化算法步骤比如递归画树。我们来画一个边长为100像素的正方形。思路是让海龟前进100步右转90度重复4次。import turtle # 1. 创建一个画布和一个海龟对象 screen turtle.Screen() screen.title(Turtle绘制四边形) screen.bgcolor(white) # 设置背景色为白色 pen turtle.Turtle() pen.speed(1) # 设置绘图速度1最慢10最快0最快 pen.pensize(3) # 设置画笔宽度 pen.color(blue) # 设置画笔颜色 # 2. 开始绘制正方形 pen.penup() # 抬起笔移动时不画线 pen.goto(-50, 50) # 将海龟移动到起始位置为了将正方形画在视野中央 pen.pendown() # 落下笔开始画线 for _ in range(4): pen.forward(100) # 向前移动100个单位 pen.right(90) # 向右转90度 # 3. 绘制一个填充的四边形例如一个平行四边形 pen.penup() pen.goto(-150, -50) # 移动到新位置 pen.pendown() pen.fillcolor(lightgreen) # 设置填充颜色 pen.begin_fill() # 开始填充 # 绘制一个平行四边形两次长边两次短边转角不是90度 pen.forward(120) pen.right(60) pen.forward(60) pen.right(120) pen.forward(120) pen.right(60) pen.forward(60) pen.right(120) pen.end_fill() # 结束填充自动填充闭合图形内部 # 4. 保持窗口打开直到用户点击关闭 turtle.done()代码解读与避坑指南penup()和pendown()这是控制“提笔”和“落笔”的关键。如果你想移动海龟到另一个位置而不留下痕迹就必须先penup()移动到位后再pendown()。新手常犯的错误就是忘记提笔导致画布上出现一条不该有的“移动轨迹线”。goto(x, y)用于将海龟精确移动到绝对坐标位置。这里的坐标是基于海龟初始坐标系默认中心为原点。我们通过计算让正方形起点在(-50, 50)这样画出的正方形大致在屏幕中央。填充 (begin_fill()/end_fill())填充是针对闭合路径的。begin_fill()之后海龟走过的路径会被记录直到end_fill()系统会将这条闭合路径的内部区域用fillcolor()设置的颜色填充。关键点你必须确保路径是闭合的即终点和起点重合。在上面的平行四边形代码中我们最后一步pen.right(120)后海龟的方向回到了初始状态并且路径自动闭合虽然我们没有显式goto回起点但end_fill()会处理。速度 (speed())设置速度可以让绘图过程可视化便于调试。设为0虽然快但你可能什么都看不清就画完了。在调试时建议设置为1-3。turtle.done()或screen.mainloop()这是必须的它启动事件循环保持绘图窗口打开。没有这行代码窗口会一闪而过。Turtle的局限性Turtle适合顺序性的、路径式的绘图。如果你想直接根据四个顶点坐标[(x1,y1), (x2,y2), (x3,y3), (x4,y4)]来画一个任意四边形用Turtle会有点绕你需要计算每边的长度和转角。因此对于更自由的几何图形我们转向下一个库。4. 实战二用PillowPIL在图像上“绘制”四边形Pillow库是Python事实上的图像处理标准库。它的ImageDraw模块提供了基本的2D绘图功能。这种方式更像是“画家作画”你直接告诉它在画布的哪些坐标点之间画线或填充区域。首先确保安装了Pillowpip install Pillow我们的目标是创建一张800x600的白色图片在上面画一个红色的空心矩形和一个蓝色的填充四边形。from PIL import Image, ImageDraw # 1. 创建一张新的RGB图像白色背景 width, height 800, 600 image Image.new(RGB, (width, height), colorwhite) # 2. 创建一个可以在图像上绘制的对象 draw ImageDraw.Draw(image) # 3. 绘制一个空心矩形需要左上角和右下角坐标 # 矩形是最简单的四边形用 rectangle 方法 rect_top_left (100, 100) rect_bottom_right (300, 300) draw.rectangle([rect_top_left, rect_bottom_right], outlinered, width5) # outline是边框色width是边框宽度。不指定fill则内部透明这里是白色背景。 # 4. 绘制一个任意四边形用 polygon 方法 # 需要按顺序提供四个顶点的坐标列表 quadrilateral_points [ (400, 150), # 顶点1 (550, 100), # 顶点2 (700, 250), # 顶点3 (450, 300) # 顶点4 ] # 绘制填充的四边形边框为绿色 draw.polygon(quadrilateral_points, filllightblue, outlinegreen, width3) # 5. 再画一个只有轮廓的凹四边形演示顶点顺序的重要性 concave_points [ (200, 400), (300, 350), (250, 500), # 这个点向内凹 (150, 450) ] draw.polygon(concave_points, outlinepurple, width4) # 不填充只看轮廓 # 6. 保存图像到文件 image.save(drawn_quadrilateral.png) print(图片已保存为 drawn_quadrilateral.png) # 可选显示图像 # image.show()核心方法解析与经验之谈ImageDraw.Draw(image)这是核心它返回一个ImageDraw对象后续所有绘图操作都基于这个对象。它相当于你的画笔。rectangle(xy, fillNone, outlineNone, width1)xy一个包含两个点的序列[ (x0, y0), (x1, y1) ]。(x0, y0)是矩形左上角(x1, y1)是右下角。注意这里的y1必须大于y0因为Y轴向下。这是与数学直觉不同的地方。fill内部填充色。outline边框颜色。width边框宽度像素。坑点width是向线条两侧扩展的。如果你画一个宽度为5的边框实际占用的像素区域会比你定义的矩形坐标大出去2个像素左右各2.5。在需要精确对齐时要注意。polygon(xy, fillNone, outlineNone, width1)xy一个包含至少三个点的列表代表多边形的各个顶点。ImageDraw会自动连接最后一个点和第一个点以闭合图形。顶点的顺序至关重要。它决定了多边形的形状。对于凸多边形顺序影响不大但影响填充算法对于凹多边形如上例中的concave_points错误的顺序可能导致图形自相交产生意想不到的填充效果比如星形变成一团乱麻。通常按顺时针或逆时针顺序给出顶点是最安全的。颜色表示可以是颜色名字符串如red十六进制字符串如#FF0000或者RGB/RGBA元组如(255, 0, 0, 255)表示不透明的红色。image.show()会调用系统默认的图片查看器打开临时文件。在服务器或无GUI环境可能报错生产环境建议用保存文件或结合其他GUI库显示。性能提示ImageDraw的绘图操作是直接修改像素数据。如果需要绘制大量图形频繁创建ImageDraw对象或进行大量单个像素操作可能会比较慢。对于复杂绘图可以考虑先在一个列表里收集所有绘图指令或者使用更专业的矢量图形库。Pillow的方式非常直接和强大适合生成静态图片。但如果我们想绘制一个可以交互、或者需要嵌入到复杂图表中的四边形就需要更强大的工具了。5. 实战三用Matplotlib绘制“图表级”四边形Matplotlib是科学计算领域的绘图神器。它虽然以绘制曲线、散点图、柱状图见长但其底层对象系统允许我们添加任意形状的“补丁”patches。这些补丁是矢量图形可以无缝集成到坐标轴中并且支持缩放、旋转等变换。我们来创建一个图表在图表上添加一个矩形和一个任意四边形并设置它们的样式。import matplotlib.pyplot as plt import matplotlib.patches as patches import numpy as np # 1. 创建一个图形和坐标轴 fig, ax plt.subplots(figsize(8, 6)) # 8英寸宽6英寸高 ax.set_xlim(0, 10) # 设置X轴范围 ax.set_ylim(0, 8) # 设置Y轴范围 ax.set_aspect(equal) # 设置纵横比相等确保图形不被拉伸 ax.grid(True, linestyle--, alpha0.7) # 添加网格线方便观察位置 ax.set_title(Matplotlib 绘制四边形示例) ax.set_xlabel(X轴) ax.set_ylabel(Y轴) # 2. 绘制一个矩形Rectangle补丁 # 参数左下角坐标(x, y)宽度高度 rect patches.Rectangle((1, 1), width3, height2, linewidth2, edgecolordarkred, facecolorsalmon, alpha0.7, label矩形) # alpha是透明度 ax.add_patch(rect) # 关键将补丁添加到坐标轴 # 3. 绘制一个任意四边形Polygon补丁 # 顶点坐标按顺序提供 quad_vertices np.array([[5, 2], [7, 1], [8, 4], [6, 5]]) # 使用NumPy数组方便计算 quad patches.Polygon(quad_vertices, closedTrue, linewidth3, edgecolornavy, facecolorskyblue, linestyle-., label任意四边形) ax.add_patch(quad) # 4. 绘制一个带圆角且旋转的矩形演示更复杂的属性 # FancyBbox是高级矩形可以设置圆角(boxstyle)、填充等。 from matplotlib.patches import FancyBboxPatch rounded_rect FancyBboxPatch((1, 5), 2.5, 1.5, boxstyleround,pad0.1,rounding_size0.2, linewidth1.5, edgecolorgreen, facecolorlightgreen, alpha0.8, label圆角矩形) ax.add_patch(rounded_rect) # 5. 添加图例 ax.legend(locupper right) # 6. 显示图形 plt.tight_layout() # 自动调整子图参数使之填充整个图像区域。 plt.show() # 7. 也可以保存为矢量图如PDF、SVG或位图如PNG # fig.savefig(matplotlib_quadrilateral.pdf, dpi300, bbox_inchestight)Matplotlib绘图的核心逻辑与技巧“补丁” (Patch) 系统Matplotlib将基本的几何图形圆形、矩形、多边形、箭头等都视为Patch对象。绘制它们分为两步创建Patch对象并设置属性添加到坐标轴 (ax.add_patch())。这种面向对象的方式使得对图形的后续操作如移动、变形、设置动画非常方便。坐标系Matplotlib的坐标轴 (ax) 使用标准的数学坐标系原点在左下角Y轴向上。这和我们之前用的Pillow是反的。在定义顶点时要时刻记住这一点。关键属性edgecolor或ec边框颜色。facecolor或fc填充颜色。linewidth或lw边框宽度。linestyle或ls线型-实线--虚线-.点划线:点线。alpha透明度0完全透明1完全不透明。hatch填充图案如/,\\,|,-,,x,o,O,.,*。closed参数对于PolygonclosedTrue默认表示自动连接首尾顶点。如果设为False则只画折线不闭合。set_aspect(equal)这行代码非常重要它保证了X轴和Y轴的单位长度在屏幕上显示时是相等的。否则一个正方形可能会被显示为长方形导致图形失真。保存图形fig.savefig()功能强大可以保存为多种格式。dpi参数控制输出分辨率每英寸点数bbox_inchestight可以自动裁剪掉图形周围的白边。性能与交互对于静态图表这种方式很好。但如果需要实时更新图形比如游戏或数据监控Matplotlib的交互模式plt.ion()可能不如pygame或tkinter流畅。不过Matplotlib的FuncAnimation模块也能制作不错的简单动画。一个常见问题如何绘制带对角线的四边形Matplotlib的Polygon只画轮廓和填充。如果你想在四边形内部画对角线需要单独用ax.plot()或ax.add_patch(patches.Polygon(...))画内部的线段。例如为上面的quad添加对角线# 获取四边形的顶点 verts quad.get_xy() # 返回一个 (N, 2) 的数组 # 画对角线假设是凸四边形连接顶点0-2和1-3 ax.plot([verts[0, 0], verts[2, 0]], [verts[0, 1], verts[2, 1]], k--, linewidth1) ax.plot([verts[1, 0], verts[3, 0]], [verts[1, 1], verts[3, 1]], k--, linewidth1)6. 进阶思考从四边形到更复杂的图形与交互掌握了绘制静态四边形的方法我们可以思考如何将这些知识应用到更实际的场景或者解决更复杂的问题。场景一在四边形内部填充图案或渐变色PillowImageDraw默认只支持纯色填充。要实现渐变或图案需要更高级的技巧比如创建一个渐变色的Image对象然后使用Image.composite()或Image.paste()配合蒙版由你的四边形形状生成进行合成。Matplotlib强大得多Polygon的facecolor可以接受一个颜色映射Colormap或者一个表示颜色的多维数组用于每个顶点的颜色插值。例如可以创建一个从顶点A到顶点C的线性渐变from matplotlib.colors import LinearSegmentedColormap cmap LinearSegmentedColormap.from_list(my_gradient, [white, blue]) # 为每个顶点分配一个颜色值在0到1之间 colors [0.2, 0.4, 0.8, 0.6] quad patches.Polygon(quad_vertices, closedTrue, linewidth2, edgecolorblack, facecolorcmap(colors), alpha0.8)更简单的渐变可以使用ax.imshow()配合extent参数和clip_path来实现但这涉及更多概念。场景二判断一个点是否在四边形内部这在游戏开发点击检测、地理信息系统点是否在区域内中非常常见。对于凸四边形一个经典且高效的算法是叉积法或称为“同侧法”。原理是如果点P在四边形ABCD内部那么它应该始终在每条边的同一侧比如左侧。我们可以计算向量叉积的符号来判断。import numpy as np def is_point_in_quadrilateral(pt, quad_vertices): 判断点pt是否在凸四边形quad_vertices内部。 quad_vertices: 按顺时针或逆时针排列的四个顶点列表 [(x1,y1), ...] pt: 待判断的点 (x, y) 返回: True/False def cross(o, a, b): 计算向量OA和OB的叉积 (在2D中叉积是一个标量代表有向面积) return (a[0]-o[0]) * (b[1]-o[1]) - (a[1]-o[1]) * (b[0]-o[0]) # 检查点是否在每条边的同一侧这里假设顶点是逆时针排列 d1 cross(quad_vertices[0], quad_vertices[1], pt) d2 cross(quad_vertices[1], quad_vertices[2], pt) d3 cross(quad_vertices[2], quad_vertices[3], pt) d4 cross(quad_vertices[3], quad_vertices[0], pt) # 如果所有叉积同号都0或都0则在内部或边上 return (d1 0 and d2 0 and d3 0 and d4 0) or (d1 0 and d2 0 and d3 0 and d4 0) # 测试 quad [(0,0), (2,0), (2,2), (0,2)] # 一个正方形 print(is_point_in_quadrilateral((1,1), quad)) # True print(is_point_in_quadrilateral((3,1), quad)) # False对于凹四边形这个方法会失效需要使用更通用的算法如射线法Ray Casting Algorithm。场景三让四边形动起来简单动画以Matplotlib为例我们可以使用FuncAnimation来更新四边形的位置或形状。import matplotlib.pyplot as plt import matplotlib.patches as patches import matplotlib.animation as animation import numpy as np fig, ax plt.subplots() ax.set_xlim(0, 10) ax.set_ylim(0, 10) ax.set_aspect(equal) # 初始四边形顶点 verts np.array([[2,2], [4,1], [5,3], [3,4]]) quad patches.Polygon(verts, closedTrue, facecolorcyan, edgecolorblack) ax.add_patch(quad) def update(frame): 每一帧更新的函数 # 例如让四边形绕其中心旋转 center verts.mean(axis0) # 计算中心点 angle frame * 0.05 # 每帧旋转的角度 rotation_matrix np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]]) # 将顶点平移到原点旋转再平移回去 new_verts (verts - center) rotation_matrix.T center quad.set_xy(new_verts) # 更新多边形的顶点 return quad, ani animation.FuncAnimation(fig, update, frames100, interval50, blitTrue) plt.show()这段代码创建了一个旋转的四边形动画。关键在于update函数中计算新的顶点坐标并通过quad.set_xy()方法来更新图形对象。从画一个静止的四边形到让它填充渐变、响应点击、甚至旋转起来这中间每一步都对应着对底层库API的深入理解和灵活运用。我个人的体会是图形编程最有魅力的地方就在于这种“控制感”——你用几行代码就能在虚拟世界里创造和操纵一个视觉对象。下次当你再看到屏幕上任何一个矩形元素时不妨想想如果让你来画你会用哪个库代码该怎么写。这或许就是“行空绘图”真正的乐趣所在。

相关新闻