乐趣区

关于javascript:图片处理好用的工具库

上面列举的图片解决库领有含糊、压缩、裁剪、旋转、合成、比对等技能。能满足咱们根本应用图片的操作。

你将学习到:

  • 如何辨别图片的类型(非文件后缀名);
  • 如何获取图片的尺寸(非右键查看图片信息);
  • 如何预览本地图片(非图片阅读器);
  • 如何实现图片压缩(非图片压缩工具);
  • 如何操作位图像素数据(非 PS 等图片处理软件);
  • 如何实现图片隐写(非肉眼可见)。

一、基础知识

1.1 位图

「位图图像(bitmap),亦称为点阵图像或栅格图像,是由称作像素(图片元素)的单个点组成的。」 这些点能够进行不同的排列和染色以形成图样。当放大位图时,能够看见赖以形成整个图像的有数单个方块。扩充位图尺寸的成果是增大单个像素,从而使线条和形态显得参差不齐。

「用数码相机拍摄的照片、扫描仪扫描的图片以及计算机截屏图等都属于位图。」 位图的特点是能够体现色调的变动和色彩的轻微过渡,产生真切的成果,毛病是在保留时须要记录每一个像素的地位和色彩值,占用较大的存储空间。罕用的位图处理软件有 Photoshop、Painter 和 Windows 零碎自带的画图工具等。

分辨率是位图不可逾越的壁垒,在对位图进行缩放、旋转等操作时,无奈生产新的像素,因而会放大原有的像素填补空白,这样会让图片显得不清晰。

(图片起源:https://zh.wikipedia.org/wiki…)

图中的小方块被称为像素,这些小方块都有一个明确的地位和被调配的色调数值,小方格色彩和地位就决定该图像所出现进去的样子。

能够将像素视为整个图像中不可分割的单位或者是元素。「不可分割的意思是它不可能再切割成更小单位抑或是元素,它是以一个繁多色彩的小格存在。」 每一个点阵图像蕴含了一定量的像素,这些像素决定图像在屏幕上所出现的大小。

1.2 矢量图

所谓矢量图,就是应用直线和曲线来形容的图形,形成这些图形的元素是一些点、线、矩形、多边形、圆和弧线等,*「 它们都是通过数学公式计算取得的,具备编辑后不失真的特点。* 例如一幅画的矢量图形实际上是由线段造成外框轮廓,由外框的色彩以及外框所关闭的色彩决定画显示出的色彩。

「矢量图以几何图形居多,图形能够有限放大,不变色、不含糊。」 罕用于图案、标记、VI、文字等设计。常用软件有:CorelDraw、Illustrator、Freehand、XARA、CAD 等。

这里咱们以 Web 开发者比拟相熟的 SVG(「Scalable Vector Graphics —— 可缩放矢量图形」)为例,来理解一下 SVG 的构造:

可缩放矢量图形(英语:Scalable Vector Graphics,SVG)是一种基于可扩大标记语言(XML),用于形容二维矢量图形的图形格局。SVG 由 W3C 制订,是一个凋谢规范。

SVG 次要反对以下几种显示对象:

  • 矢量显示对象,根本矢量显示对象包含矩形、圆、椭圆、多边形、直线、任意曲线等;
  • 嵌入式内部图像,包含 PNG、JPEG、SVG 等;
  • 文字对象。

理解完位图与矢量图的区别,上面咱们来介绍一下位图的数学示意。

1.3 位图的数学示意

位图的像素都调配有特定的地位和色彩值。每个像素的色彩信息由 RGB 组合或者灰度值示意。

依据位深度,可将位图分为 1、4、8、16、24 及 32 位图像等。每个像素应用的信息位数越多,可用的色彩就越多,色彩体现就越真切,相应的数据量越大。

「1.3.1 二值图像」

位深度为 1 的像素位图只有两个可能的值(彩色和红色),所以又称为二值图像。二值图像的像素点只有黑白两种状况,因而每个像素点能够由 0 和 1 来示意。

比方一张 4 * 4 二值图像:

1 1 0 1
1 1 0 1
1 0 0 0
1 0 1 0

「1.3.2 RGB 图像」

RGB 图像由三个色彩通道组成,其中 RGB 代表红、绿、蓝三个通道的色彩。8 位 / 通道的 RGB 图像中的每个通道有 256 个可能的值,这意味着该图像有 1600 万个以上可能的色彩值。

有时将带有 8 位 / 通道(bpc)的 RGB 图像称作 24 位图像(8 位 x 3 通道 = 24 位数据 / 像素)。通常将应用 24 位 RGB 组合数据位示意的的位图称为真彩色位图。

RGB 彩色图像可由三种矩阵示意:一种代表像素中红色的强度,一种代表绿色,另一种代表蓝色。

(图片起源:https://freecontent.manning.c…)

「图像处理的实质实际上就是对这些像素矩阵进行计算。」 其实位图中的图像类型,除了二值图像和 RGB 图像之外,还有灰度图像、索引图像和 YUV 图像。这里咱们不做过多介绍,感兴趣的小伙伴,请自行查阅相干材料。

二、图片解决库

2.1 AlloyImage

基于 HTML 5 的专业级图像处理开源引擎。

https://github.com/AlloyTeam/…

AlloyImage 基于 HTML5 技术的业余图像处理库,来自腾讯 AlloyTeam 团队。它领有以下性能个性:

  • 基于多图层操作 —— 一个图层的解决不影响其余图层;
  • 与 PS 对应的 17 种图层混合模式 —— 便于 PS 解决教程的无缝迁徙;
  • 多种根本滤镜解决成果 —— 根本滤镜不断丰富、可扩大;
  • 根本的图像调节性能 —— 色相、饱和度、对比度、亮度、曲线等;
  • 简略快捷的 API —— 链式解决、API 简洁易用、传参灵便;
  • 多种组合成果封装 —— 一句代码轻松实现一种格调;
  • 接口统一的单、多线程反对 —— 单、多线程切换无需更改一行代码,多线程放弃快捷 API 个性。

对于该库 AlloyTeam 团队倡议的应用场景如下:

  • 桌面软件客户端内嵌网页运行形式 >>> 打包 Webkit 内核:用户较大头像上传格调解决、用户相册格调解决(解决工夫均匀 < 1s);
  • Win8 Metro 利用 >>> 用户上传头像,比拟小的图片格调解决后上传(Win8 下 IE 10 反对多线程);
  • Mobile APP >>> Andriod 平台、iOS 平台小图格调 Web 解决的需要,如 PhoneGap 利用,在线头像上传时的格调解决、Mobile Web 端分享图片时格调解决等。

「应用示例」

// $AI 或 AlloyImage 初始化一个 AlloyImage 对象
var ps = $AI(img, 600).save('jpg', 0.6);
// save 将合成图片保留成 base64 格局字符串
var string = AlloyImage(img).save('jpg', 0.8);
// saveFile 将合成图片下载到本地
img.onclick = function(){AlloyImage(this).saveFile('解决后图像.jpg', 0.8);
}

「在线示例」

http://alloyteam.github.io/Al…

(图片起源:http://alloyteam.github.io/Al…)

2.2 blurify

blurify.js is a tiny(~2kb) library to blurred pictures, support graceful downgrade from css mode to canvas mode.

https://github.com/JustClear/…

blurify.js 是一个用于图片含糊,很小的 JavaScript 库(约 2 kb),并反对从 CSS 模式到 Canvas 模式的优雅降级。该插件反对三种模式:

  • css 模式:应用 filter 属性,默认模式;
  • canvas 模式:应用 canvas 导出 base64;
  • auto 模式:优先应用 css 模式,否则主动切换到 canvas 模式。

「应用示例」

import blurify from 'blurify';
new blurify({images: document.querySelectorAll('.blurify'),
    blur: 6,
    mode: 'css',
});
// or in shorthand
blurify(6, document.querySelectorAll('.blurify'));

「在线示例」

https://justclear.github.io/b…

(图片起源:https://justclear.github.io/b…)

看到这里是不是有些小伙伴感觉只是含糊解决而已,感觉不过瘾,能不能来点更酷的。嘿嘿,有求必应!阿宝哥立马来个 「“酷炫叼”」 的库 —— midori,该库用于为背景图创立动画,应用 three.js 编写并应用 WebGL。原本是想给个演示动图,无奈单个 Gif 文件太大,只能放个体验地址,感兴趣的小伙伴自行体验一下。

midori 示例地址:https://aeroheim.github.io/mi…

2.3 cropperjs

JavaScript image cropper.

https://github.com/fengyuanch…

Cropper.js 是一款十分弱小却又简略的图片裁剪工具,它能够进行非常灵活的配置,反对手机端应用,反对包含 IE9 以上的古代浏览器。它能够用于满足诸如裁剪头像上传、商品图片编辑之类的需要。

Cropper.js 反对以下个性:

  • 反对 39 个配置选项;
  • 反对 27 个办法;
  • 反对 6 种事件;
  • 反对 touch(挪动端);
  • 反对缩放、旋转和翻转;
  • 反对在画布上裁剪;
  • 反对在浏览器端通过画布裁剪图像;
  • 反对解决 Exif 方向信息;
  • 跨浏览器反对。

可替换图像文件格式(英语:Exchangeable image file format,官网简称 Exif),是专门为数码相机的照片设定的文件格式,能够记录数码照片的属性信息和拍摄数据。Exif 能够附加于 JPEG、TIFF、RIFF 等文件之中,为其减少无关数码相机拍摄信息的内容和索引图或图像处理软件的版本信息。

Exif 信息以 0xFFE1 作为结尾标记,后两个字节示意 Exif 信息的长度。所以 Exif 信息最大为 64 kB,而外部采纳 TIFF 格局。

「应用示例」

// import 'cropperjs/dist/cropper.css';
import Cropper from 'cropperjs';
const image = document.getElementById('image');
const cropper = new Cropper(image, {
  aspectRatio: 16 / 9,
  crop(event) {console.log(event.detail.x);
    console.log(event.detail.y);
    console.log(event.detail.width);
    console.log(event.detail.height);
    console.log(event.detail.rotate);
    console.log(event.detail.scaleX);
    console.log(event.detail.scaleY);
  },
});

「在线示例」

https://fengyuanchen.github.i…

2.4 compressorjs

JavaScript image compressor.

https://github.com/fengyuanch…

compressorjs 是 JavaScript 图像压缩器。应用浏览器原生的 canvas.toBlob API 进行压缩工作,这意味着它是有损压缩。通常的应用场景是,在浏览器端图片上传之前对其进行预压缩。

在浏览器端要实现图片压缩,除了应用 canvas.toBlob API 之外,还能够应用 Canvas 提供的另一个 API,即 toDataURL API,它接管 typeencoderOptions 两个可选参数。

其中 type 示意图片格式,默认为 image/png。而 encoderOptions 用于示意图片的品质,在指定图片格式为 image/jpegimage/webp 的状况下,能够从 0 到 1 的区间内抉择图片的品质。如果超出取值范畴,将会应用默认值 0.92,其余参数会被疏忽。

相比 canvas.toDataURL API 来说,canvas.toBlob API 是异步的,因而多了个 callback 参数,这个 callback 回调办法默认的第一个参数就是转换好的 blob 文件信息。canvas.toBlob 的签名如下:

canvas.toBlob(callback, mimeType, qualityArgument)

「应用示例」

import axios from 'axios';
import Compressor from 'compressorjs';
// <input type="file" id="file" accept="image/*">
document.getElementById('file').addEventListener('change', (e) => {const file = e.target.files[0];
  if (!file) {return;}
  new Compressor(file, {
    quality: 0.6,
    success(result) {const formData = new FormData();
      // The third parameter is required for server
      formData.append('file', result, result.name);
      // Send the compressed image file to server with XMLHttpRequest.
      axios.post('/path/to/upload', formData).then(() => {console.log('Upload success');
      });
    },
    error(err) {console.log(err.message);
    },
  });
});

「在线示例」

https://fengyuanchen.github.i…

2.5 fabric.js

Javascript Canvas Library, SVG-to-Canvas (& canvas-to-SVG) Parser.

https://github.com/fabricjs/f…

Fabric.js 是一个框架,可让你轻松应用 HTML5 Canvas 元素。它是一个位于 Canvas 元素之上的交互式对象模型,同时也是一个 「SVG-to-canvas」 的解析器。

应用 Fabric.js,你能够在画布上创立和填充对象。所谓的对象,能够是简略的几何形态,比方矩形,圆形,椭圆形,多边形,或更简单的形态,蕴含数百或数千个简略门路。而后,你能够应用鼠标缩放,挪动和旋转这些对象。并批改它们的属性 —— 色彩,透明度,z-index 等。此外你还能够一起操纵这些对象,即通过简略的鼠标抉择将它们分组。

Fabric.js 反对所有支流的浏览器,具体的兼容状况如下:

  • Firefox 2+
  • Safari 3+
  • Opera 9.64+
  • Chrome(所有版本)
  • IE10,IE11,Edge

「应用示例」

<!DOCTYPE html>
<html>
<head></head>
<body>
    <canvas id="canvas" width="300" height="300"></canvas>
    <script src="lib/fabric.js"></script>
    <script> var canvas = new fabric.Canvas('canvas');
        var rect = new fabric.Rect({
            top : 100,
            left : 100,
            width : 60,
            height : 70,
            fill : 'red'
        });
        canvas.add(rect); </script>
</body>
</html>

「在线示例」

http://fabricjs.com/kitchensink

(图片起源:https://github.com/fabricjs/f…)

2.6 Resemble.js

Image analysis and comparison

https://github.com/rsmbl/Rese…

Resemble.js 应用 HTML Canvas 和 JavaScript 来实现图片的剖析和比拟。兼容大于 8.0 的 Node.js 版本。

「应用示例」

// 比拟两张图片
var diff = resemble(file)
    .compareTo(file2)
    .ignoreColors()
    .onComplete(function(data) {console.log(data);
     /*
     {
        misMatchPercentage : 100, // %
        isSameDimensions: true, // or false
        dimensionDifference: {width: 0, height: -1}, 
        getImageDataUrl: function(){}
     }
    */
});

「在线示例」

http://rsmbl.github.io/Resemb…

2.7 Pica

Resize image in browser with high quality and high speed

https://github.com/nodeca/pica

Pica 可用于在浏览器中调整图像大小,没有像素化并且相当快。它会主动抉择最佳的可用技术:webworkers,webassembly,createImageBitmap,纯 JS。

借助 Pica,你能够实现以下性能:

  • 减小大图像的上传大小,节俭上传工夫;
  • 在图像处理上节俭服务器资源;
  • 在浏览器中生成缩略图。

「应用示例」

const pica = require('pica')();
// 调整画布 / 图片的大小
pica.resize(from, to, {
  unsharpAmount: 80,
  unsharpRadius: 0.6,
  unsharpThreshold: 2
})
.then(result => console.log('resize done!'));
// 调整大小并转换为 Blob
pica.resize(from, to)
  .then(result => pica.toBlob(result, 'image/jpeg', 0.90))
  .then(blob => console.log('resized to canvas & created blob!'));

「在线示例」

http://nodeca.github.io/pica/…

2.8 tui.image-editor

???????? Full-featured photo image editor using canvas. It is really easy, and it comes with great filters.

https://github.com/nhn/tui.im…

tui.image-editor 是应用 HTML5 Canvas 的全功能图像编辑器。它易于应用,并提供弱小的过滤器。同时它反对对图像进行裁剪、翻转、旋转、绘图、形态、文本、遮罩和图片过滤等操作。

tui.image-editor 的浏览器兼容状况如下:

  • Chrome
  • Edge
  • Safari
  • Firefox
  • IE 10+

「应用示例」

// Image editor
var imageEditor = new tui.ImageEditor("#tui-image-editor-container", {
     includeUI: {
       loadImage: {
         path: "img/sampleImage2.png",
         name: "SampleImage",
       },
       theme: blackTheme, // or whiteTheme
         initMenu: "filter",
         menuBarPosition: "bottom",
       },
       cssMaxWidth: 700,
       cssMaxHeight: 500,
       usageStatistics: false,
});
window.onresize = function () {imageEditor.ui.resizeEditor();
};

在线示例

https://ui.toast.com/tui-imag…

2.9 gif.js

JavaScript GIF encoding library

https://github.com/jnordberg/…

gif.js 是运行在浏览器端的 JavaScript GIF 编码器。它应用类型化数组和 Web Worker 在后盾渲染每一帧,速度真的很快。该库可工作在反对:Web Workers,File API 和 Typed Arrays 的浏览器中。

gif.js 的浏览器兼容状况如下:

  • Google Chrome
  • Firefox 17
  • Safari 6
  • Internet Explorer 10
  • Mobile Safari iOS 6

「应用示例」

var gif = new GIF({
  workers: 2,
  quality: 10
});
// add an image element
gif.addFrame(imageElement);
// or a canvas element
gif.addFrame(canvasElement, {delay: 200});
// or copy the pixels from a canvas context
gif.addFrame(ctx, {copy: true});
gif.on('finished', function(blob) {window.open(URL.createObjectURL(blob));
});
gif.render();

「在线示例」

http://jnordberg.github.io/gi…

2.10 Sharp

High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images. Uses the libvips library.

https://github.com/lovell/sharp

Sharp 的典型利用场景是将常见格局的大图像转换为尺寸较小,对网络敌对的 JPEG,PNG 和 WebP 格局的图像。因为其外部应用 libvips,使得调整图像大小通常比应用 ImageMagick 和 GraphicsMagick 设置快 4-5 倍。除了反对调整图像大小之外,Sharp 还反对旋转、提取、合成和伽马校对等性能。

Sharp 反对读取 JPEG,PNG,WebP,TIFF,GIF 和 SVG 图像。输入图像能够是 JPEG,PNG,WebP 和 TIFF 格局,也能够是未压缩的原始像素数据。

「应用示例」

// 扭转图像尺寸
sharp(inputBuffer)
  .resize(320, 240)
  .toFile('output.webp', (err, info) => {...});
       
// 旋转输出图像并扭转图片尺寸 
sharp('input.jpg')
  .rotate()
  .resize(200)
  .toBuffer()
  .then(data => { ...})
  .catch(err => { ...}); 

「在线示例」

https://segmentfault.com/a/11…

该示例是来自阿宝哥 18 年写的“Sharp 牛刀小试之生成专属分享图片”这篇文章,次要是利用 Sharp 提供的图片合成性能为每个用户生成专属的分享海报,感兴趣的小伙伴能够浏览一下原文哟。

const sharp = require("sharp");
const TextToSVG = require("text-to-svg");
const path = require("path");
// 加载字体文件
const textToSVG = TextToSVG.loadSync(path.join(__dirname, "./simhei.ttf"));
// 创立圆形 SVG,用于实现头像裁剪
const roundedCorners = new Buffer('<svg><circle r="90"cx="90"cy="90"/></svg>');
// 设置 SVG 文本元素相干参数
const attributes = {fill: "white"};
const svgOptions = {
  x: 0,
  y: 0,
  fontSize: 32,
  anchor: "top",
  attributes: attributes
};
/**
 * 应用文本生成 SVG
 * @param {*} text 
 * @param {*} options 
 */
function textToSVGFn(text, options = svgOptions) {return textToSVG.getSVG(text, options);
}
/**
 * 图层叠加生成分享图片
 * @param {*} options 
 * 
 */
async function genShareImage(options) {
  const { backgroudPath, avatarPath, qrcodePath, 
    userName, words, likes, outFilePath
  } = options;
  // 背景图片
  const backgroudBuffer = sharp(path.join(__dirname, backgroudPath)).toBuffer({resolveWithObject: true});
  const backgroundImageInfo = await backgroudBuffer;
  // 头像图片
  const avatarBuffer = await genCircleAvatar(path.join(__dirname, avatarPath));
  // 二维码图片
  const qrCodeBuffer = await sharp(path.join(__dirname, qrcodePath))
    .resize(180)
    .toBuffer({resolveWithObject: true});
  // 用户名
  const userNameSVG = textToSVGFn(userName);
  // 用户数据
  const userDataSVG = textToSVGFn(` 写了 ${words} 个字   播种 ${likes} 个赞 `);
  const userNameBuffer = await sharp(new Buffer(userNameSVG)).toBuffer({resolveWithObject: true});
  const userDataBuffer = await sharp(new Buffer(userDataSVG)).toBuffer({resolveWithObject: true});
  const buffers = [avatarBuffer, qrCodeBuffer, userNameBuffer, userDataBuffer];
  // 图层叠加参数列表
  const overlayOptions = [{ top: 150, left: 230},
    {top: 861, left: 227},
    {
      top: 365,
      left: (backgroundImageInfo.info.width - userNameBuffer.info.width) / 2
    },
    {
      top: 435,
      left: (backgroundImageInfo.info.width - userDataBuffer.info.width) / 2
    }
  ];
  // 组合多个图层:图片 + 文字图层
  return buffers
    .reduce((input, overlay, index) => {
      return input.then(result => {console.dir(overlay.info);
        return sharp(result.data)
          .overlayWith(overlay.data, overlayOptions[index])
          .toBuffer({resolveWithObject: true});
      });
    }, backgroudBuffer)
    .then((data) => {return sharp(data.data).toFile(outFilePath);
    }).catch(error => {throw new Error('Generate Share Image Failed.');
    });
}
/**
 * 生成圆形的头像
 * @param {*} avatarPath 头像门路
 */
function genCircleAvatar(avatarPath) {return sharp(avatarPath)
    .resize(180, 180)
    .overlayWith(roundedCorners, { cutout: true})
    .png()
    .toBuffer({resolveWithObject: true});
}
module.exports = {genShareImage};

三、实用示例

3.1 如何辨别图片的类型

「计算机并不是通过图片的后缀名来辨别不同的图片类型,而是通过“魔数”(Magic Number)来辨别。」 对于某一些类型的文件,起始的几个字节内容都是固定的,跟据这几个字节的内容就能够判断文件的类型。

常见图片类型对应的魔数如下表所示:

文件类型 文件后缀 魔数
JPEG jpg/jpeg 0xFFD8FF
PNG png 0x89504E47
GIF gif 0x47494638(GIF8)
BMP bmp 0x424D

这里咱们以阿宝哥的头像(abao.png)为例,验证一下该图片的类型是否正确:

在日常开发过程中,如果遇到检测图片类型的场景,咱们能够间接利用一些现成的第三方库。比方,你想要判断一张图片是否为 PNG 类型,这时你能够应用 is-png 这个库,它同时反对浏览器和 Node.js,应用示例如下:

「Node.js」

// npm install read-chunk
const readChunk = require('read-chunk'); 
const isPng = require('is-png');
const buffer = readChunk.sync('unicorn.png', 0, 8);
isPng(buffer);
//=> true

「Browser」

(async () => {const response = await fetch('unicorn.png');
 const buffer = await response.arrayBuffer();
 isPng(new Uint8Array(buffer));
 //=> true
})();

3.2 如何获取图片的尺寸

图片的尺寸、位深度、色调类型和压缩算法都会存储在文件的二进制数据中,咱们持续以阿宝哥的头像(abao.png)为例,来理解一下理论的状况:

528(十进制)=> 0x0210

560(十进制)=> 0x0230

因而如果想要获取图片的尺寸,咱们就须要根据不同的图片格式对图片二进制数据进行解析。侥幸的是,咱们不须要本人做这件事,image-size 这个 Node.js 库曾经帮咱们实现了获取支流图片类型文件尺寸的性能:

「同步形式」

var sizeOf = require('image-size');
var dimensions = sizeOf('images/abao.png');
console.log(dimensions.width, dimensions.height);

「异步形式」

var sizeOf = require('image-size');
sizeOf('images/abao.png', function (err, dimensions) {console.log(dimensions.width, dimensions.height);
});

image-size 这个库性能还是蛮弱小的,除了反对 PNG 格局之外,还反对 BMP、GIF、ICO、JPEG、SVG 和 WebP 等格局。

3.3 如何预览本地图片

利用 HTML FileReader API,咱们也能够不便的实现图片本地预览性能,具体代码如下:

<input type="file" accept="image/*" onchange="loadFile(event)">
<img id="output"/>
<script> const loadFile = function(event) {const reader = new FileReader();
    reader.onload = function(){const output = document.querySelector('output');
      output.src = reader.result;
    };
    reader.readAsDataURL(event.target.files[0]);
  };
</script>

在实现本地图片预览之后,能够间接把图片对应的 Data URLs 数据提交到服务器。针对这种情景,服务端须要做一些相干解决,能力失常保留上传的图片,这里以 Express 为例,具体解决代码如下:

const app = require('express')();
app.post('/upload', function(req, res){
    let imgData = req.body.imgData; // 获取 POST 申请中的 base64 图片数据
    let base64Data = imgData.replace(/^data:image/w+;base64,/, "");
    let dataBuffer = Buffer.from(base64Data, 'base64');
    fs.writeFile("image.png", dataBuffer, function(err) {if(err){res.send(err);
        }else{res.send("图片上传胜利!");
        }
    });
});

3.4 如何实现图片压缩

在一些场合中,咱们心愿在上传本地图片时,先对图片进行肯定的压缩,而后再提交到服务器,从而缩小传输的数据量。在前端要实现图片压缩,咱们能够利用 Canvas 对象提供的 toDataURL() 办法,该办法接管 typeencoderOptions 两个可选参数。

其中 type 示意图片格式,默认为 image/png。而 encoderOptions 用于示意图片的品质,在指定图片格式为 image/jpegimage/webp 的状况下,能够从 0 到 1 的区间内抉择图片的品质。如果超出取值范畴,将会应用默认值 0.92,其余参数会被疏忽。

上面咱们来看一下具体如何实现图片压缩:

function compress(base64, quality, mimeType) {let canvas = document.createElement("canvas");
  let img = document.createElement("img");
  img.crossOrigin = "anonymous";
  return new Promise((resolve, reject) => {
    img.src = base64;
    img.onload = () => {
      let targetWidth, targetHeight;
      if (img.width > MAX_WIDTH) {
        targetWidth = MAX_WIDTH;
        targetHeight = (img.height * MAX_WIDTH) / img.width;
      } else {
        targetWidth = img.width;
        targetHeight = img.height;
      }
      canvas.width = targetWidth;
      canvas.height = targetHeight;
      let ctx = canvas.getContext("2d");
      ctx.clearRect(0, 0, targetWidth, targetHeight); // 革除画布
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      let imageData = canvas.toDataURL(mimeType, quality / 100);
      resolve(imageData);
    };
  });
}

对于返回的 Data URL 格局的图片数据,为了进一步缩小传输的数据量,咱们能够把它转换为 Blob 对象:

function dataUrlToBlob(base64, mimeType) {let bytes = window.atob(base64.split(",")[1]);
  let ab = new ArrayBuffer(bytes.length);
  let ia = new Uint8Array(ab);
  for (let i = 0; i < bytes.length; i++) {ia[i] = bytes.charCodeAt(i);
  }
  return new Blob([ab], {type: mimeType});
}

在转换实现后,咱们就能够压缩后的图片对应的 Blob 对象封装在 FormData 对象中,而后再通过 AJAX 提交到服务器上:

function uploadFile(url, blob) {let formData = new FormData();
  let request = new XMLHttpRequest();
  formData.append("image", blob);
  request.open("POST", url, true);
  request.send(formData);
}

3.5 如何操作位图像素数据

如果想要操作图片像素数据,咱们能够利用 CanvasRenderingContext2D 提供的 getImageData 来获取图片像素数据,其中 getImageData() 返回一个 ImageData 对象,用来形容 canvas 区域隐含的像素数据,这个区域通过矩形示意,起始点为(sx, sy)、宽为 sw、高为 sh。其中 getImageData 办法的语法如下:

ctx.getImageData(sx, sy, sw, sh);

相应的参数阐明如下:

  • sx:将要被提取的图像数据矩形区域的左上角 x 坐标。
  • sy:将要被提取的图像数据矩形区域的左上角 y 坐标。
  • sw:将要被提取的图像数据矩形区域的宽度。
  • sh:将要被提取的图像数据矩形区域的高度。

在获取到图片的像素数据之后,咱们就能够对获取的像素数据进行解决,比方进行灰度化或反色解决。当实现解决后,若要在页面上显示解决成果,则咱们须要利用 CanvasRenderingContext2D 提供的另一个 API —— putImageData

该 API 是 Canvas 2D API 将数据从已有的 ImageData 对象绘制到位图的办法。如果提供了一个绘制过的矩形,则只绘制该矩形的像素。此办法不受画布转换矩阵的影响。putImageData 办法的语法如下:

void ctx.putImageData(imagedata, dx, dy);
void ctx.putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight);

相应的参数阐明如下:

  • imageData:ImageData,蕴含像素值的数组对象。
  • dx:源图像数据在指标画布中的地位偏移量(x 轴方向的偏移量)。
  • dy:源图像数据在指标画布中的地位偏移量(y 轴方向的偏移量)。
  • dirtyX(可选):在源图像数据中,矩形区域左上角的地位。默认是整个图像数据的左上角(x 坐标)。
  • dirtyY(可选):在源图像数据中,矩形区域左上角的地位。默认是整个图像数据的左上角(y 坐标)。
  • dirtyWidth(可选):在源图像数据中,矩形区域的宽度。默认是图像数据的宽度。
  • dirtyHeight(可选):在源图像数据中,矩形区域的高度。默认是图像数据的高度。

介绍完相干的 API,上面咱们来举一个理论例子:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title> 图片反色和灰度化解决 </title>
  </head>
  <body onload="loadImage()">
    <div>
      <button id="invertbtn"> 反色 </button>
      <button id="grayscalebtn"> 灰度化 </button>
    </div>
    <canvas id="canvas" width="800" height="600"></canvas>
    <script> function loadImage() {var img = new Image();
        img.crossOrigin = "";
        img.onload = function () {draw(this);
        };
        // 这是阿宝哥的头像哟
        img.src = "https://avatars3.githubusercontent.com/u/4220799";
      }
      function draw(img) {var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");
        ctx.drawImage(img, 0, 0);
        img.style.display = "none";
        var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
        var data = imageData.data;
        var invert = function () {for (var i = 0; i < data.length; i += 4) {data[i] = 255 - data[i]; // red
            data[i + 1] = 255 - data[i + 1]; // green
            data[i + 2] = 255 - data[i + 2]; // blue
          }
          ctx.putImageData(imageData, 0, 0);
        };
        var grayscale = function () {for (var i = 0; i < data.length; i += 4) {var avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
            data[i] = avg; // red
            data[i + 1] = avg; // green
            data[i + 2] = avg; // blue
          }
          ctx.putImageData(imageData, 0, 0);
        };
        var invertbtn = document.getElementById("invertbtn");
        invertbtn.addEventListener("click", invert);
        var grayscalebtn = document.getElementById("grayscalebtn");
        grayscalebtn.addEventListener("click", grayscale);
      } </script>
  </body>
</html>

须要留神的在调用 getImageData 办法获取图片像素数据时,你可能会遇到跨域问题,比方:

Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.

对于这个问题,你能够浏览 「张鑫旭」 大神“解决 canvas 图片 getImageData,toDataURL 跨域问题”这一篇文章。

3.6 如何实现图片隐写

「隐写术是一门对于信息暗藏的技巧与迷信,所谓信息暗藏指的是不让除预期的接收者之外的任何人通晓信息的传递事件或者信息的内容。」 隐写术的英文叫做 Steganography,来源于特里特米乌斯的一本讲述密码学与隐写术的著述 Steganographia,该书书名源于希腊语,意为“隐秘书写”。

下图是在线的图片隐写工具,将 「“全栈修仙之路”」 这 6 个字暗藏到原始的图片中,而后应用对应的解密工具,解密出暗藏信息的后果:

(在线图片隐写体验地址:https://c.p2hp.com/yinxietu/)

目前有多种计划能够实现图片隐写,以下是几种常见的计划:

  • 附加式的图片隐写;
  • 基于文件构造的图片隐写;
  • 基于 LSB 原理的图片隐写;
  • 基于 DCT 域的 JPG 图片隐写;
  • 数字水印的隐写;
  • 图片容差的隐写。

篇幅无限,这里咱们就不持续开展,别离介绍每种计划,感兴趣的小伙伴能够浏览“隐写术之图片隐写 (一)”这篇文章。

退出移动版