Skip to content

Commit

Permalink
Make WebP decoder use memory views (#145)
Browse files Browse the repository at this point in the history
* Make WebP decoder use memory views

* Update webp_dec README

* Port quantizer to memory views as well
  • Loading branch information
surma authored and jakearchibald committed Aug 21, 2018
1 parent 8006a1a commit e3b1b08
Show file tree
Hide file tree
Showing 18 changed files with 194 additions and 276 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules
/*.log
*.scss.d.ts
*.css.d.ts
*.o
18 changes: 5 additions & 13 deletions codecs/imagequant/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,14 @@ See `example.html`

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()`

Frees the result created by `encode()`.

### `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.
Frees the result created by `quantize()`.
25 changes: 5 additions & 20 deletions codecs/imagequant/example.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,14 @@
}

Module.onRuntimeInitialized = async _ => {
const api = {
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));
console.log('Version:', Module.version().toString(16));
const image = await loadImage('../example.png');
const p = api.create_buffer(image.width, image.height);
Module.HEAP8.set(image.data, p);
//api.quantize(p, image.width, image.height, 256, 1.0);
api.zx_quantize(p, image.width, image.height, 1);
// const rawImage = Module.quantize(image.data, image.width, image.height, 256, 1.0);
const rawImage = Module.zx_quantize(image.data, image.width, image.height, 1.0);
console.log('done');
const resultPointer = api.get_result_pointer();
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);
Module.free_result();

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');
canvas.width = image.width;
canvas.height = image.height;
Expand Down
77 changes: 48 additions & 29 deletions codecs/imagequant/imagequant.c → codecs/imagequant/imagequant.cpp
Original file line number Diff line number Diff line change
@@ -1,55 +1,63 @@
#include "emscripten.h"
#include "emscripten/bind.h"
#include "emscripten/val.h"
#include <stdlib.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>

#include "libimagequant.h"

EMSCRIPTEN_KEEPALIVE
using namespace emscripten;

int version() {
return (((LIQ_VERSION/10000) % 100) << 16) |
(((LIQ_VERSION/100 ) % 100) << 8) |
(((LIQ_VERSION/1 ) % 100) << 0);
}

EMSCRIPTEN_KEEPALIVE
uint8_t* create_buffer(int width, int height) {
return malloc(width * height * 4 * sizeof(uint8_t));
}
class RawImage {
public:
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_image *image;
liq_result *res;
int result;
EMSCRIPTEN_KEEPALIVE
void quantize(uint8_t* image_buffer, int image_width, int image_height, int num_colors, float dithering) {
uint8_t* result;
RawImage quantize(std::string rawimage, 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;
attr = liq_attr_create();
image = liq_image_create_rgba(attr, image_buffer, image_width, image_height, 0);
liq_set_max_colors(attr, num_colors);
liq_image_quantize(image, attr, &res);
liq_set_dithering_level(res, dithering);
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);
const liq_palette *pal = liq_get_palette(res);
// Turn palletted image back into an RGBA image
for(int i = 0; i < size; i++) {
((uint8_t*)result)[i * 4 + 0] = pal->entries[image8bit[i]].r;
((uint8_t*)result)[i * 4 + 1] = pal->entries[image8bit[i]].g;
((uint8_t*)result)[i * 4 + 2] = pal->entries[image8bit[i]].b;
((uint8_t*)result)[i * 4 + 3] = pal->entries[image8bit[i]].a;
result[i * 4 + 0] = pal->entries[image8bit[i]].r;
result[i * 4 + 1] = pal->entries[image8bit[i]].g;
result[i * 4 + 2] = pal->entries[image8bit[i]].b;
result[i * 4 + 3] = pal->entries[image8bit[i]].a;
}
free(image8bit);
liq_result_destroy(res);
liq_image_destroy(image);
liq_attr_destroy(attr);
return {
val(typed_memory_view(image_width*image_height*4, result)),
image_width,
image_height
};
}

const liq_color zx_colors[] = {
Expand All @@ -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
* both be 'regular' or 'bright'. Black exists as both regular and bright.
*/
EMSCRIPTEN_KEEPALIVE
void zx_quantize(uint8_t* image_buffer, int image_width, int image_height, float dithering) {
RawImage zx_quantize(std::string rawimage, 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 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);

// For each 8x8 grid
Expand Down Expand Up @@ -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++) {
int image8BitPos = y * block_width + x;
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;
((uint8_t*)result)[resultStartPos + 1] = pal->entries[image8bit[image8BitPos]].g;
((uint8_t*)result)[resultStartPos + 2] = pal->entries[image8bit[image8BitPos]].b;
((uint8_t*)result)[resultStartPos + 3] = pal->entries[image8bit[image8BitPos]].a;
result[resultStartPos + 0] = pal->entries[image8bit[image8BitPos]].r;
result[resultStartPos + 1] = pal->entries[image8bit[image8BitPos]].g;
result[resultStartPos + 2] = pal->entries[image8bit[image8BitPos]].b;
result[resultStartPos + 3] = pal->entries[image8bit[image8BitPos]].a;
}
}

Expand All @@ -213,14 +221,25 @@ void zx_quantize(uint8_t* image_buffer, int image_width, int image_height, float
}

free(image8bit);
return {
val(typed_memory_view(image_width*image_height*4, result)),
image_width,
image_height
};
}

EMSCRIPTEN_KEEPALIVE
void free_result() {
free(result);
}

EMSCRIPTEN_KEEPALIVE
int get_result_pointer() {
return result;
EMSCRIPTEN_BINDINGS(my_module) {
class_<RawImage>("RawImage")
.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);
}
16 changes: 15 additions & 1 deletion codecs/imagequant/imagequant.d.ts
Original file line number Diff line number Diff line change
@@ -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;


23 changes: 15 additions & 8 deletions codecs/imagequant/imagequant.js

Large diffs are not rendered by default.

Binary file modified codecs/imagequant/imagequant.wasm
Binary file not shown.
5 changes: 4 additions & 1 deletion codecs/imagequant/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
"scripts": {
"install": "napa",
"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": {
"libimagequant": "ImageOptim/libimagequant#2.12.1"
Expand Down
24 changes: 2 additions & 22 deletions codecs/webp_dec/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,10 @@ See `example.html`

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.

### `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.
Decodes the given webp buffer into raw RGBA. `RawImage` is a class with 3 fields: `buffer`, `width`, and `height`.

### `void free_result()`

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.
33 changes: 6 additions & 27 deletions codecs/webp_dec/example.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,14 @@
}

Module.onRuntimeInitialized = async _ => {
const api = {
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));
console.log('Version:', Module.version().toString(16));
const image = await loadFile('../example.webp');
const p = api.create_buffer(image.byteLength);
Module.HEAP8.set(new Uint8Array(image), p);
api.decode(p, image.byteLength);
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 result = Module.decode(image);
const imageData = new ImageData(new Uint8ClampedArray(result.buffer), result.width, result.height);
Module.free_result();
const canvas = document.createElement('canvas');
canvas.width = resultWidth;
canvas.height = resultHeight;
canvas.width = result.width;
canvas.height = result.height;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
ctx.putImageData(imageData, 0, 0);
Expand Down
2 changes: 1 addition & 1 deletion codecs/webp_dec/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "webp_dec",
"scripts": {
"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": {
"libwebp": "webmproject/libwebp#v1.0.0"
Expand Down
51 changes: 0 additions & 51 deletions codecs/webp_dec/webp_dec.c

This file was deleted.

Loading

0 comments on commit e3b1b08

Please sign in to comment.