@loaders.gl/images
Version:
Framework-independent loaders and writers for images (PNG, JPG, ...)
657 lines (637 loc) • 22 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if (typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd) define([], factory);
else if (typeof exports === 'object') exports['loaders'] = factory();
else root['loaders'] = factory();})(globalThis, function () {
;
var __exports__ = (() => {
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// external-global-plugin:@loaders.gl/core
var require_core = __commonJS({
"external-global-plugin:@loaders.gl/core"(exports, module) {
module.exports = globalThis.loaders;
}
});
// bundle.ts
var bundle_exports = {};
__export(bundle_exports, {
ImageLoader: () => ImageLoader,
ImageWriter: () => ImageWriter,
getBinaryImageMetadata: () => getBinaryImageMetadata,
getDefaultImageType: () => getDefaultImageType,
getImageData: () => getImageData,
getImageSize: () => getImageSize,
getImageType: () => getImageType,
getSupportedImageFormats: () => getSupportedImageFormats,
isImage: () => isImage,
isImageFormatSupported: () => isImageFormatSupported,
isImageTypeSupported: () => isImageTypeSupported,
loadImage: () => loadImage
});
__reExport(bundle_exports, __toESM(require_core(), 1));
// src/lib/utils/version.ts
var VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : "latest";
// ../loader-utils/src/lib/env-utils/assert.ts
function assert(condition, message) {
if (!condition) {
throw new Error(message || "loader assertion failed.");
}
}
// ../loader-utils/src/lib/env-utils/globals.ts
var globals = {
self: typeof self !== "undefined" && self,
window: typeof window !== "undefined" && window,
global: typeof global !== "undefined" && global,
document: typeof document !== "undefined" && document
};
var self_ = globals.self || globals.window || globals.global || {};
var window_ = globals.window || globals.self || globals.global || {};
var global_ = globals.global || globals.self || globals.window || {};
var document_ = globals.document || {};
var isBrowser = (
// @ts-ignore process does not exist on browser
Boolean(typeof process !== "object" || String(process) !== "[object process]" || process.browser)
);
var matches = typeof process !== "undefined" && process.version && /v([0-9]*)/.exec(process.version);
var nodeVersion = matches && parseFloat(matches[1]) || 0;
// src/lib/category-api/image-type.ts
var parseImageNode = globalThis.loaders?.parseImageNode;
var IMAGE_SUPPORTED = typeof Image !== "undefined";
var IMAGE_BITMAP_SUPPORTED = typeof ImageBitmap !== "undefined";
var NODE_IMAGE_SUPPORTED = Boolean(parseImageNode);
var DATA_SUPPORTED = isBrowser ? true : NODE_IMAGE_SUPPORTED;
function isImageTypeSupported(type) {
switch (type) {
case "auto":
return IMAGE_BITMAP_SUPPORTED || IMAGE_SUPPORTED || DATA_SUPPORTED;
case "imagebitmap":
return IMAGE_BITMAP_SUPPORTED;
case "image":
return IMAGE_SUPPORTED;
case "data":
return DATA_SUPPORTED;
default:
throw new Error(`@loaders.gl/images: image ${type} not supported in this environment`);
}
}
function getDefaultImageType() {
if (IMAGE_BITMAP_SUPPORTED) {
return "imagebitmap";
}
if (IMAGE_SUPPORTED) {
return "image";
}
if (DATA_SUPPORTED) {
return "data";
}
throw new Error("Install '@loaders.gl/polyfills' to parse images under Node.js");
}
// src/lib/category-api/parsed-image-api.ts
function isImage(image) {
return Boolean(getImageTypeOrNull(image));
}
function getImageType(image) {
const format = getImageTypeOrNull(image);
if (!format) {
throw new Error("Not an image");
}
return format;
}
function getImageSize(image) {
return getImageData(image);
}
function getImageData(image) {
switch (getImageType(image)) {
case "data":
return image;
case "image":
case "imagebitmap":
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
if (!context) {
throw new Error("getImageData");
}
canvas.width = image.width;
canvas.height = image.height;
context.drawImage(image, 0, 0);
return context.getImageData(0, 0, image.width, image.height);
default:
throw new Error("getImageData");
}
}
function getImageTypeOrNull(image) {
if (typeof ImageBitmap !== "undefined" && image instanceof ImageBitmap) {
return "imagebitmap";
}
if (typeof Image !== "undefined" && image instanceof Image) {
return "image";
}
if (image && typeof image === "object" && image.data && image.width && image.height) {
return "data";
}
return null;
}
// src/lib/parsers/svg-utils.ts
var SVG_DATA_URL_PATTERN = /^data:image\/svg\+xml/;
var SVG_URL_PATTERN = /\.svg((\?|#).*)?$/;
function isSVG(url) {
return url && (SVG_DATA_URL_PATTERN.test(url) || SVG_URL_PATTERN.test(url));
}
function getBlobOrSVGDataUrl(arrayBuffer, url) {
if (isSVG(url)) {
const textDecoder = new TextDecoder();
let xmlText = textDecoder.decode(arrayBuffer);
try {
if (typeof unescape === "function" && typeof encodeURIComponent === "function") {
xmlText = unescape(encodeURIComponent(xmlText));
}
} catch (error) {
throw new Error(error.message);
}
const src = `data:image/svg+xml;base64,${btoa(xmlText)}`;
return src;
}
return getBlob(arrayBuffer, url);
}
function getBlob(arrayBuffer, url) {
if (isSVG(url)) {
throw new Error("SVG cannot be parsed directly to imagebitmap");
}
return new Blob([new Uint8Array(arrayBuffer)]);
}
// src/lib/parsers/parse-to-image.ts
async function parseToImage(arrayBuffer, options, url) {
const blobOrDataUrl = getBlobOrSVGDataUrl(arrayBuffer, url);
const URL = self.URL || self.webkitURL;
const objectUrl = typeof blobOrDataUrl !== "string" && URL.createObjectURL(blobOrDataUrl);
try {
return await loadToImage(objectUrl || blobOrDataUrl, options);
} finally {
if (objectUrl) {
URL.revokeObjectURL(objectUrl);
}
}
}
async function loadToImage(url, options) {
const image = new Image();
image.src = url;
if (options.image && options.image.decode && image.decode) {
await image.decode();
return image;
}
return await new Promise((resolve, reject) => {
try {
image.onload = () => resolve(image);
image.onerror = (error) => {
const message = error instanceof Error ? error.message : "error";
reject(new Error(message));
};
} catch (error) {
reject(error);
}
});
}
// src/lib/parsers/parse-to-image-bitmap.ts
var EMPTY_OBJECT = {};
var imagebitmapOptionsSupported = true;
async function parseToImageBitmap(arrayBuffer, options, url) {
let blob;
if (isSVG(url)) {
const image = await parseToImage(arrayBuffer, options, url);
blob = image;
} else {
blob = getBlob(arrayBuffer, url);
}
const imagebitmapOptions = options && options.imagebitmap;
return await safeCreateImageBitmap(blob, imagebitmapOptions);
}
async function safeCreateImageBitmap(blob, imagebitmapOptions = null) {
if (isEmptyObject(imagebitmapOptions) || !imagebitmapOptionsSupported) {
imagebitmapOptions = null;
}
if (imagebitmapOptions) {
try {
return await createImageBitmap(blob, imagebitmapOptions);
} catch (error) {
console.warn(error);
imagebitmapOptionsSupported = false;
}
}
return await createImageBitmap(blob);
}
function isEmptyObject(object) {
for (const key in object || EMPTY_OBJECT) {
return false;
}
return true;
}
// src/lib/category-api/parse-isobmff-binary.ts
function getISOBMFFMediaType(buffer) {
if (!checkString(buffer, "ftyp", 4)) {
return null;
}
if ((buffer[8] & 96) === 0) {
return null;
}
return decodeMajorBrand(buffer);
}
function decodeMajorBrand(buffer) {
const brandMajor = getUTF8String(buffer, 8, 12).replace("\0", " ").trim();
switch (brandMajor) {
case "avif":
case "avis":
return { extension: "avif", mimeType: "image/avif" };
default:
return null;
}
}
function getUTF8String(array, start, end) {
return String.fromCharCode(...array.slice(start, end));
}
function stringToBytes(string) {
return [...string].map((character) => character.charCodeAt(0));
}
function checkString(buffer, header, offset = 0) {
const headerBytes = stringToBytes(header);
for (let i = 0; i < headerBytes.length; ++i) {
if (headerBytes[i] !== buffer[i + offset]) {
return false;
}
}
return true;
}
// src/lib/category-api/binary-image-api.ts
var BIG_ENDIAN = false;
var LITTLE_ENDIAN = true;
function getBinaryImageMetadata(binaryData) {
const dataView = toDataView(binaryData);
return getPngMetadata(dataView) || getJpegMetadata(dataView) || getGifMetadata(dataView) || getBmpMetadata(dataView) || getISOBMFFMetadata(dataView);
}
function getISOBMFFMetadata(binaryData) {
const buffer = new Uint8Array(binaryData instanceof DataView ? binaryData.buffer : binaryData);
const mediaType = getISOBMFFMediaType(buffer);
if (!mediaType) {
return null;
}
return {
mimeType: mediaType.mimeType,
// TODO - decode width and height
width: 0,
height: 0
};
}
function getPngMetadata(binaryData) {
const dataView = toDataView(binaryData);
const isPng = dataView.byteLength >= 24 && dataView.getUint32(0, BIG_ENDIAN) === 2303741511;
if (!isPng) {
return null;
}
return {
mimeType: "image/png",
width: dataView.getUint32(16, BIG_ENDIAN),
height: dataView.getUint32(20, BIG_ENDIAN)
};
}
function getGifMetadata(binaryData) {
const dataView = toDataView(binaryData);
const isGif = dataView.byteLength >= 10 && dataView.getUint32(0, BIG_ENDIAN) === 1195984440;
if (!isGif) {
return null;
}
return {
mimeType: "image/gif",
width: dataView.getUint16(6, LITTLE_ENDIAN),
height: dataView.getUint16(8, LITTLE_ENDIAN)
};
}
function getBmpMetadata(binaryData) {
const dataView = toDataView(binaryData);
const isBmp = dataView.byteLength >= 14 && dataView.getUint16(0, BIG_ENDIAN) === 16973 && dataView.getUint32(2, LITTLE_ENDIAN) === dataView.byteLength;
if (!isBmp) {
return null;
}
return {
mimeType: "image/bmp",
width: dataView.getUint32(18, LITTLE_ENDIAN),
height: dataView.getUint32(22, LITTLE_ENDIAN)
};
}
function getJpegMetadata(binaryData) {
const dataView = toDataView(binaryData);
const isJpeg = dataView.byteLength >= 3 && dataView.getUint16(0, BIG_ENDIAN) === 65496 && dataView.getUint8(2) === 255;
if (!isJpeg) {
return null;
}
const { tableMarkers, sofMarkers } = getJpegMarkers();
let i = 2;
while (i + 9 < dataView.byteLength) {
const marker = dataView.getUint16(i, BIG_ENDIAN);
if (sofMarkers.has(marker)) {
return {
mimeType: "image/jpeg",
height: dataView.getUint16(i + 5, BIG_ENDIAN),
// Number of lines
width: dataView.getUint16(i + 7, BIG_ENDIAN)
// Number of pixels per line
};
}
if (!tableMarkers.has(marker)) {
return null;
}
i += 2;
i += dataView.getUint16(i, BIG_ENDIAN);
}
return null;
}
function getJpegMarkers() {
const tableMarkers = /* @__PURE__ */ new Set([65499, 65476, 65484, 65501, 65534]);
for (let i = 65504; i < 65520; ++i) {
tableMarkers.add(i);
}
const sofMarkers = /* @__PURE__ */ new Set([
65472,
65473,
65474,
65475,
65477,
65478,
65479,
65481,
65482,
65483,
65485,
65486,
65487,
65502
]);
return { tableMarkers, sofMarkers };
}
function toDataView(data) {
if (data instanceof DataView) {
return data;
}
if (ArrayBuffer.isView(data)) {
return new DataView(data.buffer);
}
if (data instanceof ArrayBuffer) {
return new DataView(data);
}
throw new Error("toDataView");
}
// src/lib/parsers/parse-to-node-image.ts
async function parseToNodeImage(arrayBuffer, options) {
const { mimeType } = getBinaryImageMetadata(arrayBuffer) || {};
const parseImageNode2 = globalThis.loaders?.parseImageNode;
assert(parseImageNode2);
return await parseImageNode2(arrayBuffer, mimeType);
}
// src/lib/parsers/parse-image.ts
async function parseImage(arrayBuffer, options, context) {
options = options || {};
const imageOptions = options.image || {};
const imageType = imageOptions.type || "auto";
const { url } = context || {};
const loadType = getLoadableImageType(imageType);
let image;
switch (loadType) {
case "imagebitmap":
image = await parseToImageBitmap(arrayBuffer, options, url);
break;
case "image":
image = await parseToImage(arrayBuffer, options, url);
break;
case "data":
image = await parseToNodeImage(arrayBuffer, options);
break;
default:
assert(false);
}
if (imageType === "data") {
image = getImageData(image);
}
return image;
}
function getLoadableImageType(type) {
switch (type) {
case "auto":
case "data":
return getDefaultImageType();
default:
isImageTypeSupported(type);
return type;
}
}
// src/image-loader.ts
var EXTENSIONS = ["png", "jpg", "jpeg", "gif", "webp", "bmp", "ico", "svg", "avif"];
var MIME_TYPES = [
"image/png",
"image/jpeg",
"image/gif",
"image/webp",
"image/avif",
"image/bmp",
"image/vnd.microsoft.icon",
"image/svg+xml"
];
var DEFAULT_IMAGE_LOADER_OPTIONS = {
image: {
type: "auto",
decode: true
// if format is HTML
}
// imagebitmap: {} - passes (platform dependent) parameters to ImageBitmap constructor
};
var ImageLoader = {
dataType: null,
batchType: null,
id: "image",
module: "images",
name: "Images",
version: VERSION,
mimeTypes: MIME_TYPES,
extensions: EXTENSIONS,
parse: parseImage,
// TODO: byteOffset, byteLength;
tests: [(arrayBuffer) => Boolean(getBinaryImageMetadata(new DataView(arrayBuffer)))],
options: DEFAULT_IMAGE_LOADER_OPTIONS
};
// src/lib/encoders/encode-image.ts
var encodeImageNode = globalThis.loaders?.encodeImageNode;
async function encodeImage(image, options) {
options = options || {};
options.image = options.image || {};
return encodeImageNode ? encodeImageNode(image, { type: options.image.mimeType }) : encodeImageInBrowser(image, options);
}
var qualityParamSupported = true;
async function encodeImageInBrowser(image, options) {
const { mimeType, jpegQuality } = options.image;
const { width, height } = getImageSize(image);
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
drawImageToCanvas(image, canvas);
const blob = await new Promise((resolve) => {
if (jpegQuality && qualityParamSupported) {
try {
canvas.toBlob(resolve, mimeType, jpegQuality);
return;
} catch (error) {
qualityParamSupported = false;
}
}
canvas.toBlob(resolve, mimeType);
});
if (!blob) {
throw new Error("image encoding failed");
}
return await blob.arrayBuffer();
}
function drawImageToCanvas(image, canvas, x = 0, y = 0) {
if (x === 0 && y === 0 && typeof ImageBitmap !== "undefined" && image instanceof ImageBitmap) {
const context2 = canvas.getContext("bitmaprenderer");
if (context2) {
context2.transferFromImageBitmap(image);
return canvas;
}
}
const context = canvas.getContext("2d");
if (image.data) {
const clampedArray = new Uint8ClampedArray(image.data);
const imageData = new ImageData(clampedArray, image.width, image.height);
context.putImageData(imageData, 0, 0);
return canvas;
}
context.drawImage(image, 0, 0);
return canvas;
}
// src/image-writer.ts
var ImageWriter = {
name: "Images",
id: "image",
module: "images",
version: VERSION,
extensions: ["jpeg"],
options: {
image: {
mimeType: "image/png",
jpegQuality: null
}
},
encode: encodeImage
};
// src/lib/category-api/image-format.ts
var MIME_TYPES2 = [
"image/png",
"image/jpeg",
"image/gif",
"image/webp",
"image/avif",
"image/tiff",
// TODO - what is the correct type for SVG
"image/svg",
"image/svg+xml",
"image/bmp",
"image/vnd.microsoft.icon"
];
var mimeTypeSupportedPromise = null;
async function getSupportedImageFormats() {
if (mimeTypeSupportedPromise) {
return await mimeTypeSupportedPromise;
}
const supportedMimeTypes = /* @__PURE__ */ new Set();
for (const mimeType of MIME_TYPES2) {
const supported = isBrowser ? await checkBrowserImageFormatSupportAsync(mimeType) : checkNodeImageFormatSupport(mimeType);
if (supported) {
supportedMimeTypes.add(mimeType);
}
}
return supportedMimeTypes;
}
var mimeTypeSupportedSync = {};
function isImageFormatSupported(mimeType) {
if (mimeTypeSupportedSync[mimeType] === void 0) {
const supported = isBrowser ? checkBrowserImageFormatSupport(mimeType) : checkNodeImageFormatSupport(mimeType);
mimeTypeSupportedSync[mimeType] = supported;
}
return mimeTypeSupportedSync[mimeType];
}
function checkNodeImageFormatSupport(mimeType) {
const NODE_FORMAT_SUPPORT = ["image/png", "image/jpeg", "image/gif"];
const imageFormatsNode = globalThis.loaders?.imageFormatsNode || NODE_FORMAT_SUPPORT;
const parseImageNode2 = globalThis.loaders?.parseImageNode;
return Boolean(parseImageNode2) && imageFormatsNode.includes(mimeType);
}
function checkBrowserImageFormatSupport(mimeType) {
switch (mimeType) {
case "image/avif":
case "image/webp":
return testBrowserImageFormatSupport(mimeType);
default:
return true;
}
}
var TEST_IMAGE = {
"image/avif": "data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=",
// Lossy test image. Support for lossy images doesn't guarantee support for all WebP images.
"image/webp": "data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA"
};
async function checkBrowserImageFormatSupportAsync(mimeType) {
const dataURL = TEST_IMAGE[mimeType];
return dataURL ? await testBrowserImageFormatSupportAsync(dataURL) : true;
}
function testBrowserImageFormatSupport(mimeType) {
try {
const element = document.createElement("canvas");
const dataURL = element.toDataURL(mimeType);
return dataURL.indexOf(`data:${mimeType}`) === 0;
} catch {
return false;
}
}
async function testBrowserImageFormatSupportAsync(testImageDataURL) {
return new Promise((resolve) => {
const image = new Image();
image.src = testImageDataURL;
image.onload = () => resolve(image.height > 0);
image.onerror = () => resolve(false);
});
}
// src/index.ts
function loadImage() {
throw new Error("loadImage has moved to @loaders.gl/textures");
}
return __toCommonJS(bundle_exports);
})();
return __exports__;
});