diff --git a/.github/workflows/emscripten.yml b/.github/workflows/emscripten.yml index 75f69aac1b..0b36c40a00 100644 --- a/.github/workflows/emscripten.yml +++ b/.github/workflows/emscripten.yml @@ -9,7 +9,7 @@ on: jobs: emscripten: env: - EMSCRIPTEN_VERSION: 3.1.43 + EMSCRIPTEN_VERSION: 3.1.47 runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 diff --git a/build-emscripten.sh b/build-emscripten.sh index 2648c6eda2..f9449c2888 100755 --- a/build-emscripten.sh +++ b/build-emscripten.sh @@ -38,11 +38,11 @@ if [ "$ENABLE_LIBDE265" = "1" ]; then -L \ -o libde265-${LIBDE265_VERSION}.tar.gz \ https://github.com/strukturag/libde265/releases/download/v${LIBDE265_VERSION}/libde265-${LIBDE265_VERSION}.tar.gz - if [ ! -s "libde265-${LIBDE265_VERSION}/libde265/.libs/libde265.so" ]; then + if [ ! -s "libde265-${LIBDE265_VERSION}/libde265/.libs/libde265.a" ]; then tar xf libde265-${LIBDE265_VERSION}.tar.gz cd libde265-${LIBDE265_VERSION} [ -x configure ] || ./autogen.sh - CXXFLAGS=-O3 emconfigure ./configure --disable-sse --disable-dec265 --disable-sherlock265 + CXXFLAGS=-O3 emconfigure ./configure --enable-static --disable-shared --disable-sse --disable-dec265 --disable-sherlock265 emmake make -j${CORES} cd .. fi @@ -72,7 +72,7 @@ if [ "$ENABLE_AOM" = "1" ]; then -DENABLE_TOOLS=0 \ -DCONFIG_MULTITHREAD=0 \ -DCONFIG_RUNTIME_CPU_DETECT=0 \ - -DBUILD_SHARED_LIBS=1 \ + -DBUILD_SHARED_LIBS=0 \ -DCMAKE_BUILD_TYPE=Release emmake make -j${CORES} @@ -92,7 +92,7 @@ if [ "$STANDALONE" = "1" ]; then EXTRA_COMPILER_FLAGS="-D__EMSCRIPTEN_STANDALONE_WASM__=1" fi -CONFIGURE_ARGS="-DENABLE_MULTITHREADING_SUPPORT=OFF -DWITH_GDK_PIXBUF=OFF -DWITH_EXAMPLES=OFF -DBUILD_SHARED_LIBS=ON -DENABLE_PLUGIN_LOADING=OFF" +CONFIGURE_ARGS="-DENABLE_MULTITHREADING_SUPPORT=OFF -DWITH_GDK_PIXBUF=OFF -DWITH_EXAMPLES=OFF -DBUILD_SHARED_LIBS=OFF -DENABLE_PLUGIN_LOADING=OFF" emcmake cmake ${SRCDIR} $CONFIGURE_ARGS \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_FLAGS="${EXTRA_COMPILER_FLAGS}" \ @@ -108,13 +108,13 @@ EXPORTED_FUNCTIONS=$($EMSDK/upstream/bin/llvm-nm $LIBHEIFA --format=just-symbols echo "Running Emscripten..." -BUILD_FLAGS="-lembind -o libheif.js --pre-js ${SRCDIR}/pre.js --post-js ${SRCDIR}/post.js -sWASM=$USE_WASM" +BUILD_FLAGS="-lembind -o libheif.js --post-js ${SRCDIR}/post.js -sWASM=$USE_WASM" RELEASE_BUILD_FLAGS="-O3" if [ "$STANDALONE" = "1" ]; then # Note: this intentionally overwrites the BUILD_FLAGS set above echo "Building in standalone (non-web) build mode" - BUILD_FLAGS="-s STANDALONE_WASM=1 -s WASM=1 -o libheif.wasm --no-entry" + BUILD_FLAGS="-sSTANDALONE_WASM -sWASM -o libheif.wasm --no-entry" fi if [ "$DEBUG" = "1" ]; then @@ -122,11 +122,12 @@ if [ "$DEBUG" = "1" ]; then RELEASE_BUILD_FLAGS="--profile -g" fi -emcc "$LIBHEIFA" \ - -s EXPORTED_FUNCTIONS="$EXPORTED_FUNCTIONS,_free,_malloc,_memcpy" \ - -s ALLOW_MEMORY_GROWTH=1 \ - -s ERROR_ON_UNDEFINED_SYMBOLS=0 \ - -s LLD_REPORT_UNDEFINED \ +emcc -Wl,--whole-archive "$LIBHEIFA" -Wl,--no-whole-archive \ + -sEXPORTED_FUNCTIONS="$EXPORTED_FUNCTIONS,_free,_malloc,_memcpy" \ + -sMODULARIZE \ + -sEXPORT_NAME="libheif" \ + -sWASM_ASYNC_COMPILATION=0 \ + -sALLOW_MEMORY_GROWTH \ --memory-init-file 0 \ -std=c++11 \ $LIBRARY_INCLUDE_FLAGS \ diff --git a/examples/demo.html b/examples/demo.html index beb200346b..1c6f8275a7 100644 --- a/examples/demo.html +++ b/examples/demo.html @@ -191,6 +191,8 @@

libheif decoder demo

}.bind(this)); this.drawer = new CanvasDrawer(canvas); this.decoder = new libheif.HeifDecoder(); + + console.log("Using libheif", libheif.heif_get_version()); saveSupported = this.canvas.toBlob && ((URL && URL.createObjectURL) || navigator.msSaveOrOpenBlob); if (saveSupported) { @@ -313,8 +315,7 @@

libheif decoder demo

return; } - console.log("Using libheif", libheif.heif_get_version()); - var demo = new HeifDemo(libheif); + var demo = new HeifDemo(libheif()); show("form"); diff --git a/libheif/heif_emscripten.h b/libheif/heif_emscripten.h index 38d24292e5..395e1a7566 100644 --- a/libheif/heif_emscripten.h +++ b/libheif/heif_emscripten.h @@ -417,7 +417,12 @@ EMSCRIPTEN_BINDINGS(libheif) { emscripten::class_("heif_image"); emscripten::value_object("heif_error") .field("code", &heif_error::code) - .field("subcode", &heif_error::subcode); + .field("subcode", &heif_error::subcode) + .field("message", emscripten::optional_override([](const struct heif_error& err) { + return std::string(err.message); + }), emscripten::optional_override([](struct heif_error& err, const std::string& value) { + err.message = value.c_str(); + })); } #endif // LIBHEIF_BOX_EMSCRIPTEN_H diff --git a/post.js b/post.js index d50f260127..d7aaf3824c 100644 --- a/post.js +++ b/post.js @@ -14,7 +14,7 @@ var HeifImage = function(handle) { HeifImage.prototype.free = function() { if (this.handle) { - libheif.heif_image_handle_release(this.handle); + Module.heif_image_handle_release(this.handle); this.handle = null; } }; @@ -24,8 +24,8 @@ HeifImage.prototype._ensureImage = function() { return; } - var img = libheif.heif_js_decode_image(this.handle, - libheif.heif_colorspace_YCbCr, libheif.heif_chroma_420); + var img = Module.heif_js_decode_image(this.handle, + Module.heif_colorspace.heif_colorspace_YCbCr, Module.heif_chroma.heif_chroma_420); if (!img || img.code) { console.log("Decoding image failed", this.handle, img); return; @@ -36,17 +36,17 @@ HeifImage.prototype._ensureImage = function() { this.img = img; if (img.alpha !== undefined) { - this.alpha = new Uint8Array(StringToArrayBuffer(img.alpha)); - delete img.alpha; + this.alpha = new Uint8Array(StringToArrayBuffer(img.alpha)); + delete img.alpha; } }; HeifImage.prototype.get_width = function() { - return libheif.heif_image_handle_get_width(this.handle); + return Module.heif_image_handle_get_width(this.handle); }; HeifImage.prototype.get_height = function() { - return libheif.heif_image_handle_get_height(this.handle); + return Module.heif_image_handle_get_height(this.handle); }; HeifImage.prototype.is_primary = function() { @@ -63,8 +63,8 @@ HeifImage.prototype.display = function(image_data, callback) { // If image hasn't been loaded yet, decode the image if (!this.img) { - var img = libheif.heif_js_decode_image2(this.handle, - libheif.heif_colorspace_RGB, libheif.heif_chroma_interleaved_RGBA); + var img = Module.heif_js_decode_image2(this.handle, + Module.heif_colorspace.heif_colorspace_RGB, Module.heif_chroma.heif_chroma_interleaved_RGBA); if (!img || img.code) { console.log("Decoding image failed", this.handle, img); @@ -73,14 +73,13 @@ HeifImage.prototype.display = function(image_data, callback) { } for (let c of img.channels) { - if (c.id == libheif.heif_channel_interleaved) { + if (c.id == Module.heif_channel.heif_channel_interleaved) { // copy image into output array - if (c.stride == c.width*4) { + if (c.stride == c.width * 4) { image_data.data.set(c.data); - } - else { + } else { for (let y = 0; y < c.height; y++) { let slice = c.data.slice(y * c.stride, y * c.stride + c.width * 4); let offset = y * c.width * 4; @@ -90,7 +89,7 @@ HeifImage.prototype.display = function(image_data, callback) { } } - libheif.heif_image_release(img.image); + Module.heif_image_release(img.image); } callback(image_data); @@ -103,32 +102,31 @@ var HeifDecoder = function() { HeifDecoder.prototype.decode = function(buffer) { if (this.decoder) { - libheif.heif_context_free(this.decoder); + Module.heif_context_free(this.decoder); } - this.decoder = libheif.heif_context_alloc(); + this.decoder = Module.heif_context_alloc(); if (!this.decoder) { console.log("Could not create HEIF context"); return []; } - var error = libheif.heif_context_read_from_memory(this.decoder, buffer); - if (error.code !== libheif.heif_error_Ok) { - console.log("Could not parse HEIF file", error); + var error = Module.heif_context_read_from_memory(this.decoder, buffer); + if (error.code !== Module.heif_error_code.heif_error_Ok) { + console.log("Could not parse HEIF file", error.message); return []; } - var ids = libheif.heif_js_context_get_list_of_top_level_image_IDs(this.decoder); + var ids = Module.heif_js_context_get_list_of_top_level_image_IDs(this.decoder); if (!ids || ids.code) { console.log("Error loading image ids", ids); return []; - } - else if (!ids.length) { + } else if (!ids.length) { console.log("No images found"); return []; } var result = []; for (var i = 0; i < ids.length; i++) { - var handle = libheif.heif_js_context_get_image_handle(this.decoder, ids[i]); + var handle = Module.heif_js_context_get_image_handle(this.decoder, ids[i]); if (!handle || handle.code) { console.log("Could not get image data for id", ids[i], handle); continue; @@ -139,61 +137,39 @@ HeifDecoder.prototype.decode = function(buffer) { return result; }; -var libheif = { - // Expose high-level API. - /** @expose */ - HeifDecoder: HeifDecoder, - - // Expose low-level API. - /** @expose */ - fourcc: function(s) { - return s.charCodeAt(0) << 24 | - s.charCodeAt(1) << 16 | - s.charCodeAt(2) << 8 | - s.charCodeAt(3); - } -}; - -// don't pollute the global namespace -delete this['Module']; - -// On IE this function is called with "undefined" as first parameter. Override -// with a version that supports this behaviour. -function createNamedFunction(name, body) { - if (!name) { - name = "function_" + (new Date()); - } - name = makeLegalFunctionName(name); - /*jshint evil:true*/ - return new Function( - "body", - "return function " + name + "() {\n" + - " \"use strict\";" + - " return body.apply(this, arguments);\n" + - "};\n" - )(body); +var fourcc = function(s) { + return s.charCodeAt(0) << 24 | + s.charCodeAt(1) << 16 | + s.charCodeAt(2) << 8 | + s.charCodeAt(3); } -var root = this; - -if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - /** @expose */ - exports = module.exports = libheif; +Module.HeifImage = HeifImage; +Module.HeifDecoder = HeifDecoder; +Module.fourcc = fourcc; + +// Expose enum values. +const enums = [ + 'heif_error_code', + 'heif_suberror_code', + 'heif_compression_format', + 'heif_chroma', + 'heif_colorspace', + 'heif_channel' +]; +for (const e of enums) { + for (const key in Module[e]) { + if (!Module[e].hasOwnProperty(key) || key === 'values') { + continue; + } + Module[key] = Module[e][key]; } - /** @expose */ - exports.libheif = libheif; -} else { - /** @expose */ - root.libheif = libheif; } -if (typeof define === "function" && define.amd) { - /** @expose */ - define([], function() { - return libheif; - }); +// Expose internal C API. +for (const key in Module) { + if (key.indexOf('_heif_') !== 0 || Module[key.slice(1)] !== undefined) { + continue; + } + Module[key.slice(1)] = Module[key]; } - -// NOTE: wrapped inside "(function() {" block from pre.js -}).call(this); diff --git a/pre.js b/pre.js deleted file mode 100644 index 3978ad958f..0000000000 --- a/pre.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @preserve libheif.js HEIF decoder - * (c)2017 struktur AG, http://www.struktur.de, opensource@struktur.de - * - * This file is part of libheif - * https://github.com/strukturag/libheif - * - * libheif is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * libheif is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with libheif. If not, see . - */ -(function() { -var Module = { - print: function(text) { - text = Array.prototype.slice.call(arguments).join(' '); - console.log(text); - }, - printErr: function(text) { - text = Array.prototype.slice.call(arguments).join(' '); - console.error(text); - }, - canvas: {}, - noInitialRun: true, - onRuntimeInitialized: function() { - // Expose enum values. - var enums = { - "heif_error_code": true, - "heif_suberror_code": true, - "heif_compression_format": true, - "heif_chroma": true, - "heif_colorspace": true, - "heif_channel": true - }; - var e; - for (e in enums) { - if (!enums.hasOwnProperty(e)) { - continue; - } - - for (key in this[e]) { - if (!this[e].hasOwnProperty(key) || - key === "values") { - continue; - } - - libheif[key] = this[e][key]; - } - } - - // Expose internal C API. - for (key in this) { - if (enums.hasOwnProperty(key.slice(1)) || key.indexOf("_heif_") !== 0) { - continue; - } - libheif[key.slice(1)] = this[key]; - } - - // Expose embind API. - for (key in Module) { - if (enums.hasOwnProperty(key) || key.indexOf("heif_") !== 0) { - continue; - } - libheif[key] = Module[key]; - } - } -}; diff --git a/scripts/test-javascript.js b/scripts/test-javascript.js index 8b23fddfeb..a75621ef40 100644 --- a/scripts/test-javascript.js +++ b/scripts/test-javascript.js @@ -18,41 +18,27 @@ * You should have received a copy of the GNU Lesser General Public License * along with libheif. If not, see . */ -(function() { - - console.log("Running libheif JavaScript tests ..."); - - var libheif = require('../libheif.js'); - console.log("Loaded libheif.js", libheif.heif_get_version()); - - // Ensure that no "undefined" properties are exported. - var key; - var missing = []; - for (key in libheif) { - if (!libheif.hasOwnProperty(key)) { - continue; - } - if (typeof(libheif[key]) === "undefined") { - missing.push(key); - } - } - if (missing.length) { - throw new Error("The following properties are not defined: " + missing); - } - - // Decode the example file and make sure at least one image is returned. - var fs = require('fs'); - fs.readFile('examples/example.heic', function(err, data) { - if (err) { - throw err; - } - - var decoder = new libheif.HeifDecoder(); - var image_data = decoder.decode(data); - console.log("Loaded images:", image_data.length); - if (!image_data.length) { - throw new Error("Should have loaded images"); - } - }); - -})(); + +const assert = require('assert'); +const fs = require('fs'); + +console.log('Running libheif JavaScript tests ...'); + +const libheif = require('../libheif.js')(); + +// Test Embind API. +console.log('Loaded libheif.js', libheif.heif_get_version()); + +// Test internal C API. +assert(libheif.heif_get_version_number_major() === 1, 'libheif major version should be 1') + +// Test enum values. +assert(libheif.heif_error_Ok.value === 0, 'heif_error_Ok should be 0') + +// Decode the example file and make sure at least one image is returned. +const data = fs.readFileSync('examples/example.heic'); +const decoder = new libheif.HeifDecoder(); +const image_data = decoder.decode(data); + +console.log('Loaded images:', image_data.length); +assert(image_data.length > 0, "Should have loaded images")