diff options
Diffstat (limited to 'indra/llimagej2coj/llimagej2coj.cpp')
-rw-r--r-- | indra/llimagej2coj/llimagej2coj.cpp | 209 |
1 files changed, 149 insertions, 60 deletions
diff --git a/indra/llimagej2coj/llimagej2coj.cpp b/indra/llimagej2coj/llimagej2coj.cpp index a64ae8e55c..6ebbe359b9 100644 --- a/indra/llimagej2coj/llimagej2coj.cpp +++ b/indra/llimagej2coj/llimagej2coj.cpp @@ -32,8 +32,6 @@ #include <openjpeg-2.5/event.h> #include <openjpeg-2.5/cio.h> -#define MAX_ENCODED_DISCARD_LEVELS 5 - // Factory function: see declaration in llimagej2c.cpp LLImageJ2CImpl* fallbackCreateLLImageJ2CImpl() { @@ -132,73 +130,96 @@ static void opj_error(const char* msg, void* user_data) static OPJ_SIZE_T opj_read(void * buffer, OPJ_SIZE_T bytes, void* user_data) { - llassert(user_data); + llassert(user_data && buffer); + JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data); - OPJ_SIZE_T remainder = (jpeg_codec->size - jpeg_codec->offset); - if (remainder <= 0) + + if (jpeg_codec->offset < 0 || static_cast<OPJ_SIZE_T>(jpeg_codec->offset) >= jpeg_codec->size) { jpeg_codec->offset = jpeg_codec->size; - // Indicate end of stream (hacky?) - return (OPJ_OFF_T)-1; + return static_cast<OPJ_SIZE_T>(-1); // Indicate EOF } - OPJ_SIZE_T to_read = llclamp(U32(bytes), U32(0), U32(remainder)); + + OPJ_SIZE_T remainder = jpeg_codec->size - static_cast<OPJ_SIZE_T>(jpeg_codec->offset); + OPJ_SIZE_T to_read = (bytes < remainder) ? bytes : 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); + llassert(user_data && buffer); + JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data); - OPJ_SIZE_T remainder = jpeg_codec->size - jpeg_codec->offset; - if (remainder < bytes) + OPJ_OFF_T required_offset = jpeg_codec->offset + static_cast<OPJ_OFF_T>(bytes); + + // Overflow check + if (required_offset < jpeg_codec->offset) + return 0; // Overflow detected + + // Resize if needed (exponential growth) + if (required_offset > static_cast<OPJ_OFF_T>(jpeg_codec->size)) { - OPJ_SIZE_T new_size = jpeg_codec->size + (bytes - remainder); + OPJ_SIZE_T new_size = jpeg_codec->size ? jpeg_codec->size : 1024; + while (required_offset > static_cast<OPJ_OFF_T>(new_size)) + new_size *= 2; + + const OPJ_SIZE_T MAX_BUFFER_SIZE = 512 * 1024 * 1024; // 512 MB, increase if needed + if (new_size > MAX_BUFFER_SIZE) return 0; + 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; + if (!new_buffer) return 0; // Allocation failed + + if (jpeg_codec->offset > 0) + memcpy(new_buffer, jpeg_codec->buffer, static_cast<size_t>(jpeg_codec->offset)); + + ll_aligned_free_16(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; + + memcpy(jpeg_codec->buffer + jpeg_codec->offset, buffer, static_cast<size_t>(bytes)); + jpeg_codec->offset = required_offset; return bytes; } static OPJ_OFF_T opj_skip(OPJ_OFF_T bytes, void* user_data) { + llassert(user_data); JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data); - jpeg_codec->offset += bytes; - if (jpeg_codec->offset > (OPJ_OFF_T)jpeg_codec->size) - { - jpeg_codec->offset = jpeg_codec->size; - // Indicate end of stream - return (OPJ_OFF_T)-1; - } + OPJ_OFF_T new_offset = jpeg_codec->offset + bytes; - if (jpeg_codec->offset < 0) + if (new_offset < 0 || new_offset > static_cast<OPJ_OFF_T>(jpeg_codec->size)) { - // Shouldn't be possible? - jpeg_codec->offset = 0; + // Clamp and indicate EOF or error + jpeg_codec->offset = llclamp<OPJ_OFF_T>(new_offset, 0, static_cast<OPJ_OFF_T>(jpeg_codec->size)); return (OPJ_OFF_T)-1; } + jpeg_codec->offset = new_offset; return bytes; } -static OPJ_BOOL opj_seek(OPJ_OFF_T bytes, void * user_data) +static OPJ_BOOL opj_seek(OPJ_OFF_T offset, void * user_data) { + llassert(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)); + + if (offset < 0 || offset > static_cast<OPJ_OFF_T>(jpeg_codec->size)) + return OPJ_FALSE; + + jpeg_codec->offset = offset; return OPJ_TRUE; } static void opj_free_user_data(void * user_data) { + llassert(user_data); + JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data); // Don't free, data is managed externally jpeg_codec->buffer = nullptr; @@ -208,14 +229,54 @@ static void opj_free_user_data(void * user_data) static void opj_free_user_data_write(void * user_data) { + llassert(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; + if (jpeg_codec->buffer) + { + ll_aligned_free_16(jpeg_codec->buffer); + jpeg_codec->buffer = nullptr; + } jpeg_codec->size = 0; jpeg_codec->offset = 0; } +/** + * Estimates the number of layers necessary depending on the image surface (w x h) + */ +static U32 estimate_num_layers(U32 surface) +{ + if (surface <= 1024) return 2; // Tiny (≤32×32) + else if (surface <= 16384) return 3; // Small (≤128×128) + else if (surface <= 262144) return 4; // Medium (≤512×512) + else if (surface <= 1048576) return 5; // Up to ~1MP + else return 6; // Up to ~1.5–2MP +} + +/** + * Sets the parameters.tcp_rates according to the number of layers and a last tcp_rate value (which equals to the final compression ratio). + * + * Example for 6 layers: + * + * i = 5, parameters.tcp_rates[6 - 1 - 5] = 8.0f * (1 << (5 << 1)) = 8192 // Layer 5 (lowest quality) + * i = 4, parameters.tcp_rates[6 - 1 - 4] = 8.0f * (1 << (4 << 1)) = 2048 // Layer 4 + * i = 3, parameters.tcp_rates[6 - 1 - 3] = 8.0f * (1 << (3 << 1)) = 512 // Layer 3 + * i = 2, parameters.tcp_rates[6 - 1 - 2] = 8.0f * (1 << (2 << 1)) = 128 // Layer 2 + * i = 1, parameters.tcp_rates[6 - 1 - 1] = 8.0f * (1 << (1 << 1)) = 32 // Layer 1 + * i = 0, parameters.tcp_rates[6 - 1 - 0] = 8.0f * (1 << (0 << 1)) = 8 // Layer 0 (highest quality) + * + */ +static void set_tcp_rates(opj_cparameters_t* parameters, U32 num_layers = 1, F32 last_tcp_rate = LAST_TCP_RATE) +{ + parameters->tcp_numlayers = num_layers; + + for (int i = num_layers - 1; i >= 0; i--) + { + parameters->tcp_rates[num_layers - 1 - i] = last_tcp_rate * static_cast<F32>(1 << (i << 1)); + } +} + class JPEG2KDecode : public JPEG2KBase { public: @@ -430,24 +491,22 @@ public: opj_set_default_encoder_parameters(¶meters); parameters.cod_format = OPJ_CODEC_J2K; - parameters.cp_disto_alloc = 1; - parameters.max_cs_size = (1 << 15); + parameters.prog_order = OPJ_RLCP; // should be the default, but, just in case + parameters.cp_disto_alloc = 1; // enable rate allocation by distortion + parameters.max_cs_size = 0; // do not cap max size because we're using tcp_rates and also irrelevant with lossless. if (reversible) { + parameters.irreversible = 0; // should be the default, but, just in case parameters.tcp_numlayers = 1; - parameters.tcp_rates[0] = 1.0f; + /* documentation seems to be wrong, should be 0.0f for lossless, not 1.0f + see https://github.com/uclouvain/openjpeg/blob/e7453e398b110891778d8da19209792c69ca7169/src/lib/openjp2/j2k.c#L7817 + */ + parameters.tcp_rates[0] = 0.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) @@ -496,10 +555,23 @@ public: 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; + parameters.tcp_mct = (image->numcomps >= 3) ? 1 : 0; // no color transform for RGBA images + + + // if not lossless compression, computes tcp_numlayers and max_cs_size depending on the image dimensions + if( parameters.irreversible ) + { + + // computes a number of layers + U32 surface = rawImageIn.getWidth() * rawImageIn.getHeight(); + + // gets the necessary number of layers + U32 nb_layers = estimate_num_layers(surface); + + // fills parameters.tcp_rates and updates parameters.tcp_numlayers + set_tcp_rates(¶meters, nb_layers, LAST_TCP_RATE); + + } if (!opj_setup_encoder(encoder, ¶meters, image)) { @@ -510,7 +582,20 @@ public: 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 width_tiles = (rawImageIn.getWidth() >> 6); + U32 height_tiles = (rawImageIn.getHeight() >> 6); + + // Allow images with a width or height that are MIN_IMAGE_SIZE <= x < 64 + if (width_tiles == 0 && (rawImageIn.getWidth() >= MIN_IMAGE_SIZE)) + { + width_tiles = 1; + } + if (height_tiles == 0 && (rawImageIn.getHeight() >= MIN_IMAGE_SIZE)) + { + height_tiles = 1; + } + + U32 tile_count = width_tiles * height_tiles; U32 data_size_guess = tile_count * TILE_SIZE; // will be freed in opj_free_user_data_write @@ -525,7 +610,7 @@ public: opj_stream_destroy(stream); } - stream = opj_stream_create(data_size_guess, false); + stream = opj_stream_create(data_size_guess, OPJ_FALSE); if (!stream) { return false; @@ -557,7 +642,7 @@ public: { // "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); + compressedImageOut.allocateData((S32)offset); memcpy(compressedImageOut.getData(), buffer, offset); compressedImageOut.updateData(); // update width, height etc from header } @@ -566,16 +651,15 @@ public: 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(); + S32 width = raw.getWidth(); + S32 height = raw.getHeight(); + + std::vector<opj_image_cmptparm_t> cmptparm(numcomps); for (S32 c = 0; c < numcomps; c++) { - cmptparm[c].prec = 8; + cmptparm[c].prec = 8; // replaces .bpp cmptparm[c].sgnd = 0; cmptparm[c].dx = parameters.subsampling_dx; cmptparm[c].dy = parameters.subsampling_dy; @@ -583,7 +667,7 @@ public: cmptparm[c].h = height; } - image = opj_image_create(numcomps, &cmptparm[0], OPJ_CLRSPC_SRGB); + image = opj_image_create(numcomps, cmptparm.data(), OPJ_CLRSPC_SRGB); image->x1 = width; image->y1 = height; @@ -595,7 +679,7 @@ public: { for (S32 x = 0; x < width; x++) { - const U8 *pixel = src_datap + (y*width + x) * numcomps; + const U8 *pixel = src_datap + (y * width + x) * numcomps; for (S32 c = 0; c < numcomps; c++) { image->comps[c].data[i] = *pixel; @@ -813,14 +897,19 @@ bool LLImageJ2COJ::decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decod bool LLImageJ2COJ::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time, bool reversible) { + if (raw_image.isBufferInvalid()) + { + base.setLastError("Invalid input, no buffer"); + return false; + } + JPEG2KEncode encode(comment_text, reversible); bool encoded = encode.encode(raw_image, base); - if (encoded) + if (!encoded) { - LL_WARNS() << "Openjpeg encoding implementation isn't complete, returning false" << LL_ENDL; + LL_WARNS() << "Openjpeg encoding was unsuccessful, returning false" << LL_ENDL; } return encoded; - //return false; } bool LLImageJ2COJ::getMetadata(LLImageJ2C &base) |