UNPKG

image-js

Version:

Image processing and manipulation in JavaScript

260 lines (242 loc) 7.14 kB
import { decode as decodeJpegExif } from 'fast-jpeg'; import { decode as decodePng } from 'fast-png'; import imageType from 'image-type'; import { decode as decodeJpeg } from 'jpeg-js'; import { decode as decodeTiff } from 'tiff'; import Stack from '../../stack/Stack'; import { decode as base64Decode, toBase64URL } from '../../util/base64'; import Image from '../Image'; import { GREY, RGB } from '../model/model'; import { fetchBinary, DOMImage, createCanvas } from './environment'; const isDataURL = /^data:[a-z]+\/(?:[a-z]+);base64,/; /** * Load an image * @memberof Image * @static * @param {string|ArrayBuffer|Buffer|Uint8Array} image - URL of the image (browser, can be a dataURL) or path (Node.js) * or buffer containing the binary data * @param {object} [options] - In the browser, the options object is passed to the underlying `fetch` call, along with * the data URL. For binary data, the option specify decoding options. * @param {boolean} [options.ignorePalette] - When set to true and loading a tiff from binary data, if the tiff is of * type 3 (palette), load as single channel greyscale rather than as a pseudo-colored RGB. * @return {Promise<Image>} * @example * const image = await Image.load('https://example.com/image.png'); */ export default function load(image, options) { if (typeof image === 'string') { return loadURL(image, options); } else if (image instanceof ArrayBuffer) { return Promise.resolve( loadBinary( new Uint8Array(image), undefined, options && options.ignorePalette, ), ); } else if (image.buffer) { return Promise.resolve( loadBinary(image, undefined, options && options.ignorePalette), ); } else { throw new Error('argument to "load" must be a string or buffer.'); } } function loadBinary(image, base64Url, ignorePalette) { const type = imageType(image); if (type) { switch (type.mime) { case 'image/png': return loadPNG(image); case 'image/jpeg': return loadJPEG(image); case 'image/tiff': return loadTIFF(image, ignorePalette); default: return loadGeneric(getBase64(type.mime)); } } return loadGeneric(getBase64('application/octet-stream')); function getBase64(type) { if (base64Url) { return base64Url; } else { return toBase64URL(image, type); } } } function loadURL(url, options) { const dataURL = url.slice(0, 64).match(isDataURL); let binaryDataP; if (dataURL !== null) { binaryDataP = Promise.resolve(base64Decode(url.slice(dataURL[0].length))); } else { binaryDataP = fetchBinary(url, options); } return binaryDataP.then((binaryData) => { const uint8 = new Uint8Array(binaryData); return loadBinary( uint8, dataURL ? url : undefined, options && options.ignorePalette, ); }); } function loadPNG(data) { const png = decodePng(data); let channels = png.channels; let components; let alpha = 0; if (channels === 2 || channels === 4) { components = channels - 1; alpha = 1; } else { components = channels; } if (png.palette) { return loadPNGFromPalette(png); } return new Image(png.width, png.height, png.data, { components, alpha, bitDepth: png.depth, meta: { text: png.text }, }); } function loadPNGFromPalette(png) { const pixels = png.width * png.height; const channels = png.palette[0].length; const data = new Uint8Array(pixels * channels); const pixelsPerByte = 8 / png.depth; const factor = png.depth < 8 ? pixelsPerByte : 1; const mask = parseInt('1'.repeat(png.depth), 2); const hasAlpha = channels === 4; let dataIndex = 0; for (let i = 0; i < pixels; i++) { const index = Math.floor(i / factor); let value = png.data[index]; if (png.depth < 8) { value = (value >>> (png.depth * (pixelsPerByte - 1 - (i % pixelsPerByte)))) & mask; } const paletteValue = png.palette[value]; data[dataIndex++] = paletteValue[0]; data[dataIndex++] = paletteValue[1]; data[dataIndex++] = paletteValue[2]; if (hasAlpha) { data[dataIndex++] = paletteValue[3]; } } return new Image(png.width, png.height, data, { components: 3, alpha: hasAlpha, bitDepth: 8, }); } function loadJPEG(data) { const decodedExif = decodeJpegExif(data); let meta; if (decodedExif.exif) { meta = getMetadata(decodedExif.exif); } const jpeg = decodeJpeg(data, { useTArray: true, maxMemoryUsageInMB: 1024 }); let image = new Image(jpeg.width, jpeg.height, jpeg.data, { meta }); if (meta && meta.tiff.tags.Orientation) { const orientation = meta.tiff.tags.Orientation; if (orientation > 2) { image = image.rotate( { 3: 180, 4: 180, 5: 90, 6: 90, 7: 270, 8: 270, }[orientation], ); } if ([2, 4, 5, 7].includes(orientation)) { image = image.flipX(); } } return image; } function loadTIFF(data, ignorePalette) { let result = decodeTiff(data); if (result.length === 1) { return getImageFromIFD(result[0], ignorePalette); } else { return new Stack( result.map(function (image) { return getImageFromIFD(image, ignorePalette); }), ); } } function getMetadata(image) { const metadata = { tiff: { fields: image.fields, tags: image.map, }, }; if (image.exif) { metadata.exif = image.exif; } if (image.gps) { metadata.gps = image.gps; } return metadata; } function getImageFromIFD(image, ignorePalette) { if (!ignorePalette && image.type === 3) { // Palette const data = new Uint16Array(3 * image.width * image.height); const palette = image.palette; let ptr = 0; for (let i = 0; i < image.data.length; i++) { const index = image.data[i]; const color = palette[index]; data[ptr++] = color[0]; data[ptr++] = color[1]; data[ptr++] = color[2]; } return new Image(image.width, image.height, data, { components: 3, alpha: image.alpha, colorModel: RGB, bitDepth: 16, meta: getMetadata(image), }); } else { return new Image(image.width, image.height, image.data, { components: image.type === 2 ? 3 : 1, alpha: image.alpha, colorModel: image.type === 2 ? RGB : GREY, bitDepth: image.bitsPerSample.length ? image.bitsPerSample[0] : image.bitsPerSample, meta: getMetadata(image), }); } } function loadGeneric(url, options) { options = options || {}; return new Promise(function (resolve, reject) { let image = new DOMImage(); image.onload = function () { let w = image.width; let h = image.height; let canvas = createCanvas(w, h); let ctx = canvas.getContext('2d'); ctx.drawImage(image, 0, 0, w, h); let data = ctx.getImageData(0, 0, w, h).data; resolve(new Image(w, h, data, options)); }; image.onerror = function () { reject(new Error(`Could not load ${url}`)); }; image.src = url; }); }