├── .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 | 47 |
48 |
49 | Image Properties 50 |
51 |
52 |
53 |
Encode Time:
54 |
Decode Time:
55 |
Display Time:
56 |
57 |
58 |
Encoded Size:
59 |
Decoded Size:
60 |
Compression Ratio:
61 |
62 |
63 |
Resolution:
64 |
Pixel Format:
65 |
Component Count:
66 |
67 |
68 |
Min Pixel:
69 |
Max Pixel:
70 |
Dynamic Range:
71 |
72 |
73 |
74 |
75 |
Decompositions:
76 |
Block Dimensions:
77 |
Reversible:
78 |
79 |
80 |
Progression Order:
81 |
Color Transform:
82 |
HT Enabled:
83 |
84 |
85 |
Downsampling:
86 |
87 |
88 |
89 |
90 |
Sub Resolutions:
91 |
92 |
93 |
94 |
95 | Decoding Parameters 96 |
97 |
98 |
99 |
100 | Encoded Bytes Read: 0 101 | 102 |
103 |
104 |
105 |
106 | Level: 0 107 | 108 |
109 |
110 | 111 |
112 |
113 |
114 | Encoding Parameters 115 |
116 |
117 |
118 |
119 | Lossy Quantization Factor: 0.0000 120 | 121 |
122 |
123 |
124 | 125 |
126 | 127 | Progression Order: 128 | 135 | Decompositions: 136 | 147 | Block Dimensions: 148 | 155 |
156 |
157 |
158 |
159 | 160 |
161 |
162 | 163 | 164 | 638 | 639 | -------------------------------------------------------------------------------- /test/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_executable(cpptest main.cpp) 3 | 4 | target_link_libraries(cpptest PRIVATE kakadujs) 5 | 6 | target_compile_features(cpptest PRIVATE cxx_std_11) 7 | -------------------------------------------------------------------------------- /test/cpp/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Hafey. 2 | // SPDX-License-Identifier: MIT 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /* ========================================================================= */ 14 | /* Set up messaging services */ 15 | /* ========================================================================= */ 16 | 17 | class kdu_stream_message : public kdu_core::kdu_thread_safe_message 18 | { 19 | public: // Member classes 20 | kdu_stream_message(std::ostream *stream) 21 | { 22 | this->stream = stream; 23 | } 24 | void put_text(const char *string) 25 | { 26 | (*stream) << string; 27 | } 28 | void flush(bool end_of_message = false) 29 | { 30 | stream->flush(); 31 | kdu_thread_safe_message::flush(end_of_message); 32 | } 33 | 34 | private: // Data 35 | std::ostream *stream; 36 | }; 37 | 38 | static kdu_stream_message cout_message(&std::cout); 39 | static kdu_stream_message cerr_message(&std::cerr); 40 | static kdu_core::kdu_message_formatter pretty_cout(&cout_message); 41 | static kdu_core::kdu_message_formatter pretty_cerr(&cerr_message); 42 | 43 | #ifdef _WIN32 44 | #define CLOCK_PROCESS_CPUTIME_ID 0 45 | // struct timespec { long tv_sec; long tv_nsec; }; //header part 46 | int clock_gettime(int, struct timespec *spec) // C-file part 47 | { 48 | __int64 wintime; 49 | GetSystemTimeAsFileTime((FILETIME *)&wintime); 50 | wintime -= 116444736000000000i64; // 1jan1601 to 1jan1970 51 | spec->tv_sec = wintime / 10000000i64; // seconds 52 | spec->tv_nsec = wintime % 10000000i64 * 100; // nano-seconds 53 | return 0; 54 | } 55 | #endif 56 | 57 | void readFile(std::string fileName, std::vector &vec) 58 | { 59 | // open the file: 60 | std::ifstream file(fileName, std::ios::in | std::ios::binary); 61 | if (file.fail()) 62 | { 63 | printf("File %s does not exist\n", fileName.c_str()); 64 | exit(1); 65 | } 66 | // Stop eating new lines in binary mode!!! 67 | file.unsetf(std::ios::skipws); 68 | 69 | // get its size: 70 | int fileSize; 71 | file.seekg(0, std::ios::end); 72 | fileSize = file.tellg(); 73 | file.seekg(0, std::ios::beg); 74 | 75 | // reserve capacity 76 | vec.resize(0); 77 | vec.reserve(fileSize); 78 | 79 | // read the data: 80 | vec.insert(vec.begin(), 81 | std::istream_iterator(file), 82 | std::istream_iterator()); 83 | } 84 | 85 | void writeFile(std::string fileName, const std::vector &vec) 86 | { 87 | std::ofstream file(fileName, std::ios::out | std::ofstream::binary); 88 | std::copy(vec.begin(), vec.end(), std::ostreambuf_iterator(file)); 89 | } 90 | 91 | enum 92 | { 93 | NS_PER_SECOND = 1000000000 94 | }; 95 | 96 | void sub_timespec(struct timespec t1, struct timespec t2, struct timespec *td) 97 | { 98 | td->tv_nsec = t2.tv_nsec - t1.tv_nsec; 99 | td->tv_sec = t2.tv_sec - t1.tv_sec; 100 | if (td->tv_sec > 0 && td->tv_nsec < 0) 101 | { 102 | td->tv_nsec += NS_PER_SECOND; 103 | td->tv_sec--; 104 | } 105 | else if (td->tv_sec < 0 && td->tv_nsec > 0) 106 | { 107 | td->tv_nsec -= NS_PER_SECOND; 108 | td->tv_sec++; 109 | } 110 | } 111 | 112 | std::vector decodeFile(const char *path, size_t iterations = 1, bool silent = false) 113 | { 114 | HTJ2KDecoder decoder; 115 | std::vector &encodedBytes = decoder.getEncodedBytes(); 116 | readFile(path, encodedBytes); 117 | 118 | timespec start, finish, delta; 119 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start); 120 | decoder.readHeader(); 121 | 122 | for (int i = 0; i < iterations; i++) 123 | { 124 | decoder.decode(); 125 | } 126 | 127 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &finish); 128 | sub_timespec(start, finish, &delta); 129 | auto frameInfo = decoder.getFrameInfo(); 130 | 131 | auto ns = delta.tv_sec * 1000000000.0 + delta.tv_nsec; 132 | auto totalTimeMS = ns / 1000000.0; 133 | auto timePerFrameMS = ns / 1000000.0 / (double)iterations; 134 | auto pixels = (frameInfo.width * frameInfo.height); 135 | auto megaPixels = (double)pixels / (1024.0 * 1024.0); 136 | auto fps = 1000 / timePerFrameMS; 137 | auto mps = (double)(megaPixels)*fps; 138 | 139 | if (!silent) 140 | { 141 | printf("NATIVE decode %s TotalTime: %.3f s for %zu iterations; TPF=%.3f ms (%.2f MP/s, %.2f FPS)\n", path, totalTimeMS / 1000, iterations, timePerFrameMS, mps, fps); 142 | } 143 | 144 | // printf("Native-decode %s TotalTime= %.2f ms TPF=%.2f ms (%.2f MP/s, %.2f FPS)\n", path, totalTimeMS, timePerFrameMS, mps, fps); 145 | return decoder.getDecodedBytes(); 146 | } 147 | 148 | void encodeFile(const char *inPath, const FrameInfo frameInfo, const char *outPath = NULL, size_t iterations = 1, bool silent = false) 149 | { 150 | // printf("FrameInfo %dx%dx%d %d bpp\n", frameInfo.width, frameInfo.height, frameInfo.componentCount, frameInfo.bitsPerSample); 151 | HTJ2KEncoder encoder; 152 | encoder.setQuality(true, 0.0f); 153 | encoder.setDecompositions(5); 154 | encoder.setBlockDimensions(Size(64, 64)); 155 | encoder.setProgressionOrder(0); 156 | std::vector &rawBytes = encoder.getDecodedBytes(frameInfo); 157 | 158 | readFile(inPath, rawBytes); 159 | 160 | timespec start, finish, delta; 161 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start); 162 | 163 | for (int i = 0; i < iterations; i++) 164 | { 165 | encoder.encode(); 166 | } 167 | 168 | clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &finish); 169 | sub_timespec(start, finish, &delta); 170 | 171 | auto ns = delta.tv_sec * 1000000000.0 + delta.tv_nsec; 172 | auto totalTimeMS = ns / 1000000.0; 173 | auto timePerFrameMS = ns / 1000000.0 / (double)iterations; 174 | auto pixels = (frameInfo.width * frameInfo.height); 175 | auto megaPixels = (double)pixels / (1024.0 * 1024.0); 176 | auto fps = 1000 / timePerFrameMS; 177 | auto mps = (double)(megaPixels)*fps; 178 | 179 | const std::vector &encodedBytes = encoder.getEncodedBytes(); 180 | if (!silent) 181 | { 182 | printf("NATIVE encode %s TotalTime: %.3f s for %zu iterations; TPF=%.3f ms (%.2f MP/s, %.2f FPS)\n", inPath, totalTimeMS / 1000, iterations, timePerFrameMS, mps, fps); 183 | } 184 | 185 | if (outPath) 186 | { 187 | writeFile(outPath, encodedBytes); 188 | } 189 | } 190 | 191 | int main(int argc, char **argv) 192 | { 193 | kdu_customize_warnings(&pretty_cout); 194 | kdu_customize_errors(&pretty_cerr); 195 | 196 | const size_t iterations = (argc > 1) ? atoi(argv[1]) : 2000; 197 | 198 | // warm up the decoder and encoder 199 | try 200 | { 201 | decodeFile("test/fixtures/j2c/CT1.j2c", 1, false); 202 | encodeFile("test/fixtures/raw/CT1.RAW", {.width = 512, .height = 512, .bitsPerSample = 16, .componentCount = 1, .isSigned = true}, NULL, 1, true); 203 | 204 | // benchmark 205 | decodeFile("test/fixtures/j2c/CT1.j2c", iterations); 206 | // decodeFile("test/fixtures/j2c/MG1.j2c", iterations); 207 | // encodeFile("test/fixtures/raw/CT1.RAW", {.width = 512, .height = 512, .bitsPerSample = 16, .componentCount = 1, .isSigned = true}, NULL, iterations); 208 | 209 | // JPEG2000 color testing 210 | // deFile("test/fixtures/j2k/US1.j2k", 1); 211 | 212 | // encodeFile("test/fixtures/raw/CT1.RAW", {.width = 512, .height = 512, .bitsPerSample = 16, .componentCount = 1, .isSigned = true}, "test/fixtures/j2c/ignore.j2c"); 213 | // decodeFile("test/fixtures/j2c/ignore.j2c", 1); 214 | // decodeFile("test/fixtures/j2c/ignore.j2c", 1); 215 | 216 | /* 217 | encodeFile("test/fixtures/raw/CT1.RAW", {.width = 512, .height = 512, .bitsPerSample = 16, .componentCount = 1, .isSigned = true}, "test/fixtures/j2c/ignore.j2c", iterations); 218 | printf("decoding test/fixtures/raw/CT1.RAW\n"); 219 | std::vector decodedRawBytes = decodeFile("test/fixtures/raw/CT1.RAW"); 220 | std::vector originalRawBytes; 221 | readFile("test/fixtures/raw/CT1.RAW", originalRawBytes); 222 | printf("matches = %d\n", decodedRawBytes == originalRawBytes); 223 | */ 224 | } 225 | catch (const char *pError) 226 | { 227 | printf("ERROR: %s\n", pError); 228 | } 229 | return 0; 230 | } 231 | -------------------------------------------------------------------------------- /test/fixtures/CT1.ll.j2c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/CT1.ll.j2c -------------------------------------------------------------------------------- /test/fixtures/j2c/CT1.j2c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/j2c/CT1.j2c -------------------------------------------------------------------------------- /test/fixtures/j2c/CT2.j2c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/j2c/CT2.j2c -------------------------------------------------------------------------------- /test/fixtures/j2c/MG1.j2c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/j2c/MG1.j2c -------------------------------------------------------------------------------- /test/fixtures/j2c/MR1.j2c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/j2c/MR1.j2c -------------------------------------------------------------------------------- /test/fixtures/j2c/MR2.j2c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/j2c/MR2.j2c -------------------------------------------------------------------------------- /test/fixtures/j2c/MR3.j2c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/j2c/MR3.j2c -------------------------------------------------------------------------------- /test/fixtures/j2c/MR4.j2c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/j2c/MR4.j2c -------------------------------------------------------------------------------- /test/fixtures/j2c/NM1.j2c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/j2c/NM1.j2c -------------------------------------------------------------------------------- /test/fixtures/j2c/RG1.j2c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/j2c/RG1.j2c -------------------------------------------------------------------------------- /test/fixtures/j2c/RG2.j2c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/j2c/RG2.j2c -------------------------------------------------------------------------------- /test/fixtures/j2c/RG3.j2c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/j2c/RG3.j2c -------------------------------------------------------------------------------- /test/fixtures/j2c/SC1.j2c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/j2c/SC1.j2c -------------------------------------------------------------------------------- /test/fixtures/j2c/XA1.j2c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/j2c/XA1.j2c -------------------------------------------------------------------------------- /test/fixtures/j2k/US1.j2k: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/j2k/US1.j2k -------------------------------------------------------------------------------- /test/fixtures/raw/CT1.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/CT1.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/CT2.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/CT2.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/MG1.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/MG1.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/MR1.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/MR1.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/MR2.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/MR2.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/MR3.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/MR3.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/MR4.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/MR4.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/NM1.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/NM1.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/RG1.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/RG1.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/RG2.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/RG2.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/RG3.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/RG3.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/SC1.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/SC1.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/US1.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/US1.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/VL1.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/VL1.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/VL2.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/VL2.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/VL3.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/VL3.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/VL4.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/VL4.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/VL5.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/VL5.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/VL6.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/VL6.RAW -------------------------------------------------------------------------------- /test/fixtures/raw/XA1.RAW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/kakadujs/4803c99b046ab49e54ea5f0dbe0e71266b47d26a/test/fixtures/raw/XA1.RAW -------------------------------------------------------------------------------- /test/node/index.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Chris Hafey. 2 | // SPDX-License-Identifier: MIT 3 | 4 | const openjphjs = require('../../dist/kakadujs.js'); 5 | const fs = require('fs') 6 | 7 | openjphjs.onRuntimeInitialized = async _ => { 8 | 9 | // create an instance of the encoder and decoder 10 | const decoder = new openjphjs.HTJ2KDecoder(); 11 | const encoder = new openjphjs.HTJ2KEncoder(); 12 | 13 | function decode(encodedImagePath, iterations = 1, silent = false) { 14 | 15 | // read encoded bits and copy it into WASM memory 16 | const encodedBitStream = fs.readFileSync(encodedImagePath); 17 | const encodedBuffer = decoder.getEncodedBuffer(encodedBitStream.length); 18 | encodedBuffer.set(encodedBitStream); 19 | 20 | // do the actual benchmark 21 | const beginDecode = process.hrtime(); 22 | for(var i=0; i < iterations; i++) { 23 | decoder.decode(); 24 | } 25 | 26 | // Get the results 27 | const frameInfo = decoder.getFrameInfo() 28 | const decodeDuration = process.hrtime(beginDecode); // hrtime returns seconds/nanoseconds tuple 29 | const decodeDurationInSeconds = (decodeDuration[0] + (decodeDuration[1] / 1000000000)); 30 | const timePerFrameMS = ((decodeDurationInSeconds / iterations * 1000)) 31 | const pixels = (frameInfo.width * frameInfo.height); 32 | const megaPixels = pixels / (1024.0 * 1024.0); 33 | const fps = 1000 / timePerFrameMS; 34 | const mps = (megaPixels)*fps; 35 | 36 | // Print out information about the decode 37 | if(!silent) { 38 | console.log(`WASM decode ${encodedImagePath} TotalTime: ${decodeDurationInSeconds.toFixed(3)} s for ${iterations} iterations; TPF=${timePerFrameMS.toFixed(3)} ms (${mps.toFixed(2)} MP/s, ${fps.toFixed(2)} FPS)`) 39 | } 40 | } 41 | 42 | function encode(pathToUncompressedImageFrame, frameInfo, pathToJ2CFile, iterations = 1, silent=false) { 43 | // Read file and store into WASM Memory 44 | const uncompressedImageFrame = fs.readFileSync(pathToUncompressedImageFrame); 45 | const decodedBytes = encoder.getDecodedBuffer(frameInfo); 46 | decodedBytes.set(uncompressedImageFrame); 47 | //encoder.setQuality(false, 0.001); 48 | 49 | // do the actual benchmark 50 | const encodeBegin = process.hrtime(); 51 | for(var i=0; i < iterations;i++) { 52 | encoder.encode(); 53 | } 54 | 55 | // get the results 56 | const encodeDuration = process.hrtime(encodeBegin); 57 | const encodeDurationInSeconds = (encodeDuration[0] + (encodeDuration[1] / 1000000000)); 58 | const timePerFrameMS = ((encodeDurationInSeconds / iterations * 1000)) 59 | const pixels = (frameInfo.width * frameInfo.height); 60 | const megaPixels = pixels / (1024.0 * 1024.0); 61 | const fps = 1000 / timePerFrameMS; 62 | const mps = (megaPixels)*fps; 63 | 64 | // print out information about the encode 65 | if(!silent) { 66 | console.log(`WASM encode ${pathToUncompressedImageFrame} TotalTime: ${encodeDurationInSeconds.toFixed(3)} s for ${iterations} iterations; TPF=${timePerFrameMS.toFixed(3)} ms (${mps.toFixed(2)} MP/s, ${fps.toFixed(2)} FPS)`) 67 | } 68 | 69 | if(pathToJ2CFile) { 70 | //fs.writeFileSync(pathToJ2CFile, encodedBytes); 71 | } 72 | } 73 | 74 | // warm up decoder and encoder 75 | decode('../fixtures/j2c/CT2.j2c', 1, true); 76 | encode('../fixtures/raw/CT1.RAW', {width: 512, height: 512, bitsPerSample: 16, componentCount: 1, isSigned: true}, '../fixtures/j2c/CT1.j2c', 1, true); 77 | 78 | // benchmark 79 | const iterations = 20 80 | decode('../fixtures/j2c/CT1.j2c', iterations); 81 | decode('../fixtures/j2c/MG1.j2c', iterations); 82 | encode('../fixtures/raw/CT1.RAW', {width: 512, height: 512, bitsPerSample: 16, componentCount: 1, isSigned: true}, '../fixtures/j2c/CT1.j2c', iterations); 83 | 84 | // J2K Color testing 85 | //decodeFile("test/fixtures/j2k/US1.j2k", 1); 86 | 87 | 88 | // benchark encoding 89 | 90 | //encode('../fixtures/raw/CT2.RAW', {width: 512, height: 512, bitsPerSample: 16, componentCount: 1, isSigned: true}, '../fixtures/j2c/CT2.j2c'); 91 | //encode('../fixtures/raw/MG1.RAW', {width: 3064, height: 4774, bitsPerSample: 16, componentCount: 1, isSigned: false}, '../fixtures/j2c/MG1.j2c'); 92 | //encode('../fixtures/raw/MR1.RAW', {width: 512, height: 512, bitsPerSample: 16, componentCount: 1, isSigned: true}, '../fixtures/j2c/MR1.j2c'); 93 | //encode('../fixtures/raw/MR2.RAW', {width: 1024, height: 1024, bitsPerSample: 16, componentCount: 1, isSigned: false}, '../fixtures/j2c/MR2.j2c'); 94 | //encode('../fixtures/raw/MR3.RAW', {width: 512, height: 512, bitsPerSample: 16, componentCount: 1, isSigned: true}, '../fixtures/j2c/MR3.j2c'); 95 | //encode('../fixtures/raw/MR4.RAW', {width: 512, height: 512, bitsPerSample: 16, componentCount: 1, isSigned: false}, '../fixtures/j2c/MR4.j2c'); 96 | //encode('../fixtures/raw/NM1.RAW', {width: 256, height: 1024, bitsPerSample: 16, componentCount: 1, isSigned: true}, '../fixtures/j2c/NM1.j2c'); 97 | //encode('../fixtures/raw/RG1.RAW', {width: 1841, height: 1955, bitsPerSample: 16, componentCount: 1, isSigned: false}, '../fixtures/j2c/RG1.j2c'); 98 | //encode('../fixtures/raw/RG1.RAW', {width: 1841, height: 1955, bitsPerSample: 16, componentCount: 1, isSigned: false}, '../fixtures/j2c/RG1.j2c'); 99 | //encode('../fixtures/raw/RG2.RAW', {width: 1760, height: 2140, bitsPerSample: 16, componentCount: 1, isSigned: false}, '../fixtures/j2c/RG2.j2c'); 100 | //encode('../fixtures/raw/RG3.RAW', {width: 1760, height: 1760, bitsPerSample: 16, componentCount: 1, isSigned: false}, '../fixtures/j2c/RG3.j2c'); 101 | //encode('../fixtures/raw/SC1.RAW', {width: 2048, height: 2487, bitsPerSample: 16, componentCount: 1, isSigned: false}, '../fixtures/j2c/SC1.j2c'); 102 | //encode('../fixtures/raw/XA1.RAW', {width: 1024, height: 1024, bitsPerSample: 16, componentCount: 1, isSigned: false}, '../fixtures/j2c/XA1.j2c'); 103 | 104 | } 105 | -------------------------------------------------------------------------------- /test/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node index.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | --------------------------------------------------------------------------------