voluptasquisquam
Version:
Image processing and manipulation in JavaScript
157 lines (141 loc) • 4.63 kB
JavaScript
import Image from './Image';
import Stack from '../stack/Stack';
import {fetchBinary, DOMImage, Canvas, isDifferentOrigin} from './environment';
import {PNGDecoder} from 'fast-png';
import {decode as decodeJpeg} from 'fast-jpeg';
import {decode as decodeTiff} from 'tiff';
import imageType from 'image-type';
import {decode as base64Decode} from '../util/base64';
const isDataURL = /^data:[a-z]+\/([a-z]+);base64,/;
function swap16(val) {
return ((val & 0xFF) << 8) | ((val >> 8) & 0xFF);
}
export function loadImage(image, options) {
if (typeof image === 'string') {
return loadURL(image, options);
} else if (image instanceof ArrayBuffer) {
return Promise.resolve(loadBinary(new Uint8Array(image), options));
} else if (image.buffer) {
return Promise.resolve(loadBinary(image, options));
} else {
throw new Error('argument to "load" must be a string or buffer.');
}
}
function loadBinary(image, options, url) {
const type = imageType(image);
if (type) {
switch (type.ext) {
case 'png':
return loadPNG(image);
case 'jpg': {
const decoded = decodeJpeg(image);
let meta;
if (decoded.exif) {
meta = getMetadata(decoded.exif);
}
return loadFromURL(url, {meta});
}
case 'tif':
return loadTIFF(image);
// no default
}
}
return loadFromURL(url);
function loadFromURL(url, options) {
if (!url) {
throw new Error(`This kind of image (${type}) can only be loaded from URL.`);
}
return loadGeneric(url, options);
}
}
function loadURL(url, options) {
const dataURL = url.slice(0, 64).match(isDataURL);
let binaryDataP;
if (dataURL) {
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, options, url);
});
}
function loadPNG(data) {
const decoder = new PNGDecoder(data);
const png = decoder.decode();
const bitDepth = png.bitDepth;
const buffer = png.data.buffer;
let bitmap;
if (bitDepth === 8) {
bitmap = new Uint8ClampedArray(buffer);
} else if (bitDepth === 16) {
bitmap = new Uint16Array(buffer);
for (let i = 0; i < bitmap.length; i++) {
bitmap[i] = swap16(bitmap[i]);
}
}
const type = png.colourType;
let components;
let alpha = 0;
switch (type) {
case 0: components = 1; break;
case 2: components = 3; break;
case 4: components = 1; alpha = 1; break;
case 6: components = 3; alpha = 1; break;
default: throw new Error(`Unexpected colourType: ${type}`);
}
return new Image(png.width, png.height, bitmap, {components, alpha, bitDepth});
}
function loadTIFF(data) {
let result = decodeTiff(data);
if (result.length === 1) {
return getImageFromIFD(result[0]);
} else {
return new Stack(result.map(getImageFromIFD));
}
}
function getMetadata(image) {
const metadata = {
tiff: image
};
if (image.exif) {
metadata.exif = image.exif;
}
if (image.gps) {
metadata.gps = image.gps;
}
return metadata;
}
function getImageFromIFD(image) {
return new Image(image.width, image.height, image.data, {
components: 1,
alpha: 0,
colorModel: null,
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();
if (isDifferentOrigin(url)) {
// see https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
image.crossOrigin = 'Anonymous';
}
image.onload = function () {
let w = image.width;
let h = image.height;
let canvas = new Canvas(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;
});
}