canvas 图像旋转与翻转姿势解锁

栏目: 后端 · 发布时间: 7年前

内容简介:canvas 图像旋转与翻转姿势解锁

从一个游戏需求说起:

canvas 图像旋转与翻转姿势解锁
  1. 技术选型:canvas
    上图所展示的游戏场景,“可乐瓶”里有多个“气泡”,需要设置不同的动画效果,且涉及 deviceOrientation 的交互,需要有大量计算改变元素状态。从性能方面考虑,canvas 是不二的选择。
  2. 技术点:canvas 绘制图像
    通过对游戏场景的进一步分析,可见场景中的“气泡”元素形状都是相同的,且不规则,通过 canvas 直接绘制形状实现成本较高,因此需要在 canvas 上绘制图像。
  3. 技术点:canvas 图像旋转与翻转
    虽然“气泡”元素是相同的,可以使用相同的图像,但图像需要多个角度/多个方向展示,因此需要对图像进行相应的旋转与翻转(镜像),这也是本文所要介绍的重点。

后文代码以下图左侧绿框的“气泡”为示例,右侧展示了场景中用到的两个图像:

canvas 图像旋转与翻转姿势解锁

认识 canvas 坐标系

canvas 上图像的旋转和翻转,常见的做法是将 canvas 坐标系统进行变换。因此,我们需要先认识 canvas 坐标系统:

canvas 图像旋转与翻转姿势解锁

由上图可得,canvas 2D 环境中坐标系统和 Web 的坐标系统是一致的,有以下几个特点:

  1. 坐标原点 (0,0) 在左上角
  2. X坐标向右方增长
  3. Y坐标向下方延伸

回到上述需求中,我们获取 canvas 对象并设置相应的宽高:

<canvasid='myCanvas'></canvas>
// 获取 canvas 对象
var canvas = document.getElementById('myCanvas')
canvas.width = 750
canvas.height = 1054
// 获取 canvas 2D 上下文对象
var ctx = canvas.getContext('2d')

此时,canvas 的坐标系统如下图所示:

canvas 图像旋转与翻转姿势解锁

在 canvas 上绘制图像

在 canvas 上绘制图像,可以使用 drawImage() 方法,语法如下(详细用法参见 MDN ):

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);

需要注意的是,图像必须加载完毕,才能绘制到 canvas 上,否则会出现空白:

var img = new Image()
img.src = 'xxxxxxx.png'
img.onload = function(){
	// 绘制图像
	ctx.drawImage(img, 512, 220, 160, 192);
}

此时,便可以 canvas 上看到一个未旋转/翻转的“气泡”图像,如下图所示:

canvas 图像旋转与翻转姿势解锁

canvas 坐标变换

接下来,我们再来了解 canvas 坐标的变换。上述需求仅涉及 2D 绘制上下文,因此仅介绍 2D 绘制上下文支持的各种变换:

  1. 平移 translate:

    ctx.translate(x, y)
    

    translate() 方法接受两个参数。x 是左右偏移量,y 是上下偏移量。

  2. 旋转 rotate:

    ctx.rotate(angle)
    

    rotate() 方法只接受一个参数。旋转的角度 angle,它是顺时针方向的,以弧度为单位的值。

  3. 缩放 scale:

    ctx.scale(x, y)
    

    scale() 方法接受两个参数。x 和 y 分别是横轴和纵轴的缩放因子。其缩放因子默认是 1,如果比 1 小是缩小,如果比 1 大则放大。

  4. 变形 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 垂直移动绘图

图像旋转的实现

canvas 图像旋转与翻转姿势解锁

上图所示“气泡”,宽为 160,高为 192,x 轴方向距离原点 512,y 轴方向距离原点 220,逆时针旋转 35 度。

要绘制该“气泡”,需要先将坐标系平移(translate),再旋转(rotate)。具体实现步骤如下:

canvas 图像旋转与翻转姿势解锁

save() 方法与 restore() 方法:

  1. save() 方法用来保存 Canvas 状态的,没有参数。每一次调用 save() 方法,当前的状态就会被推入栈中保存起来。当前状态包括:
    • 当前应用的变形(移动/旋转/缩放)
    • strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation 的值
    • 当前的裁切路径(clipping path)
  2. restore() 方法用来恢复 Canvas 状态,没有参数。每一次调用 restore() 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。
  3. 状态保存在栈中,可以嵌套使用 save() 与 restore()。

图像翻转的实现

canvas 图像旋转与翻转姿势解锁

上图所示“气泡”,宽为 160,高为 192,x 轴方向距离原点 172,y 轴方向距离原点 365,顺时针旋转 35 度。

要绘制该“气泡”,需要先将坐标系统平移(translate),翻转(scale),平移(translate),再旋转(rotate)。具体实现步骤如下:

canvas 图像旋转与翻转姿势解锁

至此,实现了“气泡”的镜像翻转,但翻转后的“气泡”还需要旋转特定的角度,在方法一的基础上继续对坐标系统进行变换:

canvas 图像旋转与翻转姿势解锁

以上操作中进行了两次平移(translate)操作,可以进行合并简化:

canvas 图像旋转与翻转姿势解锁

坐标系统的矩阵变换

前文介绍了 2D 绘制上下文变形(transform)变换,实际是直接修改变换的矩阵,它可以实现前面介绍的平移(translate)/旋转(rotate)/缩放( scale)变换,还可以实现切变/镜像反射变换等。矩阵计算遵循数学矩阵公式规则:

canvas 图像旋转与翻转姿势解锁

由上公式可得:

x' = ax + cy + e
y' = bx + dy + f

矩阵变换可实现以下变换效果:

  1. 平移 translate:

    x' = 1x+0y+tx = x+tx
    y' = 0x+1y+ty = y+ty
    

    canvas 图像旋转与翻转姿势解锁

  2. 旋转 rotate:

    x' = x*cosθ-y*sinθ+0 = x*cosθ-y*sinθ
    y' = x*sinθ+y*cosθ+0 = x*sinθ+y*cosθ
    

    canvas 图像旋转与翻转姿势解锁

  3. 缩放 scale:

    x' = Sx*x+0y+0 = Sx*x
    y' = 0x+Sy*y+0 = Sy*y
    

    canvas 图像旋转与翻转姿势解锁

  4. 切变

    x' = x+y*tan(θx)+0 = x+y*tan(θx)
    y' = x*tan(θy)+y+0 = x*tan(θy)+y
    

    canvas 图像旋转与翻转姿势解锁

  5. 镜像反射

    // 定义(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
    

    canvas 图像旋转与翻转姿势解锁

结合上述公式,可推导出图像旋转和翻转的矩阵变换实现:

  1. 图像旋转:
    canvas 图像旋转与翻转姿势解锁
  2. 图像翻转:
    canvas 图像旋转与翻转姿势解锁
  3. 图像镜像反射(翻转+旋转):
    canvas 图像旋转与翻转姿势解锁

像素操作实现图像翻转

除了坐标系统变换,canvas 的像素操作同样可以实现图像的翻转。首先需要了解下 getImageData() 方法(详细用法参见 MDN )和 putImageData() (详细用法参见 MDN )方法:

  1. getImageData()

    CanvasRenderingContext2D.getImageData() 返回一个 ImageData 对象,用来描述 canvas 区域隐含的像素数据,这个区域通过矩形表示,起始点为 (sx, sy)、宽为 sw、高为 sh。

    ImageData ctx.getImageData(sx, sy, sw, sh);
    
  2. 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)

小结

至此,小编的数学姿势又恢复到了高考水平。

  1. 图像旋转:

    • 基础变换法:

      ctx.save()
      ctx.translate(x + width / 2,  y + height / 2)
      ctx.rotate(angle * Math.PI / 180)
      ctx.drawImage(img, -width / 2,  -height / 2, width, height)
      ctx.restore()
      
    • 矩阵变换法:

      ctx.save()
      var rad = angle * Math.PI/180
      ctx.transform( Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad), x + width / 2,  y + height / 2)
      ctx.drawImage(img, -width / 2,  -height / 2, width, height)
      ctx.restore()
      
  2. 图像翻转:

    • 基础变换法:

      // 方法一
      ctx.save()
      ctx.translate(canvasWidth, 0)
      ctx.scale(-1, 1)
      ctx.drawImage(img, canvasWidth-width-x, y, width, height)
      ctx.restore()
      
      // 方法二
      ctx.save()
      ctx.scale(-1, 1)
      ctx.drawImage(img, -width-x, y, width, height)
      ctx.restore()
      
    • 矩阵变换法:

      // 方法一
      ctx.save()
      ctx.transform(-1, 0, 0, 1, canvasWidth, 0)
      ctx.drawImage(img, canvasWidth-width-x, y, width, height)
      ctx.restore()
      
      // 方法二
      ctx.save()
      ctx.transform(-1, 0, 0, 1, 0, 0)
      ctx.drawImage(img, -width-x, y, width, height)
      ctx.restore()
      
    • 像素操作法:

      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)
      
  3. 图像镜像对称(翻转+旋转):

    • 基础变换法:

      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()
      
    • 矩阵变换法:

      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()
      

以上所述就是小编给大家介绍的《canvas 图像旋转与翻转姿势解锁》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

当下的启蒙

当下的启蒙

[美] 史迪芬·平克 / 侯新智、欧阳明亮、魏薇 / 浙江人民出版社 / 2018-12 / 159.90

[编辑推荐] ● 比尔•盖茨最喜爱的一本书。理查德·道金斯心中的诺贝尔文学奖作品。尤瓦尔•赫拉利2018年最爱的书之一。 ● 当代最伟大思想家史蒂芬·平克全面超越自我的巅峰之作,一部关于人类进步的英雄史诗。 ●《当下的启蒙》用数据和事实揭示出世界的真相:不是黑暗,而是光明;不是丧,而是燃;我们没有退步,而是一直在进步,还将继续进步。用这本书点燃生活的勇气,亲手创造更美好的未来。 ......一起来看看 《当下的启蒙》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具