JavaScript Load Image


一个用于加载和转换图像文件的 js 插件库。

Demo


Demo: https://blueimp.github.io/JavaScript-Load-Image/

概述


JavaScript Load Image 是一个用于加载图像的库,图像可以是文件Blob 对象或一个 URL。它可以返回经过缩放裁剪旋转的 HTML imgcanvas 元素。

另一方面,它也提供解析图像元数据的方法,用以提取 IPTCExif 标签,主要是获取内嵌的缩略图图像、覆盖 Exif 方向值(拍摄方向),以及在调整大小后可以完整恢复图像文件头。

安装


通过 NPM 安装:

$ npm install blueimp-load-image

上述操作会将 js 文件安装到你当前目录所相对的 ./node_modules/blueimp-load-image/js/ 目录下,你可以从该目录将它们复制到你 web 服务器运行程序所对应的目录结构中。

接下来在你的 HTML 页面中引用压缩版的 js 图像加载脚本:

<script src="js/load-image.all.min.js"></script>

或者,仅根据你所需要的操作选择必要的组件:

<!-- 基础组件,所有操作都需要 -->
<script src="js/load-image.js"></script>
<!-- 缩放、裁剪和旋转依赖项所需 --> <script src="js/load-image-scale.js"></script>
<!-- 解析元数据并恢复完整图像头所需 --> <script src="js/load-image-meta.js"></script>
<!-- 解析通过 URL 加载的图像的元数据所需 --> <script src="js/load-image-fetch.js"></script>
<!-- 跨浏览器旋转图像方向所需 --> <script src="js/load-image-orientation.js"></script>
<!-- 跨浏览器解析 Exif 标签并获取图像方向所需 --> <script src="js/load-image-exif.js"></script>
<!-- 显示 Exif 数据的各项标签所需 --> <script src="js/load-image-exif-map.js"></script>
<!-- 解析 IPTC 标签所需 --> <script src="js/load-image-iptc.js"></script>
<!-- 显示 IPTC 数据的各项标签所需 --> <script src="js/load-image-iptc-map.js"></script>

使用指南


图像加载

在你的程序代码中,以回调模式调用 loadImage() 函数:

document.getElementById('file-input').onchange = function () {
  loadImage(
    this.files[0],
    function (img) {
      document.body.appendChild(img)
    },
    { maxWidth: 600 } // 具体选项
  )
}

或者像下面这样使用基于 Promise 的 API(依赖老版浏览器的 polyfill):

document.getElementById('file-input').onchange = function () {
  loadImage(this.files[0], { maxWidth: 600 }).then(function (data) {
    document.body.appendChild(data.image)
  })
}

异步 async/await(需要主流浏览器或像 BabelTypeScript 这样的代码转译器):

document.getElementById('file-input').onchange = async function () {
  let data = await loadImage(this.files[0], { maxWidth: 600 })
  document.body.appendChild(data.image)
}

图像缩放

也可以直接对现有图像使用缩放功能:

var scaledImage = loadImage.scale(
  img, // img 或 canvas 元素
  { maxWidth: 600 }
)

依赖项


JavaScript Load Image 插件库具有零依赖性,但库中集成了下面两项 Polyfill

浏览器支持


实现下面全部接口的浏览器支持插件所有功能:

符合上述条件的包括(但不限于)以下浏览器:

  • Chrome 32+
  • Firefox 29+
  • Safari 8+
  • 手机版 Chrome 42+ (Android)
  • 手机版 Firefox 50+ (Android)
  • 手机版 Safari 8+ (iOS)
  • Edge 74+
  • 经典版 Edge 12+
  • IE 浏览器 10+ *

* IE 浏览器需要一个基于 Promise 的 API 的 polyfill。

对于通过 URL 加载的图像,所有实现了 HTMLCanvasElement 接口的浏览器都支持对其进行转换操作,包括缩放、裁剪和旋转(不过若 orientation:true,那它需要读取元数据,这种情况除外)。

同样要调整通过 URL 加载的图像大小,只要是能实现 img 元素的浏览器都能支持,即便是像 IE5 这种老版浏览器引擎也能成功通过测试(通过 IE11 的仿真模式)。

loadImage() 函数应用选项时采用渐进增强策略,当不被支持时会回退到浏览器所能支持的配置。例如,如果浏览器不支持 canvas 元素时,则返回等效的 img 元素。

API


回调

函数签名

loadImage() 函数的首参数能接受一个 FileBlob 对象,或者是一个图像 URL。

如果首参传的是一个 FileBlob,函数的返回值可能出现三种情况。第一种,对于支持 URL API 的浏览器,会返回一个 HTML img 元素。其次,若支持 FileReader API 那么返回一个 FileReader 对象。最后,若都不支持,那就返回 false

若首参传的是一个图像 URL,那本函数只会返回 HTML img 元素:

var loadingImage = loadImage(
  'https://example.org/image.png',
  function (img) {
    document.body.appendChild(img)
  },
  { maxWidth: 600 }
)
取消图像加载

某些浏览器,例如 Chrome,当 img 元素的 src 属性发生更改时,其会取消图像的加载过程。

为避免不必要的请求,我们可以使用 1x1 像素透明 GIF 图片的 data URL 作为 src 目标,取消原图下载。

要禁用回调处理,我们还可以注销掉图像的事件句柄,并且为了最大程度地兼容浏览器,如果返回的对象是 FileReader 实例,则取消文件读取过程:

var loadingImage = loadImage(
  'https://example.org/image.png',
  function (img) {
    document.body.appendChild(img)
  },
  { maxWidth: 600 }
)

if (loadingImage) {
  // 注销图像加载的事件句柄:
  loadingImage.onload = loadingImage.onerror = null

  // 取消图像加载过程:
  if (loadingImage.abort) {
    // FileReader 实例,停止文件读取:
    loadingImage.abort()
  } else {
    // HTMLImageElement 元素,通过把目标 src 改为一个 1x1 像素
    // 的透明 GIF 图像的 data URL,从而取消原图的请求:
    loadingImage.src =
      'data:image/gif;base64,' +
      'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
  }
}
请注意:
加载图像的 img 元素(或 FileReader 实例)仅在使用回调样式 API 时会返回,并不适用基于 Promise 的 API。
回调参数

对于回调模式 API,loadImage() 的第二个参数必须是一个回调函数,当图片加载完毕或加载图片出错时调用。

该回调函数需要传两个参数:

  1. HTML img 元素或 canvas 元素,或一个 error 类型的 Event 对象。
  2. 一个具有原始图像尺寸作为属性的对象,同时该 data 对象还允许附带额外的元数据
loadImage(
  fileOrBlobOrUrl,
  function (img, data) {
    document.body.appendChild(img)
    console.log('Original image width: ', data.originalWidth)
    console.log('Original image height: ', data.originalHeight)
  },
  { maxWidth: 600, meta: true }
)
请注意:
原始图像尺寸是指进行任何图像变换操作之前原来的宽和高。
为了在跨浏览器时保持图像的一致性,我们需要启用元数据解析功能(设置选项 meta:true),这样 loadImage 便可以自动检测图像方向并标准化尺寸。
Error 句柄

error 句柄的示例代码:

loadImage(
  fileOrBlobOrUrl,
  function (img, data) {
    if (img.type === 'error') {
      console.error('Error loading image file')
    } else {
      document.body.appendChild(img)
    }
  },
  { maxWidth: 600 }
)

Promise

如果在调用 loadImage() 函数时第二个参数不是一个回调函数,并且浏览器支持 Promise API,那么本次调用会返回一个 Promise 对象:

loadImage(fileOrBlobOrUrl, { maxWidth: 600, meta: true })
  .then(function (data) {
    document.body.appendChild(data.image)
    console.log('Original image width: ', data.originalWidth)
    console.log('Original image height: ', data.originalHeight)
  })
  .catch(function (err) {
    // Handling image loading errors
    console.log(err)
  })

对于 Promise 这种情况,它会是一个具有下列属性的对象:

  • image:HTML imgcanvas 元素。
  • originalWidth:图像原始宽度。
  • originalHeight:图像原始高度。

另请阅读文档回调参数中有关原始图像尺寸标准化的说明。

如果有解析元数据,则对象中可能存在其相关属性。

如果图片加载失败,返回一个带有拒绝原因的 Promise 对象,其带有一个 error 类型的 Event 对象。

选项说明


loadImage() 的可选参数允许配置图像加载时的各选项。

它可以通过以下方式与回调模式一起使用:

loadImage(
  fileOrBlobOrUrl,
  function (img) {
    document.body.appendChild(img)
  },
  {
    maxWidth: 600,
    maxHeight: 300,
    minWidth: 100,
    minHeight: 50,
    canvas: true
  }
)

或者使用基于 Promise API 的以下方式:

loadImage(fileOrBlobOrUrl, {
  maxWidth: 600,
  maxHeight: 300,
  minWidth: 100,
  minHeight: 50,
  canvas: true
}).then(function (data) {
  document.body.appendChild(data.image)
})

所有配置都是可选的。默认情况下,图像作为 HTML img 元素返回,没有任何图像大小限制。

maxWidth

定义 img/img 元素的最大宽度。

maxHeight

定义 img/img 元素的最大高度。

minWidth

定义 img/img 元素的最小宽度。

minHeight

定义 img/img 元素的最小高度。

sourceWidth

原图像中需要绘制到 canvas 画布的宽度。

默认为原图像宽度,并且要求 canvas: true

sourceHeight

原图像中需要绘制到 canvas 画布的高度。

默认为原图像高度,并且要求 canvas: true

top

原图像在绘制时的 top margin 偏移量。

默认为 0,并且要求 canvas: true

原图像在绘制时的 right margin 偏移量。

默认为 0,并且要求 canvas: true

bottom

原图像在绘制时的 bottom margin 偏移量。

默认为 0,并且要求 canvas: true

left

原图像在绘制时的 left margin 偏移量。

默认为 0,并且要求 canvas: true

contain

如果设置为 true,则缩放/拉伸图像以将其以留白方式填充至最大尺寸。

即模拟 CSS 功能 background-image: contain

cover

如果设置为 true,则缩放/拉伸图像以将其以裁切方式填充至最大尺寸。

即模拟 CSS 功能 background-image: cover

aspectRatio

将图像裁剪为给定的纵横比(例如 16/9)。

设置 aspectRatio 也会同时启用 crop 选项。

pixelRatio

定义 canvas 像素与屏幕上图像物理像素的比率。

除非缩放的图像在屏幕上渲染不出来,否则应设置为 window.devicePixelRatio

默认为 1 并且要求 canvas: true

downsamplingRatio

定义图像的下采样的比率(即图像缩小阶梯)。

默认情况下,图像下采样每次一阶。

例如,在比率为 0.5 的情况下,每一阶都将图像缩放到一半大小,直到达到目标尺寸。

要求 canvas: true

imageSmoothingEnabled

如果设为 false禁用图像平滑处理

默认为 true 并且要求 canvas: true

imageSmoothingQuality

设置图像平滑度

可选取值:'low''medium''high'

默认为 'low' 并且要求 canvas: true

crop

如果设为 true,则将图像裁剪为 maxWidth/maxHeight 所设置的尺寸。

启用 crop 选项也会一并启用 canvas 选项。

orientation

根据图片的 Exif 方向变换 canvas,取值范围可以是 18 之间的整数,或布尔值 true

当设置为 true 时,在图像具备 Exif 数据的情况下,图像可以自动调整方向为 Exif 数据所设置的方向值。

以字母 F 为例,Exif 各方向值对应的图像显示如下:

1             2
  ██████        ██████
  ██                ██
  ████            ████
  ██                ██
  ██                ██

    3             4
      ██        ██
      ██        ██
    ████        ████
      ██        ██
  ██████        ██████

    5             6
██████████    ██
██  ██        ██  ██
██            ██████████

    7             8
        ██    ██████████
    ██  ██        ██  ██
██████████            ██
meta

如果设为 true,则自动解析图像元数据。

如果存在元数据,那么传给回调函数的第二个参数,该 data 对象中会额外添加上这些元数据属性(请参阅元数据解析)。

如果图片是以 URL 方式加载的,并且浏览器支持 fetch API 或 XHR responseType blob,那么将文件处理为 Blob,以便能够解析元数据。

canvas

如果设为 canvas,则将图像作为 canvas 元素返回。

crossOrigin

img 元素上设置 crossOrigin 属性以启用跨域图像加载功能。

noRevoke

一般图像在加载时会创建一个 URL 对象,默认情况下,加载完毕后便会销毁,若此选项设为 true 则不销毁。

元数据解析


如果引用了元数据扩展(load-image.all.min.js / load-image-meta.js),那么便可以使用 meta 选项自动解析图像元数据:

loadImage(
  fileOrBlobOrUrl,
  function (img, data) {
    console.log('Original image head: ', data.imageHead)
    console.log('Exif data: ', data.exif) // requires exif extension
    console.log('IPTC data: ', data.iptc) // requires iptc extension
  },
  { meta: true }
)

又或者通过 loadImage.parseMetaData,该方法在调用时其第一个参数可以传一个有效的 FileBlob 对象。

loadImage.parseMetaData(
  fileOrBlob,
  function (data) {
    console.log('Original image head: ', data.imageHead)
    console.log('Exif data: ', data.exif) // requires exif extension
    console.log('IPTC data: ', data.iptc) // requires iptc extension
  },
  {
    maxMetaDataSize: 262144
  }
)

再或者使用基于 Promise 的 API:

loadImage
  .parseMetaData(fileOrBlob, {
    maxMetaDataSize: 262144
  })
  .then(function (data) {
    console.log('Original image head: ', data.imageHead)
    console.log('Exif data: ', data.exif) // requires exif extension
    console.log('IPTC data: ', data.iptc) // requires iptc extension
  })

另外对于 parseMetaData 方法还有一些扩展选项:

  • maxMetaDataSize:元数据所能解析的最大字节数。
  • disableImageHead:禁止解析原始图像头。
  • disableMetaDataParsers:禁止解析元数据(仅限图像头)。

图像头

JPEG 图像在调整大小后可以通过 loadImage.replaceHead 保留原图像头,调用该方法需要传递调整图像的 Blob 对象作为首参,以及一个 ArrayBuffer 图像头作为第二参数。

回调模式下,第三个参数必须是一个callback函数,该函数在调用时需要传递一个新的 Blob 对象:

loadImage(
  fileOrBlobOrUrl,
  function (img, data) {
    if (data.imageHead) {
      img.toBlob(function (blob) {
        loadImage.replaceHead(blob, data.imageHead, function (newBlob) {
          // do something with the new Blob object
        })
      }, 'image/jpeg')
    }
  },
  { meta: true, canvas: true, maxWidth: 800 }
)

或者可以使用基于 Promise 的 API,如下所示:

loadImage(fileOrBlobOrUrl, { meta: true, canvas: true, maxWidth: 800 })
  .then(function (data) {
    if (!data.imageHead) throw new Error('Could not parse image metadata')
    return new Promise(function (resolve) {
      data.image.toBlob(function (blob) {
        data.blob = blob
        resolve(data)
      }, 'image/jpeg')
    })
  })
  .then(function (data) {
    return loadImage.replaceHead(data.blob, data.imageHead)
  })
  .then(function (blob) {
    // do something with the new Blob object
  })
  .catch(function (err) {
    console.error(err)
  })
请注意:
可以通过 HTMLCanvasElement.toBlob 创建调整图像大小的 Blob 对象。
blueimp-canvas-to-blob 为不支持原生 canvas.toBlob() 的浏览器提供了一个 polyfill。

Exif 解析器

如果你引用了 Exif 解析器扩展(load-image.all.min.js / load-image-exif.js),那么在 parseMetaData 中回调的参数里,将囊括以下属性(如果对应图像中能找到 Exif 数据):

  • exif:解析的 Exif 标签
  • exifOffsets:解析后的 Exif 标签偏移量
  • exifTiffOffset:TIFF 标头偏移(用于偏移指针)
  • exifLittleEndian:数据存放顺序,true 为小端模式,false 为大端模式

exif 对象存储解析后的 Exif 标签:

var orientation = data.exif[0x0112] // Orientation

exifexifOffsets 对象还提供了一个 get() 方法来通过标签的映射名检索标签值 / 偏移量:

var orientation = data.exif.get('Orientation')
var orientationOffset = data.exifOffsets.get('Orientation')

默认情况下,仅以下映射为有效名称:

如果你同时还引用了 Exif Map 库(load-image-exif-map.js),那便能够使用其他的标签映射,并且新增以下三个扩展方法:

  • exif.getText()
  • exif.getName()
  • exif.getAll()
var orientationText = data.exif.getText('Orientation') // e.g. "Rotate 90° CW"

var name = data.exif.getName(0x0112) // "Orientation"

// A map of all parsed tags with their mapped names/text as keys/values:
var allTags = data.exif.getAll()
Exif 缩略图

显示 Exif 元数据中内嵌的缩略图的代码示例:

loadImage(
  fileOrBlobOrUrl,
  function (img, data) {
    var exif = data.exif
    var thumbnail = exif && exif.get('Thumbnail')
    var blob = thumbnail && thumbnail.get('Blob')
    if (blob) {
      loadImage(
        blob,
        function (thumbImage) {
          document.body.appendChild(thumbImage)
        },
        { orientation: exif.get('Orientation') }
      )
    }
  },
  { meta: true }
)
Exif IFD

显示 Exif IFD(Image File Directory译者注:可交换图像文件格式中的循环数据结构)数据的代码示例,其中包含 Exif 指定的 TIFF 标签:

loadImage(
  fileOrBlobOrUrl,
  function (img, data) {
    var exifIFD = data.exif && data.exif.get('Exif')
    if (exifIFD) {
      // Map of all Exif IFD tags with their mapped names/text as keys/values:
      console.log(exifIFD.getAll())
      // A specific Exif IFD tag value:
      console.log(exifIFD.get('UserComment'))
    }
  },
  { meta: true }
)
GPSInfo IFD

显示 Exif IFD 数据的 GPS 信息的代码示例:

loadImage(
  fileOrBlobOrUrl,
  function (img, data) {
    var gpsInfo = data.exif && data.exif.get('GPSInfo')
    if (gpsInfo) {
      // Map of all GPSInfo tags with their mapped names/text as keys/values:
      console.log(gpsInfo.getAll())
      // A specific GPSInfo tag value:
      console.log(gpsInfo.get('GPSLatitude'))
    }
  },
  { meta: true }
)
Interoperability IFD

显示 Exif IFD 数据中的互操作性数据的代码示例:

loadImage(
  fileOrBlobOrUrl,
  function (img, data) {
    var interoperabilityData = data.exif && data.exif.get('Interoperability')
    if (interoperabilityData) {
      // The InteroperabilityIndex tag value:
      console.log(interoperabilityData.get('InteroperabilityIndex'))
    }
  },
  { meta: true }
)
Exif 解析器选项

Exif 解析器添加了些扩展选项:

  • disableExif:为 true 时禁用 Exif 解析。
  • disableExifOffsets:为 true 时禁止存储 Exif 标签偏移值。
  • includeExifTags:所有需要解析的 Exif 标签的映射表(默认包含所有标签,需要排除的除外)。
  • excludeExifTags:需要排除解析的 Exif 标签映射表(默认排除 Exif MakerNote)。

以下代码举例如何仅解析 Orientation,Thumbnail 和 ExifVersion 标签:

loadImage.parseMetaData(
  fileOrBlob,
  function (data) {
    console.log('Exif data: ', data.exif)
  },
  {
    includeExifTags: {
      0x0112: true, // Orientation
      ifd1: {
        0x0201: true, // JPEGInterchangeFormat (Thumbnail data offset)
        0x0202: true // JPEGInterchangeFormatLength (Thumbnail data length)
      },
      0x8769: {
        // ExifIFDPointer
        0x9000: true // ExifVersion
      }
    }
  }
)

排除 Exif MakerNoteGPSInfo 标签的示例:

loadImage.parseMetaData(
  fileOrBlob,
  function (data) {
    console.log('Exif data: ', data.exif)
  },
  {
    excludeExifTags: {
      0x8769: {
        // ExifIFDPointer
        0x927c: true // MakerNote
      },
      0x8825: true // GPSInfoIFDPointer
    }
  }
)

Exif 编辑器

Exif 解析器扩展还包含了一个基础编辑器,它可以修改已解析的imageHead缓存数组中的数据值,下述举例对 Orientation 进行修改:

loadImage(
  fileOrBlobOrUrl,
  function (img, data) {
    if (data.imageHead && data.exif) {
      // Reset Exif Orientation data:
      loadImage.writeExifData(data.imageHead, data, 'Orientation', 1)
      img.toBlob(function (blob) {
        loadImage.replaceHead(blob, data.imageHead, function (newBlob) {
          // do something with newBlob
        })
      }, 'image/jpeg')
    }
  },
  { meta: true, orientation: true, canvas: true, maxWidth: 800 }
)
请注意:
图像在解析后会存为一个 Exif data 数据,而Exit 编辑器是以该数据的标签偏移量作为依据的,其对应data.exifOffsets 属性。
Exif 编辑器只能修改已有值,不能添加新标签。例如,它不能够给一个原本不存在 Orientation 标签的图像添加一个 Orientation 标签。

IPTC 解析器

如果你引用了 IPTC 解析器扩展(load-image-iptc.js),那么 parseMetaData 中回调所传递的参数会包含下列扩展属性,假如在所解析的图像中能找到 IPTC 数据:

  • iptc:所解析的 IPTC 标签
  • iptcOffsets:IPTC 标签偏移量

已解析的 IPTC 标签会保存在 iptc 对象中:

var objectname = data.iptc[5]

iptciptcOffsets 对象还提供了一个 get() 方法来通过标签映射名去检索对应的标签值或偏移量:

var objectname = data.iptc.get('ObjectName')

默认情况下,只存在下面这一个映射名:

  • ObjectName

如果你同时还引用了 IPTC Map 库(load-image-pitch-map.js),那便能映射其他标签,而且还能使用下述三个扩展方法:

  • iptc.getText()
  • iptc.getName()
  • iptc.getAll()
var keywords = data.iptc.getText('Keywords') // e.g.: ['Weather','Sky']

var name = data.iptc.getName(5) // ObjectName

// A map of all parsed tags with their mapped names/text as keys/values:
var allTags = data.iptc.getAll()
IPTC 解析器选项

IPTC 解析器新增了下述扩展选项:

  • disableIptc:设为 true 时禁用 IPTC 解析。
  • disableIptcOffsets:设为 true 时禁止存储 IPTC 标签偏移值。
  • includeIptcTags:需要进行解析的 IPTC 标签映射表(包含所有标签,除了需要排除的)。
  • excludeIptcTags:IPTC 标签中剔除解析的映射表(默认不解析 ObjectPreviewData)。

仅对 ObjectName 标签进行解析的示例:

loadImage.parseMetaData(
  fileOrBlob,
  function (data) {
    console.log('IPTC data: ', data.iptc)
  },
  {
    includeIptcTags: {
      5: true // ObjectName
    }
  }
)

解析时剔除 ApplicationRecordVersionObjectPreviewData 的示例:

loadImage.parseMetaData(
  fileOrBlob,
  function (data) {
    console.log('IPTC data: ', data.iptc)
  },
  {
    excludeIptcTags: {
      0: true, // ApplicationRecordVersion
      202: true // ObjectPreviewData
    }
  }
)