Skip to content

Commit a30342a

Browse files
authored
Fix and optimize openjpeg J2C encoder (#4017, #4032)
1 parent efdb86d commit a30342a

File tree

2 files changed

+115
-84
lines changed

2 files changed

+115
-84
lines changed

indra/llimagej2coj/llimagej2coj.cpp

Lines changed: 113 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@
3232
#include "event.h"
3333
#include "cio.h"
3434

35-
#define MAX_ENCODED_DISCARD_LEVELS 5
36-
3735
// Factory function: see declaration in llimagej2c.cpp
3836
LLImageJ2CImpl* fallbackCreateLLImageJ2CImpl()
3937
{
@@ -132,73 +130,96 @@ static void opj_error(const char* msg, void* user_data)
132130

133131
static OPJ_SIZE_T opj_read(void * buffer, OPJ_SIZE_T bytes, void* user_data)
134132
{
135-
llassert(user_data);
133+
llassert(user_data && buffer);
134+
136135
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
137-
OPJ_SIZE_T remainder = (jpeg_codec->size - jpeg_codec->offset);
138-
if (remainder <= 0)
136+
137+
if (jpeg_codec->offset < 0 || static_cast<OPJ_SIZE_T>(jpeg_codec->offset) >= jpeg_codec->size)
139138
{
140139
jpeg_codec->offset = jpeg_codec->size;
141-
// Indicate end of stream (hacky?)
142-
return (OPJ_OFF_T)-1;
140+
return static_cast<OPJ_SIZE_T>(-1); // Indicate EOF
143141
}
144-
OPJ_SIZE_T to_read = llclamp(U32(bytes), U32(0), U32(remainder));
142+
143+
OPJ_SIZE_T remainder = jpeg_codec->size - static_cast<OPJ_SIZE_T>(jpeg_codec->offset);
144+
OPJ_SIZE_T to_read = (bytes < remainder) ? bytes : remainder;
145+
145146
memcpy(buffer, jpeg_codec->buffer + jpeg_codec->offset, to_read);
146147
jpeg_codec->offset += to_read;
148+
147149
return to_read;
148150
}
149151

150152
static OPJ_SIZE_T opj_write(void * buffer, OPJ_SIZE_T bytes, void* user_data)
151153
{
152-
llassert(user_data);
154+
llassert(user_data && buffer);
155+
153156
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
154-
OPJ_SIZE_T remainder = jpeg_codec->size - jpeg_codec->offset;
155-
if (remainder < bytes)
157+
OPJ_OFF_T required_offset = jpeg_codec->offset + static_cast<OPJ_OFF_T>(bytes);
158+
159+
// Overflow check
160+
if (required_offset < jpeg_codec->offset)
161+
return 0; // Overflow detected
162+
163+
// Resize if needed (exponential growth)
164+
if (required_offset > static_cast<OPJ_OFF_T>(jpeg_codec->size))
156165
{
157-
OPJ_SIZE_T new_size = jpeg_codec->size + (bytes - remainder);
166+
OPJ_SIZE_T new_size = jpeg_codec->size ? jpeg_codec->size : 1024;
167+
while (required_offset > static_cast<OPJ_OFF_T>(new_size))
168+
new_size *= 2;
169+
170+
const OPJ_SIZE_T MAX_BUFFER_SIZE = 512 * 1024 * 1024; // 512 MB, increase if needed
171+
if (new_size > MAX_BUFFER_SIZE) return 0;
172+
158173
U8* new_buffer = (U8*)ll_aligned_malloc_16(new_size);
159-
memcpy(new_buffer, jpeg_codec->buffer, jpeg_codec->offset);
160-
U8* old_buffer = jpeg_codec->buffer;
174+
if (!new_buffer) return 0; // Allocation failed
175+
176+
if (jpeg_codec->offset > 0)
177+
memcpy(new_buffer, jpeg_codec->buffer, static_cast<size_t>(jpeg_codec->offset));
178+
179+
ll_aligned_free_16(jpeg_codec->buffer);
161180
jpeg_codec->buffer = new_buffer;
162-
ll_aligned_free_16(old_buffer);
163181
jpeg_codec->size = new_size;
164182
}
165-
memcpy(jpeg_codec->buffer + jpeg_codec->offset, buffer, bytes);
166-
jpeg_codec->offset += bytes;
183+
184+
memcpy(jpeg_codec->buffer + jpeg_codec->offset, buffer, static_cast<size_t>(bytes));
185+
jpeg_codec->offset = required_offset;
167186
return bytes;
168187
}
169188

170189
static OPJ_OFF_T opj_skip(OPJ_OFF_T bytes, void* user_data)
171190
{
191+
llassert(user_data);
172192
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
173-
jpeg_codec->offset += bytes;
174193

175-
if (jpeg_codec->offset > (OPJ_OFF_T)jpeg_codec->size)
176-
{
177-
jpeg_codec->offset = jpeg_codec->size;
178-
// Indicate end of stream
179-
return (OPJ_OFF_T)-1;
180-
}
194+
OPJ_OFF_T new_offset = jpeg_codec->offset + bytes;
181195

182-
if (jpeg_codec->offset < 0)
196+
if (new_offset < 0 || new_offset > static_cast<OPJ_OFF_T>(jpeg_codec->size))
183197
{
184-
// Shouldn't be possible?
185-
jpeg_codec->offset = 0;
198+
// Clamp and indicate EOF or error
199+
jpeg_codec->offset = llclamp<OPJ_OFF_T>(new_offset, 0, static_cast<OPJ_OFF_T>(jpeg_codec->size));
186200
return (OPJ_OFF_T)-1;
187201
}
188202

203+
jpeg_codec->offset = new_offset;
189204
return bytes;
190205
}
191206

192-
static OPJ_BOOL opj_seek(OPJ_OFF_T bytes, void * user_data)
207+
static OPJ_BOOL opj_seek(OPJ_OFF_T offset, void * user_data)
193208
{
209+
llassert(user_data);
194210
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
195-
jpeg_codec->offset = bytes;
196-
jpeg_codec->offset = llclamp(U32(jpeg_codec->offset), U32(0), U32(jpeg_codec->size));
211+
212+
if (offset < 0 || offset > static_cast<OPJ_OFF_T>(jpeg_codec->size))
213+
return OPJ_FALSE;
214+
215+
jpeg_codec->offset = offset;
197216
return OPJ_TRUE;
198217
}
199218

200219
static void opj_free_user_data(void * user_data)
201220
{
221+
llassert(user_data);
222+
202223
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
203224
// Don't free, data is managed externally
204225
jpeg_codec->buffer = nullptr;
@@ -208,14 +229,54 @@ static void opj_free_user_data(void * user_data)
208229

209230
static void opj_free_user_data_write(void * user_data)
210231
{
232+
llassert(user_data);
233+
211234
JPEG2KBase* jpeg_codec = static_cast<JPEG2KBase*>(user_data);
212235
// Free, data was allocated here
213-
ll_aligned_free_16(jpeg_codec->buffer);
214-
jpeg_codec->buffer = nullptr;
236+
if (jpeg_codec->buffer)
237+
{
238+
ll_aligned_free_16(jpeg_codec->buffer);
239+
jpeg_codec->buffer = nullptr;
240+
}
215241
jpeg_codec->size = 0;
216242
jpeg_codec->offset = 0;
217243
}
218244

245+
/**
246+
* Estimates the number of layers necessary depending on the image surface (w x h)
247+
*/
248+
static U32 estimate_num_layers(U32 surface)
249+
{
250+
if (surface <= 1024) return 2; // Tiny (≤32×32)
251+
else if (surface <= 16384) return 3; // Small (≤128×128)
252+
else if (surface <= 262144) return 4; // Medium (≤512×512)
253+
else if (surface <= 1048576) return 5; // Up to ~1MP
254+
else return 6; // Up to ~1.5–2MP
255+
}
256+
257+
/**
258+
* Sets the parameters.tcp_rates according to the number of layers and a last tcp_rate value (which equals to the final compression ratio).
259+
*
260+
* Example for 6 layers:
261+
*
262+
* i = 5, parameters.tcp_rates[6 - 1 - 5] = 8.0f * (1 << (5 << 1)) = 8192 // Layer 5 (lowest quality)
263+
* i = 4, parameters.tcp_rates[6 - 1 - 4] = 8.0f * (1 << (4 << 1)) = 2048 // Layer 4
264+
* i = 3, parameters.tcp_rates[6 - 1 - 3] = 8.0f * (1 << (3 << 1)) = 512 // Layer 3
265+
* i = 2, parameters.tcp_rates[6 - 1 - 2] = 8.0f * (1 << (2 << 1)) = 128 // Layer 2
266+
* i = 1, parameters.tcp_rates[6 - 1 - 1] = 8.0f * (1 << (1 << 1)) = 32 // Layer 1
267+
* i = 0, parameters.tcp_rates[6 - 1 - 0] = 8.0f * (1 << (0 << 1)) = 8 // Layer 0 (highest quality)
268+
*
269+
*/
270+
static void set_tcp_rates(opj_cparameters_t* parameters, U32 num_layers = 1, F32 last_tcp_rate = LAST_TCP_RATE)
271+
{
272+
parameters->tcp_numlayers = num_layers;
273+
274+
for (int i = num_layers - 1; i >= 0; i--)
275+
{
276+
parameters->tcp_rates[num_layers - 1 - i] = last_tcp_rate * static_cast<F32>(1 << (i << 1));
277+
}
278+
}
279+
219280
class JPEG2KDecode : public JPEG2KBase
220281
{
221282
public:
@@ -430,15 +491,16 @@ class JPEG2KEncode : public JPEG2KBase
430491

431492
opj_set_default_encoder_parameters(&parameters);
432493
parameters.cod_format = OPJ_CODEC_J2K;
433-
parameters.cp_disto_alloc = 1;
494+
parameters.prog_order = OPJ_RLCP; // should be the default, but, just in case
495+
parameters.cp_disto_alloc = 1; // enable rate allocation by distortion
496+
parameters.max_cs_size = 0; // do not cap max size because we're using tcp_rates and also irrelevant with lossless.
434497

435498
if (reversible)
436499
{
437-
parameters.max_cs_size = 0; // do not limit size for reversible compression
438500
parameters.irreversible = 0; // should be the default, but, just in case
439501
parameters.tcp_numlayers = 1;
440502
/* documentation seems to be wrong, should be 0.0f for lossless, not 1.0f
441-
see https://github.com/uclouvain/openjpeg/blob/39e8c50a2f9bdcf36810ee3d41bcbf1cc78968ae/src/lib/openjp2/j2k.c#L7755
503+
see https://github.com/uclouvain/openjpeg/blob/e7453e398b110891778d8da19209792c69ca7169/src/lib/openjp2/j2k.c#L7817
442504
*/
443505
parameters.tcp_rates[0] = 0.0f;
444506
}
@@ -493,53 +555,22 @@ class JPEG2KEncode : public JPEG2KBase
493555

494556
encoder = opj_create_compress(OPJ_CODEC_J2K);
495557

496-
parameters.tcp_mct = (image->numcomps >= 3) ? 1 : 0;
497-
parameters.cod_format = OPJ_CODEC_J2K;
498-
parameters.prog_order = OPJ_RLCP;
499-
parameters.cp_disto_alloc = 1;
558+
parameters.tcp_mct = (image->numcomps >= 3) ? 1 : 0; // no color transform for RGBA images
559+
500560

501561
// if not lossless compression, computes tcp_numlayers and max_cs_size depending on the image dimensions
502-
if( parameters.irreversible ) {
562+
if( parameters.irreversible )
563+
{
503564

504565
// computes a number of layers
505566
U32 surface = rawImageIn.getWidth() * rawImageIn.getHeight();
506-
U32 nb_layers = 1;
507-
U32 s = 64*64;
508-
while (surface > s)
509-
{
510-
nb_layers++;
511-
s *= 4;
512-
}
513-
nb_layers = llclamp(nb_layers, 1, 6);
514-
515-
parameters.tcp_numlayers = nb_layers;
516-
parameters.tcp_rates[nb_layers - 1] = (U32)(1.f / DEFAULT_COMPRESSION_RATE); // 1:8 by default
517567

518-
// for each subsequent layer, computes its rate and adds surface * numcomps * 1/rate to the max_cs_size
519-
U32 max_cs_size = (U32)(surface * image->numcomps * DEFAULT_COMPRESSION_RATE);
520-
U32 multiplier;
521-
for (int i = nb_layers - 2; i >= 0; i--)
522-
{
523-
if( i == nb_layers - 2 )
524-
{
525-
multiplier = 15;
526-
}
527-
else if( i == nb_layers - 3 )
528-
{
529-
multiplier = 4;
530-
}
531-
else
532-
{
533-
multiplier = 2;
534-
}
535-
parameters.tcp_rates[i] = parameters.tcp_rates[i + 1] * multiplier;
536-
max_cs_size += (U32)(surface * image->numcomps * (1 / parameters.tcp_rates[i]));
537-
}
568+
// gets the necessary number of layers
569+
U32 nb_layers = estimate_num_layers(surface);
538570

539-
//ensure that we have at least a minimal size
540-
max_cs_size = llmax(max_cs_size, (U32)FIRST_PACKET_SIZE);
571+
// fills parameters.tcp_rates and updates parameters.tcp_numlayers
572+
set_tcp_rates(&parameters, nb_layers, LAST_TCP_RATE);
541573

542-
parameters.max_cs_size = max_cs_size;
543574
}
544575

545576
if (!opj_setup_encoder(encoder, &parameters, image))
@@ -579,7 +610,7 @@ class JPEG2KEncode : public JPEG2KBase
579610
opj_stream_destroy(stream);
580611
}
581612

582-
stream = opj_stream_create(data_size_guess, false);
613+
stream = opj_stream_create(data_size_guess, OPJ_FALSE);
583614
if (!stream)
584615
{
585616
return false;
@@ -620,25 +651,23 @@ class JPEG2KEncode : public JPEG2KBase
620651

621652
void setImage(const LLImageRaw& raw)
622653
{
623-
opj_image_cmptparm_t cmptparm[MAX_ENCODED_DISCARD_LEVELS];
624-
memset(&cmptparm[0], 0, MAX_ENCODED_DISCARD_LEVELS * sizeof(opj_image_cmptparm_t));
625-
626654
S32 numcomps = raw.getComponents();
627-
S32 width = raw.getWidth();
628-
S32 height = raw.getHeight();
655+
S32 width = raw.getWidth();
656+
S32 height = raw.getHeight();
657+
658+
std::vector<opj_image_cmptparm_t> cmptparm(numcomps);
629659

630660
for (S32 c = 0; c < numcomps; c++)
631661
{
632-
cmptparm[c].prec = 8;
633-
cmptparm[c].bpp = 8;
662+
cmptparm[c].prec = 8; // replaces .bpp
634663
cmptparm[c].sgnd = 0;
635664
cmptparm[c].dx = parameters.subsampling_dx;
636665
cmptparm[c].dy = parameters.subsampling_dy;
637666
cmptparm[c].w = width;
638667
cmptparm[c].h = height;
639668
}
640669

641-
image = opj_image_create(numcomps, &cmptparm[0], OPJ_CLRSPC_SRGB);
670+
image = opj_image_create(numcomps, cmptparm.data(), OPJ_CLRSPC_SRGB);
642671

643672
image->x1 = width;
644673
image->y1 = height;
@@ -650,7 +679,7 @@ class JPEG2KEncode : public JPEG2KBase
650679
{
651680
for (S32 x = 0; x < width; x++)
652681
{
653-
const U8 *pixel = src_datap + (y*width + x) * numcomps;
682+
const U8 *pixel = src_datap + (y * width + x) * numcomps;
654683
for (S32 c = 0; c < numcomps; c++)
655684
{
656685
image->comps[c].data[i] = *pixel;

indra/llimagej2coj/llimagej2coj.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
#include "llimagej2c.h"
3131

32+
const F32 LAST_TCP_RATE = 1.f/DEFAULT_COMPRESSION_RATE; // should be 8, giving a 1:8 ratio
33+
3234
class LLImageJ2COJ : public LLImageJ2CImpl
3335
{
3436
public:

0 commit comments

Comments
 (0)