An Introduction to Canvas in GNU Emacs
An Introduction to Canvas in GNU Emacs
GNU Emacs Canvas 简介
by Divya2026 August 23 作者:Divya2026,8月23日
Bad apple playing on the top right, a 2d rainbow growing box on the left. The bottom contains: dots 3d demo, pendulum, a bouncing ball. The canvas patch has been finally merged to upstream GNU Emacs. If you have been following my Mastodon, this was a journey that took us ~8 months. But finally we have, in core Emacs, a feature that I desperately desired in early 2025 when I was starting the Emacs Reader project: a way to update and manipulate images in Emacs without choking Emacs by putting the image data in strings. 右上角播放着《Bad Apple》,左侧是一个不断生长的 2D 彩虹方块。底部包含:3D 点阵演示、钟摆和弹跳球。Canvas 补丁终于被合并到了 GNU Emacs 上游。如果你一直关注我的 Mastodon,就会知道这是一段历时约 8 个月的旅程。但最终,我们在 Emacs 核心中拥有了我在 2025 年初启动 Emacs Reader 项目时梦寐以求的功能:一种在 Emacs 中更新和操作图像的方法,而无需通过将图像数据放入字符串中来导致 Emacs 卡顿。
I went through a series of different hacks to overcome these limitations, some more successful than others, but thanks to the magnificent Daniel (aka minad) who suggested the idea of exposing a pixel buffer from within Emacs via the dynamic module API. That idea is now a feature in Emacs, and will be part of Emacs 32 release cycle. A more detailed history about this will be written later, for now this article is an introduction to the new feature, its API and what you can do using it. I will try to keep it as self-contained as I can, so that beginners are able to understand it. But first, we need to clarify certain misconceptions that people might have about this feature. 我尝试了一系列不同的黑客手段来克服这些限制,有些成功了,有些则不然。但多亏了出色的 Daniel(又名 minad),他提出了通过动态模块 API 从 Emacs 内部暴露像素缓冲区的想法。这个想法现在已成为 Emacs 的一项功能,并将包含在 Emacs 32 的发布周期中。关于此功能的详细历史稍后会撰写,目前本文旨在介绍这一新功能、其 API 以及你可以用它做什么。我会尽量保持内容的独立性,以便初学者也能理解。但首先,我们需要澄清人们对该功能可能存在的一些误解。
1. Frequently Asked Questions
1. 常见问题解答
“Is this related to HTML5 Canvas?” No. It has nothing to do with any web framework or technology. We call it “canvas” because it allows the user to draw arbitrarily on to a surface in an Emacs buffer. So at a high-level, both the HTML5 and Emacs canvas do the same thing, but they have zero relation in their implementation or how they work. HTML5 Canvas also has a more rich API with drawing functions and such, right now Emacs’ canvas API integrates with the existing image API and just provides access to the underlying pixel buffer via dynamic module API. See Canvas via Dynamic Modules. “这与 HTML5 Canvas 有关吗?”没有。它与任何 Web 框架或技术无关。我们称之为“canvas”(画布),因为它允许用户在 Emacs 缓冲区的表面上进行任意绘制。因此,从宏观上看,HTML5 和 Emacs 的画布功能相同,但在实现方式或工作原理上毫无关联。HTML5 Canvas 拥有更丰富的绘图函数 API,而目前的 Emacs Canvas API 与现有的图像 API 集成,仅通过动态模块 API 提供对底层像素缓冲区的访问。请参阅“通过动态模块实现 Canvas”。
“How is this different from SVG that Emacs has support for since a long time?” Good question! So, at the level of an Emacs buffer the canvas looks and behaves just like any other Emacs image, including SVGs. Where it differs from SVG and other images is: a canvas’ :data is just ARGB32 pixel data (in strings or vectors), so no specific compressed format. the ability to access an Emacs Lisp image object from a lower level and be able to manipulate it update the image without having to call a full redisplay.
“这与 Emacs 长期支持的 SVG 有什么不同?”问得好!在 Emacs 缓冲区层面,Canvas 的外观和行为与其他 Emacs 图像(包括 SVG)完全一样。它与 SVG 及其他图像的区别在于:Canvas 的 :data 只是 ARGB32 像素数据(以字符串或向量形式存在),因此没有特定的压缩格式;它能够从更底层访问 Emacs Lisp 图像对象,并能够对其进行操作,从而在无需调用完整重绘的情况下更新图像。
“Isn’t this still limited by Emacs’ redisplay engine?” Not entirely. We have a function called canvas-refresh that is to be called every time you wish to update the canvas, and this function doesn’t use/call Emacs’ redisplay, it has its own redrawing path that only updates the specific glyphs in the buffer that pertain to that canvas. Thus, whereas a full redisplay would cost more because it will try to update all the glyphs that changed, canvas-refresh only updates what needs to be. Since Emacs 25 we have double-buffering so if that’s enabled in the frame you’d still need to call redisplay to flip the buffers, but that won’t slow down your canvas.
“这难道不会受到 Emacs 重绘引擎的限制吗?”不完全是。我们有一个名为 canvas-refresh 的函数,每当你想要更新画布时调用它。该函数不使用也不调用 Emacs 的重绘机制,它有自己的重绘路径,仅更新缓冲区中与该画布相关的特定字形。因此,虽然完整重绘会因为尝试更新所有已更改的字形而消耗更多资源,但 canvas-refresh 只更新必要的部分。自 Emacs 25 以来,我们有了双缓冲技术,因此如果框架中启用了该功能,你仍然需要调用重绘来翻转缓冲区,但这不会拖慢你的画布。
“Is this hardware-accelerated? If not, why are you not using the GPU for this?” Because we simply don’t need it :D! A canvas simply exposes a pixel buffer, and Emacs simply redisplays it as it changes. You can get your pixels hardware accelerated outside of Emacs if you wish to, and then put them in canvas’ pixel buffer. This will give you hardware acceleration where you need it the most, pixel calculation, texture math, etc. Ideally, an introduction of a GPU powered redisplay within Emacs itself would be great and certainly help but one can still reap much of the benefits of HW acceleration from current Emacs via canvas. “这是硬件加速的吗?如果不是,为什么不用 GPU?”因为我们根本不需要 :D!Canvas 只是暴露了一个像素缓冲区,而 Emacs 只是在它发生变化时进行重绘。如果你愿意,可以在 Emacs 之外进行硬件加速处理像素,然后将它们放入 Canvas 的像素缓冲区中。这将在你最需要的地方(如像素计算、纹理数学等)提供硬件加速。理想情况下,在 Emacs 内部引入 GPU 加速重绘会很棒且肯定有帮助,但通过 Canvas,人们仍然可以从当前的 Emacs 中获得硬件加速的大部分好处。
2. What Is a Canvas?
2. 什么是 Canvas?
Before answering that question, we should ask: “what is an image in GNU Emacs?” Everybody has seen Emacs display dazzling images since decades, and of varying formats. How does Emacs do that? Well, at the level of an Emacs buffer we have something called Text Properties (also see Overlay Properties), as its name suggests it is a property list (aka plist) for a character position or a string within a buffer. Among the many properties a text can have, there’s a special one called the display property. This special property is responsible for how the text gets displayed.
在回答这个问题之前,我们应该先问:“GNU Emacs 中的图像是什么?”几十年来,每个人都见过 Emacs 显示各种格式的精美图像。Emacs 是如何做到的呢?在 Emacs 缓冲区层面,我们有一种叫做“文本属性”(Text Properties,另请参阅 Overlay Properties)的东西。顾名思义,它是缓冲区内字符位置或字符串的属性列表(即 plist)。在文本可以拥有的众多属性中,有一个特殊的属性叫做 display 属性。这个特殊属性负责控制文本的显示方式。
This is exactly what we use to display images! You use create-image to create an image object, which is exactly a plist, and then you set this image object (called an image specification) to be the display property of some text or overlay. This is briefly how the API of how images work in GNU Emacs. The new canvas feature works in full compatibility with this API. In short, at the level of an Emacs buffer, a canvas is just another image type! Indeed, compiling the latest GNU Emacs from source and evaluating: (image-type-available-p 'canvas) should return a t.
这正是我们用来显示图像的方法!你使用 create-image 创建一个图像对象(本质上就是一个 plist),然后将此图像对象(称为图像规范)设置为某些文本或覆盖层(overlay)的 display 属性。这就是 GNU Emacs 中图像 API 工作原理的简述。新的 Canvas 功能与此 API 完全兼容。简而言之,在 Emacs 缓冲区层面,Canvas 只是另一种图像类型!事实上,从源码编译最新的 GNU Emacs 并执行 (image-type-available-p 'canvas) 应该会返回 t。
The immediate question then is: if Emacs already supported images, and if canvas just follows the same API, then what was the point of it? Why have a new image type, when we have XBM, PPM, PNG, GIF, SVG, etc.? Well, the two-fold short answer is: 那么随之而来的问题是:如果 Emacs 已经支持图像,且 Canvas 遵循相同的 API,那么它的意义何在?既然我们已经有了 XBM、PPM、PNG、GIF、SVG 等格式,为什么还需要一种新的图像类型?简短的回答有两点:
-
You need to pass image data via strings to create these objects and for most of the above formats it’s expensive to do so (except SVGs).
-
你需要通过字符串传递图像数据来创建这些对象,对于上述大多数格式(SVG 除外),这样做代价高昂。
-
Any arbitrary external image data cannot be directly displayed without throttling Emacs strings or objects, etc. (i.e., there’s no low-level access to the image data). With SVGs you can mostly do really good things as long as they are simple enough, the moment you lead to have complex graphics at high refresh rate, Emacs starts throttling.
-
任何任意的外部图像数据都无法在不导致 Emacs 字符串或对象卡顿的情况下直接显示(即,没有对图像数据的底层访问权限)。使用 SVG,只要图形足够简单,通常可以做得很好;但一旦涉及到高刷新率下的复杂图形,Emacs 就会开始卡顿。