uni-app微信小程序-利用canvas给图片添加水印

2023-11-16

实现思路

选择图片 → 将图片绘制到 canvas 中并绘制水印 →将添加水印的图片绘制到 canvas 中 → 将 canvas 画布转换为图片地址 → 上传/展示操作

一、选择图片

注意:微信小程序在选择照片或者唤起相机之前需要获取相应的 权限

  1. 利用 uni.getSetting 查看用户是否调用相机的权限(有就选择图片,没有就获取授权后再选择图片)
    uni.getSetting({
    	success: async (res) => {
    		// 是否有相机权限
    		if (!res.authSetting['scope.camera']) {
    			// 获取相机权限
    			uni.authorize({
    				scope: 'scope.camera',
    				async success() {
    					// 选择图片
    					chooseImage()
    				}
    			})
    		} else {
    			// 选择图片
    			chooseImage()
    		}
    	},
    	fail(err) {
    		console.log('err :>> ', err);
    	}
    })
    
  2. 利用 uni.chooseMedia 选择图片
    uni.chooseMedia({
    	count: 1, // 1就是最多选择一张图片
    	mediaType: ['image'],
    	sourceType: _this.sourceType, // album相册、camera相机
    	success: async (res) => {
    		const tempFilePath = res.tempFiles[0].tempFilePath
    		//添加水印
    		_this.addWaterMarking(tempFilePath)
    	}
    })
    

二、将图片绘制到 canvas 中并绘制水印

  1. 放置 canvas 画布并将其放置到窗口外

    <template>
    	<view class='waterMarkingCamera'>
    		<canvas id="myCanvas" type="2d" :style="'position:fixed;left:8888px'" />
    	</view>
    </template>
    
  2. 利用 uni.getImageInfo 获取原图片信息

    addWaterMarking(imageUrl) {
    	//获取原图片信息
    	uni.getImageInfo({
    		src: imageUrl,
    		success: async function(res) {
    			const imgWidth = res.width; // 图片的宽
    			const imgHeight = res.height; // 图片的高
    		}
    	})
    }
    
  3. 利用 wx.createSelectorQuery 获取 canvas 对象并渲染上下文

    addWaterMarking(imageUrl) {
    	//获取原图片信息
    	uni.getImageInfo({
    		src: imageUrl,
    		success: async function(res) {
    			const imgWidth = res.width; // 图片的宽
    			const imgHeight = res.height; // 图片的高
    			// 获取 canvas 
    			wx.createSelectorQuery()
    				.in(_this)
    				.select('#myCanvas') // <canvas id="myCanvas" type="2d"  :style="'position:fixed;left:8888px'" />
    				.fields({
    					node: true,
    					size: true
    				})
    				.exec((resCanvas) => {
    					// canvas 对象
    					const canvas = resCanvas[0].node
    					// 初始化画布大小
    					canvas.width = imgWidth;
    					canvas.height = imgHeight;
    					// 渲染上下文
    					const ctx = canvas.getContext('2d')
    				}
    		}
    	})
    }
    
  4. 创建图片对象并绘制图片和水印

    addWaterMarking(imageUrl) {
    	//获取原图片信息
    	uni.getImageInfo({
    		src: imageUrl,
    		success: async function(res) {
    			const imgWidth = res.width; // 图片的宽
    			const imgHeight = res.height; // 图片的高
    			// 获取 canvas 
    			wx.createSelectorQuery()
    				.in(_this)
    				.select('#myCanvas') // <canvas id="myCanvas" type="2d"  :style="'position:fixed;left:8888px'" />
    				.fields({
    					node: true,
    					size: true
    				})
    				.exec((resCanvas) => {
    					// canvas 对象
    					const canvas = resCanvas[0].node
    					// 初始化画布大小
    					canvas.width = imgWidth;
    					canvas.height = imgHeight;
    					// 渲染上下文
    					const ctx = canvas.getContext('2d')
    					// 图片对象
    					const image = canvas.createImage()
    					// 图片加载完成回调
    					image.onload = async () => {
    						let waterMarkingText = _this.waterMarkingText;
    						// 将图片绘制到 canvas 上
    						ctx.drawImage(image, 0, 0, imgWidth, imgHeight)
    						// 添加遮罩水印
    						if (_this.isOverlay) {
    							_this.addOverlay(ctx, res, waterMarkingText)
    						}
    						// 添加文字水印
    						if (typeDetection.call(waterMarkingText) == '[object Array]') {// 判断是否添加多条水印
    							for (let i = 0; i < waterMarkingText.length; i++) {
    								// 设置水印内容
    								const text = waterMarkingText[i] == 'time' ? 
    								_this.$parseTime(new Date()) :
    								waterMarkingText[i] == 'address' ?
    								_this.address : waterMarkingText[i]
    								// 设置水印颜色
    								ctx.fillStyle = typeDetection.call(_this.fontColor) == '[object Array]' ?
    								_this.fontColor[i] : _this.fontColor;
    								// 设置水印文字大小
    								ctx.font = `${typeDetection.call(_this.fontSize) == '[object Array]' ?
    								_this.fontSize[i] : _this.fontSize}px arial`;
    								// 绘制水印文字
    								ctx.fillText(text, _this.interval[0], res.height - 
    								(typeDetection.call(_this.fontSize) == '[object Array]' ?
    								 _this.fontSize[i] : 
    								_this.fontSize) - _this.interval[1] * i)
    								ctx.restore();
    							}
    						} else {
    							// 设置水印颜色
    							ctx.fillStyle = _this.fontColor;
    							// 设置水印文字大小
    							ctx.font = `${_this.fontSize}px arial`;
    							// 绘制水印文字
    							ctx.fillText(waterMarkingText, _this.interval[0], res.height -  _this.fontSize);
    							ctx.restore();
    						}
    					}
    					image.src = imageUrl
    				})
    		}
    	})
    }
    

三、将 canvas 画布转换为图片地址

	addWaterMarking(imageUrl) {
		//获取原图片信息
		uni.getImageInfo({
			src: imageUrl,
			success: async function(res) {
				const imgWidth = res.width; // 图片的宽
				const imgHeight = res.height; // 图片的高
				// 获取 canvas 
				wx.createSelectorQuery()
					.in(_this)
					.select('#myCanvas') // <canvas id="myCanvas" type="2d"  :style="'position:fixed;left:8888px'" />
					.fields({
						node: true,
						size: true
					})
					.exec((resCanvas) => {
						// canvas 对象
						const canvas = resCanvas[0].node
						// 初始化画布大小
						canvas.width = imgWidth;
						canvas.height = imgHeight;
						// 渲染上下文
						const ctx = canvas.getContext('2d')
						// 图片对象
						const image = canvas.createImage()
						// 图片加载完成回调
						image.onload = async () => {
							let waterMarkingText = _this.waterMarkingText;
							// 将图片绘制到 canvas 上
							ctx.drawImage(image, 0, 0, imgWidth, imgHeight)
							// 添加遮罩水印
							if (_this.isOverlay) {
								_this.addOverlay(ctx, res, waterMarkingText)
							}
							// 添加文字水印
							if (typeDetection.call(waterMarkingText) == '[object Array]') {
								for (let i = 0; i < waterMarkingText.length; i++) {
									// 设置水印内容
									const text = waterMarkingText[i] == 'time' ? 
									_this.$parseTime(new Date()) :
									waterMarkingText[i] == 'address' ?
									_this.address : waterMarkingText[i]
									// 设置水印颜色
									ctx.fillStyle = typeDetection.call(_this.fontColor) == '[object Array]' ?
									_this.fontColor[i] : _this.fontColor;
									// 设置水印文字大小
									ctx.font = `${typeDetection.call(_this.fontSize) == '[object Array]' ?
									_this.fontSize[i] : _this.fontSize}px arial`;
									// 绘制水印文字
									ctx.fillText(text, _this.interval[0], res.height - 
									(typeDetection.call(_this.fontSize) == '[object Array]' ?
									 _this.fontSize[i] : 
									_this.fontSize) - _this.interval[1] * i)
									ctx.restore();
								}
							} else {
								// 设置水印颜色
								ctx.fillStyle = _this.fontColor;
								// 设置水印文字大小
								ctx.font = `${_this.fontSize}px arial`;
								// 绘制水印文字
								ctx.fillText(waterMarkingText, _this.interval[0], res.height -  _this.fontSize);
								ctx.restore();
							}
							// 某些平台 canvas 绘制比较慢,需要等待绘制完成
							await _this.sleep(500)
							// 将 canvas 画布转换为图片地址
							wx.canvasToTempFilePath({
								canvas: canvas,
								async success(res) {
								// 上传图片操作
								await _this.uploadImage(res.tempFilePath)
								}
							})
						}
						image.src = imageUrl
					})
			}
		})
	}

四、最终效果

在这里插入图片描述

五、完整代码

<template>
	<view class='waterMarkingCamera'>
		<canvas id="myCanvas" type="2d" :style="'position:fixed;left:8888px'" />
		<view class="waterMarkingCamera-icon" @tap='handleChooseImage()' v-show="!imageArr||imageArr.length<limit">
			<slot name="W_M_C_Icon">
				<text class="iconfont icon-a-xiangji"></text>
			</slot>
		</view>
	</view>
</template>
<script>
	var typeDetection = Object.prototype.toString;
	export default {
		name: 'waterMarkingCamera',
		/**	
		 *	waterMarkingCamera 水印相机
		 *	@property {Array} attachmentList 附件列表
		 * 	@property {Array} sourceType 数据源类型 album(相册) camera(相机)
		 * 	@property {Number|String} limit 限制照片数 999
		 * 	@property {Boolean} isOverlay 是否需要遮罩 true
		 * 	@property {Array|String} waterMarkingText 水印文字 ['time','address'] ['水印文字',水印文字2] | '水印文字' time(添加时间)、address(添加地址)
		 * 	@property {Array|String} fontSize 水印文字大小 32 ['32',100] | '32'
		 * 	@property {Array|String} fontColor 水印文字颜色 '#ffffff' ['#ffffff','red','rgba(255, 255, 255,0.2)','rgb(255, 255, 255)'] | '#ffffff'
		 * 	@property {Array} interval 间距 [50, 80]
		 * 
		 * */
		props: {
			attachmentList: {
				type: Array,
				default: () => {
					return []
				}
			},
			sourceType: {
				type: Array,
				default: () => {
					return ['album', 'camera']
				}
			},
			limit: {
				type: [Number, String],
				default: 999
			},
			isOverlay: {
				type: Boolean,
				default: true
			},
			waterMarkingText: {
				type: [Array, String],
				default: () => {
					return ['time', 'address']
				}
			},
			fontSize: {
				type: [Array, Number, String],
				default: 32
			},
			fontColor: {
				type: [Array, String],
				default: '#ffffff'
			},
			interval: {
				type: Array,
				default: () => {
					return [50, 50]
				}
			}
		},
		data() {
			return {
				imageArr: [],
				address: '你的位置信息'
			}
		},
		mounted() {
			this.imageArr = this.attachmentList
			// 反转内容数组
			if (typeDetection.call(this.waterMarkingText) ==
				'[object Array]') {
				this.waterMarkingText.reverse()
			}
		},
		methods: {
			// 选择照片
			handleChooseImage() {
				let _this = this
				uni.getSetting({
					success: async (res) => {
						const chooseImage = () => {
							uni.chooseMedia({
								count: 1, // 最多选择一张图片
								mediaType: ['image'],
								sourceType: _this.sourceType, // 相册、相机
								success: async (res) => {
									const tempFilePath = res.tempFiles[0].tempFilePath
									_this.addWaterMarking(tempFilePath)
								}
							})
						}
						// 是否有相机权限
						if (!res.authSetting['scope.camera']) {
							// 获取相机权限
							uni.authorize({
								scope: 'scope.camera',
								async success() {
									// 选择照片
									chooseImage()
								}
							})
						} else {
							// 选择照片
							chooseImage()
						}
					},
					fail(err) {
						console.log('err :>> ', err);
					}
				})
			},
			// 添加水印
			addWaterMarking(imageUrl) {
				uni.showLoading({
					title: '上传中...',
					mask: true
				})
				var _this = this;
				//获取原图片信息
				uni.getImageInfo({
					src: imageUrl,
					success: async function(res) {
						const imgWidth = res.width; // 图片的宽
						const imgHeight = res.height; // 图片的高
						// 获取canvas 
						wx.createSelectorQuery()
							.in(_this)
							.select('#myCanvas') // 在 WXML 中填入的 id
							.fields({
								node: true,
								size: true
							})
							.exec((resCanvas) => {
								// Canvas 对象
								const canvas = resCanvas[0].node
								// 初始化画布大小
								canvas.width = imgWidth;
								canvas.height = imgHeight;
								// 渲染上下文
								const ctx = canvas.getContext('2d')
								// 图片对象
								const image = canvas.createImage()
								// 图片加载完成回调
								image.onload = async () => {
									let waterMarkingText = _this.waterMarkingText;
									// 将图片绘制到 canvas 上
									ctx.drawImage(image, 0, 0, imgWidth, imgHeight)
									// 添加遮罩水印
									if (_this.isOverlay) {
										_this.addOverlay(ctx, res, waterMarkingText)
									}
									// 添加文字水印
									if (typeDetection.call(waterMarkingText) ==
										'[object Array]') {
										for (let i = 0; i < waterMarkingText.length; i++) {
											// 设置水印内容
											const text = waterMarkingText[i] == 'time' ?
												_this.$parseTime(new Date()) :
												waterMarkingText[i] ==
												'address' ?
												_this.address : waterMarkingText[i]
											// 设置水印颜色
											ctx.fillStyle = typeDetection.call(_this.fontColor) ==
												'[object Array]' ?
												_this.fontColor[i] : _this.fontColor;
											// 设置水印文字大小
											ctx.font = `${typeDetection.call(_this.fontSize) ==
												'[object Array]' ?
												_this.fontSize[i] : _this.fontSize}px arial`;
											// 绘制水印文字
											ctx.fillText(text, _this.interval[0], res.height - (
													typeDetection
													.call(_this
														.fontSize) == '[object Array]' ?
													_this.fontSize[i] : _this.fontSize) - _this
												.interval[1] * i)
											ctx.restore();
										}
									} else {
										// 设置水印颜色
										ctx.fillStyle = _this.fontColor;
										// 设置水印文字大小
										ctx.font = `${_this.fontSize}px arial`;
										// 绘制水印文字
										ctx.fillText(waterMarkingText, _this.interval[0], res
											.height -
											_this
											.fontSize);
										ctx.restore();
									}
									// 某些平台 canvas 绘制比较慢,需要等待绘制完成
									await _this.sleep(500)
									// 将 canvas 画布转换为图片地址
									wx.canvasToTempFilePath({
										canvas: canvas,
										async success(res) {
											// 上传图片操作
										}
									})
								}
								image.src = imageUrl
							})
					}
				})
				this.handleEmit()
			},
			// 添加遮罩
			addOverlay(ctx, res, waterMarkingText) {
				let makerHeight = 0
				if (typeDetection.call(this.fontSize) ==
					'[object Array]') {
					for (let i = 0; i < this.fontSize; i++) {
						makerHeight += (this.fontSize[i] + this
							.interval[1])
					}
				} else {
					makerHeight += (this.fontSize * waterMarkingText.length + this
						.interval[1] * (waterMarkingText.length - 1))
				}
				ctx.rect(0, res.height - makerHeight, res.width, makerHeight);
				ctx.fillStyle = "rgba(46, 46, 46,0.4)";
				ctx.fill();
			},
			// 触发emit
			handleEmit() {
				this.$emit('change', this.imageArr)
				this.$emit('input', this.imageArr)
				this.$emit('update:modelValue', this.imageArr)
			},
			sleep(millisecond) {
				return new Promise((resolve) => {
					setTimeout(resolve, millisecond)
				})
			}
		}
	}
</script>
<style lang="scss" scoped>
	.waterMarkingCamera {
		display: flex;
		flex-flow: row;
		align-items: center;
		justify-content: flex-end;
		padding: 20rpx 0;

		&-icon {
			width: 146rpx;
			height: 120rpx;
			border-radius: 4rpx;
			font-size: 42rpx;
			text-align: center;
			line-height: 108rpx;
			border: 2rpx dashed #666666;
		}
	}
</style>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

uni-app微信小程序-利用canvas给图片添加水印 的相关文章

  • osgEarth的Rex引擎原理分析(一二三)osgEarth的缓存及其结构

    目标 十七 中问题43 1 缓存分两类 1 文件缓存 osgDB FileCache FileSystemCache 位于osgEarthDrivers cache filesystem FileSystemCache osgDB File
  • 51单片机——中断

    中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置 中断功能的存在 很大程度上提高了单片机处理外部或内部事件的能力 老版51单片机内部共有5个中断源 中断是处理器一种工作状态的描述 我们把引起中断的原因 或者能够发出中断请求信号的
  • 11-----curl命令行代替post请求带baby

    1 curl命令行代替post请求带baby 使用curl命令行代替postman在linux是非常方便的 curl H Content Type application json X POST data camera uid 123 45
  • 半虚拟化和全虚拟化的区别

    全虚拟化 Full virtualization 也称为原始虚拟化技术 是另一种虚拟化方法 该模型使用虚拟机协调客户 操作系统和原始硬件 见图2 这里 协调 是一个关键词 因为VMM在客户操作系统和裸硬件之间用于工作协调 一些受保护的指令必
  • 【nginx】静态文件处理:root和alias的区别以及try_files用法

    对于静态文件 nginx支持配置文件路径 关键字为root和alias 简介 配置系统 data www目录下有如下文件 data www file a txt b txt backup c txt d txt nginx 配置中 loca
  • 面向对象设计基本原则(举例说明)

    单一职责原则 SRP 就一个类而言 应该仅有一个引起它变化的原因 如果一个类承担的职责过多 就等于把这些职责耦合在一起 一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力 这种耦合会导致脆弱的设计 当变化发生时 设计会遭受到意想不到的
  • 基于Qt的轻量级的Ribbon控件(Office样式UI)

    基于Qt的轻量级的Ribbon控件 Office样式UI 界面截图 它支持4种目前常见的ribbon样式在线切换 包括2种office模式 office模式是最常见的ribbon模式了 就是我们经常看到的word模式 office模式的ta
  • Rust vs Go:两者结合效果更好!

    最近看到一个程序员工资排行的图 435501份数据 调查显示 Rust 是最赚钱的 随着 Rust 的发展和它表现出的很多优点 越来越多 Gopher 开始关注 Rust 首先 Rust 没有历史包袱 集表达力 高性能 内存安全于一身 可以
  • 2022年11月7日--11月13日(ue4 tf1视频教程+cesium for ue源码CesiumUtility抄写,本周10小时,合计1737小时,剩余8263小时)

    目前 mysql 7 1 tf1 3 3 oss 12 1 蓝图反射 1 7 moba 1 5 webapp 2 4 mmoarpg 00A 04 socket 2 8 根据月计划 ue4 tf1视频教程 进度按照每天一小时时长视频 其余时
  • 博客点击率过万

    写博客有5个月了 今天一看点击率过万了 但是转载的一篇文章点击了2500多次 怎么大家喜欢看文字的东西 而不喜欢技术呢 还是我写的太烂了 2012 7 9

随机推荐

  • QT6+Halcon

    2020年12月8日 Qt公司正式发布了Qt 6 0 这一软件开发平台全新的主要版本 Qt 6 0 已被重新设计为面向未来 以生产力为重点的基础平台 QT迎来一个新时代 Qt Halcon这种组合在机器视觉方面应用非常广泛 一 Qt6全新理
  • C++面向对象+案例(附代码)

    C 核心编程 接上一篇 c 基础入门 文章目录 C 核心编程 1 内存分区模型 1 1 程序运行前 1 2程序运行后 1 3new操作符 2 引用 略 3 函数提高 4 类和对象 4 1封装 4 1 2struct与class区别 4 1
  • 现货交易技巧有哪些可以帮助大家

    想要在现代生活中实现资产升值 或许各种投资理财是最好的选择 但是 不管是选择哪一种 大家都需要掌握基本的一些投资技巧的 比如说现货交易 作为新兴的投资行业 大家必须要认真了解现货交易技巧 这样才可以更好地开展现货交易活动 只有现货交易基础打
  • three.js入门到实战

    学习之前 示例演示 参考资料 api查询 http www webgl3d cn threejs docs index html 代码地址 https github com mrdoob three js 学习方法讲解 对于没有基础的前端小
  • 电机与接触器小结

    目录 各类电机区别 交流 直流电机的区别 同步 异步两类电机区别 为什么会同步 为什么会不同步呢 永磁同步电机 永磁同步电机内部构造 永磁同步电机工作原理 普通 变频两类电机区别 电动机的绝缘强度问题 谐波电磁噪声与震动 低转速时的冷却问题
  • 全面认识Linux下打包解压压缩命令

    1 前言 最近通过sudo tar czf usr src tgz usr src 这个命令发现我对打包方面的命令一无所知 故正式学习记录下 这个命令动作为 将 usr src 目录下的文件打包压缩为当前路径下的usr src tgz文件
  • Explain详解与索引最佳实践

    文章目录 Explain 解释 示范表 使用语句 explain 每一列说明 id select type table type key len ref rows EXTRA 索引最佳实践 Explain 解释 示范表 DROP TABLE
  • VMware虚拟机下的CentOS7网络配置

    一 虚拟机设置 VMware界面最上面 选择虚拟机 gt 设置 将网络连接改为桥接模式 如下图所示 二 查看主机DNS地址 win R 输入cmd 启动命令行界面 输入ipconfig all 查看主机DNS服务器地址 如下图所示 三 修改
  • 基于ARM-contexA9蜂鸣器驱动开发

    上次 我们写了一个LED的驱动程序 这一节 我们只需稍微改动一下就可以实现蜂鸣器的驱动 让我们来看看吧 还是跟之前一样 先找电路图 找到电路板上对应的引脚和相关联的寄存器 1 看电路图 1 蜂鸣器接口位于电路板的底板 看电路图可知道是高电平
  • Servlet与Jsp之间有哪些数据传输的方式?

    前言 根据MVC架构大家都很清楚 servlet充当咱们mvc中的c 也就是controller 而jsp则是咱们的view 所以呀 根据它们各自的职责划分 servlet相当于是一个指挥官 将页面数据交给业务逻辑层去处理 处理后的数据也就
  • 斗破苍穹算法版—萧炎的成长之路(一)

    前言 作者主页 雪碧有白泡泡 个人网站 雪碧的个人网站 推荐专栏 java一站式服务 前端炫酷代码分享 uniapp 从构建到提升 从0到英雄 vue成神之路 解决算法 一个专栏就够了 架构咱们从0说 数据流通的精妙之道 文章目录 前言 主
  • 什么是CA数字证书,CA证书有什么作用?

    CA证书 也是根证书 是最顶级的证书 也是CA认证中心与用户建立信任关系的基础 用户的数字证书必须有一个受信任的根证书 用户的数字证书才是有效的 那么 CA数字证书是干嘛用的 有什么作用呢 通过下文来详细了解下 所谓CA认证中心 它是采用P
  • Latex模板elsevier爱思唯尔KBS投稿步骤

    1 注册账号 我投的是KBS Elsevier旗下的应该都差不多 2 选择文章类型 我选的 full length article 3 Attach Files 必填项可以去搜一下 都有模板 Elsevier作者指南都有超链接 可以直接看例
  • python手写光线追踪(不使用图形学API)——第二期

    本文未经允许禁止转载 B站 https space bilibili com 455965619 作者 Heskey0 赫斯基皇 二 specular材质和glass材质 在这个案例中 总共有4种材质 none specular glass
  • 并发编程基础和原理

    1 了解多线程的意义和使用 1 1 什么是进程 什么是线程 进程 是一个正在执行中的程序 每一个进程执行都有一个执行顺序 该顺序是一个执行路径 或者叫一个控制单元 我们打开电脑上的qq时 点击qq exe 电脑就会运行一个qq的程序 这个程
  • 【ML on Kubernetes】第 2 章:理解 MLOps

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • 【目标检测】38、PAA

    文章目录 一 背景 二 方法 2 1 Probabilistic Anchor Assignment Algorithm 2 2 IoU prediction as Localization Quality 2 3 Score Voting
  • ctfshow 网络迷踪-哐啷哐啷+鲶鱼之谜

    12 哐啷哐啷 通过谷歌识图找到这张图片出自的文章 得到这是这是新疆和田 ctfshow 和田 13 鲶鱼之谜 这题在群里有wp我就不写了各位可以参考一下群文件 最终flag ctfshow ca1524 1837
  • 运行jupyter notebook 时报“kernel error“ ,以及如何切换python解释器

    运行jupyter notebook 时报 kernel error 以及如何切换python解释器 一般出现 kernel error 时 多半是因为系统中存在较多的python解释器 1 查看内核 假定读者已通过pip 安装了 jupy
  • uni-app微信小程序-利用canvas给图片添加水印

    实现思路 一 选择图片 二 将图片绘制到 canvas 中并绘制水印 三 将 canvas 画布转换为图片地址 四 最终效果 五 完整代码 实现思路 选择图片 将图片绘制到 canvas 中并绘制水印 将添加水印的图片绘制到 canvas