javaScript 给图片加水印

news2024/12/23 6:26:14

背景

在很多地方,我们都可以看到,上传图片的时候,图片都会被加上默认的水印,水印的作用主要体现在以下几个方面:

  • 1.版权保护:在商业用途的照片中添加水印可以帮助保护作者的版权,防止他人未经授权使用照片。
  • 2.品牌推广:将商业品牌、商标或公司标志添加到照片中,可以帮助提高品牌知名度和曝光率。
  • 3.防止盗版:添加水印可以防止盗版和未经授权的使用,因为水印会明显表明该照片的版权归原作者所有。
  • 4.标识来源:在社交媒体平台上分享照片时,添加水印可以帮助其他用户识别出照片的来源和作者。
  • 5.识别真伪:对于一些重要的照片或证件,如证书或合同等,加上水印可以帮助识别真伪,防止伪造和篡改。

因此,我们在个人网站进行图片操作时,也可以给它加上自己独特的水印,那么作为一名前端开发,我们该如何实现给图片加上水印呢?

实现

对图片进行处理,我们的首选当然是canvas啦,使用canvas我们可以便捷地对图片进行操作,我们需要操作的图片主要分为以下两种:

  • 1、本地上传的图片
  • 2、线上链接图片

file 转 base64

对于本地上传的图片,我们需要先将其转换成 base64 再进行后续处理:

我们可以通过FileReader来获取图片的 base64,FileReader 是一种异步读取文件机制。

FileReader 提供了如下方法:

  • readAsArrayBuffer(file):按字节读取文件内容,结果用 ArrayBuffer 对象表示

  • readAsBinaryString(file):按字节读取文件内容,结果为文件的二进制串

  • readAsDataURL(file):读取文件内容,结果用 data:url 的字符串形式表示

  • readAsText(file,encoding):按字符读取文件内容,结果用字符串形式表示

  • abort():终止文件读取操作

readAsDataURL 方法会读取指定的 Blob 或 File 对象。并生成 data URl(base64 编码)。这里我们可以使用readAsDataURL来获取上传图片的 base64 编码:

function fileToBase64Async(file) {
  return new Promise((resolve, reject) => {
    let reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = (e) => {
      resolve(e.target.result);
    };
  });
}

使用 canvas 给图片加水印

使用在线图片链接的时候需要注意给图片设置crossOrigin属性(img.setAttribute("crossOrigin",'Anonymous')),不然会出现以下错误:

Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

大概意思是 canvas 无法执行 toDataURL 方法:污染的画布无法输出。受限于 CORS 策略,会存在跨域问题,虽然可以使用图像(比如 append 到页面上)但是绘制到画布上会污染画布,一旦一个画布被污染,就无法提取画布的数据,比如无法使用使用画布 toBlob(),toDataURL(),或 getImageData()方法;当使用这些方法的时候 会抛出一个安全错误。

我们这里可以分为文字水印和图片水印两种:

文字水印

添加文字水印的大致步骤如下:

  • 1、生成一个新的 canvas 画布;
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
  • 2、将现有需要添加水印的图片绘制到画布上;
const ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  • 3、绘制需要添加的水印文本并设置样式。

我们可以使用fillTextstrokeText这两个方法来绘制文字,fillText绘制的是默认的普通实线文本,strokeText绘制的是描边文本,这里我使用了strokeText来进行水印文本绘制。

完整代码如下:

const remFontSize = canvas.width / 35;
ctx.font = "bolder " + remFontSize + "px Verdana";
ctx.textAlign = "center";
ctx.strokeStyle = "#fff";
const name = "@JYeontu";
const spaceH = remFontSize * 0.3;
ctx.strokeText(name, canvas.width / 2, canvas.height - remFontSize - spaceH);
function fillTextToImg(base64) {
  const img = new Image();
  img.src = base64;
  img.setAttribute("crossOrigin", "Anonymous");
  return new Promise((resolve, reject) => {
    img.onload = () => {
      const canvas = document.createElement("canvas");
      canvas.width = img.width;
      canvas.height = img.height;
      const ctx = canvas.getContext("2d");
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      const remFontSize = canvas.width / 35;
      ctx.font = "bolder " + remFontSize + "px Verdana";
      ctx.textAlign = "center";
      /**
        ctx.textAlign = "center|end|left|right|start";
        start:默认,文本在指定的位置开始。
        end:文本在指定的位置结束。
        center:文本的中心在指定的位置。
        left:文本左对齐。
        right:文本右对齐。
        **/
      ctx.strokeStyle = "#fff";
      const name = "@JYeontu";
      const spaceH = remFontSize * 0.3;
      ctx.strokeText(
        name,
        canvas.width / 2,
        canvas.height - remFontSize - spaceH
      );
      resolve(canvas.toDataURL("image/jpeg"));
    };
  });
}

效果如下图,左边为原图,右边为加了文字水印的图片:
image.png

图片水印
//图片转为base64
async function getImgBase64(base64, width = 50) {
  const img = new Image();
  img.src = base64;
  img.setAttribute("crossOrigin", "Anonymous");
  return new Promise((resolve, reject) => {
    img.onload = () => {
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d");
      canvas.width = width;
      canvas.height = (img.height * width) / img.width;
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      resolve(canvas.toDataURL("image/jpeg"));
    };
  });
}
function fillImgToImg(base64, waterMark = imgLink) {
  waterMark =
    "https://img2.baidu.com/it/u=2243573419,589412055&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1684861200&t=7bf0a17ca21ae8ec8aa77b0f98cb4c7e";
  const img = new Image();
  img.src = base64;
  img.setAttribute("crossOrigin", "Anonymous");
  return new Promise((resolve, reject) => {
    img.onload = async () => {
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d");
      canvas.width = img.width;
      canvas.height = img.height;
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      const waterMarkSrc = await getImgBase64(waterMark, 100);
      const waterMarkImg = new Image();
      waterMarkImg.src = waterMarkSrc;
      waterMarkImg.setAttribute("crossOrigin", "Anonymous");
      waterMarkImg.onload = () => {
        ctx.drawImage(
          waterMarkImg,
          canvas.width / 2 - waterMarkImg.width / 2,
          canvas.height - waterMarkImg.height - 10,
          waterMarkImg.width,
          waterMarkImg.height
        );
        resolve(canvas.toDataURL("image/jpeg"));
      };
    };
  });
}

效果如下图,左边为原图,右边为加了图片水印的图片:
image.png

完整代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      img {
        width: 500px;
      }
    </style>
  </head>
  <body>
    <input type="file" id="fileUplodBox" />
    <img alt="原图" id="originPic" />
    <img alt="水印图" id="waterMark" />
  </body>
  <script>
    const imgLink =
      "https://img2.baidu.com/it/u=2048195462,703560066&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1684861200&t=a0c977f68632303e7dac2196e8ad2866";
    document.getElementById("originPic").setAttribute("src", imgLink);
    const fileUplodBox = document.getElementById("fileUplodBox");
    fileUplodBox.addEventListener("change", (e) => {
      const file = e.target.files[0];
      dealFile(file);
    });
    test();
    async function test() {
      const img = await fillImgToImg(imgLink);
      document.getElementById("waterMark").setAttribute("src", img);
    }
    async function dealFile(file) {
      const base64 = await fileToBase64Async(file);
      document.getElementById("originPic").setAttribute("src", base64);
      const img = await fillTextToImg(base64);
      document.getElementById("waterMark").setAttribute("src", img);
    }
    function fileToBase64Async(file) {
      return new Promise((resolve, reject) => {
        let reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = (e) => {
          resolve(e.target.result);
        };
      });
    }
    // 给图片加文字水印
    function fillTextToImg(base64) {
      const img = new Image();
      img.src = base64;
      img.setAttribute("crossOrigin", "Anonymous");
      return new Promise((resolve, reject) => {
        img.onload = () => {
          const canvas = document.createElement("canvas");
          const ctx = canvas.getContext("2d");
          canvas.width = img.width;
          canvas.height = img.height;
          ctx.fillRect(0, 0, canvas.width, canvas.height);
          ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
          const remFontSize = canvas.width / 35;
          ctx.font = "bolder " + remFontSize + "px Verdana";
          ctx.textAlign = "center";
          ctx.strokeStyle = "#fff";
          const uploadTime = new Date();
          const name = "@JYeontu";
          const spaceH = remFontSize * 0.3;
          ctx.strokeText(
            name,
            canvas.width / 2,
            canvas.height - remFontSize - spaceH
          );
          resolve(canvas.toDataURL("image/jpeg"));
        };
      });
    }
    async function getImgBase64(base64, width = 50) {
      const img = new Image();
      img.src = base64;
      img.setAttribute("crossOrigin", "Anonymous");
      return new Promise((resolve, reject) => {
        img.onload = () => {
          const canvas = document.createElement("canvas");
          const ctx = canvas.getContext("2d");
          canvas.width = width;
          canvas.height = (img.height * width) / img.width;
          ctx.fillRect(0, 0, canvas.width, canvas.height);
          ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
          resolve(canvas.toDataURL("image/jpeg"));
        };
      });
    }
    // 给图片加图片水印
    function fillImgToImg(base64, waterMark = imgLink) {
      waterMark =
        "https://img2.baidu.com/it/u=2243573419,589412055&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1684861200&t=7bf0a17ca21ae8ec8aa77b0f98cb4c7e";
      const img = new Image();
      img.src = base64;
      img.setAttribute("crossOrigin", "Anonymous");
      return new Promise((resolve, reject) => {
        img.onload = async () => {
          const canvas = document.createElement("canvas");
          const ctx = canvas.getContext("2d");
          canvas.width = img.width;
          canvas.height = img.height;
          ctx.fillRect(0, 0, canvas.width, canvas.height);
          ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
          const waterMarkSrc = await getImgBase64(waterMark, 100);
          const waterMarkImg = new Image();
          waterMarkImg.src = waterMarkSrc;
          waterMarkImg.setAttribute("crossOrigin", "Anonymous");
          waterMarkImg.onload = () => {
            ctx.drawImage(
              waterMarkImg,
              canvas.width / 2 - waterMarkImg.width / 2,
              canvas.height - waterMarkImg.height - 10,
              waterMarkImg.width,
              waterMarkImg.height
            );
            resolve(canvas.toDataURL("image/jpeg"));
          };
        };
      });
    }
  </script>
</html>

说在后面

🎉这里是JYeontu,喜欢算法,GDCPC打过卡;热爱羽毛球,大运会打过酱油。毕业两年,三年前端开发经验,目前担任H5前端开发,算法业余爱好者,有空会刷刷算法题,平时喜欢打打羽毛球🏸 ,也喜欢写些东西,既为自己记录📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解🙇,写错的地方望指出,定会认真改进😊,在此谢谢大家的支持,我们下文再见🙌。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于LeNet-5的手写数字识别实战

图像识别是计算机视觉最常用的任务之一&#xff0c;几乎所有的有关图像识别的教程都会将MNIST数据集作为入门数据集&#xff0c;因为MNIST数据集是图像识别问题中难度最小、特征差异较为明显的数据集&#xff0c;非常适合作为图像识别入门者的学习案例。本案例使用MNIST数据集&…

最小二乘法求导-公式推导

多元线性回归模型 1. 建立模型&#xff1a;模型函数 Y ^ W T X \hat{Y} W^TX Y^WTX 如果有 n1 条数据&#xff0c;每条数据有 m1 种x因素&#xff08;每种x因素都对应 1 个权重w&#xff09;&#xff0c;则 &#x1f449;已知数据&#xff1a;实际Y值 [ y 0 y 1 y 2 y 3 . …

【c语言小项目】基于easyX实现的《是男人就下一百层》小游戏

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c语言系列专栏&#xff1a;c语言之路重点知识整合 &#x…

论述安科瑞智慧消防在高层建筑信息化管理中的作用

安科瑞 徐浩竣 江苏安科瑞电器制造有限公司 zx acrelxhj 【摘要】为了顺应时代的发展&#xff0c;我们做好信息化时代下的“智慧消防”工作&#xff0c;为“智慧城市”的建设奠定良好的基础。本文主要就“智慧消防”的含义、对如高层建筑等单位进行信息化“智慧消防”管理&a…

CryoEM - 使用 3DMod (IMOD) 评估蛋白质三维结构的质量

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/130867416 IMOD 官网&#xff1a;The IMOD Home Page IMOD is a set of image processing, modeling and display programs used for to…

chatgpt赋能python:PythonShe-全面的SEO工具

Python She - 全面的SEO工具 在当今互联网时代&#xff0c;优化网站的排名对于企业的成功至关重要。Python She是一款可靠的SEO工具&#xff0c;它针对搜索引擎优化提供了全面的解决方案。本文将介绍Python She的功能以及如何使用它来提高您的网站排名。 Python She的功能 P…

从感官沉浸到无边界互操作,细数元宇宙游戏的底层逻辑世界

元宇宙已经不是全宇宙最热的概念了&#xff0c;冷下来之后或许才能踏踏实实落地。 元宇宙是下一代的互联网形态&#xff0c;是三维的“空间互联网”。或者说是大家更希望这是未来的互联网。我们将通过VR虚拟现实头盔、AR增强现实眼镜等一系列设备&#xff0c;体验三维化的互联…

《程序员的炫技代码》

程序员&#xff0c;这个职业总是让人感到神秘而又充满魅力。他们手中的代码常常充满了令人惊叹的炫技操作&#xff0c;让人不禁感叹他们的技术能力之高。在这篇文章中&#xff0c;我想和大家分享一些我所知道的程序员的炫技代码。 一行代码实现斐波那契数列 斐波那契数列是一…

外包公司程序员的水平真的很垃圾吗?

在互联网圈存在着这么一条鄙视链&#xff1a;大厂程序员看不起自研小厂程序员&#xff0c;自研小厂程序员看不起外包程序员&#xff0c;很多人觉得自己进了外包以后简历就被污染了&#xff0c;所以哪怕失业都不愿意进外包…… 实际上&#xff0c;何不食肉糜…… 外包可能还把你…

javaWebssh车辆保养管理系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 java ssh车辆保养管理系统是一套完善的web设计系统&#xff08;系统采用ssh框架进行设计开发&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用 B/S模式开发。开发环境为TOMCAT7.…

Python竖版大屏 | 用pyecharts开发可视化的奇妙探索2

你好&#xff01;我是马哥python说&#xff0c;一名10年程序猿&#xff0c;正在试错用pyecharts开发可视化大屏的非常规排版。 以下&#xff0c;我用8种ThemeType展示的同一个可视化数据大屏&#xff0c;可视化主题是分析淄博烧烤现象。 1、SHINE主题 2、LIGHT主题 3、MACARO…

API测试| 了解API接口测试| API接口测试指南

什么是API&#xff1f; API是一个缩写&#xff0c;它代表了一个 pplication P AGC软件覆盖整个房间。API是用于构建软件应用程序的一组例程&#xff0c;协议和工具。API指定一个软件程序应如何与其他软件程序进行交互。 例行程序&#xff1a;执行特定任务的程序。例程也称为过…

23 # generator 的使用

1、类数组&#xff1a;长的像数组 const likeArray {0: "a",1: "b",2: "c",3: "d",length: 4 };可以 Array.from 将类数组转为数组 Array.from(likeArray) // [ a, b, c, d ]也可以使用拓展运算符&#xff1a;原理就是遍历这个对象…

【LIS的nlogn做法】ABC134 E - Sequence Decomposing

E - Sequence Decomposing (atcoder.jp) 题意&#xff1a; 思路&#xff1a; 让你求一个序列里上升子序列个数 Dilworth定理告诉我们答案就是最长非上升子序列长度 那就是反着用nlogn求最长上升子序列长度 可以当板子用 Code&#xff1a; #include <bits/stdc.h>#d…

死磕测试10余年,呕心整理出了核心知识点已经做成PDF,无私奉献

前言 想在面试、工作中脱颖而出&#xff1f;想在最短的时间内快速掌握软件测试的核心基础知识点&#xff1f;想要成为一位优秀的软件测试工程师&#xff1f;本篇文章能助你一臂之力&#xff01; 目前正值招聘求职旺季&#xff0c;很多同学对一些新技术名词都能侃侃而谈&#…

web自动化测试——定位几秒自动消失的弹窗

问题&#xff1a; 我们在进行web自动化测试时&#xff0c;会遇见需要定位那种几秒自动消失的弹窗中的元素&#xff0c;但是弹窗消失太快&#xff0c;经常会来不及操作。 解决办法&#xff1a; 1.在对应的页面按F12 &#xff0c;然后选择Sources&#xff1b; 2.在页面中进行操…

ISO21434 概述

目录 一、ISO21434 1.1 目的 1.2 ISO21434文档组织结构 二、适用范围 三、引用标准 四、术语和缩写 4.1 术语 4.2 缩写 五、一般考虑 一、ISO21434 1.1 目的 本文件阐述了道路车辆内电气和电子&#xff08;E/E&#xff09;系统工程中的网络安全问题。通过确保对网络安…

day03 MyBatis 核心

mapper接口和原理 之前的持久层组成部分:UserMapper.xmlIUserDAOUserDAOimpl 使用mapper接口:UserMapper.xmlUserMaper接口 mapper接口的好处; 避免持久层里面传入参数错误:以前里面写错了不会报错,只有等到运行代码才能看到错误,第二个参数的类型是Objiect MAPPer使用注意…

GC之查看GC日志

写在前面 本文一起看下如何查看GC日志。 1&#xff1a;环境准备 为了能更模拟真实的业务环境生成GC日志&#xff0c;我们首先来准备一个测试类&#xff0c;详细的注释已经在代码中&#xff0c;如下&#xff1a; import java.util.Random; import java.util.concurrent.TimeU…

从RE到RSE:聊聊无线产品EMC认证测试中的辐射项(上)

无线产品的EMC认证测试里&#xff0c;有两个与辐射发射有关的项目&#xff1a;Radiated Emission&#xff08;RE&#xff09;和Radiated Spurious Emission&#xff08;RSE&#xff09;。 RE和RSE&#xff0c;名字上仅一字之差&#xff0c;测试结果看起来也仿佛孪生哥俩。下面有…