前言
上次写到图片框选的技术难点,现在项目已经发布4个月之久。总结一下AI绘画两种方式:画笔和框选分别的技术难点
参考原文:小程序实现图片框选 - 掘金 (juejin.cn)
最终效果
可以使用涂抹 或者框选的方式对图片进行AI替换
和传统的AI绘画的区别是我们是在图片的基础上进行局部替换
难点坑点
一、样式难点
1.canvas层级
小程序上的canvas的层级比普通view元素要高,即使view设置的z-index无限大。由于导航栏是自定义的,弹窗也是自定义的,遮不住canvas区域。所以只要是比canvas层级高的元素,都要使用cover-view 和cover-image 进行开发,而这两个标签本身存在bug。详情官方文档 / cover-view (qq.com)
html
<cover-view class="navigationbar-left">
<cover-view class="navigationbar-button-single" @click="goBack">
<cover-image class="button-group-image" mode="scaleToFill" src="/static/image/navigation/nav_back_white.png">
</cover-image>
</cover-view>
</cover-view>
2.图片尺寸
用户上传的图片是横图还是竖图,要分别处理对应的样式,保持永远居中。从而动态设置canvas宽高
js
if (isHorizontal) {
// 横图
this.canvasWidth = this.windowWidth
this.canvasHeight = this.windowWidth * imageHeight / imageWidth
this.canvasLeft = 0
this.canvasTop = (this.canvasBgHeight - this.canvasHeight) / 2
} else {
// 竖图
this.canvasHeight = this.canvasBgHeight
this.canvasWidth = this.canvasBgHeight / imageHeight * imageWidth
this.canvasTop = 0
this.canvasLeft = (this.windowWidth - this.canvasWidth) / 2
}
export function isVerticalImage (url, scale = 1.5) {
return new Promise(async (reslove, reject) => {
const res = await wx.getImageInfo({ src: url })
if (res) {
const { width = 0, height = 0, path= "" } = res; // 图片宽度、高度
const ratio = (height / width).toFixed(2); // 高宽比例
reslove({
isVertical: ratio >= scale,
width,
height,
ratio,
path
})
} else {
reject(res)
}
})
}
二、涂抹难点
1.画笔粗细切换
粗细切换本身不是难点,只需设置 this.ctx.setLineWidth(this.width) 即可。
但是由于要保存之前的轨迹还能进行恢复和撤销功能。在安卓真机上诡异bug就出现了粗细切换好,之前画好的轨迹也会诡异的变粗变细。
最终的解决方式就是记录每一步的轨迹(保存成临时图片的路走不通,亲测了),撤销的时候重新生成。
js
// 记录轨迹
this.ctx.lineTo(e.touches[0].x, e.touches[0].y);
this.ctx.setLineWidth(this.width);
this.ctx.setStrokeStyle(this.color);
this.ctx.setLineCap('round');
this.ctx.setLineJoin('round');
this.ctx.stroke();
this.ctx.draw(true);
this.ctx.moveTo(e.touches[0].x, e.touches[0].y);
this.drawData.splice(this.index, this.drawData.length - this.index, {
type: 'move',
x: e.touches[0].x,
y: e.touches[0].y
})
this.index++
// 撤销/恢复时重画
while (this.index > 0 && this.drawData[this.index - 1].type !== 'start') {
this.index--
}
2.轨迹撤销/恢复
这个实现起来不算难,但也不是无脑能写出来,思路如下
- touchStart时,记录type为start,记录开始坐标,记录画笔粗细
- touchMove时,记录type为move,记录开始轨迹
- touchEnd时,记录type为end
每当撤销或者恢复时,重新生成画布的轨迹
3.黑白蒙版
由于AI需要传入一张同原图一样轨迹的黑白蒙版(AI能识别替换的原理)所以还需要一个画布。
亲测一个canvas实现不了,已经踩过坑了。。。
两个canvas同时画会非常卡顿 ,所以只有在用户确定生成的时候,再去根据轨迹生成另一个canvas
三、框选难点
1.必须清掉之前轨迹
之所以能够出现框选图形,是因为它在使用时要不断的清除之前画好的轨迹。
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
如果不清除,就会出现图形不规则,你们不信可以试试。。。
那么岂不是我之前的轨迹白画了?难点就出现了。解决方式是每次清除完轨迹,先把轨迹重绘出来,再去画新的轨迹。
js
// 重绘之前的轨迹
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
for (let i = 0; i < this.index; i++) {
let data = this.drawData[i]
if (data.type === 'end') {
this.ctx.rect(data.x, data.y, data.w, data.h);
this.ctx.fill();
this.ctx.stroke()
}
}
// 新轨迹
this.ctx.setFillStyle(this.color)
this.ctx.setStrokeStyle(this.color);
this.ctx.setLineWidth(1);
let rectWidth = 0
let rectHeight = 0
if (this.p2.x >= this.p1.x) {
rectWidth = this.p1.width
if (this.p2.y >= this.p1.y) {
rectHeight = this.p1.height
} else {
rectHeight = -this.p1.height
}
} else {
rectWidth = -this.p1.width
if (this.p2.y >= this.p1.y) {
rectHeight = this.p1.height
} else {
rectHeight = -this.p1.height
}
}
this.p2.width = rectWidth
this.p2.height = rectHeight
this.ctx.rect(this.p1.x, this.p1.y, rectWidth, rectHeight);
this.ctx.fill()
this.ctx.stroke();
this.ctx.draw(true);
2.安卓真机不支持坐标为负值
用户框选的时候可以从右向左框选,那么坐标会为负值画不出来。
解决方法就是将 fillRect() 方法拆成rect ,再去fill
3.其他小坑点
参照我的另一篇文章 小程序实现图片框选 - 掘金 (juejin.cn)
总结
所以总结起来很简单,但是前前后后想过多种方案,踩过无数坑。用时一个月原创出来的方法和经验教训。希望能帮助到你们。
如果觉得有用,希望收藏一下~
原创不易,转发记得标明原文出处哦~