不久前,我做了一个小玩具,可以 模拟雕刻南瓜。这是在 WebOS 驱动的 TouchPad 刚刚推出并大受欢迎的时期。
从那时起,网络浏览器已经发展了很多,如今,Mozilla 正在通过 Firefox OS 实现基于浏览器的操作系统的愿景。无论如何,我一直都在挖掘和整理一些旧的应用程序。当你的应用程序在 FirefoxOS 上运行时,你不仅要将其移植到另一台设备上,而是将其移植到网络上。所以现在,Halloween Artist 几乎可以在任何地方运行,包括那些开始在各地出现的超赞(而且价格实惠!)的 Firefox 手机。
与平台无关的网络应用程序:http://halloweenartist.penduin.net
在 Firefox Marketplace 上:https://marketplace.firefox.com/app/halloween-artist
如果你之前没有玩过 Halloween Artist,可以在阅读之前尝试一下。
但足够的历史了;这篇文章的重点是深入了解南瓜灯的内部结构,并讨论程序的工作原理!
参考
在开始之前,先来看一些链接!
- 直到今天,我 每次使用 canvas 时都会从这里开始
- 以前我只使用鼠标事件,最近才添加了真正的触摸支持。现在,我建议反过来,或者至少 将两者都考虑在内。
- 有很多使用 canvas 创建简单绘图程序的指南。 这篇文章非常详细。
我们开始吧!
第一步是在背景中获取一个南瓜图像,并在其顶部叠加一个画布。这个画布将跟踪鼠标和触摸事件,并允许用户勾勒形状。
接下来,我们需要一个“内部”图像,它将显示在雕刻的形状中。在上面,我们将绘制南瓜,但用户绘制的部分被切掉(变为透明)。幸运的是,canvas API 具有一些方便的合成模式,非常适合这些任务。我们需要的主要操作是“source-out”。保留目标图像,除了它与源形状相交的部分。然后,只需要进行一个正常的 source-over 合成即可。
var face = document.getElementById("draw");
var faceCtx = face.getContext("2d");
var glow = document.getElementById("glow");
var dest = document.getElementById("bg");
var destCtx = dest.getContext("2d");
var img = document.getElementById("pumpkin");
dest.width = dest.width; // reset the destination canvas
faceCtx.globalCompositeOperation = "source-out";
faceCtx.drawImage(img,
0, 0, img.width, img.height,
0, 0, size, size);
// draw glowing background (to dest)
destCtx.drawImage(glow, 0, 0, glow.width, glow.height,
0, 0, size, size);
// apply the face
destCtx.globalAlpha = 1;
destCtx.globalCompositeOperation = "source-over";
destCtx.drawImage(face, 0, 0);
这是一个开始!但要看起来像一个真正的雕刻南瓜,我们需要添加一些 3D 魔法来绘制内边缘。实际上,把这句话划掉 - 我们要作弊!:^) 我们将从一个分辨率较低、略微放大的南瓜图像开始
…然后我们将使用 canvas 的“lighter”globalCompositeOperation 和 0.5 的 globalAlpha 值将其亮度提高
…然后我们将“source-out”面部,与处理前景时一样
…然后缩小它,将其居中,并在内部背景和外部面部之间绘制它
我们正在接近!但根据形状的不同,我们的角落可能看起来不太令人信服。
幸运的是,我们一直都在作弊 - 这些合成操作和缩放 - 都非常快。即使在移动浏览器上也是如此。让我们将作弊提升到最大,并在循环中绘制该中间层,每次缩小幅度都更小。
…我们同时可以让每一步都比前一步(更“内”的一步)更轻微地亮起当前层。我们在这样做的时候,让我们使用更黄的颜色将其亮度提高;我们的第一次尝试看起来有点粉红色。
// build pumpkin by layer and draw it shrunk, inner to outer (to dest)
var i;
var darken = 0.4;
for(i = 56; i > 0; i -= 4) {
// mix a color for the layer
scratchCtx.globalCompositeOperation = "source-over";
scratchCtx.globalAlpha = 1;
scratchCtx.drawImage(glow, 0, 0, glow.width, glow.height,
0, 0, 768, 768); // bright glow...
scratchCtx.globalAlpha = 0.3;
scratchCtx.drawImage(flick, // light...
0, 0, 16, 16, 0, 0, 768, 768);
scratchCtx.globalAlpha = darken;
scratchCtx.drawImage(flesh, // darken with flesh
0, 0, 256, 256, -8, 0, 768 + 16, 768);
darken += 0.02; // ...more each time
// cut out the face
// NOTE: "face" is already the outer layer.
// we want to copy its alpha mask, so "-in" instead of "-out".
scratchCtx.globalCompositeOperation = "destination-in";
scratchCtx.globalAlpha = 1;
scratchCtx.drawImage(face, 0, 0);
// draw layer
destCtx.drawImage(scratch, 0, 0, 768, 768,
i, i, 768 - (i * 2), 768 - (i * 2));
}
通过将该中间层步骤放在循环中并进行一些调整,我们可以获得更逼真的边缘
因此,我们绘制了一大堆这些中间层,只是为了在下一遍绘制过程中覆盖大部分内容。如果你从这个角度考虑,这有点浪费,但想想这比尝试“真实地”模拟所有这些任意的切口表面要简单多少。相反,这段代码从内到外,一次构建一小块南瓜壳。如果我说这是一种相当优雅的幻觉,那我就这么说了;我们可以用一种具有深度感的图像,而无需进行任何复杂的计算或密集的处理。
添加润色
警告:前方更多历史。我会尽量简明扼要。:^)
确定“深度”的起点(缩小多少)以及绘制多少步,同时保持“雕刻”功能运行得足够快,这都是有趣的实验和妥协。在高分辨率显示器上,如果你绘制非常陡峭、参差不齐的形状,仍然会出现伪影。但在性价比、低端设备的兼容性以及 99% 的形状用例方面,我感到非常满意。
我希望这个程序可以轻松上手,所以我每天都会将最新版本加载到我的 TouchPad 上,然后交给我的同事,不给他们任何说明。
早期,有人建议雕刻的图像应该闪烁,好像里面的蜡烛燃烧不均匀一样。太容易了!“雕刻”函数现在会生成两个图像,一个正常图像,另一个稍亮一些的图像,它(使用 CSS)恰好位于主图像之上。它使用随机的超时和 CSS 动画对“opacity”属性进行淡入淡出。几分钟的润色,幻觉就更好了。
.fade {
transition: opacity 0.5s linear;
-moz-transition: opacity 0.5s linear;
-webkit-transition: opacity 0.5s linear;
}
.fade.out {
opacity: 0;
}
function animateFlicker() {
var flicker = document.getElementById("flicker");
if(HA.flickerTimer) {
clearTimeout(HA.flickerTimer);
HA.flickerTimer = null;
}
if(!HA.settings.flicker) {
return;
}
if(flicker.className === "fade out") {
flicker.className = "fade ";
} else {
flicker.className = "fade out";
}
HA.flickerTimer = setTimeout(animateFlicker, (Math.random() * 1000));
}
我不记得是谁了,但有一位同事在没有绘制任何东西的情况下直接点击了“雕刻”按钮。这是自然的本能。所以,我添加了一些逻辑来查看是否已经绘制了任何形状,如果没有,就为用户提供一些快速说明。我很高兴我注意到了这一点,因为之后在向更多人展示应用程序时,大约四分之一的人做了同样的事情。与让用户疑惑为什么没有发生任何事情相比,显示一个提示弹出窗口要好得多。
如果用户在南瓜外面绘制怎么办?会产生各种奇怪的伪影。所以,我使用那个方便的 canvas 合成,在雕刻之前对用户的输入进行蒙版。一个好的副作用是,你现在可以一直雕刻到南瓜的边缘,就像把它切成两半一样。
这个解决方案导致了另一个问题。当人们意识到他们可以肆无忌惮地雕刻巨大的洞时,他们会看到南瓜的空空如也、发光的内部表面。光是从哪里来的呢?
所以,在绘制背景和绘制缩放后的果肉层之间,我插入了一支蜡烛。我实际上拍了一些漂亮白色蜡烛的照片,并用 GIMP 将其加工成我方块状的“penduin”头像。在这个程序中,不妨包含某种签名吧,对吧?我制作了两个不同的版本,分别对应于两个闪烁的图像,并且在更亮的版本上让火焰更大一些。这增添了一丝动画效果,使幻觉更加逼真。
当然,还有那些不那么令人兴奋的事情。删除那些讨厌的硬编码值,让它在任何屏幕尺寸下都能正常工作。确保触摸和鼠标支持都能按预期工作。如果屏幕很小,按钮会挡路,那就重新排列一下按钮。所有这些都是家常便饭。
GitHub 上的代码 - 万圣节快乐!
好吧,这已经涵盖了大部分内容。你可以随意查看源代码,如果你想了解更多或添加自己的调整,你也可以这样做。(注意混乱;我在里面留了一些实验性的调整和之前的尝试,已注释掉。)Halloween Artist 是 GPLv3,由于你可能不想从网络应用程序本身获取源代码(而且我的糟糕 DSL 可能随时会断线),所以我将它 在 GitHub 上发布。
玩得开心,万圣节快乐!:^)
关于 Owen Swerkstrom
从 Construx 开始,一直是爱折腾的人。从 Apple II 开始,一直是程序员。从 Slackware 2.1 开始,一直是 Linux 用户。
关于 Robert Nyman [荣誉编辑]
Mozilla Hacks 的技术布道者和编辑。发表关于 HTML5、JavaScript 和开放网络的演讲和博客文章。Robert 是 HTML5 和开放网络的坚定支持者,从 1999 年开始从事网络前端开发 - 在瑞典和纽约市。他也会定期在 http://robertnyman.com 上写博客,喜欢旅行和结识新朋友。
一条评论