libheif/plugins/decoder_openjpeg.cc (254 lines of code) (raw):

/* * OpenJPEG codec. * Copyright (c) 2023 Devon Sookhoo * Copyright (c) 2023 Dirk Farin <dirk.farin@gmail.com> * * 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 <http://www.gnu.org/licenses/>. */ #include "libheif/heif.h" #include "libheif/heif_plugin.h" #include "decoder_openjpeg.h" #include <openjpeg.h> #include <cstring> #include <vector> #include <cassert> static const int OPENJPEG_PLUGIN_PRIORITY = 100; struct openjpeg_decoder { std::vector<uint8_t> encoded_data; size_t read_position = 0; }; #define MAX_PLUGIN_NAME_LENGTH 80 static char plugin_name[MAX_PLUGIN_NAME_LENGTH]; static const char* openjpeg_plugin_name() { snprintf(plugin_name, MAX_PLUGIN_NAME_LENGTH, "OpenJPEG %s", opj_version()); plugin_name[MAX_PLUGIN_NAME_LENGTH - 1] = 0; return plugin_name; } static void openjpeg_init_plugin() { } static void openjpeg_deinit_plugin() { } static int openjpeg_does_support_format(enum heif_compression_format format) { if (format == heif_compression_JPEG2000) { return OPENJPEG_PLUGIN_PRIORITY; } else { return 0; } } struct heif_error openjpeg_new_decoder(void** dec, int nthreads) { struct openjpeg_decoder* decoder = new openjpeg_decoder(); *dec = decoder; return heif_error_ok; } void openjpeg_free_decoder(void* decoder_raw) { struct openjpeg_decoder* decoder = (openjpeg_decoder*) decoder_raw; if (!decoder) { return; } delete decoder; } void openjpeg_set_strict_decoding(void* decoder_raw, int flag) { } struct heif_error openjpeg_push_data(void* decoder_raw, const void* frame_data, size_t frame_size) { struct openjpeg_decoder* decoder = (struct openjpeg_decoder*) decoder_raw; const uint8_t* frame_data_src = (const uint8_t*) frame_data; decoder->encoded_data.insert(decoder->encoded_data.end(), frame_data_src, frame_data_src + frame_size); return heif_error_ok; } //************************************************************************** // This will read from our memory to the buffer. static OPJ_SIZE_T opj_memory_stream_read(void* p_buffer, OPJ_SIZE_T p_nb_bytes, void* p_user_data) { openjpeg_decoder* decoder = (openjpeg_decoder*) p_user_data; // Our data. size_t data_size = decoder->encoded_data.size(); OPJ_SIZE_T l_nb_bytes_read = p_nb_bytes; // Amount to move to buffer. // Check if the current offset is outside our data buffer. if (decoder->read_position >= data_size) { return (OPJ_SIZE_T) -1; } // Check if we are reading more than we have. if (p_nb_bytes > (data_size - decoder->read_position)) { //Read all we have. l_nb_bytes_read = data_size - decoder->read_position; } // Copy the data to the internal buffer. memcpy(p_buffer, &(decoder->encoded_data[decoder->read_position]), l_nb_bytes_read); decoder->read_position += l_nb_bytes_read; // Update the pointer to the new location. return l_nb_bytes_read; } // This will write from the buffer to our memory. static OPJ_SIZE_T opj_memory_stream_write(void* p_buffer, OPJ_SIZE_T p_nb_bytes, void* p_user_data) { assert(false); // We should never need to write to the buffer. return 0; } // Moves the pointer forward, but never more than we have. static OPJ_OFF_T opj_memory_stream_skip(OPJ_OFF_T p_nb_bytes, void* p_user_data) { openjpeg_decoder* decoder = (openjpeg_decoder*) p_user_data; // Our data. size_t data_size = decoder->encoded_data.size(); OPJ_SIZE_T l_nb_bytes; if (p_nb_bytes < 0) { //No skipping backwards. return -1; } l_nb_bytes = (OPJ_SIZE_T) p_nb_bytes; // Allowed because it is positive. // Do not allow jumping past the end. if (l_nb_bytes > data_size - decoder->read_position) { l_nb_bytes = data_size - decoder->read_position;//Jump the max. } // Make the jump. decoder->read_position += l_nb_bytes; // Return how far we jumped. return l_nb_bytes; } // Sets the pointer to anywhere in the memory. static OPJ_BOOL opj_memory_stream_seek(OPJ_OFF_T p_nb_bytes, void* p_user_data) { openjpeg_decoder* decoder = (openjpeg_decoder*) p_user_data; // Our data. size_t data_size = decoder->encoded_data.size(); // No before the buffer. if (p_nb_bytes < 0) return OPJ_FALSE; // No after the buffer. if (p_nb_bytes > (OPJ_OFF_T) data_size) return OPJ_FALSE; // Move to new position. decoder->read_position = (OPJ_SIZE_T) p_nb_bytes; return OPJ_TRUE; } //The system needs a routine to do when finished, the name tells you what I want it to do. static void opj_memory_stream_do_nothing(void* p_user_data) { OPJ_ARG_NOT_USED(p_user_data); } // Create a stream to use memory as the input or output. opj_stream_t* opj_stream_create_default_memory_stream(openjpeg_decoder* p_decoder, OPJ_BOOL p_is_read_stream) { opj_stream_t* stream; if (!(stream = opj_stream_default_create(p_is_read_stream))) { return nullptr; } // Set how to work with the frame buffer. if (p_is_read_stream) { opj_stream_set_read_function(stream, opj_memory_stream_read); } else { opj_stream_set_write_function(stream, opj_memory_stream_write); } opj_stream_set_seek_function(stream, opj_memory_stream_seek); opj_stream_set_skip_function(stream, opj_memory_stream_skip); opj_stream_set_user_data(stream, p_decoder, opj_memory_stream_do_nothing); opj_stream_set_user_data_length(stream, p_decoder->encoded_data.size()); return stream; } //************************************************************************** struct heif_error openjpeg_decode_image(void* decoder_raw, struct heif_image** out_img) { struct openjpeg_decoder* decoder = (struct openjpeg_decoder*) decoder_raw; OPJ_BOOL success; opj_dparameters_t decompression_parameters; opj_codec_t* l_codec; // Initialize Decoder opj_set_default_decoder_parameters(&decompression_parameters); l_codec = opj_create_decompress(OPJ_CODEC_J2K); success = opj_setup_decoder(l_codec, &decompression_parameters); if (!success) { struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "opj_setup_decoder()"}; return err; } // Create Input Stream OPJ_BOOL is_read_stream = true; opj_stream_t* stream = opj_stream_create_default_memory_stream(decoder, is_read_stream); // Read Codestream Header opj_image_t* image = NULL; success = opj_read_header(stream, l_codec, &image); if (!success) { struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "opj_read_header()"}; return err; } else if (image->numcomps != 3 && image->numcomps != 1) { //TODO - Handle other numbers of components struct heif_error err = {heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Number of components must be 3 or 1"}; return err; } else if ((image->color_space != OPJ_CLRSPC_UNSPECIFIED) && (image->color_space != OPJ_CLRSPC_SRGB)) { //TODO - Handle other colorspaces struct heif_error err = {heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Colorspace must be SRGB"}; return err; } const int width = (image->x1 - image->x0); const int height = (image->y1 - image->y0); /* Get the decoded image */ success = opj_decode(l_codec, stream, image); if (!success) { struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "opj_decode()"}; return err; } success = opj_end_decompress(l_codec, stream); if (!success) { struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "opj_end_decompress()"}; return err; } /* Close the byte stream */ opj_stream_destroy(stream); heif_colorspace colorspace = heif_colorspace_YCbCr; heif_chroma chroma = heif_chroma_444; //heif_chroma_interleaved_RGB; std::vector<heif_channel> channels; if (image->numcomps == 1) { colorspace = heif_colorspace_monochrome; chroma = heif_chroma_monochrome; channels = {heif_channel_Y}; } else if (image->numcomps == 3 && image->comps[1].dx == 1 && image->comps[1].dy == 1) { colorspace = heif_colorspace_YCbCr; chroma = heif_chroma_444; channels = {heif_channel_Y, heif_channel_Cb, heif_channel_Cr}; } else if (image->numcomps == 3 && image->comps[1].dx == 2 && image->comps[1].dy == 1) { colorspace = heif_colorspace_YCbCr; chroma = heif_chroma_422; channels = {heif_channel_Y, heif_channel_Cb, heif_channel_Cr}; } else if (image->numcomps == 3 && image->comps[1].dx == 2 && image->comps[1].dy == 2) { colorspace = heif_colorspace_YCbCr; chroma = heif_chroma_420; channels = {heif_channel_Y, heif_channel_Cb, heif_channel_Cr}; } else { struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "unsupported image format"}; return err; } struct heif_error error = heif_image_create(width, height, colorspace, chroma, out_img); if (error.code) { return error; } for (size_t c = 0; c < image->numcomps; c++) { const opj_image_comp_t& opj_comp = image->comps[c]; int bit_depth = opj_comp.prec; int cwidth = opj_comp.w; int cheight = opj_comp.h; error = heif_image_add_plane(*out_img, channels[c], cwidth, cheight, bit_depth); int stride = -1; uint8_t* p = heif_image_get_plane(*out_img, channels[c], &stride); // TODO: a SIMD implementation to convert int32 to uint8 would speed this up // https://stackoverflow.com/questions/63774643/how-to-convert-uint32-to-uint8-using-simd-but-not-avx512 if (stride == cwidth) { for (int i = 0; i < cwidth * cheight; i++) { p[i] = (uint8_t) opj_comp.data[i]; } } else { for (int y = 0; y < cheight; y++) { for (int x = 0; x < cwidth; x++) { p[y * stride + x] = (uint8_t) opj_comp.data[y * cwidth + x]; } } } } return heif_error_ok; } static const struct heif_decoder_plugin decoder_openjpeg{ 3, openjpeg_plugin_name, openjpeg_init_plugin, openjpeg_deinit_plugin, openjpeg_does_support_format, openjpeg_new_decoder, openjpeg_free_decoder, openjpeg_push_data, openjpeg_decode_image, openjpeg_set_strict_decoding, "openjpeg" }; const struct heif_decoder_plugin* get_decoder_plugin_openjpeg() { return &decoder_openjpeg; } #if PLUGIN_OPENJPEG_DECODER heif_plugin_info plugin_info { 1, heif_plugin_type_decoder, &decoder_openjpeg }; #endif