/**
* canvas工具集
* @module canvasKit
*/
export default {
/**
* 绘制图片,保持宽高比居中裁剪,短边完全展示,长边居中截取
* 说明:
* 1.应先绘制图片,后填充图片周边内容,否则图片周边长边方向的现有内容会被擦除
* 2.在开发者工具上图片多余部分无法被清除,但在真机上正常
* 3.早期小程序canvas不支持clip,所以采用先绘制再擦除的方式实现,导致绘制顺序比较受限,后续考虑改用clip方式实现,待优化
* @param ctx wx.createCanvasContext返回的canvas绘图上下文
* @param {string} picFile 图片临时文件路径
* @param {object} picInfo wx.getImageInfo返回的图片原始信息
* @param {number} x 左上角横坐标
* @param {number} y 左上角纵坐标
* @param {number} w 宽度
* @param {number} h 高度
* @param {string} [bgColor="#ffffff"] 背景色,裁剪后多余部分用背景色擦除
*
*/
aspectFill({ctx, picFile, picInfo, x, y, w, h, bgColor="#ffffff"}){
let aspect = picInfo.width / picInfo.height; //图片宽高比
let [dx, dy, dw, dh] = [0, 0, 0, 0]; //整张图片绘制位置
let extras = []; //需擦除的多余区域
if (aspect < w/h) {
dw = w;
dh = dw/aspect;
dx = x;
dy = y - (dh-h)/2;
extras = [[dx-1, dy-1, dw+2, (dh-h)/2+1], [dx-1, dy+(dh-h)/2+h, dw+2, (dh-h)/2+1]]; //为避免残余半像素的细线,擦除方向多加1px
} else {
dh = h;
dw = dh*aspect;
dx = x - (dw-w)/2;
dy = y;
extras = [[dx-1, dy-1, (dw-w)/2+1, dh+2], [dx+(dw-w)/2+w, dy-1, (dw-w)/2+1, dh+2]];//为避免残余半像素的细线,擦除方向多加1px
}
ctx.drawImage(picFile, dx, dy, dw, dh); //保持宽高比,缩放至指定区域后,绘制整张图片
ctx.save();
ctx.setFillStyle(bgColor);
for (let extra of extras) { //擦除整张图片中多余区域
let [ex, ey, ew, eh] = extra;
if (ex+ew <= 0 || ey+eh<=0)
continue;
if (ex < 0) {
ew -= Math.abs(ex);
ex = 0;
}
if (ey < 0) {
eh -= Math.abs(ey);
ey = 0;
}
ctx.fillRect(ex, ey, ew, eh);
}
ctx.restore();
},
/**
* 绘制图片,保持图片纵横比,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。
* @param ctx wx.createCanvasContext返回的canvas绘图上下文
* @param {string} picFile 图片临时文件路径
* @param {number} x 左上角横坐标
* @param {number} y 左上角纵坐标
* @param {number} w 宽度
* @param {number} h 高度
* @param {string} bgColor 背景色,裁剪后多余部分用背景色填充
*
*/
aspectFit({ctx, picFile, x, y, w, h, bgColor}){
return this._getImageInfo(picFile)
.then((picInfo)=>{
let aspect = picInfo.width / picInfo.height; //图片宽高比
let [dx, dy, dw, dh] = [0, 0, 0, 0]; //整张图片绘制位置
if (aspect < w/h) {
dh = h;
dw = dh*aspect;
dx = x - (dw-w)/2;
dy = y;
} else {
dw = w;
dh = dw/aspect;
dx = x;
dy = y - (dh-h)/2;
}
if(bgColor){
ctx.save();
ctx.setFillStyle(bgColor);
ctx.fillRect(x,y,w,h);
ctx.restore();
}
ctx.drawImage(picFile, dx, dy, dw, dh);
})
},
/**
* 将方形区域切成圆形,场景示例:将头像切成圆形展示
* 说明:
* 1.方形区域四角会被填充成指定的背景色,只保留中央圆形区域不变
* 2.早期小程序canvas不支持clip,所以采用先绘制再擦除的方式实现圆形头像,只能在纯色背景上使用,后续考虑改用clip方式实现,待优化
* @param ctx wx.createCanvasContext返回的canvas绘图上下文
* @param {number} x 左上角横坐标
* @param {number} y 左上角纵坐标
* @param {number} w 宽度/高度/圆的直径
* @param {string} [bgColor="#ffffff"] 背景色,擦除部分以背景色填充
*/
rounded({ctx, x, y, w, bgColor="#ffffff"}){
ctx.save();
ctx.translate(x, y);
ctx.beginPath();
ctx.moveTo(w, w/2);
ctx.arc(w/2,w/2,w/2,0,2*Math.PI, false);
ctx.lineTo(w, 0);
ctx.lineTo(0, 0);
ctx.lineTo(0, w);
ctx.lineTo(w, w);
ctx.closePath();
ctx.setFillStyle(bgColor);
ctx.fill();
ctx.restore();
},
/**
* 将矩形切成圆角矩形
* 说明:
* 1.方形区域四角会被填充成指定的背景色,只保留中央圆角矩形区域不变
* 2.早期小程序canvas不支持clip,所以采用先绘制再擦除的方式实现圆角矩形,只能在纯色背景上使用,现推荐改用 canvasKit.createBorderRadiusPath + ctx.clip 生成圆角矩形/图片/边框式实现,待优化
* @param ctx wx.createCanvasContext返回的canvas绘图上下文
* @param {number} x 矩形左上角横坐标
* @param {number} y 矩形左上角纵坐标
* @param {number} w 矩形宽度
* @param {number} h 矩形高度
* @param {number} radius 圆角半径
* @param {string} [bgColor="#ffffff"] 背景色,擦除部分以背景色填充
*/
borderRadius({ctx, x, y, w, h, radius, bgColor="#ffffff"}){
ctx.save();
ctx.translate(x, y);
ctx.setFillStyle(bgColor);
//擦除左上角多余部分
ctx.beginPath();
ctx.moveTo(0, 0+radius);
ctx.quadraticCurveTo(0, 0, 0+radius, 0);
ctx.lineTo(0, 0);
ctx.closePath();
ctx.fill();
//擦除右上角多余部分
ctx.beginPath();
ctx.moveTo(w-radius, 0);
ctx.quadraticCurveTo(w, 0, w, radius);
ctx.lineTo(w, 0);
ctx.closePath();
ctx.fill();
//擦除右下角角多余部分
ctx.beginPath();
ctx.moveTo(w-radius, h);
ctx.quadraticCurveTo(w, h, w, h-radius);
ctx.lineTo(w, h);
ctx.closePath();
ctx.fill();
//擦除左下角多余部分
ctx.beginPath();
ctx.moveTo(0, h-radius);
ctx.quadraticCurveTo(0, h, 0+radius, h);
ctx.lineTo(0, h);
ctx.closePath();
ctx.fill();
ctx.restore();
},
/**
* 生成圆角边框路径,后续可使用该路径绘制圆角矩形、圆角图片、圆角边框等
* @param ctx wx.createCanvasContext返回的canvas绘图上下文
* @param {number} x 矩形左上角横坐标
* @param {number} y 矩形左上角纵坐标
* @param {number} w 矩形宽度
* @param {number} h 矩形高度
* @param {number} radius 圆角半径
*/
createBorderRadiusPath({ctx, x, y, w, h, radius}){
ctx.beginPath();
ctx.moveTo(x, y+radius);
ctx.quadraticCurveTo(x, y, x+radius, y); //左上角弧线
ctx.lineTo(x+w-radius, y); //顶部水平线
ctx.quadraticCurveTo(x+w, y, x+w, y+radius); //右上角弧线
ctx.lineTo(x+w, y+h-radius); //右侧竖线
ctx.quadraticCurveTo(x+w, y+h, x+w-radius, y+h); //右下角弧线
ctx.lineTo(x+radius, y+h); //底部水平线
ctx.quadraticCurveTo(x, y+h, x, y+h-radius); //左下角弧线
ctx.closePath(); //左侧竖线
},
/**
* 绘制文本,支持\n换行
* @param ctx wx.createCanvasContext返回的canvas绘图上下文
* @param {string} text 文本内容,支持\n换行
* @param {number} x 文本区域(含行高)左上角横坐标;居中对齐时,改取中点横坐标
* @param {number} y 文本区域(含行高)左上角纵坐标
* @param {number} fontSize 字号,单位:px
* @param {string} color 颜色
* @param {number} lineHeight 行高
* @param {string} textAlign 水平对齐方式,支持'left'、'center',其它值没试过
*/
fillText(ctx, {text, x, y, fontSize, color, lineHeight, textAlign}){
ctx.save();
lineHeight = lineHeight || fontSize;
fontSize && ctx.setFontSize(fontSize);
color && ctx.setFillStyle(color);
textAlign && ctx.setTextAlign(textAlign);
let lines = text.split('\n');
for (let line of lines) {
ctx.fillText(line, x, y+lineHeight-(lineHeight-fontSize)/2);
y += lineHeight;
}
ctx.restore();
},
/**
* 字符串过长截断,1个字母长度计为1,1个汉字长度计为2
* 更新:
* 1. 早期小程序canvas不支持测量文本实际尺寸,所以采用手动粗略计算的方式实现过长处理
* 2. 后来小程序canvas提供了measureText接口,支持测量文本实际尺寸信息,本方法待优化
* @param {string} str 原字符串
* @param {number} len 最大长度
* @param {boolean} ellipsis 过长时截断后是否加'...'
* @return {string} 截断后字符串
*/
ellipsisStr(str, len, ellipsis=true) {
var str_length = 0;
var str_len = 0;
var str_cut = new String();
str_len = str.length;
for (var i = 0; i < str_len; i++) {
let a = str.charAt(i);
str_length++;
if (escape(a).length > 4) {
//中文字符的长度经编码之后大于4
str_length++;
}
str_cut = str_cut.concat(a);
if (str_length >= len) {
str_cut = str_cut.concat(ellipsis&&(str_length>len || i+1<str_len) ? "..." : "");
return str_cut;
}
}
//如果给定字符串小于指定长度,则返回源字符串;
if (str_length < len) {
return str;
}
},
/**
* 字符串长度,1个字母长度计为1,1个汉字长度计为2
* canvas目前似乎不支持获取文本绘制后所占宽度,只能根据字数粗略计算了
* 更新:
* 1. 后来小程序canvas提供了measureText接口,支持测量文本实际尺寸信息,本方法待优化
* @param {string} str 字符串
* @return {number} 总长度
*/
strLenGraphic(str) {
var str_length = 0;
for (var i = 0; i < str.length; i++) {
let a = str.charAt(i);
str_length++;
if (escape(a).length > 4) {
//中文字符的长度经编码之后大于4
str_length++;
}
}
return str_length;
},
_getImageInfo(picFile) {
return new Promise((resolve,reject)=>{
wx.getImageInfo({
src: picFile,
success: res =>{
resolve(res)
},
fail: res=>{
reject(res)
}
})
})
}
}