| 7 min read

随着 html5 canvas 的 api支持,我们现在可以轻松的对图片进行一些处理,比如图片的放大,缩小,图片的裁剪和旋转。因为它可以接触到像素级别的操作,进行更加复杂的操作。

绘制一张图片

在 canvas 中绘制一张图片 只需要利用到 drawImage 这个方法就可以实现。

ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

drawImage 它可以接受九个参数(不一定全部传入)

  • image: 它可以是一个 图片的 Image 元素,或者 Video ,Canvas 元素。

  • dx 表示在画布 x 轴的坐标值

  • dy 表示在画布 y 轴的坐标值

  • dWidth 表示在画布绘制的长度

  • dHeight 表示在画布绘制的高度

  • sx 表示在画布所绘制图片本身的 x轴 距离

  • sy 表示在画布所绘制图片本身的 y轴 距离

  • sWidth 表示所绘制图片的宽度范围,一般默认是图片的完整大小,你也可以将图片的某一部分开始绘制。

  • sHeight 表示所绘制图片的高度范围,一般默认是图片的完整大小,你也可以将图片的某一部分开始绘制。

一般我们实现 canvas 裁剪图片的就是利用这个方法, 来实现对于图片某些区域的裁剪。可以参考 crop方法

旋转图片

实现旋转图片我们需要使用 rotate 方法,然后传图响应的角度即可。

rotate 只支持传入角度的参数 angle ,它表示传入的弧度值,比如我们如果需要选择90度,我们需要将其转换为弧度值

const angle = 90 * Math.PI / 180;
ctx.rotate(angle);

旋转的角度参考坐标顶点是canvas 的笛卡尔坐标系的原点,如果你需要改变选择的角度参考坐标原点的话,你可以使用 translate 进行画布平移。

我们先列举较为常见的使用场景,就是 90度的递增旋转(0,90,180,270)这种情况。

当然0度的时候,非常好理解,就是简单的图片绘制在画布上。

这是绘制的 开始坐标点 (0,0)。

再看下面的图,简单的顺时针旋转90度后的情况:

旋转90度的情况,坐标轴会进行旋转,这个时候如果我们还是按照 (0,0) 进行绘制,图片的情况如图蓝色区域,这个时候我们需要进行初始也就是(dx, dy)的设置,这个时候需要从(0, -h) 开始进行绘制。

如果是旋转180度的话,效果如下:

这个时候我们需要将起点平移到(-w, -h) 开始。

同理,如果逆时针旋转90度后的效果如下:

这个时候需要我们在绘制图片的时候将起点移动到(0, -h)即可。

函数实现如下:

/**
* compress image
* reference https://github.com/brunobar79/J-I-C
**/

export default {
  // .... some other methods 
  
  _getImageType(str) {
    let mimeType = 'image/jpeg';
    const outputType = str.match(/(image\/[\w]+)\.*/)[0];
    if (typeof outputType !== 'undefined'){
      mimeType = outputType;
    }
    return mimeType;
  },

  rotate(src, degrees, callback) {
    this._loadImage(src, (image) => {
      let w = image.naturalWidth;
      let h = image.naturalHeight;
      const canvasWidth = Math.max(w, h);
      let cvs = this._getCanvas(w, h);
      let ctx = cvs.getContext('2d');
      ctx.save();
      let x = 0;
      degrees %= 360;
      if (degrees === 0) {
        return callback(src, w, h);
      }
      let y = 0;
      if ((degrees % 180) !== 0) {

        if (degrees === -90 || degrees === 270) {
          x = -h;
        } else {
          y = -w;
        }
        const c = w;
        w = h;
        h = c;
        cvs.width = w;
        cvs.height = h;
      } else {
        x = -w;
        y = -h;
      }
      ctx.rotate(degrees * (Math.PI / 180));
      ctx.drawImage(image, x, y);
      ctx.restore();
      const mimeType = this._getImageType(image.src);
      const data = cvs.toDataURL(mimeType, 1);
      callback(data, w, h);
      cvs = null;
      ctx = null;
    });
  },

  _loadImage(data, callback) {
    const image = new Image();
    image.src = data;
    image.onload = function () {
      callback(image);
    };
    image.onerror = function () {
      console.log('Error: image error!');
    };
  },

  _getCanvas(width, height) {
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    return canvas;
  },

};

但是上面的实现会存在一个问题,就是你需要去改变画布的范围,应为大多数图片长宽高都是不一致的,因此在旋转后 canvas 能够重合旋转的图片的趋于。

比如上图旋转90后,你会发现

旋转后画布的坐标范围是覆盖不了图像的。

我们可以在初始化的时候,将画布设置为一个最大正方形。

 const canvasWidth = Math.max(w, h);
 const cvs = this._getCanvas(canvasWidth, canvasWidth);
 const ctx = cvs.getContext('2d');

这个时候我们的 canvas 的大小无论怎么旋转都能够确保角度的覆盖。这个时候我们在试着将旋转的中心移到画布的中心开始。

ctx.translate(canvasWidth / 2, canvasWidth / 2);

这个时候如果旋转90度整个坐标系效果如图:

图中情况是 图片宽度小于高度,因此画布大小为 h x h

这个时候我们在绘制图片的起点就应该变成了(- h/2, h/2 - h ) 才能保证所绘制的图片位置能够完全放在画布顶部(放在顶部有利于我们使用另外一个canvas 进行多余空白的裁剪)。

x = - canvasWidth/2;
y = canvasWidth/2 - h;

同理我们可以通过平移算法获得在180度的情况下我们的绘制其实坐标。

这个时候我们的绘制坐标起点会是(-h/2 - h, h/2 - w)这个是后我们的代码可以是这样:

x = canvasWidth / 2 - h;
y = canvasWidth / 2 - w;

如果旋转270度的坐标系便是这样:

这个时候我们的坐标起点便是(-2/h, h/2 - w);

x = -canvasWidth / 2;
y = canvasWidth / 2 - w;

这个时候我们在旋转的图片的时候输出的 canvas 还是 h x h 的宽度大小,效果类似下面:

不过之前我们说过 drawImage 可以实现图片的裁剪,我们只需生成一个新的画布,然后进行裁剪并进行数据输出

var cvs2 = _this._getCanvas(w, h);
      var ctx2 = cvs2.getContext('2d');
      ctx2.drawImage(cvs, 0, 0, w, h, 0, 0, w, h);
      var mimeType = _this._getImageType(image.src);
      var data = cvs2.toDataURL(mimeType, 1);

Demo

Code

虽然日常需求90度的比较多,但是这个还未结束,下一篇写支持任意角度的图片旋转,并且进行最小面积切割。

最后安利daycaca 一款开源图片操作类库,方便处理你的图片;

扩展阅读

You Can Speak "Hi" to Me in Those Ways