diff options
author | Andrey Kleshchev <andreykproductengine@lindenlab.com> | 2021-12-17 01:39:40 +0200 |
---|---|---|
committer | Andrey Kleshchev <andreykproductengine@lindenlab.com> | 2022-10-01 11:25:14 +0300 |
commit | c844bf0ab8acbbb2d4ef0814d8ed7ac9291b7f44 (patch) | |
tree | feb5f12ad75e68918da4018aa765e198590972c0 /indra | |
parent | 338aeef7e9e8cfd167489e0707f3e4a884d2c00f (diff) |
SL-16542 Pull in chunks of graham's openjpeg code, update openjpeg and enable partial bitstreams
Openjpeg was modified: "p_max_len -= l_nb_bytes_read;" was causing an overflow.
I'm not sure if I did something incorectly in opj_skip/opj_seek viewer side, but seems like openjpeg should have been checking remaining space in p_max_len either way.
P.S. Many thanks to Chafey and Neopallium for implementing openjpeg's partial bitstream support
Diffstat (limited to 'indra')
-rw-r--r-- | indra/cmake/Copy3rdPartyLibs.cmake | 4 | ||||
-rw-r--r-- | indra/cmake/FindOpenJPEG.cmake | 5 | ||||
-rw-r--r-- | indra/cmake/OpenJPEG.cmake | 14 | ||||
-rw-r--r-- | indra/integration_tests/llui_libtest/CMakeLists.txt | 4 | ||||
-rw-r--r-- | indra/llimagej2coj/llimagej2coj.cpp | 1101 | ||||
-rw-r--r-- | indra/newview/CMakeLists.txt | 6 | ||||
-rwxr-xr-x | indra/newview/viewer_manifest.py | 4 |
7 files changed, 737 insertions, 401 deletions
diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index ff705101de..04b4202ca3 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -52,7 +52,7 @@ if(WINDOWS) set(release_src_dir "${ARCH_PREBUILT_DIRS_RELEASE}") set(release_files - openjpeg.dll + openjp2.dll libapr-1.dll libaprutil-1.dll libapriconv-1.dll @@ -220,7 +220,7 @@ elseif(LINUX) libgobject-2.0.so libhunspell-1.3.so.0.0.0 libopenal.so - libopenjpeg.so + libopenjp2.so libuuid.so.16 libuuid.so.16.0.22 libfontconfig.so.1.8.0 diff --git a/indra/cmake/FindOpenJPEG.cmake b/indra/cmake/FindOpenJPEG.cmake index 949384eec4..afe319fb06 100644 --- a/indra/cmake/FindOpenJPEG.cmake +++ b/indra/cmake/FindOpenJPEG.cmake @@ -9,14 +9,15 @@ # also defined, but not for general use are # OPENJPEG_LIBRARY, where to find the OpenJPEG library. -FIND_PATH(OPENJPEG_INCLUDE_DIR openjpeg.h +FIND_PATH(OPENJPEG_INCLUDE_DIR openjp2.h /usr/local/include/openjpeg /usr/local/include /usr/include/openjpeg /usr/include +include/openjpeg ) -SET(OPENJPEG_NAMES ${OPENJPEG_NAMES} openjpeg) +SET(OPENJPEG_NAMES ${OPENJPEG_NAMES} openjp2) FIND_LIBRARY(OPENJPEG_LIBRARY NAMES ${OPENJPEG_NAMES} PATHS /usr/lib /usr/local/lib diff --git a/indra/cmake/OpenJPEG.cmake b/indra/cmake/OpenJPEG.cmake index bf0bde2ba7..a078c97cb8 100644 --- a/indra/cmake/OpenJPEG.cmake +++ b/indra/cmake/OpenJPEG.cmake @@ -8,15 +8,7 @@ if (USESYSTEMLIBS) include(FindOpenJPEG) else (USESYSTEMLIBS) use_prebuilt_binary(openjpeg) - - if(WINDOWS) - # Windows has differently named release and debug openjpeg(d) libs. - set(OPENJPEG_LIBRARIES - debug openjpegd - optimized openjpeg) - else(WINDOWS) - set(OPENJPEG_LIBRARIES openjpeg) - endif(WINDOWS) - - set(OPENJPEG_INCLUDE_DIR ${LIBS_PREBUILT_DIR}/include/openjpeg) + + set(OPENJPEG_LIBRARIES openjp2) + set(OPENJPEG_INCLUDE_DIR ${LIBS_PREBUILT_DIR}/include/openjpeg) endif (USESYSTEMLIBS) diff --git a/indra/integration_tests/llui_libtest/CMakeLists.txt b/indra/integration_tests/llui_libtest/CMakeLists.txt index d7706e73b2..3957ede77f 100644 --- a/indra/integration_tests/llui_libtest/CMakeLists.txt +++ b/indra/integration_tests/llui_libtest/CMakeLists.txt @@ -99,14 +99,14 @@ if (WINDOWS) # Copy over OpenJPEG.dll # *NOTE: On Windows with VS2005, only the first comment prints set(OPENJPEG_RELEASE - "${ARCH_PREBUILT_DIRS_RELEASE}/openjpeg.dll") + "${ARCH_PREBUILT_DIRS_RELEASE}/openjp2.dll") add_custom_command( TARGET llui_libtest POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${OPENJPEG_RELEASE} ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Copying OpenJPEG DLLs to binary directory" ) set(OPENJPEG_DEBUG - "${ARCH_PREBUILT_DIRS_DEBUG}/openjpegd.dll") + "${ARCH_PREBUILT_DIRS_DEBUG}/openjp2.dll") add_custom_command( TARGET llui_libtest POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${OPENJPEG_DEBUG} ${CMAKE_CURRENT_BINARY_DIR} diff --git a/indra/llimagej2coj/llimagej2coj.cpp b/indra/llimagej2coj/llimagej2coj.cpp index 925da5674b..944629cf4c 100644 --- a/indra/llimagej2coj/llimagej2coj.cpp +++ b/indra/llimagej2coj/llimagej2coj.cpp @@ -29,40 +29,41 @@ // this is defined so that we get static linking. #include "openjpeg.h" +#include "event.h" +#include "cio.h" -#include "lltimer.h" -//#include "llmemory.h" +#define MAX_ENCODED_DISCARD_LEVELS 5 // Factory function: see declaration in llimagej2c.cpp LLImageJ2CImpl* fallbackCreateLLImageJ2CImpl() { - return new LLImageJ2COJ(); + return new LLImageJ2COJ(); } std::string LLImageJ2COJ::getEngineInfo() const { #ifdef OPENJPEG_VERSION - return std::string("OpenJPEG: " OPENJPEG_VERSION ", Runtime: ") - + opj_version(); + return std::string("OpenJPEG: " OPENJPEG_VERSION ", Runtime: ") + + opj_version(); #else - return std::string("OpenJPEG runtime: ") + opj_version(); + return std::string("OpenJPEG runtime: ") + opj_version(); #endif } // Return string from message, eliminating final \n if present static std::string chomp(const char* msg) { - // stomp trailing \n - std::string message = msg; - if (!message.empty()) - { - size_t last = message.size() - 1; - if (message[last] == '\n') - { - message.resize( last ); - } - } - return message; + // stomp trailing \n + std::string message = msg; + if (!message.empty()) + { + size_t last = message.size() - 1; + if (message[last] == '\n') + { + message.resize(last); + } +} + return message; } /** @@ -70,419 +71,761 @@ sample error callback expecting a LLFILE* client object */ void error_callback(const char* msg, void*) { - LL_DEBUGS() << "LLImageJ2COJ: " << chomp(msg) << LL_ENDL; + LL_DEBUGS() << "LLImageJ2COJ: " << chomp(msg) << LL_ENDL; } /** sample warning callback expecting a LLFILE* client object */ void warning_callback(const char* msg, void*) { - LL_DEBUGS() << "LLImageJ2COJ: " << chomp(msg) << LL_ENDL; + LL_DEBUGS() << "LLImageJ2COJ: " << chomp(msg) << LL_ENDL; } /** sample debug callback expecting no client object */ void info_callback(const char* msg, void*) { - LL_DEBUGS() << "LLImageJ2COJ: " << chomp(msg) << LL_ENDL; + LL_DEBUGS() << "LLImageJ2COJ: " << chomp(msg) << LL_ENDL; } // Divide a by 2 to the power of b and round upwards int ceildivpow2(int a, int b) { - return (a + (1 << b) - 1) >> b; + return (a + (1 << b) - 1) >> b; } - -LLImageJ2COJ::LLImageJ2COJ() - : LLImageJ2CImpl() +class JPEG2KBase { -} +public: + JPEG2KBase() {} + U8* buffer = nullptr; + OPJ_SIZE_T size = 0; + OPJ_OFF_T offset = 0; +}; -LLImageJ2COJ::~LLImageJ2COJ() +#define WANT_VERBOSE_OPJ_SPAM LL_DEBUG + +static void opj_info(const char* msg, void* user_data) { + llassert(user_data); +#if WANT_VERBOSE_OPJ_SPAM + LL_INFOS("OpenJPEG") << msg << LL_ENDL; +#endif } -bool LLImageJ2COJ::initDecode(LLImageJ2C &base, LLImageRaw &raw_image, int discard_level, int* region) +static void opj_warn(const char* msg, void* user_data) { - // No specific implementation for this method in the OpenJpeg case - return false; + llassert(user_data); +#if WANT_VERBOSE_OPJ_SPAM + LL_WARNS("OpenJPEG") << msg << LL_ENDL; +#endif } -bool LLImageJ2COJ::initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int blocks_size, int precincts_size, int levels) +static void opj_error(const char* msg, void* user_data) { - // No specific implementation for this method in the OpenJpeg case - return false; + llassert(user_data); +#if WANT_VERBOSE_OPJ_SPAM + LL_WARNS("OpenJPEG") << msg << LL_ENDL; +#endif } -bool LLImageJ2COJ::decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count) +static OPJ_SIZE_T opj_read(void * buffer, OPJ_SIZE_T bytes, void* user_data) { - // - // FIXME: Get the comment field out of the texture - // - - LLTimer decode_timer; - - opj_dparameters_t parameters; /* decompression parameters */ - opj_event_mgr_t event_mgr; /* event manager */ - opj_image_t *image = NULL; - - opj_dinfo_t* dinfo = NULL; /* handle to a decompressor */ - opj_cio_t *cio = NULL; - - - /* configure the event callbacks (not required) */ - memset(&event_mgr, 0, sizeof(opj_event_mgr_t)); - event_mgr.error_handler = error_callback; - event_mgr.warning_handler = warning_callback; - event_mgr.info_handler = info_callback; - - /* set decoding parameters to default values */ - opj_set_default_decoder_parameters(¶meters); - - parameters.cp_reduce = base.getRawDiscardLevel(); - - /* decode the code-stream */ - /* ---------------------- */ - - /* JPEG-2000 codestream */ - - /* get a decoder handle */ - dinfo = opj_create_decompress(CODEC_J2K); - - /* catch events using our callbacks and give a local context */ - opj_set_event_mgr((opj_common_ptr)dinfo, &event_mgr, stderr); - - /* setup the decoder decoding parameters using user parameters */ - opj_setup_decoder(dinfo, ¶meters); - - /* open a byte stream */ - cio = opj_cio_open((opj_common_ptr)dinfo, base.getData(), base.getDataSize()); - - /* decode the stream and fill the image structure */ - image = opj_decode(dinfo, cio); - - /* close the byte stream */ - opj_cio_close(cio); - - /* free remaining structures */ - if(dinfo) - { - opj_destroy_decompress(dinfo); - } - - // The image decode failed if the return was NULL or the component - // count was zero. The latter is just a sanity check before we - // dereference the array. - if(!image || !image->numcomps) - { - LL_DEBUGS("Texture") << "ERROR -> decodeImpl: failed to decode image!" << LL_ENDL; - if (image) - { - opj_image_destroy(image); - } - - return true; // done - } - - // sometimes we get bad data out of the cache - check to see if the decode succeeded - for (S32 i = 0; i < image->numcomps; i++) - { - if (image->comps[i].factor != base.getRawDiscardLevel()) - { - // if we didn't get the discard level we're expecting, fail - opj_image_destroy(image); - base.mDecoding = false; - return true; - } - } - - if(image->numcomps <= first_channel) - { - LL_WARNS() << "trying to decode more channels than are present in image: numcomps: " << image->numcomps << " first_channel: " << first_channel << LL_ENDL; - if (image) - { - opj_image_destroy(image); - } - - return true; - } - - // Copy image data into our raw image format (instead of the separate channel format - - S32 img_components = image->numcomps; - S32 channels = img_components - first_channel; - if( channels > max_channel_count ) - channels = max_channel_count; - - // Component buffers are allocated in an image width by height buffer. - // The image placed in that buffer is ceil(width/2^factor) by - // ceil(height/2^factor) and if the factor isn't zero it will be at the - // top left of the buffer with black filled in the rest of the pixels. - // It is integer math so the formula is written in ceildivpo2. - // (Assuming all the components have the same width, height and - // factor.) - S32 comp_width = image->comps[0].w; - S32 f=image->comps[0].factor; - S32 width = ceildivpow2(image->x1 - image->x0, f); - S32 height = ceildivpow2(image->y1 - image->y0, f); - raw_image.resize(width, height, channels); - U8 *rawp = raw_image.getData(); - - // first_channel is what channel to start copying from - // dest is what channel to copy to. first_channel comes from the - // argument, dest always starts writing at channel zero. - for (S32 comp = first_channel, dest=0; comp < first_channel + channels; - comp++, dest++) - { - if (image->comps[comp].data) - { - S32 offset = dest; - for (S32 y = (height - 1); y >= 0; y--) - { - for (S32 x = 0; x < width; x++) - { - rawp[offset] = image->comps[comp].data[y*comp_width + x]; - offset += channels; - } - } - } - else // Some rare OpenJPEG versions have this bug. - { - LL_DEBUGS("Texture") << "ERROR -> decodeImpl: failed to decode image! (NULL comp data - OpenJPEG bug)" << LL_ENDL; - opj_image_destroy(image); - - return true; // done - } - } - - /* free image data structure */ - opj_image_destroy(image); - - return true; // done + llassert(user_data); + JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data); + OPJ_SIZE_T remainder = (jpeg_codec->size - jpeg_codec->offset); + if (remainder <= 0) + { + jpeg_codec->offset = jpeg_codec->size; + // Indicate end of stream (hacky?) + return (OPJ_OFF_T)-1; + } + OPJ_SIZE_T to_read = llclamp(U32(bytes), U32(0), U32(remainder)); + memcpy(buffer, jpeg_codec->buffer + jpeg_codec->offset, to_read); + jpeg_codec->offset += to_read; + return to_read; } - -bool LLImageJ2COJ::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time, bool reversible) +static OPJ_SIZE_T opj_write(void * buffer, OPJ_SIZE_T bytes, void* user_data) { - const S32 MAX_COMPS = 5; - opj_cparameters_t parameters; /* compression parameters */ - opj_event_mgr_t event_mgr; /* event manager */ - - - /* - configure the event callbacks (not required) - setting of each callback is optional - */ - memset(&event_mgr, 0, sizeof(opj_event_mgr_t)); - event_mgr.error_handler = error_callback; - event_mgr.warning_handler = warning_callback; - event_mgr.info_handler = info_callback; - - /* set encoding parameters to default values */ - opj_set_default_encoder_parameters(¶meters); - parameters.cod_format = 0; - parameters.cp_disto_alloc = 1; - - if (reversible) - { - parameters.tcp_numlayers = 1; - parameters.tcp_rates[0] = 0.0f; - } - else - { - parameters.tcp_numlayers = 5; - parameters.tcp_rates[0] = 1920.0f; - parameters.tcp_rates[1] = 480.0f; - parameters.tcp_rates[2] = 120.0f; - parameters.tcp_rates[3] = 30.0f; - parameters.tcp_rates[4] = 10.0f; - parameters.irreversible = 1; - if (raw_image.getComponents() >= 3) - { - parameters.tcp_mct = 1; - } - } - - if (!comment_text) - { - parameters.cp_comment = (char *) ""; - } - else - { - // Awful hacky cast, too lazy to copy right now. - parameters.cp_comment = (char *) comment_text; - } - - // - // Fill in the source image from our raw image - // - OPJ_COLOR_SPACE color_space = CLRSPC_SRGB; - opj_image_cmptparm_t cmptparm[MAX_COMPS]; - opj_image_t * image = NULL; - S32 numcomps = raw_image.getComponents(); - S32 width = raw_image.getWidth(); - S32 height = raw_image.getHeight(); - - memset(&cmptparm[0], 0, MAX_COMPS * sizeof(opj_image_cmptparm_t)); - for(S32 c = 0; c < numcomps; c++) { - cmptparm[c].prec = 8; - cmptparm[c].bpp = 8; - cmptparm[c].sgnd = 0; - cmptparm[c].dx = parameters.subsampling_dx; - cmptparm[c].dy = parameters.subsampling_dy; - cmptparm[c].w = width; - cmptparm[c].h = height; - } - - /* create the image */ - image = opj_image_create(numcomps, &cmptparm[0], color_space); - - image->x1 = width; - image->y1 = height; - - S32 i = 0; - const U8 *src_datap = raw_image.getData(); - for (S32 y = height - 1; y >= 0; y--) - { - for (S32 x = 0; x < width; x++) - { - const U8 *pixel = src_datap + (y*width + x) * numcomps; - for (S32 c = 0; c < numcomps; c++) - { - image->comps[c].data[i] = *pixel; - pixel++; - } - i++; - } - } - - - - /* encode the destination image */ - /* ---------------------------- */ - - int codestream_length; - opj_cio_t *cio = NULL; - - /* get a J2K compressor handle */ - opj_cinfo_t* cinfo = opj_create_compress(CODEC_J2K); - - /* catch events using our callbacks and give a local context */ - opj_set_event_mgr((opj_common_ptr)cinfo, &event_mgr, stderr); - - /* setup the encoder parameters using the current image and using user parameters */ - opj_setup_encoder(cinfo, ¶meters, image); - - /* open a byte stream for writing */ - /* allocate memory for all tiles */ - cio = opj_cio_open((opj_common_ptr)cinfo, NULL, 0); - - /* encode the image */ - bool bSuccess = opj_encode(cinfo, cio, image, NULL); - if (!bSuccess) - { - opj_cio_close(cio); - LL_DEBUGS("Texture") << "Failed to encode image." << LL_ENDL; - return false; - } - codestream_length = cio_tell(cio); - - base.copyData(cio->buffer, codestream_length); - base.updateData(); // set width, height - - /* close and free the byte stream */ - opj_cio_close(cio); - - /* free remaining compression structures */ - opj_destroy_compress(cinfo); - - - /* free user parameters structure */ - if(parameters.cp_matrice) free(parameters.cp_matrice); - - /* free image data */ - opj_image_destroy(image); - return true; + llassert(user_data); + JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data); + OPJ_SIZE_T remainder = jpeg_codec->size - jpeg_codec->offset; + if (remainder < bytes) + { + OPJ_SIZE_T new_size = jpeg_codec->size + (bytes - remainder); + U8* new_buffer = (U8*)ll_aligned_malloc_16(new_size); + memcpy(new_buffer, jpeg_codec->buffer, jpeg_codec->offset); + U8* old_buffer = jpeg_codec->buffer; + jpeg_codec->buffer = new_buffer; + ll_aligned_free_16(old_buffer); + jpeg_codec->size = new_size; + } + memcpy(jpeg_codec->buffer + jpeg_codec->offset, buffer, bytes); + jpeg_codec->offset += bytes; + return bytes; } -bool LLImageJ2COJ::getMetadata(LLImageJ2C &base) +static OPJ_OFF_T opj_skip(OPJ_OFF_T bytes, void* user_data) { - // - // FIXME: We get metadata by decoding the ENTIRE image. - // - - // Update the raw discard level - base.updateRawDiscardLevel(); - - opj_dparameters_t parameters; /* decompression parameters */ - opj_event_mgr_t event_mgr; /* event manager */ - opj_image_t *image = NULL; - - opj_dinfo_t* dinfo = NULL; /* handle to a decompressor */ - opj_cio_t *cio = NULL; - - - /* configure the event callbacks (not required) */ - memset(&event_mgr, 0, sizeof(opj_event_mgr_t)); - event_mgr.error_handler = error_callback; - event_mgr.warning_handler = warning_callback; - event_mgr.info_handler = info_callback; - - /* set decoding parameters to default values */ - opj_set_default_decoder_parameters(¶meters); + JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data); + jpeg_codec->offset += bytes; + if (jpeg_codec->offset > jpeg_codec->size) + { + jpeg_codec->offset = jpeg_codec->size; + // Indicate end of stream + return (OPJ_OFF_T)-1; + } + return bytes; +} - // Only decode what's required to get the size data. - parameters.cp_limit_decoding=LIMIT_TO_MAIN_HEADER; +static OPJ_BOOL opj_seek(OPJ_OFF_T bytes, void * user_data) +{ + JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data); + jpeg_codec->offset += bytes; + jpeg_codec->offset = llclamp(U32(jpeg_codec->offset), U32(0), U32(jpeg_codec->size)); + return OPJ_TRUE; +} - //parameters.cp_reduce = mRawDiscardLevel; +static void opj_free_user_data(void * user_data) +{ + JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data); + // Don't free, data is managed externally + jpeg_codec->buffer = nullptr; + jpeg_codec->size = 0; + jpeg_codec->offset = 0; +} - /* decode the code-stream */ - /* ---------------------- */ +static void opj_free_user_data_write(void * user_data) +{ + JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data); + // Free, data was allocated here + ll_aligned_free_16(jpeg_codec->buffer); + jpeg_codec->buffer = nullptr; + jpeg_codec->size = 0; + jpeg_codec->offset = 0; +} - /* JPEG-2000 codestream */ +class JPEG2KDecode : public JPEG2KBase +{ +public: + + JPEG2KDecode(S8 discardLevel) + { + memset(&event_mgr, 0, sizeof(opj_event_mgr_t)); + memset(¶meters, 0, sizeof(opj_dparameters_t)); + event_mgr.error_handler = error_callback; + event_mgr.warning_handler = warning_callback; + event_mgr.info_handler = info_callback; + opj_set_default_decoder_parameters(¶meters); + parameters.cp_reduce = discardLevel; + } + + ~JPEG2KDecode() + { + if (decoder) + { + opj_destroy_codec(decoder); + } + decoder = nullptr; + + if (image) + { + opj_image_destroy(image); + } + image = nullptr; + + if (stream) + { + opj_stream_destroy(stream); + } + stream = nullptr; + + if (codestream_info) + { + opj_destroy_cstr_info(&codestream_info); + } + codestream_info = nullptr; + } + + bool readHeader( + U8* data, + U32 dataSize, + S32& widthOut, + S32& heightOut, + S32& components, + S32& discard_level) + { + parameters.flags |= OPJ_DPARAMETERS_DUMP_FLAG; + + decoder = opj_create_decompress(OPJ_CODEC_J2K); + + if (!opj_setup_decoder(decoder, ¶meters)) + { + return false; + } + + if (stream) + { + opj_stream_destroy(stream); + } + + stream = opj_stream_create(dataSize, true); + if (!stream) + { + return false; + } + + opj_stream_set_user_data(stream, this, opj_free_user_data); + opj_stream_set_user_data_length(stream, dataSize); + opj_stream_set_read_function(stream, opj_read); + opj_stream_set_write_function(stream, opj_write); + opj_stream_set_skip_function(stream, opj_skip); + opj_stream_set_seek_function(stream, opj_seek); + + buffer = data; + size = dataSize; + offset = 0; + + // enable decoding partially loaded images + opj_decoder_set_strict_mode(decoder, OPJ_FALSE); + + /* Read the main header of the codestream and if necessary the JP2 boxes*/ + if (!opj_read_header((opj_stream_t*)stream, decoder, &image)) + { + return false; + } + + codestream_info = opj_get_cstr_info(decoder); + + if (!codestream_info) + { + return false; + } + + U32 tileDimX = codestream_info->tdx; + U32 tileDimY = codestream_info->tdy; + U32 tilesW = codestream_info->tw; + U32 tilesH = codestream_info->th; + + widthOut = S32(tilesW * tileDimX); + heightOut = S32(tilesH * tileDimY); + components = codestream_info->nbcomps; + + discard_level = 0; + while (tilesW > 1 && tilesH > 1 && discard_level < MAX_DISCARD_LEVEL) + { + discard_level++; + tilesW >>= 1; + tilesH >>= 1; + } + + return true; + } + + bool decode(U8* data, U32 dataSize, U32* channels, U8 discard_level) + { + parameters.flags &= ~OPJ_DPARAMETERS_DUMP_FLAG; + + decoder = opj_create_decompress(OPJ_CODEC_J2K); + opj_setup_decoder(decoder, ¶meters); + + opj_set_info_handler(decoder, opj_info, this); + opj_set_warning_handler(decoder, opj_warn, this); + opj_set_error_handler(decoder, opj_error, this); + + if (stream) + { + opj_stream_destroy(stream); + } + + stream = opj_stream_create(dataSize, true); + if (!stream) + { + return false; + } + + opj_stream_set_user_data(stream, this, opj_free_user_data); + opj_stream_set_user_data_length(stream, dataSize); + opj_stream_set_read_function(stream, opj_read); + opj_stream_set_write_function(stream, opj_write); + opj_stream_set_skip_function(stream, opj_skip); + opj_stream_set_seek_function(stream, opj_seek); + + buffer = data; + size = dataSize; + offset = 0; + + if (image) + { + opj_image_destroy(image); + image = nullptr; + } + + // needs to happen before opj_read_header and opj_decode... + opj_set_decoded_resolution_factor(decoder, discard_level); + + // enable decoding partially loaded images + opj_decoder_set_strict_mode(decoder, OPJ_FALSE); + + if (!opj_read_header(stream, decoder, &image)) + { + return false; + } + + // needs to happen before decode which may fail + if (channels) + { + *channels = image->numcomps; + } + + OPJ_BOOL decoded = opj_decode(decoder, stream, image); + + // count was zero. The latter is just a sanity check before we + // dereference the array. + if (!decoded || !image || !image->numcomps) + { + opj_end_decompress(decoder, stream); + return false; + } + + opj_end_decompress(decoder, stream); + + return true; + } + + opj_image_t* getImage() { return image; } + +private: + opj_dparameters_t parameters; + opj_event_mgr_t event_mgr; + opj_image_t* image = nullptr; + opj_codec_t* decoder = nullptr; + opj_stream_t* stream = nullptr; + opj_codestream_info_v2_t* codestream_info = nullptr; +}; + +class JPEG2KEncode : public JPEG2KBase +{ +public: + const OPJ_UINT32 TILE_SIZE = 64 * 64 * 3; + + JPEG2KEncode(const char* comment_text_in, bool reversible) + { + memset(¶meters, 0, sizeof(opj_cparameters_t)); + memset(&event_mgr, 0, sizeof(opj_event_mgr_t)); + event_mgr.error_handler = error_callback; + event_mgr.warning_handler = warning_callback; + event_mgr.info_handler = info_callback; + + opj_set_default_encoder_parameters(¶meters); + parameters.cod_format = 0; + parameters.cp_disto_alloc = 1; + parameters.max_cs_size = (1 << 15); + + if (reversible) + { + parameters.tcp_numlayers = 1; + parameters.tcp_rates[0] = 1.0f; + } + else + { + parameters.tcp_numlayers = 5; + parameters.tcp_rates[0] = 1920.0f; + parameters.tcp_rates[1] = 960.0f; + parameters.tcp_rates[2] = 480.0f; + parameters.tcp_rates[3] = 120.0f; + parameters.tcp_rates[4] = 30.0f; + parameters.irreversible = 1; + parameters.tcp_mct = 1; + } + + if (comment_text) + { + free(comment_text); + } + comment_text = comment_text_in ? strdup(comment_text_in) : nullptr; + + parameters.cp_comment = comment_text ? comment_text : (char*)"no comment"; + llassert(parameters.cp_comment); + } + + ~JPEG2KEncode() + { + if (encoder) + { + opj_destroy_codec(encoder); + } + encoder = nullptr; + + if (image) + { + opj_image_destroy(image); + } + image = nullptr; + + if (stream) + { + opj_stream_destroy(stream); + } + stream = nullptr; + + if (comment_text) + { + free(comment_text); + } + comment_text = nullptr; + } + + bool encode(const LLImageRaw& rawImageIn, LLImageJ2C &compressedImageOut) + { + setImage(rawImageIn); + + encoder = opj_create_compress(OPJ_CODEC_J2K); + + parameters.tcp_mct = (image->numcomps >= 3) ? 1 : 0; + parameters.cod_format = OPJ_CODEC_J2K; + parameters.numresolution = MAX_ENCODED_DISCARD_LEVELS; + parameters.prog_order = OPJ_RLCP; + parameters.cp_disto_alloc = 1; + + opj_setup_encoder(encoder, ¶meters, image); + + opj_set_info_handler(encoder, opj_info, this); + opj_set_warning_handler(encoder, opj_warn, this); + opj_set_error_handler(encoder, opj_error, this); + + U32 tile_count = (rawImageIn.getWidth() >> 6) * (rawImageIn.getHeight() >> 6); + U32 data_size_guess = tile_count * TILE_SIZE; + + // will be freed in opj_free_user_data_write + buffer = (U8*)ll_aligned_malloc_16(data_size_guess); + size = data_size_guess; + offset = 0; + +#if LL_DEBUG + memset(buffer, 0, data_size_guess); +#endif - /* get a decoder handle */ - dinfo = opj_create_decompress(CODEC_J2K); + if (stream) + { + opj_stream_destroy(stream); + } + + stream = opj_stream_create(data_size_guess, false); + if (!stream) + { + return false; + } + + opj_stream_set_user_data(stream, this, opj_free_user_data_write); + opj_stream_set_user_data_length(stream, data_size_guess); + opj_stream_set_read_function(stream, opj_read); + opj_stream_set_write_function(stream, opj_write); + opj_stream_set_skip_function(stream, opj_skip); + opj_stream_set_seek_function(stream, opj_seek); + + OPJ_BOOL started = opj_start_compress(encoder, image, stream); + + if (!started) + { + return false; + } + + if (!opj_encode(encoder, stream)) + { + return false; + } + + OPJ_BOOL encoded = opj_end_compress(encoder, stream); + + // if we successfully encoded, then stream out the compressed data... + if (encoded) + { + // "append" (set) the data we "streamed" (memcopied) for writing to the formatted image + // with side-effect of setting the actually encoded size to same + compressedImageOut.allocateData(offset); + memcpy(compressedImageOut.getData(), buffer, offset); + compressedImageOut.updateData(); // update width, height etc from header + } + return encoded; + } + + void setImage(const LLImageRaw& raw) + { + opj_image_cmptparm_t cmptparm[MAX_ENCODED_DISCARD_LEVELS]; + memset(&cmptparm[0], 0, MAX_ENCODED_DISCARD_LEVELS * sizeof(opj_image_cmptparm_t)); + + S32 numcomps = raw.getComponents(); + S32 width = raw.getWidth(); + S32 height = raw.getHeight(); + + for (S32 c = 0; c < numcomps; c++) + { + cmptparm[c].prec = 8; + cmptparm[c].bpp = 8; + cmptparm[c].sgnd = 0; + cmptparm[c].dx = parameters.subsampling_dx; + cmptparm[c].dy = parameters.subsampling_dy; + cmptparm[c].w = width; + cmptparm[c].h = height; + } + + image = opj_image_create(numcomps, &cmptparm[0], OPJ_CLRSPC_SRGB); + + image->x1 = width; + image->y1 = height; + + const U8 *src_datap = raw.getData(); + + S32 i = 0; + for (S32 y = height - 1; y >= 0; y--) + { + for (S32 x = 0; x < width; x++) + { + const U8 *pixel = src_datap + (y*width + x) * numcomps; + for (S32 c = 0; c < numcomps; c++) + { + image->comps[c].data[i] = *pixel; + pixel++; + } + i++; + } + } + + // This likely works, but there seems to be an issue openjpeg side + // check over after gixing that. + + // De-interleave to component plane data + /* + switch (numcomps) + { + case 0: + default: + break; + + case 1: + { + U32 rBitDepth = image->comps[0].bpp; + U32 bytesPerPixel = rBitDepth >> 3; + memcpy(image->comps[0].data, src, width * height * bytesPerPixel); + } + break; + + case 2: + { + U32 rBitDepth = image->comps[0].bpp; + U32 gBitDepth = image->comps[1].bpp; + U32 totalBitDepth = rBitDepth + gBitDepth; + U32 bytesPerPixel = totalBitDepth >> 3; + U32 stride = width * bytesPerPixel; + U32 offset = 0; + for (S32 y = height - 1; y >= 0; y--) + { + const U8* component = src + (y * stride); + for (S32 x = 0; x < width; x++) + { + image->comps[0].data[offset] = *component++; + image->comps[1].data[offset] = *component++; + offset++; + } + } + } + break; + + case 3: + { + U32 rBitDepth = image->comps[0].bpp; + U32 gBitDepth = image->comps[1].bpp; + U32 bBitDepth = image->comps[2].bpp; + U32 totalBitDepth = rBitDepth + gBitDepth + bBitDepth; + U32 bytesPerPixel = totalBitDepth >> 3; + U32 stride = width * bytesPerPixel; + U32 offset = 0; + for (S32 y = height - 1; y >= 0; y--) + { + const U8* component = src + (y * stride); + for (S32 x = 0; x < width; x++) + { + image->comps[0].data[offset] = *component++; + image->comps[1].data[offset] = *component++; + image->comps[2].data[offset] = *component++; + offset++; + } + } + } + break; + + + case 4: + { + U32 rBitDepth = image->comps[0].bpp; + U32 gBitDepth = image->comps[1].bpp; + U32 bBitDepth = image->comps[2].bpp; + U32 aBitDepth = image->comps[3].bpp; + + U32 totalBitDepth = rBitDepth + gBitDepth + bBitDepth + aBitDepth; + U32 bytesPerPixel = totalBitDepth >> 3; + + U32 stride = width * bytesPerPixel; + U32 offset = 0; + for (S32 y = height - 1; y >= 0; y--) + { + const U8* component = src + (y * stride); + for (S32 x = 0; x < width; x++) + { + image->comps[0].data[offset] = *component++; + image->comps[1].data[offset] = *component++; + image->comps[2].data[offset] = *component++; + image->comps[3].data[offset] = *component++; + offset++; + } + } + } + break; + }*/ + } + + opj_image_t* getImage() { return image; } + +private: + opj_cparameters_t parameters; + opj_event_mgr_t event_mgr; + opj_image_t* image = nullptr; + opj_codec_t* encoder = nullptr; + opj_stream_t* stream = nullptr; + char* comment_text = nullptr; +}; - /* catch events using our callbacks and give a local context */ - opj_set_event_mgr((opj_common_ptr)dinfo, &event_mgr, stderr); - /* setup the decoder decoding parameters using user parameters */ - opj_setup_decoder(dinfo, ¶meters); +LLImageJ2COJ::LLImageJ2COJ() + : LLImageJ2CImpl() +{ +} - /* open a byte stream */ - cio = opj_cio_open((opj_common_ptr)dinfo, base.getData(), base.getDataSize()); - /* decode the stream and fill the image structure */ - image = opj_decode(dinfo, cio); +LLImageJ2COJ::~LLImageJ2COJ() +{ +} - /* close the byte stream */ - opj_cio_close(cio); +bool LLImageJ2COJ::initDecode(LLImageJ2C &base, LLImageRaw &raw_image, int discard_level, int* region) +{ + base.mDiscardLevel = discard_level; + return false; +} - /* free remaining structures */ - if(dinfo) - { - opj_destroy_decompress(dinfo); - } +bool LLImageJ2COJ::initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int blocks_size, int precincts_size, int levels) +{ + // No specific implementation for this method in the OpenJpeg case + return false; +} - if(!image) - { - LL_WARNS() << "ERROR -> getMetadata: failed to decode image!" << LL_ENDL; - return false; - } +bool LLImageJ2COJ::decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count) +{ + JPEG2KDecode decoder(0); + + U32 image_channels = 0; + S32 data_size = base.getDataSize(); + S32 max_bytes = (base.getMaxBytes() ? base.getMaxBytes() : data_size); + bool decoded = decoder.decode(base.getData(), max_bytes, &image_channels, base.mDiscardLevel); + + // set correct channel count early so failed decodes don't miss it... + S32 channels = (S32)image_channels - first_channel; + channels = llmin(channels, max_channel_count); + + if (!decoded) + { + // reset the channel count if necessary + if (raw_image.getComponents() != channels) + { + raw_image.resize(raw_image.getWidth(), raw_image.getHeight(), S8(channels)); + } + + LL_DEBUGS("Texture") << "ERROR -> decodeImpl: failed to decode image!" << LL_ENDL; + return true; // done + } + + opj_image_t *image = decoder.getImage(); + + // Component buffers are allocated in an image width by height buffer. + // The image placed in that buffer is ceil(width/2^factor) by + // ceil(height/2^factor) and if the factor isn't zero it will be at the + // top left of the buffer with black filled in the rest of the pixels. + // It is integer math so the formula is written in ceildivpo2. + // (Assuming all the components have the same width, height and + // factor.) + U32 comp_width = image->comps[0].w; // leave this unshifted by 'f' discard factor, the strides are always for the full buffer width + U32 f = image->comps[0].factor; + + // do size the texture to the mem we'll acrually use... + U32 width = image->comps[0].w; + U32 height = image->comps[0].h; + + raw_image.resize(U16(width), U16(height), S8(channels)); + + U8 *rawp = raw_image.getData(); + + // first_channel is what channel to start copying from + // dest is what channel to copy to. first_channel comes from the + // argument, dest always starts writing at channel zero. + for (S32 comp = first_channel, dest = 0; comp < first_channel + channels; comp++, dest++) + { + llassert(image->comps[comp].data); + if (image->comps[comp].data) + { + S32 offset = dest; + for (S32 y = (height - 1); y >= 0; y--) + { + for (S32 x = 0; x < width; x++) + { + rawp[offset] = image->comps[comp].data[y*comp_width + x]; + offset += channels; + } + } + } + else // Some rare OpenJPEG versions have this bug. + { + LL_DEBUGS("Texture") << "ERROR -> decodeImpl: failed! (OpenJPEG bug)" << LL_ENDL; + } + } + + base.setDiscardLevel(f); + + return true; // done +} - // Copy image data into our raw image format (instead of the separate channel format - S32 width = 0; - S32 height = 0; - S32 img_components = image->numcomps; - width = image->x1 - image->x0; - height = image->y1 - image->y0; - base.setSize(width, height, img_components); +bool LLImageJ2COJ::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time, bool reversible) +{ + JPEG2KEncode encode(comment_text, reversible); + bool encoded = encode.encode(raw_image, base); + if (encoded) + { + LL_WARNS() << "Openjpeg encoding implementation isn't complete, returning false" << LL_ENDL; + } + //return encoded; + return false; +} - /* free image data structure */ - opj_image_destroy(image); - return true; +bool LLImageJ2COJ::getMetadata(LLImageJ2C &base) +{ + JPEG2KDecode decode(0); + + S32 width = 0; + S32 height = 0; + S32 components = 0; + S32 discard_level = 0; + + U32 dataSize = base.getDataSize(); + U8* data = base.getData(); + bool header_read = decode.readHeader(data, dataSize, width, height, components, discard_level); + if (!header_read) + { + return false; + } + + base.mDiscardLevel = discard_level; + base.setSize(width, height, components); + return true; } diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 7d78ec9e3c..4fe83f0c45 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1825,9 +1825,9 @@ if (WINDOWS) ${SHARED_LIB_STAGING_DIR}/Release/libcollada14dom22.dll ${SHARED_LIB_STAGING_DIR}/RelWithDebInfo/libcollada14dom22.dll ${SHARED_LIB_STAGING_DIR}/Debug/libcollada14dom22-d.dll - ${SHARED_LIB_STAGING_DIR}/Release/openjpeg.dll - ${SHARED_LIB_STAGING_DIR}/RelWithDebInfo/openjpeg.dll - ${SHARED_LIB_STAGING_DIR}/Debug/openjpegd.dll + ${SHARED_LIB_STAGING_DIR}/Release/openjp2.dll + ${SHARED_LIB_STAGING_DIR}/RelWithDebInfo/openjp2.dll + ${SHARED_LIB_STAGING_DIR}/Debug/openjp2.dll ${SHARED_LIB_STAGING_DIR}/Release/libhunspell.dll ${SHARED_LIB_STAGING_DIR}/RelWithDebInfo/libhunspell.dll ${SHARED_LIB_STAGING_DIR}/Debug/libhunspell.dll diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index de5ac5ed3d..a1d2d12779 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -518,7 +518,7 @@ class WindowsManifest(ViewerManifest): self.path("alut.dll") # For textures - self.path("openjpeg.dll") + self.path("openjp2.dll") # Uriparser self.path("uriparser.dll") @@ -1498,7 +1498,7 @@ class Linux_i686_Manifest(LinuxManifest): self.path("libdirectfb-1.*.so.*") self.path("libfusion-1.*.so.*") self.path("libdirect-1.*.so.*") - self.path("libopenjpeg.so*") + self.path("libopenjp2.so*") self.path("libdirectfb-1.4.so.5") self.path("libfusion-1.4.so.5") self.path("libdirect-1.4.so.5*") |