随着 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);
虽然日常需求90度的比较多,但是这个还未结束,下一篇写支持任意角度的图片旋转,并且进行最小面积切割。
最后安利daycaca 一款开源图片操作类库,方便处理你的图片;