前端图像处理(一)
目录
一、上传
1.1、图片转base64
二、图片样式
2.1、图片边框【border-image】
三、Canvas
3.1、把canvas图片上传到服务器
3.2、在canvas中绘制和拖动矩形
3.3、图片(同色区域)点击变色
一、上传
1.1、图片转base64
传统上传:
客户端选择图片,将图片上传到服务端,等服务端返回一个url,客户端再将url设置到图片的src里。图片在渲染时,通过url去请求服务器,服务端就会回馈url,客户端就可以看到预览图了。
优化上传:
客户端选择图片后立刻展示,然后继续上传到服务端保存,他俩互不影响。
了解:
url【统一资源定位符】: 协议://主机名[:端口号]/路径名[?查询字符串][#片段标识符]。
MIME【多用途互联网邮件扩展】: 指示数据的内容类型。
MIME类型 | 内容 | 表示含义 |
文本类型 | text/html | 超文本标记语言,用于网页 |
图像类型 | image/png | PNG 图像格式,支持透明度 |
音频类型 | audio/mpeg | MP3 音频格式 |
应用类型 | application/json | JSON 数据格式,用于数据交换 |
多部分类型 | multipart/form-data | 用于 HTTP 表单数据,特别是文件上传 |
示例:
<body><!-- 运行后页面会弹出 alert(123)--><script src="data:application/javascript,alert(123)"></script>
</body>
base64:二进制数据转为ASCII 字符串
科普:在js中:
btoa() 函数用于将字符串进行 Base64 编码【btoa('alert(123)')】
atob() 函数用于将 Base64 编码的字符串解码回原始字符串【atob('YWxlcnQoMTIzKQ==')】
进入正题:
<body><input type="file" /><img src="" alt="" id="preview" /><script src="./1.base64.js"></script></body>
const inp = document.querySelector("input");
inp.onchange = function () {const file = inp.files[0];//多文件,所以是数组const reader = new FileReader();//创建了一个FileReader 对象,用于读取文件内容reader.onload = (e) => {preview.src = e.target.result;// e.target.result 以 Data URL 格式表示,并赋值console.log(file,'转化后',e.target.result)};reader.readAsDataURL(file);//告诉FileReader 以 Data URL 格式读取文件内容
};
后端有时要FormData格式并添加其他参数,而不是原始的二进制格式,可以参考下:
formatImage(type, file) {if (!this.fileUrl) {this.$message.warning('请上传图片')return false}for (let i = 0; i < 5; i++) {const form = new FormData()form.append('matting_type', i + 1)form.append('hd_type', i + 1)form.append('file', file)waterAxios.post('/oss/upload', form).then((res) => {if (res.code == 200) {this.$message.success('上传ok')}})}},
二进制格式上传的消息格式:application/octet-stream
FormData格式上传的消息格式:multipart/form-data
二、图片样式
2.1、图片边框【border-image】
<style>body {background-color: black;}.bdr-img {color: white;text-align: center;padding: 5rem;margin: 2rem auto;width: 50%;border: 50px solid #fff;border-image: url(./stamp.svg) 50 round;/* 相当于下面三行代码的组合 *//* border-image-source: url(./stamp.svg);border-image-slice: 50;border-image-repeat: round; */}</style><body><div class="bdr-img"><p>Hello, My name is [Your Name], and I am a [Your Profession] with [Numberof Years] years of experience in [Your Industry]. I specialize in [YourArea of Expertise] and have a strong background in [Relevant Skills orTechnologies].</p></div></body>
三、Canvas
3.1、把canvas图片上传到服务器
let base64 = canvas.toDataURL()//canvas指canvas格式的图片let imgUrlBlob = dataURLToBlob(base64)var file = new window.File([imgUrlBlob], 'image.png', { type: 'image/png' })let fd = new FormData()fd.append('image', file)
3.2、在canvas中绘制和拖动矩形
<body><div><input type="color" /></div><canvas></canvas><script src="./canvas.js"></script></body>
//============================canvas.js==================
const collorPicker = document.querySelector("input");
const cvs = document.querySelector("canvas");
const ctx = cvs.getContext("2d");
function init() {const w = 500,h = 300;cvs.width = w * devicePixelRatio;cvs.height = h * devicePixelRatio;cvs.style.width = w + "px";cvs.style.height = h + "px";cvs.style.backgroundColor = "gray";
}
init();
const shapes = [];
// 绘制矩形
// 矩形分为起始坐标和结束坐标,最初结束坐标就是起始坐标,结束坐标随着绘制发生改变
// 告诉canvas左上角是起始坐标,确定最小值和最大值
class Rectangle {constructor(color, startX, startY) {this.color = color;this.startX = startX;this.startY = startY;this.endX = startX;this.endY = startY;}//访问器属性get minX() {return Math.min(this.startX, this.endX);}get minY() {return Math.min(this.startY, this.endY);}get maxX() {return Math.max(this.startX, this.endX);}get maxY() {return Math.max(this.startY, this.endY);}draw() {ctx.beginPath();ctx.moveTo(this.minX * devicePixelRatio, this.minY * devicePixelRatio); //左上角(起始坐标)ctx.lineTo(this.maxX * devicePixelRatio, this.minY * devicePixelRatio); //从左上角到右上角ctx.lineTo(this.maxX * devicePixelRatio, this.maxY * devicePixelRatio); //从右上角到右下角ctx.lineTo(this.minX * devicePixelRatio, this.maxY * devicePixelRatio); //从右下角到左下角ctx.lineTo(this.minX * devicePixelRatio, this.minY * devicePixelRatio); //从左下角到左上角ctx.fillStyle = this.color;ctx.fill(); //颜色填充ctx.strokeStyle = "#fff"; //画笔颜色ctx.lineCap = "square"; //线条交界处变圆润ctx.lineWidth = 3 * devicePixelRatio; //画笔宽度ctx.stroke(); //完成边框的绘制}
}
// 自己随意画一个矩形
// const rect = new Rectangle("red", 100, 100);
// rect.endX = 200;
// rect.endY = 200;
// rect.draw();
// 鼠标按下确定起始位置,鼠标移动确定结束位置,鼠标抬起结束事件
cvs.onmousedown = (e) => {const bouding = cvs.getBoundingClientRect();const rect = new Rectangle(collorPicker.value, e.offsetX, e.offsetY);// 进行判断const shape = getShape(e.offsetX, e.offsetY);if (shape) {const { startX, startY, endX, endY } = shape;const moveX = e.offsetX;const moveY = e.offsetY;window.onmousemove = (e) => {//拖动矩形const disX = e.clientX - bouding.left - moveX;const disY = e.clientY - bouding.top - moveY;shape.startX = startX + disX;shape.startY = startY + disY;shape.endX = endX + disX;shape.endY = endY + disY;};window.onmouseup = () => {window.onmousemove = null;window.onmouseup = null;};} else {shapes.push(rect); //将每个矩形数据加进去window.onmousemove = (e) => {rect.endX = e.clientX - bouding.left;rect.endY = e.clientY - bouding.top;};window.onmouseup = () => {window.onmousemove = null;window.onmouseup = null;};}
};
// 辅助函数:判断鼠标按下时是否落在某个矩形内?是:执行移动 否:执行新建矩形
function getShape(x, y) {// 从后往前遍历矩形数组,找到最上面的那个矩形for (let i = shapes.length - 1; i >= 0; i--) {if (x >= shapes[i].minX &&x <= shapes[i].maxX &&y >= shapes[i].minY &&y <= shapes[i].maxY) {return shapes[i];}}return null;
}
// 将shapes依次渲染出来
function draw() {requestAnimationFrame(draw);ctx.clearRect(0, 0, cvs.width, cvs.height); //画完清空一下for (const shape of shapes) {shape.draw();}
}
draw(); //初始化执行一次,后续在每一帧里执行“画”这个动作,前提:数据shapes已经有了
3.3、图片(同色区域)点击变色
<body><canvas></canvas><script src="./index.js"></script></body>
const cvs = document.querySelector("canvas");
const ctx = cvs.getContext("2d", { willReadFrequently: true }); //获取 Canvas 上下文function init() {const img = new Image();img.onload = () => {cvs.width = img.width;cvs.height = img.height;ctx.drawImage(img, 0, 0, img.width, img.height);}; //当图片加载完成时:将图片绘制到画布上img.src = "./redhat.png";
}
init(); //初始化时加载图片cvs.addEventListener("click", (e) => {const x = e.offsetX,y = e.offsetY;// 1、获取点击位置的颜色: imgData.data就是目标对象所有的颜色信息const imgData = ctx.getImageData(0, 0, cvs.width, cvs.height); //开始范围,结束范围const clickColor = getColor(x, y, imgData.data); //点击位置// 2、改变颜色const targetColor = [46, 139, 87, 255]; // 改变后颜色为绿色,透明度为不透明const visited = new Set(); // 记录访问过的像素点changeColor(x, y, targetColor, imgData.data, clickColor, visited); //点击的像素点改变了ctx.putImageData(imgData, 0, 0);
});function pointIndex(x, y) {return (y * cvs.width + x) * 4;
}function getColor(x, y, imgData) {const index = pointIndex(x, y);return [imgData[index],imgData[index + 1],imgData[index + 2],imgData[index + 3],]; //分别对应:r、g、b、a
}// 使用BFS来代替递归
function changeColor(x, y, targetColor, imgData, clickColor, visited) {const queue = [[x, y]]; // 用队列保存待处理的像素点const directions = [[1, 0], [-1, 0], [0, 1], [0, -1]]; // 上下左右四个方向visited.add(`${x},${y}`); // 初始像素点标记为已访问while (queue.length > 0) {const [cx, cy] = queue.shift(); // 从队列中取出一个像素点const index = pointIndex(cx, cy);const curColor = getColor(cx, cy, imgData);// 如果颜色差异大于100或当前像素已经是目标颜色,就跳过if (diff(clickColor, curColor) > 100 || diff(curColor, targetColor) === 0) {continue;}// 修改颜色imgData.set(targetColor, index);// 对周围的像素点进行处理(上下左右)for (const [dx, dy] of directions) {const nx = cx + dx, ny = cy + dy;// 检查边界if (nx >= 0 && nx < cvs.width && ny >= 0 && ny < cvs.height) {const key = `${nx},${ny}`;if (!visited.has(key) && diff(clickColor, getColor(nx, ny, imgData)) <= 100) {visited.add(key); // 标记为已访问queue.push([nx, ny]); // 将该像素点加入队列}}}}
}function diff(color1, color2) {return (Math.abs(color1[0] - color2[0]) +Math.abs(color1[1] - color2[1]) +Math.abs(color1[2] - color2[2]) +Math.abs(color1[3] - color2[3]));
} //计算颜色差异
第三个案例总结:
最初使用无穷递归来实现:
// 递归找相同的像素点(上下左右)changeColor(x + 1, y, targetColor, imgData, clickColor, visited);changeColor(x - 1, y, targetColor, imgData, clickColor, visited);changeColor(x, y + 1, targetColor, imgData, clickColor, visited);changeColor(x, y - 1, targetColor, imgData, clickColor, visited);
但是导致了Maximum call stack size exceeded。最后使用广度优先搜索(BFS)来替代递归:
优势:
(1)使用队列实现BFS:保存待处理的像素点,避免递归带来的栈溢出;
(2)逐层处理:通过
queue.shift()
从队列中取出当前像素点,检查它的上下左右四个方向,并将符合条件的邻接像素点加入队列。(3)避免重复访问:通过
visited
集合避免重复访问已处理过的像素点。
......待更新
相关文章:

前端图像处理(一)
目录 一、上传 1.1、图片转base64 二、图片样式 2.1、图片边框【border-image】 三、Canvas 3.1、把canvas图片上传到服务器 3.2、在canvas中绘制和拖动矩形 3.3、图片(同色区域)点击变色 一、上传 1.1、图片转base64 传统上传: 客户端选择图片…...

unity中:超低入门级显卡、集显(功耗30W以下)运行unity URP管线输出的webgl程序有那些地方可以大幅优化帧率
删除Global Volume: 删除Global Volume是一项简单且高效的优化措施。实测表明,这一改动可以显著提升帧率,甚至能够将原本无法流畅运行的场景变得可用。 更改前的效果: 更改后的效果: 优化阴影和材质: …...

ftdi_sio应用学习笔记 4 - I2C
目录 1. 查找设备 2. 打开设备 3. 写数据 4. 读数据 5. 设置频率 6 验证 6.1 遍历设备 6.2 开关设备 6.3 读写测试 I2C设备最多有6个(FT232H),其他为2个。和之前的设备一样,定义个I2C结构体记录找到的设备。 #define FT…...

如何更好的把控软件测试质量
如何更好的把控软件测试质量 在软件开发过程中,测试是确保软件质量、稳定性和用户体验的重要环节。随着需求的不断变化以及技术的不断进步,如何更好的把控软件测试质量已成为一个不可忽视的话题。本文将从几个维度探讨确保软件质量的方法和方案…...

“漫步北京”小程序及“气象景观数字化服务平台”上线啦
随着科技的飞速发展,智慧旅游已成为现代旅游业的重要趋势。近日,北京万云科技有限公司联合北京市气象服务中心,打造的“气象景观数字化服务平台“和“漫步北京“小程序已经上线,作为智慧旅游的典型代表,以其丰富的功能…...

SOL链上的 Meme 生态发展:从文化到创新的融合#dapp开发#
一、引言 随着区块链技术的不断发展,Meme 文化在去中心化领域逐渐崭露头角。从 Dogecoin 到 Shiba Inu,再到更多细分的 Meme 项目,这类基于网络文化的加密货币因其幽默和社区驱动力吸引了广泛关注。作为近年来备受瞩目的区块链平台之一&…...

身份证实名认证API接口助力电商购物安全
亲爱的网购达人们,你们是否曾经因为网络上的虚假信息和诈骗而感到困扰?在享受便捷的网购乐趣时,如何确保交易安全成为了我们共同关注的话题。今天,一起来了解一下翔云身份证实名认证接口如何为电子商务保驾护航,让您的…...

【过程控制系统】第6章 串级控制系统
目录 6. l 串级控制系统的概念 6.1.2 串级控制系统的组成 6.l.3 串级控制系统的工作过程 6.2 串级控制系统的分析 6.2.1 增强系统的抗干扰能力 6.2.2 改善对象的动态特性 6.2.3 对负荷变化有一定的自适应能力 6.3 串级控制系统的设计 6.3.1 副回路的选择 2.串级系…...

YOLOv11融合针对小目标FFCA-YOPLO中的FEM模块及相关改进思路
YOLOv11v10v8使用教程: YOLOv11入门到入土使用教程 YOLOv11改进汇总贴:YOLOv11及自研模型更新汇总 《FFCA-YOLO for Small Object Detection in Remote Sensing Images》 一、 模块介绍 论文链接:https://ieeexplore.ieee.org/document/10…...

qt+opengl 三维物体加入摄像机
1 在前几期的文章中,我们已经实现了三维正方体的显示了,那我们来实现让物体的由远及近,和由近及远。这里我们需要了解一个概念摄像机。 1.1 摄像机定义:在世界空间中位置、观察方向、指向右侧向量、指向上方的向量。如下图所示: …...

day05(单片机高级)PCB基础
目录 PCB基础 什么是PCB?PCB的作用? PCB的制作过程 PCB板的层数 PCB设计软件 安装立创EDA PCB基础 什么是PCB?PCB的作用? PCB(Printed Circuit Board),中文名称为印制电路板,又称印刷…...

全球天气预报5天-经纬度版免费API接口教程
接口简介: 获取全球任意地区未来5天天气预报,必须传经纬度参数。可先调用【位置坐标】分类下相关接口获取地区经纬度坐标。 请求地址: https://cn.apihz.cn/api/tianqi/tqybjw5.php 请求方式: POST或GET。 请求参数:…...

Shell编程8
声明! 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下,如涉及侵权马上删除文章,笔记只是方便各位师傅的学习和探讨,文章所提到的网站以及内容,只做学习交流,其他均与本人以及泷羽sec团队无关&a…...

python语言基础-5 进阶语法-5.5 上下文管理协议(with语句)
声明:本内容非盈利性质,也不支持任何组织或个人将其用作盈利用途。本内容来源于参考书或网站,会尽量附上原文链接,并鼓励大家看原文。侵删。 5.5 上下文管理协议(with语句)(参考链接࿱…...

自动驾驶3D目标检测综述(三)
前两篇综述阅读理解放在这啦,有需要自行前往观看: 第一篇:自动驾驶3D目标检测综述(一)_3d 目标检测-CSDN博客 第二篇:自动驾驶3D目标检测综述(二)_子流行稀疏卷积 gpu实现-CSDN博客…...

【GESP】C++三级练习 luogu-B3661, [语言月赛202209] 排排
三级知识点一维数组练习,除了应用了数组以外,其余逻辑比较简单,适合初学者。 题目题解详见:https://www.coderli.com/gesp-3-luogu-b3661/ 【GESP】C三级练习 luogu-B3661, [语言月赛202209] 排排队 | OneCoder三级知识点一维数…...

【PPTist】添加PPT模版
前言:这篇文章来探索一下如何应用其他的PPT模版,给一个下拉菜单,列出几个项目中内置的模版 PPT模版数据 (一)增加菜单项 首先在下面这个菜单中增加一个“切换模版”的菜单项,点击之后在弹出框中显示所有的…...

大疆上云api开发
目前很多公司希望使用上云api开发自己的无人机平台,但是官网资料不是特别全,下面浅谈一下本人开发过程中遇到的一系列问题。 本人使用机场为大疆机场2,飞机为M3TD,纯内网使用 部署 链接: 上云api代码. 首先从github上面拉去代码 上云api代码github. 后…...

IDEA2023 SpringBoot整合MyBatis(三)
一、数据库表 CREATE TABLE students (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(100) NOT NULL,age INT,gender ENUM(Male, Female, Other),email VARCHAR(100) UNIQUE,phone_number VARCHAR(20),address VARCHAR(255),date_of_birth DATE,enrollment_date DATE,cours…...

【Apache Paimon】-- 6 -- 清理过期数据
目录 1、简要介绍 2、操作方式和步骤 2.1、调整快照文件过期时间 2.2、设置分区过期时间 2.2.1、举例1 2.2.2、举例2 2.3、清理废弃文件 3、参考 1、简要介绍 清理 paimon (表)过期数据可以释放存储空间,优化资源利用并提升系统运行效率等。本文将介绍如何清理 Paim…...

C语言数据结构——详细讲解 双链表
从单链表到双链表:数据结构的演进与优化 前言一、单链表回顾二、单链表的局限性三、什么是双链表四、双链表的优势1.双向遍历2.不带头双链表的用途3.带头双链表的用途 五、双链表的操作双链表的插入操作(一)双链表的尾插操作(二&a…...

Shell脚本基础(4):条件判断
内容预览 ≧∀≦ゞ Shell脚本基础(4):条件判断声明导语基本的if语句结构数值比较运算符文件测试运算符扩展:使用elif和else使用&&和||结合条件判断小结 Shell脚本基础(4):条件判断 声明…...

在 Swift 中实现字符串分割问题:以字典中的单词构造句子
文章目录 前言摘要描述题解答案题解代码题解代码分析示例测试及结果时间复杂度空间复杂度总结 前言 本题由于没有合适答案为以往遗留问题,最近有时间将以往遗留问题一一完善。 LeetCode - #140 单词拆分 II 不积跬步,无以至千里;不积小流&…...

win10中使用ffmpeg和MediaMTX 推流rtsp视频
在win10上测试下ffmpeg推流rtsp视频,需要同时用到流媒体服务器MediaMTX 。ffmpeg推流到流媒体服务器MediaMTX ,其他客户端从流媒体服务器拉流。 步骤如下: 1 下载MediaMTX github: Release v1.9.3 bluenviron/mediamtx GitHub…...

16. 【.NET 8 实战--孢子记账--从单体到微服务】--汇率获取定时器
这篇文章我们将一起编写这个系列专栏中第一个和外部系统交互的功能:获取每日汇率。下面我们一起来编写代码吧。 一、需求 根据文章标题可知,在这片文章中我们只进行汇率的获取和写入数据库。 编号需求说明1获取每日汇率1. 从第三方汇率API中获取汇率信…...

C#元组详解:创建、访问与解构
在C#中,元组(Tuple)是一种数据结构,用于将多个元素组合成一个单一的对象。元组可以包含不同类型的元素,并且每个元素都有一个指定的位置(索引)。元组在需要临时组合多个值而不想创建自定义类时非…...

wsl2安装
Windows Subsystem for Linux 2 (WSL2) 是 Windows 10 和 Windows 11 中用于运行 Linux 二进制可执行文件的兼容层。WSL2 是 WSL 的最新版本,提供了更快的文件系统性能和完整的系统调用兼容性。本教程将指导你如何在 Windows 系统上安装 WSL2。 前提条件 操作系统要…...

android studio无法下载,Could not GET xxx, Received status code 400
-- 1. 使用下面的地址代替 原地址: distributionUrlhttps\://services.gradle.org/distributions/gradle-6.5-all.zip 镜像地址: distributionUrlhttps\://downloads.gradle-dn.com/distributions/gradle-6.5-all.zips 上面的已经不好用了 https\://mirrors.cloud.tencent.c…...

RUST学习教程-安装教程
文章目录 参考文档安装教程更新卸载 参考文档 https://course.rs/first-try/installation.html 安装教程 Linux或者mac安装教程 curl --proto https --tlsv1.2 https://sh.rustup.rs -sSf | sh安装完成,当出现command not found的时候,需要source一下…...

redis6.0之后的多线程版本的问题
一、redis早期版本和新版本的讨论 这个问题其实有些废话,哪个版本肯定都有不同啊。其实这里主要是提到的网上的大家对Redis6.0中的多线程版本的不同即以前宣传的Redis是单线程程版的,之后变成了多线程版本的。网上对这个讨论非常激烈,反正各…...