前端图像处理(一)

目录

一、上传

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 表单数据,特别是文件上传 |

示例:

html 复制代码
<body>
  <!-- 运行后页面会弹出 alert(123)-->
  <script src="data:application/javascript,alert(123)"></script>
</body>

base64:二进制数据转为ASCII 字符串

科普:在js中:

btoa() 函数用于将字符串进行 Base64 编码【btoa('alert(123)')】

atob() 函数用于将 Base64 编码的字符串解码回原始字符串【atob('YWxlcnQoMTIzKQ==')】

进入正题:

html 复制代码
	<body>
		<input type="file" />
		<img src="" alt="" id="preview" />
    <script src="./1.base64.js"></script>
	</body>
javascript 复制代码
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格式并添加其他参数,而不是原始的二进制格式,可以参考下:

javascript 复制代码
    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】

html 复制代码
	<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 [Number
				of Years] years of experience in [Your Industry]. I specialize in [Your
				Area of Expertise] and have a strong background in [Relevant Skills or
				Technologies].
			</p>
		</div>
	</body>

三、Canvas

3.1、把canvas图片上传到服务器

javascript 复制代码
  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中绘制和拖动矩形

html 复制代码
	<body>
		<div><input type="color" /></div>
		<canvas></canvas>
		<script src="./canvas.js"></script>
	</body>
javascript 复制代码
//============================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、图片(同色区域)点击变色

html 复制代码
	<body>
		<canvas></canvas>
		<script src="./index.js"></script>
	</body>
javascript 复制代码
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])
    );
} //计算颜色差异

第三个案例总结:

最初使用无穷递归来实现:

javascript 复制代码
    // 递归找相同的像素点(上下左右)
    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 集合避免重复访问已处理过的像素点。

......待更新

相关推荐
y先森8 分钟前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy8 分钟前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu108301891111 分钟前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿1 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡2 小时前
commitlint校验git提交信息
前端
虾球xz3 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇3 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒3 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员3 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js