diff --git a/CMakeLists.txt b/CMakeLists.txt index eb71b9e6d2..b56dabad04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,6 +185,14 @@ if (WITH_FFMPEG_DECODER) find_package(FFMPEG COMPONENTS avcodec) endif () +# openjph + +plugin_option(OPENJPH_ENCODER "OpenJPH HT-J2K encoder" OFF ON) +# plugin_option(OPENJPH_DECODER "OpenJPH HT-J2K decoder" OFF ON) +if (WITH_OPENJPH_ENCODER OR WITH_OPENJPH_DECODER) + find_package(OPENJPH) +endif() + # uncompressed option(WITH_UNCOMPRESSED_CODEC " Support internal ISO/IEC 23001-17 uncompressed codec (experimental) " OFF) @@ -206,7 +214,8 @@ plugin_compilation_info(JPEG_DECODER JPEG "JPEG decoder") plugin_compilation_info(JPEG_ENCODER JPEG "JPEG encoder") plugin_compilation_info(OpenJPEG_DECODER OpenJPEG "OpenJPEG J2K decoder") plugin_compilation_info(OpenJPEG_ENCODER OpenJPEG "OpenJPEG J2K encoder") - +# plugin_compilation_info(OPENJPH_DECODER OPENJPH "OpenJPH HT-J2K decoder") +plugin_compilation_info(OPENJPH_ENCODER OPENJPH "OpenJPH HT-J2K encoder") # --- show summary which formats are supported @@ -224,9 +233,9 @@ macro(format_compilation_info formatName decoding_supported encoding_supported) endif() string(LENGTH "${formatName}" len) - math(EXPR fill "10 - ${len}") + math(EXPR fill "12 - ${len}") string(SUBSTRING " " 0 ${fill} filler) - message("${formatName}${filler} ${decoding} ${encoding}") + message("${formatName}${filler} ${decoding} ${encoding}") unset(msg) endmacro() @@ -251,22 +260,31 @@ if (JPEG_FOUND AND WITH_JPEG_ENCODER) endif() if (OpenJPEG_FOUND AND WITH_OpenJPEG_DECODER) set(SUPPORTS_J2K_DECODING TRUE) + set(SUPPORTS_J2K_HT_DECODING TRUE) endif() if (OpenJPEG_FOUND AND WITH_OpenJPEG_ENCODER) set(SUPPORTS_J2K_ENCODING TRUE) endif() +if (OPENJPH_FOUND AND WITH_OPENJPH_ENCODER) + set(SUPPORTS_J2K_HT_ENCODING TRUE) +endif() +if (OPENJPH_FOUND AND WITH_OPENJPH_DECODER) + set(SUPPORTS_J2K_HT_ENCODING TRUE) +endif() + if (WITH_UNCOMPRESSED_CODEC) set(SUPPORTS_UNCOMPRESSED_DECODING TRUE) set(SUPPORTS_UNCOMPRESSED_ENCODING TRUE) endif() message("\n=== Supported formats ===") -message("format decoding encoding") +message("format decoding encoding") format_compilation_info("HEIC" SUPPORTS_HEIC_DECODING SUPPORTS_HEIC_ENCODING) format_compilation_info("AVIF" SUPPORTS_AVIF_DECODING SUPPORTS_AVIF_ENCODING) format_compilation_info("JPEG" SUPPORTS_JPEG_DECODING SUPPORTS_JPEG_ENCODING) format_compilation_info("JPEG2000" SUPPORTS_J2K_DECODING SUPPORTS_J2K_ENCODING) -format_compilation_info("Uncompr." SUPPORTS_UNCOMPRESSED_DECODING SUPPORTS_UNCOMPRESSED_ENCODING) +format_compilation_info("JPEG2000-HT" SUPPORTS_J2K_HT_DECODING SUPPORTS_J2K_HT_ENCODING) +format_compilation_info("Uncompressed" SUPPORTS_UNCOMPRESSED_DECODING SUPPORTS_UNCOMPRESSED_ENCODING) message("") # --- Libsharpyuv color space transforms diff --git a/CMakePresets.json b/CMakePresets.json index dbbcde1db7..ac99334b84 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -37,7 +37,7 @@ "WITH_OpenJPEG_ENCODER" : "ON", "WITH_OpenJPEG_ENCODER_PLUGIN" : "ON", "WITH_FFMPEG_DECODER" : "ON", - "WITH_FFMPEG_DECODER_PLUGIN" : "ON" + "WITH_FFMPEG_DECODER_PLUGIN" : "ON", } }, { @@ -63,6 +63,7 @@ "WITH_KVAZAAR" : "ON", "WITH_OpenJPEG_DECODER" : "ON", "WITH_OpenJPEG_ENCODER" : "ON", + "WITH_OPENJPH_ENCODER" : "ON", "WITH_FFMPEG_DECODER" : "ON", "WITH_REDUCED_VISIBILITY" : "OFF", "WITH_DEFLATE_HEADER_COMPRESSION" : "ON", diff --git a/README.md b/README.md index faf4d9f387..e5252a8985 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ For each codec, there are two configuration variables: * `WITH_{codec}_PLUGIN`: when enabled, the codec is compiled as a separate plugin. In order to use dynamic plugins, also make sure that `ENABLE_PLUGIN_LOADING` is enabled. -The placeholder `{codec}` can have these values: `LIBDE265`, `X265`, `AOM_DECODER`, `AOM_ENCODER`, `SvtEnc`, `DAV1D`, `FFMPEG_DECODER`, `JPEG_DECODER`, `JPEG_ENCODER`, `KVAZAAR`, `OpenJPEG_DECODER`, `OpenJPEG_ENCODER`. +The placeholder `{codec}` can have these values: `LIBDE265`, `X265`, `AOM_DECODER`, `AOM_ENCODER`, `SvtEnc`, `DAV1D`, `FFMPEG_DECODER`, `JPEG_DECODER`, `JPEG_ENCODER`, `KVAZAAR`, `OpenJPEG_DECODER`, `OpenJPEG_ENCODER`, `OPENJPH_ENCODER` Further options are: diff --git a/cmake/modules/FindOPENJPH.cmake b/cmake/modules/FindOPENJPH.cmake new file mode 100644 index 0000000000..443f9d6f54 --- /dev/null +++ b/cmake/modules/FindOPENJPH.cmake @@ -0,0 +1,24 @@ +include(LibFindMacros) +libfind_pkg_check_modules(OPENJPH_PKGCONF openjph) + +find_path(OPENJPH_INCLUDE_DIR + NAMES openjph/ojph_version.h + HINTS ${OPENJPH_PKGCONF_INCLUDE_DIRS} ${OPENJPH_PKGCONF_INCLUDEDIR} + PATH_SUFFIXES OPENJPH +) + +find_library(OPENJPH_LIBRARY + NAMES libopenjph openjph + HINTS ${OPENJPH_PKGCONF_LIBRARY_DIRS} ${OPENJPH_PKGCONF_LIBDIR} +) + +set(OPENJPH_PROCESS_LIBS OPENJPH_LIBRARY) +set(OPENJPH_PROCESS_INCLUDES OPENJPH_INCLUDE_DIR) +libfind_process(OPENJPH) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(OPENJPH + REQUIRED_VARS + OPENJPH_INCLUDE_DIR + OPENJPH_LIBRARY +) diff --git a/examples/heif_convert.cc b/examples/heif_convert.cc index 74183d1d68..4b690aa86e 100644 --- a/examples/heif_convert.cc +++ b/examples/heif_convert.cc @@ -175,6 +175,9 @@ void list_all_decoders() std::cout << "JPEG 2000 decoders:\n"; list_decoders(heif_compression_JPEG2000); + std::cout << "HT-J2K decoders:\n"; + list_decoders(heif_compression_HTJ2K); + #if WITH_UNCOMPRESSED_CODEC std::cout << "uncompressed: yes\n"; #else diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index 3798961a45..8704d90e58 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -95,6 +95,7 @@ const int OPTION_PITM_DESCRIPTION = 1005; const int OPTION_USE_JPEG_COMPRESSION = 1006; const int OPTION_USE_JPEG2000_COMPRESSION = 1007; const int OPTION_VERBOSE = 1008; +const int OPTION_USE_HTJ2K_COMPRESSION = 1009; static struct option long_options[] = { @@ -115,6 +116,7 @@ static struct option long_options[] = { {(char* const) "avif", no_argument, 0, 'A'}, {(char* const) "jpeg", no_argument, 0, OPTION_USE_JPEG_COMPRESSION}, {(char* const) "jpeg2000", no_argument, 0, OPTION_USE_JPEG2000_COMPRESSION}, + {(char* const) "htj2k", no_argument, 0, OPTION_USE_HTJ2K_COMPRESSION}, #if WITH_UNCOMPRESSED_CODEC {(char* const) "uncompressed", no_argument, 0, 'U'}, #endif @@ -162,6 +164,7 @@ void show_help(const char* argv0) << " -A, --avif encode as AVIF (not needed if output filename with .avif suffix is provided)\n" << " --jpeg encode as JPEG\n" << " --jpeg2000 encode as JPEG 2000 (experimental)\n" + << " --htj2k encode as High Throughput JPEG 2000 (experimental)\n" #if WITH_UNCOMPRESSED_CODEC << " -U, --uncompressed encode as uncompressed image (according to ISO 23001-17) (EXPERIMENTAL)\n" #endif @@ -359,6 +362,9 @@ static const char* get_compression_format_name(heif_compression_format format) case heif_compression_JPEG2000: return "JPEG 2000"; break; + case heif_compression_HTJ2K: + return "HT-J2K"; + break; case heif_compression_uncompressed: return "Uncompressed"; break; @@ -370,7 +376,7 @@ static const char* get_compression_format_name(heif_compression_format format) static void show_list_of_all_encoders() { - for (auto compression_format : {heif_compression_HEVC, heif_compression_AV1, heif_compression_JPEG, heif_compression_JPEG2000 + for (auto compression_format : {heif_compression_HEVC, heif_compression_AV1, heif_compression_JPEG, heif_compression_JPEG2000, heif_compression_HTJ2K #if WITH_UNCOMPRESSED_CODEC , heif_compression_uncompressed #endif @@ -389,6 +395,9 @@ static void show_list_of_all_encoders() case heif_compression_JPEG2000: std::cout << "JPEG 2000"; break; + case heif_compression_HTJ2K: + std::cout << "HT-J2K"; + break; case heif_compression_uncompressed: std::cout << "Uncompressed"; break; @@ -477,6 +486,7 @@ int main(int argc, char** argv) bool force_enc_uncompressed = false; bool force_enc_jpeg = false; bool force_enc_jpeg2000 = false; + bool force_enc_htj2k = false; bool crop_to_even_size = false; std::vector raw_params; @@ -558,6 +568,9 @@ int main(int argc, char** argv) case OPTION_USE_JPEG2000_COMPRESSION: force_enc_jpeg2000 = true; break; + case OPTION_USE_HTJ2K_COMPRESSION: + force_enc_htj2k = true; + break; case OPTION_PLUGIN_DIRECTORY: { int nPlugins; heif_error error = heif_load_plugins(optarg, nullptr, &nPlugins, 0); @@ -653,6 +666,9 @@ int main(int argc, char** argv) else if (force_enc_jpeg2000) { compressionFormat = heif_compression_JPEG2000; } + else if (force_enc_htj2k) { + compressionFormat = heif_compression_HTJ2K; + } else { compressionFormat = guess_compression_format_from_filename(output_filename); } diff --git a/go/heif/heif.go b/go/heif/heif.go index 2015ffa8f4..ad2fa5b641 100644 --- a/go/heif/heif.go +++ b/go/heif/heif.go @@ -55,6 +55,7 @@ const ( CompressionEVC = C.heif_compression_EVC CompressionUncompressed = C.heif_compression_uncompressed CompressionMask = C.heif_compression_mask + CompressionHTJ2K = C.heif_compression_HTJ2K ) type Chroma C.enum_heif_chroma diff --git a/libheif/context.cc b/libheif/context.cc index fdbe07d0ec..6b6866d6b4 100644 --- a/libheif/context.cc +++ b/libheif/context.cc @@ -2272,7 +2272,8 @@ Error HeifContext::encode_image(const std::shared_ptr& pixel_ima out_image); } break; - case heif_compression_JPEG2000: { + case heif_compression_JPEG2000: + case heif_compression_HTJ2K: { error = encode_image_as_jpeg2000(pixel_image, encoder, options, diff --git a/libheif/file.cc b/libheif/file.cc index a65c7df5ef..51786da4eb 100644 --- a/libheif/file.cc +++ b/libheif/file.cc @@ -185,6 +185,7 @@ void HeifFile::set_brand(heif_compression_format format, bool miaf_compatible) break; case heif_compression_JPEG2000: + case heif_compression_HTJ2K: m_ftyp_box->set_major_brand(fourcc("j2ki")); m_ftyp_box->set_minor_version(0); m_ftyp_box->add_compatible_brand(fourcc("mif1")); diff --git a/libheif/heif.cc b/libheif/heif.cc index c35e803788..cff1bb7d70 100644 --- a/libheif/heif.cc +++ b/libheif/heif.cc @@ -2026,7 +2026,7 @@ int heif_get_decoder_descriptors(enum heif_compression_format format_filter, std::vector plugins; std::vector formats; if (format_filter == heif_compression_undefined) { - formats = {heif_compression_HEVC, heif_compression_AV1, heif_compression_JPEG, heif_compression_JPEG2000, heif_compression_VVC}; + formats = {heif_compression_HEVC, heif_compression_AV1, heif_compression_JPEG, heif_compression_JPEG2000, heif_compression_HTJ2K, heif_compression_VVC}; } else { formats.emplace_back(format_filter); diff --git a/libheif/heif.h b/libheif/heif.h index 3e79cf60d1..1c3eb7089d 100644 --- a/libheif/heif.h +++ b/libheif/heif.h @@ -412,7 +412,14 @@ enum heif_compression_format * * See ISO/IEC 23008-12:2022 Section 6.10.2 */ - heif_compression_mask = 9 + heif_compression_mask = 9, + /** + * High Throughput JPEG 2000 (HT-J2K) compression. + * + * The encapsulation of HT-J2K is specified in ISO/IEC 15444-16:2021. + * The core encoding is defined in ISO/IEC 15444-15, or ITU-T T.814. + */ + heif_compression_HTJ2K = 10 }; enum heif_chroma diff --git a/libheif/heif_emscripten.h b/libheif/heif_emscripten.h index 52e85a6373..fe79be19f6 100644 --- a/libheif/heif_emscripten.h +++ b/libheif/heif_emscripten.h @@ -375,7 +375,8 @@ EMSCRIPTEN_BINDINGS(libheif) { .value("heif_compression_EVC", heif_compression_EVC) .value("heif_compression_JPEG2000", heif_compression_JPEG2000) .value("heif_compression_uncompressed", heif_compression_uncompressed) - .value("heif_compression_mask", heif_compression_mask); + .value("heif_compression_mask", heif_compression_mask) + .value("heif_compression_HTJ2K", heif_compression_HTJ2K); emscripten::enum_("heif_chroma") .value("heif_chroma_undefined", heif_chroma_undefined) .value("heif_chroma_monochrome", heif_chroma_monochrome) diff --git a/libheif/plugin_registry.cc b/libheif/plugin_registry.cc index 5786c369a2..f5aaa07e26 100644 --- a/libheif/plugin_registry.cc +++ b/libheif/plugin_registry.cc @@ -84,6 +84,10 @@ #include "libheif/plugins/encoder_mask.h" +#if HAVE_OPENJPH_ENCODER +#include "libheif/plugins/encoder_openjph.h" +#endif + std::set s_decoder_plugins; std::multiset, @@ -171,6 +175,10 @@ void register_default_plugins() register_decoder(get_decoder_plugin_openjpeg()); #endif +#if HAVE_OPENJPH_ENCODER + register_encoder(get_encoder_plugin_openjph()); +#endif + #if WITH_UNCOMPRESSED_CODEC register_encoder(get_encoder_plugin_uncompressed()); #endif diff --git a/libheif/plugins/CMakeLists.txt b/libheif/plugins/CMakeLists.txt index ad2f074876..812b87df52 100644 --- a/libheif/plugins/CMakeLists.txt +++ b/libheif/plugins/CMakeLists.txt @@ -91,6 +91,10 @@ set(FFMPEG_DECODER_sources decoder_ffmpeg.cc decoder_ffmpeg.h) set(FFMPEG_DECODER_extra_plugin_sources ../error.cc) plugin_compilation(ffmpegdec FFMPEG FFMPEG_FOUND FFMPEG_DECODER FFMPEG_DECODER) +set(OPENJPH_ENCODER_sources encoder_openjph.cc encoder_openjph.h) +set(OPENJPH_ENCODER_extra_plugin_sources) +plugin_compilation(jphenc OPENJPH OPENJPH_FOUND OPENJPH_ENCODER OPENJPH_ENCODER) + target_sources(heif PRIVATE encoder_mask.h encoder_mask.cc) diff --git a/libheif/plugins/encoder_openjph.cc b/libheif/plugins/encoder_openjph.cc new file mode 100644 index 0000000000..9a500739fc --- /dev/null +++ b/libheif/plugins/encoder_openjph.cc @@ -0,0 +1,864 @@ +/* + * OpenJPH codec. + * Copyright (c) 2023 Devon Sookhoo + * Copyright (c) 2023 Dirk Farin + * Copyright (C) 2024 Brad Hards + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +// Portions of this code adapted from OpenJPH's ojph_compress.cpp +// which is under the following license: +//***************************************************************************/ +// This software is released under the 2-Clause BSD license, included +// below. +// +// Copyright (c) 2019, Aous Naman +// Copyright (c) 2019, Kakadu Software Pty Ltd, Australia +// Copyright (c) 2019, The University of New South Wales, Australia +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//***************************************************************************/ + + +#include "libheif/heif.h" +#include "libheif/heif_plugin.h" +#include "encoder_openjph.h" + +#include "openjph/ojph_mem.h" +#include "openjph/ojph_defs.h" +#include "openjph/ojph_file.h" +#include "openjph/ojph_codestream.h" +#include "openjph/ojph_params.h" +#include "openjph/ojph_version.h" + +#include + +#include +#include +#include +#include +#include + +#include + +static const int OJPH_PLUGIN_PRIORITY = 80; + + +struct encoder_struct_ojph +{ + // We do this for API reasons. Has no effect at this stage. + int quality = 70; + heif_chroma chroma = heif_chroma_undefined; + + // Context + ojph::codestream codestream; + std::string comment; + + // --- output + bool data_read = false; + ojph::mem_outfile outfile; +}; + + +#define MAX_NPARAMETERS 10 +static struct heif_encoder_parameter ojph_encoder_params[MAX_NPARAMETERS]; +const static struct heif_encoder_parameter* ojph_encoder_parameter_ptrs[MAX_NPARAMETERS + 1]; + +static const char* kParam_chroma = "chroma"; +static const char* const kParam_chroma_valid_values[] = { + "420", "422", "444", nullptr +}; + +static const char* kParam_num_decompositions = "num_decompositions"; +static const int NUM_DECOMPOSITIONS_MIN = 0; +static const int NUM_DECOMPOSITIONS_MAX = 32; + +static const char* kParam_progression_order = "progression_order"; +static const char* const kParam_progression_order_valid_values[] = { + "LRCP", "RLCP", "RPCL", "PCRL", "CPRL", nullptr +}; + +static const char* kParam_tlm_marker = "tlm_marker"; + +static const char* kParam_codestream_comment = "codestream_comment"; + +static const char* kParam_tile_size = "tile_size"; + +static const char* kParam_tilepart_division = "tilepart_division"; +static const char* const kParam_tilepart_division_valid_values[] = { + "none", "resolution", "component", "both", nullptr +}; + +static const char* kParam_block_dimensions = "block_dimensions"; + +static void ojph_init_encoder_parameters() +{ + struct heif_encoder_parameter* p = ojph_encoder_params; + const struct heif_encoder_parameter** d = ojph_encoder_parameter_ptrs; + int i = 0; + + assert(i < MAX_NPARAMETERS); + p->version = 2; + p->name = heif_encoder_parameter_name_lossless; + p->type = heif_encoder_parameter_type_boolean; + p->boolean.default_value = false; + p->has_default = true; + d[i++] = p++; + + assert(i < MAX_NPARAMETERS); + p->version = 2; + p->name = kParam_chroma; + p->type = heif_encoder_parameter_type_string; + p->string.default_value = "444"; + p->has_default = true; + p->string.valid_values = kParam_chroma_valid_values; + d[i++] = p++; + + assert(i < MAX_NPARAMETERS); + p->version = 2; + p->name = kParam_num_decompositions; + p->type = heif_encoder_parameter_type_integer; + p->integer.default_value = 5; + p->integer.have_minimum_maximum = true; + p->integer.minimum = NUM_DECOMPOSITIONS_MIN; + p->integer.maximum = NUM_DECOMPOSITIONS_MAX; + p->integer.valid_values = NULL; + p->integer.num_valid_values = 0; + p->has_default = true; + d[i++] = p++; + + assert(i < MAX_NPARAMETERS); + p->version = 2; + p->name = kParam_progression_order; + p->type = heif_encoder_parameter_type_string; + p->string.default_value = "RPCL"; + p->has_default = true; + p->string.valid_values = kParam_progression_order_valid_values; + d[i++] = p++; + + assert(i < MAX_NPARAMETERS); + p->version = 2; + p->name = kParam_tlm_marker; + p->type = heif_encoder_parameter_type_boolean; + p->boolean.default_value = false; + p->has_default = true; + d[i++] = p++; + + assert(i < MAX_NPARAMETERS); + p->version = 2; + p->name = kParam_codestream_comment; + p->type = heif_encoder_parameter_type_string; + p->string.default_value = nullptr; + p->has_default = false; + p->string.valid_values = nullptr; + d[i++] = p++; + + assert(i < MAX_NPARAMETERS); + p->version = 2; + p->name = kParam_tile_size; + p->type = heif_encoder_parameter_type_string; + p->string.default_value = "0,0"; + p->has_default = true; + p->string.valid_values = nullptr; + d[i++] = p++; + + assert(i < MAX_NPARAMETERS); + p->version = 2; + p->name = kParam_tilepart_division; + p->type = heif_encoder_parameter_type_string; + p->string.default_value = "none"; + p->has_default = true; + p->string.valid_values = kParam_tilepart_division_valid_values; + d[i++] = p++; + + assert(i < MAX_NPARAMETERS); + p->version = 2; + p->name = kParam_block_dimensions; + p->type = heif_encoder_parameter_type_string; + p->string.default_value = "64,64"; + p->has_default = true; + p->string.valid_values = nullptr; + d[i++] = p++; + + d[i++] = nullptr; +} + + +void ojph_init_plugin() +{ + ojph_init_encoder_parameters(); +} + +void ojph_cleanup_plugin() +{ +} + + +//////////// Integer parameter setters + +// Note quality is part of the plugin API. + +struct heif_error ojph_set_parameter_quality(void* encoder_raw, int quality) +{ + auto* encoder = (struct encoder_struct_ojph*) encoder_raw; + + encoder->quality = quality; + + return heif_error_ok; +} + +static const heif_error &ojph_set_num_decompositions(int value, encoder_struct_ojph *encoder) +{ + if ((value < NUM_DECOMPOSITIONS_MIN) || (value > NUM_DECOMPOSITIONS_MAX)) { + return heif_error_invalid_parameter_value; + } + encoder->codestream.access_cod().set_num_decomposition(value); + return heif_error_ok; +} + +struct heif_error ojph_set_parameter_integer(void *encoder_raw, const char *name, int value) +{ + struct encoder_struct_ojph* encoder = (struct encoder_struct_ojph*) encoder_raw; + + if (strcmp(name, heif_encoder_parameter_name_quality) == 0) { + return ojph_set_parameter_quality(encoder, value); + } else if (strcmp(name, kParam_num_decompositions) == 0) { + return ojph_set_num_decompositions(value, encoder); + } else { + return heif_error_unsupported_parameter; + } +} + + +//////////// Integer parameter getters + +// Note quality is part of the plugin API + +struct heif_error ojph_get_parameter_quality(void* encoder_raw, int* quality) +{ + auto* encoder = (struct encoder_struct_ojph*) encoder_raw; + + *quality = encoder->quality; + + return heif_error_ok; +} + +const heif_error &ojph_get_parameter_num_decompositions(encoder_struct_ojph *encoder, int *value) +{ + *value = encoder->codestream.access_cod().get_num_decompositions(); + return heif_error_ok; +} + +struct heif_error ojph_get_parameter_integer(void *encoder_raw, const char *name, int *value) +{ + struct encoder_struct_ojph* encoder = (struct encoder_struct_ojph*) encoder_raw; + + if (strcmp(name, heif_encoder_parameter_name_quality) == 0) { + return ojph_get_parameter_quality(encoder, value); + } else if (strcmp(name, kParam_num_decompositions) == 0) { + return ojph_get_parameter_num_decompositions(encoder, value); + } else { + return heif_error_unsupported_parameter; + } +} + + +//////////// Boolean parameter setters + +// Note lossless is part of the plugin API + +struct heif_error ojph_set_parameter_lossless(void* encoder_raw, int lossless) +{ + auto* encoder = (struct encoder_struct_ojph*) encoder_raw; + encoder->codestream.access_cod().set_reversible(lossless); + return heif_error_ok; +} + +const heif_error &ojph_set_tlm_marker_requested(encoder_struct_ojph *encoder, int value) +{ + encoder->codestream.request_tlm_marker(value); + return heif_error_ok; +} + +struct heif_error ojph_set_parameter_boolean(void *encoder_raw, const char *name, int value) +{ + auto* encoder = (struct encoder_struct_ojph*) encoder_raw; + if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) { + return ojph_set_parameter_lossless(encoder, value); + } else if (strcmp(name, kParam_tlm_marker) == 0) { + return ojph_set_tlm_marker_requested(encoder, value); + } + return heif_error_unsupported_parameter; +} + +//////////// Boolean parameter getters + +// Note lossless is part of the plugin API + +struct heif_error ojph_get_parameter_lossless(void* encoder_raw, int* lossless) +{ + auto* encoder = (struct encoder_struct_ojph*) encoder_raw; + *lossless = encoder->codestream.access_cod().is_reversible(); + return heif_error_ok; +} + +const heif_error &ojph_get_parameter_tlm_marker(encoder_struct_ojph *encoder, int *value) +{ + *value = encoder->codestream.is_tlm_requested(); + return heif_error_ok; +} + +struct heif_error ojph_get_parameter_boolean(void *encoder_raw, const char *name, int *value) +{ + auto* encoder = (struct encoder_struct_ojph*) encoder_raw; + if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) { + return ojph_get_parameter_lossless(encoder, value); + } else if (strcmp(name, kParam_tlm_marker) == 0) { + return ojph_get_parameter_tlm_marker(encoder, value); + } else { + return heif_error_unsupported_parameter; + } +} + + +//////////// String parameter getters + +static void safe_strcpy(char* dst, int dst_size, const char* src) +{ + strncpy(dst, src, dst_size - 1); + dst[dst_size - 1] = 0; +} + +const heif_error &ojph_get_parameter_chroma(encoder_struct_ojph *encoder, char *value, int value_size) +{ + switch (encoder->chroma) { + case heif_chroma_420: + safe_strcpy(value, value_size, "420"); + break; + case heif_chroma_422: + safe_strcpy(value, value_size, "422"); + break; + case heif_chroma_444: + safe_strcpy(value, value_size, "444"); + break; + case heif_chroma_undefined: + safe_strcpy(value, value_size, "undefined"); + break; + default: + assert(false); + return heif_error_invalid_parameter_value; + } + return heif_error_ok; +} + +const heif_error &ojph_get_parameter_progression_order(encoder_struct_ojph *encoder, char *value, int value_size) +{ + safe_strcpy(value, value_size, encoder->codestream.access_cod().get_progression_order_as_string()); + return heif_error_ok; +} + +const heif_error &ojph_get_parameter_codestream_comment(encoder_struct_ojph *encoder, char *value, int value_size) +{ + safe_strcpy(value, value_size, encoder->comment.c_str()); + return heif_error_ok; +} + +const heif_error &ojph_get_parameter_tile_size(encoder_struct_ojph *encoder, char *value, int value_size) +{ + ojph::size tile_size = encoder->codestream.access_siz().get_tile_size(); + std::stringstream stringStream; + stringStream << tile_size.w << "," << tile_size.h; + safe_strcpy(value, value_size, stringStream.str().c_str()); + return heif_error_ok; +} + +const heif_error &ojph_get_parameter_tilepart_division(encoder_struct_ojph *encoder, char *value, int value_size) +{ + bool res = encoder->codestream.is_tilepart_division_at_resolutions(); + bool comp = encoder->codestream.is_tilepart_division_at_components(); + if (res && comp) { + safe_strcpy(value, value_size, "both"); + } else if (res) { + safe_strcpy(value, value_size, "resolution"); + } else if (comp) { + safe_strcpy(value, value_size, "component"); + } else { + safe_strcpy(value, value_size, "none"); + } + return heif_error_ok; +} + +const heif_error &ojph_get_parameter_block_dimensions(encoder_struct_ojph *encoder, char *value, int value_size) +{ + ojph::size block_dims = encoder->codestream.access_cod().get_block_dims(); + std::stringstream stringStream; + stringStream << block_dims.w << "," << block_dims.h; + safe_strcpy(value, value_size, stringStream.str().c_str()); + return heif_error_ok; +} + +struct heif_error ojph_get_parameter_string(void *encoder_raw, const char *name, char *value, int value_size) +{ + struct encoder_struct_ojph* encoder = (struct encoder_struct_ojph*) encoder_raw; + + if (strcmp(name, kParam_chroma) == 0) { + return ojph_get_parameter_chroma(encoder, value, value_size); + } else if (strcmp(name, kParam_progression_order) == 0) { + return ojph_get_parameter_progression_order(encoder, value, value_size); + } else if (strcmp(name, kParam_codestream_comment) == 0) { + return ojph_get_parameter_codestream_comment(encoder, value, value_size); + } else if (strcmp(name, kParam_tile_size) == 0) { + return ojph_get_parameter_tile_size(encoder, value, value_size); + } else if (strcmp(name, kParam_tilepart_division) == 0) { + return ojph_get_parameter_tilepart_division(encoder, value, value_size); + } else if (strcmp(name, kParam_block_dimensions) == 0) { + return ojph_get_parameter_block_dimensions(encoder, value, value_size); + } else { + return heif_error_unsupported_parameter; + } +} + +//////////// String parameter setters + +static const heif_error &ojph_set_chroma(encoder_struct_ojph *encoder, const char *value) +{ + if (strcmp(value, "420") == 0) { + encoder->chroma = heif_chroma_420; + return heif_error_ok; + } else if (strcmp(value, "422") == 0) { + encoder->chroma = heif_chroma_422; + return heif_error_ok; + } else if (strcmp(value, "444") == 0) { + encoder->chroma = heif_chroma_444; + return heif_error_ok; + } else { + return heif_error_invalid_parameter_value; + } +} + +static bool string_list_contains(const char* const* values_list, const char* value) +{ + for (int i = 0; values_list[i]; i++) { + if (strcmp(values_list[i], value) == 0) { + return true; + } + } + + return false; +} + +static const heif_error &ojph_set_progression_order(encoder_struct_ojph *encoder, const char *value) +{ + if (string_list_contains(kParam_progression_order_valid_values, value)) { + encoder->codestream.access_cod().set_progression_order(value); + return heif_error_ok; + } else { + return heif_error_invalid_parameter_value; + } +} + +static const heif_error &ojph_set_codestream_comment(encoder_struct_ojph *encoder, const char *value) +{ + if (value != nullptr) { + encoder->comment = std::string(value); + } + return heif_error_ok; +} + +static const heif_error &ojph_set_tile_size(encoder_struct_ojph *encoder, const char *value) +{ + std::string valueStr(value); + size_t commaOffset = valueStr.find(","); + if (commaOffset == std::string::npos) { + return heif_error_invalid_parameter_value; + } + std::string xTSizText = valueStr.substr(0, commaOffset); + unsigned long xTSiz = std::stoul(xTSizText); + std::string yTSizText = valueStr.substr(commaOffset + 1); + unsigned long yTSiz = std::stoul(yTSizText); + if ((xTSiz < 1) + || (xTSiz > std::numeric_limits::max()) + || (yTSiz < 1) + || (yTSiz > std::numeric_limits::max())) { + return heif_error_invalid_parameter_value; + } + encoder->codestream.access_siz().set_tile_size(ojph::size((ojph::ui32)xTSiz, (ojph::ui32)yTSiz)); + return heif_error_ok; +} + +static const heif_error &ojph_set_tilepart_division(encoder_struct_ojph *encoder, const char *value) +{ + if (strcmp(value, "none") == 0) { + encoder->codestream.set_tilepart_divisions(false, false); + return heif_error_ok; + } else if (strcmp(value, "resolution") == 0) { + encoder->codestream.set_tilepart_divisions(true, false); + return heif_error_ok; + } else if (strcmp(value, "component") == 0) { + encoder->codestream.set_tilepart_divisions(false, true); + return heif_error_ok; + } else if (strcmp(value, "both") == 0) { + encoder->codestream.set_tilepart_divisions(true, true); + return heif_error_ok; + } else { + return heif_error_invalid_parameter_value; + } +} + +// Get the base 2 logarithm for code block sizes. See ITU-T T.800 (11/2015) Table A.18 +// Values are encoded 0 to 8 (i.e. its -2) +static const int log_base_2(unsigned long v) +{ + switch (v) { + case 4: + return 2 - 2; + case 8: + return 3 - 2; + case 16: + return 4 - 2; + case 32: + return 5 - 2; + case 64: + return 6 - 2; + case 128: + return 7 - 2; + case 256: + return 8 - 2; + case 512: + return 9 - 2; + case 1024: + return 10 - 2; + default: + // any other value is invalid + return -1; + } +} + +static const heif_error &ojph_set_block_dimensions(encoder_struct_ojph *encoder, const char *value) +{ + std::string valueStr(value); + size_t commaOffset = valueStr.find(","); + if (commaOffset == std::string::npos) { + return heif_error_invalid_parameter_value; + } + std::string widthText = valueStr.substr(0, commaOffset); + unsigned long width = std::stoul(widthText); + std::string heightText = valueStr.substr(commaOffset + 1); + unsigned long height = std::stoul(heightText); + int xcb = log_base_2(width); + int ycb = log_base_2(height); + if ((xcb == -1) || (ycb == -1) || (xcb + ycb > 12)) { + return heif_error_invalid_parameter_value; + } + encoder->codestream.access_cod().set_block_dims((ojph::ui32)width, (ojph::ui32)height); + return heif_error_ok; +} + +struct heif_error ojph_set_parameter_string(void *encoder_raw, const char *name, const char *value) +{ + auto* encoder = (struct encoder_struct_ojph*) encoder_raw; + + if (strcmp(name, kParam_chroma) == 0) { + return ojph_set_chroma(encoder, value); + } else if (strcmp(name, kParam_progression_order) == 0) { + return ojph_set_progression_order(encoder, value); + } else if (strcmp(name, kParam_codestream_comment) == 0) { + return ojph_set_codestream_comment(encoder, value); + } else if (strcmp(name, kParam_tile_size) == 0) { + return ojph_set_tile_size(encoder, value); + } else if (strcmp(name, kParam_tilepart_division) == 0) { + return ojph_set_tilepart_division(encoder, value); + } else if (strcmp(name, kParam_block_dimensions) == 0) { + return ojph_set_block_dimensions(encoder, value); + } else { + return heif_error_unsupported_parameter; + } +} + +static void ojph_set_default_parameters(void* encoder_raw) +{ + struct encoder_struct_ojph* encoder = (struct encoder_struct_ojph*) encoder_raw; + for (const struct heif_encoder_parameter** p = ojph_encoder_parameter_ptrs; *p; p++) { + const struct heif_encoder_parameter* param = *p; + + if (param->has_default) { + switch (param->type) { + case heif_encoder_parameter_type_integer: + ojph_set_parameter_integer(encoder, param->name, param->integer.default_value); + break; + case heif_encoder_parameter_type_boolean: + ojph_set_parameter_boolean(encoder, param->name, param->boolean.default_value); + break; + case heif_encoder_parameter_type_string: + ojph_set_parameter_string(encoder, param->name, param->string.default_value); + break; + } + } + } +} + +///// Actual encoding functionality + +struct heif_error ojph_new_encoder(void** encoder_out) +{ + struct encoder_struct_ojph* encoder = new encoder_struct_ojph(); + encoder->outfile.open(); + *encoder_out = encoder; + + ojph_set_default_parameters(encoder); + + return heif_error_ok; +} + +void ojph_free_encoder(void* encoder_raw) +{ + struct encoder_struct_ojph* encoder = (struct encoder_struct_ojph*) encoder_raw; + encoder->codestream.close(); + delete encoder; +} + +struct heif_error ojph_set_parameter_logging_level(void* encoder, int logging) +{ + // No logging level options in OpenJPH + return heif_error_ok; +} + +struct heif_error ojph_get_parameter_logging_level(void* encoder, int* logging) +{ + // No logging level options in OpenJPH + return heif_error_ok; +} + +const struct heif_encoder_parameter** ojph_list_parameters(void* encoder_raw) +{ + return ojph_encoder_parameter_ptrs; +} + + +void ojph_query_input_colorspace(enum heif_colorspace* inout_colorspace, enum heif_chroma* inout_chroma) +{ + // Replace the input colorspace/chroma with the one that is supported by the encoder and that + // comes as close to the input colorspace/chroma as possible. + + if (*inout_colorspace == heif_colorspace_monochrome) { + *inout_colorspace = heif_colorspace_monochrome; + *inout_chroma = heif_chroma_monochrome; + } + else { + *inout_colorspace = heif_colorspace_YCbCr; + *inout_chroma = heif_chroma_444; + } +} + +void ojph_query_input_colorspace2(void* encoder_raw, enum heif_colorspace* inout_colorspace, enum heif_chroma* inout_chroma) +{ + auto* encoder = (struct encoder_struct_ojph*) encoder_raw; + + if (*inout_colorspace == heif_colorspace_monochrome) { + *inout_colorspace = heif_colorspace_monochrome; + *inout_chroma = heif_chroma_monochrome; + } + else { + *inout_colorspace = heif_colorspace_YCbCr; + + if (encoder->chroma != heif_chroma_undefined) { + *inout_chroma = encoder->chroma; + } + else { + *inout_chroma = heif_chroma_444; + } + } +} + +std::vector build_SIZ(encoder_struct_ojph *encoder, const heif_image *image) +{ + std::vector sourceChannels; + ojph::param_siz siz = encoder->codestream.access_siz(); + int width = heif_image_get_primary_width(image); + int height = heif_image_get_primary_height(image); + siz.set_image_extent(ojph::point(width, height)); + + heif_chroma chroma = heif_image_get_chroma_format(image); + + encoder->codestream.set_planar(true); + sourceChannels = {heif_channel_Y, heif_channel_Cb, heif_channel_Cr}; + siz.set_num_components((ojph::ui32)sourceChannels.size()); + for (ojph::ui32 i = 0; i < siz.get_num_components(); i++) { + int bit_depth = heif_image_get_bits_per_pixel_range(image, sourceChannels[i]); + if (sourceChannels[i] == heif_channel_Y) { + siz.set_component(i, ojph::point(1, 1), bit_depth, false); + } else { // Cb or Cr + if (chroma == heif_chroma_444) { + siz.set_component(i, ojph::point(1, 1), bit_depth, false); + } else if (chroma == heif_chroma_422) { + siz.set_component(i, ojph::point(2, 1), bit_depth, false); + } else { + siz.set_component(i, ojph::point(2, 2), bit_depth, false); + } + } + } + siz.set_image_offset(ojph::point(0, 0)); + siz.set_tile_offset(ojph::point(0, 0)); + return sourceChannels; +} + +void build_COD(encoder_struct_ojph *encoder) +{ + ojph::param_cod cod = encoder->codestream.access_cod(); + cod.set_color_transform(false); +} + +struct heif_error ojph_encode_image(void *encoder_raw, const struct heif_image *image, enum heif_image_input_class image_class) +{ + struct encoder_struct_ojph* encoder = (struct encoder_struct_ojph*) encoder_raw; + + if (heif_image_get_colorspace(image) != heif_colorspace_YCbCr) { + return heif_error{heif_error_Encoding_error, + heif_suberror_Unspecified, + "OpenJPH encoder plugin received image with invalid colorspace."}; + } + + std::vector sourceChannels = build_SIZ(encoder, image); + build_COD(encoder); + bool hasComment = (encoder->comment.length() > 0); + ojph::comment_exchange com_ex; + if (hasComment) { + com_ex.set_string(encoder->comment.c_str()); + } + encoder->codestream.write_headers(&(encoder->outfile), &com_ex, hasComment ? 1 : 0); + + ojph::ui32 next_comp; + ojph::line_buf* cur_line = encoder->codestream.exchange(NULL, next_comp); + + for (const auto& sourceChannel : sourceChannels) { + int stride; + const uint8_t *data = heif_image_get_plane_readonly(image, sourceChannel, &stride); + uint32_t component_height = heif_image_get_height(image, sourceChannel); + for (uint32_t y = 0; y < component_height; y++) { + const uint8_t *sourceLine = data + y * stride; + size_t outputWidth = cur_line->size; + ojph::si32 *targetLine = cur_line->i32; + for (uint32_t x = 0; x < outputWidth; x++) { + targetLine[x] = sourceLine[x]; + } + cur_line = encoder->codestream.exchange(cur_line, next_comp); + } + } + encoder->codestream.flush(); + + return heif_error_ok; +} + +struct heif_error ojph_get_compressed_data(void* encoder_raw, uint8_t** data, int* size, enum heif_encoded_data_type* type) +{ + struct encoder_struct_ojph* encoder = (struct encoder_struct_ojph*) encoder_raw; + + if (encoder->data_read) { + *size = 0; + *data = nullptr; + } + else { + *size = (int) encoder->outfile.tell(); + *data = (uint8_t*) encoder->outfile.get_data(); + encoder->data_read = true; + } + return heif_error_ok; +} + + +static const int MAX_PLUGIN_NAME_LENGTH = 80; +static char plugin_name[MAX_PLUGIN_NAME_LENGTH]; + +const char* ojph_plugin_name() +{ + snprintf(plugin_name, MAX_PLUGIN_NAME_LENGTH, + "OpenJPH %s.%s.%s", + OJPH_INT_TO_STRING(OPENJPH_VERSION_MAJOR), + OJPH_INT_TO_STRING(OPENJPH_VERSION_MINOR), + OJPH_INT_TO_STRING(OPENJPH_VERSION_PATCH) + ); + plugin_name[MAX_PLUGIN_NAME_LENGTH - 1] = 0; + + return plugin_name; +} + +static const struct heif_encoder_plugin encoder_plugin_openjph { + /* plugin_api_version */ 3, + /* compression_format */ heif_compression_HTJ2K, + /* id_name */ "openjph", + /* priority */ OJPH_PLUGIN_PRIORITY, + /* supports_lossy_compression */ true, + /* supports_lossless_compression */ true, + /* get_plugin_name */ ojph_plugin_name, + /* init_plugin */ ojph_init_plugin, + /* cleanup_plugin */ ojph_cleanup_plugin, + /* new_encoder */ ojph_new_encoder, + /* free_encoder */ ojph_free_encoder, + /* set_parameter_quality */ ojph_set_parameter_quality, + /* get_parameter_quality */ ojph_get_parameter_quality, + /* set_parameter_lossless */ ojph_set_parameter_lossless, + /* get_parameter_lossless */ ojph_get_parameter_lossless, + /* set_parameter_logging_level */ ojph_set_parameter_logging_level, + /* get_parameter_logging_level */ ojph_get_parameter_logging_level, + /* list_parameters */ ojph_list_parameters, + /* set_parameter_integer */ ojph_set_parameter_integer, + /* get_parameter_integer */ ojph_get_parameter_integer, + /* set_parameter_boolean */ ojph_set_parameter_boolean, + /* get_parameter_boolean */ ojph_get_parameter_boolean, + /* set_parameter_string */ ojph_set_parameter_string, + /* get_parameter_string */ ojph_get_parameter_string, + /* query_input_colorspace */ ojph_query_input_colorspace, + /* encode_image */ ojph_encode_image, + /* get_compressed_data */ ojph_get_compressed_data, + /* query_input_colorspace (v2) */ ojph_query_input_colorspace2, + /* query_encoded_size (v3) */ nullptr +}; + +const struct heif_encoder_plugin* get_encoder_plugin_openjph() +{ + return &encoder_plugin_openjph; +} + + +#if PLUGIN_OPENJPH_ENCODER +heif_plugin_info plugin_info { + 1, + heif_plugin_type_encoder, + &encoder_plugin_openjph +}; +#endif diff --git a/libheif/plugins/encoder_openjph.h b/libheif/plugins/encoder_openjph.h new file mode 100644 index 0000000000..1b8002834b --- /dev/null +++ b/libheif/plugins/encoder_openjph.h @@ -0,0 +1,37 @@ +/* + * OpenJPH codec. + * Copyright (c) 2023 Devon Sookhoo + * Copyright (c) 2023 Dirk Farin + * Copyright (C) 2024 Brad Hards + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +#ifndef LIBHEIF_ENCODER_OPENJPH_H +#define LIBHEIF_ENCODER_OPENJPH_H + +#include "libheif/common_utils.h" + + +const struct heif_encoder_plugin* get_encoder_plugin_openjph(); + +#if PLUGIN_OPENJPH_ENCODER +extern "C" { +MAYBE_UNUSED LIBHEIF_API extern heif_plugin_info plugin_info; +} +#endif + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 62fcf79954..6d619b7070 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,6 +30,12 @@ endif() add_libheif_test(encode) add_libheif_test(region) +if (WITH_OPENJPH_ENCODER) + add_libheif_test(encode_htj2k) +else() + message(INFO "Disabling HT-JPEG 2000 encoder tests because no HT-JPEG 2000 codec is enabled") +endif() + if (WITH_OpenJPEG_ENCODER) add_libheif_test(encode_jpeg2000) else() diff --git a/tests/encode_htj2k.cc b/tests/encode_htj2k.cc new file mode 100644 index 0000000000..9214c7031e --- /dev/null +++ b/tests/encode_htj2k.cc @@ -0,0 +1,85 @@ +/* + libheif unit tests + + MIT License + + Copyright (c) 2023 Brad Hards + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include "catch.hpp" +#include "libheif/heif.h" +#include "libheif/pixelimage.h" +#include "libheif/api_structs.h" + +#include "test_utils.h" + +#include + +static heif_encoding_options * get_encoding_options() +{ + heif_encoding_options * options = heif_encoding_options_alloc(); + options->macOS_compatibility_workaround = false; + options->macOS_compatibility_workaround_no_nclx_profile = true; + options->image_orientation = heif_orientation_normal; + return options; +} + +static void do_encode(heif_image* input_image, const char* filename, bool lossless) +{ + REQUIRE(input_image != nullptr); + + heif_context *ctx = heif_context_alloc(); + heif_encoder *encoder; + struct heif_error err; + err = heif_context_get_encoder_for_format(ctx, heif_compression_HTJ2K, &encoder); + REQUIRE(err.code == heif_error_Ok); + + err = heif_encoder_set_lossless(encoder, lossless); + REQUIRE(err.code == heif_error_Ok); + + struct heif_encoding_options *options = get_encoding_options(); + + heif_image_handle *output_image_handle; + + err = heif_context_encode_image(ctx, input_image, encoder, options, &output_image_handle); + REQUIRE(err.code == heif_error_Ok); + err = heif_context_write_to_file(ctx, filename); + REQUIRE(err.code == heif_error_Ok); + + heif_image_handle_release(output_image_handle); + heif_encoding_options_free(options); + heif_encoder_release(encoder); + heif_image_release(input_image); + + heif_context_free(ctx); +} + +TEST_CASE("Encode High Throughput JPEG2000 lossy") +{ + heif_image *input_image = createImage_RGB_planar(); + do_encode(input_image, "encode_htj2k_rgb_lossy.heif", false); +} + +TEST_CASE("Encode High Throughput JPEG2000 lossless") +{ + heif_image *input_image = createImage_RGB_planar(); + do_encode(input_image, "encode_htj2k_rgb_lossless.heif", true); +}