From f6fe61343c69fcb571412aa0f7ea39487f0a9597 Mon Sep 17 00:00:00 2001 From: Alireza Date: Thu, 9 Jul 2026 13:45:37 -0400 Subject: [PATCH 1/3] chore(libjpeg-turbo): update extern/libjpeg-turbo submodule to upstream 3.2.0 Advances both the 8-bit and 12-bit packages' shared submodule from dc4a93f (2.1.4-era, Dec 2022) to upstream 3.2.0 (2026-06-30). No custom fork patches (clean version advance). Fork PR: cornerstonejs/libjpeg-turbo#1. Major-version jump (2.x -> 3.x): CI is the first build of 3.2.0 against our 8-bit and 12-bit glue; API drift (incl. 3.x's unified precision handling vs the old WITH_12BIT flag) is expected and will be iterated. --- packages/libjpeg-turbo-12bit/extern/libjpeg-turbo | 2 +- packages/libjpeg-turbo-8bit/extern/libjpeg-turbo | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/libjpeg-turbo-12bit/extern/libjpeg-turbo b/packages/libjpeg-turbo-12bit/extern/libjpeg-turbo index dc4a93f..c85e6b9 160000 --- a/packages/libjpeg-turbo-12bit/extern/libjpeg-turbo +++ b/packages/libjpeg-turbo-12bit/extern/libjpeg-turbo @@ -1 +1 @@ -Subproject commit dc4a93fab38b42d29b89a533409e012570180e28 +Subproject commit c85e6b905bf237038faa936dab160ebfc5da0344 diff --git a/packages/libjpeg-turbo-8bit/extern/libjpeg-turbo b/packages/libjpeg-turbo-8bit/extern/libjpeg-turbo index dc4a93f..c85e6b9 160000 --- a/packages/libjpeg-turbo-8bit/extern/libjpeg-turbo +++ b/packages/libjpeg-turbo-8bit/extern/libjpeg-turbo @@ -1 +1 @@ -Subproject commit dc4a93fab38b42d29b89a533409e012570180e28 +Subproject commit c85e6b905bf237038faa936dab160ebfc5da0344 From 5e15392a4520f0e5f0adcfb9403c9d7564082fc0 Mon Sep 17 00:00:00 2001 From: Alireza Date: Thu, 9 Jul 2026 14:16:39 -0400 Subject: [PATCH 2/3] build(libjpeg-turbo-8bit): build libjpeg-turbo 3.x as a separate project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libjpeg-turbo 3.x forbids add_subdirectory() integration, so build it standalone (its own emscripten cmake) and link the produced libturbojpeg.a as an IMPORTED target. Handles 3.x layout changes: headers moved under src/, disable the new SPNG/ZLIB dep (WITH_SPNG=0). No glue changes — the legacy TurboJPEG API our wrapper uses (tjInitDecompress/tjDecompress2/...) is still present in 3.2.0. First blind cut; iterating on CI. 12-bit rework to follow. --- packages/libjpeg-turbo-8bit/CMakeLists.txt | 12 ++++---- packages/libjpeg-turbo-8bit/build.sh | 29 ++++++++++++------- .../libjpeg-turbo-8bit/src/CMakeLists.txt | 13 ++++++++- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/packages/libjpeg-turbo-8bit/CMakeLists.txt b/packages/libjpeg-turbo-8bit/CMakeLists.txt index 00807fd..431e923 100644 --- a/packages/libjpeg-turbo-8bit/CMakeLists.txt +++ b/packages/libjpeg-turbo-8bit/CMakeLists.txt @@ -31,11 +31,13 @@ if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/libjpeg-turbo/CMakeLists.txt") message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.") endif() -option(ENABLE_SHARED "" OFF) -option(ENABLE_STATIC "" ON) - -# add the external library -add_subdirectory(extern/libjpeg-turbo EXCLUDE_FROM_ALL) +# NOTE: libjpeg-turbo 3.x refuses add_subdirectory() integration (it asserts it +# is the top-level project and errors out). It is now built as a separate +# project by build.sh, which passes its build dir in LIBJPEG_TURBO_BUILD_DIR; +# src/CMakeLists.txt links the produced libturbojpeg.a as an IMPORTED target. +if(EMSCRIPTEN AND NOT DEFINED LIBJPEG_TURBO_BUILD_DIR) + message(FATAL_ERROR "LIBJPEG_TURBO_BUILD_DIR not set — run build.sh, which builds libjpeg-turbo first and passes its build dir.") +endif() # add the js wrapper if(EMSCRIPTEN) diff --git a/packages/libjpeg-turbo-8bit/build.sh b/packages/libjpeg-turbo-8bit/build.sh index 8741403..489394b 100644 --- a/packages/libjpeg-turbo-8bit/build.sh +++ b/packages/libjpeg-turbo-8bit/build.sh @@ -1,18 +1,27 @@ #!/bin/sh # Disable exit on non 0 set +e -mkdir -p build -mkdir -p dist +rm -rf build build-libjpeg +mkdir -p build build-libjpeg dist -# DEBUG CONFIGURE -#(cd build && emcmake cmake -DCMAKE_BUILD_TYPE=Debug ..) && +# libjpeg-turbo 3.x forbids add_subdirectory() integration (its CMake asserts +# it is the top-level project). So we build it as a SEPARATE project first, +# then link the produced static lib (libturbojpeg.a) into our wasm wrapper. +# WITH_SIMD=0 keeps parity with the previous build; WITH_SPNG=0 avoids the new +# 3.x zlib/spng dependency (only used by the tj* tools, not our decode/encode). +echo "~~~ CONFIGURE libjpeg-turbo 3.x (standalone) ~~~" +(cd build-libjpeg && emcmake cmake -G"Unix Makefiles" \ + -DCMAKE_BUILD_TYPE=Release \ + -DENABLE_SHARED=0 -DENABLE_STATIC=1 \ + -DWITH_SIMD=0 -DWITH_SPNG=0 -DWITH_TURBOJPEG=1 \ + ../extern/libjpeg-turbo) +echo "~~~ MAKE libjpeg-turbo ~~~" +(cd build-libjpeg && emmake make VERBOSE=1 -j 16 turbojpeg-static jpeg-static) -echo "~~~ CONFIGURE ~~~" -# Only include decoding to make it smaller -# see https://github.com/libjpeg-turbo/libjpeg-turbo/issues/431 -(cd build && emcmake cmake -G"Unix Makefiles" ..) -#(cd build && emcmake cmake -G"Unix Makefiles"..) -echo "~~~ MAKE ~~~" +echo "~~~ CONFIGURE wrapper ~~~" +LIBJPEG_TURBO_BUILD_DIR="$(cd build-libjpeg && pwd)" +(cd build && emcmake cmake -G"Unix Makefiles" -DLIBJPEG_TURBO_BUILD_DIR="$LIBJPEG_TURBO_BUILD_DIR" ..) +echo "~~~ MAKE wrapper ~~~" (cd build && emmake make VERBOSE=1 -j 16) echo "~~~ COPY ~~~ " cp ./build/src/libjpegturbowasm.js ./dist diff --git a/packages/libjpeg-turbo-8bit/src/CMakeLists.txt b/packages/libjpeg-turbo-8bit/src/CMakeLists.txt index 433ff18..e657495 100644 --- a/packages/libjpeg-turbo-8bit/src/CMakeLists.txt +++ b/packages/libjpeg-turbo-8bit/src/CMakeLists.txt @@ -1,6 +1,17 @@ - include_directories("../extern/libjpeg-turbo" "../build/extern/libjpeg-turbo") + # libjpeg-turbo 3.x moved its public headers under src/; jconfig.h is + # generated into the standalone build dir. turbojpeg-static is consumed as + # a pre-built IMPORTED library (built separately by build.sh — 3.x forbids + # add_subdirectory). + include_directories( + "../extern/libjpeg-turbo/src" + "${LIBJPEG_TURBO_BUILD_DIR}" + "${LIBJPEG_TURBO_BUILD_DIR}/src") + + add_library(turbojpeg-static STATIC IMPORTED) + set_target_properties(turbojpeg-static PROPERTIES + IMPORTED_LOCATION "${LIBJPEG_TURBO_BUILD_DIR}/libturbojpeg.a") add_executable(libjpegturbojs jslib.cpp) From 7c0c49af57b81e2d1ae4f253546af769da6b0782 Mon Sep 17 00:00:00 2001 From: Alireza Date: Thu, 9 Jul 2026 14:42:16 -0400 Subject: [PATCH 3/3] build+fix(libjpeg-turbo-12bit): upgrade to libjpeg-turbo 3.x (multi-precision API) 3.x forbids add_subdirectory() and removed WITH_12BIT (one build is now multi-precision). Build libjpeg-turbo standalone and link libjpeg.a as an IMPORTED target (two-phase build.sh), and rewrite the decoder for 3.x: - decode grayscale 12-bit via jpeg12_read_scanlines + J12SAMPARRAY (the 3.x per-precision API) instead of jpeg_read_scanlines (the old WITH_12BIT model) - guard on num_components==1 and data_precision==12; overflow-checked sizing - correct single-component int16 output (no JCS_EXT_RGBA overflow) 3.x headers moved under src/. No dependency on #73 (left untouched); the decode-correctness fix here mirrors #73's grayscale logic but on the 3.x API. --- packages/libjpeg-turbo-12bit/CMakeLists.txt | 13 ++-- packages/libjpeg-turbo-12bit/build.sh | 27 +++++-- .../libjpeg-turbo-12bit/src/CMakeLists.txt | 12 ++- .../libjpeg-turbo-12bit/src/JPEGDecoder.hpp | 77 ++++++++++++------- 4 files changed, 89 insertions(+), 40 deletions(-) diff --git a/packages/libjpeg-turbo-12bit/CMakeLists.txt b/packages/libjpeg-turbo-12bit/CMakeLists.txt index 00807fd..037815e 100644 --- a/packages/libjpeg-turbo-12bit/CMakeLists.txt +++ b/packages/libjpeg-turbo-12bit/CMakeLists.txt @@ -31,11 +31,14 @@ if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/libjpeg-turbo/CMakeLists.txt") message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.") endif() -option(ENABLE_SHARED "" OFF) -option(ENABLE_STATIC "" ON) - -# add the external library -add_subdirectory(extern/libjpeg-turbo EXCLUDE_FROM_ALL) +# libjpeg-turbo 3.x refuses add_subdirectory() integration and dropped the +# WITH_12BIT build flag (a single build is now multi-precision: 8/12/16-bit). +# build.sh builds it as a separate project and passes its build dir in +# LIBJPEG_TURBO_BUILD_DIR; src/CMakeLists.txt links the produced libjpeg.a and +# the decoder uses the 3.x jpeg12_* API for 12-bit samples. +if(EMSCRIPTEN AND NOT DEFINED LIBJPEG_TURBO_BUILD_DIR) + message(FATAL_ERROR "LIBJPEG_TURBO_BUILD_DIR not set — run build.sh, which builds libjpeg-turbo first and passes its build dir.") +endif() # add the js wrapper if(EMSCRIPTEN) diff --git a/packages/libjpeg-turbo-12bit/build.sh b/packages/libjpeg-turbo-12bit/build.sh index 8593234..5043fc5 100644 --- a/packages/libjpeg-turbo-12bit/build.sh +++ b/packages/libjpeg-turbo-12bit/build.sh @@ -1,16 +1,27 @@ #!/bin/sh # Disable exit on non 0 set +e -rm -rf dist -mkdir -p build -mkdir -p dist +rm -rf build build-libjpeg dist +mkdir -p build build-libjpeg dist -# DEBUG CONFIGURE -#(cd build && emcmake cmake -DCMAKE_BUILD_TYPE=Debug ..) && +# libjpeg-turbo 3.x forbids add_subdirectory() and dropped WITH_12BIT — a single +# build is now multi-precision (8/12/16-bit), exposing jpeg12_* APIs. So build +# libjpeg-turbo as a SEPARATE project first (Release, WITH_SIMD=0 for parity, +# WITH_SPNG=0 to avoid the new zlib/spng dep), then link its libjpeg.a; the +# 12-bit decoder uses jpeg12_read_scanlines. +echo "~~~ CONFIGURE libjpeg-turbo 3.x (standalone, multi-precision) ~~~" +(cd build-libjpeg && emcmake cmake -G"Unix Makefiles" \ + -DCMAKE_BUILD_TYPE=Release \ + -DENABLE_SHARED=0 -DENABLE_STATIC=1 \ + -DWITH_SIMD=0 -DWITH_SPNG=0 \ + ../extern/libjpeg-turbo) +echo "~~~ MAKE libjpeg-turbo ~~~" +(cd build-libjpeg && emmake make VERBOSE=1 -j 16 jpeg-static) -echo "~~~ CONFIGURE ~~~" -(cd build && emcmake cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug -DWITH_12BIT=1 ..) -echo "~~~ MAKE ~~~" +echo "~~~ CONFIGURE wrapper ~~~" +LIBJPEG_TURBO_BUILD_DIR="$(cd build-libjpeg && pwd)" +(cd build && emcmake cmake -G"Unix Makefiles" -DLIBJPEG_TURBO_BUILD_DIR="$LIBJPEG_TURBO_BUILD_DIR" ..) +echo "~~~ MAKE wrapper ~~~" (cd build && emmake make VERBOSE=1 -j 16) echo "~~~ COPY ~~~ " cp ./build/src/libjpegturbo12wasm.js ./dist diff --git a/packages/libjpeg-turbo-12bit/src/CMakeLists.txt b/packages/libjpeg-turbo-12bit/src/CMakeLists.txt index 57e688c..092bd62 100644 --- a/packages/libjpeg-turbo-12bit/src/CMakeLists.txt +++ b/packages/libjpeg-turbo-12bit/src/CMakeLists.txt @@ -1,6 +1,16 @@ - include_directories("../extern/libjpeg-turbo" "../build/extern/libjpeg-turbo") + # libjpeg-turbo 3.x moved public headers under src/; jconfig.h is generated + # into the standalone build dir. jpeg-static is consumed as a pre-built + # IMPORTED library (built separately by build.sh — 3.x forbids add_subdirectory). + include_directories( + "../extern/libjpeg-turbo/src" + "${LIBJPEG_TURBO_BUILD_DIR}" + "${LIBJPEG_TURBO_BUILD_DIR}/src") + + add_library(jpeg-static STATIC IMPORTED) + set_target_properties(jpeg-static PROPERTIES + IMPORTED_LOCATION "${LIBJPEG_TURBO_BUILD_DIR}/libjpeg.a") add_executable(libjpegturbo12js jslib.cpp) diff --git a/packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp b/packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp index 7a15c92..7a53102 100644 --- a/packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp +++ b/packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp @@ -3,7 +3,10 @@ #pragma once +#include #include +#include +#include #include // #include "config.h" #include "jpeglib.h" @@ -117,43 +120,65 @@ class JPEGDecoder { jpeg_create_decompress(&cinfo); jpeg_mem_src(&cinfo, encoded_.data(), encoded_.size()); - // Read file header, set default decompression parameters + // Read the header. In libjpeg-turbo 3.x this is precision-agnostic. jpeg_read_header(&cinfo, TRUE); - // Force RGBA decoding, even for grayscale images - cinfo.out_color_space = JCS_EXT_RGBA; - jpeg_start_decompress(&cinfo); + // This codec handles single-component (grayscale) 12-bit JPEGs only. Fail + // closed on color input: forcing JCS_GRAYSCALE on a multi-component image + // would silently drop chroma and mis-report componentCount=1. + if (cinfo.num_components != 1) { + jpeg_destroy_decompress(&cinfo); + throw std::runtime_error( + "Unsupported 12-bit JPEG: expected 1 component (grayscale), got " + + std::to_string(cinfo.num_components)); + } + // libjpeg-turbo 3.x is multi-precision in a single build; this codec only + // supports 12-bit samples. Reject other precisions rather than mis-decode. + if (cinfo.data_precision != 12) { + jpeg_destroy_decompress(&cinfo); + throw std::runtime_error( + "Unsupported JPEG precision: expected 12-bit, got " + + std::to_string(cinfo.data_precision)); + } + + cinfo.out_color_space = JCS_GRAYSCALE; + jpeg_start_decompress(&cinfo); frameInfo_.width = cinfo.output_width; frameInfo_.height = cinfo.output_height; - frameInfo_.bitsPerSample = 8; - frameInfo_.componentCount = 1; //inColorspace == 2 ? 1 : 3; - - // Prepare output buffer - // int pixelFormat = (frameInfo_.componentCount == 1) ? TJPF_GRAY : TJPF_RGB; - - // const size_t destinationSize = frameInfo_.width * frameInfo_.height * tjPixelSize[pixelFormat]; - int pixelFormat = 1; - size_t output_size = cinfo.output_width * cinfo.output_height * pixelFormat; - - // std::vector output_buffer(output_size); + frameInfo_.bitsPerSample = 12; + frameInfo_.componentCount = 1; + + // One 12-bit sample per pixel, stored in a 16-bit-wide J12SAMPLE (short). + // Overflow-checked size (capped at 512 MiB of samples) so a malformed + // header cannot overflow the computation or force a huge allocation. + constexpr uint64_t kMaxOutputSamples = 512ull * 1024ull * 1024ull; + const uint64_t width64 = static_cast(cinfo.output_width); + const uint64_t height64 = static_cast(cinfo.output_height); + if (width64 == 0 || height64 == 0) { + jpeg_destroy_decompress(&cinfo); + throw std::runtime_error("Invalid JPEG dimensions (zero width or height)"); + } + uint64_t output_size64 = width64 * height64; + if (output_size64 / width64 != height64 || output_size64 == 0 || + output_size64 > kMaxOutputSamples) { + jpeg_destroy_decompress(&cinfo); + throw std::runtime_error("Decoded buffer size out of range"); + } + const size_t output_size = static_cast(output_size64); decoded_.resize(output_size); + const size_t stride = static_cast(cinfo.output_width); - auto stride = cinfo.output_width * pixelFormat; - - // Process data + // 12-bit precision decodes through jpeg12_read_scanlines with a + // J12SAMPARRAY (short-based) — the libjpeg-turbo 3.x per-precision API. + // decoded_ is std::vector, matching J12SAMPLE. while (cinfo.output_scanline < cinfo.output_height) { - int16_t* output_data = &decoded_[stride * cinfo.output_scanline]; - (void)jpeg_read_scanlines(&cinfo, &output_data, 1); + J12SAMPROW output_data = + reinterpret_cast(&decoded_[stride * cinfo.output_scanline]); + (void)jpeg12_read_scanlines(&cinfo, &output_data, 1); } jpeg_finish_decompress(&cinfo); - - // Step 7: release JPEG compression object - - // auto data = Uint8ClampedArray.new_(typed_memory_view(output_size, &output_buffer[0])); - - // This is an important step since it will release a good deal of memory. jpeg_destroy_decompress(&cinfo); }