Skip to content

Commit

Permalink
Mozjpeg opts (GoogleChromeLabs#140)
Browse files Browse the repository at this point in the history
* Switching to embind

* Adding options to mozjpeg wasm

* Updating packages

* Ditching enum - causing more problems than it's worth

* Adding mozjpeg options UI

* Forgot about this enum

* Bools just work
  • Loading branch information
jakearchibald authored Aug 17, 2018
1 parent 7fd8e94 commit 5f537a6
Show file tree
Hide file tree
Showing 13 changed files with 426 additions and 392 deletions.
45 changes: 25 additions & 20 deletions codecs/mozjpeg_enc/example.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!doctype html>
<script src='mozjpeg_enc.js'></script>
<script>
const Module = mozjpeg_enc();
const module = mozjpeg_enc();

async function loadImage(src) {
// Load image
Expand All @@ -17,27 +17,32 @@
return ctx.getImageData(0, 0, img.width, img.height);
}

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']),
encode: Module.cwrap('encode', '', ['number', 'number', 'number', 'number']),
free_result: Module.cwrap('free_result', '', ['number']),
get_result_pointer: Module.cwrap('get_result_pointer', 'number', []),
get_result_size: Module.cwrap('get_result_size', 'number', []),
};
console.log('Version:', api.version().toString(16));
module.onRuntimeInitialized = async _ => {
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.encode(p, image.width, image.height, 2);
const resultPointer = api.get_result_pointer();
const resultSize = api.get_result_size();
const resultView = new Uint8Array(Module.HEAP8.buffer, resultPointer, resultSize);
const p = module.create_buffer(image.width, image.height);
module.HEAP8.set(image.data, p);
module.encode(p, image.width, image.height, {
quality: 40,
baseline: false,
arithmetic: false,
progressive: true,
optimize_coding: true,
smoothing: 0,
color_space: 3,
quant_table: 3,
trellis_multipass: true,
trellis_opt_zero: true,
trellis_opt_table: true,
trellis_loops: 1,
});
const resultPointer = module.get_result_pointer();
const resultSize = module.get_result_size();
console.log('size', resultSize);
const resultView = new Uint8Array(module.HEAP8.buffer, resultPointer, resultSize);
const result = new Uint8Array(resultView);
api.free_result(resultPointer);
api.destroy_buffer(p);
module.free_result();
module.destroy_buffer(p);

const blob = new Blob([result], {type: 'image/jpeg'});
const blobURL = URL.createObjectURL(blob);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,33 @@
#include <stdio.h>
#include <setjmp.h>
#include <string.h>
#include "jpeglib.h"
#include <emscripten/bind.h>
#include "config.h"
#include "jpeglib.h"
#include "cdjpeg.h"

using namespace emscripten;

// MozJPEG doesn’t expose a numeric version, so I have to do some fun C macro hackery to turn it into a string. More details here: https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html
// MozJPEG doesn’t expose a numeric version, so I have to do some fun C macro hackery to turn it
// into a string. More details here: https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html
#define xstr(s) str(s)
#define str(s) #s

EMSCRIPTEN_KEEPALIVE
struct MozJpegOptions {
int quality;
bool baseline;
bool arithmetic;
bool progressive;
bool optimize_coding;
int smoothing;
int color_space;
int quant_table;
bool trellis_multipass;
bool trellis_opt_zero;
bool trellis_opt_table;
int trellis_loops;
};

int version() {
char buffer[] = xstr(MOZJPEG_VERSION);
int version = 0;
Expand All @@ -28,27 +47,17 @@ int version() {
return version;
}

EMSCRIPTEN_KEEPALIVE
uint8_t* create_buffer(int width, int height) {
return malloc(width * height * 4 * sizeof(uint8_t));
int create_buffer(int width, int height) {
return (int) malloc(width * height * 4 * sizeof(uint8_t));
}

EMSCRIPTEN_KEEPALIVE
void destroy_buffer(uint8_t* p) {
free(p);
void destroy_buffer(int p) {
free((uint8_t*) p);
}

int result[2];
EMSCRIPTEN_KEEPALIVE
void encode(uint8_t* image_buffer, int image_width, int image_height, int quality) {
// Manually convert RGBA data into RGB
for(int y = 0; y < image_height; y++) {
for(int x = 0; x < image_width; x++) {
image_buffer[(y*image_width + x)*3 + 0] = image_buffer[(y*image_width + x)*4 + 0];
image_buffer[(y*image_width + x)*3 + 1] = image_buffer[(y*image_width + x)*4 + 1];
image_buffer[(y*image_width + x)*3 + 2] = image_buffer[(y*image_width + x)*4 + 2];
}
}
void encode(int image_in, int image_width, int image_height, MozJpegOptions opts) {
uint8_t* image_buffer = (uint8_t*) image_in;

// The code below is basically the `write_JPEG_file` function from
// https://github.com/mozilla/mozjpeg/blob/master/example.c
Expand Down Expand Up @@ -109,18 +118,48 @@ void encode(uint8_t* image_buffer, int image_width, int image_height, int qualit
*/
cinfo.image_width = image_width; /* image width and height, in pixels */
cinfo.image_height = image_height;
cinfo.input_components = 3; /* # of color components per pixel */
cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
cinfo.input_components = 4; /* # of color components per pixel */
cinfo.in_color_space = JCS_EXT_RGBA; /* colorspace of input image */
/* Now use the library's routine to set default compression parameters.
* (You must set at least cinfo.in_color_space before calling this,
* since the defaults depend on the source color space.)
*/
jpeg_set_defaults(&cinfo);

/* Now you can set any non-default parameters you wish to.
* Here we just illustrate the use of quality (quantization table) scaling:
*/
jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
jpeg_set_colorspace(&cinfo, (J_COLOR_SPACE) opts.color_space);

if (opts.quant_table != -1) {
jpeg_c_set_int_param(&cinfo, JINT_BASE_QUANT_TBL_IDX, opts.quant_table);
}

cinfo.optimize_coding = opts.optimize_coding;

if (opts.arithmetic) {
cinfo.arith_code = TRUE;
cinfo.optimize_coding = FALSE;
}

cinfo.smoothing_factor = opts.smoothing;

jpeg_c_set_bool_param(&cinfo, JBOOLEAN_USE_SCANS_IN_TRELLIS, opts.trellis_multipass);
jpeg_c_set_bool_param(&cinfo, JBOOLEAN_TRELLIS_EOB_OPT, opts.trellis_opt_zero);
jpeg_c_set_bool_param(&cinfo, JBOOLEAN_TRELLIS_Q_OPT, opts.trellis_opt_table);
jpeg_c_set_int_param(&cinfo, JINT_TRELLIS_NUM_LOOPS, opts.trellis_loops);

std::string quality_str = std::to_string(opts.quality);
char const *pqual = quality_str.c_str();

set_quality_ratings(&cinfo, (char*) pqual, opts.baseline);

if (!opts.baseline && opts.progressive) {
jpeg_simple_progression(&cinfo);
} else {
cinfo.num_scans = 0;
cinfo.scan_info = NULL;
}
/* Step 4: Start compressor */

/* TRUE ensures that we will write a complete interchange-JPEG file.
Expand All @@ -136,7 +175,7 @@ void encode(uint8_t* image_buffer, int image_width, int image_height, int qualit
* To keep things simple, we pass one scanline per call; you can pass
* more if you wish, though.
*/
row_stride = image_width * 3; /* JSAMPLEs per row in image_buffer */
row_stride = image_width * 4; /* JSAMPLEs per row in image_buffer */

while (cinfo.next_scanline < cinfo.image_height) {
/* jpeg_write_scanlines expects an array of pointers to scanlines.
Expand All @@ -161,18 +200,39 @@ void encode(uint8_t* image_buffer, int image_width, int image_height, int qualit
/* And we're done! */
}

EMSCRIPTEN_KEEPALIVE
void free_result() {
free(result[0]); // not sure if this is right with mozjpeg
free((void*)result[0]); // not sure if this is right with mozjpeg
}

EMSCRIPTEN_KEEPALIVE
int get_result_pointer() {
return result[0];
}

EMSCRIPTEN_KEEPALIVE
int get_result_size() {
return result[1];
}

EMSCRIPTEN_BINDINGS(my_module) {
value_object<MozJpegOptions>("MozJpegOptions")
.field("quality", &MozJpegOptions::quality)
.field("baseline", &MozJpegOptions::baseline)
.field("arithmetic", &MozJpegOptions::arithmetic)
.field("progressive", &MozJpegOptions::progressive)
.field("optimize_coding", &MozJpegOptions::optimize_coding)
.field("smoothing", &MozJpegOptions::smoothing)
.field("color_space", &MozJpegOptions::color_space)
.field("quant_table", &MozJpegOptions::quant_table)
.field("trellis_multipass", &MozJpegOptions::trellis_multipass)
.field("trellis_opt_zero", &MozJpegOptions::trellis_opt_zero)
.field("trellis_opt_table", &MozJpegOptions::trellis_opt_table)
.field("trellis_loops", &MozJpegOptions::trellis_loops)
;

function("version", &version);
function("create_buffer", &create_buffer, allow_raw_pointers());
function("destroy_buffer", &destroy_buffer, allow_raw_pointers());
function("encode", &encode, allow_raw_pointers());
function("free_result", &free_result);
function("get_result_pointer", &get_result_pointer, allow_raw_pointers());
function("get_result_size", &get_result_size, allow_raw_pointers());
}
14 changes: 13 additions & 1 deletion codecs/mozjpeg_enc/mozjpeg_enc.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
export default function(opts: EmscriptenWasm.ModuleOpts): EmscriptenWasm.Module;
import { EncodeOptions } from '../../src/codecs/mozjpeg/encoder';

interface MozJPEGModule extends EmscriptenWasm.Module {
create_buffer(width: number, height: number): number;
encode(pointer: number, width: number, height: number, options: EncodeOptions): void;
get_result_pointer(): number;
get_result_size(): number;
free_result(): void;
destroy_buffer(pointer: number): void;
}


export default function(opts: EmscriptenWasm.ModuleOpts): MozJPEGModule;
2 changes: 1 addition & 1 deletion codecs/mozjpeg_enc/mozjpeg_enc.js

Large diffs are not rendered by default.

Binary file modified codecs/mozjpeg_enc/mozjpeg_enc.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion codecs/mozjpeg_enc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"install": "napa",
"build": "npm run build:library && npm run build:wasm",
"build:library": "cd node_modules/mozjpeg && autoreconf -fiv && docker run --rm -v $(pwd):/src trzeci/emscripten emconfigure ./configure --without-simd && docker run --rm -v $(pwd):/src trzeci/emscripten emmake make libjpeg.la",
"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=\"mozjpeg_enc\"' -I node_modules/mozjpeg -o ./mozjpeg_enc.js mozjpeg_enc.c node_modules/mozjpeg/.libs/libjpeg.a"
"build:wasm": "docker run --rm -v $(pwd):/src trzeci/emscripten emcc --bind -O3 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s 'EXPORT_NAME=\"mozjpeg_enc\"' -I node_modules/mozjpeg -o ./mozjpeg_enc.js -Wno-deprecated-register -Wno-writable-strings node_modules/mozjpeg/rdswitch.c -x c++ -std=c++11 mozjpeg_enc.cpp node_modules/mozjpeg/.libs/libjpeg.a"
},
"napa": {
"mozjpeg": "mozilla/mozjpeg#v3.3.1"
Expand Down
2 changes: 1 addition & 1 deletion codecs/webp_enc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "webp_enc",
"scripts": {
"install": "napa",
"build": "docker run --rm -v $(pwd):/src trzeci/emscripten emcc --bind -O3 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s 'EXPORT_NAME=\"webp_enc\"' -I node_modules/libwebp -o ./webp_enc.js -x c node_modules/libwebp/src/{dec,dsp,demux,enc,mux,utils}/*.c -x c++ -std=c++11 webp_enc.cpp "
"build": "docker run --rm -v $(pwd):/src trzeci/emscripten emcc --bind -O3 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s 'EXPORT_NAME=\"webp_enc\"' -I node_modules/libwebp -o ./webp_enc.js -x c node_modules/libwebp/src/{dec,dsp,demux,enc,mux,utils}/*.c -x c++ -std=c++11 webp_enc.cpp"
},
"napa": {
"libwebp": "webmproject/libwebp#v1.0.0"
Expand Down
Loading

0 comments on commit 5f537a6

Please sign in to comment.