Appearance
vue
<template>
<view class="container">
<!--画布区域-->
<cover-view class="topBox">
<button class="backStep" type="default" @click="backStep" size="mini">撤销</button>
<button class="saveBtn" type="warn" @click="saveimg" size="mini">保存</button>
</cover-view>
<view class="canvas_area">
<canvas
canvas-id="myCanvas"
class="myCanvas"
disable-scroll="false"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
></canvas>
</view>
<!--画布工具区域-->
<view class="canvas_tools">
<view class="box box1" @click="penSelect(3)"></view>
<view class="box box2" @click="penSelect(15)"></view>
</view>
</view>
</template>
<script setup lang="ts">
import { onLoad, onReady } from '@dcloudio/uni-app'
import { ref, getCurrentInstance } from 'vue'
let imgurl = ref('')
let pen = ref(15) //画笔粗细默认值
let startX = ref(0)//保存X坐标轴变量
let startY = ref(0) //保存X坐标轴变量
let canvasWidth = ref('')
let canvasHeight = ref('')
let ctx = ref(null)
let allDrawWorksPath = ref([])//图片路径 用于撤销
let pixels = ref(null) // 图片像素点
onLoad((options) => {
//真实环境为后端返回图片路径
// imgurl.value = 'https://ss3.baidu.com/9fo3dSag_xI4khGko9WTAnF6hhy/zhidao/pic/item/72f082025aafa40f2982756baa64034f78f0193b.jpg';
imgurl.value = 'https://onmc-production-frontend.oss-cn-beijing.aliyuncs.com/tenant/org202/poss/miniprogram/patient/homeNew/hospital_logo.png'
getSystemInfo(); //获取设备信息
ctx.value = uni.createCanvasContext('myCanvas')
})
// 获取设备信息
const getSystemInfo = () => {
uni.getSystemInfo({
success: (res:any) => {
console.log('设备信息', res);
canvasHeight.value = res.windowHeight; //若加底部操作区域 需-60
canvasWidth.value = res.windowWidth;
}
});
}
onReady(() => {
console.log(222,imgurl.value, ctx.value)
uni.getImageInfo({
src: imgurl.value,
success: (ress) => {
console.log('图片信息', ress, ctx.value);
ctx.value.drawImage(ress.path, 0, 0, canvasWidth.value, canvasHeight.value);
ctx.value.stroke();
wx.drawCanvas({
canvasId: 'myCanvas',
reserve: true,
actions: ctx.value.getActions() // 获取绘图动作数组
});
uni.canvasGetImageData({
canvasId: 'myCanvas',
x: 0,
y: 0,
width: canvasWidth.value,
height: canvasHeight.value,
success: res => {
pixels.value = getPixel2D(res)
}
}, getCurrentInstance())
},
fail(err) {
console.log('图片信息', imgurl.value, canvasHeight.value);
console.log('err', err);
}
});
})
// 保存图片
const saveimg = () => {
// ctx.value.draw();
setTimeout(()=> {
drawAfter();
}, 500);
}
// 保存
const drawAfter = () => {
uni.canvasToTempFilePath(
{
// width: canvasWidth.value, //686
// height: canvasHeight.value,
canvasId: 'myCanvas',
success:(res) => {
console.log('保存图片', res);
var tempFilePath = res.tempFilePath;
console.log(tempFilePath);
//把图片保存到相册
wx.saveImageToPhotosAlbum({
filePath: tempFilePath
});
//把图片保存到相册
//进行文件的拷贝
//上传
}
},);
}
// 开始
const touchStart = (e) => {
console.log('开始绘制');
//得到触摸点的坐标
startX.value = e.changedTouches[0].x;
startY.value = e.changedTouches[0].y;
// this.ctx.setStrokeStyle(this.color); //画笔颜色
ctx.value.setLineWidth(pen.value); //线条宽度
ctx.value.setLineCap('round'); // 让线条圆润
ctx.value.beginPath();
saveCurrentDrawWorks(); //记录每一步步骤-撤销
}
//手指触摸后移动
const touchMove = (e) => {
console.log('开始移动中');
var startX1 = e.changedTouches[0].x;
var startY1 = e.changedTouches[0].y;
// 画笔颜色
let color = 'rgb(' + pixels.value[startY1][startX1][0] + ',' + pixels.value[startY1][startX1][1] + ',' + pixels.value[startY1][startX1][2] + ')'
ctx.value.setStrokeStyle(color)
ctx.value.moveTo(startX.value, startY.value);
ctx.value.lineTo(startX1, startY1);
ctx.value.stroke();
startX.value = startX1;
startY.value = startY1;
//只是一个记录方法调用的容器,用于生成记录绘制行为的actions数组。
// context跟<canvas/>不存在对应关系,一个context生成画布的绘制动作数组可以应用于多个<canvas/>
wx.drawCanvas({
canvasId: 'myCanvas',
reserve: true,
actions: ctx.value.getActions() // 获取绘图动作数组
});
}
const getPixel2D = (res) => {
const { data, width, height } = res;
// 定义二维数组,存储每个坐标 (x,y) 的像素数据
let pixel2D = [];
// 遍历行(y 轴)
for (let y = 0; y < height; y++) {
pixel2D[y] = []; // 初始化第 y 行
// 遍历列(x 轴)
for (let x = 0; x < width; x++) {
// 计算当前像素在一维数组中的起始索引
const index = (y * width + x) * 4;
// 提取 RGBA 数据
const rgba = [
data[index], // R(红色通道,0~255)
data[index + 1], // G(绿色通道,0~255)
data[index + 2], // B(蓝色通道,0~255)
data[index + 3] // A(透明度,0~255,0 为完全透明)
];
pixel2D[y][x] = rgba; // 存储到二维数组的 (x,y) 位置
}
}
return pixel2D
}
//手指触摸动作结束
const touchEnd = () => {
console.log('停止');
}
//更改画笔大小的方法
const penSelect = (e) => {
console.log(e);
pen.value = e;
}
// 保存每一步操作
const saveCurrentDrawWorks = () => {
wx.canvasToTempFilePath({
canvasId: 'myCanvas',
success: (res) => {
var imgPath = res.tempFilePath;
allDrawWorksPath.value = [...allDrawWorksPath.value, imgPath];
console.log('步骤', allDrawWorksPath.value);
},
fail: res => {
console.log('获取画布图片失败', res);
}
});
}
// 撤销一步
const backStep = () => {
if (allDrawWorksPath.value == null || allDrawWorksPath.value.length == 0 || allDrawWorksPath.value == undefined) {
uni.showToast({
icon: 'none',
title: '已经撤销到起始位置'
});
allDrawWorksPath.value = [];
return;
}
let privWorksPath = allDrawWorksPath.value.pop();
ctx.value.drawImage(privWorksPath, 0, 0, canvasWidth.value, canvasHeight.value);
ctx.value.stroke();
ctx.value.draw()
wx.drawCanvas({
canvasId: 'myCanvas',
reserve: true,
actions: ctx.value.getActions() // 获取绘图动作数组
});
}
</script>
<style lang="scss">
.container {
position: relative;
width: 100%;
height: 100%;
.topBox {
position: fixed;
z-index: 99999;
width: 100%;
.backStep {
width: 50px;
top: 2px;
left: 2px;
}
.saveBtn {
width: 50px;
position: absolute;
top: 2px;
right: 2px;
}
}
.canvas_tools {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 60px;
display: flex;
flex-direction: row;
justify-content: space-around;
background-color: #eee;
align-items: center;
}
.box {
width: 100rpx;
height: 100rpx;
line-height: 100rpx;
border-radius: 50%;
background-color: rebeccapurple;
}
.box1 {
background-color: #99cccc;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAgCAYAAAAbifjMAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH3gUEFTU3owPJ+gAAArpJREFUSMeVlU9IVFEUxn/nvjdTaTQZYyaaZlEpIfhvkVSMSAmBFRZpYYugbcs2QbWpXUFKC3chLdpFUFHQpqxE2rQ0ahEp5BRm6TgqODP3tpg78nzznukHF+7lnfOd73zn8q7gw8jISGFbCzQCLvAGmANIJBKr4l3CcRqIAhVAFngRFKRCqu8ALtkCMeAyEPHFFBN40Am0Ajm7TgDNayrwMEeBi4ADGLu2ARf+24JFE9AR4kmdv40ggnPWAz/2Ad2BCjyMVbZSGHqtqSs5fgVdwIE1CFqBo6sUeKpvAc4TPplCTJ81uEhBM9Ae0KKf8DjQUGjD+7EH2G73WeAV8A54DoySHydAJXDGr6DK5/AjK/Uj8NKSP/EVi3sJOoH9dv8XGALmPQnTwCCQsudGIFEg2Ex+9gVj5m2CH0kgbfdRa3hEAW3AEU9gHDgUQFAPlHnOHUCLIn/v454PJcANOxUFiJV83Y6xgAqg3wVOBlRrB54BY4AGDpP/wfjRrYAHHnO8qLZ99oUkp4EhZd29AyyzfuSAe8B9ZSUOAgN2vx48BO4CWac1vsTS3HSurffKh9mpHzsRaRFRIqIIWko5T8t2H7w6/np4Pjk+itw4tRdgl85lmsqq6ytr2rpuupFNdcba74XOZWeS42O3k5/Hvjpu9AvwTW6dbcAY0yMij0VEKaUcUcoxBrRe3ZExWhutsyLiGGOuiciAW3WsH7RRynGiNbW1amtpKQALi4tMTkz4SRT5W4gx2lHKwS0prwOjcdyIKd9TTywWAyCVSvF7SRWpWFGjNcpxcTPpGYwxOI5L+s9PJLOQH3I6zXJ6Bp3TxWYYMMYSTL4dBmMQEabeRxClVipkMpnQORoDogQ3szAL1nH/TRLCYQAl4IoIxs5srYRABgE3p/OPj5gNpeefLA3up++/CueNwgD8A0fm3rIoh3Y0AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE2LTA5LTE3VDE1OjIwOjM4KzA4OjAwDAwhawAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0wNS0wNFQyMTo1Mzo1NSswODowMMTKtgsAAABNdEVYdHNvZnR3YXJlAEltYWdlTWFnaWNrIDcuMC4xLTYgUTE2IHg4Nl82NCAyMDE2LTA5LTE3IGh0dHA6Ly93d3cuaW1hZ2VtYWdpY2sub3Jn3dmlTgAAABh0RVh0VGh1bWI6OkRvY3VtZW50OjpQYWdlcwAxp/+7LwAAABl0RVh0VGh1bWI6OkltYWdlOjpIZWlnaHQAMTA2Mx0uiBoAAAAXdEVYdFRodW1iOjpJbWFnZTo6V2lkdGgANTM3XiCV0QAAABl0RVh0VGh1bWI6Ok1pbWV0eXBlAGltYWdlL3BuZz+yVk4AAAAXdEVYdFRodW1iOjpNVGltZQAxMzk5MjExNjM1VZFQ9AAAABJ0RVh0VGh1bWI6OlNpemUAMTguNEtCh1extQAAAF90RVh0VGh1bWI6OlVSSQBmaWxlOi8vL2hvbWUvd3d3cm9vdC9zaXRlL3d3dy5lYXN5aWNvbi5uZXQvY2RuLWltZy5lYXN5aWNvbi5jbi9zcmMvMTE2MDIvMTE2MDI0NC5wbmcC2t7tAAAAAElFTkSuQmCC);
background-repeat: no-repeat;
background-position: center;
}
.box2 {
background-color: #0099cc;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAeCAMAAAAvtQ9FAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACf1BMVEUAAAB8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiNINiWHUyJ+TyN9TyN9TyN9TyN9TyN+TyOPVyJBMyYoKCgpKChaPyVkQyVjQyVjQyVjQyVjQyVTPCYmJygoKCgfHx8eHh4fHx4gHx4gHx4gHx4gHx4gHx4eHh4eHh4fHx8TExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExIHBwcHBwcHBwcHBwd8TiMTExITExITExITExIHBwf////3ECwAAAAAznRSTlMAAAAARX4EAAAAAFLkqwIAAAAAVOv/vQkAAAA54v//4CEAAAARvP////1qAAAAAGj9/////9o1AAAAFMb/////2WgXAE70//////7BLI///////6O+/////+fY///84f///dz///HI/////9Wl/////6hy/f///G056////+YxDr7/////sggAcv////1hAAAg3P/////JEwBMTLj//v7//pxKS+j7+//////++vvi1f///////////83V///O1f//ztX//87V///O1f//zSidjOsAAAABYktHRNQJuwuFAAAAB3RJTUUH3gUEFTU21AT5bAAAAS9JREFUGNNjYGBgZGJmYWVjZwABDk4ubh5ePn4QW0BQSFhEVExcAsSRlJKWkZWTV1BUAnKUVVTV1DU0tbR1dBkY9PQNDI2MTUzNzC0sGaysbWzPnbOzd3B0cmZwcXU7d+6cu4enl7cPg6+f/zkQCAgMCmYICQ0Dc86FR0QyREWfg4KYWIa4eBgnIZEhKRnGSUllSEvPgLAzs7IZcnLzIJz8gkKGouISCKe0rJyhorIKzK6uqa1jqG9obAJxmlta2xjaOzq7QBLdPb19DP0TJk6aPHnylKnTps9gmDlr9py58+bNX7Bw0WKGJUuXLV+xcuWq1WvWrmNYv2Hjps1btmzdtn3HToZdu89DwZ69DPv2X4CCAwfxcA4dvggFR44yHDt+CQpOnGQ4dfoyFJw5CwBZdsIQZAP16AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNi0wOS0xN1QxNToyMDozOCswODowMAwMIWsAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMDUtMDRUMjE6NTM6NTQrMDg6MDBivb2/AAAATXRFWHRzb2Z0d2FyZQBJbWFnZU1hZ2ljayA3LjAuMS02IFExNiB4ODZfNjQgMjAxNi0wOS0xNyBodHRwOi8vd3d3LmltYWdlbWFnaWNrLm9yZ93ZpU4AAAAYdEVYdFRodW1iOjpEb2N1bWVudDo6UGFnZXMAMaf/uy8AAAAZdEVYdFRodW1iOjpJbWFnZTo6SGVpZ2h0ADEwNjMdLogaAAAAF3RFWHRUaHVtYjo6SW1hZ2U6OldpZHRoADQyNaj3r4sAAAAZdEVYdFRodW1iOjpNaW1ldHlwZQBpbWFnZS9wbmc/slZOAAAAF3RFWHRUaHVtYjo6TVRpbWUAMTM5OTIxMTYzNCKWYGIAAAASdEVYdFRodW1iOjpTaXplADEyLjdLQs+hF00AAABfdEVYdFRodW1iOjpVUkkAZmlsZTovLy9ob21lL3d3d3Jvb3Qvc2l0ZS93d3cuZWFzeWljb24ubmV0L2Nkbi1pbWcuZWFzeWljb24uY24vc3JjLzExNjAyLzExNjAyNDMucG5nsPoC/QAAAABJRU5ErkJggg==);
background-repeat: no-repeat;
background-position: center;
}
.box3 {
background-color: #cc0033;
}
.box4 {
background-color: #ff9900;
}
.box5 {
background-color: #00cd00;
}
.box6 {
text-align: center;
color: #fff;
}
}
</style>