前端图像处理(三)

目录

一、大文件分片

二、图片添加防篡改水印

2.1、主图+水印图(vue2)

2.2、主图+文字(vue3)

一、大文件分片

并行传输:多个数据块可以同时并行传输,利用多线程或多路复用技术提高传输效率;
减少延迟:通过将大文件分割成小块,可以减少单次传输的数据量,从而减少延迟;

以文件上传为例:

根据文件总大小将它分为N组线程,分割后按照下标排序整理,通过Promise.all拿到所有分组数据

//==========================1、index.js=====================
import { cutFile } from "./cutFile.js";
const inpFile = document.querySelector('input[type="file"]');
inpFile.onchange = async (e) => {
	const file = e.target.files[0];
	const chunks=await cutFile(file);
	console.log(chunks,'所有分块的数据');
};
//=======================2、cutFile.js============================
const CHUNK_SIZE = 1024 * 1024 * 5; //5MB
// hardwareConcurrency: 返回当前设备的逻辑处理器数量(即 CPU 核心数)
const THREAD_COUNT = navigator.hardwareConcurrency || 4; //最低4个线程
export function cutFile(file) {
  return new Promise((resolve) => {
    const chunkCount = Math.ceil(file.size / CHUNK_SIZE); // 分块的数量
    const threadChunkCount = Math.ceil(chunkCount / THREAD_COUNT); // 总共分为几组线程
    const result = new Array(chunkCount); // 初始化结果数组
    let finishCount = 0;

    for (let i = 0; i < THREAD_COUNT; i++) {
      // 创建一个线程,并分配任务
      const worker = new Worker("./worker.js", {
        type: "module",
      });
      let end = (i + 1) * threadChunkCount;
      const start = i * threadChunkCount;
      if (end > chunkCount) {
        end = chunkCount;
      }
      worker.postMessage({
        file,
        CHUNK_SIZE,
        startChunkIndex: start,
        endChunkIndex: end,
      });
      worker.onmessage = (e) => {
        // result.push(...e.data)  为啥不直接push ?进程完成时间不确定,会导致顺序错乱
        // 将结果放入 result 数组的正确位置:按照下标依次放入
        for (let j = start; j < end; j++) {
          result[j] = e.data[j - start];
        }
        worker.terminate();
        finishCount++;
        if (finishCount === THREAD_COUNT) {
          // 所有线程结束后解决 Promise
          resolve(result);
        }
      };
    }
  });
}
//======================3、worker.js========================
import { creatChunk } from "./creatChunk.js";
onmessage = async (e) => {
  const {
    file,
    CHUNK_SIZE,
    startChunkIndex: start,
    endChunkIndex: end,
  } = e.data;
  const proms = [];
  for (let i = start; i < end; i++) {
    proms.push(creatChunk(file, i, CHUNK_SIZE));
  }
  const chunks = await Promise.all(proms);
  postMessage(chunks); // 发送所有分块的数据
};
//======================4、creatChunk.js========================
//SparkMD5 是一个用于计算 MD5 哈希值的库,它可以处理大文件而不会冻结浏览器界面
// import SparkMD5 from "./sparkmd5.js";
export function creatChunk(file, index, chunkSize) {
	return new Promise((resolve) => {
		const start = index + chunkSize;
		const end = start + chunkSize;//计算当前块的起始和结束索引
		// const spark = new SparkMD5.ArrayBuffer();//创建了一个新的 SparkMD5 实例
		const fileReader = new FileReader();//FileReader 对象用于读取文件内容
		const blob = file.slice(start, end);//从原始文件中切出当前块的 Blob 对象
		fileReader.onload = (e) => {
			// spark.append(e.target.result);
			resolve({
				start,
				end,
				index,
				// hash: spark.end(),
				blob,
				data: e.target.result,
			});
		};
		fileReader.readAsArrayBuffer(blob);//开始读取文件块的内容,并将结果作为 ArrayBuffer
	});
}

二、图片添加防篡改水印

2.1、主图+水印图(vue2)

    addWatermarkToImage(baseImageUrl, watermarkUrl, callback) {
      fetch(baseImageUrl)
        .then((response) => response.blob())
        .then((blob) => {
          const baseImage = new Image()
          baseImage.onload = () => {
            const canvas = document.createElement('canvas')
            const ctx = canvas.getContext('2d')
            canvas.width = baseImage.width
            canvas.height = baseImage.height
            ctx.drawImage(baseImage, 0, 0, baseImage.width, baseImage.height)

            fetch(watermarkUrl)
              .then((response) => response.blob())
              .then((watermarkBlob) => {
                const watermark = new Image()
                watermark.onload = () => {
                  // 计算水印的位置(中间)
                  const x = (baseImage.width - watermark.width) / 2
                  const y = (baseImage.height - watermark.height) / 2

                  // 绘制水印图片
                  ctx.globalAlpha = 0.2 // 设置水印透明度
                  ctx.drawImage(
                    watermark,
                    x,
                    y,
                    watermark.width,
                    watermark.height
                  )
                  canvas.toBlob(callback, 'image/png') // 将带水印的图片转换为 Blob 对象
                }
                watermark.src = URL.createObjectURL(watermarkBlob)
              })
          }
          baseImage.src = URL.createObjectURL(blob)
        })
    },
//==========================================添加水印================================
addMark(){
       this.addWatermarkToImage(
            this.img_url,
            this.waterMarkImg,
            (watermarkedImageUrl) => {
              const url = URL.createObjectURL(watermarkedImageUrl)
              console.log(watermarkedImageUrl, '结果',url)
            }
          )
}

2.2、主图+文字(vue3)

index.vue页面:

<template>
  <div class="container">
    <waterMark text="版权所有" style="background: #FF9900">
      <div class="content"></div>
    </waterMark>
    <waterMark text="禁止转载" style="background: #28c840">
      <div class="content"></div>
    </waterMark>
  </div>
</template>
<script setup lang="ts">
import waterMark from './watermark.vue'
</script>
<style lang="scss" scoped>
.container {
  width: 100%;
  height: 100%;
  display: flex;
}
</style>

watermark.vue组件:

<template>
  <div ref="parentRef" class="watermark-container">
    <slot></slot>
  </div>
</template>
<script setup>
import { onMounted, ref, defineProps, onUnmounted } from 'vue'
import useWaterMarkBg from './useWaterMarkBg.js'
const parentRef = ref()
const props = defineProps({
  text: {
    type: String,
    require: true,
    default: 'watermark',
  },
  fontSize: {
    type: Number,
    default: 40,
  },
  gap: {
    type: Number,
    default: 20,
  },
})
const bg = useWaterMarkBg(props)
let div
// 重置水印
function resetWatermark() {
  if (!parentRef.value) {
    return
  }
  if (div) {
    div.remove()
  }
  const { base64, size } = bg.value
  console.log('Generated watermark:', base64) // 查看 base64 是否有效
  div = document.createElement('div')
  div.style.position = 'absolute'
  div.style.backgroundImage = `url(${base64})`
  div.style.backgroundSize = `${size}px ${size}px`
  div.style.backgroundRepeat = 'repeat'
  div.style.zIndex = 999
  div.style.inset = 0
  parentRef.value.appendChild(div) //这样是为了防止从浏览器修改
}
const ob = new MutationObserver((entries) => {
  for (const entry of entries) {
    //  删除
    for (const dom of entry.removedNodes) {
      if (dom === div) {
        resetWatermark()//如果有人在浏览器删除这个盒子,此时立即重新画一次
        return
      }
    }
    // 修改属性
    if (entry.target === div) {
        resetWatermark()//如果有人在浏览器修改盒子属性,此时立即重新画一次
        return
      }
  }
})
onMounted(() => {
  resetWatermark()
  ob.observe(parentRef.value, {
    childList: true,//观察子节点的变化(添加、删除节点)
    subtree: true,//观察整个子树(不仅仅是直接子节点,所有后代节点
    attributes: true,//观察属性的变化(例如 div 的属性发生变化时)
  })
})
onUnmounted(() => {
  ob.disconnect()
})
</script>
<style lang="scss" scoped>
.watermark-container {
  width: 400px;
  height: 400px;
  position: relative;
  z-index: 1; /* 确保水印不会覆盖其他内容 */
  margin-right: 20px;
}
</style>

其中:

MutationObserver 用于监听 DOM 树的变化
使用 ob.observe() 开始监听父容器(parentRef.value)上的变化

useWaterMarkBg.js代码:

import { computed } from 'vue';
export default function useWaterMarkBg(props) {
  return computed(() => {
    const canvas = document.createElement('canvas');
    const devicePixelRatio = window.devicePixelRatio || 1;
    const fontSize = props.fontSize * devicePixelRatio;
    const font = `${fontSize}px serif`;
    const ctx = canvas.getContext('2d');
    ctx.font = font;
    const { width } = ctx.measureText(props.text);
    const canvasSize = Math.max(100, width) + props.gap * devicePixelRatio;
    canvas.width = canvasSize;
    canvas.height = canvasSize;
    ctx.translate(canvas.width / 2, canvas.height / 2);
    ctx.rotate((Math.PI / 180) * -45);
    ctx.fillStyle = 'rgba(0,0,0,0.3)';
    ctx.font = font;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(props.text, 0, 0);
    return {
      base64: canvas.toDataURL(),
      size: canvasSize / devicePixelRatio,
    };
  });
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值