uni-app(微信小程序)图片旋转放缩,文字绘制、海报绘制
总结一下:
要进行海报绘制离不开canvas,我们是先进行图片,文字的拖拽、旋转等操作
最后再对canvas进行绘制,完成海报绘制。
- 背景区域设置为 position: relative,方便图片在当前区域中拖动等处理。
- 添加图片,监听图片在背景区域下的 touchstart touchmove touchend 事件
- 拖动图片,在touchmove中,对图片进行位置(后续坐标-初始坐标)、角度(勾股定理计算点到圆心距离,利用角度计算公式计算)、放缩比例(勾股定理计算点到圆心的半径距离,拖动停止的半径除以初始的半径,获得放缩比例scale)的计算
- 最终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 = nulltimer = 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; // 返回角度},
体验:
相关文章:

uni-app(微信小程序)图片旋转放缩,文字绘制、海报绘制
总结一下: 要进行海报绘制离不开canvas,我们是先进行图片,文字的拖拽、旋转等操作 最后再对canvas进行绘制,完成海报绘制。 背景区域设置为 position: relative,方便图片在当前区域中拖动等处理。添加图片࿰…...
Spring Boot 2.x基础教程
Spring Boot 2.x基础教程 一、简介1. Spring Boot 2.x 简介2. Spring Boot 2.x 特点3. Spring Boot 2.x 与 Spring Framework 的关系 二、Spring Boot 2.x 环境搭建1. JDK环境安装与配置2. Maven环境安装与配置3. Spring Boot 2.x 项目创建 三、核心功能1. 配置文件及其加载顺序…...
汽车红外夜视系统行业发展总体概况
汽车红外夜视系统是一种技术,旨在帮助驾驶员在夜间或低光条件下提供更好的视觉能力。它利用红外光谱的特性来检测和显示在正常光线下难以察觉的热能辐射。这使驾驶员能够在夜间或恶劣天气条件下更好地识别和辨别道路上的物体、行人、动物或其他车辆。 汽车红外夜视…...
Java 和 PHP GC 的差异和差异出现的原因
JAVA 的 GC 处理 判断草死掉的两种方式:引用计数和可达性分析 可达性分析对 JAVA 比较好用的原因是 JAVA遵守这面向对象的严格要求,每个变量都被对象包裹,所以每个变量都能通过对象来进行遍历找到,最终判断他们的是否被引用&…...
loguru logger使用
一、基本使用 ①标准使用 from loguru import logger# 在标准输出里面输出一行debug日志 logger.debug("Thats dubug")②设置输出格式 from loguru import loggerlogger.remove(0) # 先删除格式 logger.add(sink./logger.log, format"{time: %Y-%m-%d %H:%M…...
vue-自适应布局-postcss-pxtorem
原理: 比如一个375px设计稿 其中一个320px宽度的元素 如何实现自适应布局呢? 其实可以这样理解: 我们先计算出375屏幕时候320px的大小,在屏幕变化时候,这些元素都会等比例缩放 比如屏幕从375 变为750px时候࿰…...

9.12|day 5|day 44 |完全背包| 518. 零钱兑换 II | 377. 组合总和 Ⅳ
● 完全背包 主要是看清01背包和完全背包的区别 //01背包 for(int i 0;i<weight.size();i){ for(int j bagWeight;j>weight[i];j--){dp[j] Math.max(dp[j],dp[j-weight[i]]value[i]); } } //完全背包 for(int i 0;i<weight.size();i){for(int j weight[i];j<…...
C++ 中的原子变量(std::atomic)使用指南
目录 C 中的原子变量(std::atomic)使用指南基本概念使用方法创建原子变量读取值修改值原子操作 常见应用场景1. 计数器2. 控制标志3. 链表和数据结构 示例代码结论 C 中的原子变量(std::atomic)使用指南 原子变量(std…...

【用unity实现100个游戏之9】使用Unity制作类八方旅人、饥荒风格的俯视角2.5D游戏
前言 2.5D游戏 是一种介于二维和三维之间的游戏形式。它通常在二维平面上展示游戏内容,但利用三维技术来实现更加逼真的图像效果。 在2.5D游戏中,角色和环境通常是以平面的形式呈现,但可以在垂直方向上移动。这意味着玩家可以在一个相对较薄…...

如何在群晖中,正确配置 docker 的 ipv6 地址
参考 2023年9月12日 https://synocommunity.com/ https://github.com/wangliangliang2/fix_synology_docker_ipv6 https://post.smzdm.com/p/an3np8m7/ 正文 关于这个话题,国内搜索引擎得到的结果出奇的一致,且过时。 (看的我脑壳痛&#…...

XSS入门 XSS Challenges
level1(直接注入) <script>alert(xss)</script>level2(双引号闭合标签) 测试 <sCr<ScRiPt>IPT>OonN"\/(hrHRefEF)</sCr</ScRiPt>IPT>发现<>"被转换,构造新的语句 "><script>alert(/xss/)</…...

李沐《动手学深度学习》torch.cat() 和 torch.stack()的区别及思考
一、问题引出 好久没更新啦!最近在学习沐神《动手学深度学习》6.5节池化层的时候,发现沐神在两处相似的地方使用了两种Python拼接函数torch.cat()和torch.stack(): 百思不得其解,于是查阅相关文档之后终于弄清楚了两者之间的区别…...

【算法与数据结构】235、LeetCode二叉搜索树的最近公共祖先
文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析:本题和这道题类似【算法与数据结构】236、LeetCode二叉树的最近公共祖先,相同的算法也能解…...

bboss 流批一体化框架 与 数据采集 ETL
数据采集 ETL 与 流批一体化框架 特性: 高效、稳定、快速、安全 bboss 是一个基于开源协议 Apache License 发布的开源项目,主要由以下三部分构成: Elasticsearch Highlevel Java Restclient , 一个高性能高兼容性的Elasticsea…...

JVM详细教程
JVM 前言 还在完善中先发布 JVM虚拟机厂家多钟多样,具体实现细节可能不一样,这里主要讲的是虚拟机的规范,以下内容融合了各个平台发布的内容和周志明老师的《深入理解java虚拟机》 JVM概述 如何理解jvm跨平台? 编译成汇编代码…...

Smartbi吴华夫:后疫情时代,BI发展趋势的观察与应对
沿着旧地图找不到新大陆,“基于指标体系的可视化分析和增强分析”成为BI发展新阶段。Smartbi V11系列新品与时俱进,以指标为核心,同时融合BI应用,赋能管理者和业务,成为引领数字化运营的新航标! ——思迈特…...

软件设计模式系列之三———工厂方法模式
1 模式的定义 工厂方法模式是一种常见的设计模式,属于创建型设计模式之一,它在软件工程中用于对象的创建。该模式的主要思想是将对象的创建过程抽象化,将具体对象的实例化延迟到子类中完成,以便在不同情况下可以创建不同类型的对…...
pytorch 多卡分布式训练 调用all_gather_object 出现阻塞等待死锁的问题
pytorch 多卡分布式训练 torch._C._distributed_c10d中的函数all_gather_object 出现阻塞等待死锁的问题 解决办法就是 在进程通信之前调用torch.cuda.set_device(local_rank) For NCCL-based processed groups, internal tensor representations of objects must be moved …...

SpringMvc增删改查
SpringMvc增删改查 一、前期准备二、逆向生成增删改查2.2.aspect切面层2.3.Mybatis generator逆向生成2.4.根据生成代码编写Biz层与实现类 三、controller层代码编写四、前台代码与分页代码五、案例测试 一、前期准备 1.2.导入pom.xml依赖 <?xml version"1.0" …...
【计算机网络】网络编程接口 Socket API 解读(5)
Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。 本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...

循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...

现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...

STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...