Skip to content

Commit

Permalink
Merge 56902bc into c8e6bf3
Browse files Browse the repository at this point in the history
  • Loading branch information
ibgreen committed Sep 8, 2019
2 parents c8e6bf3 + 56902bc commit c3a21a7
Show file tree
Hide file tree
Showing 20 changed files with 361 additions and 43 deletions.
85 changes: 85 additions & 0 deletions docs/specifications/category-image.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Category: Image

> The Image Category is being defined for loaders.gl v2.0 and is currently experimental and unstable.
The image category documents a common data format, options, conventions and utilities for loader and writers for images that follow loaders.gl conventions.

Image category loaders includes: `JPEGLoader`, `PNGLoader`, `GIFLoader`, `BMPLoader`, `SVGLoader` and of course all the loaders in the `ImageLoaders` composite loader.

## Features and Capabilities

Apart from providing a set of image loaders that integrate with loaders.gl, there are a number of capabilities that are provided for

- Worka under both node and browser.
- Transparently uses ImageBitmap on supporting browsers
- Loads images on workers
- Handles SVG images
- Image type detection (without loading images)


## Installation and Usage

Image category support is bundled in the `@loaders.gl/images` module:

```bash
npm install @loaders.gl/core @loaders.gl/images
```

Individual loaders for specific image formats can be imported for `@loaders.gl/images`:
```js
import {JEPGLoader, PNGLoader} from '@loaders.gl/images';
import {registerLoaders, load} from '@loaders.gl/core';
registerLoaders([JEPGLoader, PNGLoader]);
const image = await load('image.jpeg');
```

However since each image loader is quite small (in terms of code size and bundle size impact), most applications will just install all image loaders in one go:

```js
import {ImageLoaders} from '@loaders.gl/images';
import {registerLoaders, load} from '@loaders.gl/core';
registerLoaders(ImageLoaders);
const image = await load('image.jpeg');
```

## Data Formats

The loaded image representation can vary somewhat based on your environment. For performance, image loaders use native image loading functionality in browsers. Browsers can load into two types of image classes (`ImageBitmap` and `HTMLImageElement`) and on Node.js images are represented using `ndarray`. The following table summarizes the situation:

| Format Name | Format | Availability | Workers | Description |
| --- | --- | --- | --- | --- |
| `imagebitmap` | `ImageBitmap` | Chrome/Firefox | Yes: **transferrable** | A newer class designed for efficient loading of images for use with WebGL |
| `html` | `Image` (aka `HTMLImageElement`) | All browsers | No | The original HTML class used for image loading into DOM trees. WebGL compatible. |
| `ndarray` | ndarray | Node only, via `@loaders.gl/polyfills` | No | Used to load images under node. Compatible with headless gl. |


## Options

The image category support some generic options (specified using `options.image.<option-name>`), that are applicable to all (or most) image loaders.

| Option | Default | Type | Availability | Description |
| --- | --- | --- | --- | --- |
| `options.image.format` | `'auto'` | string | See table | One of `auto`, `imagebitmap`, `html`, `ndarray` |
| `options.image.decodeHTML` | `true` | boolean | No: Edge, IE11 | Wait for HTMLImages to be fully decoded. |
| `options.image.crossOrigin` | `'anonymous'` | boolean | All Browsers | Sets `crossOrigin` field for HTMLImage loads |
| `options.image.useWorkers` (TBA) | `true` | boolean | Chrome, Firefox | If true, uses worker loaders on supported platforms. |


## Notes

### About worker support

- Worker loading is only supported for the `imagebitmap` format (on Chrome and Firefox).
- `ImageBitmap` is **transferrable** and can be moved back to main thread without copying.ß
- There should be no technical limitations to loading images on workers in node, however node workers are not supported yet.

In contrast to other modules, where worker loaders have to be separately installed, since image workers are small and worker loading is only available on some browsers, the image loaders dynamically determines if worker loading is available.
Use `options.image.useWorkers: false` to disable worker loading of images on all platforms.


## Utilities

The image category also provides a few utilities:

- Detecting ("sniffing") mime type and size of image files before parsing them
- Getting image data (arrays of pixels) from an image without knowing which type was loaded (TBA)
18 changes: 18 additions & 0 deletions docs/whats-new.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# What's New

## v2.0 (no release avaialble, experimental parts accessible in v1.4-alpha)

- `@loaders.gl/images`: Redefined as a new Image Category, see below
- `@loaders.gl/core`: `loader.loadAndParse` deprecated.


### @loaders.gl/images
- **Image Category** now defined
- Now have a separate micro loader for each format: `JPEGLoader`, `PNGLoader`, `GIFLoader`, `BMPLoader`, `SVGLoader`
- Category ensures interchangability
- Composite loader (Array) `ImageLoaders` for easy registration.
- No longer uses `loadAndParse`, even loads from URLs are blobified and object URL:ed.
- `options.image` contain common options that apply across the category
- Ability to control loaded image format `options.image.format`, default `auto`
- Worker Image Loaders on Chrome and Firefox `options.image.useWorkers: true`
- Support for `Image.decode()` to ensure images are ready to go when loader promise resolves: `options.image.decodeHTML: true`


## v1.2 (In Development, alpha/beta releases available)

Release Date: Aug 8, 2019
Expand Down
3 changes: 2 additions & 1 deletion modules/images/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
],
"scripts": {
"pre-build": "npm run build-bundle && npm run build-bundle -- --env.dev",
"build-bundle": "webpack --display=minimal --config ../../scripts/bundle.config.js"
"build-bundle": "webpack --display=minimal --config ../../scripts/bundle.config.js",
"build-worker": "webpack --entry ./src/image-loader.worker.js --output ./dist/image-loader.worker.js --config ../../scripts/worker-webpack-config.js"
}
}
7 changes: 6 additions & 1 deletion modules/images/src/image-loader.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
/* global Image */
import {canParseImage, parseImage, parseToImageBitmap, loadToHTMLImage} from './lib/parse-image';
import {
canParseImage,
parseImage,
parseToImageBitmap,
loadToHTMLImage
} from './lib/parsers/parse-image-v1';

const EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'ico', 'svg'];

Expand Down
5 changes: 5 additions & 0 deletions modules/images/src/image-loader.worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {ImageLoaders} from './image-loaders';
import {createWorker} from '@loaders.gl/loader-utils';

// TODO - Can createWorker handle an array of loaders
createWorker(ImageLoaders);
64 changes: 64 additions & 0 deletions modules/images/src/image-loaders.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {parseImage} from './lib/parsers/parse-image';
import {parseSVG} from './lib/parsers/parse-svg';

export const JPEGLoader = {
id: 'jpeg',
category: 'image',
name: 'JPEG',
extensions: ['jpg', 'jpeg'],
mimeType: 'image/jpeg',
parse: parseImage,
// test: ,
options: {}
};

export const PNGLoader = {
id: 'png',
category: 'image',
name: 'PNG',
extensions: ['png'],
mimeType: 'image/png',
parse: parseImage,
// test: , - Add sniffer here
options: {}
};

export const GIFLoader = {
id: 'gif',
category: 'image',
name: 'GIF',
extensions: ['gif'],
mimeType: 'image/gif',
parse: parseImage,
options: {}
};

export const BMPLoader = {
id: 'bmp',
category: 'image',
name: 'BMP',
extensions: ['gif'],
mimeType: 'image/gif',
// test: , - Add sniffer here
parse: parseImage,
options: {}
};

export const SVGLoader = {
id: 'svg',
name: 'SVG',
extensions: ['svg'],
mimeType: 'image/svg+xml',
parse: parseSVG
// test: , - Add sniffer here
};

export const ImageLoaders = [
JPEGLoader,
PNGLoader,
GIFLoader,
BMPLoader,
// WEBPLoader,
// ICOLoader,
SVGLoader
];
Empty file.
2 changes: 1 addition & 1 deletion modules/images/src/image-writer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {encodeImage} from './lib/encode-image';
import {encodeImage} from './lib/encoders/encode-image';

export default {
name: 'Images',
Expand Down
40 changes: 35 additions & 5 deletions modules/images/src/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,40 @@
export {default as ImageLoader, HTMLImageLoader, ImageBitmapLoader} from './image-loader';
export {default as ImageWriter} from './image-writer';

export {loadImage} from './lib/parse-image';

// UTILS
export {isImage, getImageMetadata, getImageMIMEType, getImageSize} from './lib/get-image-metadata';
export {
isImage,
getImageMetadata,
getImageMIMEType,
getImageSize
} from './lib/metadata/get-image-metadata';

// EXPERIMENTAL V2.0
export {
JPEGLoader as _JPEGLoader,
PNGLoader as _PNGLoader,
GIFLoader as _GIFLoader,
BMPLoader as _BMPLoader,
SVGLoader as _SVGLoader,
ImageLoaders as _ImageLoaders
} from './image-loaders';

// DEPRECATED
export {loadImage} from './lib/parsers/parse-image-v1';

// Now possible to use ImageLoaders on arrayBuffer input
// Unpacks compressed image data into an HTML image
export function decodeImage(arrayBufferOrView, {mimeType = 'image/jpeg'}) {
/* global window, Blob, Image */
const blob = new Blob([arrayBufferOrView], {type: mimeType});
const urlCreator = window.URL || window.webkitURL;
const imageUrl = urlCreator.createObjectURL(blob);

// Experimental
export {decodeImage} from './lib/image-utils-browser';
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => resolve(image);
image.onerror = reject;
image.src = imageUrl;
return image;
});
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Image loading/saving for browser
/* global document, HTMLCanvasElement, Image */

import assert from '../utils/assert';
import {global} from '../utils/globals';
import assert from '../../utils/assert';
import {global} from '../../utils/globals';

// Returns data bytes representing a compressed image in PNG or JPG format,
// This data can be saved using file system (f) methods or
Expand Down
30 changes: 0 additions & 30 deletions modules/images/src/lib/image-utils-browser.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// import {bufferToArrayBuffer} from '../node/buffer-to-array-buffer';
// TODO - this should be handled in @loaders.gl/polyfills

import {mimeTypeMap} from './image-parsers';
import {mimeTypeMap} from './image-sniffers';

const ERR_INVALID_MIME_TYPE = `Invalid MIME type. Supported MIME types are: ${Array.from(
mimeTypeMap.keys()
Expand Down
File renamed without changes.
36 changes: 36 additions & 0 deletions modules/images/src/lib/parsers/get-image-output-format.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {NODE_IMAGE_SUPPORTED} from './parse-to-node-image';
import {HTML_IMAGE_SUPPORTED} from './parse-to-html-image';
import {IMAGE_BITMAP_SUPPORTED} from './parse-to-image-bitmap';

import assert from '../../utils/assert';

// The user can request a specific output format
// If using loaders.gl to load images for HTML
// TODO - should this throw or silently return the available format?
// TODO - ImageBitmap vs HTMLImage depends on worker threads...
export default function getImageOutputFormat(options = {}) {
const imageOptions = options.image || {};
const requestedFormat = imageOptions.format || 'auto';
switch (requestedFormat) {
case 'imagebitmap':
assert(IMAGE_BITMAP_SUPPORTED);
return requestedFormat;
case 'html':
assert(HTML_IMAGE_SUPPORTED);
return requestedFormat;
default:
// warn?
// fall through
case 'auto':
if (IMAGE_BITMAP_SUPPORTED) {
return 'imagebitmap';
}
if (HTML_IMAGE_SUPPORTED) {
return 'html';
}
if (NODE_IMAGE_SUPPORTED) {
return 'ndarray';
}
return assert(false);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* global Image, Blob, createImageBitmap, btoa, fetch */
import {global} from '../utils/globals';
import {getImageMetadata} from './get-image-metadata';
import {global} from '../../utils/globals';
import {getImageMetadata} from '../metadata/get-image-metadata';

export const canParseImage = global._parseImageNode || typeof ImageBitmap !== 'undefined';

Expand Down
21 changes: 21 additions & 0 deletions modules/images/src/lib/parsers/parse-image.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import assert from '../../utils/assert';

import getImageOutputFormat from './get-image-output-format';
import parseToNodeImage from './parse-to-node-image';
import parseToHTMLImage from './parse-to-html-image';
import parseToImageBitmap from './parse-to-image-bitmap';

// Parse to platform defined image type (ndarray on node, ImageBitmap or HTMLImage on browser)
export async function parseImage(arrayBuffer, options) {
const format = getImageOutputFormat(options);
switch (format) {
case 'imagebitmap':
return await parseToImageBitmap(arrayBuffer, options);
case 'html':
return await parseToHTMLImage(arrayBuffer, options);
case 'ndarray':
return await parseToNodeImage(arrayBuffer, options);
default:
return assert(false);
}
}
11 changes: 11 additions & 0 deletions modules/images/src/lib/parsers/parse-svg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* global TextDecoder, btoa */
import parseImage from './parse-image';

export async function parseSVG(arrayBuffer, options) {
// Prepare a properly tagged data URL, and load using normal mechanism
const textDecoder = new TextDecoder();
const xmlText = textDecoder.decode(arrayBuffer);
// base64 encoding is safer. utf-8 fails in some browsers
const src = `data:image/svg+xml;base64,${btoa(xmlText)}`;
return await parseImage(src);
}
Loading

0 comments on commit c3a21a7

Please sign in to comment.