需求背景
技术选型:canvas
上图所展示的游戏场景,“可乐瓶”里有多个“气泡”,需要设置不同的动画效果,且涉及 deviceOrientation 的交互,需要有大量计算改变元素状态。从性能方面考虑,canvas 是不二的选择。技术点:canvas 绘制图像
通过对游戏场景的进一步分析,可见场景中的“气泡”元素形状都是相同的,且不规则,通过 canvas 直接绘制形状实现成本较高,因此需要在 canvas 上绘制图像。技术点:canvas 图像旋转与翻转
虽然“气泡”元素是相同的,可以使用相同的图像,但图像需要多个角度/多个方向展示,因此需要对图像进行相应的旋转与翻转(镜像),这也是本文所要介绍的重点。
认识 canvas 坐标系
坐标原点 (0,0) 在左上角
X坐标向右方增长
Y坐标向下方延伸
<canvas id='myCanvas'></canvas>
// 获取 canvas 对象
var canvas = document.getElementById('myCanvas')
canvas.width = 750
canvas.height = 1054
// 获取 canvas 2D 上下文对象
var ctx = canvas.getContext('2d')
在 canvas 上绘制图像
void ctx.drawImage(image, dx, dy);
void ctx.drawImage(image, dx, dy, dWidth, dHeight);
void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
var img = new Image()
img.src = 'xxxxxxx.png'
img.onload = function() {
// 绘制图像
ctx.drawImage(img, 512, 220, 160, 192);
}
canvas 坐标变换
平移 translate:
ctx.translate(x, y)
translate() 方法接受两个参数。x 是左右偏移量,y 是上下偏移量。
旋转 rotate:
ctx.rotate(angle)
rotate() 方法只接受一个参数。旋转的角度 angle,它是顺时针方向的,以弧度为单位的值。
缩放 scale:
ctx.scale(x, y)
scale() 方法接受两个参数。x 和 y 分别是横轴和纵轴的缩放因子。其缩放因子默认是 1,如果比 1 小是缩小,如果比 1 大则放大。
变形 transform:
ctx.transform (a, b, c, d, e, f)
transform() 方法是对当前坐标系进行矩阵变换。
ctx.setTransform (a, b, c, d, e, f)
setTransform() 方法重置变形矩阵。先将当前的矩阵重置为单位矩阵(即默认的坐标系),再用相同的参数调用 transform() 方法设置矩阵。
以上两个方法均接受六个参数,具体如下:
参数 | 含义 |
---|---|
a | 水平缩放绘图 |
b | 水平倾斜绘图 |
c | 垂直倾斜绘图 |
d | 垂直缩放绘图 |
e | 水平移动绘图 |
f | 垂直移动绘图 |
图像旋转的实现
save() 方法用来保存 Canvas 状态的,没有参数。每一次调用 save() 方法,当前的状态就会被推入栈中保存起来。当前状态包括:
当前应用的变形(移动/旋转/缩放)
strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation 的值
当前的裁切路径(clipping path)
restore() 方法用来恢复 Canvas 状态,没有参数。每一次调用 restore() 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。
状态保存在栈中,可以嵌套使用 save() 与 restore()。
图像翻转的实现
坐标系统的矩阵变换
x' = ax + cy + e
y' = bx + dy + f
平移 translate:
x' = 1x+0y+tx = x+tx
y' = 0x+1y+ty = y+ty旋转 rotate:
x' = x*cosθ-y*sinθ+0 = x*cosθ-y*sinθ
y' = x*sinθ+y*cosθ+0 = x*sinθ+y*cosθ缩放 scale:
x' = Sx*x+0y+0 = Sx*x
y' = 0x+Sy*y+0 = Sy*y切变
x' = x+y*tan(θx)+0 = x+y*tan(θx)
y' = x*tan(θy)+y+0 = x*tan(θy)+y镜像反射
// 定义(ux,uy)为直线(y=kx)方向的单位向量
ux=1/sqrt(1+k^2)
uy=k/sqrt(1+k^2)
x' = (2*ux^2-1)*x+2*ux*uy*y
y' = 2*ux*uy*x+(2*uy^2-1)*y
图像旋转:
图像翻转:
图像镜像反射(翻转+旋转):
像素操作实现图像翻转
ImageData ctx.getImageData(sx, sy, sw, sh);
putImageData()
CanvasRenderingContext2D.putImageData()
是 Canvas 2D API 将数据从已有的 ImageData 对象绘制到位图的方法。 如果提供了脏矩形,只能绘制矩形的像素。
void ctx.putImageData(imagedata, dx, dy);
void ctx.putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight);
// 绘制图像
ctx.drawImage(img, x, y, width, height)
// 获取 img_data 数据
var img_data = ctx.getImageData(x, y, width, height),
i, i2, t,
h = img_data.height,
w = img_data.width,
w_2 = w / 2;
// 将 img_data 的数据水平翻转
for (var dy = 0; dy < h; dy ++) {
for (var dx = 0; dx < w_2; dx ++) {
i = (dy << 2) * w + (dx << 2)
i2 = ((dy + 1) << 2) * w - ((dx + 1) << 2)
for (var p = 0; p < 4; p ++) {
t = img_data.data[i + p]
img_data.data[i + p] = img_data.data[i2 + p]
img_data.data[i2 + p] = t
}
}
}
// 重绘水平翻转后的图片
ctx.putImageData(img_data, x, y)
小结
图像翻转:
基础变换法:
javascript // 方法一 ctx.save() ctx.translate(canvasWidth, 0) ctx.scale(-1, 1) ctx.drawImage(img, canvasWidth-width-x, y, width, height) ctx.restore()
javascript // 方法二 ctx.save() ctx.scale(-1, 1) ctx.drawImage(img, -width-x, y, width, height) ctx.restore()
矩阵变换法:
javascript // 方法一 ctx.save() ctx.transform(-1, 0, 0, 1, canvasWidth, 0) ctx.drawImage(img, canvasWidth-width-x, y, width, height) ctx.restore()
javascript // 方法二 ctx.save() ctx.transform(-1, 0, 0, 1, 0, 0) ctx.drawImage(img, -width-x, y, width, height) ctx.restore()
像素操作法:
javascript ctx.drawImage(img, x, y, width, height) var img_data = ctx.getImageData(x, y, width, height), i, i2, t, h = img_data.height, w = img_data.width, w_2 = w / 2; for (var dy = 0; dy < h; dy ++) { for (var dx = 0; dx < w_2; dx ++) { i = (dy << 2) * w + (dx << 2) i2 = ((dy + 1) << 2) * w - ((dx + 1) << 2) for (var p = 0; p < 4; p ++) { t = img_data.data[i + p] img_data.data[i + p] = img_data.data[i2 + p] img_data.data[i2 + p] = t } } } ctx.putImageData(img_data, x, y)
图像镜像对称(翻转+旋转):
基础变换法:
javascript ctx.save() ctx.scale(-1, 1) ctx.translate(-width/2-x, y+height/2) ctx.rotate(-angle * Math.PI / 180) ctx.drawImage(img, -width / 2, -height / 2, width, height) ctx.restore()
矩阵变换法:
javascript ctx.save() var k = Math.tan( (180-angle)/2 * Math.PI / 180 ) var ux = 1 / Math.sqrt(1 + k * k) var uy = k / Math.sqrt(1 + k * k) ctx.transform( (2*ux*ux-1), 2*ux*uy, 2*ux*uy, (2*uy*uy-1), x + width/2, y + height/2 ) ctx.drawImage(img, -width/2, -height/2, width, height) ctx.restore()
参考文章
《W3cplus - CANVAS 系列》
《html5 canvas.transform[转]》
《html5 canvas 学习笔记》
《在HTML5中翻转图片》
本文分享自微信公众号 - 凹凸实验室(AOTULabs)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。