diff --git a/autobuild.xml b/autobuild.xml index 6fcae6e218..3ff5bd086c 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2708,9 +2708,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 53966a7ba6342395acb7ce15bc3fbe0a + 6bb03fe0b9dd44d3850ae9cae2161d76 url - http://3p.firestormviewer.org/openjpeg-2.3.1.203000304-darwin-203000304.tar.bz2 + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/105416/922669/openjpeg-2.5.0.575468-darwin64-575468.tar.bz2 name darwin64 @@ -2732,9 +2732,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - ba2034b4a372fd46c3e09f56bede38a7 + a01d66d72d7c865364cd5761e56e8735 url - http://3p.firestormviewer.org/openjpeg-2.4.0.211361403-windows-211361403.tar.bz2 + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/105418/922681/openjpeg-2.5.0.575468-windows-575468.tar.bz2 name windows @@ -2744,16 +2744,16 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - d7ac606703a9330a2d8a3f7276cb6894 + 8bd727bc7d6e0e6223e9ea49c14dd511 url - http://3p.firestormviewer.org/openjpeg-2.4.0.211361407-windows64-211361407.tar.bz2 + https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/105419/922682/openjpeg-2.5.0.575468-windows64-575468.tar.bz2 name windows64 version - 2.4.0 + 2.5.0.575468 openssl diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index e4a2ada897..18ed3b1e1c 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -250,7 +250,7 @@ elseif(LINUX) ${EXPAT_COPY} 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 cdbc6c5841..76556b3321 100644 --- a/indra/cmake/FindOpenJPEG.cmake +++ b/indra/cmake/FindOpenJPEG.cmake @@ -9,16 +9,17 @@ # 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-2.1 /usr/local/include/openjpeg /usr/local/include /usr/include/openjpeg-2.1 /usr/include/openjpeg /usr/include +include/openjpeg ) -SET(OPENJPEG_NAMES ${OPENJPEG_NAMES} openjpeg openjp2) +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 f254094df6..a078c97cb8 100644 --- a/indra/cmake/OpenJPEG.cmake +++ b/indra/cmake/OpenJPEG.cmake @@ -8,8 +8,7 @@ if (USESYSTEMLIBS) include(FindOpenJPEG) else (USESYSTEMLIBS) use_prebuilt_binary(openjpeg) - - set(OPENJPEG_LIBRARIES openjp2) + set(OPENJPEG_LIBRARIES openjp2) set(OPENJPEG_INCLUDE_DIR ${LIBS_PREBUILT_DIR}/include/openjpeg) endif (USESYSTEMLIBS) diff --git a/indra/llimagej2coj/llimagej2coj.cpp b/indra/llimagej2coj/llimagej2coj.cpp index 08f03c7bdb..4f8aa59c1a 100644 --- a/indra/llimagej2coj/llimagej2coj.cpp +++ b/indra/llimagej2coj/llimagej2coj.cpp @@ -25,184 +25,48 @@ */ #include "linden_common.h" -// #include "fstelemetry.h" // instrument image decodes #include "llimagej2coj.h" -#define OPENJPEG2 // this is defined so that we get static linking. #include "openjpeg.h" -#ifndef OPENJPEG2 -#include "cio.h" -#endif #include "event.h" +#include "cio.h" -#include "lltimer.h" - -// [SL:KB] - Patch: Viewer-OpenJPEG2 | Checked: Catznip-5.3 -#ifdef OPENJPEG2 -class LLJp2StreamReader { -public: - LLJp2StreamReader(LLImageJ2C* pImage) : m_pImage(pImage), m_Position(0) { } - - static OPJ_SIZE_T readStream(void* pBufferOut, OPJ_SIZE_T szBufferOut, void* pUserData) - { - LLJp2StreamReader* pStream = (LLJp2StreamReader*)pUserData; - if ( (!pBufferOut) || (!pStream) || (!pStream->m_pImage) ) - return (OPJ_SIZE_T)-1; - - OPJ_SIZE_T szBufferRead = llmin(szBufferOut, pStream->m_pImage->getDataSize() - pStream->m_Position); - if (!szBufferRead) - return (OPJ_SIZE_T)-1; - - memcpy(pBufferOut, pStream->m_pImage->getData() + pStream->m_Position, szBufferRead); - pStream->m_Position += szBufferRead; - return szBufferRead; - } - - static OPJ_OFF_T skipStream(OPJ_OFF_T bufferOffset, void* pUserData) - { - LLJp2StreamReader* pStream = (LLJp2StreamReader*)pUserData; - if ( (!pStream) || (!pStream->m_pImage) ) - return (OPJ_SIZE_T)-1; - - if (bufferOffset < 0) - { - // Skipping backward - if (pStream->m_Position == 0) - return (OPJ_SIZE_T)-1; // Already at the start of the stream - else if (pStream->m_Position + bufferOffset < 0) - bufferOffset = -(OPJ_OFF_T)pStream->m_Position; // Don't underflow - } - else - { - // Skipping forward - OPJ_SIZE_T szRemaining = pStream->m_pImage->getDataSize() - pStream->m_Position; - if (!szRemaining) - return (OPJ_SIZE_T)-1; // Already at the end of the stream - else if (bufferOffset > szRemaining) - bufferOffset = szRemaining; // Don't overflow - } - pStream->m_Position += bufferOffset; - - return bufferOffset; - } - - static OPJ_BOOL seekStream(OPJ_OFF_T bufferOffset, void* pUserData) - { - LLJp2StreamReader* pStream = (LLJp2StreamReader*)pUserData; - if ( (!pStream) || (!pStream->m_pImage) ) - return OPJ_FALSE; - - if ( (bufferOffset < 0) || (bufferOffset > pStream->m_pImage->getDataSize()) ) - return OPJ_FALSE; - - pStream->m_Position = bufferOffset; - return OPJ_TRUE; - } -protected: - LLImageJ2C* m_pImage = nullptr; - OPJ_SIZE_T m_Position = 0; -}; - -class LLJp2StreamWriter { -public: - LLJp2StreamWriter(LLImageJ2C* pImage) : m_pImage(pImage), m_Position(0) { } - - static OPJ_SIZE_T writeStream(void* pBufferIn, OPJ_SIZE_T szBufferIn, void* pUserData) - { - LLJp2StreamWriter* pStream = (LLJp2StreamWriter*)pUserData; - if ( (!pBufferIn) || (!pStream) || (!pStream->m_pImage) ) - return (OPJ_SIZE_T)-1; - - if (pStream->m_Position + szBufferIn > pStream->m_pImage->getDataSize()) - pStream->m_pImage->reallocateData(pStream->m_Position + szBufferIn); - - memcpy(pStream->m_pImage->getData() + pStream->m_Position, pBufferIn, szBufferIn); - pStream->m_Position += szBufferIn; - return szBufferIn; - } - - static OPJ_OFF_T skipStream(OPJ_OFF_T bufferOffset, void* pUserData) - { - LLJp2StreamWriter* pStream = (LLJp2StreamWriter*)pUserData; - if ( (!pStream) || (!pStream->m_pImage) ) - return -1; - - if (bufferOffset < 0) - { - // Skipping backward - if (pStream->m_Position == 0) - return -1; // Already at the start of the stream - else if (pStream->m_Position + bufferOffset < 0) - bufferOffset = -pStream->m_Position; // Don't underflow - } - else - { - // Skipping forward - if (pStream->m_Position + bufferOffset > pStream->m_pImage->getDataSize()) - return -1; // Don't allow skipping past the end of the stream - } - - pStream->m_Position += bufferOffset; - return bufferOffset; - } - - static OPJ_BOOL seekStream(OPJ_OFF_T bufferOffset, void* pUserData) - { - LLJp2StreamWriter* pStream = (LLJp2StreamWriter*)pUserData; - if ( (!pStream) || (!pStream->m_pImage) ) - return OPJ_FALSE; - - if ( (bufferOffset < 0) || (bufferOffset > pStream->m_pImage->getDataSize()) ) - return OPJ_FALSE; - - pStream->m_Position = bufferOffset; - return OPJ_TRUE; - } - -protected: - LLImageJ2C* m_pImage = nullptr; - OPJ_OFF_T m_Position = 0; -}; -#endif -// [/SL:KB] +#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(); -#elif defined OPJ_PACKAGE_VERSION - return std::string("OpenJPEG: " OPJ_PACKAGE_VERSION ", Runtime: ") + opj_version(); -#else -#ifdef OPENJPEG2 - return llformat("OpenJPEG: %i.%i.%i, Runtime: %s", OPJ_VERSION_MAJOR, OPJ_VERSION_MINOR, OPJ_VERSION_BUILD, opj_version()); -#else - return std::string("OpenJPEG Runtime: ") + opj_version(); -#endif -#endif +// OpenJpeg fixes +//#ifdef OPENJPEG_VERSION +// return std::string("OpenJPEG: " OPENJPEG_VERSION ", Runtime: ") +// + opj_version(); +//#else +// return std::string("OpenJPEG runtime: ") + opj_version(); +//#endif + return llformat("OpenJPEG: %i.%i.%i, Runtime: %s", OPJ_VERSION_MAJOR, OPJ_VERSION_MINOR, OPJ_VERSION_BUILD, opj_version()); +// } // 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; } /** @@ -210,29 +74,642 @@ 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; } +class JPEG2KBase +{ +public: + JPEG2KBase() {} + + U8* buffer = nullptr; + OPJ_SIZE_T size = 0; + OPJ_OFF_T offset = 0; +}; + +#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 +} + +static void opj_warn(const char* msg, void* user_data) +{ + llassert(user_data); +#if WANT_VERBOSE_OPJ_SPAM + LL_WARNS("OpenJPEG") << msg << LL_ENDL; +#endif +} + +static void opj_error(const char* msg, void* user_data) +{ + llassert(user_data); +#if WANT_VERBOSE_OPJ_SPAM + LL_WARNS("OpenJPEG") << msg << LL_ENDL; +#endif +} + +static OPJ_SIZE_T opj_read(void * buffer, OPJ_SIZE_T bytes, void* user_data) +{ + llassert(user_data); + JPEG2KBase* jpeg_codec = static_cast(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; +} + +static OPJ_SIZE_T opj_write(void * buffer, OPJ_SIZE_T bytes, void* user_data) +{ + llassert(user_data); + JPEG2KBase* jpeg_codec = static_cast(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; +} + +static OPJ_OFF_T opj_skip(OPJ_OFF_T bytes, void* user_data) +{ + JPEG2KBase* jpeg_codec = static_cast(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; + } + + if (jpeg_codec->offset < 0) + { + // Shouldn't be possible? + jpeg_codec->offset = 0; + return (OPJ_OFF_T)-1; + } + + return bytes; +} + +static OPJ_BOOL opj_seek(OPJ_OFF_T bytes, void * user_data) +{ + JPEG2KBase* jpeg_codec = static_cast(user_data); + jpeg_codec->offset = bytes; + jpeg_codec->offset = llclamp(U32(jpeg_codec->offset), U32(0), U32(jpeg_codec->size)); + return OPJ_TRUE; +} + +static void opj_free_user_data(void * user_data) +{ + JPEG2KBase* jpeg_codec = static_cast(user_data); + // Don't free, data is managed externally + jpeg_codec->buffer = nullptr; + jpeg_codec->size = 0; + jpeg_codec->offset = 0; +} + +static void opj_free_user_data_write(void * user_data) +{ + JPEG2KBase* jpeg_codec = static_cast(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; +} + +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 = OPJ_CODEC_J2K; + 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.prog_order = OPJ_RLCP; + parameters.cp_disto_alloc = 1; + + if (!opj_setup_encoder(encoder, ¶meters, image)) + { + return false; + } + + 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; + + memset(buffer, 0, data_size_guess); + + 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; +}; + LLImageJ2COJ::LLImageJ2COJ() : LLImageJ2CImpl() @@ -246,7 +723,7 @@ LLImageJ2COJ::~LLImageJ2COJ() bool LLImageJ2COJ::initDecode(LLImageJ2C &base, LLImageRaw &raw_image, int discard_level, int* region) { - // No specific implementation for this method in the OpenJpeg case + base.mDiscardLevel = discard_level; return false; } @@ -258,656 +735,165 @@ bool LLImageJ2COJ::initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int block bool LLImageJ2COJ::decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count) { - // texture comment metadata reader - LL_PROFILE_ZONE_SCOPED; // instrument image decodes - U8* c_data = base.getData(); - S32 c_size = base.getDataSize(); - S32 position = 0; - - while (position < 1024 && position < (c_size - 7)) // the comment field should be in the first 1024 bytes. - { - if (c_data[position] == 0xff && c_data[position + 1] == 0x64) - { - U8 high_byte = c_data[position + 2]; - U8 low_byte = c_data[position + 3]; - S32 c_length = (high_byte * 256) + low_byte; // This size also counts the markers, 00 01 and itself - if (c_length > 200) // sanity check - { - // While comments can be very long, anything longer then 200 is suspect. - break; - } + JPEG2KDecode decoder(0); - if (position + 2 + c_length > c_size) - { - // comment extends past end of data, corruption, or all data not retrived yet. - break; - } + // texture comment metadata reader + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; // instrument image decodes + U8* c_data = base.getData(); + S32 c_size = base.getDataSize(); + S32 position = 0; - // if the comment block does not end at the end of data, check to see if the next - // block starts with 0xFF - if (position + 2 + c_length < c_size && c_data[position + 2 + c_length] != 0xff) - { - // invalied comment block - break; - } + while (position < 1024 && position < (c_size - 7)) // the comment field should be in the first 1024 bytes. + { + if (c_data[position] == 0xff && c_data[position + 1] == 0x64) + { + U8 high_byte = c_data[position + 2]; + U8 low_byte = c_data[position + 3]; + S32 c_length = (high_byte * 256) + low_byte; // This size also counts the markers, 00 01 and itself + if (c_length > 200) // sanity check + { + // While comments can be very long, anything longer then 200 is suspect. + break; + } - // extract the comment minus the markers, 00 01 - raw_image.mComment.assign((char*)(c_data + position + 6), c_length - 4); - break; - } - position++; - } - // + if (position + 2 + c_length > c_size) + { + // comment extends past end of data, corruption, or all data not retrived yet. + break; + } - LLTimer decode_timer; + // if the comment block does not end at the end of data, check to see if the next + // block starts with 0xFF + if (position + 2 + c_length < c_size && c_data[position + 2 + c_length] != 0xff) + { + // invalied comment block + break; + } - opj_dparameters_t parameters; /* decompression parameters */ - opj_event_mgr_t event_mgr; /* event manager */ - opj_image_t *image = NULL; + // extract the comment minus the markers, 00 01 + raw_image.mComment.assign((char*)(c_data + position + 6), c_length - 4); + break; + } + position++; + } + // -#ifndef OPENJPEG2 - opj_dinfo_t* dinfo = NULL; /* handle to a decompressor */ - opj_cio_t *cio = NULL; -#endif + 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); - /* 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 correct channel count early so failed decodes don't miss it... + S32 channels = (S32)image_channels - first_channel; + channels = llmin(channels, max_channel_count); - /* set decoding parameters to default values */ - opj_set_default_decoder_parameters(¶meters); + if (!decoded) + { + // reset the channel count if necessary + if (raw_image.getComponents() != channels) + { + raw_image.resize(raw_image.getWidth(), raw_image.getHeight(), S8(channels)); + } - parameters.cp_reduce = base.getRawDiscardLevel(); + LL_DEBUGS("Texture") << "ERROR -> decodeImpl: failed to decode image!" << LL_ENDL; + // [SL:KB] - Patch: Viewer-OpenJPEG2 | Checked: Catznip-5.3 + base.decodeFailed(); + // [SL:KB] + return true; // done + } - /* decode the code-stream */ - /* ---------------------- */ + opj_image_t *image = decoder.getImage(); - /* JPEG-2000 codestream */ + // 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; -#ifdef OPENJPEG2 -// [SL:KB] - Patch: Viewer-OpenJPEG2 | Checked: Catznip-5.3 - /* get a decoder handle */ - opj_codec_t* opj_decoder_p = opj_create_decompress(OPJ_CODEC_J2K); + // do size the texture to the mem we'll acrually use... + U32 width = image->comps[0].w; + U32 height = image->comps[0].h; - /* catch events using our callbacks and give a local context */ - opj_set_error_handler(opj_decoder_p, error_callback, 0); - opj_set_warning_handler(opj_decoder_p, warning_callback, 0); - opj_set_info_handler(opj_decoder_p, info_callback, 0); + raw_image.resize(U16(width), U16(height), S8(channels)); - /* setup the decoder decoding parameters using user parameters */ - opj_setup_decoder(opj_decoder_p, ¶meters); + U8 *rawp = raw_image.getData(); - /* allow multi-threading */ - if (opj_has_thread_support()) - { - opj_codec_set_threads(opj_decoder_p, opj_get_num_cpus()); - } + // Port fix for MAINT-4327/MAINT-6584 to OpenJPEG decoder + if (!rawp) + { + base.setLastError("Memory error"); + base.decodeFailed(); + return true; // done + } + // - /* open a byte stream */ - LLJp2StreamReader streamReader(&base); - opj_stream_t* opj_stream_p = opj_stream_default_create(OPJ_STREAM_READ); - opj_stream_set_read_function(opj_stream_p, LLJp2StreamReader::readStream); - opj_stream_set_skip_function(opj_stream_p, LLJp2StreamReader::skipStream); - opj_stream_set_seek_function(opj_stream_p, LLJp2StreamReader::seekStream); - opj_stream_set_user_data(opj_stream_p, &streamReader, nullptr); - opj_stream_set_user_data_length(opj_stream_p, base.getDataSize()); + // 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; + // [SL:KB] - Patch: Viewer-OpenJPEG2 | Checked: Catznip-5.3 + base.decodeFailed(); + // [SL:KB] + } + } - /* decode the stream and fill the image structure */ - bool fSuccess = opj_read_header(opj_stream_p, opj_decoder_p, &image) && - opj_decode(opj_decoder_p, opj_stream_p, image) && - opj_end_decompress(opj_decoder_p, opj_stream_p); + base.setDiscardLevel(f); - /* close the byte stream */ - opj_stream_destroy(opj_stream_p); - - /* free remaining structures */ - opj_destroy_codec(opj_decoder_p); -#else - /* 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); - } -#endif -// [/SL:KB] - - // 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. -// [SL:KB] - Patch: Viewer-OpenJPEG2 | Checked: Catznip-5.3 -#ifdef OPENJPEG2 - if ( (!fSuccess) || (!image) || (!image->numcomps) ) -#else - if(!image || !image->numcomps) -#endif -// [/SL:KB] - { - LL_DEBUGS("Texture") << "ERROR -> decodeImpl: failed to decode image!" << LL_ENDL; - if (image) - { - opj_image_destroy(image); - } - -// [SL:KB] - Patch: Viewer-OpenJPEG2 | Checked: Catznip-5.3 - base.decodeFailed(); -// [SL:KB] - 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 -// -//// [SN:SG] - Patch: Import-MiscOpenJPEG -// LL_WARNS("Texture") << "Expected discard level not reached!" << LL_ENDL; -// base.decodeFailed(); -//// [SN:SG] -//// 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); - } - -// [SN:SG] - Patch: Import-MiscOpenJPEG - base.decodeFailed(); -// [SN:SG] - - 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(); - - // Port fix for MAINT-4327/MAINT-6584 to OpenJPEG decoder - if (!rawp) - { - base.setLastError("Memory error"); - base.decodeFailed(); - opj_image_destroy(image); - return true; // done - } - // - - // 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); - -// [SN:SG] - Patch: Import-MiscOpenJPEG - base.decodeFailed(); -// [SN:SG] - return true; // done - } - } - - /* free image data structure */ - opj_image_destroy(image); - - return true; // done + return true; // done } bool LLImageJ2COJ::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time, bool reversible) { - 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 - // -// [SL:KB] - Patch: Viewer-OpenJPEG2 | Checked: Catznip-5.3 -#ifdef OPENJPEG2 - OPJ_COLOR_SPACE color_space = OPJ_CLRSPC_SRGB; -#else - OPJ_COLOR_SPACE color_space = CLRSPC_SRGB; -#endif -// [/SL:KB] - 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 */ - /* ---------------------------- */ - -// [SL:KB] - Patch: Viewer-OpenJPEG2 | Checked: Catznip-5.3 -#ifdef OPENJPEG2 - /* get a J2K compressor handle */ - opj_codec_t* opj_encoder_p = opj_create_compress(OPJ_CODEC_J2K); - - /* catch events using our callbacks and give a local context */ - opj_set_error_handler(opj_encoder_p, error_callback, 0); - opj_set_warning_handler(opj_encoder_p, warning_callback, 0); - opj_set_info_handler(opj_encoder_p, info_callback, 0); - - /* setup the encoder parameters using the current image and using user parameters */ - bool fSuccess = opj_setup_encoder(opj_encoder_p, ¶meters, image); - if (!fSuccess) - { - opj_destroy_codec(opj_encoder_p); - opj_image_destroy(image); - LL_DEBUGS("Texture") << "Failed to encode image." << LL_ENDL; - return false; - } - - /* open a byte stream for writing */ - /* allocate memory for all tiles */ - LLJp2StreamWriter streamWriter(&base); - opj_stream_t* opj_stream_p = opj_stream_default_create(OPJ_STREAM_WRITE); - opj_stream_set_write_function(opj_stream_p, LLJp2StreamWriter::writeStream); - opj_stream_set_skip_function(opj_stream_p, LLJp2StreamWriter::skipStream); - opj_stream_set_seek_function(opj_stream_p, LLJp2StreamWriter::seekStream); - opj_stream_set_user_data(opj_stream_p, &streamWriter, nullptr); - opj_stream_set_user_data_length(opj_stream_p, raw_image.getDataSize()); - - /* encode the image */ - fSuccess = opj_start_compress(opj_encoder_p, image, opj_stream_p) && - opj_encode(opj_encoder_p, opj_stream_p) && - opj_end_compress(opj_encoder_p, opj_stream_p); - if (!fSuccess) - { - opj_stream_destroy(opj_stream_p); - opj_destroy_codec(opj_encoder_p); - opj_image_destroy(image); - LL_DEBUGS("Texture") << "Failed to encode image." << LL_ENDL; - return false; - } - - base.updateData(); // set width, height - - /* close and free the byte stream */ - opj_stream_destroy(opj_stream_p); - - /* free remaining compression structures */ - opj_destroy_codec(opj_encoder_p); -#else - 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); -#endif - // [/SL:KB] - - /* free image data */ - opj_image_destroy(image); - return true; -} - -inline S32 extractLong4( U8 const *aBuffer, int nOffset ) -{ - S32 ret = aBuffer[ nOffset ] << 24; - ret += aBuffer[ nOffset + 1 ] << 16; - ret += aBuffer[ nOffset + 2 ] << 8; - ret += aBuffer[ nOffset + 3 ]; - return ret; -} - -inline S32 extractShort2( U8 const *aBuffer, int nOffset ) -{ - S32 ret = aBuffer[ nOffset ] << 8; - ret += aBuffer[ nOffset + 1 ]; - - return ret; -} - -inline bool isSOC( U8 const *aBuffer ) -{ - return aBuffer[ 0 ] == 0xFF && aBuffer[ 1 ] == 0x4F; -} - -inline bool isSIZ( U8 const *aBuffer ) -{ - return aBuffer[ 0 ] == 0xFF && aBuffer[ 1 ] == 0x51; -} - -bool getMetadataFast( LLImageJ2C &aImage, S32 &aW, S32 &aH, S32 &aComps ) -{ - const int J2K_HDR_LEN( 42 ); - const int J2K_HDR_X1( 8 ); - const int J2K_HDR_Y1( 12 ); - const int J2K_HDR_X0( 16 ); - const int J2K_HDR_Y0( 20 ); - const int J2K_HDR_NUMCOMPS( 40 ); - - if( aImage.getDataSize() < J2K_HDR_LEN ) - return false; - - U8 const* pBuffer = aImage.getData(); - - if( !isSOC( pBuffer ) || !isSIZ( pBuffer+2 ) ) - return false; - - S32 x1 = extractLong4( pBuffer, J2K_HDR_X1 ); - S32 y1 = extractLong4( pBuffer, J2K_HDR_Y1 ); - S32 x0 = extractLong4( pBuffer, J2K_HDR_X0 ); - S32 y0 = extractLong4( pBuffer, J2K_HDR_Y0 ); - S32 numComps = extractShort2( pBuffer, J2K_HDR_NUMCOMPS ); - - aComps = numComps; - aW = x1 - x0; - aH = y1 - y0; - - return true; + 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; } bool LLImageJ2COJ::getMetadata(LLImageJ2C &base) { - // - // FIXME: We get metadata by decoding the ENTIRE image. - // + JPEG2KDecode decode(0); - // Update the raw discard level - base.updateRawDiscardLevel(); + S32 width = 0; + S32 height = 0; + S32 components = 0; + S32 discard_level = 0; - S32 width(0); - S32 height(0); - S32 img_components(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; + } - if ( getMetadataFast( base, width, height, img_components ) ) - { - base.setSize(width, height, img_components); - return true; - } - - // Do it the old and slow way, decode the image with openjpeg - - opj_dparameters_t parameters; /* decompression parameters */ - opj_event_mgr_t event_mgr; /* event manager */ - opj_image_t *image = NULL; - -#ifndef OPENJPEG2 - opj_dinfo_t* dinfo = NULL; /* handle to a decompressor */ - opj_cio_t *cio = NULL; -#endif - - /* 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); - - // Only decode what's required to get the size data. -#ifndef OPENJPEG2 - parameters.cp_limit_decoding=LIMIT_TO_MAIN_HEADER; -#endif - - //parameters.cp_reduce = mRawDiscardLevel; - - /* decode the code-stream */ - /* ---------------------- */ - - /* JPEG-2000 codestream */ - -// [SL:KB] - Patch: Viewer-OpenJPEG2 | Checked: Catznip-5.3 -#ifdef OPENJPEG2 - /* get a decoder handle */ - opj_codec_t* opj_decoder_p = opj_create_decompress(OPJ_CODEC_J2K); - - /* catch events using our callbacks and give a local context */ - opj_set_error_handler(opj_decoder_p, error_callback, 0); - opj_set_warning_handler(opj_decoder_p, warning_callback, 0); - opj_set_info_handler(opj_decoder_p, info_callback, 0); - - /* setup the decoder decoding parameters using user parameters */ - bool fSuccess = opj_setup_decoder(opj_decoder_p, ¶meters); - if (!fSuccess) - { - opj_destroy_codec(opj_decoder_p); - LL_WARNS() << "ERROR -> getMetadata: failed to decode image!" << LL_ENDL; - return false; - } - - /* open a byte stream */ - LLJp2StreamReader streamReader(&base); - opj_stream_t* opj_stream_p = opj_stream_default_create(OPJ_STREAM_READ); - opj_stream_set_read_function(opj_stream_p, LLJp2StreamReader::readStream); - opj_stream_set_skip_function(opj_stream_p, LLJp2StreamReader::skipStream); - opj_stream_set_seek_function(opj_stream_p, LLJp2StreamReader::seekStream); - opj_stream_set_user_data(opj_stream_p, &streamReader, nullptr); - opj_stream_set_user_data_length(opj_stream_p, base.getDataSize()); - - /* decode the stream and fill the image structure */ - fSuccess = opj_read_header(opj_stream_p, opj_decoder_p, &image); - if (!fSuccess) - { - opj_stream_destroy(opj_stream_p); - opj_destroy_codec(opj_decoder_p); - LL_WARNS() << "ERROR -> getMetadata: failed to decode image!" << LL_ENDL; - return false; - } - - /* close the byte stream */ - opj_stream_destroy(opj_stream_p); - - /* free remaining structures */ - opj_destroy_codec(opj_decoder_p); -#else - /* 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); - } -#endif -// [/SL:KB] - - if(!image) - { - LL_WARNS() << "ERROR -> getMetadata: failed to decode image!" << LL_ENDL; - return false; - } - - // Copy image data into our raw image format (instead of the separate channel format - - img_components = image->numcomps; - width = image->x1 - image->x0; - height = image->y1 - image->y0; - - base.setSize(width, height, img_components); - - /* free image data structure */ - opj_image_destroy(image); - return true; + base.mDiscardLevel = discard_level; + base.setSize(width, height, components); + return true; } diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index 1b0b25629f..100a9bbdcf 100644 --- a/indra/newview/llpanelprofile.cpp +++ b/indra/newview/llpanelprofile.cpp @@ -1589,6 +1589,8 @@ void LLPanelProfileSecondLife::fillRightsData() void LLPanelProfileSecondLife::fillAgeData(const LLDate &born_on) { // Fix LL UI/UX design accident + //// Date from server comes already converted to stl timezone, + //// so display it as an UTC + 0 //std::string name_and_date = getString("date_format"); //LLSD args_name; //args_name["datetime"] = (S32)born_on.secondsSinceEpoch(); diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 76057fbdfb..5ecc0b92cf 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -2154,7 +2154,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*")