Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions packages/libjpeg-turbo-12bit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
27 changes: 19 additions & 8 deletions packages/libjpeg-turbo-12bit/build.sh
Original file line number Diff line number Diff line change
@@ -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)
Comment on lines +13 to +19

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 cmake

Repository: 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-turbo

Repository: 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-12bit

Repository: 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:


🌐 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:


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.

Comment on lines +18 to +19

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.

Suggested change
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.


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
Expand Down
2 changes: 1 addition & 1 deletion packages/libjpeg-turbo-12bit/extern/libjpeg-turbo
Submodule libjpeg-turbo updated 756 files
12 changes: 11 additions & 1 deletion packages/libjpeg-turbo-12bit/src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
77 changes: 51 additions & 26 deletions packages/libjpeg-turbo-12bit/src/JPEGDecoder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

#pragma once

#include <cstdint>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
// #include "config.h"
#include "jpeglib.h"
Expand Down Expand Up @@ -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);
Comment on lines +123 to +145

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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 -S

Repository: 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:


🌐 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:


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;
Comment on lines 147 to +150

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.


// 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<uint64_t>(cinfo.output_width);
const uint64_t height64 = static_cast<uint64_t>(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<size_t>(output_size64);

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);
Comment on lines 170 to +179

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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));
JS

Repository: 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)}));
JS

Repository: 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.cpp

Repository: 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`.

}
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);
}

Expand Down
12 changes: 7 additions & 5 deletions packages/libjpeg-turbo-8bit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
29 changes: 19 additions & 10 deletions packages/libjpeg-turbo-8bit/build.sh
Original file line number Diff line number Diff line change
@@ -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
Comment on lines 3 to +5

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.


# 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
Expand Down
2 changes: 1 addition & 1 deletion packages/libjpeg-turbo-8bit/extern/libjpeg-turbo
Submodule libjpeg-turbo updated 756 files
13 changes: 12 additions & 1 deletion packages/libjpeg-turbo-8bit/src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
Loading