Make WebP decoder use memory views (#145)

* Make WebP decoder use memory views

* Update webp_dec README

* Port quantizer to memory views as well
This commit is contained in:
Surma
2018-08-21 13:29:53 +01:00
committed by Jake Archibald
parent 8006a1a5e7
commit e3b1b08424
18 changed files with 194 additions and 276 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ node_modules
/*.log /*.log
*.scss.d.ts *.scss.d.ts
*.css.d.ts *.css.d.ts
*.o

View File

@@ -17,22 +17,14 @@ See `example.html`
Returns the version of libimagequant as a number. va.b.c is encoded as 0x0a0b0c Returns the version of libimagequant as a number. va.b.c is encoded as 0x0a0b0c
### `uint8_t* create_buffer(int width, int height)` ### `RawImage quantize(std::string buffer, int image_width, int image_height, int numColors, float dithering)`
Allocates an RGBA buffer for an image with the given dimension. Quantizes the given images, using at most `numColors`, a value between 2 and 256. `dithering` is a value between 0 and 1 controlling the amount of dithering. `RawImage` is a class with 3 fields: `buffer`, `width`, and `height`.
### `void destroy_buffer(uint8_t* p)` ### `RawImage zx_quantize(std::string buffer, int image_width, int image_height, float dithering)`
Frees a buffer created with `create_buffer`. ???
### `void quantize(uint8_t* image_buffer, int image_width, int image_height, int numColors, float dithering)`
Quantizes the given images, using at most `numColors`, a value between 2 and 256. `dithering` is a value between 0 and 1 controlling the amount of dithering.
### `void free_result()` ### `void free_result()`
Frees the result created by `encode()`. Frees the result created by `quantize()`.
### `int get_result_pointer()`
Returns the pointer to the start of the buffer holding the encoded data. It has the same size as the input image buffer.

View File

@@ -23,29 +23,14 @@
} }
Module.onRuntimeInitialized = async _ => { Module.onRuntimeInitialized = async _ => {
const api = { console.log('Version:', Module.version().toString(16));
version: Module.cwrap('version', 'number', []),
create_buffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
destroy_buffer: Module.cwrap('destroy_buffer', '', ['number']),
quantize: Module.cwrap('quantize', '', ['number', 'number', 'number', 'number', 'number']),
zx_quantize: Module.cwrap('zx_quantize', '', ['number', 'number', 'number', 'number']),
free_result: Module.cwrap('free_result', '', ['number']),
get_result_pointer: Module.cwrap('get_result_pointer', 'number', []),
};
console.log('Version:', api.version().toString(16));
const image = await loadImage('../example.png'); const image = await loadImage('../example.png');
const p = api.create_buffer(image.width, image.height); // const rawImage = Module.quantize(image.data, image.width, image.height, 256, 1.0);
Module.HEAP8.set(image.data, p); const rawImage = Module.zx_quantize(image.data, image.width, image.height, 1.0);
//api.quantize(p, image.width, image.height, 256, 1.0);
api.zx_quantize(p, image.width, image.height, 1);
console.log('done'); console.log('done');
const resultPointer = api.get_result_pointer(); Module.free_result();
const resultView = new Uint8Array(Module.HEAP8.buffer, resultPointer, image.width * image.height * 4);
const result = new Uint8ClampedArray(resultView);
api.free_result();
api.destroy_buffer(p);
const imageData = new ImageData(result, image.width, image.height); const imageData = new ImageData(new Uint8ClampedArray(rawImage.buffer), rawImage.width, rawImage.height);
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = image.width; canvas.width = image.width;
canvas.height = image.height; canvas.height = image.height;

View File

@@ -1,4 +1,5 @@
#include "emscripten.h" #include "emscripten/bind.h"
#include "emscripten/val.h"
#include <stdlib.h> #include <stdlib.h>
#include <inttypes.h> #include <inttypes.h>
#include <limits.h> #include <limits.h>
@@ -6,29 +7,31 @@
#include "libimagequant.h" #include "libimagequant.h"
EMSCRIPTEN_KEEPALIVE using namespace emscripten;
int version() { int version() {
return (((LIQ_VERSION/10000) % 100) << 16) | return (((LIQ_VERSION/10000) % 100) << 16) |
(((LIQ_VERSION/100 ) % 100) << 8) | (((LIQ_VERSION/100 ) % 100) << 8) |
(((LIQ_VERSION/1 ) % 100) << 0); (((LIQ_VERSION/1 ) % 100) << 0);
} }
EMSCRIPTEN_KEEPALIVE class RawImage {
uint8_t* create_buffer(int width, int height) { public:
return malloc(width * height * 4 * sizeof(uint8_t)); val buffer;
} int width;
int height;
RawImage(val b, int w, int h)
: buffer(b), width(w), height(h) {}
};
EMSCRIPTEN_KEEPALIVE
void destroy_buffer(uint8_t* p) {
free(p);
}
liq_attr *attr; liq_attr *attr;
liq_image *image; liq_image *image;
liq_result *res; liq_result *res;
int result; uint8_t* result;
EMSCRIPTEN_KEEPALIVE RawImage quantize(std::string rawimage, int image_width, int image_height, int num_colors, float dithering) {
void quantize(uint8_t* image_buffer, int image_width, int image_height, int num_colors, float dithering) { const uint8_t* image_buffer = (uint8_t*)rawimage.c_str();
int size = image_width * image_height; int size = image_width * image_height;
attr = liq_attr_create(); attr = liq_attr_create();
image = liq_image_create_rgba(attr, image_buffer, image_width, image_height, 0); image = liq_image_create_rgba(attr, image_buffer, image_width, image_height, 0);
@@ -36,20 +39,25 @@ void quantize(uint8_t* image_buffer, int image_width, int image_height, int num_
liq_image_quantize(image, attr, &res); liq_image_quantize(image, attr, &res);
liq_set_dithering_level(res, dithering); liq_set_dithering_level(res, dithering);
uint8_t* image8bit = (uint8_t*) malloc(size); uint8_t* image8bit = (uint8_t*) malloc(size);
result = (int) malloc(size * 4); result = (uint8_t*) malloc(size * 4);
liq_write_remapped_image(res, image, image8bit, size); liq_write_remapped_image(res, image, image8bit, size);
const liq_palette *pal = liq_get_palette(res); const liq_palette *pal = liq_get_palette(res);
// Turn palletted image back into an RGBA image // Turn palletted image back into an RGBA image
for(int i = 0; i < size; i++) { for(int i = 0; i < size; i++) {
((uint8_t*)result)[i * 4 + 0] = pal->entries[image8bit[i]].r; result[i * 4 + 0] = pal->entries[image8bit[i]].r;
((uint8_t*)result)[i * 4 + 1] = pal->entries[image8bit[i]].g; result[i * 4 + 1] = pal->entries[image8bit[i]].g;
((uint8_t*)result)[i * 4 + 2] = pal->entries[image8bit[i]].b; result[i * 4 + 2] = pal->entries[image8bit[i]].b;
((uint8_t*)result)[i * 4 + 3] = pal->entries[image8bit[i]].a; result[i * 4 + 3] = pal->entries[image8bit[i]].a;
} }
free(image8bit); free(image8bit);
liq_result_destroy(res); liq_result_destroy(res);
liq_image_destroy(image); liq_image_destroy(image);
liq_attr_destroy(attr); liq_attr_destroy(attr);
return {
val(typed_memory_view(image_width*image_height*4, result)),
image_width,
image_height
};
} }
const liq_color zx_colors[] = { const liq_color zx_colors[] = {
@@ -76,11 +84,11 @@ uint8_t block[8 * 8 * 4];
* The ZX has one bit per pixel, but can assign two colours to an 8x8 block. The two colours must * The ZX has one bit per pixel, but can assign two colours to an 8x8 block. The two colours must
* both be 'regular' or 'bright'. Black exists as both regular and bright. * both be 'regular' or 'bright'. Black exists as both regular and bright.
*/ */
EMSCRIPTEN_KEEPALIVE RawImage zx_quantize(std::string rawimage, int image_width, int image_height, float dithering) {
void zx_quantize(uint8_t* image_buffer, int image_width, int image_height, float dithering) { const uint8_t* image_buffer = (uint8_t*) rawimage.c_str();
int size = image_width * image_height; int size = image_width * image_height;
int bytes_per_pixel = 4; int bytes_per_pixel = 4;
result = (int) malloc(size * bytes_per_pixel); result = (uint8_t*) malloc(size * bytes_per_pixel);
uint8_t* image8bit = (uint8_t*) malloc(8 * 8); uint8_t* image8bit = (uint8_t*) malloc(8 * 8);
// For each 8x8 grid // For each 8x8 grid
@@ -199,10 +207,10 @@ void zx_quantize(uint8_t* image_buffer, int image_width, int image_height, float
for(int x = 0; x < block_width; x++) { for(int x = 0; x < block_width; x++) {
int image8BitPos = y * block_width + x; int image8BitPos = y * block_width + x;
int resultStartPos = ((block_start_y + y) * bytes_per_pixel * image_width) + ((block_start_x + x) * bytes_per_pixel); int resultStartPos = ((block_start_y + y) * bytes_per_pixel * image_width) + ((block_start_x + x) * bytes_per_pixel);
((uint8_t*)result)[resultStartPos + 0] = pal->entries[image8bit[image8BitPos]].r; result[resultStartPos + 0] = pal->entries[image8bit[image8BitPos]].r;
((uint8_t*)result)[resultStartPos + 1] = pal->entries[image8bit[image8BitPos]].g; result[resultStartPos + 1] = pal->entries[image8bit[image8BitPos]].g;
((uint8_t*)result)[resultStartPos + 2] = pal->entries[image8bit[image8BitPos]].b; result[resultStartPos + 2] = pal->entries[image8bit[image8BitPos]].b;
((uint8_t*)result)[resultStartPos + 3] = pal->entries[image8bit[image8BitPos]].a; result[resultStartPos + 3] = pal->entries[image8bit[image8BitPos]].a;
} }
} }
@@ -213,14 +221,25 @@ void zx_quantize(uint8_t* image_buffer, int image_width, int image_height, float
} }
free(image8bit); free(image8bit);
return {
val(typed_memory_view(image_width*image_height*4, result)),
image_width,
image_height
};
} }
EMSCRIPTEN_KEEPALIVE
void free_result() { void free_result() {
free(result); free(result);
} }
EMSCRIPTEN_KEEPALIVE EMSCRIPTEN_BINDINGS(my_module) {
int get_result_pointer() { class_<RawImage>("RawImage")
return result; .property("buffer", &RawImage::buffer)
.property("width", &RawImage::width)
.property("height", &RawImage::height);
function("quantize", &quantize);
function("zx_quantize", &zx_quantize);
function("version", &version);
function("free_result", &free_result);
} }

View File

@@ -1 +1,15 @@
export default function(opts: EmscriptenWasm.ModuleOpts): EmscriptenWasm.Module; interface RawImage {
buffer: Uint8Array;
width: number;
height: number;
}
interface QuantizerModule extends EmscriptenWasm.Module {
quantize(data: BufferSource, width: number, height: number, numColors: number, dither: number): RawImage;
zx_quantize(data: BufferSource, width: number, height: number, dither: number): RawImage;
free_result(): void;
}
export default function(opts: EmscriptenWasm.ModuleOpts): QuantizerModule;

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -3,7 +3,10 @@
"scripts": { "scripts": {
"install": "napa", "install": "napa",
"build": "npm run build:wasm", "build": "npm run build:wasm",
"build:wasm": "docker run --rm -v $(pwd):/src trzeci/emscripten emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"cwrap\"]' -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s 'EXPORT_NAME=\"imagequant\"' -I node_modules/libimagequant -o ./imagequant.js imagequant.c node_modules/libimagequant/{libimagequant,pam,mediancut,blur,mempool,kmeans,nearest}.c" "build:wasm:lib": "docker run --rm -v $(pwd):/src trzeci/emscripten emcc --bind -O3 -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s 'EXPORT_NAME=\"imagequant\"' -I node_modules/libimagequant --std=c99 node_modules/libimagequant/{libimagequant,pam,mediancut,blur,mempool,kmeans,nearest}.c -c ",
"build:wasm:module": "docker run --rm -v $(pwd):/src trzeci/emscripten emcc --bind -O3 -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s 'EXPORT_NAME=\"imagequant\"' -I node_modules/libimagequant -o ./imagequant.js --std=c++11 *.o -x c++ imagequant.cpp",
"build:wasm": "npm run build:wasm:lib && npm run build:wasm:module"
}, },
"napa": { "napa": {
"libimagequant": "ImageOptim/libimagequant#2.12.1" "libimagequant": "ImageOptim/libimagequant#2.12.1"

View File

@@ -13,30 +13,10 @@ See `example.html`
Returns the version of libwebp as a number. va.b.c is encoded as 0x0a0b0c Returns the version of libwebp as a number. va.b.c is encoded as 0x0a0b0c
### `uint8_t* create_buffer(int size)` ### `RawImage decode(std::string buffer)`
Allocates an buffer for the file data. Decodes the given webp buffer into raw RGBA. `RawImage` is a class with 3 fields: `buffer`, `width`, and `height`.
### `void destroy_buffer(uint8_t* p)`
Frees a buffer created with `create_buffer`.
### `void decode(uint8_t* img_in, int size)`
Decodes the given webp file into raw RGBA. The result is implicitly stored and can be accessed using the `get_result_*()` functions.
### `void free_result()` ### `void free_result()`
Frees the result created by `decode()`. Frees the result created by `decode()`.
### `int get_result_pointer()`
Returns the pointer to the start of the buffer holding the encoded data. Length is width x height x 4 bytes.
### `int get_result_width()`
Returns the width of the image.
### `int get_result_height()`
Returns the height of the image.

View File

@@ -9,35 +9,14 @@
} }
Module.onRuntimeInitialized = async _ => { Module.onRuntimeInitialized = async _ => {
const api = { console.log('Version:', Module.version().toString(16));
version: Module.cwrap('version', 'number', []),
create_buffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
destroy_buffer: Module.cwrap('destroy_buffer', '', ['number']),
decode: Module.cwrap('decode', '', ['number', 'number']),
free_result: Module.cwrap('free_result', '', ['number']),
get_result_pointer: Module.cwrap('get_result_pointer', 'number', []),
get_result_width: Module.cwrap('get_result_width', 'number', []),
get_result_height: Module.cwrap('get_result_height', 'number', []),
};
console.log('Version:', api.version().toString(16));
const image = await loadFile('../example.webp'); const image = await loadFile('../example.webp');
const p = api.create_buffer(image.byteLength); const result = Module.decode(image);
Module.HEAP8.set(new Uint8Array(image), p); const imageData = new ImageData(new Uint8ClampedArray(result.buffer), result.width, result.height);
api.decode(p, image.byteLength); Module.free_result();
const resultPointer = api.get_result_pointer();
if(resultPointer === 0) {
throw new Error("Could not decode image");
}
const resultWidth = api.get_result_width();
const resultHeight = api.get_result_height();
const resultView = new Uint8Array(Module.HEAP8.buffer, resultPointer, resultWidth * resultHeight * 4);
const result = new Uint8ClampedArray(resultView);
const imageData = new ImageData(result, resultWidth, resultHeight);
api.free_result(resultPointer);
api.destroy_buffer(p);
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = resultWidth; canvas.width = result.width;
canvas.height = resultHeight; canvas.height = result.height;
document.body.appendChild(canvas); document.body.appendChild(canvas);
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
ctx.putImageData(imageData, 0, 0); ctx.putImageData(imageData, 0, 0);

View File

@@ -2,7 +2,7 @@
"name": "webp_dec", "name": "webp_dec",
"scripts": { "scripts": {
"install": "napa", "install": "napa",
"build": "docker run --rm -v $(pwd):/src trzeci/emscripten emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"cwrap\"]' -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s 'EXPORT_NAME=\"webp_dec\"' -I node_modules/libwebp -o ./webp_dec.js webp_dec.c node_modules/libwebp/src/{dec,dsp,demux,enc,mux,utils}/*.c" "build": "docker run --rm -v $(pwd):/src trzeci/emscripten emcc -O3 --bind -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s 'EXPORT_NAME=\"webp_dec\"' --std=c++11 -I node_modules/libwebp -o ./webp_dec.js node_modules/libwebp/src/{dec,dsp,demux,enc,mux,utils}/*.c -x c++ webp_dec.cpp"
}, },
"napa": { "napa": {
"libwebp": "webmproject/libwebp#v1.0.0" "libwebp": "webmproject/libwebp#v1.0.0"

View File

@@ -1,51 +0,0 @@
#include "emscripten.h"
#include "src/webp/decode.h"
#include "src/webp/demux.h"
#include <stdlib.h>
EMSCRIPTEN_KEEPALIVE
int version() {
return WebPGetDecoderVersion();
}
EMSCRIPTEN_KEEPALIVE
uint8_t* create_buffer(int size) {
return malloc(size);
}
EMSCRIPTEN_KEEPALIVE
void destroy_buffer(uint8_t* p) {
free(p);
}
int result[3];
EMSCRIPTEN_KEEPALIVE
void decode(uint8_t* img_in, int size) {
int width, height;
uint8_t* img_out = WebPDecodeRGBA(img_in, size, &width, &height);
result[0] = (int)img_out;
result[1] = width;
result[2] = height;
}
EMSCRIPTEN_KEEPALIVE
void free_result() {
WebPFree(result[0]);
}
EMSCRIPTEN_KEEPALIVE
int get_result_pointer() {
return result[0];
}
EMSCRIPTEN_KEEPALIVE
int get_result_width() {
return result[1];
}
EMSCRIPTEN_KEEPALIVE
int get_result_height() {
return result[2];
}

View File

@@ -0,0 +1,47 @@
#include "emscripten/bind.h"
#include "emscripten/val.h"
#include "src/webp/decode.h"
#include "src/webp/demux.h"
#include <string>
using namespace emscripten;
int version() {
return WebPGetDecoderVersion();
}
class RawImage {
public:
val buffer;
int width;
int height;
RawImage(val b, int w, int h)
: buffer(b), width(w), height(h) {}
};
uint8_t* last_result;
RawImage decode(std::string buffer) {
int width, height;
last_result = WebPDecodeRGBA((const uint8_t*)buffer.c_str(), buffer.size(), &width, &height);
return RawImage(
val(typed_memory_view(width*height*4, last_result)),
width,
height
);
}
void free_result() {
free(last_result);
}
EMSCRIPTEN_BINDINGS(my_module) {
class_<RawImage>("RawImage")
.property("buffer", &RawImage::buffer)
.property("width", &RawImage::width)
.property("height", &RawImage::height);
function("decode", &decode);
function("version", &version);
function("free_result", &free_result);
}

View File

@@ -1 +1,13 @@
export default function(opts: EmscriptenWasm.ModuleOpts): EmscriptenWasm.Module; interface RawImage {
buffer: Uint8Array;
width: number;
height: number;
}
interface WebPModule extends EmscriptenWasm.Module {
decode(data: BufferSource): RawImage;
free_result(): void;
}
export default function(opts: EmscriptenWasm.ModuleOpts): WebPModule;

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -1,22 +1,10 @@
import { QuantizeOptions } from './quantizer'; import { QuantizeOptions } from './quantizer';
import imagequant from '../../../codecs/imagequant/imagequant'; import imagequant, { QuantizerModule } from '../../../codecs/imagequant/imagequant';
// Using require() so TypeScript doesnt complain about this not being a module. // Using require() so TypeScript doesnt complain about this not being a module.
const wasmBinaryUrl = require('../../../codecs/imagequant/imagequant.wasm'); const wasmBinaryUrl = require('../../../codecs/imagequant/imagequant.wasm');
// API exposed by wasm module. Details in the codecs README.
interface ModuleAPI {
version(): number;
create_buffer(width: number, height: number): number;
destroy_buffer(pointer: number): void;
quantize(buffer: number, width: number, height: number, numColors: number, dither: number): void;
zx_quantize(buffer: number, width: number, height: number, dither: number): void;
free_result(): void;
get_result_pointer(): number;
}
export default class ImageQuant { export default class ImageQuant {
private emscriptenModule: Promise<EmscriptenWasm.Module>; private emscriptenModule: Promise<QuantizerModule>;
private api: Promise<ModuleAPI>;
constructor() { constructor() {
this.emscriptenModule = new Promise((resolve) => { this.emscriptenModule = new Promise((resolve) => {
@@ -41,45 +29,18 @@ export default class ImageQuant {
}, },
}); });
}); });
this.api = (async () => {
// Not sure why, but TypeScript complains that I am using
// `emscriptenModule` before its getting assigned, which is clearly not
// true :shrug: Using `any`
const m = await (this as any).emscriptenModule;
return {
version: m.cwrap('version', 'number', []),
create_buffer: m.cwrap('create_buffer', 'number', ['number', 'number']),
destroy_buffer: m.cwrap('destroy_buffer', '', ['number']),
quantize: m.cwrap('quantize', '', ['number', 'number', 'number', 'number', 'number']),
zx_quantize: m.cwrap('zx_quantize', '', ['number', 'number', 'number', 'number']),
free_result: m.cwrap('free_result', '', []),
get_result_pointer: m.cwrap('get_result_pointer', 'number', []),
};
})();
} }
async quantize(data: ImageData, opts: QuantizeOptions): Promise<ImageData> { async quantize(data: ImageData, opts: QuantizeOptions): Promise<ImageData> {
const m = await this.emscriptenModule; const m = await this.emscriptenModule;
const api = await this.api;
const p = api.create_buffer(data.width, data.height); const result = opts.zx ?
m.HEAP8.set(new Uint8Array(data.data), p); m.zx_quantize(data.data, data.width, data.height, opts.dither)
if (opts.zx) { :
api.zx_quantize(p, data.width, data.height, opts.dither); m.quantize(data.data, data.width, data.height, opts.maxNumColors, opts.dither);
} else {
api.quantize(p, data.width, data.height, opts.maxNumColors, opts.dither);
}
const resultPointer = api.get_result_pointer();
const resultView = new Uint8Array(
m.HEAP8.buffer,
resultPointer,
data.width * data.height * 4,
);
const result = new Uint8ClampedArray(resultView);
api.free_result();
api.destroy_buffer(p);
return new ImageData(result, data.width, data.height); m.free_result();
return new ImageData(new Uint8ClampedArray(result.buffer), result.width, result.height);
} }
} }

View File

@@ -1,22 +1,11 @@
import webp_dec from '../../../codecs/webp_dec/webp_dec'; import webp_dec, { WebPModule } from '../../../codecs/webp_dec/webp_dec';
// Using require() so TypeScript doesnt complain about this not being a module. // Using require() so TypeScript doesnt complain about this not being a module.
const wasmBinaryUrl = require('../../../codecs/webp_dec/webp_dec.wasm'); const wasmBinaryUrl = require('../../../codecs/webp_dec/webp_dec.wasm');
// API exposed by wasm module. Details in the codecs README. // API exposed by wasm module. Details in the codecs README.
interface ModuleAPI {
version(): number;
create_buffer(size: number): number;
destroy_buffer(pointer: number): void;
decode(buffer: number, size: number): void;
free_result(): void;
get_result_pointer(): number;
get_result_width(): number;
get_result_height(): number;
}
export default class WebpDecoder { export default class WebpDecoder {
private emscriptenModule: Promise<EmscriptenWasm.Module>; private emscriptenModule: Promise<WebPModule>;
private api: Promise<ModuleAPI>;
constructor() { constructor() {
this.emscriptenModule = new Promise((resolve) => { this.emscriptenModule = new Promise((resolve) => {
@@ -41,44 +30,17 @@ export default class WebpDecoder {
}, },
}); });
}); });
this.api = (async () => {
// Not sure why, but TypeScript complains that I am using
// `emscriptenModule` before its getting assigned, which is clearly not
// true :shrug: Using `any`
const m = await (this as any).emscriptenModule;
return {
version: m.cwrap('version', 'number', []),
create_buffer: m.cwrap('create_buffer', 'number', ['number']),
destroy_buffer: m.cwrap('destroy_buffer', '', ['number']),
decode: m.cwrap('decode', '', ['number', 'number']),
free_result: m.cwrap('free_result', '', []),
get_result_pointer: m.cwrap('get_result_pointer', 'number', []),
get_result_height: m.cwrap('get_result_height', 'number', []),
get_result_width: m.cwrap('get_result_width', 'number', []),
};
})();
} }
async decode(data: ArrayBuffer): Promise<ImageData> { async decode(data: ArrayBuffer): Promise<ImageData> {
const m = await this.emscriptenModule; const m = await this.emscriptenModule;
const api = await this.api; const rawImage = m.decode(data);
m.free_result();
const p = api.create_buffer(data.byteLength); return new ImageData(
m.HEAP8.set(new Uint8Array(data), p); new Uint8ClampedArray(rawImage.buffer),
api.decode(p, data.byteLength); rawImage.width,
const resultPointer = api.get_result_pointer(); rawImage.height,
const resultWidth = api.get_result_width();
const resultHeight = api.get_result_height();
const resultView = new Uint8Array(
m.HEAP8.buffer,
resultPointer,
resultWidth * resultHeight * 4,
); );
const result = new Uint8ClampedArray(resultView);
api.free_result();
api.destroy_buffer(p);
return new ImageData(result, resultWidth, resultHeight);
} }
} }