canvas进阶——实现静态图像的变形并合成动态效果

写在最前

在以前的这篇bezierMaker.js——N阶贝塞尔曲线生成器的文章中咱们提到了对于高阶贝塞尔公式的绘制与生成。不过更多的童鞋看到后可能会不知道其使用场景是什么。故做者本次分享一下基于bezierMaker.js实现的将静态图片按照自定义曲线轨迹扭曲图片并合称为动态效果。javascript

欢迎关注个人博客,不按期更新中——html

效果预览

以前的描述可能不是很清楚咱们直接看下效果图:java

首先加载一张图:
imagegit

而后经过bezierMaker.js提供的试验场功能来绘制一段曲线,进行图片扭曲:
imagegithub

最后拟合为动态图:web

2018-01-19 12_40_32

再来一个竖直方向的扭动:canvas

anmate

demo地址数组

源码地址svg

图像变形实现思路

  1. 绘制一条由bezierMaker.js生成的贝塞尔曲线,以此来掌握曲线各点的准确坐标值
  2. 肯定扭曲方向为横向或纵向
  3. 根据该方向的基准线(图中的灰色线)来计算本次绘制的曲线与基准对比的偏移量,按照该方向每隔1px记录一个值
  4. 将图像数据按照选定方向进行切分,将一个一维数组imgData.data变为一段一段有方向的二维数组
  5. 将每段数组按照以前记录的偏移值进行移位后再拼接为一维数组
  6. 将新拼接好的数组从新赋值到imgData中

其中较为核心的实现即横向与纵向对一维图像数据的切分。其中横向相对简单,细节以下:动画

image

如上图所示,在原始图像的数据中的数据形式为一维数组的形式,而对其进行拆分则是一个从中不断截取与提取数据的过程。横向拆分较为简单,只须要肯定每一行开始的位置便可,截取的数量就是一行的元素数。同时纵向拆分则须要多加一步,咱们须要计算每一层数组中的每个数,像上图通常拆分第每列数组时首先要遍历图的宽获得每一列的索引,再遍历图的高,经过高✖️宽✖️4 + 宽 ✖️ 4算出当前值在原数据中的位置。当拆分红功数组后,将数组依次移位,移位数为以前曲线与基准线的偏移量决定。

//pg.js
//按行拆分
bezierArr.forEach(function (obj, index) {
    if (_.imgStartY < obj.y && _.imgStartY + _.imgHeight > obj.y && type === 'row') {

        var diffX = parseInt(obj.x - _.baseX, 10) //计算偏移量
        var dissY = parseInt(obj.y - _.imgStartY, 10)
        var rowNum = dissY
        imgDataSlice = _.imgData.data.slice((rowNum) * _.imgWidth * 4, rowNum * _.imgWidth * 4 + _.imgWidth * 4) //按层切片
        ...
    }
})

//按列拆分
for (var i = 0; i < _.imgWidth; i++) {
    imgDataSlice = []
    for (var j = 0; j < _.imgHeight; j++) {
        var index = j * _.imgWidth * 4 + i * 4
        var sliceArr = _.imgData.data.slice(index, index + 4)
        imgDataSlice = imgDataSlice.concat(Array.from(sliceArr))
    }
    if(_.imgChangeObj[i]) {
        for (var k = 0; k < Math.abs(_.imgChangeObj[i].diffY * 4); k++) {
            imgDataSlice = _.arraymove(_.imgChangeObj[i].diffY, imgDataSlice)
        }
        for (var p = 0; p < imgDataSlice.length / 4; p++) {
            arr[p * _.imgWidth * 4 + i * 4] = imgDataSlice[p * 4]
            arr[p * _.imgWidth * 4 + i * 4 + 1] = imgDataSlice[p * 4 + 1]
            arr[p * _.imgWidth * 4 + i * 4 + 2] = imgDataSlice[p * 4 + 2]
            arr[p * _.imgWidth * 4 + i * 4 + 3] = imgDataSlice[p * 4 + 3]
        }
    }
}

核心的数组拆分移位再合并的逻辑相对分散,知道思路便可有兴趣的同窗欢迎戳源码~

合并成动态效果

核心思想为从咱们的原始形态到最终态的两张静态图咱们已经获得了。如今咱们须要作的是添加几张过渡态。在这里面有两种方式:
- 将计算的各点偏移量进行按比例偏移,好比一共四张图合成则须要三次改变状态,那么每次将数组移位的量设定为总量的1/3,每次移位后拼出一维数组更新到一张离屏canvas中将其保存为base64,做为后续合并时的替换url
- 计算贝塞尔曲线控制点的偏移量且进行按比例偏移。如一开始的垂直或水平的初始图控制点造成了一条直线。同时最终形态的控制点位置咱们已经知道了,借此咱们能够将控制点由直线到两边的过程按比例切分,依次计算各中间态控制点所造成曲线致使的偏移图像数据,导出base64,做为后续合并替换的url

做者一开始使用了第一种方式,可是有一个明显的缺陷及经过按比例直接偏移会致使拆分出来的每层的偏移每次都是相同的,那么就会出现锯齿现象。由于图像扭曲可能上一层在这一次移位的时候偏移5合适但是你仍然偏移了总量的1/3致使与下一层的图像不匹配从而出现锯齿。故从新选择了第二种方式,由从新计算各中间态图像的控制点再来移位图像数据,图像的呈现状况就改善了不少。

小结

因为操做图像数据量比较大,故在尝试demo的时候若是遇到ui卡顿那是正在计算中,并无引入webworker之类的因此请稍等一会就会出现结果=。=
PS:demo使用步骤
- 加载图像
- 画曲线,竖向切分请点击checkbox,同时曲线宽要大于图像的宽。横向切分数据则曲线高要大于图像,保证起终点在基准线外。描点后点击绘制
- 计算结束后点击合成

其余canvas相关文章

最后

demo地址

源码地址

惯例po做者的博客,不定时更新中——

有问题欢迎在issues下交流。