From 407992ea52129c183de8f5a1637a0a1e7174bda3 Mon Sep 17 00:00:00 2001 From: Brad Hards Date: Mon, 22 Jan 2024 22:16:26 +1100 Subject: [PATCH] wip: openjph HT decoder --- CMakeLists.txt | 4 +- libheif/context.cc | 21 ++- libheif/plugin_registry.cc | 8 + libheif/plugins/CMakeLists.txt | 4 + libheif/plugins/decoder_openjpeg.cc | 2 +- libheif/plugins/decoder_openjph.cc | 238 ++++++++++++++++++++++++++++ libheif/plugins/decoder_openjph.h | 35 ++++ 7 files changed, 303 insertions(+), 9 deletions(-) create mode 100644 libheif/plugins/decoder_openjph.cc create mode 100644 libheif/plugins/decoder_openjph.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b56dabad04..073a3634d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -188,7 +188,7 @@ endif () # openjph plugin_option(OPENJPH_ENCODER "OpenJPH HT-J2K encoder" OFF ON) -# plugin_option(OPENJPH_DECODER "OpenJPH HT-J2K decoder" OFF ON) +plugin_option(OPENJPH_DECODER "OpenJPH HT-J2K decoder" OFF ON) if (WITH_OPENJPH_ENCODER OR WITH_OPENJPH_DECODER) find_package(OPENJPH) endif() @@ -214,7 +214,7 @@ 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_DECODER OPENJPH "OpenJPH HT-J2K decoder") plugin_compilation_info(OPENJPH_ENCODER OPENJPH "OpenJPH HT-J2K encoder") # --- show summary which formats are supported diff --git a/libheif/context.cc b/libheif/context.cc index 9c2abc7787..1b4e3fc135 100644 --- a/libheif/context.cc +++ b/libheif/context.cc @@ -1314,6 +1314,12 @@ Error HeifContext::decode_image_planar(heif_item_id ID, image_type == "jpeg" || (image_type == "mime" && m_heif_file->get_content_type(ID) == "image/jpeg")) { + std::vector data; + error = m_heif_file->get_compressed_image_data(ID, &data); + if (error) { + return error; + } + heif_compression_format compression = heif_compression_undefined; if (image_type == "hvc1") { compression = heif_compression_HEVC; @@ -1326,7 +1332,14 @@ Error HeifContext::decode_image_planar(heif_item_id ID, compression = heif_compression_JPEG; } else if (image_type == "j2k1") { - compression = heif_compression_JPEG2000; + JPEG2000MainHeader j2k_header; + j2k_header.setHeaderData(data); + j2k_header.doParse(); + if (j2k_header.hasHighThroughputExtension()) { + compression = heif_compression_HTJ2K; + } else { + compression = heif_compression_JPEG2000; + } } const struct heif_decoder_plugin* decoder_plugin = get_decoder(compression, options.decoder_id); @@ -1334,11 +1347,7 @@ Error HeifContext::decode_image_planar(heif_item_id ID, return Error(heif_error_Plugin_loading_error, heif_suberror_No_matching_decoder_installed); } - std::vector data; - error = m_heif_file->get_compressed_image_data(ID, &data); - if (error) { - return error; - } + void* decoder; struct heif_error err = decoder_plugin->new_decoder(&decoder); diff --git a/libheif/plugin_registry.cc b/libheif/plugin_registry.cc index f5aaa07e26..784ed6bd58 100644 --- a/libheif/plugin_registry.cc +++ b/libheif/plugin_registry.cc @@ -88,6 +88,10 @@ #include "libheif/plugins/encoder_openjph.h" #endif +#if HAVE_OPENJPH_DECODER +#include "libheif/plugins/decoder_openjph.h" +#endif + std::set s_decoder_plugins; std::multiset, @@ -179,6 +183,10 @@ void register_default_plugins() register_encoder(get_encoder_plugin_openjph()); #endif +#if HAVE_OPENJPH_DECODER + register_decoder(get_decoder_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 812b87df52..35c1fbcf84 100644 --- a/libheif/plugins/CMakeLists.txt +++ b/libheif/plugins/CMakeLists.txt @@ -95,6 +95,10 @@ 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) +set(OPENJPH_DECODER_sources decoder_openjph.cc decoder_openjph.h) +set(OPENJPH_DECODER_extra_plugin_sources) +plugin_compilation(jphdec OPENJPH OPENJPH_FOUND OPENJPH_DECODER OPENJPH_DECODER) + target_sources(heif PRIVATE encoder_mask.h encoder_mask.cc) diff --git a/libheif/plugins/decoder_openjpeg.cc b/libheif/plugins/decoder_openjpeg.cc index 7e801bff00..255c9601e2 100644 --- a/libheif/plugins/decoder_openjpeg.cc +++ b/libheif/plugins/decoder_openjpeg.cc @@ -62,7 +62,7 @@ static void openjpeg_deinit_plugin() static int openjpeg_does_support_format(enum heif_compression_format format) { - if (format == heif_compression_JPEG2000) { + if ((format == heif_compression_JPEG2000) || (format == heif_compression_HTJ2K)) { return OPENJPEG_PLUGIN_PRIORITY; } else { diff --git a/libheif/plugins/decoder_openjph.cc b/libheif/plugins/decoder_openjph.cc new file mode 100644 index 0000000000..342acf2738 --- /dev/null +++ b/libheif/plugins/decoder_openjph.cc @@ -0,0 +1,238 @@ +/* + * OpenJPH High Throughput JPEG 2000 decoder. + * + * 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. + * + */ + +#include "libheif/heif.h" +#include "libheif/heif_plugin.h" +#include "decoder_openjph.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +struct openjph_dec_context +{ + std::vector data; + bool strict_decoding = false; +}; + + +static const int OPENJPH_DEC_PLUGIN_PRIORITY = 100; + +static void openjph_dec_init_plugin() +{ +} + +static void openjph_dec_deinit_plugin() +{ +} + +static int openjph_dec_does_support_format(enum heif_compression_format format) +{ + if (format == heif_compression_HTJ2K) { + return OPENJPH_DEC_PLUGIN_PRIORITY; + } else { + return 0; + } +} + +struct heif_error openjph_dec_new_decoder(void **dec) +{ + struct openjph_dec_context *decoder_context = new openjph_dec_context(); + *dec = decoder_context; + return heif_error_ok; +} + +void openjph_dec_free_decoder(void *decoder_raw) +{ + openjph_dec_context *decoder_context = (openjph_dec_context *)decoder_raw; + delete decoder_context; +} + +void openjph_dec_set_strict_decoding(void *decoder_raw, int flag) +{ + openjph_dec_context *decoder_context = (openjph_dec_context *)decoder_raw; + decoder_context->strict_decoding = flag; +} + +struct heif_error openjph_dec_push_data(void *decoder_raw, const void *frame_data, size_t frame_size) +{ + openjph_dec_context *decoder_context = (openjph_dec_context *)decoder_raw; + const uint8_t* data = (const uint8_t*)frame_data; + decoder_context->data.insert(decoder_context->data.end(), data, data + frame_size); + return heif_error_ok; +} + +struct heif_error openjph_dec_decode_image(void *decoder_raw, struct heif_image **out_img) +{ + openjph_dec_context *decoder_context = (openjph_dec_context *)decoder_raw; + ojph::codestream codestream; + ojph::mem_infile input; + input.open(decoder_context->data.data(), decoder_context->data.size()); + if (!(decoder_context->strict_decoding)) { + codestream.enable_resilience(); + } + codestream.read_headers(&input); + codestream.create(); + + struct heif_image* heif_img = nullptr; + + ojph::param_siz siz = codestream.access_siz(); + ojph::point imageExtent = siz.get_image_extent(); + ojph::point imageOffset = siz.get_image_offset(); + uint32_t width = imageExtent.x - imageOffset.x; + uint32_t height = imageExtent.y - imageOffset.y; + + // TODO: work out colorspace and chroma correctly + heif_colorspace colourspace = heif_colorspace_undefined; + heif_chroma chroma = heif_chroma_undefined; + if (siz.get_num_components() == 3) { + colourspace = heif_colorspace_YCbCr; + chroma = heif_chroma_444; + // check components after the first one + for (unsigned int i = 1; i < siz.get_num_components(); ++i) { + ojph::point downsampling = siz.get_downsampling(i); + if ((downsampling.x == 1) && (downsampling.y == 1)) { + // 4:4:4 + if (chroma != heif_chroma_444) { + struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "mismatched chroma format"}; + return err; + } + } else if ((downsampling.x == 2) && (downsampling.y == 1)) { + // 4:2:2 + if ((chroma == heif_chroma_444) && (i == 1)) { + chroma = heif_chroma_422; + } else if (chroma != heif_chroma_422) { + struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "mismatched chroma format"}; + return err; + } + } else if ((downsampling.x == 2) && (downsampling.y == 2)) { + // 4:2:0 + if ((chroma == heif_chroma_444) && (i == 1)) { + chroma = heif_chroma_420; + } else if (chroma != heif_chroma_420) { + struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "mismatched chroma format"}; + return err; + } + + } + } + } else if (siz.get_num_components() == 1) { + colourspace = heif_colorspace_monochrome; + chroma = heif_chroma_monochrome; + } else { + struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "unsupported number of components"}; + return err; + + } + + struct heif_error err = heif_image_create(width, height, colourspace, chroma, &heif_img); + if (err.code != heif_error_Ok) { + assert(heif_img == nullptr); + return err; + } + + // TODO: map component to channel + if (colourspace == heif_colorspace_monochrome) { + heif_image_add_plane(heif_img, heif_channel_Y, width, height, siz.get_bit_depth(0)); + // TODO: make image + struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "unsupported monochrome image"}; + return err; + } else { + if (codestream.is_planar()) { + heif_channel channels[] = {heif_channel_Y, heif_channel_Cb, heif_channel_Cr}; + for (uint32_t componentIndex = 0; componentIndex < siz.get_num_components(); ++componentIndex) { + uint32_t component_width = siz.get_recon_width(componentIndex); + uint32_t component_height = siz.get_recon_height(componentIndex); + uint32_t bit_depth = siz.get_bit_depth(componentIndex); + heif_channel channel = channels[componentIndex]; + heif_image_add_plane(heif_img, channel, component_width, component_height, bit_depth); + int planeStride; + uint8_t* plane = heif_image_get_plane(heif_img, channel, &planeStride); + for (uint32_t rowIndex = 0; rowIndex < component_height; rowIndex++) { + uint32_t comp_num; + ojph::line_buf *line = codestream.pull(comp_num); + const int32_t *cursor = line->i32; + for (uint32_t colIndex = 0; colIndex < component_width; ++colIndex) { + int v = *cursor; + // TODO: this only works for the 8 bit case + plane[rowIndex * planeStride + colIndex] = (uint8_t) v; + cursor++; + } + } + } + } else { + struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "unsupported interleaved image"}; + return err; + } + } + *out_img = heif_img; + return heif_error_ok; +} + +static const int MAX_PLUGIN_NAME_LENGTH = 80; +static char plugin_name[MAX_PLUGIN_NAME_LENGTH]; + +const char *openjph_dec_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_decoder_plugin decoder_openjph +{ + 3, + openjph_dec_plugin_name, + openjph_dec_init_plugin, + openjph_dec_deinit_plugin, + openjph_dec_does_support_format, + openjph_dec_new_decoder, + openjph_dec_free_decoder, + openjph_dec_push_data, + openjph_dec_decode_image, + openjph_dec_set_strict_decoding, + "openjph" +}; + +const struct heif_decoder_plugin *get_decoder_plugin_openjph() +{ + return &decoder_openjph; +} + +#if PLUGIN_OPENJPH_DECODER +heif_plugin_info plugin_info{ + 1, + heif_plugin_type_decoder, + &decoder_openjph}; +#endif diff --git a/libheif/plugins/decoder_openjph.h b/libheif/plugins/decoder_openjph.h new file mode 100644 index 0000000000..90c5df7d1f --- /dev/null +++ b/libheif/plugins/decoder_openjph.h @@ -0,0 +1,35 @@ +/* + * OpenJPH High Throughput JPEG 2000 decoder. + * + * 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_HEIF_DECODER_OPENJPH_H +#define LIBHEIF_HEIF_DECODER_OPENJPH_H + +#include "libheif/common_utils.h" + +const struct heif_decoder_plugin* get_decoder_plugin_openjph(); + +#if PLUGIN_OPENJPH_DECODER +extern "C" { +MAYBE_UNUSED LIBHEIF_API extern heif_plugin_info plugin_info; +} +#endif + +#endif