uniapp-原生地图截屏返回base64-进行画板编辑功能

一、场景

vue写uniapp打包安卓包,实现原生地图截屏(andirod同事做的)-画板编辑功能

实现效果:

二、逻辑步骤简略

1. 由 原生地图nvue部分,回调返回 地图截屏生成的base64 数据,

2. 通过 uni插件市场 image-tools 插件 base64ToPath方法,将base64数据 转成文件路径

3. 通过 uni -API- uni.createCanvasContext() 、ctx.createPattern() 方法,将 图片数据 创建绘图对象

4. 通过 uni - movable-area+movable-view 控制画布缩放

5. 通过 canvas @touchmove="touchmove"  @touchend="touchend"  @touchstart="touchstart" 等方法实现在画布上绘制画笔

6. 生成图片及清空画布

三、具体实现

1.  由 原生地图nvue部分,回调返回 原生地图截屏生成的base64 数据(andirod同事做的)

2.  image-tools 插件 base64ToPath 

image-tools - DCloud 插件市场

import { pathToBase64, base64ToPath } from '@/js_sdk/mmmm-image-tools/index.js'

3.通过 uni -API- uni.createCanvasContext() 、ctx.createPattern() 方法

uni-app官网 API- createPattern()

initC() {const that = this// 创建绘图对象this.ctx = uni.createCanvasContext('mycanvas', this);// 在canvas设置背景 - 入参 仅支持包内路径和临时路径const pattern = this.ctx.createPattern(this.imageUrl, 'repeat-x')this.ctx.fillStyle = patternthis.ctx.setStrokeStyle('red')this.ctx.fillRect(0, 0, this.dWidth, this.dHeight)this.ctx.draw()// 方法二  在画布上插入图片// this.img = new Image();// this.img.src = this.imageUrl;// this.img.onload = () => {//   console.log('this.img', that.img.width)//   that.ctx.drawImage(that.img, 0, 0, this.dWidth, this.dHeight)//   // that.ctx.draw()// }},

4. 通过 uni - movable-area+movable-view 控制画布缩放

<movable-area :scale-area="true" :style="{'width':windowWidth+'px','height':windowHeight+'px','backgroundColor':'#ddd','overflow':'hidden'}"><movable-view direction="all":inertia="false":out-of-bounds="false":scale-min="0.001":scale-max="4"   :scale="true":disabled="movableDisabled":scale-value="scaleValue"class="pr":style="{'width':widths+'px','height':heights+'px'}"@scale="scaleChange"><canvasid="mycanvas"canvas-id="mycanvas":style="{'width':widths+'px','height':heights+'px'}"@touchmove="touchmove"@touchend="touchend"@touchstart="touchstart"></canvas></movable-view></movable-area>

5.通过 canvas @touchmove="touchmove"  等方法实现在画布上绘制画笔

touchstart(e) {let startX = e.changedTouches[0].xlet startY = e.changedTouches[0].yif (this.scaleValue > 1) {startX = e.changedTouches[0].x / this.scaleValue;startY = e.changedTouches[0].y / this.scaleValue;} else {startX = e.changedTouches[0].x * this.scaleValue;startY = e.changedTouches[0].y * this.scaleValue;}console.log('touchstart()-x', e.changedTouches[0].x, 'scaleValue', this.scaleValue, 'startX', startX)let startPoint = { X: startX, Y: startY };this.points.push(startPoint);// 每次触摸开始,开启新的路径this.ctx.beginPath();},touchmove(e) {if (this.isEdit) {let moveX = e.changedTouches[0].xlet moveY = e.changedTouches[0].yif (this.scaleValue > 1) {moveX = e.changedTouches[0].x / this.scaleValue;moveY = e.changedTouches[0].y / this.scaleValue;} else {moveX = e.changedTouches[0].x * this.scaleValue;moveY = e.changedTouches[0].y * this.scaleValue;}console.log('touchmove()-x', e.changedTouches[0].x, 'scaleValue', this.scaleValue, 'moveX', moveX)let movePoint = { X: moveX, Y: moveY };this.points.push(movePoint); // 存点let len = this.points.length;if (len >= 2) {this.draw(); // 绘制路径}}},touchend() {this.points = [];},draw() {let point1 = this.points[0];let point2 = this.points[1];this.points.shift();this.ctx.moveTo(point1.X, point1.Y);this.ctx.lineTo(point2.X, point2.Y);this.ctx.stroke();this.ctx.draw(true);},

6.生成图片及清空画布

clear() {let that = this;this.scaleValue = 1this.isEdit = falsethis.movableDisabled = falseuni.getSystemInfo({success: function(res) {let canvasw = res.windowWidth;let canvash = res.windowHeight;that.ctx.clearRect(0, 0, canvasw, canvash);const pattern = that.ctx.createPattern(that.imageUrl, 'repeat-x')that.ctx.fillStyle = patternthat.dWidth = 285that.dHeight = 200that.ctx.setStrokeStyle('red')that.ctx.fillRect(0, 0, that.dWidth, that.dHeight)that.ctx.draw()// that.ctx.draw(true);}});},finish() {let that = this;uni.canvasToTempFilePath({canvasId: 'mycanvas',success: function(res) {// 这里的res.tempFilePath就是生成的签字图片// console.log('tempFilePath', res.tempFilePath);that.tempFilePath = res.tempFilePaththat.$emit('onImgUrl', that.tempFilePath) // 向父级组件传值}});},

utils:

// 是否是 base64数据
export function isBase64Two(str) {try {return btoa(atob(str)) === str;} catch (err) {return false;}
}
export function isBase64(str) {// 正则表达式匹配B4-64编码格式const regex = /^[a-zA-Z0-9+\/]+={0,2}$/;return regex.test(str);
}
// 校验内容是否包含base64格式的图片
export function isBase64Three(str){let imgReg = RegExp(/data:image\/.*;base64,/)const res = imgReg.test(str)return res
}

四、总结

以下完整代码 DrawingBoard.vue:

<template><view class="canvas-frame"><view class="icon-frame"><uni-icons :class="{ 'is-edit': isEdit }" type="compose" size="18" class="icon-item mr10" @click="createCanvas">编辑</uni-icons><uni-iconstype="plus"size="18" class="icon-item mr10"title="放大"@click="plusImageScalex"></uni-icons><uni-iconstype="minus"size="18" class="icon-item"title="缩小"@click="minusImageScalex"></uni-icons></view><view class="button-frame"><button size="mini" class="mr10" @click="clear">清空</button><button size="mini" @click="finish">确定</button></view><!-- style="border: 1rpx solid #ccc;width: 570rpx; height: 400rpx;" --><!-- <canvasid="mycanvas"canvas-id="mycanvas":style="{'width':widths+'px','height':heights+'px'}"@touchmove="touchmove"@touchend="touchend"@touchstart="touchstart"></canvas> --><movable-area :scale-area="true" :style="{'width':windowWidth+'px','height':windowHeight+'px','backgroundColor':'#ddd','overflow':'hidden'}"><movable-view direction="all":inertia="false":out-of-bounds="false":scale-min="0.001":scale-max="4"   :scale="true":disabled="movableDisabled":scale-value="scaleValue"class="pr":style="{'width':widths+'px','height':heights+'px'}"@scale="scaleChange"><canvasid="mycanvas"canvas-id="mycanvas":style="{'width':widths+'px','height':heights+'px'}"@touchmove="touchmove"@touchend="touchend"@touchstart="touchstart"></canvas></movable-view></movable-area></view>
</template><script>
// import { fabric } from 'fabric';
// import { fabric } from '@/utils/fabric.min.js';
// import { Database64ToFile } from '@/utils/index';
import { pathToBase64, base64ToPath } from '@/js_sdk/mmmm-image-tools/index.js'
import { isBase64 } from '@/utils/index.js';
// isBase64 方法判断 原生端返回到的数据格式是否正确
export default {props: {// 更新 原始地图画布mapImageUrl: {type: String,default: '',}},data() {return {canvasEle: null,isEdit: false,imageContainer: null,scaleValue: 1,ctx: '', // 绘图图像points: [], // 路径点集合tempFilePath: '', // 签名图片imageUrl: require('@/static/res/imgs/all/fushanhou-area.jpg'), // 本地图片画布资源img: null,dWidth: 285,dHeight: 200,widths: 285,heights: 200,windowWidth: 285,windowHeight: 200,movableDisabled: false,};},mounted() {this.initC()},watch: {mapImageUrl(newV, oldV) {const that = thisconsole.log('watch()-mapImageUrl-newV,监听数据变化-newV', newV? '有值': '无值')if (!['',undefined,null].includes(newV)) {console.log('watch()-mapImageUrl-isBase64(newV)', isBase64(newV))// const base64Image = 'data:image/png;base64,/9j/4AAQSkZJRgA...'; // that.base64ToTempFilePath(newV ,(tempFilePath) => {//   console.log('转换成功,临时地址为:', tempFilePath)//   that.imageUrl = tempFilePath //   // 会在canvas中调用//   that.initC()// }, // () =>{//   console.log('fail转换失败')// });const base64 = 'data:image/png;base64,' + newV;base64ToPath(base64).then((tempFilePath) => {console.log('转换成功,临时地址为:', tempFilePath)that.imageUrl = tempFilePaththat.initC()})}},},methods: {initC() {const that = this// 创建绘图对象this.ctx = uni.createCanvasContext('mycanvas', this);// 在canvas设置背景 - 入参 仅支持包内路径和临时路径const pattern = this.ctx.createPattern(this.imageUrl, 'repeat-x')this.ctx.fillStyle = patternthis.ctx.setStrokeStyle('red')this.ctx.fillRect(0, 0, this.dWidth, this.dHeight)this.ctx.draw()// 方法二  在画布上插入图片// this.img = new Image();// this.img.src = this.imageUrl;// this.img.onload = () => {//   console.log('this.img', that.img.width)//   that.ctx.drawImage(that.img, 0, 0, this.dWidth, this.dHeight)//   // that.ctx.draw()// }},createCanvas() {this.isEdit = !this.isEditif (this.isEdit) {this.movableDisabled = true// 设置画笔样式this.ctx.lineWidth = 2;this.ctx.lineCap = 'round';this.ctx.lineJoin = 'round';} else {this.movableDisabled = false}},touchstart(e) {let startX = e.changedTouches[0].xlet startY = e.changedTouches[0].yif (this.scaleValue > 1) {startX = e.changedTouches[0].x / this.scaleValue;startY = e.changedTouches[0].y / this.scaleValue;} else {startX = e.changedTouches[0].x * this.scaleValue;startY = e.changedTouches[0].y * this.scaleValue;}console.log('touchstart()-x', e.changedTouches[0].x, 'scaleValue', this.scaleValue, 'startX', startX)let startPoint = { X: startX, Y: startY };this.points.push(startPoint);// 每次触摸开始,开启新的路径this.ctx.beginPath();},touchmove(e) {if (this.isEdit) {let moveX = e.changedTouches[0].xlet moveY = e.changedTouches[0].yif (this.scaleValue > 1) {moveX = e.changedTouches[0].x / this.scaleValue;moveY = e.changedTouches[0].y / this.scaleValue;} else {moveX = e.changedTouches[0].x * this.scaleValue;moveY = e.changedTouches[0].y * this.scaleValue;}console.log('touchmove()-x', e.changedTouches[0].x, 'scaleValue', this.scaleValue, 'moveX', moveX)let movePoint = { X: moveX, Y: moveY };this.points.push(movePoint); // 存点let len = this.points.length;if (len >= 2) {this.draw(); // 绘制路径}}},touchend() {this.points = [];},draw() {let point1 = this.points[0];let point2 = this.points[1];this.points.shift();this.ctx.moveTo(point1.X, point1.Y);this.ctx.lineTo(point2.X, point2.Y);this.ctx.stroke();this.ctx.draw(true);},clear() {let that = this;this.scaleValue = 1this.isEdit = falsethis.movableDisabled = falseuni.getSystemInfo({success: function(res) {let canvasw = res.windowWidth;let canvash = res.windowHeight;that.ctx.clearRect(0, 0, canvasw, canvash);const pattern = that.ctx.createPattern(that.imageUrl, 'repeat-x')that.ctx.fillStyle = patternthat.dWidth = 285that.dHeight = 200that.ctx.setStrokeStyle('red')that.ctx.fillRect(0, 0, that.dWidth, that.dHeight)that.ctx.draw()// that.ctx.draw(true);}});},finish() {let that = this;uni.canvasToTempFilePath({canvasId: 'mycanvas',success: function(res) {// 这里的res.tempFilePath就是生成的签字图片// console.log('tempFilePath', res.tempFilePath);that.tempFilePath = res.tempFilePaththat.$emit('onImgUrl', that.tempFilePath)}});},plusImageScalex() {const num = this.scaleValue + 0.4this.scaleValue = Math.floor(num * 100) / 100;// this.setImageScale(this.scaleValue);},minusImageScalex() {const num = this.scaleValue + 0.4this.scaleValue = - (Math.floor(num * 100) / 100);// this.setImageScale(-this.scaleValue);},// 设置图片缩放setImageScale(scale) {const that = thisconsole.log('this.ctx.', this.ctx.dWidth, scale)// const value = this.imageContainer.scaleX + scale;// const zoom = Number(value.toFixed(2));// // 设置图片的缩放比例和位置// this.imageContainer.set({//   scaleX: zoom,//   scaleY: zoom,// });// this.canvasEle.renderAll();// that.ctx.fillRect(0, 0, 285, 200)// that.ctx.draw()const pattern = that.ctx.createPattern(that.imageUrl, 'repeat-x')that.ctx.fillStyle = patternconst w = that.dWidth * scale const h = that.dHeight * scaleconsole.log('this.ctx.',w, h)that.ctx.fillRect(0, 0, w, h)that.ctx.draw()},//点击事件 判断缩放比例 touchstart(e) {let x = e.touches[0].xlet y = e.touches[0].y// this.node.forEach(item => {//   if (x > item.x * this.scale && x < (item.x + item.w) * this.scale//       && y > item.y * this.scale && y < (item.y + item.h) * this.scale) {//       //在范围内,根据标记定义节点类型//       // this.lookDetial(item)//   }// }) },//s缩放比例scaleChange(e) {this.scaleValue = e.detail.scale},// 将base64图片转换为临时地址base64ToTempFilePath(base64Data, success, fail) {const fs = uni.getFileSystemManager()const fileName = 'temp_image_' + Date.now() + '.png' // 自定义文件名,可根据需要修改const USER_DATA_PATH = 'ttfile://user' // uni.env.USER_DATA_PATHconst filePath = USER_DATA_PATH + '/' + fileNameconst buffer = uni.base64ToArrayBuffer(base64Data)fs.writeFile({filePath,data: buffer,encoding: 'binary',success() {success && success(filePath)},fail() { fail && fail()}});},// base64转化成本地文件路径parseBlob(base64, success) {const arr = base64.split(',');console.log('parseBlob()-arr:', arr)const mime = arr[0].match(/:(.*?);/)[1];const bstr = atob(arr[1]);const n = bstr.length;const u8arr = new Uint8Array(n);for(let i = 0; i < n; i++) {u8arr[i] = bstr.charCodeAt(i);}// const url = URL || webkitURL;let a = new Blob([u8arr], {type: mime});const file = new File([a], 'test.png', {type: 'image/png'});console.log('parseBlob()-file', file);success && success(file)},}
};
</script><style lang="scss" scoped>
.pr{position: relative;
}
.canvas-frame {position: relative;width: 570rpx;// overflow: hidden;.icon-frame {position: absolute;top: 20rpx;right: 40rpx;z-index: 2;}.blockS{background: transparent;width: 570rpx; height: 400rpx;position: absolute;top: 0;left: 0;z-index: 1;}.icon-item {// font-size: 36rpx;// padding: 12rpx;// border-radius: 8rpx;// margin-right: 16rpx;// border: 1rpx solid #ccc;// background-color: #fff;&:hover {// background-color: #f1f1f1;}&:active {opacity: 0.8;}}.is-edit {color: #007EF3 !important;}.button-frame {position: absolute;bottom: 10rpx;right: 40rpx;z-index: 2;}#canvasElement {cursor: pointer;}
}
</style>

由于hbuildex-真机调试-打印很费劲,需要来回构建打包,从而找问题找了好久,其中因为 原生地图截屏返回的是纯base64的数据,未带 data:image\/.*;base64,然后找了半天的问题,需要一步步的推导和确认有没有错,错在那,花费了很多时间和精力;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/36236.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《图解HTTP》——HTTP协议详解

一、HTTP协议概述 HTTP是一个属于应用层的面向对象协议&#xff0c;由于其简捷、快速的方式&#xff0c;适用于分布式超媒体信息系统。它于1990年提出&#xff0c;经过几年的使用与发展&#xff0c;得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版&#xff0c;HTTP…

SQL 语句解析过程详解

SQL 语句解析过程详解&#xff1a; 1&#xff0e;输入SQL语句 2&#xff0e;词法分析------flex 使用词法分析器&#xff08;由Flex生成&#xff09;将 SQL 语句分解为一个个单词&#xff0c;这些单词被称为“标记“。标记包括关键字、标识符、运算符、分隔符等。 2.1 flex 原…

20个互联网用户Python数据分析项目

这篇文章给大家整理了20个互联网用户数据分析的项目。所有收录的项目&#xff0c;进行了严格的筛选&#xff0c;标准有二&#xff1a; 1.有解说性文字&#xff0c;大家能知道每一步在干嘛&#xff0c;新手友好 2.数据集公开&#xff0c;保证大家可以在原文的基础上自行探索 更…

[保研/考研机试] KY96 Fibonacci 上海交通大学复试上机题 C++实现

题目链接&#xff1a; KY96 Fibonacci https://www.nowcoder.com/share/jump/437195121692000803047 描述 The Fibonacci Numbers{0,1,1,2,3,5,8,13,21,34,55...} are defined by the recurrence: F00 F11 FnFn-1Fn-2,n>2 Write a program to calculate the Fibon…

【STM32】FreeRTOS互斥量学习

互斥量&#xff08;Mutex&#xff09; 互斥量又称互斥信号量&#xff08;本质也是一种信号量&#xff0c;不具备传递数据功能&#xff09;&#xff0c;是一种特殊的二值信号量&#xff0c;它和信号量不同的是&#xff0c;它支持互斥量所有权、递归访问以及防止优先级翻转的特性…

人文景区有必要做VR云游吗?如何满足游客出行需求?

VR云游在旅游行业中的应用正在快速增长&#xff0c;为游客带来沉浸式体验的同时&#xff0c;也为文旅景区提供了新的营销方式。很多人说VR全景展示是虚假的&#xff0c;比不上真实的景区触感&#xff0c;人文景区真的有必要做VR云游吗&#xff1f;我的答案是很有必要。 如果你认…

【Pytroch】基于K邻近算法的数据分类预测(Excel可直接替换数据)

【Pytroch】基于K邻近算法的数据分类预测&#xff08;Excel可直接替换数据&#xff09; 1.模型原理2.数学公式3.文件结构4.Excel数据5.下载地址6.完整代码7.运行结果 1.模型原理 K最近邻&#xff08;K-Nearest Neighbors&#xff0c;简称KNN&#xff09;是一种简单但常用的机器…

Redis基础学习

目录 第一章、Redis数据库的下载和安装1.1&#xff09;nosql数据库和 Redis 介绍1.2&#xff09;Windows中下载安装Redis数据库1.3&#xff09;Linux中安装Redis数据库1.4&#xff09;Linux中启动redis1.5&#xff09;Linux中关闭redis 第二章、三种Redis客户端连接Redis数据库…

安全远控如何设置?揭秘ToDesk、TeamViewer 、向日葵安全远程防御大招

写在前面一、远程控制&#xff1a;安全性不可忽略二、远控软件安全设置实测◉ ToDesk◉ TeamViewer◉ 向日葵 三、远控安全的亮点功能四、个人总结与建议 写在前面 说到远程办公&#xff0c;相信大家都不陌生。远程工作是员工在家中或者其他非办公场所上班的一种工作模式&…

传输层协议

传输层协议 再谈端口号端口号范围划分认识知名端口号两个问题netstatpidof UDP协议UDP协议端格式UDP的特点面向数据报UDP的缓冲区UDP使用注意事项基于UDP的应用层协议 TCP协议TCP协议段格式确认应答(ACK)机制超时重传机制连接管理机制理解 CLOSE_WAIT 状态理解TIME_WAIT状态解决…

修改el-select和el-input样式;修改element-plus的下拉框el-select样式

修改el-select样式 .select_box{// 默认placeholder:deep .el-input__inner::placeholder {font-size: 14px;font-weight: 500;color: #3E534F;}// 默认框状态样式更改:deep .el-input__wrapper {height: 42px;background-color: rgba(0,0,0,0)!important;box-shadow: 0 0 0 …

OptaPlanner笔记6 N皇后

N 个皇后 问题描述 将n个皇后放在n大小的棋盘上&#xff0c;没有两个皇后可以互相攻击。 最常见的 n 个皇后谜题是八个皇后谜题&#xff0c;n 8&#xff1a; 约束&#xff1a; 使用 n 列和 n 行的棋盘。在棋盘上放置n个皇后。没有两个女王可以互相攻击。女王可以攻击同一水…

如何做好一名网络工程师?具体实践?

预防问题 – 资格与认证 在安装线缆或升级网络时测试线缆是预防问题的有效方式。对已安装布线进行测试的方法有两种。 资格测试确定布线是否有资格执行某些操作 — 换言之&#xff0c;支持特定网络速度或应用。尽管“通过”认证测试也表明按标准支持某一网络速度或应用的能力…

Redux - Redux在React函数式组件中的基本使用

文章目录 一&#xff0c;简介二&#xff0c;安装三&#xff0c;三大核心概念Store、Action、Reducer3.1 Store3.2 Reducer3.3 Action 四&#xff0c;开始函数式组件中使用4.1&#xff0c;引入store4.1&#xff0c;store.getState()方法4.3&#xff0c;store.dispatch()方法4.4&…

深入了解 Rancher Desktop 设置

Rancher Desktop 设置的全面概述 Rancher Desktop 拥有方便、强大的功能&#xff0c;是最佳的开发者工具之一&#xff0c;也是在本地构建和部署 Kubernetes 的最快捷方式。 本文将介绍 Rancher Desktop 的功能和特性&#xff0c;以及 Rancher Desktop 作为容器管理平台和本地…

人工智能原理(2)

目录 一、知识与知识表示 1、知识 2、知识表示 3、知识表示方法 二、谓词逻辑表示法 1、命题逻辑 2、谓词逻辑 三、产生式表达法 1、知识的表示方法 2、产生式系统组成 3、推理方式 4、产生式表示法特点 四、语义网络 1、概念及结构 2、语义网络的基本语义联系 …

zookeeper案例

目录 案例一&#xff1a;服务器动态上下线 服务端&#xff1a; &#xff08;1&#xff09;先获取zookeeper连接 &#xff08;2&#xff09;注册服务器到zookeeper集群&#xff1a; &#xff08;3&#xff09;业务逻辑&#xff08;睡眠&#xff09;&#xff1a; 服务端代码…

Java+Excel+POI+testNG基于数据驱动做一个简单的接口测试【杭州多测师_王sir】

一、创建一个apicases.xlsx放入到eclipse的resource里面&#xff0c;然后refresh刷新一下 二、在pom.xml文件中加入poi和testng的mvn repository、然后在eclipse的对应目录下放入features和plugins&#xff0c;重启eclipse就可以看到testNG了 <!--poi excel解析 --><d…

运维监控学习笔记3

DELL的IPMI页面的登录&#xff1a; 风扇的状态&#xff1a; 电源温度&#xff1a;超过70度就告警&#xff1a; 日志信息&#xff1a; 可以看到更换过磁盘。 iDRAC的设置 虚拟控制台&#xff1a;启动远程控制台&#xff1a; 可以进行远程控制。 机房工程师帮我们接远程控制&…

【云原生】kubernetes中容器的资源限制

目录 1 metrics-server 2 指定内存请求和限制 3 指定 CPU 请求和限制 资源限制 在k8s中对于容器资源限制主要分为以下两类: 内存资源限制: 内存请求&#xff08;request&#xff09;和内存限制&#xff08;limit&#xff09;分配给一个容器。 我们保障容器拥有它请求数量的…