JS前端基于canvas给图片添加水印,并下载带有水印的图片

news2024/12/25 10:06:05

基于canvas给图片添加水印

  • 实现效果图
  • 图片添加水印的步骤
    • 1.获取图片路径,将图片转换为canvas
    • 2.canvas画布上绘制文字水印
    • 3.水印绘制完成后,将canvas转换为图片格式
    • 4.水印绘制完成后,将canvas下载为图片
  • 完整代码总结
    • 1、在utils.js 封装添加水印的通用方法,将以上方法export导出
    • 2、在页面import 导入方法并调用

实现效果图

在这里插入图片描述

图片添加水印的步骤

  1. 获取图片路径,将图片转换为canvas
  2. canvas画布上绘制文字水印
  3. 预览:水印绘制完成后,将canvas转换为图片格式
  4. 下载:水印绘制完成后,将canvas下载为图片

1.获取图片路径,将图片转换为canvas

/**
 * 图片路径转成canvas
 * @param {图片url} url
 */
async function imgToCanvas(url) {
  // 创建img元素
  const img = document.createElement("img");
  img.src = url;
  img.setAttribute("crossOrigin", "anonymous"); // 防止跨域引起的 Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
  await new Promise((resolve) => (img.onload = resolve));
  // 创建canvas DOM元素,并设置其宽高和图片一样
  const canvas = document.createElement("canvas");
  canvas.width = img.width;
  canvas.height = img.height;
  // 坐标(0,0) 表示从此处开始绘制,相当于偏移。
  canvas.getContext("2d").drawImage(img, 0, 0);
  return canvas;
}

2.canvas画布上绘制文字水印

/**
 * 画布添加水印
 */
const drawWaterMark = ({ canvas, textArray, fontFamily = "microsoft yahei", fontSize, fontcolor = "#dadbdc", rotate = 30, textAlign = "left", density = 2.0 }) => {
  const ctx = canvas.getContext("2d");
  let imgWidth = canvas.width;
  let imgHeight = canvas.height;
  ctx.font = `${fontSize}px ${fontFamily}`;
  ctx.lineWidth = 1;
  ctx.fillStyle = fontcolor;
  ctx.textAlign = textAlign;
  ctx.textBaseline = "middle";
  // //文字坐标
  const maxPx = Math.max(imgWidth, imgHeight);
  const stepPx = Math.floor(maxPx / density);
  let arrayX = [0]; //初始水印位置 canvas坐标 0 0 点
  while (arrayX[arrayX.length - 1] < maxPx / 2) {
    arrayX.push(arrayX[arrayX.length - 1] + stepPx);
  }
  arrayX.push(
    ...arrayX.slice(1, arrayX.length).map((el) => {
      return -el;
    })
  );

  for (let i = 0; i < arrayX.length; i++) {
    for (let j = 0; j < arrayX.length; j++) {
      ctx.save();
      ctx.translate(imgWidth / 2, imgHeight / 2); ///画布旋转原点 移到 图片中心
      // ctx.rotate(-Math.PI / 5);
      ctx.rotate((Math.PI / 120) * -rotate);
      if (textArray.length > 3) {
        //最多显示三行水印,也可以根据需要自定义
        textArray = textArray.slice(0, 3);
      }
      textArray.forEach((el, index) => {
        let offsetY = fontSize * index + 2;
        ctx.fillText(el, arrayX[i], arrayX[j] + offsetY);
      });
      ctx.restore();
    }
  }
};

3.水印绘制完成后,将canvas转换为图片格式

/**
 * canvas转成img
 * @param {canvas对象} canvas
 */
export function canvasToImg({canvas, maxWidth = "100%", maxHeight = "100%"}) {
  // 新建Image对象,可以理解为DOM
  var image = new Image();
  // canvas.toDataURL 返回的是一串Base64编码的URL
  // 指定格式 PNG
  image.src = canvas.toDataURL("image/png");
  image.style.maxHeight = maxHeight;
  image.style.maxWidth = maxWidth;
  return image;
}

4.水印绘制完成后,将canvas下载为图片

/**
 * 下载canves为图片
 * @param {canvas对象} canvas
 * @param {文件名} filename
 */
export function downloadCanves(canvas, filename = "下载") {
  let base64 = canvas.toDataURL("image/png");
  let blob = dataURItoBlob(base64);
  const a = document.createElement("a"); // 动态创建a标签,防止下载大文件时,用户没看到下载提示连续点击
  const url = window.URL.createObjectURL(blob);
  a.href = url;
  a.download = filename;
  a.click();
  window.URL.revokeObjectURL(url);
}

完整代码总结

1、在utils.js 封装添加水印的通用方法,将以上方法export导出

/**
 * 图片路径转成canvas
 * @param {图片url} url
 */
export async function imgToCanvas(url) {
  // 创建img元素
  const img = document.createElement("img");
  img.src = url;
  img.setAttribute("crossOrigin", "anonymous"); // 防止跨域引起的 Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
  await new Promise((resolve) => (img.onload = resolve));
  // 创建canvas DOM元素,并设置其宽高和图片一样
  const canvas = document.createElement("canvas");
  canvas.width = img.width;
  canvas.height = img.height;
  // 坐标(0,0) 表示从此处开始绘制,相当于偏移。
  canvas.getContext("2d").drawImage(img, 0, 0);
  return canvas;
}
/**
 * 画布添加水印
 */
export const drawWaterMark = ({ canvas, textArray, fontFamily = "microsoft yahei", fontSize, fontcolor = "#dadbdc", rotate = 30, textAlign = "left", density = 2.0 }) => {
  const ctx = canvas.getContext("2d");
  let imgWidth = canvas.width;
  let imgHeight = canvas.height;
  ctx.font = `${fontSize}px ${fontFamily}`;
  ctx.lineWidth = 1;
  ctx.fillStyle = fontcolor;
  ctx.textAlign = textAlign;
  ctx.textBaseline = "middle";
  // //文字坐标
  const maxPx = Math.max(imgWidth, imgHeight);
  const stepPx = Math.floor(maxPx / density);
  let arrayX = [0]; //初始水印位置 canvas坐标 0 0 点
  while (arrayX[arrayX.length - 1] < maxPx / 2) {
    arrayX.push(arrayX[arrayX.length - 1] + stepPx);
  }
  arrayX.push(
    ...arrayX.slice(1, arrayX.length).map((el) => {
      return -el;
    })
  );

  for (let i = 0; i < arrayX.length; i++) {
    for (let j = 0; j < arrayX.length; j++) {
      ctx.save();
      ctx.translate(imgWidth / 2, imgHeight / 2); ///画布旋转原点 移到 图片中心
      // ctx.rotate(-Math.PI / 5);
      ctx.rotate((Math.PI / 120) * -rotate);
      if (textArray.length > 3) {
        //最多显示三行水印,也可以根据需要自定义
        textArray = textArray.slice(0, 3);
      }
      textArray.forEach((el, index) => {
        let offsetY = fontSize * index + 2;
        ctx.fillText(el, arrayX[i], arrayX[j] + offsetY);
      });
      ctx.restore();
    }
  }
};

/**
 * canvas转成img
 * @param {canvas对象} canvas
 */
export function canvasToImg({canvas, maxWidth = "100%", maxHeight = "100%"}) {
  // 新建Image对象,可以理解为DOM
  var image = new Image();
  // 指定格式 PNG; canvas.toDataURL 返回的是一串Base64编码的URL
  image.src = canvas.toDataURL("image/png");
  image.style.maxHeight = maxHeight;
  image.style.maxWidth = maxWidth;
  return image;
}
/**
 * 下载canves为图片
 * @param {canvas对象} canvas
 * @param {文件名} filename
 */
export function downloadCanves(canvas, filename = "下载") {
  let base64 = canvas.toDataURL("image/png");
  let blob = dataURItoBlob(base64);
  const a = document.createElement("a"); // 动态创建a标签,防止下载大文件时,用户没看到下载提示连续点击
  const url = window.URL.createObjectURL(blob);
  a.href = url;
  a.download = filename;
  a.click();
  window.URL.revokeObjectURL(url);
}

2、在页面import 导入方法并调用

<template>
  <!-- 水印图片预览 -->
    <div class="content">
      <div class="imageBox" id="imageBox"></div>
      <el-button type="primary" @click="downFile" style="margin-top: 10px; width: 100px">下 载</el-button>
    </div>
</template>

<script>
import {  drawWaterMark, imgToCanvas, canvasToImg, downloadCanves } from "@/utils/fileUtils.js";
export default {
  data() {
    return {
      fileUrl:"XXXX.jpg",
      fontSize: 50,
      objmsg: {
        canvas: null, //canvas  [必传]
        textArray: ["水印图片", "2022-01-01"], // /水印文字 数组类型  最大三行  [必传]
        fontFamily: "fangsong", // 默认 microsoft yahei
        fontSize: 30, //字体大小 默认 #dadbdc
        fontcolor: "#fff", //字体颜色   默认 #dadbdc
        rotate: 30, //旋转角度   数字类型
        textAlign: "center", //水印文字居中方式:left center right  默认 left
        density: 3.0, // 稠密度
      },
    };
  },
  mounted() {
    this.drawWaterMarkFn();// 添加水印
  },
  methods: {
    // 图片添加水印
    async drawWaterMarkFn() {
      // 1.图片路径转成canvas
      const tempCanvas = await imgToCanvas(this.fileUrl);
      this.objmsg.canvas = tempCanvas 
      // 2.canvas添加水印
      drawWaterMark(this.objmsg);
      // 3.canvas转成img
      const waterImage = await canvasToImg({
        canvas: tempCanvas,
        maxWidth: "650px", // 最大宽度
        maxHeight: "650px", // 最大高度
      });
      // 查看效果
      var Box = document.getElementById("imageBox");
      Box.appendChild(waterImage);
    },
    // 下载添加水印的图片
    async downFile() {
      // 1.图片路径转成canvas
      const tempCanvas = await imgToCanvas(this.fileUrl);
      this.objmsg.canvas = tempCanvas 
      // 2.canvas添加水印
      await drawWaterMark(this.objmsg);
      // 3.下载加水印后的图片
      downloadCanves(tempCanvas,  "水印图片下载");
    },
  },
};
</script>

<style lang="scss" scoped>
.waterMakerinfo {
  .content {
    text-align: center;
    ::v-deep .imageBox {
      max-height: 650px;
      min-height: 500px;
      .img {
        width: 100%;
        height: 100%;
      }
    }
  }

  ::v-deep {
    .el-dialog__footer {
      text-align: right !important;
      position: absolute;
      width: 200px;
      top: unset;
      right: 20px;
      bottom: 0px;
      padding: 10px !important;
      padding-left: 20px !important;
      background-color: transparent !important;
      z-index: 99;
    }
  }
}
</style>

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

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

相关文章

POE交换机全方位解读(中)

POE供电距离到底怎么算 只针对符合IEEE802.3af/at 标准PoE设备 ① 网线对供电距离的影响 首先我们先来看下表IEEE802.af和IEEE802.3at标准中对Cat5e网线要求&#xff1a; 说明&#xff1a;Type 1 value和Type 2 value 分别指IEEE802.3af和IEEE802.3at的要求。 从表中可以看出&a…

PCB电路板单面板和双面板的区别和共同点

PCB电路板可以分为单面板、双面板和多面板&#xff0c;我们常用的主要是单面板和双面板&#xff0c;那么单面板和双面板有哪些区别呢&#xff1f;在了解二者区别前&#xff0c;沐渥小编先给大家介绍一下什么是单面板和双面板。 单面板是指单面的线路板&#xff0c;元器件在一面…

如何实现报表集成?(四)——权限集成

在上一篇&#xff0c;我们介绍了报表工具的资源集成&#xff0c;基本知道了报表工具链接、模块、页面和移动端如何实现集成。 这一篇&#xff0c;我们看下如何做权限集成。使用第三方系统的资源权限验证 实际上往往存在多个系统需要统一权限认证&#xff0c;用户要求将某个系统…

PixelLib图像分割

文章目录前言一、PixelLib依赖安装二、实例模型训练前言 图像分割就是把图像分成若干个特定的、具有独特性质的区域并提出感兴趣目标的技术和过程。它是由图像处理到图像分析的关键步骤。 传统的图像分割方法主要分以下几类&#xff1a;基于阈值的分割方法、基于区域的分割方…

Mybatis核心原理梳理

文章目录Mybatis的简单使用Mybatis组件名词介绍Mybatis主要工作流程Mybatis如何控制事务Mybatis中事务的生命周期一二级缓存分别如何生效一二级缓存分别如何失效一级缓存的实体可能会被修改Mybatis中的已经存在PooledDataSource连接池为啥还选择Durid等为啥连接close之后被没有…

如何获取 WWDC 视频对应的官方源代码?

零 概览 每年的 WWDC&#xff08;The Apple Worldwide Developers Conference&#xff09; 是 Apple 开发者的盛大节日&#xff0c;我们可以从 WWDC 海量官方视频中学到大量的知识。 不过&#xff0c;有些视频仅包含一些“惨不忍睹”&#xff08;由于网络质量差等原因&#…

【C++】C++ 入门(二)(引用)

目录 一、前言 二、引用 1、引用的概念 2、引用特性 3、使用场景 3.1、做参数 3.2、做返回值 4、传值、传引用效率比较 值和引用作为参数的性能比较 值和引用作为返回值类型的性能比较 5、常引用 6、引用和指针的区别 一、前言 上一篇文章我们讲解了 C 的命名空间…

IDEA快速生成实体类(加注释)

步骤&#xff1a; 1、点击右侧的datesource图标&#xff0c;要是没有该图标&#xff0c;请去自行百度 2、点击 号 3、选择 datasource 4、选择 mysql 1、填写一个连接名&#xff0c;随便填什么都行 2、不用选择&#xff0c;默认就行 3、填写数据库连接的 IP地址&#xff0c;比…

Android 时间工具类

最近总结了一下时间相关的用法&#xff0c;如下。 1、日期转换为字符串 默认"yyyy-MM-dd HH:mm:ss" 2、任意类型日期字符串转时间 3、获取当前对应格式的日期 4、获取当前对应格式的日期 默认"yyyyMMddHHmmssSSS" 5、计算该天是星期几 6、获取星期几…

XSS - 进阶篇(蓝莲花的基本使用)

数据来源 本文仅用于信息安全的学习&#xff0c;请遵守相关法律法规&#xff0c;严禁用于非法途径。若观众因此作出任何危害网络安全的行为&#xff0c;后果自负&#xff0c;与本人无关。 xss漏洞接收平台-蓝莲花&#xff1a; 1&#xff09;下载并安装Phpstudy&#xff08;安…

分享157个ASP源码,总有一款适合您

ASP源码 分享157个ASP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 157个ASP源码下载链接&#xff1a;https://pan.baidu.com/s/1_IF9pFQX4NM-kmJyIAGBQQ?pwdcb55 提取码&#x…

RBAC简介

RBAC BAC基于角色的访问控制&#xff0c;RBAC认为权限授权的过程可以抽象地概括为&#xff1a;Who是否可以对What进行How的访问操作 RBAC简介 基于角色的权限访问控制模型 在RBAC模型里面&#xff0c;有3个基础组成部分&#xff0c;分别是&#xff1a;用户、角色和权限。RB…

微信公众号小程序怎么做?

​微信公众号小程序在当下已经成为人们日常生活中不可或缺的工具&#xff0c;在用户体验方面也做得很好&#xff0c;不仅可以实现沟通和交流&#xff0c;还可以通过微信公众号进行在线预约服务。那么关于微信公众号小程序怎么做&#xff0c;下面就给大家说说。 1、注册微信公众…

Cadence PCB仿真 使用 Allegro PCB SI 元器件端口设置的PDN分析功能介绍图文教程

🏡《总目录》   🏡《分目录》 目录 1,概述2,启动方法3,功能介绍3.1,元器件设置列表(Device)3.2,端口设置列表(Ports)4,总结1,概述 在进行PDN分析时需要对电源网络涉及到的所有元器件的指定端口的参数进行配置。本文介绍PDN网络元器件端口设置的功能。 2,启动…

【寒假day3】leetcode刷题

&#x1f308;一、选择题 ❤第1题&#xff1a;关于重载函数,&#xff08; &#xff09;说明是正确的。 A: 函数名相同&#xff0c;参数类型或个数不同 B: 函数名相同&#xff0c;返回值类型不同 C: 函数名相同&#xff0c;函数内部实现不同 D: 函数名称不同答案&#xff1a…

数据挖掘-特征选择方法:方差过滤,相关性过滤

目录特征选择1、Filter过滤法方差过滤1&#xff0c;消除方差为0的特征2&#xff0c;只留下一半的特征3&#xff0c;特征是二分类时2、相关性过滤法2.1 卡方过滤2.2 F检验2.3 互信息法3、 Embedded嵌入法4、Wrapper包装法5、总结特征选择 数据预处理完成后&#xff0c;就进入特…

常见网络报文数据包格式

当我们应用程序用TCP传输数据的时候&#xff0c;数据被送入协议栈中&#xff0c;然后逐个通过每一层&#xff0c;知道最后到物理层数据转换成比特流&#xff0c;送入网络。而再这个过程中&#xff0c;每一层都会对要发送的数据加一些首部信息。整个过程如下图。以太网帧格式以太…

Mybatis框架(二)再遇Mybatis之Mybatis配置文件与映射文件

本文是本人专栏【Java开发后端系列框架】里的文章&#xff0c;文章根据各框架官网与网上资料加上本人工作经验&#xff0c;进行修改总结发布在这个专栏&#xff0c;主要目的是用于自我提升&#xff0c;不用于获取利益。如果系列文章能到帮到您本人将感到荣幸&#xff0c;如果有…

2023年3月5日DAMA-CDGA/CDGP数据治理认证考试报名入口

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

【每日阅读】前端进阶知识点(一)

如何更改网页中语言属性值 声明当前语言类 html标签更改属性值 lang属性中不区分大小写 en-us en-US 一致 具体可使用 window,document.querySelector(“html”)?.setAttribute(“lang”,newValue); qs库 qs是一个流行的查询参数序列化和解析库。可以将一个普通的object序列…