├── .devcontainer
├── Dockerfile
└── devcontainer.json
├── .gitignore
├── .vscode
├── launch.json
└── settings.json
├── CMakeLists.txt
├── CMakeSettings.json
├── LICENSE
├── README.md
├── build-emscripten.sh
├── build.sh
├── extern
└── kakadu
│ ├── CMakeLists.txt
│ └── ensure_config.cpp
├── src
├── CMakeLists.txt
├── FrameInfo.hpp
├── HTJ2KDecoder.hpp
├── HTJ2KEncoder.hpp
├── Point.hpp
├── Size.hpp
└── jslib.cpp
└── test
├── browser
└── index.html
├── cpp
├── CMakeLists.txt
└── main.cpp
├── fixtures
├── CT1.ll.j2c
├── j2c
│ ├── CT1.j2c
│ ├── CT2.j2c
│ ├── MG1.j2c
│ ├── MR1.j2c
│ ├── MR2.j2c
│ ├── MR3.j2c
│ ├── MR4.j2c
│ ├── NM1.j2c
│ ├── RG1.j2c
│ ├── RG2.j2c
│ ├── RG3.j2c
│ ├── SC1.j2c
│ └── XA1.j2c
├── j2k
│ └── US1.j2k
└── raw
│ ├── CT1.RAW
│ ├── CT2.RAW
│ ├── MG1.RAW
│ ├── MR1.RAW
│ ├── MR2.RAW
│ ├── MR3.RAW
│ ├── MR4.RAW
│ ├── NM1.RAW
│ ├── RG1.RAW
│ ├── RG2.RAW
│ ├── RG3.RAW
│ ├── SC1.RAW
│ ├── US1.RAW
│ ├── VL1.RAW
│ ├── VL2.RAW
│ ├── VL3.RAW
│ ├── VL4.RAW
│ ├── VL5.RAW
│ ├── VL6.RAW
│ └── XA1.RAW
└── node
├── index.js
└── package.json
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | #-------------------------------------------------------------------------------------------------------------
2 | # Copyright (c) Microsoft Corporation. All rights reserved.
3 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
4 | #-------------------------------------------------------------------------------------------------------------
5 |
6 | #FROM mcr.microsoft.com/vscode/devcontainers/base:0-debian-9
7 | FROM emscripten/emsdk
8 |
9 | # Avoid warnings by switching to noninteractive
10 | ENV DEBIAN_FRONTEND=noninteractive
11 |
12 | # This Dockerfile's base image has a non-root user with sudo access. Use the "remoteUser"
13 | # property in devcontainer.json to use it. On Linux, the container user's GID/UIDs
14 | # will be updated to match your local UID/GID (when using the dockerFile property).
15 | # See https://aka.ms/vscode-remote/containers/non-root-user for details.
16 | ARG USERNAME=vscode
17 | ARG USER_UID=1000
18 | ARG USER_GID=$USER_UID
19 |
20 | # Configure apt and install packages
21 | RUN apt-get update \
22 | #
23 | # Install C++ tools
24 | #&& apt-get -y install build-essential cmake cppcheck valgrind \
25 | && apt-get -y install build-essential cppcheck valgrind \
26 | # version 3.17 of cmake
27 | && wget -qO- "https://cmake.org/files/v3.17/cmake-3.17.0-Linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C /usr/local\
28 | #
29 | # [Optional] Update UID/GID if needed
30 | && if [ "$USER_GID" != "1000" ] || [ "$USER_UID" != "1000" ]; then \
31 | groupmod --gid $USER_GID $USERNAME \
32 | && usermod --uid $USER_UID --gid $USER_GID $USERNAME \
33 | && chown -R $USER_UID:$USER_GID /home/$USERNAME; \
34 | fi \
35 | #
36 | # Clean up
37 | && apt-get autoremove -y \
38 | && apt-get clean -y \
39 | && rm -rf /var/lib/apt/lists/*
40 |
41 | # Switch back to dialog for any ad-hoc use of apt-get
42 | ENV DEBIAN_FRONTEND=dialog
43 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.224.2/containers/javascript-node
3 | {
4 | "name": "Node.js",
5 | "build": {
6 | "dockerfile": "Dockerfile",
7 | // Update 'VARIANT' to pick a Node version: 16, 14, 12.
8 | // Append -bullseye or -buster to pin to an OS version.
9 | // Use -bullseye variants on local arm64/Apple Silicon.
10 | "args": {
11 | "VARIANT": "16-bullseye"
12 | }
13 | },
14 | // Set *default* container specific settings.json values on container create.
15 | "settings": {},
16 | // Add the IDs of extensions you want installed when the container is created.
17 | "extensions": [
18 | "ms-vscode.cpptools",
19 | "twxs.cmake",
20 | "flixs.vs-code-http-server-and-html-preview",
21 | "dbaeumer.vscode-eslint"
22 | ],
23 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
24 | // "forwardPorts": [],
25 | // Use 'postCreateCommand' to run commands after the container is created.
26 | // "postCreateCommand": "yarn install",
27 | // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
28 | "remoteUser": "emscripten"
29 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build-native
2 | build-wasm
3 | dist
4 | test/fixtures/j2c/ignore.j2c
5 | .DS_Store
6 | extern/v*
7 | .vs
8 | out
9 | build
10 | build-gcc
11 | build-clang
12 | build-emscripten
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "(lldb) Launch",
9 | "type": "cppdbg",
10 | "request": "launch",
11 | "program": "${workspaceFolder}/build-native/test/cpp/cpptest",
12 | "args": [],
13 | "stopAtEntry": false,
14 | "cwd": "${workspaceFolder}/",
15 | "environment": [],
16 | "externalConsole": false,
17 | "MIMode": "lldb"
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cmake.configureOnOpen": false,
3 | "files.associations": {
4 | "limits": "cpp",
5 | "type_traits": "cpp",
6 | "filesystem": "cpp",
7 | "functional": "cpp",
8 | "array": "cpp",
9 | "*.tcc": "cpp",
10 | "memory": "cpp",
11 | "istream": "cpp",
12 | "tuple": "cpp",
13 | "utility": "cpp",
14 | "iterator": "cpp",
15 | "string": "cpp",
16 | "string_view": "cpp",
17 | "vector": "cpp",
18 | "__bit_reference": "cpp",
19 | "__bits": "cpp",
20 | "__config": "cpp",
21 | "__debug": "cpp",
22 | "__errc": "cpp",
23 | "__functional_base": "cpp",
24 | "__hash_table": "cpp",
25 | "__locale": "cpp",
26 | "__mutex_base": "cpp",
27 | "__node_handle": "cpp",
28 | "__nullptr": "cpp",
29 | "__split_buffer": "cpp",
30 | "__string": "cpp",
31 | "__threading_support": "cpp",
32 | "__tuple": "cpp",
33 | "algorithm": "cpp",
34 | "atomic": "cpp",
35 | "bit": "cpp",
36 | "bitset": "cpp",
37 | "cctype": "cpp",
38 | "chrono": "cpp",
39 | "clocale": "cpp",
40 | "cmath": "cpp",
41 | "complex": "cpp",
42 | "cstdarg": "cpp",
43 | "cstddef": "cpp",
44 | "cstdint": "cpp",
45 | "cstdio": "cpp",
46 | "cstdlib": "cpp",
47 | "cstring": "cpp",
48 | "ctime": "cpp",
49 | "cwchar": "cpp",
50 | "cwctype": "cpp",
51 | "deque": "cpp",
52 | "exception": "cpp",
53 | "fstream": "cpp",
54 | "initializer_list": "cpp",
55 | "iomanip": "cpp",
56 | "ios": "cpp",
57 | "iosfwd": "cpp",
58 | "iostream": "cpp",
59 | "list": "cpp",
60 | "locale": "cpp",
61 | "mutex": "cpp",
62 | "new": "cpp",
63 | "optional": "cpp",
64 | "ostream": "cpp",
65 | "ratio": "cpp",
66 | "sstream": "cpp",
67 | "stack": "cpp",
68 | "stdexcept": "cpp",
69 | "streambuf": "cpp",
70 | "system_error": "cpp",
71 | "thread": "cpp",
72 | "typeinfo": "cpp",
73 | "unordered_map": "cpp"
74 | },
75 | "editor.tokenColorCustomizations": {
76 | "textMateRules": [
77 | {
78 | "scope": "googletest.failed",
79 | "settings": {
80 | "foreground": "#f00"
81 | }
82 | },
83 | {
84 | "scope": "googletest.passed",
85 | "settings": {
86 | "foreground": "#0f0"
87 | }
88 | },
89 | {
90 | "scope": "googletest.run",
91 | "settings": {
92 | "foreground": "#0f0"
93 | }
94 | }
95 | ]
96 | }
97 | }
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.20)
2 |
3 | project (kakadujs LANGUAGES CXX)
4 |
5 | # search for the kakadu source code in extern
6 | find_path(KAKADU_ROOT NAMES Enabling_HT.txt HINTS ${CMAKE_SOURCE_DIR}/extern/* REQUIRED NO_CACHE)
7 |
8 | # set the build type if not specified
9 | set(default_build_type "Release")
10 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
11 | message(STATUS "Setting build type to '${default_build_type}' as none was specified.")
12 | set(CMAKE_BUILD_TYPE "${default_build_type}")
13 | endif()
14 |
15 | # force KAKADU_THREADING off as we haven't tested it yet.
16 | SET(KAKADU_THREADING OFF CACHE BOOL "Kakadu Threading has not been tested yet" FORCE) # TODO: Test with threading enabled
17 |
18 | # add the kakadu library from extern
19 | add_subdirectory(extern/kakadu EXCLUDE_FROM_ALL)
20 |
21 | # add the library code
22 | add_subdirectory(src)
23 |
24 | # c++ native test case
25 | if(NOT EMSCRIPTEN)
26 | add_subdirectory(test/cpp)
27 | endif()
--------------------------------------------------------------------------------
/CMakeSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "name": "x64-Release",
5 | "generator": "Ninja",
6 | "configurationType": "RelWithDebInfo",
7 | "buildRoot": "${projectDir}\\out\\build\\${name}",
8 | "installRoot": "${projectDir}\\out\\install\\${name}",
9 | "cmakeCommandArgs": "",
10 | "buildCommandArgs": "",
11 | "ctestCommandArgs": "",
12 | "inheritEnvironments": [ "msvc_x64_x64" ]
13 | }
14 | ]
15 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Chris Hafey
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # kakadujs
2 |
3 | Easy to use wrapper for the Kakadu JPEG2000 library
4 |
5 | Includes builds for WASM and native C/C++ on Mac/Linux/Windows
6 |
7 | ## Status
8 |
9 | Experimental - use at your own risk
10 |
11 | ## Building
12 |
13 | This code has been developed/tested against v8_4_1 of Kakadu for Mac (x64/ARM), Linux (x64/ARM) and
14 | Windows (x64). You must place a fresh version of the Kakadu library in the extern folder
15 | (e.g. extern/v_8_4_1-02044N). CMake will find your installation of Kakadu for you.
16 |
17 | NOTE - The CMake files are setup to automatically build with the processor SIMD optimizations
18 | without making any changes to the default Kakadu source distribution. You do should NOT replace
19 | srclib_ht with altlib_ht_opt or set FC_BLOCK_ENABLED as described in the Kakadu/Enabling_HT.txt file.
20 |
21 | ### Prerequisites
22 |
23 | #### Linux/Mac OS X
24 |
25 | - CMake (v3.20)
26 | - C++ Compiler Toolchain (e.g. Ubuntu build-essentials, XCode command line tools v14.0.0)
27 | - Emscripten (v3.1.25)
28 |
29 | #### Windows
30 |
31 | - Visual Studio 2022 (Community Edition with Desktop development with C++)
32 |
33 | ### Building the native C++ version with Linux/Mac OS X
34 |
35 | ```
36 | $ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
37 | $ cmake --build build
38 | $ build-clang/test/cpp/cpptest
39 | ```
40 |
41 | The test app in $BUILD_DIR/test/cpp/main.cpp will generate benchmarks for decoding and encoding.
42 |
43 | - TPF = Time Per Frame/Image.
44 | - MP/s = Mega pixels / second
45 | - FPS = Frames per second decoding
46 |
47 | Numbers from an Apple M1 MacBook Pro running macOS Monterey 12.6.1
48 |
49 | ```
50 | build-clang/test/cpp/cpptest
51 | NATIVE decode test/fixtures/j2c/CT1.j2c TotalTime: 0.014 s for 20 iterations; TPF=0.692 ms (361.12 MP/s, 1444.46 FPS)
52 | NATIVE decode test/fixtures/j2c/MG1.j2c TotalTime: 0.744 s for 20 iterations; TPF=37.206 ms (374.94 MP/s, 26.88 FPS)
53 | NATIVE encode test/fixtures/raw/CT1.RAW TotalTime: 0.037 s for 20 iterations; TPF=1.846 ms (135.44 MP/s, 541.74 FPS)
54 | ```
55 |
56 | Numbers from Intel 13900k on Linux
57 |
58 | ```
59 | >
60 | build-gcc/test/cpp/cpptest
61 | NATIVE decode test/fixtures/j2c/CT1.j2c TotalTime: 0.009 s for 20 iterations; TPF=0.448 ms (558.05 MP/s, 2232.20 FPS)
62 | NATIVE decode test/fixtures/j2c/MG1.j2c TotalTime: 0.564 s for 20 iterations; TPF=28.177 ms (495.09 MP/s, 35.49 FPS)
63 | NATIVE encode test/fixtures/raw/CT1.RAW TotalTime: 0.009 s for 20 iterations; TPF=0.438 ms (570.52 MP/s, 2282.07 FPS)
64 | ```
65 |
66 | ### Building the native C++ version with Windows/Visual Studio 2022
67 |
68 | Build the x64-release version. Run cpp test from the project root directory.
69 |
70 | - TPF = Time Per Frame/Image.
71 | - MP/s = Mega pixels / second
72 | - FPS = Frames per second decoding
73 |
74 | Numbers from an Intel 13900K running Windows 11 Pro
75 |
76 | ```
77 | C:\Users\chafe\source\repos\kakadujs>out\build\x64-Release\test\cpp\cpptest.exe
78 | NATIVE decode test/fixtures/j2c/CT1.j2c TotalTime: 0.017 s for 20 iterations; TPF=0.850 ms (294.11 MP/s, 1176.43 FPS)
79 | NATIVE decode test/fixtures/j2c/MG1.j2c TotalTime: 0.568 s for 20 iterations; TPF=28.400 ms (491.19 MP/s, 35.21 FPS)
80 | NATIVE encode test/fixtures/raw/CT1.RAW TotalTime: 0.014 s for 20 iterations; TPF=0.700 ms (357.15 MP/s, 1428.61 FPS)
81 | ```
82 |
83 | ### Building WASM version
84 |
85 | Install EMSCRIPTEN or launch the docker container using Visual Studio Code Remote Containers. From the terminal:
86 |
87 | ```
88 | $ emcmake cmake -S . -B build-emscripten -DCMAKE_BUILD_TYPE=Release -DCMAKE_FIND_ROOT_PATH=/
89 | $ (cd build-emscripten; emmake make -j)
90 | $ build-emscripten/test/cpp/cpptest
91 | ```
92 |
93 | ```
94 | $ build-emscripten/test/cpp/cpptest
95 | WASM decode ../fixtures/j2c/CT1.j2c TotalTime: 0.100 s for 20 iterations; TPF=5.001 ms (49.99 MP/s, 199.95 FPS)
96 | WASM decode ../fixtures/j2c/MG1.j2c TotalTime: 4.090 s for 20 iterations; TPF=204.477 ms (68.22 MP/s, 4.89 FPS)
97 | WASM encode ../fixtures/raw/CT1.RAW TotalTime: 0.074 s for 20 iterations; TPF=3.710 ms (67.38 MP/s, 269.52 FPS)
98 | ```
99 |
--------------------------------------------------------------------------------
/build-emscripten.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | rm -rf build-wasm
4 | mkdir -p build-wasm
5 | #-DCMAKE_FIND_ROOT_PATH=/ is a workaround for find_path when run via EMSCRIPTEN emcmake
6 | (cd build-wasm && emcmake cmake .. -DCMAKE_FIND_ROOT_PATH=/)
7 | if [ $retVal -ne 0 ]; then
8 | echo "CMAKE FAILED"
9 | exit 1
10 | fi
11 |
12 | (cd build-wasm && emmake make VERBOSE=1 -j)
13 | retVal=$?
14 | if [ $retVal -ne 0 ]; then
15 | echo "MAKE FAILED"
16 | exit 1
17 | fi
18 | mkdir -p ./dist
19 | cp ./build-wasm/src/kakadujs.js ./dist
20 | cp ./build-wasm/src/kakadujs.wasm ./dist
21 | (cd test/node; npm run test)
22 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # this script does a clean build and is useful for development when modifying cmakefiles
4 | clear
5 | rm -rf build
6 |
7 | export CMAKE_BUILD_TYPE=Release # | Debug
8 | #export VERBOSE=1
9 |
10 | (cmake -S . -B build -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE) || (exit 1;)
11 | (cmake --build build -- -j) || (exit 1;)
12 | build/test/cpp/cpptest
13 |
--------------------------------------------------------------------------------
/extern/kakadu/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Enable SIMD by default
2 | OPTION(KAKADU_SIMD_ACCELERATION "Enable Kakadu's heavily optimized implementation of HTJ2K" ON)
3 |
4 | # NOTE - Has not been tested yet
5 | option(KAKADU_THREADING "Build Kakadu with threading" OFF)
6 |
7 | # disable threads if not enabled
8 | if(NOT KAKADU_THREADING)
9 | add_compile_definitions(KDU_NO_THREADS)
10 | endif()
11 |
12 | # configure for compiler options and link directories based on SIMD Acceleration enabled or not
13 | if(KAKADU_SIMD_ACCELERATION)
14 | if(EXISTS "${KAKADU_ROOT}/altlib_ht_opt/${KAKADU_PLATFORM}")
15 | message("KAKADU SIMD Acceleration ENABLED using altlib_ht_opt")
16 | set(KAKDU_HT_LIB "altlib_ht_opt") # this has the CPU optimized libraries (see Enabling_HT.txt)
17 | else()
18 | message("KAKADU SIMD Acceleration ENABLED using srclib_ht")
19 | set(KAKDU_HT_LIB "srclib_ht") # this has the CPU optimized libraries (see Enabling_HT.txt)
20 | endif()
21 | else()
22 | message("KAKADU SIMD Acceleration DISABLED")
23 | add_compile_definitions(FBC_NO_ACCELERATION) # disable kakadu HT acceleration (see Enabling_HT.txt)
24 | set(KAKDU_HT_LIB "srclib_ht") # this has the CPU optimized libraries (see Enabling_HT.txt)
25 | endif()
26 |
27 | # do platform specific stuff
28 | if(EMSCRIPTEN)
29 | SET(BUILD_SHARED_LIBS OFF CACHE BOOL "Shared libraries forced off for EMSCRIPTEN" FORCE) # EMSCRIPTEN does not support shared libraries
30 | SET(KAKADU_THREADING OFF CACHE BOOL "Kakadu threading forced off for EMSCRIPTEN" FORCE) # EMSCRIPTEN threading has not been tested with kakadu yet
31 | SET(KAKADU_SIMD_ACCELERATION OFF CACHE BOOL "Kakadu SIMD acceleration forced off for EMSCRIPTEN" FORCE) # Kakadu does not support WASM-SIMD yet
32 |
33 | add_compile_options(-msimd128) # enabled LLVM autovectoring for WASM SIMD
34 | elseif(UNIX AND(NOT EMSCRIPTEN))
35 | if(BUILD_SHARED_LIBS)
36 | add_compile_options(-fPIC) # enable position independent code for shared libraries
37 | endif()
38 |
39 | # figure out what architecture we are running on
40 | EXECUTE_PROCESS(COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE ARCHITECTURE)
41 | message(STATUS "ARCHITECURE=${ARCHITECTURE}")
42 |
43 | if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") # Mac
44 | add_compile_definitions(KDU_MAC_SPEEDUPS) # enables cpu specific optimizations for mac (ARM | x86)
45 |
46 | if(ARCHITECTURE STREQUAL "arm64")
47 | set(KAKADU_PLATFORM "Mac-arm-64-gcc")
48 | else()
49 | add_compile_definitions(KDU_X86_INTRINSICS) # TODO - test removing this
50 | set(KAKADU_PLATFORM "Mac-x86-64-gcc")
51 | endif()
52 | else() # Non mac unix (linux)
53 | if(ARCHITECTURE STREQUAL "aarch64")
54 | add_compile_options(-march=armv8.1-a) # TODO - try removing this and see if it builds (this was taken from kakadu makefile)
55 | add_compile_options(-flax-vector-conversions) # TODO - try compiling without this (this was taken from kakadu makefile)
56 | set(KAKADU_PLATFORM "Linux-arm-64-gcc")
57 | else()
58 | set(KAKADU_PLATFORM "Linux-x86-64-gcc")
59 | endif()
60 | endif()
61 | elseif(WIN32)
62 | set(ARCHITECTURE "x86_64")
63 | set(KAKADU_PLATFORM "Win-x86-64")
64 | else()
65 | message(FATAL_ERROR "Platform not supported yet")
66 | endif()
67 |
68 | set(PUBLIC_HEADERS
69 | "${KAKADU_ROOT}/coresys/common"
70 | "${KAKADU_ROOT}/coresys/shared"
71 | )
72 |
73 | set(FBC_HEADERS
74 | "${KAKADU_ROOT}/coresys/fast_coding/"
75 | )
76 |
77 | set(PUBLIC_HEADERS_APP_SUPPORT
78 | "${KAKADU_ROOT}/apps/support"
79 | "${KAKADU_ROOT}/apps/compressed_io"
80 | )
81 |
82 | set(KAKADUJS_SOURCES
83 | "ensure_config.cpp" # no code, just verifies that the preprocessor definitions are correct since the defaults changed between v8_3 and v8_4_1
84 | )
85 |
86 | set(CODING_SOURCES
87 | "${KAKADU_ROOT}/coresys/coding/block_coding_common.cpp"
88 | "${KAKADU_ROOT}/coresys/coding/block_decoder.cpp"
89 | "${KAKADU_ROOT}/coresys/coding/block_encoder.cpp"
90 | "${KAKADU_ROOT}/coresys/coding/decoder.cpp"
91 | "${KAKADU_ROOT}/coresys/coding/encoder.cpp"
92 | "${KAKADU_ROOT}/coresys/coding/cplex_analysis.cpp"
93 | "${KAKADU_ROOT}/coresys/coding/mq_decoder.cpp"
94 | "${KAKADU_ROOT}/coresys/coding/mq_encoder.cpp"
95 | )
96 |
97 | set(COMPRESSED_SOURCES
98 | "${KAKADU_ROOT}/coresys/compressed/blocks.cpp"
99 | "${KAKADU_ROOT}/coresys/compressed/codestream.cpp"
100 | "${KAKADU_ROOT}/coresys/compressed/compressed.cpp"
101 | )
102 | set(SHARED_SOURCES
103 | "${KAKADU_ROOT}/coresys/shared/core_local.cpp"
104 | )
105 |
106 | set(KERNEL_SOURCES
107 | "${KAKADU_ROOT}/coresys/kernels/kernels.cpp"
108 | )
109 |
110 | set(MESSAGING_SOURCES
111 | "${KAKADU_ROOT}/coresys/messaging/messaging.cpp"
112 | )
113 |
114 | set(PARAMETERS_SOURCES
115 | "${KAKADU_ROOT}/coresys/parameters/params.cpp"
116 | )
117 |
118 | set(TRANSFORM_SOURCES
119 | "${KAKADU_ROOT}/coresys/transform/colour.cpp"
120 | "${KAKADU_ROOT}/coresys/transform/transform.cpp"
121 | "${KAKADU_ROOT}/coresys/transform/analysis.cpp"
122 | "${KAKADU_ROOT}/coresys/transform/synthesis.cpp"
123 | "${KAKADU_ROOT}/coresys/transform/fusion.cpp"
124 | "${KAKADU_ROOT}/coresys/transform/multi_transform.cpp"
125 | "${KAKADU_ROOT}/coresys/transform/analysis2.cpp"
126 | "${KAKADU_ROOT}/coresys/transform/synthesis2.cpp"
127 | "${KAKADU_ROOT}/coresys/transform/transform2.cpp"
128 | )
129 |
130 | set(ROI_SOURCES
131 | "${KAKADU_ROOT}/coresys/roi/roi.cpp"
132 | )
133 |
134 | set(COMMON_SOURCES
135 | "${KAKADU_ROOT}/coresys/common/kdu_arch.cpp"
136 | "${KAKADU_ROOT}/coresys/threads/kdu_threads.cpp"
137 | )
138 |
139 | set(FASTCODING_SOURCES
140 | "${KAKADU_ROOT}/coresys/fast_coding/fbc_common.cpp"
141 | "${KAKADU_ROOT}/coresys/fast_coding/fbc_encoder.cpp"
142 | "${KAKADU_ROOT}/coresys/fast_coding/fbc_decoder.cpp"
143 | "${KAKADU_ROOT}/coresys/fast_coding/fbc_encoder_tools.cpp"
144 | "${KAKADU_ROOT}/coresys/fast_coding/fbc_decoder_tools.cpp"
145 | )
146 |
147 | set(APP_SUPPORT_SOURCES
148 | "${KAKADU_ROOT}/apps/support/kdu_stripe_decompressor.cpp"
149 | "${KAKADU_ROOT}/apps/support/kdu_stripe_compressor.cpp"
150 | "${KAKADU_ROOT}/apps/support/supp_local.cpp"
151 | "${KAKADU_ROOT}/apps/jp2/jp2.cpp"
152 | "${KAKADU_ROOT}/apps/jp2/jpx.cpp"
153 | "${KAKADU_ROOT}/apps/client_server/kdu_client_window.cpp"
154 | )
155 |
156 | # include platform specific code
157 | if(WIN32)
158 | ENABLE_LANGUAGE(ASM_MASM)
159 | set(WIN32_SOURCES
160 | "${KAKADU_ROOT}/coresys/common/arch_masm64.asm"
161 | )
162 | endif()
163 |
164 | # include SIMD acelerated code
165 | if(KAKADU_SIMD_ACCELERATION)
166 | if(ARCHITECTURE STREQUAL "x86_64")
167 | add_compile_definitions(KDU_X86_INTRINSICS) # enable x86 SIMD optimizations
168 |
169 | set(SSSE3_SOURCES
170 | "${KAKADU_ROOT}/coresys/coding/ssse3_coder_local.cpp"
171 | "${KAKADU_ROOT}/coresys/transform/ssse3_colour_local.cpp"
172 | "${KAKADU_ROOT}/coresys/transform//ssse3_dwt_local.cpp"
173 | )
174 |
175 | set(SSE4_SOURCES
176 | "${KAKADU_ROOT}/coresys/transform/sse4_multi_transform_local.cpp"
177 | )
178 |
179 | set(AVX_SOURCES
180 | "${KAKADU_ROOT}/coresys/coding/avx_coder_local.cpp"
181 | "${KAKADU_ROOT}/coresys/transform/avx_colour_local.cpp"
182 | )
183 |
184 | set(AVX2_SOURCES
185 | "${KAKADU_ROOT}/coresys/coding/avx2_coder_local.cpp"
186 | "${KAKADU_ROOT}/coresys/transform/avx2_colour_local.cpp"
187 | "${KAKADU_ROOT}/coresys/transform/avx2_dwt_local.cpp"
188 | )
189 |
190 | set(AVX2_X64_SOURCES
191 | "${KAKADU_ROOT}/coresys/transform/avx2_analysis2.cpp"
192 | "${KAKADU_ROOT}/coresys/transform/avx2_synthesis2.cpp"
193 | )
194 |
195 | set(APP_SUPPORT_SOURCES_INTEL_SIMD
196 | "${KAKADU_ROOT}/apps/support/avx2_stripe_transfer.cpp"
197 | "${KAKADU_ROOT}/apps/support/ssse3_stripe_transfer.cpp"
198 | )
199 |
200 | if(UNIX)
201 | add_compile_options(-msse2)
202 | add_compile_options(-mssse3)
203 | add_compile_options(-msse4.1)
204 | add_compile_options(-mavx)
205 | add_compile_options(-mavx2)
206 | add_compile_options(-mfma)
207 | add_compile_options(-mbmi)
208 | add_compile_options(-mbmi2)
209 | add_compile_options(-mlzcnt)
210 | add_compile_options(-m64)
211 | endif()
212 |
213 | elseif(ARCHITECTURE STREQUAL "arm64")
214 | add_compile_definitions(KDU_NEON_INTRINSICS) # enable ARM NEON SIMD optimizations
215 |
216 | set(NEON_SOURCES
217 | "${KAKADU_ROOT}/coresys/coding/neon_coder_local.cpp"
218 | "${KAKADU_ROOT}/coresys/transform/neon_colour_local.cpp"
219 | "${KAKADU_ROOT}/coresys/transform/neon_dwt_local.cpp"
220 | "${KAKADU_ROOT}/coresys/transform/neon_multi_transform_local.cpp"
221 | "${KAKADU_ROOT}/coresys/transform/neon_synthesis2.cpp"
222 | "${KAKADU_ROOT}/coresys/transform/neon_analysis2.cpp"
223 | )
224 |
225 | set(NEON_APP_SUPPORT
226 | "${KAKADU_ROOT}/apps/support/neon_stripe_transfer.cpp"
227 | "${KAKADU_ROOT}/apps/support/neon_region_decompressor.cpp"
228 | "${KAKADU_ROOT}/apps/support/neon_region_compositor.cpp"
229 | "${KAKADU_ROOT}/apps/image/neon_sample_reorg.cpp"
230 | )
231 | endif() # arm64
232 | endif() # KAKADU_SIMD_ACCELERATION
233 |
234 | # include non SIMD acelerated code
235 | if(NOT KAKADU_SIMD_ACCELERATION)
236 | if(ARCHITECTURE STREQUAL "arm64")
237 | add_compile_definitions("KDU_NO_NEON") # explicitly disable ARM NEON SIMD code as it gets turned on by default
238 | endif()
239 | endif()
240 |
241 | # # Kakadu Library
242 | add_library(kakadu
243 | ${KAKADUJS_SOURCES}
244 | ${SHARED_SOURCES}
245 | ${CODING_SOURCES}
246 | ${COMPRESSED_SOURCES}
247 | ${KERNEL_SOURCES}
248 | ${MESSAGING_SOURCES}
249 | ${PARAMETERS_SOURCES}
250 | ${TRANSFORM_SOURCES}
251 | ${ROI_SOURCES}
252 | ${COMMON_SOURCES}
253 | ${FASTCODING_SOURCES}
254 | ${SSSE3_SOURCES}
255 | ${SSE4_SOURCES}
256 | ${AVX_SOURCES}
257 | ${AVX2_SOURCES}
258 | ${AVX2_X64_SOURCES}
259 | ${NEON_SOURCES}
260 | ${WIN32_SOURCES}
261 | )
262 |
263 | target_include_directories(kakadu PUBLIC ${PUBLIC_HEADERS} PRIVATE ${FBC_HEADERS})
264 |
265 | # include the platform specific kakadu ht library
266 | if(UNIX AND(NOT EMSCRIPTEN))
267 | target_link_libraries(kakadu PUBLIC
268 | ${KAKADU_ROOT}${KAKDU_HT_LIB}/${KAKADU_PLATFORM}/libkdu_ht.a m
269 | )
270 | elseif(WIN32)
271 | target_link_libraries(kakadu PUBLIC
272 | ${KAKADU_ROOT}${KAKDU_HT_LIB}/${KAKADU_PLATFORM}/kdu_ht2019R.lib
273 | )
274 | endif()
275 |
276 | # KakaduAppSupport library
277 | add_library(kakaduappsupport
278 | ${APP_SUPPORT_SOURCES}
279 | ${APP_SUPPORT_SOURCES_INTEL_SIMD}
280 | ${NEON_APP_SUPPORT}
281 | )
282 | target_include_directories(kakaduappsupport PUBLIC ${PUBLIC_HEADERS_APP_SUPPORT})
283 | target_link_libraries(kakaduappsupport PUBLIC kakadu)
284 |
285 | # turn off specific compiler warnings depending upon the compiler
286 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
287 | target_compile_options(kakadu PRIVATE -Wno-return-type -Wno-volatile -Wno-deprecated-declarations)
288 | target_compile_options(kakaduappsupport PRIVATE -Wno-return-type -Wno-volatile -Wno-deprecated-declarations)
289 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
290 | target_compile_options(kakadu PRIVATE -Wno-deprecated-volatile -Wno-return-type)
291 | target_compile_options(kakaduappsupport PRIVATE -Wno-deprecated-volatile -Wno-return-type)
292 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
293 | target_compile_options(kakadu PRIVATE "/wd4244" "/wd4715")
294 | target_compile_options(kakaduappsupport PRIVATE "/wd4244" "/wd4715")
295 | elseif(EMSCRIPTEN)
296 | target_compile_options(kakadu PRIVATE -Wno-deprecated-volatile -Wno-return-type -Wno-tautological-constant-out-of-range-compare -Wno-implicit-const-int-float-conversion)
297 | target_compile_options(kakaduappsupport PRIVATE -Wno-deprecated-volatile -Wno-return-type -Wno-tautological-constant-out-of-range-compare -Wno-implicit-const-int-float-conversion)
298 | endif()
299 |
--------------------------------------------------------------------------------
/extern/kakadu/ensure_config.cpp:
--------------------------------------------------------------------------------
1 | #include "fbc_common.h"
2 |
3 | // This file simply allows us to verify that the write preprocessor definitions are setup since the defaults changed between v8_3 and v8_4_1
4 |
5 | #if !defined(FBC_ENABLED)
6 | #error FBC_ENABLED is not defined - Kakadu requires this to support HTJ2K
7 | #endif
8 | #if defined(FBC_NO_ACCELERATION)
9 | #warning FBC_NO_ACCELERATION is defined - Kakadu will use non accelerated (slow) HTJ2K path
10 | #endif
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | if(EMSCRIPTEN)
2 | add_executable(kakadujs ${SOURCES} jslib.cpp)
3 |
4 | target_link_libraries(kakadujs PRIVATE kakadu kakaduappsupport)
5 |
6 | if(KAKADU_THREADING)
7 | message(FATAL_ERROR "EMSCRIPTEN builds have not been tested with threading enabled yet")
8 | endif()
9 |
10 | # Explicitly turn off Kakadu threading to prevent compile errors
11 | # due to kakadu not knowing about EMSCRIPTEN pthreads. EMSCRIPTEN
12 | # does support threads, so this might work but it requires extra
13 | # deployment work (enabling SharedArrayBuffers) and Kakadujs hasn't
14 | # been tested with it yet
15 | add_compile_definitions(KDU_NO_THREADS)
16 |
17 | set_target_properties(
18 | kakadujs
19 | PROPERTIES
20 | LINK_FLAGS "\
21 | -O3 \
22 | -lembind \
23 | -s DISABLE_EXCEPTION_CATCHING=1 \
24 | -s ASSERTIONS=0 \
25 | -s NO_EXIT_RUNTIME=1 \
26 | -s MALLOC=emmalloc \
27 | -s ALLOW_MEMORY_GROWTH=1 \
28 | -s INITIAL_MEMORY=50MB \
29 | -s FILESYSTEM=0 \
30 | -s EXPORTED_FUNCTIONS=[] \
31 | -s EXPORTED_RUNTIME_METHODS=[ccall] \
32 | ")
33 |
34 | else() # C++ header only library
35 | add_library(kakadujs INTERFACE)
36 | target_link_libraries(kakadujs INTERFACE kakaduappsupport kakadu)
37 | target_include_directories(kakadujs INTERFACE ".")
38 | endif()
--------------------------------------------------------------------------------
/src/FrameInfo.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Chris Hafey.
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | struct FrameInfo {
7 | ///
8 | /// Width of the image, range [1, 65535].
9 | ///
10 | uint16_t width;
11 |
12 | ///
13 | /// Height of the image, range [1, 65535].
14 | ///
15 | uint16_t height;
16 |
17 | ///
18 | /// Number of bits per sample, range [2, 16]
19 | ///
20 | uint8_t bitsPerSample;
21 |
22 | ///
23 | /// Number of components contained in the frame, range [1, 255]
24 | ///
25 | uint8_t componentCount;
26 |
27 | ///
28 | /// true if signed, false if unsigned
29 | ///
30 | bool isSigned;
31 | };
--------------------------------------------------------------------------------
/src/HTJ2KDecoder.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Chris Hafey.
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | // Kakadu core includes
11 | #include "kdu_elementary.h"
12 | #include "kdu_messaging.h"
13 | #include "kdu_params.h"
14 | #include "kdu_compressed.h"
15 | #include "kdu_sample_processing.h"
16 | #include "kdu_utils.h" // Access `kdu_memsafe_mul' etc. for safe mem calcs
17 | #include "jp2.h"
18 | #include "jpx.h"
19 | #include "kdu_stripe_decompressor.h"
20 |
21 | #ifdef __EMSCRIPTEN__
22 | #include
23 | #endif
24 |
25 | #include "FrameInfo.hpp"
26 | #include "Point.hpp"
27 | #include "Size.hpp"
28 |
29 | #define ojph_div_ceil(a, b) (((a) + (b)-1) / (b))
30 |
31 | ///
32 | /// JavaScript API for decoding HTJ2K bistreams with OpenJPH
33 | ///
34 | class HTJ2KDecoder
35 | {
36 | public:
37 | ///
38 | /// Constructor for decoding a HTJ2K image from JavaScript.
39 | ///
40 | HTJ2KDecoder()
41 | : pEncoded_(&encodedInternal_),
42 | pDecoded_(&decodedInternal_)
43 | {
44 | }
45 |
46 | #ifdef __EMSCRIPTEN__
47 | ///
48 | /// Resizes encoded buffer and returns a TypedArray of the buffer allocated
49 | /// in WASM memory space that will hold the HTJ2K encoded bitstream.
50 | /// JavaScript code needs to copy the HTJ2K encoded bistream into the
51 | /// returned TypedArray. This copy operation is needed because WASM runs
52 | /// in a sandbox and cannot access memory managed by JavaScript.
53 | ///
54 | emscripten::val getEncodedBuffer(size_t encodedSize)
55 | {
56 | pDecoded_->resize(encodedSize);
57 | return emscripten::val(emscripten::typed_memory_view(pDecoded_->size(), pDecoded_->data()));
58 | }
59 |
60 | ///
61 | /// Returns a TypedArray of the buffer allocated in WASM memory space that
62 | /// holds the decoded pixel data
63 | ///
64 | emscripten::val getDecodedBuffer()
65 | {
66 | return emscripten::val(emscripten::typed_memory_view(pDecoded_->size(), pDecoded_->data()));
67 | }
68 | #else
69 | ///
70 | /// Returns the buffer to store the encoded bytes. This method is not exported
71 | /// to JavaScript, it is intended to be called by C++ code
72 | ///
73 | std::vector &getEncodedBytes()
74 | {
75 | return *pEncoded_;
76 | }
77 |
78 | ///
79 | /// Sets a pointer to a vector containing the encoded bytes. This can be used to avoid having to copy the encoded. Set to 0
80 | /// to reset to the internal buffer
81 | ///
82 | void setEncodedBytes(std::vector *pEncoded)
83 | {
84 | if (pEncoded == 0)
85 | {
86 | pEncoded_ = &encodedInternal_;
87 | }
88 | else
89 | {
90 | pEncoded_ = pEncoded;
91 | }
92 | }
93 |
94 | ///
95 | /// Returns the buffer to store the decoded bytes. This method is not exported
96 | /// to JavaScript, it is intended to be called by C++ code
97 | ///
98 | const std::vector &getDecodedBytes() const
99 | {
100 | return *pDecoded_;
101 | }
102 |
103 | ///
104 | /// Sets a pointer to a vector containing the encoded bytes. This can be used to avoid having to copy the encoded. Set to 0
105 | /// to reset to the internal buffer
106 | ///
107 | void setDecodedBytes(std::vector *pDecoded)
108 | {
109 | if (pDecoded == 0)
110 | {
111 | pDecoded_ = &decodedInternal_;
112 | }
113 | else
114 | {
115 | pDecoded_ = pDecoded;
116 | }
117 | }
118 |
119 | #endif
120 |
121 | ///
122 | /// Reads the header from an encoded HTJ2K bitstream. The caller must have
123 | /// copied the HTJ2K encoded bitstream into the encoded buffer before
124 | /// calling this method, see getEncodedBuffer() and getEncodedBytes() above.
125 | ///
126 | void readHeader()
127 | {
128 | kdu_core::kdu_compressed_source_buffered input(pEncoded_->data(), pEncoded_->size());
129 | kdu_core::kdu_codestream codestream;
130 | readHeader_(codestream, input);
131 | codestream.destroy();
132 | input.close();
133 | }
134 |
135 | ///
136 | /// Calculates the resolution for a given decomposition level based on the
137 | /// current values in FrameInfo (which is populated via readHeader() and
138 | /// decode()). level = 0 = full res, level = _numDecompositions = lowest resolution
139 | ///
140 | Size calculateSizeAtDecompositionLevel(int decompositionLevel)
141 | {
142 | Size result(frameInfo_.width, frameInfo_.height);
143 | while (decompositionLevel > 0)
144 | {
145 | result.width = ojph_div_ceil(result.width, 2);
146 | result.height = ojph_div_ceil(result.height, 2);
147 | decompositionLevel--;
148 | }
149 | return result;
150 | }
151 |
152 | ///
153 | /// Decodes the encoded HTJ2K bitstream. The caller must have copied the
154 | /// HTJ2K encoded bitstream into the encoded buffer before calling this
155 | /// method, see getEncodedBuffer() and getEncodedBytes() above.
156 | ///
157 | void decode()
158 | {
159 | kdu_core::kdu_codestream codestream;
160 | kdu_core::kdu_compressed_source_buffered input(pEncoded_->data(), pEncoded_->size());
161 | readHeader_(codestream, input);
162 | decode_(codestream, input, 0);
163 | codestream.destroy();
164 | input.close();
165 | }
166 |
167 | ///
168 | /// Decodes the encoded HTJ2K bitstream to the requested decomposition level.
169 | /// The caller must have copied the HTJ2K encoded bitstream into the encoded
170 | /// buffer before calling this method, see getEncodedBuffer() and
171 | /// getEncodedBytes() above.
172 | ///
173 | void decodeSubResolution(size_t decompositionLevel)
174 | {
175 | kdu_core::kdu_codestream codestream;
176 | kdu_core::kdu_compressed_source_buffered input(pEncoded_->data(), pEncoded_->size());
177 | readHeader_(codestream, input);
178 | decode_(codestream, input, decompositionLevel);
179 | codestream.destroy();
180 | input.close();
181 | }
182 |
183 | ///
184 | /// returns the FrameInfo object for the decoded image.
185 | ///
186 | const FrameInfo &getFrameInfo() const
187 | {
188 | return frameInfo_;
189 | }
190 |
191 | ///
192 | /// returns the number of wavelet decompositions.
193 | ///
194 | const size_t getNumDecompositions() const
195 | {
196 | return numDecompositions_;
197 | }
198 |
199 | ///
200 | /// returns true if the image is lossless, false if lossy
201 | ///
202 | const bool getIsReversible() const
203 | {
204 | return isReversible_;
205 | }
206 |
207 | ///
208 | /// returns progression order.
209 | // 0 = LRCP
210 | // 1 = RLCP
211 | // 2 = RPCL
212 | // 3 = PCRL
213 | // 4 = CPRL
214 | ///
215 | const size_t getProgressionOrder() const
216 | {
217 | return progressionOrder_;
218 | }
219 |
220 | ///
221 | /// returns the down sampling used for component.
222 | ///
223 | Point getDownSample(size_t component) const
224 | {
225 | return downSamples_[component];
226 | }
227 |
228 | ///
229 | /// returns the block dimensions
230 | ///
231 | Size getBlockDimensions() const
232 | {
233 | return blockDimensions_;
234 | }
235 |
236 | ///
237 | /// returns whether or not a color transform is used
238 | ///
239 | bool getIsUsingColorTransform() const
240 | {
241 | return isUsingColorTransform_;
242 | }
243 |
244 | ///
245 | /// returns whether or not HT encoding was used
246 | ///
247 | bool getIsHTEnabled() const
248 | {
249 | return isHTEnabled_;
250 | }
251 |
252 | private:
253 | void readHeader_(kdu_core::kdu_codestream &codestream, kdu_core::kdu_compressed_source_buffered &source)
254 | {
255 | kdu_supp::jp2_family_src jp2_ultimate_src;
256 | jp2_ultimate_src.open(&source);
257 | kdu_supp::jpx_source jpx_in;
258 | if (jpx_in.open(&jp2_ultimate_src, true) < 0)
259 | {
260 | jp2_ultimate_src.close();
261 | source.seek(-source.get_pos()); // rewind the source to the first byte since jp2_family_src moved it forward
262 | }
263 | else
264 | {
265 | // move to the first compositing layer
266 | kdu_supp::jpx_layer_source jpx_layer = jpx_in.access_layer(0);
267 | }
268 |
269 | // Create the codestream object.
270 | codestream.create(&source);
271 |
272 | // Determine number of components to decompress
273 | kdu_core::kdu_dims dims;
274 | codestream.get_dims(0, dims);
275 |
276 | int num_components = codestream.get_num_components();
277 | if (num_components == 2)
278 | num_components = 1;
279 | else if (num_components >= 3)
280 | { // Check that components have consistent dimensions
281 | num_components = 3;
282 | kdu_core::kdu_dims dims1;
283 | codestream.get_dims(1, dims1);
284 | kdu_core::kdu_dims dims2;
285 | codestream.get_dims(2, dims2);
286 | if ((dims1 != dims) || (dims2 != dims))
287 | num_components = 1;
288 | }
289 | codestream.apply_input_restrictions(0, num_components, 0, 0, NULL);
290 | frameInfo_.width = dims.size.x;
291 | frameInfo_.height = dims.size.y;
292 | frameInfo_.componentCount = num_components;
293 | frameInfo_.bitsPerSample = codestream.get_bit_depth(0);
294 | frameInfo_.isSigned = codestream.get_signed(0);
295 | }
296 |
297 | void decode_(kdu_core::kdu_codestream &codestream, kdu_core::kdu_compressed_source_buffered &input, size_t decompositionLevel)
298 | {
299 | kdu_core::siz_params *siz = codestream.access_siz();
300 | kdu_core::kdu_params *cod = siz->access_cluster(COD_params);
301 | cod->get(Clevels, 0, 0, (int &)numDecompositions_);
302 | cod->get(Corder, 0, 0, (int &)progressionOrder_);
303 | cod->get(Creversible, 0, 0, isReversible_);
304 | cod->get(Cblk, 0, 0, (int &)blockDimensions_.height);
305 | cod->get(Cblk, 0, 1, (int &)blockDimensions_.width);
306 |
307 | isHTEnabled_ = codestream.get_ht_usage();
308 | size_t bytesPerPixel = (frameInfo_.bitsPerSample + 1) / 8;
309 | // Now decompress the image in one hit, using `kdu_stripe_decompressor'
310 | size_t num_samples = kdu_core::kdu_memsafe_mul(frameInfo_.componentCount,
311 | kdu_core::kdu_memsafe_mul(frameInfo_.width,
312 | frameInfo_.height));
313 | pDecoded_->resize(num_samples * bytesPerPixel);
314 | kdu_core::kdu_byte *buffer = pDecoded_->data();
315 | kdu_supp::kdu_stripe_decompressor decompressor;
316 | decompressor.start(codestream);
317 | int stripe_heights[3] = {frameInfo_.height, frameInfo_.height, frameInfo_.height};
318 |
319 | bool is_signed[3] = {frameInfo_.isSigned, frameInfo_.isSigned, frameInfo_.isSigned};
320 | if (bytesPerPixel == 1)
321 | {
322 | decompressor.pull_stripe((kdu_core::kdu_byte *)buffer, stripe_heights);
323 | }
324 | else
325 | {
326 | decompressor.pull_stripe(
327 | (kdu_core::kdu_int16 *)buffer,
328 | stripe_heights,
329 | NULL, // sample_offsets
330 | NULL, // sample_gaps
331 | NULL, // row_gaps
332 | NULL, // precisions
333 | is_signed, // is_signed
334 | NULL, // pad_flags
335 | 0 // vectorized_store_prefs
336 | );
337 | }
338 | decompressor.finish();
339 | }
340 |
341 | std::vector *pEncoded_;
342 | std::vector *pDecoded_;
343 | std::vector encodedInternal_;
344 | std::vector decodedInternal_;
345 |
346 | // std::vector encoded_;
347 | // std::vector decoded_;
348 | FrameInfo frameInfo_;
349 | std::vector downSamples_;
350 | size_t numDecompositions_;
351 | bool isReversible_;
352 | size_t progressionOrder_;
353 | Size blockDimensions_;
354 | bool isUsingColorTransform_;
355 | bool isHTEnabled_;
356 | };
357 |
--------------------------------------------------------------------------------
/src/HTJ2KEncoder.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Chris Hafey.
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | // Kakadu core includes
7 | #include "kdu_elementary.h"
8 | #include "kdu_messaging.h"
9 | #include "kdu_params.h"
10 | #include "kdu_compressed.h"
11 | #include "kdu_sample_processing.h"
12 | #include "kdu_utils.h"
13 | #include "jp2.h"
14 |
15 | // Application level includes
16 | #include "kdu_stripe_compressor.h"
17 |
18 | #ifdef __EMSCRIPTEN__
19 | #include
20 | #endif
21 |
22 | #include "FrameInfo.hpp"
23 |
24 | class kdu_buffer_target : public kdu_core::kdu_compressed_target
25 | {
26 | public: // Member functions
27 | kdu_buffer_target(std::vector &encoded) : encoded_(encoded)
28 | {
29 | encoded_.resize(0);
30 | }
31 | ~kdu_buffer_target() { return; } // Destructor must be virtual
32 | int get_capabilities() { return KDU_TARGET_CAP_CACHED; }
33 | bool write(const kdu_core::kdu_byte *buf, int num_bytes)
34 | {
35 | const size_t size = encoded_.size();
36 | encoded_.resize(size + num_bytes);
37 | memcpy(encoded_.data() + size, buf, num_bytes);
38 | return true;
39 | }
40 |
41 | private: // Data
42 | std::vector &encoded_;
43 | };
44 |
45 | ///
46 | /// JavaScript API for encoding images to HTJ2K bitstreams with OpenJPH
47 | ///
48 | class HTJ2KEncoder
49 | {
50 | public:
51 | ///
52 | /// Constructor for encoding a HTJ2K image from JavaScript.
53 | ///
54 | HTJ2KEncoder() : decompositions_(5),
55 | lossless_(true),
56 | quantizationStep_(-1.0),
57 | progressionOrder_(2), // RPCL
58 | blockDimensions_(64, 64),
59 | htEnabled_(true)
60 | {
61 | }
62 |
63 | #ifdef __EMSCRIPTEN__
64 | ///
65 | /// Resizes the decoded buffer to accomodate the specified frameInfo.
66 | /// Returns a TypedArray of the buffer allocated in WASM memory space that
67 | /// will hold the pixel data to be encoded. JavaScript code needs
68 | /// to copy the pixel data into the returned TypedArray. This copy
69 | /// operation is needed because WASM runs in a sandbox and cannot access
70 | /// data managed by JavaScript
71 | ///
72 | /// FrameInfo that describes the pixel data to be encoded
73 | ///
74 | /// TypedArray for the buffer allocated in WASM memory space for the
75 | /// source pixel data to be encoded.
76 | ///
77 | emscripten::val getDecodedBuffer(const FrameInfo &frameInfo)
78 | {
79 | frameInfo_ = frameInfo;
80 | const size_t bytesPerPixel = (frameInfo_.bitsPerSample + 8 - 1) / 8;
81 | const size_t decodedSize = frameInfo_.width * frameInfo_.height * frameInfo_.componentCount * bytesPerPixel;
82 | decoded_.resize(decodedSize);
83 | return emscripten::val(emscripten::typed_memory_view(decoded_.size(), decoded_.data()));
84 | }
85 |
86 | ///
87 | /// Returns a TypedArray of the buffer allocated in WASM memory space that
88 | /// holds the encoded pixel data.
89 | ///
90 | ///
91 | /// TypedArray for the buffer allocated in WASM memory space for the
92 | /// encoded pixel data.
93 | ///
94 | emscripten::val getEncodedBuffer()
95 | {
96 | return emscripten::val(emscripten::typed_memory_view(encoded_.size(), encoded_.data()));
97 | }
98 | #else
99 | ///
100 | /// Returns the buffer to store the decoded bytes. This method is not
101 | /// exported to JavaScript, it is intended to be called by C++ code
102 | ///
103 | std::vector &getDecodedBytes(const FrameInfo &frameInfo)
104 | {
105 | frameInfo_ = frameInfo;
106 | return decoded_;
107 | }
108 |
109 | ///
110 | /// Returns the buffer to store the encoded bytes. This method is not
111 | /// exported to JavaScript, it is intended to be called by C++ code
112 | ///
113 | const std::vector &getEncodedBytes() const
114 | {
115 | return encoded_;
116 | }
117 | #endif
118 |
119 | ///
120 | /// Sets the number of wavelet decompositions and clears any precincts
121 | ///
122 | void setDecompositions(size_t decompositions)
123 | {
124 | decompositions_ = decompositions;
125 | }
126 |
127 | ///
128 | /// Sets the quality level for the image. If lossless is false then
129 | /// quantizationStep controls the lossy quantization applied. quantizationStep
130 | /// is ignored if lossless is true
131 | ///
132 | void setQuality(bool lossless, float quantizationStep)
133 | {
134 | lossless_ = lossless;
135 | quantizationStep_ = quantizationStep;
136 | }
137 |
138 | ///
139 | /// Sets the progression order
140 | /// 0 = LRCP
141 | /// 1 = RLCP
142 | /// 2 = RPCL
143 | /// 3 = PCRL
144 | /// 4 = CPRL
145 | ///
146 | void setProgressionOrder(size_t progressionOrder)
147 | {
148 | progressionOrder_ = progressionOrder;
149 | }
150 |
151 | ///
152 | /// Sets the block dimensions
153 | ///
154 | void setBlockDimensions(Size blockDimensions)
155 | {
156 | blockDimensions_ = blockDimensions;
157 | }
158 |
159 | ///
160 | /// Sets HT encoding
161 | ///
162 | void setHTEnabled(bool htEnabled)
163 | {
164 | htEnabled_ = htEnabled;
165 | }
166 |
167 | ///
168 | /// Executes an HTJ2K encode using the data in the source buffer. The
169 | /// JavaScript code must copy the source image frame into the source
170 | /// buffer before calling this method. See documentation on getSourceBytes()
171 | /// above
172 | ///
173 | void encode()
174 | {
175 | // resize the encoded buffer so we don't have to keep resizing it
176 | const size_t bytesPerPixel = (frameInfo_.bitsPerSample + 8 - 1) / 8;
177 | encoded_.reserve(frameInfo_.width * frameInfo_.height * frameInfo_.componentCount * bytesPerPixel);
178 |
179 | // Construct code-stream object
180 | kdu_core::siz_params siz;
181 | siz.set(Scomponents, 0, 0, frameInfo_.componentCount);
182 | siz.set(Sdims, 0, 0, frameInfo_.height);
183 | siz.set(Sdims, 0, 1, frameInfo_.width);
184 | siz.set(Sprecision, 0, 0, frameInfo_.bitsPerSample);
185 | siz.set(Ssigned, 0, 0, frameInfo_.isSigned);
186 | kdu_core::kdu_params *siz_ref = &siz;
187 | siz_ref->finalize();
188 |
189 | kdu_buffer_target target(encoded_);
190 | kdu_supp::jp2_family_tgt tgt;
191 | tgt.open(&target);
192 | kdu_supp::jp2_target output;
193 | output.open(&tgt);
194 | kdu_supp::jp2_dimensions dims = output.access_dimensions();
195 | dims.init(&siz);
196 | kdu_supp::jp2_colour colr = output.access_colour();
197 | colr.init((frameInfo_.componentCount == 3) ? kdu_supp::JP2_sRGB_SPACE : kdu_supp::JP2_sLUM_SPACE);
198 | output.write_header();
199 | output.open_codestream(true);
200 |
201 | kdu_core::kdu_codestream codestream;
202 | codestream.create(&siz, &output);
203 |
204 | // Set up any specific coding parameters and finalize them.
205 | if (htEnabled_)
206 | {
207 | codestream.access_siz()->parse_string("Cmodes=HT");
208 | }
209 | char param[32];
210 | if (lossless_)
211 | {
212 | codestream.access_siz()->parse_string("Creversible=yes");
213 | }
214 | else
215 | {
216 | codestream.access_siz()->parse_string("Creversible=no");
217 | snprintf(param, 32, "Qstep=%f", quantizationStep_);
218 | codestream.access_siz()->parse_string(param);
219 | }
220 |
221 | switch (progressionOrder_)
222 | {
223 | case 0:
224 | codestream.access_siz()->parse_string("Corder=LRCP");
225 | break;
226 | case 1:
227 | codestream.access_siz()->parse_string("Corder=RLCP");
228 | break;
229 | case 2:
230 | codestream.access_siz()->parse_string("Corder=RPCL");
231 | break;
232 | case 3:
233 | codestream.access_siz()->parse_string("Corder=PCRL");
234 | break;
235 | case 4:
236 | codestream.access_siz()->parse_string("Corder=CPRL");
237 | break;
238 | }
239 |
240 | snprintf(param,32, "Clevels=%zu", decompositions_);
241 | codestream.access_siz()->parse_string(param);
242 |
243 | snprintf(param, 32, "Cblk={%d,%d}", blockDimensions_.width, blockDimensions_.height);
244 | codestream.access_siz()->parse_string(param);
245 | codestream.access_siz()->finalize_all(); // Set up coding defaults
246 |
247 | // Now compress the image in one hit, using `kdu_stripe_compressor'
248 | kdu_supp::kdu_stripe_compressor compressor;
249 | compressor.start(codestream);
250 | int stripe_heights[3] = {frameInfo_.height, frameInfo_.height, frameInfo_.height};
251 | if (frameInfo_.bitsPerSample <= 8)
252 | {
253 | compressor.push_stripe(
254 | decoded_.data(),
255 | stripe_heights);
256 | }
257 | else
258 | {
259 | bool is_signed[3] = {frameInfo_.isSigned, frameInfo_.isSigned, frameInfo_.isSigned};
260 | int precisions[3] = {frameInfo_.bitsPerSample, frameInfo_.bitsPerSample, frameInfo_.bitsPerSample};
261 | compressor.push_stripe(
262 | (kdu_core::kdu_int16 *)decoded_.data(),
263 | stripe_heights,
264 | NULL,
265 | NULL,
266 | NULL,
267 | precisions,
268 | is_signed);
269 | }
270 | compressor.finish();
271 |
272 | // Finally, cleanup
273 | codestream.destroy();
274 | tgt.close();
275 | output.close();
276 | target.close();
277 | }
278 |
279 | private:
280 | std::vector decoded_;
281 | std::vector encoded_;
282 | FrameInfo frameInfo_;
283 | size_t decompositions_;
284 | bool lossless_;
285 | float quantizationStep_;
286 | size_t progressionOrder_;
287 | Size blockDimensions_;
288 | bool htEnabled_;
289 | };
290 |
--------------------------------------------------------------------------------
/src/Point.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Chris Hafey.
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | struct Point {
7 | Point() : x(0), y(0) {}
8 | Point(uint32_t x, uint32_t y) : x(x), y(y) {}
9 |
10 | uint32_t x;
11 | uint32_t y;
12 | };
--------------------------------------------------------------------------------
/src/Size.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Chris Hafey.
2 | // SPDX-License-Identifier: MIT
3 |
4 | #pragma once
5 |
6 | struct Size {
7 | Size() : width(0), height(0) {}
8 | Size(uint32_t width, uint32_t height) : width(width), height(height) {}
9 |
10 | uint32_t width;
11 | uint32_t height;
12 | };
--------------------------------------------------------------------------------
/src/jslib.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Chris Hafey.
2 | // SPDX-License-Identifier: MIT
3 |
4 | #include "HTJ2KDecoder.hpp"
5 | #include "HTJ2KEncoder.hpp"
6 |
7 | #include
8 | #include
9 |
10 | using namespace emscripten;
11 |
12 | static std::string getVersion()
13 | {
14 | std::string version = KDU_CORE_VERSION;
15 | return version;
16 | }
17 |
18 | EMSCRIPTEN_BINDINGS(charlsjs)
19 | {
20 | function("getVersion", &getVersion);
21 | }
22 |
23 | EMSCRIPTEN_BINDINGS(FrameInfo)
24 | {
25 | value_object("FrameInfo")
26 | .field("width", &FrameInfo::width)
27 | .field("height", &FrameInfo::height)
28 | .field("bitsPerSample", &FrameInfo::bitsPerSample)
29 | .field("componentCount", &FrameInfo::componentCount)
30 | .field("isSigned", &FrameInfo::isSigned);
31 | }
32 |
33 | EMSCRIPTEN_BINDINGS(Point)
34 | {
35 | value_object("Point")
36 | .field("x", &Point::x)
37 | .field("y", &Point::y);
38 | }
39 |
40 | EMSCRIPTEN_BINDINGS(Size)
41 | {
42 | value_object("Size")
43 | .field("width", &Size::width)
44 | .field("height", &Size::height);
45 | }
46 |
47 | EMSCRIPTEN_BINDINGS(HTJ2KDecoder)
48 | {
49 | class_("HTJ2KDecoder")
50 | .constructor<>()
51 | .function("getEncodedBuffer", &HTJ2KDecoder::getEncodedBuffer)
52 | .function("getDecodedBuffer", &HTJ2KDecoder::getDecodedBuffer)
53 | .function("readHeader", &HTJ2KDecoder::readHeader)
54 | .function("calculateSizeAtDecompositionLevel", &HTJ2KDecoder::calculateSizeAtDecompositionLevel)
55 | .function("decode", &HTJ2KDecoder::decode)
56 | .function("decodeSubResolution", &HTJ2KDecoder::decodeSubResolution)
57 | .function("getFrameInfo", &HTJ2KDecoder::getFrameInfo)
58 | .function("getDownSample", &HTJ2KDecoder::getDownSample)
59 | .function("getNumDecompositions", &HTJ2KDecoder::getNumDecompositions)
60 | .function("getIsReversible", &HTJ2KDecoder::getIsReversible)
61 | .function("getProgressionOrder", &HTJ2KDecoder::getProgressionOrder)
62 | .function("getBlockDimensions", &HTJ2KDecoder::getBlockDimensions)
63 | .function("getIsUsingColorTransform", &HTJ2KDecoder::getIsUsingColorTransform)
64 | .function("getIsHTEnabled", &HTJ2KDecoder::getIsHTEnabled);
65 | }
66 |
67 | EMSCRIPTEN_BINDINGS(HTJ2KEncoder)
68 | {
69 | class_("HTJ2KEncoder")
70 | .constructor<>()
71 | .function("getDecodedBuffer", &HTJ2KEncoder::getDecodedBuffer)
72 | .function("getEncodedBuffer", &HTJ2KEncoder::getEncodedBuffer)
73 | .function("encode", &HTJ2KEncoder::encode)
74 | .function("setDecompositions", &HTJ2KEncoder::setDecompositions)
75 | .function("setQuality", &HTJ2KEncoder::setQuality)
76 | .function("setProgressionOrder", &HTJ2KEncoder::setProgressionOrder)
77 | .function("setBlockDimensions", &HTJ2KEncoder::setBlockDimensions)
78 | .function("setHTEnabled", &HTJ2KEncoder::setHTEnabled);
79 | }
--------------------------------------------------------------------------------
/test/browser/index.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
HTJ2K Decoding with WebAssembly using Kakadu
19 |
20 | Select an image or drag and drop a HTJ2K/JPH file. Files dropped here remain local in your browser, they are
21 | not
22 | uploaded anywhere.
23 |