JS中图片的处理与合成(生成水印)

以美图的图片处理为例子,学习JS中对图片的处理。处理图片时需要使用canvas

1 图片的跨域

使用canvas处理图片,首先需要加载图片,如果是在线图片需要针对图片进行跨域的处理。具体的处理方法是:

在图片服务器上设置跨域头,并且在前端加载图片之前将<img>标签的crossOrigin设置为*

这样做的原因是:

尽管不通过CORS就可以在画布中使用图片,但是这会污染画布。一旦画布被污染,你就无法读取其数据。例如,你不能再使用画布的toBlob(), toDataURL()getImageData()方法,调用它们会抛出安全错误。(MDN Web docs)

还要注意的是:
1. crossOrgin只有在线上图片时才设置,本地图片或者是base64图片不能设置,否在某些系统会报错,导致图片加载失败
2. 当项目为本地包环境时,例如内置于App中时,crossOrigin值无效,webview的安全机制会导致无论该值设置与否,都会报跨域的错误。解决办法是:需要将所有图片转换成base64才能正确绘制;
3. crossOrigin值一定要在图片加载之前设置,即为<img>赋值src之前进行设置,否则无效。

我的图片是存储在七牛云的,七牛对图片的跨域进行了默认的处理

七牛跨域

所以,一个Promise话的图片加载函数:

function loadImage(src) {
  return new Promise((resolve, reject) = > {
    let img = new Image();
    // 在线图片设置crossOrigin跨域
    if (src.indexOf(src) === 0) {
      img.crossOrigin = '*';
    }
    img.src = src;
    img.onload = () = > {
      resolve(img)
    };
    img.onerror = () = > {
      reject(new Error('图片解析失败'))
    }
  })
}

2 图片的缩放

图片缩放最常见的场景是图片的压缩,在保证清晰的前提下合理的缩小图片尺寸,能大大的降低图片的大小。

新建一个canvas画布,将宽高设置为需要压缩的尺寸,要注意的是需要保证图片的比例,所以canvas的尺寸是通过计算得出的。

2.1 drawImage

// 创建图片
const src = 'http://pblesaqy5.bkt.clouddn.com/18-7-27/52991435.jpg';
const img = await loadImage(src);

// 计算画布尺寸
const canvas = document.querySelector('#canvas');
const imgRatio = img.naturalWidth / img.naturalHeight;
const ctxWidth = 300;
const ctxHeight = ctxWidth / imgRatio;
canvas.width = ctxWidth;
canvas.height = ctxHeight;

// 绘制图片
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, ctxWidth, ctxHeight);

ctx.drawImage(image, dx, dy, dw, dh)来讲图片画入canvas中,这个方法最多可以接收9个参数, 实现压缩,只需要使用其中的5个参数即可, 其余参数在其它部分使用到时再做详解:

  • image : 需要绘制的图片源,需要接收已经加载完成的HTMLImageElement,HTMLCanvasElement或者HTMLVideoElement;
  • dx / dy : 相对于画布左上角的绘制起始点坐标;
  • dw / dh : 绘制的宽度和高度,宽高比例并不锁定,可使图片变形;

2.2 toDataURL

然后使用canvas.toDataURL(type, quality):方法将图片转为base64格式的图片,

其中:
1. type: 图片格式, 一般可以使用image/png或者image/jpeg, 当图片不包含透明时,建议使用jpeg,可使导出的图片大小减小很多;
2. quality: 图片质量,可使用0~1之间的任意值;经过测试,该值设置成0.9时较为合适,可以有效减小图片文件大小且基本不影响图片清晰度,导出后的base64既为压缩后的图片

// 转为base64
const b64 = canvas.toDataURL('image/jpeg', 1);

3 将图片转为Base64

我们常用的图片上传功能,我们使用的是原生的<input type="file">标签,此时获取到的是File格式的图片,图片的格式各异且尺寸很大,我们应该压缩处理后再使用。

三种方式,一种方式就是上面提到的,利用canvas来实现,另一种是利用fileReader实现,还有一种是使用createObjectURL

3.1 fileReader

// 利用fileReader预览图片
function previewImage(inputEle, preview) {
  inputEle.onchange = function(e) {
    const file = e.target.files[0];
    if (!file) {
      preview.src = '';
      return
    }
    const fileReader = new FileReader();
    fileReader.readAsDataURL(file);
    fileReader.onload = t = > {
      preview.src = t.target.result;
    }
  }
}

3.2 createObjectURL

Window.URL属性返回一个对象,它提供了用于创建和管理对象URLs的静态方法。它也可以作为一个构造函数被调用来构造URL对象。

URL.createObjectURL()静态方法会创建一个DOMString,其中包含一个表示参数中给出的对象的URL。这个URL的生命周期和创建它的窗口中的document绑定。这个新的URL对象表示指定的File对象或Blob对象。

这是一个实验性的功能,浏览器兼容性如下:

浏览器兼容性

function previewImage2(inputEle, preview) {
  inputEle.onchange = function(e) {
    const file = e.target.files[0];
    if (!file) {
      preview.src = '';
      return
    }
    preview.src = window.URL.createObjectURL(file)
  }
}

3.3 使用canvas

就如同上面说的一样,先通过drawImage绘制图片,然后通过toDataURL转为base64格式

要注意的是,图片的EXIF信息中的方向值会影响图片的展示,在IOS会出现图片的宽高和图片的方向不匹配的问题,需要特殊处理:

  1. 可以使用exif.js来获取图片信息中的Orientation属性,利用canvas的绘制来矫正
  2. 可以使用canvasResize.js来解决File到base64的一系列问题。

4 图片的剪裁

通过drawImage来进行剪裁:

// 居中剪裁
function clipImage(img, ops) {
  // 图片原始尺寸
  const imgNaturalWidth = img.naturalWidth;
  const imgNaturalHeight = img.naturalHeight;

  // 剪切尺寸,默认值为原图狂傲
  let clippedWidth = ops.width || imgNaturalWidth;
  let clippedHeight = ops.height || imgNaturalHeight;
  let clippedRatio = clippedWidth / clippedHeight;

  // 居中剪裁的坐标
  let dx = (imgNaturalWidth - clippedWidth) / 2;
  let dy = (imgNaturalHeight - clippedHeight) / 2;

  // 创建画布,并设定尺寸为剪切后的尺寸
  let cvs = document.createElement('canvas');
  const ctxWidth = 300;
  const ctxHeight = ctxWidth / clippedRatio;
  cvs.width = ctxWidth;
  cvs.height = ctxHeight;

  // 绘制图片
  let ctx = cvs.getContext('2d');
  ctx.drawImage(img, dx, dy, clippedWidth, clippedHeight, 0, 0, ctxWidth, ctxHeight);

  // 返回base64图片
  return cvs.toDataURL('image/jpeg', 0.9)
}

5 合成

参考这个吧,弄旋转弄了半天也没成,没兴趣了,有时间再说。

6 水印

水印有几种方式可以生成,第一种就是使用canvas,第二种是通过SVG,第三种是通过NodeJS

6.1 canvas生成水印

makeWaterMark(content, {
  container = document.body,
  width = 250,
  height = 200,
  textAlign = 'center',
  textBaseline = 'middle',
  font = '20px 微软雅黑',
  fillStyle = 'rgba(184, 184, 184, 0.8)',
  angle = 30,
  zIndex = 11000,
} = {}) {
  // 创建画布
  const cvs = document.createElement('canvas');
  // 画布尺寸
  cvs.width = width;
  cvs.height = height;
  // 上下文
  const ctx = cvs.getContext('2d');
  // 文字样式
  ctx.textAlign = textAlign;
  ctx.textBaseline = textBaseline;
  ctx.font = font;
  ctx.fillStyle = fillStyle;
  // 文字旋转
  ctx.rotate(angle * Math.PI / 180);
  // 绘制文字
  ctx.fillText(content, width / 2, height / 2);
  // 生成base64图片编码
  const base64Url = cvs.toDataURL();
  // 生成水印容器div
  const div = document.createElement('div');
  // CSS属性
  div.id = 'watermark';
  div.setAttribute('style', `position: fixed; top: 0; left: 0; width: 100 % ; height: 100 % ; z - index: $ {
    zIndex
  }; pointer - events: none; background - repeat: repeat; background - image: url('${base64Url}')`);
  // 插入容器
  container.insertBefore(div, null);
  return base64Url

导出这个方法的话可以使用UMD格式导出:

if (typeof module != 'undefined' && module.exports) { //CMD
  module.exports = makeWaterMark;
} else if (typeof define == 'function' && define.amd) { // AMD
  define(function() {
    return makeWaterMark;
  });
} else {
  window.makeWaterMark = makeWaterMark;
}

这里面还有一个问题,就是水印很容易在开发者工具中去除,这时候可以使用MutationObserver接口来进行处理.

MutationObserver接口提供了监视对DOM树所做更改的能力。MutationObserver用来监视DOM变动。DOM的任何变动,比如节点的增减、属性的变动、文本内容的变动,这个API都可以得到通知。

// 监控DOM变动
const MutationObserver = window.MutationObserver || window.webkitMutationObserver;
if (MutationObserver) {
  let mo = new MutationObserver(() = > {
    const watermark = document.querySelector('#watermark');
    if ((watermark && watermark.getAttribute('style') !== divStyle) || !watermark) {
      // 避免循环触发
      mo.disconnect();
      mo = null;
      this.makeWaterMark.apply(this, [].slice.call(arguments))
    }
  });
  // 监听对象
  const config = {
    attributes: true,
    childList: true,
    subtree: true
  };
  mo.observe(container, config)
}

将上面的代码应用到生成水印的方法中,就可以阻止DOM节点的删除

6.2 通过SVG生成水印

相比Canvas,SVG有更好的浏览器兼容性,使用SVG生成水印的方式与Canvas的方式类似,只是base64Url的生成方式换成了SVG。

(function () {
  // svg 实现 watermark
  function __svgWM({ container = document.body,
                     content = '请勿外传',
                     width = '300px',
                     height = '200px',
                     opacity = '0.2',
                     fontSize = '20px',
                     zIndex = 1000
                   } = {}) {
    const args = arguments[0];
    const svgStr = `
      <svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${width}">
        <text x="50%" y="50%" dy="12px" text-anchor="middle" stroke="#000000" stroke-width="1" stroke-opacity="${opacity}" fill="none" transform="rotate(-45, 120 120)" style="font-size: ${fontSize};">${content}</text></svg>`;
    const base64Url = `data: image / svg + xml;
      base64, ${
      window.btoa(unescape(encodeURIComponent(svgStr)))
      }`;
    const __wm = document.querySelector('.__wm');
    const watermarkDiv = __wm || document.createElement("div");
    // ...
    // 与 canvas 的一致
    // ...
  }
)();
__svgWM({
  content: 'QQMusicFE'
})

6.3 NodeJS生成水印

具体参考这篇文章吧。

6.4 图片水印

浏览器给图片添加水印就是通过canvas绘制图片,然后增加文字的形式完成的。

(function () {
  function __picWM({ url = '',
                     textAlign = 'center',
                     textBaseline = 'middle',
                     font = "20px Microsoft Yahei",
                     fillStyle = 'rgba(184, 184, 184, 0.8)',
                     content = '请勿外传',
                     cb = null,
                     textX = 100,
                     textY = 30
                   } = {}) {
    const img = new Image();
    img.src = url;
    img.crossOrigin = 'anonymous';
    img.onload = function () {
      const canvas = document.createElement('canvas');
      canvas.width = img.width;
      canvas.height = img.height;
      const ctx = canvas.getContext('2d');

      ctx.drawImage(img, 0, 0);
      ctx.textAlign = textAlign;
      ctx.textBaseline = textBaseline;
      ctx.font = font;
      ctx.fillStyle = fillStyle;
      ctx.fillText(content, img.width - textX, img.height - textY);

      const base64Url = canvas.toDataURL();
      cb && cb(base64Url);
    }
  }

  if (typeof module != 'undefined' && module.exports) {  //CMD
    module.exports = __picWM;
  } else if (typeof define == 'function' && define.amd) { // AMD
    define(function () {
      return __picWM;
    });
  } else {
    window.__picWM = __picWM;
  }

})();

// 调用
__picWM({
  url: 'http://localhost:3000/imgs/google.png',
  content: 'QQMusicFE',
  cb: (base64Url) => {
    document.querySelector('img').src = base64Url
  },
});

可以使用NodeJS批量为图片添加水印,使用的gm这个库。

6.5 隐水印

参考这篇文章不能说的秘密——前端也能玩的图片隐写术

6.6 水印加密

可以将水印内容通过加密来防止水印的造假,比如使用md5加密

// MD5加密库 utility
const utils = require('utility')

// 加盐MD5
exports.md5 =  function (content) {
  const salt = 'microzz_asd!@#IdSDAS~~';
  return utils.md5(utils.md5(content + salt));
}

参考

猜你喜欢

转载自blog.csdn.net/duola8789/article/details/81317205