mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-12 00:37:19 +00:00
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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ node_modules
|
|||||||
/*.log
|
/*.log
|
||||||
*.scss.d.ts
|
*.scss.d.ts
|
||||||
*.css.d.ts
|
*.css.d.ts
|
||||||
|
*.o
|
||||||
|
|||||||
@@ -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.
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
16
codecs/imagequant/imagequant.d.ts
vendored
16
codecs/imagequant/imagequant.d.ts
vendored
@@ -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.
@@ -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"
|
||||||
|
|||||||
@@ -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.
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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];
|
|
||||||
}
|
|
||||||
|
|
||||||
47
codecs/webp_dec/webp_dec.cpp
Normal file
47
codecs/webp_dec/webp_dec.cpp
Normal 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);
|
||||||
|
}
|
||||||
14
codecs/webp_dec/webp_dec.d.ts
vendored
14
codecs/webp_dec/webp_dec.d.ts
vendored
@@ -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.
@@ -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 doesn’t complain about this not being a module.
|
// Using require() so TypeScript doesn’t 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 codec’s 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 it’s 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 doesn’t complain about this not being a module.
|
// Using require() so TypeScript doesn’t 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 codec’s README.
|
// API exposed by wasm module. Details in the codec’s 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 it’s 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user