Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make WebP decoder use memory views #145

Merged
merged 3 commits into from
Aug 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
???
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*diabolical laughter*

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brilliant


### `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 ",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fun fact: imagequant has pragmas that make compilationn fail if you don’t compile with --std=c99, so I had to split this into two steps here.

"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