最近圣诞节就要到了,于是想为我的小程序添加个圣诞帽生成的功能,可以为你的头像选择圣诞帽,然后生成图片到本地。看下效果图,有兴趣的小伙伴也可以扫小程序码体验一下
在开发这个功能的过程中,遇到了一些小问题,就在这里记录一下吧。
一、canvas加载图片问题
在canvas里无法直接加载网络图片,只能使用本地的。所以需要使用 wx.getImageInfo 来把网络图片缓存到本地,然后才能使用。我在绘制图片的时候是使用懒加载的方式,没有在一开始的时候就把全部图片都缓存下来,只有在用户点击的时候,才会缓存图片,并保存缓存路径,这样下次继续绘制同样的图片时速度就会快很多。
二、canvas坐标原点变换问题
我们在绘制帽子的时候,因为帽子是有一定的旋转角度以及它和左上方的顶点是有距离的,所以我们需要通过translate和rotate方法去变换坐标原点和角度。
从官方文档我们可以看到,rotate是会叠加的,所以我们可以在rotate某个degree后,再rotate个-degree去修正,但是在rotate前我translate了坐标原点,因为不translate的话帽子的原点就会一直是(0,0),一直在左上方。在往相反方向rotate和translate之后,本以为就可以恢复原点坐标为(0,0),但事实却并非如此,我通过绘制一些辅助的矩形发现,rotate个-degree后对原点没有影响,原点仍然是我们rotate个degree后的位置,所以我们需要通过计算去获取旋转后的坐标,在此附上计算公式吧。
假设对图片上任意点(x,y),绕一个坐标点(rx0,ry0)逆时针旋转a角度后的新的坐标设为(x0, y0),有公式:
x0= (x - rx0)*cos(a) - (y - ry0)*sin(a) + rx0 ;
y0= (x - rx0)*sin(a) + (y - ry0)*cos(a) + ry0 ;
通过上面的公式我们就可以计算出当前帽子左上角的坐标了,注意角度是弧度制的(degrees * Math.PI/180)。
三、多个帽子的识别问题
要让程序知道是哪个帽子,就要对每一个帽子进行记录,记录的信息为:帽子的编号/左上角坐标/旋转角度/大小。
在此记录下具体的实现代码
start:function(e){//开始按住画布时触发
// console.log(e)
var sx = e.changedTouches[0].x, sy = e.changedTouches[0].y,
t_icon=this.data.targetIcon;
this.data.current = t_icon.length - 1;
this.data.startX=sx;
this.data.startY =sy;
//防止同时作用于多个帽子
if (this.data.scaleMode)return;
for(var i=t_icon.length-1;i>=0;i--){//倒序读取,因为后添加的会覆盖在之前添加的上面
var obj=t_icon[i],wid=Number(this.getCapWidth(obj.size)),px=Number(obj.px),py=Number(obj.py),
degree=obj.degree*Math.PI/180;
var pos=this.getRotatePosition(0,0,(wid/2),(wid/2),degree);
//帽子左上角的坐标px,py;右下角的rx,ry
px+=pos[0];
py+=pos[1];
//console.log(px+' '+py+' '+sx+' '+sy);
pos=this.getRotatePosition(wid,wid,wid/2,wid/2,degree);
var rx=pos[0],
ry=pos[1];
//删除动作
var off=iconSize/2;//图标大小的一半
if (sx >= px - off && sx <= px + off && sy >= py - off && sy <= py + off) {
this.data.targetIcon.splice(i, 1);
this.drawCap();
return;
}
//旋转缩放动作
px = Number(obj.px); py = Number(obj.py);//恢复到旋转前的坐标
if (sx >= px+rx - off && sx <= px+rx + off && sy >= py+ry - off && sy <= py+ry + off) {
this.data.scaleMode=1;//按住了缩放
return;//防止同时作用于多个帽子
}
if(sx>=px&&sy>=py&&sx<=(px+wid)&&sy<=(py+wid)){
//交换位置,把选中的移到最后面,这样才能让其显示在最上面
[t_icon[i], t_icon[this.data.current]] = [t_icon[this.data.current], t_icon[i]]
break;
}
}
},
move: function (e) {
var nx = e.changedTouches[0].x, ny = e.changedTouches[0].y,
sx = this.data.startX, sy = this.data.startY;
var ti=this.data.targetIcon,current=this.data.current;
if(!this.data.scaleMode){
var obj = ti[current];
if(!obj)return;
var wid = Number(this.getCapWidth(obj.size)), px = Number(obj.px), py = Number(obj.py);
//触摸到帽子区域
if (sx >= px && sy >= py && sx <= (px + wid) && sy <= (py + wid)) {
obj.px += (nx - sx);
obj.py += (ny - sy);
}
ti[current]=obj;
}else{//缩放模式
var obj = ti[current],wid=this.getCapWidth(obj.size),px=obj.px,py=obj.py;
var ox=obj.ox,oy=obj.oy;//px+wid/2
var old=Math.sqrt((sx-ox)*(sx-ox)+(sy-oy)*(sy-oy)),
now=Math.sqrt((nx-ox)*(nx-ox)+(ny-oy)*(ny-oy));
//旧半径,要保持旋转中心不变
var old_size=ti[current].size;
ti[current].size=now*Math.SQRT2;
ti[current].px = ox - ti[current].size/2;
ti[current].py = oy - ti[current].size/2;
var [y,x]=[(ny-oy),(nx-ox)],
tan_a = y* 1.0 / x, tan_b = 1,
origin_degree = Math.atan(tan_a)*180/Math.PI,
rotate_degree = origin_degree -45 ;
//处理一些特殊情况
if ((y >= 0 && x < 0) || (y < 0 && x < 0))rotate_degree=135+origin_degree;
console.log(origin_degree + ' ' + rotate_degree + ' ' + y + ' ' + x)
ti[current].degree = rotate_degree;
// console.log(ox+' now:['+nx+','+ny+'] '+(ny-oy)+' '+(nx-ox)+' '+ti[current].degree)
}
this.data.targetIcon = ti;
this.data.startX = nx;
this.data.startY = ny;
this.drawCap();
},
其中还有一些不足之处,希望各位小伙伴帮忙指正~~