农妇山泉一亩田 发表于 2024-11-23 23:29:23

前端图像处置惩罚(一)

目录
一、上传
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==')】
进入正题:
https://i-blog.csdnimg.cn/direct/2bdcaa9ddd394c018bf73c608b3a4a44.png
        <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;//多文件,所以是数组
        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】

https://i-blog.csdnimg.cn/direct/d78b6e5b8ed642418168760ff8652bee.png
        <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 , and I am a with [Number
                                of Years] years of experience in . I specialize in [Your
                                Area of Expertise] and have a strong background in [Relevant Skills or
                                Technologies].
                        </p>
                </div>
        </body> 三、Canvas

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

let base64 = canvas.toDataURL()//canvas指canvas格式的图片
let imgUrlBlob = dataURLToBlob(base64)
var file = new window.File(, 'image.png', { type: 'image/png' })
let fd = new FormData()
fd.append('image', file) 3.2、在canvas中绘制和拖动矩形

https://i-blog.csdnimg.cn/direct/7fb1c02d209841278518a3ca00f3a4e2.png
https://i-blog.csdnimg.cn/direct/5223d7f4d5584c45b484eb118e43bec3.png
        <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.minX &&
                        x <= shapes.maxX &&
                        y >= shapes.minY &&
                        y <= shapes.maxY
                ) {
                        return shapes;
                }
        }
        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、图片(同色区域)点击变色

https://i-blog.csdnimg.cn/direct/eadeb4ebb69f46e1ad2abbf3304985f8.png
        <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 = ; // 改变后颜色为绿色,透明度为不透明
    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,
      imgData,
      imgData,
      imgData,
    ]; //分别对应:r、g、b、a
}

// 使用BFS来代替递归
function changeColor(x, y, targetColor, imgData, clickColor, visited) {
    const queue = []; // 用队列保存待处理的像素点
    const directions = [, [-1, 0], , ]; // 上下左右四个方向
    visited.add(`${x},${y}`); // 初始像素点标记为已访问

    while (queue.length > 0) {
      const = 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 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(); // 将该像素点加入队列
                }
            }
      }
    }
}

function diff(color1, color2) {
    return (
      Math.abs(color1 - color2) +
      Math.abs(color1 - color2) +
      Math.abs(color1 - color2) +
      Math.abs(color1 - color2)
    );
} //计算颜色差异
第三个案例总结:
最初使用无穷递归来实现:
    // 递归找相同的像素点(上下左右)
    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 聚集避免重复访问已处置惩罚过的像素点。
......待更新

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 前端图像处置惩罚(一)