chore(libjpeg-turbo): update submodule to upstream 3.2.0 (8-bit + 12-bit)#79
chore(libjpeg-turbo): update submodule to upstream 3.2.0 (8-bit + 12-bit)#79sedghi wants to merge 3 commits into
Conversation
…am 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.
📝 WalkthroughWalkthroughBoth libjpeg-turbo-12bit and libjpeg-turbo-8bit packages switch from in-tree submodule builds to a two-stage standalone build process using LIBJPEG_TURBO_BUILD_DIR, linking pre-built static libraries via imported CMake targets. The 12-bit decoder is rewritten to validate and decode only single-component 12-bit grayscale JPEGs. Submodule pointers are updated. Changeslibjpeg-turbo-12bit standalone build and decoder update
libjpeg-turbo-8bit standalone build
Estimated code review effort: 3 (Moderate) | ~25 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Merging this PR will improve performance by 7.18%
Performance Changes
Tip Curious why this is faster? Comment Comparing Footnotes
|
…recision 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.
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (2)
packages/libjpeg-turbo-8bit/CMakeLists.txt (1)
38-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider validating that
LIBJPEG_TURBO_BUILD_DIRactually exists.The guard checks
NOT DEFINEDbut not whether the directory exists or contains the expectedlibturbojpeg.a. A stale or empty directory would pass configuration and fail later at link time with a less clear error. Adding an existence check (e.g.,NOT IS_DIRECTORYorNOT EXISTS "${LIBJPEG_TURBO_BUILD_DIR}/libturbojpeg.a") would surface the problem early.This is low-priority since
build.shis the expected entry point and it creates the directory, but it would help when invokingcmakemanually during debugging.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/libjpeg-turbo-8bit/CMakeLists.txt` around lines 38 - 39, Add an existence validation for LIBJPEG_TURBO_BUILD_DIR in the CMake guard so configuration fails early if the path is stale, empty, or missing the expected libturbojpeg.a. Update the existing EMSCRIPTEN check in CMakeLists.txt to verify the directory/file before proceeding, and keep the fatal error message in the same guard so manual cmake invocations surface a clear setup issue. Use the LIBJPEG_TURBO_BUILD_DIR check as the main entry point for locating the fix.packages/libjpeg-turbo-8bit/build.sh (1)
16-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDrop
jpeg-staticfrompackages/libjpeg-turbo-8bit/build.sh. The wrapper only linksturbojpeg-static, so this extra target just adds build time.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/libjpeg-turbo-8bit/build.sh` around lines 16 - 19, Remove the unnecessary jpeg-static target from the libjpeg-turbo build step in build.sh, since the wrapper only depends on turbojpeg-static. Update the emmake make invocation in the libjpeg-turbo build block to build only the turbojpeg-static target and keep the rest of the build flow unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/libjpeg-turbo-12bit/build.sh`:
- Around line 18-19: The libjpeg-turbo build step in build.sh is not checked for
failure, so a failed emmake make can be masked and later surface as a misleading
imported library error. Add explicit error handling immediately after the build
stage in the script that runs the build-libjpeg/emmake make command so the
script exits with a clear libjpeg-turbo build failed message before continuing
to the wrapper CMake flow.
- Around line 13-19: The build configuration in build.sh is using the wrong
CMake flag for spng support; update the libjpeg-turbo cmake invocation in the
build-libjpeg setup to use WITH_SYSTEM_SPNG instead of WITH_SPNG. Keep the
existing jpeg-static target and libjpeg.a output path unchanged, and only adjust
the flag in the emcmake cmake command so the libjpeg-turbo 3.x build is
configured correctly.
In `@packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp`:
- Around line 147-150: The 12-bit grayscale metadata setup in JPEGDecoder should
also initialize frameInfo_.isSigned, since JPEGDecoder’s constructor does not
set it and downstream consumers read this field. Update the frameInfo_
population block where width, height, bitsPerSample, and componentCount are
assigned so isSigned is explicitly set for this path, keeping the FrameInfo
state fully defined.
- Around line 170-179: `getDecodedBuffer()` is still exposing the decoded 12-bit
samples as byte-sized data, which truncates values above 255. Update the
JS-facing buffer wrapping to use a 16-bit typed view (`Int16Array` or
`Uint16Array`) over `decoded_` so the `J12SAMPROW`/`J12SAMPLE` data from
`JPEGDecoder` is preserved end-to-end. Keep the change aligned with the
`decoded_` storage type and the decoding path in `jpeg12_read_scanlines`.
- Around line 123-145: The JPEGDecoder path still uses the default libjpeg error
handling, so fatal decode failures can abort the WASM instance instead of
surfacing as exceptions. In JPEGDecoder.hpp, add a custom jpeg_error_mgr for the
decode flow around jpeg_std_error and jpeg_create_decompress, wire in
setjmp/longjmp before any libjpeg calls that can fail, and convert libjpeg’s
reported message into a C++ exception in the same decode path that already
performs the component and precision checks.
In `@packages/libjpeg-turbo-8bit/build.sh`:
- Around line 3-5: The build script currently disables exit-on-error with set
+e, which lets later stages continue after an earlier failure and hides the real
root cause. Update build.sh so each stage (especially the libjpeg-turbo
configure/make work and the later packaging step) either runs under set -e or
explicitly checks command exit codes before proceeding, using the existing build
stage flow to stop immediately on failure and preserve the original error.
---
Nitpick comments:
In `@packages/libjpeg-turbo-8bit/build.sh`:
- Around line 16-19: Remove the unnecessary jpeg-static target from the
libjpeg-turbo build step in build.sh, since the wrapper only depends on
turbojpeg-static. Update the emmake make invocation in the libjpeg-turbo build
block to build only the turbojpeg-static target and keep the rest of the build
flow unchanged.
In `@packages/libjpeg-turbo-8bit/CMakeLists.txt`:
- Around line 38-39: Add an existence validation for LIBJPEG_TURBO_BUILD_DIR in
the CMake guard so configuration fails early if the path is stale, empty, or
missing the expected libturbojpeg.a. Update the existing EMSCRIPTEN check in
CMakeLists.txt to verify the directory/file before proceeding, and keep the
fatal error message in the same guard so manual cmake invocations surface a
clear setup issue. Use the LIBJPEG_TURBO_BUILD_DIR check as the main entry point
for locating the fix.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 6d518ffc-ca76-4d47-b5cf-8dbb283192ff
📒 Files selected for processing (9)
packages/libjpeg-turbo-12bit/CMakeLists.txtpackages/libjpeg-turbo-12bit/build.shpackages/libjpeg-turbo-12bit/extern/libjpeg-turbopackages/libjpeg-turbo-12bit/src/CMakeLists.txtpackages/libjpeg-turbo-12bit/src/JPEGDecoder.hpppackages/libjpeg-turbo-8bit/CMakeLists.txtpackages/libjpeg-turbo-8bit/build.shpackages/libjpeg-turbo-8bit/extern/libjpeg-turbopackages/libjpeg-turbo-8bit/src/CMakeLists.txt
| (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) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify libjpeg-turbo 3.x CMake configuration: target names, options, and library output path.
SUBMODULE="packages/libjpeg-turbo-12bit/extern/libjpeg-turbo"
echo "=== Checking jpeg-static target definition ==="
rg -n 'add_library.*jpeg-static' "$SUBMODULE" --type cmake
echo "=== Checking CMAKE_ARCHIVE_OUTPUT_DIRECTORY ==="
rg -n 'CMAKE_ARCHIVE_OUTPUT_DIRECTORY' "$SUBMODULE" --type cmake
echo "=== Checking add_library location (root vs src/) ==="
rg -n 'add_library.*jpeg' "$SUBMODULE/CMakeLists.txt" 2>/dev/null
rg -n 'add_library.*jpeg' "$SUBMODULE/src/CMakeLists.txt" 2>/dev/null
echo "=== Checking WITH_SPNG option ==="
rg -n 'WITH_SPNG\|WITH_PNG' "$SUBMODULE" --type cmake
echo "=== Checking ENABLE_SHARED/ENABLE_STATIC options ==="
rg -n 'ENABLE_SHARED\|ENABLE_STATIC' "$SUBMODULE" --type cmake
echo "=== Checking jpeg12_ API symbols ==="
rg -n 'jpeg12_read_scanlines\|jpeg12_read_header' "$SUBMODULE" --type c --type cmakeRepository: cornerstonejs/codecs
Length of output: 430
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== Files of interest ==="
git ls-files | rg 'packages/libjpeg-turbo-12bit/(build\.sh|src/CMakeLists\.txt|extern/libjpeg-turbo/.*CMakeLists\.txt|extern/libjpeg-turbo/.*cmake|extern/libjpeg-turbo/.*/CMakeLists\.txt)$'
echo "=== build.sh ==="
cat -n packages/libjpeg-turbo-12bit/build.sh
echo "=== top-level vendored CMake files (small only) ==="
for f in \
packages/libjpeg-turbo-12bit/extern/libjpeg-turbo/CMakeLists.txt \
packages/libjpeg-turbo-12bit/extern/libjpeg-turbo/src/CMakeLists.txt
do
if [ -f "$f" ]; then
echo "--- $f ($(wc -l < "$f") lines) ---"
sed -n '1,260p' "$f"
fi
done
echo "=== Search for relevant symbols/options/targets ==="
rg -n 'jpeg-static|WITH_SPNG|WITH_PNG|ENABLE_SHARED|ENABLE_STATIC|CMAKE_ARCHIVE_OUTPUT_DIRECTORY|add_library|OUTPUT_NAME|ARCHIVE_OUTPUT_DIRECTORY' \
packages/libjpeg-turbo-12bit/extern/libjpeg-turboRepository: cornerstonejs/codecs
Length of output: 2052
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== packages/libjpeg-turbo-12bit/src/CMakeLists.txt ==="
cat -n packages/libjpeg-turbo-12bit/src/CMakeLists.txt
echo "=== references to libjpeg-turbo build dir / libjpeg.a / jpeg-static in wrapper files ==="
rg -n 'LIBJPEG_TURBO_BUILD_DIR|libjpeg\.a|jpeg-static|WITH_SPNG|WITH_PNG|ENABLE_SHARED|ENABLE_STATIC' \
packages/libjpeg-turbo-12bitRepository: cornerstonejs/codecs
Length of output: 5473
🌐 Web query:
libjpeg-turbo 3.x CMake WITH_SPNG jpeg-static libjpeg.a output directory target name
💡 Result:
In libjpeg-turbo 3.x, the CMake build system provides specific configurations and targets for handling static libraries and external dependencies like libspng. WITH_SPNG Option The build system uses the WITH_SYSTEM_SPNG option to determine how libspng is handled [1]. When set to OFF (the default), libjpeg-turbo uses its internal libspng source tree. When set to ON, it attempts to find and link against a system-supplied libspng library using pkg-config [1][2]. Static Library Target Names The CMake target name for the static libjpeg library is jpeg-static [3][4][5]. On non-MSVC platforms (e.g., Linux, macOS), the build system typically sets the output name property of this target to jpeg, resulting in a file named libjpeg.a [3][5]. On MSVC, it typically defaults to jpeg-static.lib [6][7]. Output Directory By default, the build artifacts are placed in the root of the binary directory (the directory from which CMake is executed) [6][7]. If a multi-configuration generator (like Visual Studio) is used, output files are often placed in subdirectories corresponding to the build configuration (e.g., Debug/ or Release/) [6][7]. Advanced Control If you require custom output naming for the static libraries, you can utilize the CMake cache variable JPEG_STATIC_OUTPUT_NAME (e.g., -DJPEG_STATIC_OUTPUT_NAME=jpeg) [8]. For general installation, the build system respects the CMAKE_INSTALL_PREFIX variable to determine the installation directory [9]. Top Results: [1][6][3][8][7]
Citations:
- 1: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/CMakeLists.txt
- 2: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/sharedlib/CMakeLists.txt
- 3: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/b1682fb7c8441a2fa9090b54f97b28a8a7c6d9cf/CMakeLists.txt
- 4: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/6f241d4d425296badbf6b4f9477fbc3cace5a8fc/CMakeLists.txt
- 5: https://android.googlesource.com/platform/external/libjpeg-turbo/+/5b599a9db6ef31cf504d58a4988232f400996e13/CMakeLists.txt
- 6: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/BUILDING.md
- 7: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/dev/BUILDING.md
- 8: Allow changing output names for static libs and install pdb files for static archives. libjpeg-turbo/libjpeg-turbo#770
- 9: https://skia.googlesource.com/external/github.com/libjpeg-turbo/libjpeg-turbo/+/HEAD/BUILDING.md
🌐 Web query:
libjpeg-turbo 3.x CMakeLists WITH_SYSTEM_SPNG WITH_SPNG jpeg-static libjpeg.a
💡 Result:
In libjpeg-turbo 3.x, the CMake build system provides options to control how libspng is handled during the build process [1][2]. Build Options for libspng: - WITH_SYSTEM_SPNG: This boolean CMake option (default: OFF) allows you to build libjpeg-turbo against a system-supplied libspng implementation instead of using the version included in the libjpeg-turbo source tree [1][2]. - When WITH_SYSTEM_SPNG is enabled (ON), the build system uses pkg-config to locate the system's libspng [1]. If disabled (OFF, default), it uses the internal implementation located in the src/spng subdirectory of the source tree [1][2]. Static Libraries and Dependencies: - The build process is designed to be self-contained [3]. When not using a system-supplied libspng or zlib, the build uses the internal, static versions provided within the source tree (e.g., spng-static, zlib-static) [1]. - The executables cjpeg and djpeg are configured to link against these static objects [4]. - Regarding libjpeg.a and jpeg-static: libjpeg-turbo provides the ability to build both shared and static libraries (governed by ENABLE_SHARED and ENABLE_STATIC options, default both TRUE) [5][6]. The static library is typically referred to as jpeg-static (the target name in CMake) [5][6]. Applications linking statically to libjpeg-turbo often link against this jpeg-static target [5]. In summary, the WITH_SYSTEM_SPNG option enables the use of an external libspng dependency, while the default behavior is to use a bundled version [1][2]. Static linking of the core libjpeg functionality is supported via the standard ENABLE_STATIC option [5][6].
Citations:
- 1: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/CMakeLists.txt
- 2: https://context7.com/libjpeg-turbo/libjpeg-turbo/llms.txt
- 3: PNG image I/O libjpeg-turbo/libjpeg-turbo#645
- 4: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/sharedlib/CMakeLists.txt
- 5: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/b1682fb7c8441a2fa9090b54f97b28a8a7c6d9cf/CMakeLists.txt
- 6: https://android.googlesource.com/platform/external/libjpeg-turbo/+/f5c8ca7bb600d1e5488f65c6d5447b19b18d899c/CMakeLists.txt
Use WITH_SYSTEM_SPNG here, not WITH_SPNG.
jpeg-static and ${LIBJPEG_TURBO_BUILD_DIR}/libjpeg.a already match libjpeg-turbo 3.x; only the spng CMake flag is off.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/libjpeg-turbo-12bit/build.sh` around lines 13 - 19, The build
configuration in build.sh is using the wrong CMake flag for spng support; update
the libjpeg-turbo cmake invocation in the build-libjpeg setup to use
WITH_SYSTEM_SPNG instead of WITH_SPNG. Keep the existing jpeg-static target and
libjpeg.a output path unchanged, and only adjust the flag in the emcmake cmake
command so the libjpeg-turbo 3.x build is configured correctly.
| echo "~~~ MAKE libjpeg-turbo ~~~" | ||
| (cd build-libjpeg && emmake make VERBOSE=1 -j 16 jpeg-static) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Add error checking after the libjpeg-turbo build stage.
Line 2 (set +e) disables exit-on-error, so if the first-stage emmake make fails, the script continues. LIBJPEG_TURBO_BUILD_DIR (line 22) will still be set because mkdir -p already created build-libjpeg, so the wrapper CMake guard passes — but the wrapper build then fails with a confusing "imported library not found" error instead of "libjpeg-turbo build failed." This is especially painful when iterating in CI as noted in the PR objectives.
🔧 Proposed fix
echo "~~~ MAKE libjpeg-turbo ~~~"
-(cd build-libjpeg && emmake make VERBOSE=1 -j 16 jpeg-static)
+(cd build-libjpeg && emmake make VERBOSE=1 -j 16 jpeg-static) || {
+ echo "ERROR: libjpeg-turbo 3.x build failed — aborting" >&2
+ exit 1
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| echo "~~~ MAKE libjpeg-turbo ~~~" | |
| (cd build-libjpeg && emmake make VERBOSE=1 -j 16 jpeg-static) | |
| echo "~~~ MAKE libjpeg-turbo ~~~" | |
| (cd build-libjpeg && emmake make VERBOSE=1 -j 16 jpeg-static) || { | |
| echo "ERROR: libjpeg-turbo 3.x build failed — aborting" >&2 | |
| exit 1 | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/libjpeg-turbo-12bit/build.sh` around lines 18 - 19, The
libjpeg-turbo build step in build.sh is not checked for failure, so a failed
emmake make can be masked and later surface as a misleading imported library
error. Add explicit error handling immediately after the build stage in the
script that runs the build-libjpeg/emmake make command so the script exits with
a clear libjpeg-turbo build failed message before continuing to the wrapper
CMake flow.
| // 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); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the target file first.
ast-grep outline packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp --view expanded || true
# Read the relevant slice with line numbers.
sed -n '1,240p' packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp | cat -n
# Search for libjpeg error handling patterns in this package.
rg -n "jpeg_std_error|error_exit|setjmp|longjmp|jpeg_destroy_decompress|jpeg12_read_scanlines|jpeg_read_header|jpeg_start_decompress" packages/libjpeg-turbo-12bit -SRepository: cornerstonejs/codecs
Length of output: 9797
🌐 Web query:
libjpeg default error_exit exit process jpeg_std_error documentation
💡 Result:
In libjpeg, the default error_exit method provided by jpeg_std_error is designed to terminate the application upon encountering a fatal error [1][2][3]. When a fatal error occurs, the default implementation of error_exit performs the following steps: 1. Calls the output_message method to display the error message to stderr [2][3]. 2. Calls jpeg_destroy to clean up the JPEG object [2][3]. 3. Terminates the process by calling exit(EXIT_FAILURE) [2][3]. Because the library assumes that the error_exit method will not return to the caller, it effectively forces a process exit [4]. To prevent the application from terminating, you must override the error_exit method in the jpeg_error_mgr structure [2][5]. The standard approach for overriding this behavior is to use setjmp and longjmp to return control to a safe point in your application code [2][5]. After performing a longjmp, you are responsible for cleaning up the JPEG object using jpeg_destroy or jpeg_abort to return it to a known state [1][2][3].
Citations:
- 1: https://raw.githubusercontent.com/libjpeg-turbo/libjpeg-turbo/main/doc/libjpeg.txt
- 2: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/c23e36e80009728b8c0f5e36ba6cb07885334d3b/jerror.c
- 3: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/b1682fb7c8441a2fa9090b54f97b28a8a7c6d9cf/jerror.c
- 4: Possibility of non-unwinding error handling libjpeg-turbo/libjpeg-turbo#311
- 5: https://stackoverflow.com/questions/20383815/libjpeg-decode-crashes-when-jpeg-data-is-bad
🌐 Web query:
libjpeg-turbo error_exit default behavior jpeg_std_error setjmp longjmp
💡 Result:
In libjpeg-turbo, the default behavior of the error_exit method, which is configured via jpeg_std_error, is to print the error message to stderr and terminate the application by calling exit(EXIT_FAILURE) [1][2][3]. Because this default behavior calls exit, it does not naturally support returning control to the caller when a fatal error occurs [4][5]. To change this, applications must override the default error_exit method [4][2][3]. The standard mechanism for overriding this behavior—and recovering from fatal errors without terminating the process—is to use the C setjmp and longjmp facilities [4][5]. The general implementation steps are as follows: 1. Create a custom error manager structure that extends the standard jpeg_error_mgr struct to include a jmp_buf field [4][5]. 2. Initialize this structure and point cinfo->err to it [1][4][6]. 3. Replace the default error_exit method with a custom function that performs a longjmp back to a location specified by a setjmp call in the main application logic [4][5]. 4. Before calling any libjpeg-turbo functions that might trigger a fatal error, perform a setjmp call to establish the recovery point [4][5]. When the custom error_exit method is triggered, it should perform any necessary cleanup, such as calling jpeg_destroy or jpeg_abort to return the JPEG object to a known state, before executing longjmp [1][2][3][6]. Attempting to use a JPEG object after a fatal error without first resetting its state with jpeg_destroy or jpeg_abort is unsafe [1][6][7].
Citations:
- 1: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/3.0.90/doc/libjpeg.txt
- 2: https://android.googlesource.com/platform/external/libjpeg-turbo/+/f5c8ca7bb600d1e5488f65c6d5447b19b18d899c/jerror.c
- 3: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/c23e36e80009728b8c0f5e36ba6cb07885334d3b/jerror.c
- 4: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/870b4f28b41609221cebad08d50298a5d447d307/example.c
- 5: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/989630f70cf1af69ebfefca8910d1647bf189712/example.c
- 6: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/main/doc/libjpeg.txt
- 7: https://raw.githubusercontent.com/libjpeg-turbo/libjpeg-turbo/main/doc/libjpeg.txt
Install a custom libjpeg error handler
jpeg_std_error(&jerr) leaves the default error_exit in place, so any fatal decode error can terminate the whole WASM instance instead of throwing back to JS. Add a jpeg_error_mgr override with setjmp/longjmp before jpeg_create_decompress(), then convert libjpeg’s message into a C++ exception.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp` around lines 123 - 145, The
JPEGDecoder path still uses the default libjpeg error handling, so fatal decode
failures can abort the WASM instance instead of surfacing as exceptions. In
JPEGDecoder.hpp, add a custom jpeg_error_mgr for the decode flow around
jpeg_std_error and jpeg_create_decompress, wire in setjmp/longjmp before any
libjpeg calls that can fail, and convert libjpeg’s reported message into a C++
exception in the same decode path that already performs the component and
precision checks.
| 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<uint8_t> output_buffer(output_size); | ||
| frameInfo_.bitsPerSample = 12; | ||
| frameInfo_.componentCount = 1; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
frameInfo_.isSigned is left uninitialized.
The metadata block sets width, height, bitsPerSample, and componentCount, but not isSigned, and the constructor (Lines 38-40) doesn't initialize frameInfo_. Downstream consumers read this field (e.g. test/browser/index.html displays frameInfo.isSigned), so it currently reports an indeterminate value. Set it explicitly for the 12-bit grayscale path.
🩹 Set isSigned explicitly
frameInfo_.bitsPerSample = 12;
frameInfo_.componentCount = 1;
+ frameInfo_.isSigned = false;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 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<uint8_t> output_buffer(output_size); | |
| frameInfo_.bitsPerSample = 12; | |
| frameInfo_.componentCount = 1; | |
| frameInfo_.width = cinfo.output_width; | |
| frameInfo_.height = cinfo.output_height; | |
| frameInfo_.bitsPerSample = 12; | |
| frameInfo_.componentCount = 1; | |
| frameInfo_.isSigned = false; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp` around lines 147 - 150, The
12-bit grayscale metadata setup in JPEGDecoder should also initialize
frameInfo_.isSigned, since JPEGDecoder’s constructor does not set it and
downstream consumers read this field. Update the frameInfo_ population block
where width, height, bitsPerSample, and componentCount are assigned so isSigned
is explicitly set for this path, keeping the FrameInfo state fully defined.
| decoded_.resize(output_size); | ||
| const size_t stride = static_cast<size_t>(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<int16_t>, 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<J12SAMPROW>(&decoded_[stride * cinfo.output_scanline]); | ||
| (void)jpeg12_read_scanlines(&cinfo, &output_data, 1); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the relevant files first
git ls-files 'packages/libjpeg-turbo-12bit/src/*' 'test/node/*' 'test/browser/*' | sed 's#^`#FILE` #'
echo
echo "=== Outline: JPEGDecoder.hpp ==="
ast-grep outline packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp --view expanded || true
echo
echo "=== Relevant getDecodedBuffer references ==="
rg -n "getDecodedBuffer|decodedBuffer|typed_memory_view|Uint8ClampedArray|Int16Array|Uint16Array" \
packages/libjpeg-turbo-12bit/src test/node test/browser -S
echo
echo "=== Read JPEGDecoder.hpp around getDecodedBuffer and decode path ==="
sed -n '1,260p' packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp
echo
echo "=== Read JS consumer files ==="
sed -n '1,220p' test/node/index.js
echo '---'
sed -n '1,220p' test/browser/index.html
echo
echo "=== Small semantic probe: Uint8ClampedArray from Int16Array in JS ==="
node - <<'JS'
const src = Int16Array.from([0, 1, 255, 256, 4095, -1, 30000]);
const u8 = new Uint8ClampedArray(src);
console.log('src:', Array.from(src));
console.log('u8 :', Array.from(u8));
JSRepository: cornerstonejs/codecs
Length of output: 1962
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== JPEGDecoder.hpp around getDecodedBuffer ==="
sed -n '1,120p' packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp
echo
echo "=== JPEGEncoder.hpp around getDecodedBuffer ==="
sed -n '1,110p' packages/libjpeg-turbo-12bit/src/JPEGEncoder.hpp
echo
echo "=== JS/HTML consumers in repo ==="
rg -n "getDecodedBuffer|decodedBuffer|decode\\(|Uint8ClampedArray|Int16Array|Uint16Array" . \
-g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' -S
echo
echo "=== File list near tests/examples ==="
git ls-files | rg '(^|/)(test|tests|example|examples|demo|demos|browser|node)/'
echo
echo "=== Small runtime probe: copy/clamp behavior for Uint8ClampedArray ==="
node - <<'JS'
const src = new Int16Array([0, 1, 255, 256, 4095, -1]);
const dst = new Uint8ClampedArray(src);
console.log(JSON.stringify({src: Array.from(src), dst: Array.from(dst)}));
JSRepository: cornerstonejs/codecs
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== JPEGDecoder.hpp getDecodedBuffer and decode tail ==="
sed -n '1,220p' packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp
echo
echo "=== libjpegTurbo12bit JS adapter ==="
sed -n '1,220p' packages/dicom-codec/src/codecs/libjpegTurbo12bit.js
echo
echo "=== libjpeg-turbo-12bit browser test around decoded buffer usage ==="
sed -n '130,390p' packages/libjpeg-turbo-12bit/test/browser/index.html
echo
echo "=== libjpeg-turbo-12bit node test around decoded buffer usage ==="
sed -n '1,170p' packages/libjpeg-turbo-12bit/test/node/index.js
echo
echo "=== libjpeg-turbo-12bit JS library bindings ==="
sed -n '1,120p' packages/libjpeg-turbo-12bit/src/jslib.cppRepository: cornerstonejs/codecs
Length of output: 24605
Expose 12-bit samples with a 16-bit typed array
getDecodedBuffer() is still wrapping decoded_ in Uint8ClampedArray, so the int16_t samples get copied and clamped to 0–255 before crossing into JS. That breaks the 12-bit contract: the browser/node consumers reinterpret the result as 16-bit pixel data, but this buffer now has byte-sized elements and loses every value above 255. Return an Int16Array or Uint16Array view instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp` around lines 170 - 179,
`getDecodedBuffer()` is still exposing the decoded 12-bit samples as byte-sized
data, which truncates values above 255. Update the JS-facing buffer wrapping to
use a 16-bit typed view (`Int16Array` or `Uint16Array`) over `decoded_` so the
`J12SAMPROW`/`J12SAMPLE` data from `JPEGDecoder` is preserved end-to-end. Keep
the change aligned with the `decoded_` storage type and the decoding path in
`jpeg12_read_scanlines`.
| set +e | ||
| mkdir -p build | ||
| mkdir -p dist | ||
| rm -rf build build-libjpeg | ||
| mkdir -p build build-libjpeg dist |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Add error checking between build stages to avoid confusing CI failures.
set +e disables exit-on-error for the entire script. If stage 1 (libjpeg-turbo configure or make) fails, the script continues to stage 2, which will then fail with a confusing "missing libturbojpeg.a" error instead of the actual root cause. Since the PR notes this is being iterated in CI without local builds, this will waste CI cycles and make debugging harder.
Consider either switching to set -e (and selectively allowing expected failures) or adding explicit exit-code checks after each stage:
🛡️ Proposed fix: add stage-level error checking
set +e
rm -rf build build-libjpeg
mkdir -p build build-libjpeg dist
# ... stage 1 configure ...
(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)
+STAGE1_CFG=$?
+
(cd build-libjpeg && emmake make VERBOSE=1 -j 16 turbojpeg-static jpeg-static)
+STAGE1_MAKE=$?
+
+if [ "$STAGE1_CFG" -ne 0 ] || [ "$STAGE1_MAKE" -ne 0 ]; then
+ echo "ERROR: libjpeg-turbo standalone build failed (cfg=$STAGE1_CFG, make=$STAGE1_MAKE)"
+ exit 1
+fi🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/libjpeg-turbo-8bit/build.sh` around lines 3 - 5, The build script
currently disables exit-on-error with set +e, which lets later stages continue
after an earlier failure and hides the real root cause. Update build.sh so each
stage (especially the libjpeg-turbo configure/make work and the later packaging
step) either runs under set -e or explicitly checks command exit codes before
proceeding, using the existing build stage flow to stop immediately on failure
and preserve the original error.
Status: builds + tests + CodSpeed green; dist-size red pending a size decisionlibjpeg-turbo 3.2.0 (from 2.1.4-era) — the major-version jump, for both the 8-bit and 12-bit packages. What it took (a real rewrite, not just a bump): libjpeg-turbo 3.x forbids
Size (
Fork PR: cornerstonejs/libjpeg-turbo#1 — MERGEABLE (clean advance to 3.2.0, no custom patches). (The 2.1.5.1 fork PR #2 was closed — marginal bump not worth it.) Actions to merge (needs decisions)
|
Fourth and final codec submodule upgrade (after openjph #76, charls #77, openjpeg #78).
Bumps the shared
extern/libjpeg-turbosubmodule (bothlibjpeg-turbo-8bitandlibjpeg-turbo-12bit) fromdc4a93f(2.1.4-era, Dec 2022) to upstream 3.2.0 (2026-06-30). Fork PR: cornerstonejs/libjpeg-turbo#1.WITH_12BIT=1flag). I'll iterate CI to green.libjpeg-turbo-12bit/build.shforcesCMAKE_BUILD_TYPE=Debug(same bug openjph had) — will address once it builds.Not built locally; iterating on CI. Not for merge.
Summary by CodeRabbit
New Features
Bug Fixes