前端图像处理(一)

目录

一、上传

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/pngPNG 图像格式,支持透明度
 音频类型audio/mpegMP3 音频格式
 应用类型application/jsonJSON 数据格式,用于数据交换
多部分类型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 集合避免重复访问已处理过的像素点。

......待更新

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

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

相关文章

推荐一款龙迅HDMI2.0转LVDS芯片 LT6211UX LT6211UXC

龙迅的HDMI2.0转LVDS芯片LT6211UX和LT6211UXC是两款高性能的转换器芯片&#xff0c;它们在功能和应用上有所差异&#xff0c;同时也存在一些共同点。以下是对这两款芯片的详细比较和分析&#xff1a; 一、LT6211UX 主要特性&#xff1a; HDMI2.0至LVDS和MIPI转换器。HDMI2.0输…

51单片机从入门到精通:理论与实践指南入门篇(二)

续51单片机从入门到精通&#xff1a;理论与实践指南&#xff08;一&#xff09;https://blog.csdn.net/speaking_me/article/details/144067372 第一篇总体给大家在&#xff08;全局&#xff09;总体上讲解了一下51单片机&#xff0c;那么接下来几天结束详细讲解&#xff0c;从…

STM32C011开发(3)----Flash操作

STM32C011开发----3.Flash操作 概述硬件准备视频教学样品申请源码下载参考程序生成STM32CUBEMX串口配置堆栈设置串口重定向FLASH数据初始化FLASH 读写演示 概述 STM32C011 系列微控制器内置 Flash 存储器&#xff0c;支持程序存储与数据保存&#xff0c;具备页面擦除、双字写入…

IDEA无法创建java8、11项目创建出的pom.xml为空

主要是由于Spring3.X版本不支持JDK8&#xff0c;JDK11&#xff0c;最低支持JDK17 解决的话要不就换成JDK17以上的版本&#xff0c;但是不太现实 另外可以参考以下方式解决 修改spring初始化服务器地址为阿里云的 https://start.aliyun.com/

【Unity3D】创建自定义字体

前置准备 如图所示&#xff0c;项目工程中需要用文件夹存储0-9的Sprite图片。 使用流程 直接右键存放图片的文件夹&#xff0c;选择【创建自定义字体】&#xff0c;之后会在脚本定义的FontOutputPath中生成材质球和字体。 源码 using System; using System.Collections.Gene…

logminer挖掘日志归档查找问题

--根据发生问题时间点查找归档文件 select first_time,NAME from gv$archived_log where first_time>2016-03-15 17:00:00 and first_time<2016-03-15 21:00:00; 2016-03-15 17:23:55 ARCH/jxdb/archivelog/2016_03_15/thread_1_seq_41588.4060.906577337 2016-03-15 17:…

电商项目高级篇06-缓存

电商项目高级篇06-缓存 1、docker下启动redis2、项目整合redis 缓存 流程图&#xff1a; data cache.load(id);//从缓存加载数据 If(data null){ data db.load(id);//从数据库加载数据 cache.put(id,data);//保存到 cache 中 } return data;在我们的单体项目中可以用Map作…

如何使用GCC手动编译stm32程序

如何不使用任何IDE&#xff08;集成开发环境&#xff09;编译stm32程序? 集成开发环境将编辑器、编译器、链接器、调试器等开发工具集成在一个统一的软件中&#xff0c;使得开发人员可以更加简单、高效地完成软件开发过程。如果我们不使用KEIL,IAR等集成开发环境&#xff0c;…

一个专为云原生环境设计的高性能分布式文件系统

大家好&#xff0c;今天给大家分享一款开源创新的分布式 POSIX 文件系统JuiceFS&#xff0c;旨在解决海量云存储与各类应用平台&#xff08;如大数据、机器学习、人工智能等&#xff09;之间高效对接的问题。 项目介绍 JuiceFS 是一款面向云原生设计的高性能分布式文件系统&am…

Vue-TreeSelect组件最下级隐藏No sub-options

问题&#xff1a;最下级没有数据的话&#xff0c;去除No sub-options信息 为什么没下级&#xff0c;会展示这个&#xff1f; 整个树形结构数据都是由后端构造好返回给前端的。默认子类没数据的话&#xff0c;children是一个空数组。也就是因为这最下级的空数组&#xff0c;导致…

k8s集群增加nfs-subdir-external-provisioner存储类

文章目录 前言一、版本信息二、本机安装nfs组件包三、下载nfs-subdir-external-provisioner配置文件并进行配置1.下载文件2.修改配置 三、进行部署备注&#xff1a;关于镜像无法拉取问题的处理 前言 手里的一台服务器搭建一个单点的k8s集群&#xff0c;然后在本机上使用nfs-su…

C语言数据结构-链表

C语言数据结构-链表 1.单链表1.1概念与结构1.2结点3.2 链表性质1.3链表的打印1.4实现单链表1.4.1 插入1.4.2删除1.4.3查找1.4.4在指定位置之前插入或删除1.4.5在指定位置之后插入或删除1.4.6删除指定位置1.4.7销毁链表 2.链表的分类3.双向链表3.1实现双向链表3.1.1尾插3.1.2头插…

【SpringCloud详细教程】-04-服务容错--Sentinel

精品专题&#xff1a; 01.《C语言从不挂科到高绩点》课程详细笔记 https://blog.csdn.net/yueyehuguang/category_12753294.html?spm1001.2014.3001.5482 02. 《SpringBoot详细教程》课程详细笔记 https://blog.csdn.net/yueyehuguang/category_12789841.html?spm1001.20…

【Python中while循环】

一、深拷贝、浅拷贝 1、需求 1&#xff09;拷贝原列表产生一个新列表 2&#xff09;想让两个列表完全独立开&#xff08;针对改操作&#xff0c;读的操作不改变&#xff09; 要满足上述的条件&#xff0c;只能使用深拷贝 2、如何拷贝列表 1&#xff09;直接赋值 # 定义一个…

在 Mac(ARM 架构)上安装 JDK 8 环境

文章目录 步骤 1&#xff1a;检查系统版本步骤 2&#xff1a;下载支持 ARM 的 JDK 8步骤 3&#xff1a;安装 JDK步骤 4&#xff1a;配置环境变量步骤 5&#xff1a;验证安装步骤 6&#xff1a;注意事项步骤7&#xff1a;查看Java的安装路径 在 Mac&#xff08;ARM 架构&#xf…

对比C++,Rust在内存安全上做的努力

简介 近年来&#xff0c;越来越多的组织表示&#xff0c;如果新项目在技术选型时需要使用系统级开发语言&#xff0c;那么不要选择使用C/C这种内存不安全的系统语言&#xff0c;推荐使用内存安全的Rust作为替代。 谷歌也声称&#xff0c;Android 的安全漏洞&#xff0c;从 20…

小程序基础:流程。

一、前言 该文章是个人的学习笔记&#xff0c;是学习了黑马程序的微信小程序开发视频后写的笔记&#xff0c;将老师所讲的内容重点汇总起来&#xff0c;目的是为了方便自己日后回顾&#xff0c;同时也方便大家学习和了解小程序的开发 想要入门小程序&#xff0c;那么看这一篇文…

【漏洞复现】CVE-2020-13925

漏洞信息 NVD - CVE-2020-13925 Similar to CVE-2020-1956, Kylin has one more restful API which concatenates the API inputs into OS commands and then executes them on the server; while the reported API misses necessary input validation, which causes the hac…

数据结构 (11)串的基本概念

一、串的定义 1.串是由一个或者多个字符组成的有限序列&#xff0c;一般记为&#xff1a;sa1a2…an&#xff08;n≥0&#xff09;。其中&#xff0c;s是串的名称&#xff0c;用单括号括起来的字符序列是串的值&#xff1b;ai&#xff08;1≤i≤n&#xff09;可以是字母、数字或…

LLM PPT Translator

LLM PPT Translator 引言Github 地址UI PreviewTranslated Result Samples 引言 周末开发了1个PowerPoint文档翻译工具&#xff0c;上传PowerPoint文档&#xff0c;指定想翻译的目标语言&#xff0c;通过LLM的能力将文档翻译成目标语言的文档。 Github 地址 https://github.…