uni-app(微信小程序)图片旋转放缩,文字绘制、海报绘制

news2024/11/20 1:27:34

总结一下:

要进行海报绘制离不开canvas,我们是先进行图片,文字的拖拽、旋转等操作
最后再对canvas进行绘制,完成海报绘制。

  1. 背景区域设置为 position: relative,方便图片在当前区域中拖动等处理。
  2. 添加图片,监听图片在背景区域下的 touchstart touchmove touchend 事件
  3. 拖动图片,在touchmove中,对图片进行位置(后续坐标-初始坐标)、角度(勾股定理计算点到圆心距离,利用角度计算公式计算)、放缩比例(勾股定理计算点到圆心的半径距离,拖动停止的半径除以初始的半径,获得放缩比例scale)的计算
  4. 最终canvas绘制,利用dom中的空canvas,将图片依次绘制到canvas中,并获取链接

部分主要代码如下:

const ctx = uni.createCanvasContext("myCanvas", this);
ctx.drawImage(this.imageSrc, 0, 0, IMG_REAL_W, IMG_REAL_H);

const ctx = uni.createCanvasContext("myCanvas", this);
ctx.drawImage(this.imageSrc, 0, 0, IMG_REAL_W, IMG_REAL_H);
ctx.save();
ctx.beginPath();

// 画背景色(白色)
// ctx.setFillStyle('#fff');
// ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
for (let i=0; i<items.length; i++) {
	const cur = items[i]
	ctx.save();
	ctx.translate(0, 0);
	ctx.beginPath();
	if(cur.image) {
		ctx.translate(cur.x, cur.y); // 圆心坐标
		ctx.rotate(cur.angle * Math.PI / 180); // 图片旋转的角度
		ctx.translate(-(cur.width * cur.scale / 2), -(cur.height * cur.scale / 2)) // 图片的缩放
		ctx.drawImage(cur.image, 0, 0, cur.width * cur.scale, cur.height * cur.scale); // 图片绘制
	}
	if (cur.text) {
		ctx.font = `${cur.font}px arial`;
		ctx.fillStyle = cur.fillStyle;
		ctx.fillText(cur.text, cur.left, cur.top + Number(cur.font));
		console.log(cur.left, cur.top + Number(cur.font))
	}
	ctx.restore();
}
ctx.draw(true, () => {
	// 获取画布要裁剪的位置和宽度   均为百分比 * 画布中图片的宽度    保证了在微信小程序中裁剪的图片模糊  位置不对的问题 canvasT = (this.cutT / this.cropperH) * (this.imageH / pixelRatio)
	var canvasW = ((this.cropperW - this.cutL - this.cutR) / this.cropperW) * IMG_REAL_W;
	var canvasH = ((this.cropperH - this.cutT - this.cutB) / this.cropperH) * IMG_REAL_H;
	var canvasL = (this.cutL / this.cropperW) * IMG_REAL_W;
	var canvasT = (this.cutT / this.cropperH) * IMG_REAL_H;
	uni.canvasToTempFilePath({
			x: canvasL,
			y: canvasT,
			width: canvasW,
			height: canvasH,
			// destWidth: canvasW,
			// destHeight: canvasH,
			quality: +this.quality,
			fileType: this.fileType,
			canvasId: "myCanvas",
			success: (res) => {
				uni.hideLoading();
				// 成功获得地址的地方
				// this.$emit("getImg", res.tempFilePath);
				this.saveImg(res.tempFilePath)
				this.isShow = false;
			},
			fail: (err) => {
				uni.hideLoading();
				uni.showToast({
					title: "图片截取失败!",
					icon: "none",
				});
			},
		},
		this
	);
});
<!-- 海报背景区域,采用style动态调整,cropperInitW,cropperInitH一般为满屏 -->
			<view class="uni-corpper"
				:style="'width:' + cropperInitW + 'px;height:' + cropperInitH + 'px;background:#000'">
				<!-- 海报绘制区域,采用style动态调整,按照图片的长宽比例动态计算 cropperW等-->
				<view class="uni-corpper-content" :style="
						'width:' +
							cropperW +
							'px;height:' +
							cropperH +
							'px;left:' +
							cropperL +
							'px;top:' +
							cropperT +
							'px'
					">
					<!-- 背景图片区域 cropperW等同上 -->
					<image :src="imageSrc" :style="'width:' + cropperW + 'px;height:' + cropperH + 'px;' + 'border: 3px solid #ff0000;'"></image>
					<!-- 海报上其他图片处理,for循环,itemList通过点击添加 -->
					<block v-for="item in itemList" :key="item.id">
					<!-- 动态设置图片区域的缩放比例,还有pisition的左右位置,选中时z-index变大 -->
						<view class='touchWrap' :style="{transform: 'scale(' + item.scale + ')', top: item.top + 'px', left: item.left + 'px', 'z-index':item.active ? 100 : 1}">
							<view class='imgWrap' :class="item.active ? 'touchActive' : ''" :style="{transform: 'rotate(' + item.angle + 'deg)', border: item.active ? 4 * item.oScale : 0 + 'rpx #fff dashed'}">
								<image 
									v-if="item.image"
									:src='item.image' 
									:style="{width: item.width + 'px', height: item.height + 'px'}" 
									<!-- 图片点击时,记录点击图片当前位置 -->
									@touchstart="(e) => WraptouchStart(e, item)"
									<!-- 图片拖动时,记录图片当前位置,并实时计算图片大小、旋转角度等,并存储至itemList中 -->
									@touchmove="(e) => WraptouchMove(e, item)"
									<!--一般不做处理 -->
									@touchend="(e) => WraptouchEnd(e, item)"
									mode="widthFix"
								>
								</image>
								<!-- 删除按钮 -->
								<image 
									class='x' 
									src='/static/close.png' 
									:style="{transform: 'scale(' + item.oScale + ')', 'transform-origin': center}"
									@click="(e) => deleteItem(e, item)"
								>
								</image>
								<!-- 图片放缩按钮 -->
								<image 
									v-if="item.image"
									class='o' 
									src='/static/scale.png' 
									:style="{transform: 'scale(' + item.oScale + ')', 'transform-origin': center}"
									<!-- 图片点击时,记录点击图片当前坐标,半径 -->
									@touchstart="(e) => oTouchStart(e, item)"
									<!-- 图片点击时,记录点击图片当前坐标,计算新的半径(得到scale缩放比例)计算角度差,获取当前角度 -->
									@touchmove="(e) => oTouchMove(e, item)"
									@touchend="(e) => WraptouchEnd(e, item)"
								>
								</image>
							</view>
						</view>
					</block>
				</view>
							<canvas canvas-id="myCanvas" :style="
				'position:absolute;border: 2px solid red; width:' +
					imageW +
					'px;height:' +
					imageH +
					'px;top:-9999px;left:-9999px;'
			">
			</canvas>
			</view>
// 点击图片或文字
			WraptouchStart(e, it) {
				currentChoose = it
				// 循环图片数组获取点击的图片信息
				for (let i = 0; i < items.length; i++) {
					items[i].active = false;
					if (it.id == items[i].id) {
						index = i;
						items[index].active = true;
					}
				}
				// this.setData({
				//   itemList: items
				// })
				this.setList(items, 'itemList')
				// 获取点击的坐标值 lx ly是图片点击时的位置值
				items[index].lx = e.touches[0].clientX;
				items[index].ly = e.touches[0].clientY;
			},
			// 拖动图片
			WraptouchMove(e) {
				// 获取点击的坐标值 _lx _ly 是图片移动的位置值
				items[index]._lx = e.touches[0].clientX;
				items[index]._ly = e.touches[0].clientY;
				// left 是_lx 减去 lx,_ly 减去 ly,也就是现在位置,减去原来的位置。
				items[index].left += items[index]._lx - items[index].lx;
				items[index].top += items[index]._ly - items[index].ly;
				// 同理更新图片中心坐标点,用于旋转
				items[index].x += items[index]._lx - items[index].lx;
				items[index].y += items[index]._ly - items[index].ly;
				// 停止了以后,把lx的值赋值为现在的位置
				items[index].lx = e.touches[0].clientX;
				items[index].ly = e.touches[0].clientY;
				
				// this.setData({
				//   itemList: items
				// })
				this.setList(items, 'itemList')
			},
			// 放开图片
			WraptouchEnd(e, it) {
				touchNum ++
				clearTimeout(timer)
				timer = null
				timer = setTimeout(this.timeSta, 250)
			},
			// 计算坐标点到圆心的距离
			getDistancs(cx, cy, pointer_x, pointer_y) {
				var ox = pointer_x - cx;
				var oy = pointer_y - cy;
				return Math.sqrt(
					ox * ox + oy * oy
				);
			},
			/*
			*参数cx和cy为图片圆心坐标
			*参数pointer_x和pointer_y为手点击的坐标
			*返回值为手点击的坐标到圆心的角度
			*/
			countDeg(cx, cy, pointer_x, pointer_y) {
				var ox = pointer_x - cx;
				var oy = pointer_y - cy;
				var to = Math.abs(ox / oy); // 勾股定理,计算当前点距离中心点的距离。
				var angle = Math.atan(to) / (2 * Math.PI) * 360; // 计算当前角度
				if (ox < 0 && oy < 0) //相对在左上角,第四象限,js中坐标系是从左上角开始的,这里的象限是正常坐标系  
				{
					angle = -angle;
				} else if (ox <= 0 && oy >= 0) //左下角,3象限  
				{
					angle = -(180 - angle)
				} else if (ox > 0 && oy < 0) //右上角,1象限  
				{
					angle = angle;
				} else if (ox > 0 && oy > 0) //右下角,2象限  
				{
					angle = 180 - angle;
				}
				return angle; // 返回角度
			},

体验:
自定义画报

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

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

相关文章

一文掌握SSD、EMMC、UFS原理与差异

有一天&#xff0c;小明在他的智能手机上播放了一段高清视频&#xff0c;发现视频播放得非常流畅。他感叹道&#xff1a;“现在的存储技术真是太棒了&#xff01;”他的朋友小华却告诉他&#xff1a;“这还不算什么&#xff0c;你还没用过UFS呢&#xff01;”小明一下子好奇起来…

【Spring Cloud系统】- 轻量级高可用工具Keepalive详解

【Spring Cloud系统】- 轻量级高可用工具Keepalive详解 文章目录 【Spring Cloud系统】- 轻量级高可用工具Keepalive详解一、概述二、Keepalive分类2.1 TCP的keepalive2.2 HTTP的keep-alive2.3 TCP的 KeepAlive 和 HTTP的 Keep-Alive区别 三、nginx的keepalive配置3.1 nginx保持…

初出茅庐的小李博客之根据编译时间生成软件版本号

为什么要软件版本号呢&#xff1f; 生成软件版本号是在软件开发和维护过程中非常重要的一项任务&#xff0c;它有很多意义和好处&#xff0c;同时也有多种常见的方法。 标识和追踪&#xff1a;软件版本号是唯一的标识符&#xff0c;用于区分不同版本的软件。这有助于开发人员和…

【案例分享】部署华为防火墙确保园区出口安全

【微|信|公|众|号&#xff1a;厦门微思网络】 部署华为防火墙确保园区出口安全案例 本案例将以园区典型组网为例&#xff0c;主要介绍园区出口安全的部署。具体业务安全要求如下&#xff1a; 内网用户可以正常访问Internet资源&#xff0c;但只能访问教育/科学类、搜索/门户类网…

【计算机组成 课程笔记】5.2 处理器的设计步骤(2)

课程链接&#xff1a; 计算机组成_北京大学_中国大学MOOC(慕课) 5 - 3 - 503-运算指令的控制信号&#xff08;14-58--&#xff09;_哔哩哔哩_bilibili 【计算机组成 课程笔记】5.1 处理器的设计步骤-CSDN博客 中介绍了处理器的设计步骤中的前三步&#xff0c;接下来我们继续介绍…

一文了解大模型工作原理——以ChatGPT为例

文章目录 写在前面1.Tansformer架构模型2.ChatGPT原理3.提示学习与大模型能力的涌现3.1 提示学习3.2 上下文学习3.3 思维链 4.行业参考建议4.1 拥抱变化4.2 定位清晰4.3 合规可控4.4 经验沉淀 写在前面 2022年11月30日&#xff0c;ChatGPT模型问世后&#xff0c;立刻在全球范围…

模电课程设计

主要内容跟本科实验关系很大&#xff0c;可以用来借鉴。 包含文件有&#xff1a;实验报告、Multisim仿真文件&#xff0c;资料很全&#xff0c;有问题可以私信 目录 1、模电课设&#xff1a;用Multisim简单了解二极管 2、模电课设&#xff1a;用Multisim简析三极管与场效应…

Python基础语法练习

输出欢迎信息 方法一&#xff1a; 定义变量赋值并输出&#xff0c;用将变量和字符拼接 username "EMT" print("Welcome,” username)方法二&#xff1a; 用format函数格式化输出变量&#xff0c;并替换{}中的内容 username "EMT" print("welcom…

vue3_动态添加路由,以及路由刷新后页面丢失问题

首先&#xff0c;是要将权限数据本地持久化存储 接着在router文件夹index.js中引入store中的权限数据&#xff0c;并导出一个方法&#xff0c;判断权限当中的数据&#xff0c;并跟roleEnum路由规则匹配&#xff0c;匹配上之后则addRoute() 在登录之后调用这个方法&#xff0c;动…

django添加数据库字段进行数据迁移

1.修改view.py里面的变量 2.在model.py新增字段 3.打开terminal并将环境切到项目所在环境&#xff0c;切换方式为 4.执行命令 python manage.py makemigrations backend python manage.py migrate

CSP 201403-1 相反数

答题 用两个优先队列&#xff0c;一个记录正数升序排序&#xff0c;一个记录负数降序排序&#xff0c;然后在两个队列都不为空的情况下取二者top相加与0比较大小&#xff0c;如果等于0&#xff0c;那么相反数的数目增加一对并同时弹出队列&#xff0c;如果小于0&#xff0c;那…

智能称重解决方案

智能称重解决方案 在现代制造业中&#xff0c;确保产品质量是至关重要的。尤其是在装配产线中&#xff0c;经常面临着漏装、少装等装箱异常问题&#xff0c;这可能导致产品不合格、客户投诉以及损失。为了应对这一挑战&#xff0c;我们需要开发一套智能监控系统&#xff0c;采…

PostgreSQL Page结构

Page结构 在数据文件&#xff08;堆表、索引、自由空间映射和可见性映射&#xff09;内部&#xff0c;它被划分为固定长度的page&#xff08;或block&#xff09;&#xff0c;默认为 8192 字节&#xff08;8 KB&#xff09;。每个文件中的页面从 0 开始顺序编号&#xff0c;这…

管理类联考——数学——汇总篇——知识点突破——应用题——线性规划

⛲️ 一、考点讲解 线性规划特征 线性规划是运筹学中辅助人们进行科学管理的一种数学方法。线性规划所研究的是&#xff1a;在一定条件下&#xff0c;合理安排人力物力等资源&#xff0c;使经济效果达到最好。一般地&#xff0c;求线性目标函数在线性约束条件下的最大值或最小…

Shell编程之sort

sort 命令将文件的每一行作为比较对象&#xff0c;通过将不同行进行相互比较&#xff0c;从而得到最终结果。从首字符开始&#xff0c;依次按ASCII码值进行比较&#xff0c;最后将结果按升序输出。 基本语法 sort (选项)(参数) 常用选项 常用选项 -n根据字符串的数字比较-r…

多功能批量剪辑软件一天剪辑1000条原创视频

下面一个视频用了呆头鹅批量剪辑软件播放量竟然能达到100多万。 他是怎么做到的呢&#xff1f;下面我给大家详细的说一下 ​ 在短视频平台开展业务&#xff0c;需具备批量制作视频的能力&#xff0c;为了超越同行&#xff0c;需大量更新作品&#xff0c;争取更多曝光。素…

pycharm打开远程宿主机或远程docker文件夹目录方法,以及设置代码同步

pycharm打开远程宿主机或远程docker文件夹目录方法&#xff0c;以及设置代码同步_pycharm怎么查看服务器目录_Sisyphus~~的博客-CSDN博客1.如何显示远程的文件夹目录2.如何设置代码同步_pycharm怎么查看服务器目录https://blog.csdn.net/weixin_62321285/article/details/12740…

机器学习入门教学——梯度下降、梯度上升

1、简介 梯度表示某一函数在该点处的方向导数沿着该方向取得最大值&#xff0c;即函数在该点处沿着该方向&#xff08;梯度的方向&#xff09;变化最快&#xff0c;变化率&#xff08;梯度的模&#xff09;最大&#xff0c;可理解为导数。梯度上升和梯度下降是优化算法中常用的…

兵工七子,学硕爆冷!公平,可不考英语!

一、学校及专业介绍 沈阳理工大学&#xff08;Shenyang Ligong University&#xff09;&#xff0c;位于辽宁省沈阳市。东北老牌工科院校&#xff0c;始建于1948年&#xff0c;是我军为培养新中国急需的兵工专门人才在东北地区创建的第一所本科军工高等学校&#xff0c;是共和…

【C++】构造函数与析构函数用途 ( 代码示例 - 构造函数与析构函数用途 )

文章目录 一、构造函数与析构函数二、代码示例 - 构造函数与析构函数用途 一、构造函数与析构函数 在 C 语言中 , " 构造函数 " 和 " 析构函数 " 都是 C 类中的 特殊函数 , 分别用于 初始化对象销毁对象 ; C 类 在创建 实例对象 时自动调用 构造函数 这个…