实现 Canvas 图片编辑的解决过程

144
作者 赵团结
2016.09.29 18:26* 字数 940

本文记录了在手机上实现图片编辑、画画并保存导出的实现过程和解决办法。前端实现图片编辑主流的办法就是基于 Canvas 来做,我实现了一个类,封装了比较完整的 Canvas 方法,实现初始化画布、设置画笔颜色、旋转画布和异步传入图片等。

背景


客户想要的样子

我有收到一个客户需求,他们是类似做保险公司取证的App,他们发现业务员在拍摄的现场图片找不到重点。所以希望能在拍照后,在图片上“画画圈,打打横线”来标注再上传。

后来又希望能在加上旋转、缩放等功能。

问题

下面主要举两个例子来引出细节:

  • 图片自适应
  • 旋转

图片自适应

拿 iPhone 6的设备宽度375 * 667举例,手机拍照后需要对图片进行缩放才可以放到画布中。

如果要等比缩放:
1.需要将图片的宽度缩小到375px
2.再同比缩小高度
3.高度仍可能过长,所以要再次重复以上步骤。

其实文字已经描述出递归的意味了,就用它来实现吧!

// 递归大法好
function scale(w, h, maxW, maxH) {
    if (w > maxW) {
        return scale(maxW, h / (w / maxW), maxW, maxH);
    }
    if (h > maxH) {
        return scale(w / (h / maxH), maxH, maxW, maxH);
    }
    return [w, h];
}

坐标纠正与真实缩放

但是你不能真正缩放这个图片的。
如果你真的用了ctx.scale来达到真实缩小,那导出时候无法保证原图尺寸和无损。

DPR

这里考虑了 DevicePixelRatio 的思路,就是分为 真实比例 你看到的比例
<canvas/> 的 attribute 长宽与 CSS 的长宽做“倍数”设置,来达到 视觉缩放

var img, ctx;
ctx.width = img.width;
ctx.height = img.height;

var scaled = scale(img.width, img.height, 375, 667);
ctx.style.width = scaled[0];
ctx.style.height = scaled[1];

虽然视觉上缩小了,但是坐标还是原来的,需要得到缩放比例。

var ratio = img.width / scaled[0];

必须对 Touch 事件返回的坐标进行纠正,不然画笔歪掉了。

var preventEvent = function(e) {
  e.preventDefault();

  var ratio; // 上面提到的 ratio
  var touch = isTouch ? e.touches[0] : e;

  e.moveX = (touch.clientX - e.target.offsetLeft) * ratio;
  e.moveY = (touch.clientY - e.target.offsetTop) * ratio;
  return e;
}

$canvas.addEventListener('touchmove', function(e) {
  e = preventEvent(e);
});

这里我直接在 Touch 事件中的坐标加入 ratio 增量,这样在后面 lineTo(x, y) 的时候纠正因缩放造成的偏移影响。

旋转功能

在设计旋转函数时,采取的是直接将当前 canvas toDataURL,然后作为新的图层旋转。

这样做可以很完美的达到画笔、图片一起旋转,但是缺点就是每次旋转操作都是一次“截图”,造成性能浪费和图片失真。


旋转10次后

如图,这是做出来的 Demo,实际测试发现手机拍摄的高清图片在旋转10次后已经丢失细节。

那么如何解决旋转问题?

这里我发现了一个新的思路:

  1. 不直接使用 Canvas 操作图片,图片导入到 <img/> 上。
  2. 旋转、放大缩小等用 CSS 的 transform
  3. 每个操作记录都要保留,放大了多少倍,X轴移动多少像素,图片旋转多少度。
  4. 最后在保存时候,再在 canvas 绘制一遍即可。

同时满足无损操作,和撤销到上一步的功能。更为完善。

hidpi

在实现过程中我并没有对 DPR > 1 的 hidpi 屏幕做特殊处理,不过缩放功能已经实现。
这里可以引入一个 hidpi-canvas-polyfill来处理。

总结

1.对hidpi和图片自适应,利用 CSS 的 w, h 与<canvas/> 的 w, h 来达到视觉缩放。
2.不直接用 Canvas 操作图片,而导入为<img/>用 CSS transform,然后记录操作记录。
3.写文章要图文并茂,尽量用美女,不然没人仔细看。

日记本