From 7e2b6bebff12eab8bdc17fc9af017e8c11652f4f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 19 Sep 2017 16:52:07 +0200 Subject: [PATCH] Add capability to decode only a subset of all components of an image. This adds a opj_set_decoded_components(opj_codec_t *p_codec, OPJ_UINT32 numcomps, const OPJ_UINT32* comps_indices) function, and equivalent "opj_decompress -c compno[,compno]*" option. When specified, neither the MCT transform nor JP2 channel transformations will be applied. Tests added for various combinations of whole image vs tiled-based decoding, full or reduced resolution, use of decode area or not. --- src/bin/jp2/opj_decompress.c | 46 ++++++- src/lib/openjp2/j2k.c | 166 +++++++++++++++++++----- src/lib/openjp2/j2k.h | 19 +++ src/lib/openjp2/jp2.c | 20 +++ src/lib/openjp2/jp2.h | 21 +++ src/lib/openjp2/openjpeg.c | 33 +++++ src/lib/openjp2/openjpeg.h | 25 ++++ src/lib/openjp2/opj_codec.h | 5 + src/lib/openjp2/tcd.c | 70 ++++++++-- src/lib/openjp2/tcd.h | 8 ++ tests/nonregression/md5refs.txt | 6 + tests/nonregression/test_suite.ctest.in | 18 +++ 12 files changed, 390 insertions(+), 47 deletions(-) diff --git a/src/bin/jp2/opj_decompress.c b/src/bin/jp2/opj_decompress.c index 479d8382..6c28277a 100644 --- a/src/bin/jp2/opj_decompress.c +++ b/src/bin/jp2/opj_decompress.c @@ -152,6 +152,10 @@ typedef struct opj_decompress_params { int num_threads; /* Quiet */ int quiet; + /** number of components to decode */ + OPJ_UINT32 numcomps; + /** indices of components to decode */ + OPJ_UINT32* comps_indices; } opj_decompress_parameters; /* -------------------------------------------------------------------------- */ @@ -227,6 +231,10 @@ static void decode_help_display(void) " If 'C' is specified (default), values are clipped.\n" " If 'S' is specified, values are scaled.\n" " A 0 value can be specified (meaning original bit depth).\n"); + fprintf(stdout, " -c first_comp_index[,second_comp_index][,...]\n" + " OPTIONAL\n" + " To limit the number of components to decoded.\n" + " Component indices are numbered starting at 0.\n"); fprintf(stdout, " -force-rgb\n" " Force output image colorspace to RGB\n" " -upsample\n" @@ -560,7 +568,7 @@ int parse_cmdline_decoder(int argc, char **argv, {"quiet", NO_ARG, NULL, 1}, }; - const char optlist[] = "i:o:r:l:x:d:t:p:" + const char optlist[] = "i:o:r:l:x:d:t:p:c:" /* UniPG>> */ #ifdef USE_JPWL @@ -770,6 +778,25 @@ int parse_cmdline_decoder(int argc, char **argv, return 1; } } + break; + + /* ----------------------------------------------------- */ + case 'c': { /* Componenets */ + const char* iter = opj_optarg; + while (1) { + parameters->numcomps ++; + parameters->comps_indices = (OPJ_UINT32*) realloc( + parameters->comps_indices, + parameters->numcomps * sizeof(OPJ_UINT32)); + parameters->comps_indices[parameters->numcomps - 1] = + (OPJ_UINT32) atoi(iter); + iter = strchr(iter, ','); + if (iter == NULL) { + break; + } + iter ++; + } + } break; /* ----------------------------------------------------- */ @@ -1015,6 +1042,9 @@ static void destroy_parameters(opj_decompress_parameters* parameters) free(parameters->precision); parameters->precision = NULL; } + + free(parameters->comps_indices); + parameters->comps_indices = NULL; } } @@ -1455,6 +1485,20 @@ int main(int argc, char **argv) goto fin; } + if (parameters.numcomps) { + if (! opj_set_decoded_components(l_codec, + parameters.numcomps, + parameters.comps_indices)) { + fprintf(stderr, + "ERROR -> opj_decompress: failed to set the component indices!\n"); + opj_destroy_codec(l_codec); + opj_stream_destroy(l_stream); + opj_image_destroy(image); + failed = 1; + goto fin; + } + } + if (getenv("USE_OPJ_SET_DECODED_RESOLUTION_FACTOR") != NULL) { /* For debugging/testing purposes, and also an illustration on how to */ /* use the alternative API opj_set_decoded_resolution_factor() instead */ diff --git a/src/lib/openjp2/j2k.c b/src/lib/openjp2/j2k.c index 3e2bb749..c3f6ba11 100644 --- a/src/lib/openjp2/j2k.c +++ b/src/lib/openjp2/j2k.c @@ -8266,6 +8266,11 @@ void opj_j2k_destroy(opj_j2k_t *p_j2k) p_j2k->m_specific_param.m_decoder.m_header_data = 00; p_j2k->m_specific_param.m_decoder.m_header_data_size = 0; } + + opj_free(p_j2k->m_specific_param.m_decoder.m_comps_indices_to_decode); + p_j2k->m_specific_param.m_decoder.m_comps_indices_to_decode = 00; + p_j2k->m_specific_param.m_decoder.m_numcomps_to_decode = 0; + } else { if (p_j2k->m_specific_param.m_encoder.m_encoded_tile_data) { @@ -8914,6 +8919,8 @@ OPJ_BOOL opj_j2k_decode_tile(opj_j2k_t * p_j2k, l_image_for_bounds->y0, l_image_for_bounds->x1, l_image_for_bounds->y1, + p_j2k->m_specific_param.m_decoder.m_numcomps_to_decode, + p_j2k->m_specific_param.m_decoder.m_comps_indices_to_decode, l_tcp->m_data, l_tcp->m_data_size, p_tile_index, @@ -9028,6 +9035,11 @@ static OPJ_BOOL opj_j2k_update_image_data(opj_tcd_t * p_tcd, p_src_data = l_tilec->data_win; } + if (p_src_data == NULL) { + /* Happens for partial component decoding */ + continue; + } + l_width_src = (OPJ_UINT32)(res_x1 - res_x0); l_height_src = (OPJ_UINT32)(res_y1 - res_y0); @@ -9228,6 +9240,65 @@ static OPJ_BOOL opj_j2k_update_image_dimensions(opj_image_t* p_image, return OPJ_TRUE; } +OPJ_BOOL opj_j2k_set_decoded_components(opj_j2k_t *p_j2k, + OPJ_UINT32 numcomps, + const OPJ_UINT32* comps_indices, + opj_event_mgr_t * p_manager) +{ + OPJ_UINT32 i; + OPJ_BOOL* already_mapped; + + if (p_j2k->m_private_image == NULL) { + opj_event_msg(p_manager, EVT_ERROR, + "opj_read_header() should be called before " + "opj_set_decoded_components().\n"); + return OPJ_FALSE; + } + + already_mapped = (OPJ_BOOL*) opj_calloc(sizeof(OPJ_BOOL), + p_j2k->m_private_image->numcomps); + if (already_mapped == NULL) { + return OPJ_FALSE; + } + + for (i = 0; i < numcomps; i++) { + if (comps_indices[i] >= p_j2k->m_private_image->numcomps) { + opj_event_msg(p_manager, EVT_ERROR, + "Invalid component index: %u\n", + comps_indices[i]); + opj_free(already_mapped); + return OPJ_FALSE; + } + if (already_mapped[comps_indices[i]]) { + opj_event_msg(p_manager, EVT_ERROR, + "Component index %u used several times\n", + comps_indices[i]); + opj_free(already_mapped); + return OPJ_FALSE; + } + already_mapped[comps_indices[i]] = OPJ_TRUE; + } + opj_free(already_mapped); + + opj_free(p_j2k->m_specific_param.m_decoder.m_comps_indices_to_decode); + if (numcomps) { + p_j2k->m_specific_param.m_decoder.m_comps_indices_to_decode = + (OPJ_UINT32*) opj_malloc(numcomps * sizeof(OPJ_UINT32)); + if (p_j2k->m_specific_param.m_decoder.m_comps_indices_to_decode == NULL) { + p_j2k->m_specific_param.m_decoder.m_numcomps_to_decode = 0; + return OPJ_FALSE; + } + memcpy(p_j2k->m_specific_param.m_decoder.m_comps_indices_to_decode, + comps_indices, + numcomps * sizeof(OPJ_UINT32)); + } else { + p_j2k->m_specific_param.m_decoder.m_comps_indices_to_decode = NULL; + } + p_j2k->m_specific_param.m_decoder.m_numcomps_to_decode = numcomps; + + return OPJ_TRUE; +} + OPJ_BOOL opj_j2k_set_decode_area(opj_j2k_t *p_j2k, opj_image_t* p_image, @@ -10817,13 +10888,71 @@ static OPJ_BOOL opj_j2k_setup_decoding_tile(opj_j2k_t *p_j2k, return OPJ_TRUE; } +static OPJ_BOOL opj_j2k_move_data_from_codec_to_output_image(opj_j2k_t * p_j2k, + opj_image_t * p_image) +{ + OPJ_UINT32 compno; + + /* Move data and copy one information from codec to output image*/ + if (p_j2k->m_specific_param.m_decoder.m_numcomps_to_decode > 0) { + opj_image_comp_t* newcomps = + (opj_image_comp_t*) opj_malloc( + p_j2k->m_specific_param.m_decoder.m_numcomps_to_decode * + sizeof(opj_image_comp_t)); + if (newcomps == NULL) { + opj_image_destroy(p_j2k->m_private_image); + p_j2k->m_private_image = NULL; + return OPJ_FALSE; + } + for (compno = 0; compno < p_image->numcomps; compno++) { + opj_image_data_free(p_image->comps[compno].data); + p_image->comps[compno].data = NULL; + } + for (compno = 0; + compno < p_j2k->m_specific_param.m_decoder.m_numcomps_to_decode; compno++) { + OPJ_UINT32 src_compno = + p_j2k->m_specific_param.m_decoder.m_comps_indices_to_decode[compno]; + memcpy(&(newcomps[compno]), + &(p_j2k->m_output_image->comps[src_compno]), + sizeof(opj_image_comp_t)); + newcomps[compno].resno_decoded = + p_j2k->m_output_image->comps[src_compno].resno_decoded; + newcomps[compno].data = p_j2k->m_output_image->comps[src_compno].data; + p_j2k->m_output_image->comps[src_compno].data = NULL; + } + for (compno = 0; compno < p_image->numcomps; compno++) { + assert(p_j2k->m_output_image->comps[compno].data == NULL); + opj_image_data_free(p_j2k->m_output_image->comps[compno].data); + p_j2k->m_output_image->comps[compno].data = NULL; + } + p_image->numcomps = p_j2k->m_specific_param.m_decoder.m_numcomps_to_decode; + opj_free(p_image->comps); + p_image->comps = newcomps; + } else { + for (compno = 0; compno < p_image->numcomps; compno++) { + p_image->comps[compno].resno_decoded = + p_j2k->m_output_image->comps[compno].resno_decoded; + opj_image_data_free(p_image->comps[compno].data); + p_image->comps[compno].data = p_j2k->m_output_image->comps[compno].data; +#if 0 + char fn[256]; + sprintf(fn, "/tmp/%d.raw", compno); + FILE *debug = fopen(fn, "wb"); + fwrite(p_image->comps[compno].data, sizeof(OPJ_INT32), + p_image->comps[compno].w * p_image->comps[compno].h, debug); + fclose(debug); +#endif + p_j2k->m_output_image->comps[compno].data = NULL; + } + } + return OPJ_TRUE; +} + OPJ_BOOL opj_j2k_decode(opj_j2k_t * p_j2k, opj_stream_private_t * p_stream, opj_image_t * p_image, opj_event_mgr_t * p_manager) { - OPJ_UINT32 compno; - if (!p_image) { return OPJ_FALSE; } @@ -10874,23 +11003,7 @@ OPJ_BOOL opj_j2k_decode(opj_j2k_t * p_j2k, } /* Move data and copy one information from codec to output image*/ - for (compno = 0; compno < p_image->numcomps; compno++) { - p_image->comps[compno].resno_decoded = - p_j2k->m_output_image->comps[compno].resno_decoded; - opj_image_data_free(p_image->comps[compno].data); - p_image->comps[compno].data = p_j2k->m_output_image->comps[compno].data; -#if 0 - char fn[256]; - sprintf(fn, "/tmp/%d.raw", compno); - FILE *debug = fopen(fn, "wb"); - fwrite(p_image->comps[compno].data, sizeof(OPJ_INT32), - p_image->comps[compno].w * p_image->comps[compno].h, debug); - fclose(debug); -#endif - p_j2k->m_output_image->comps[compno].data = NULL; - } - - return OPJ_TRUE; + return opj_j2k_move_data_from_codec_to_output_image(p_j2k, p_image); } OPJ_BOOL opj_j2k_get_tile(opj_j2k_t *p_j2k, @@ -11005,20 +11118,7 @@ OPJ_BOOL opj_j2k_get_tile(opj_j2k_t *p_j2k, } /* Move data and copy one information from codec to output image*/ - for (compno = 0; compno < p_image->numcomps; compno++) { - p_image->comps[compno].resno_decoded = - p_j2k->m_output_image->comps[compno].resno_decoded; - - if (p_image->comps[compno].data) { - opj_image_data_free(p_image->comps[compno].data); - } - - p_image->comps[compno].data = p_j2k->m_output_image->comps[compno].data; - - p_j2k->m_output_image->comps[compno].data = NULL; - } - - return OPJ_TRUE; + return opj_j2k_move_data_from_codec_to_output_image(p_j2k, p_image); } OPJ_BOOL opj_j2k_set_decoded_resolution_factor(opj_j2k_t *p_j2k, diff --git a/src/lib/openjp2/j2k.h b/src/lib/openjp2/j2k.h index c9308f7d..b5fa5143 100644 --- a/src/lib/openjp2/j2k.h +++ b/src/lib/openjp2/j2k.h @@ -480,6 +480,10 @@ typedef struct opj_j2k_dec { * SOD reader function. FIXME NOT USED for the moment */ OPJ_BOOL m_last_tile_part; + + OPJ_UINT32 m_numcomps_to_decode; + OPJ_UINT32 *m_comps_indices_to_decode; + /** to tell that a tile can be decoded. */ OPJ_BITFIELD m_can_decode : 1; OPJ_BITFIELD m_discard_tiles : 1; @@ -705,6 +709,21 @@ OPJ_BOOL opj_j2k_read_tile_header(opj_j2k_t * p_j2k, opj_event_mgr_t * p_manager); +/** Sets the indices of the components to decode. + * + * @param p_j2k the jpeg2000 codec. + * @param numcomps Number of components to decode. + * @param comps_indices Array of num_compts indices (numbering starting at 0) + * corresponding to the components to decode. + * @param p_manager Event manager + * + * @return OPJ_TRUE in case of success. + */ +OPJ_BOOL opj_j2k_set_decoded_components(opj_j2k_t *p_j2k, + OPJ_UINT32 numcomps, + const OPJ_UINT32* comps_indices, + opj_event_mgr_t * p_manager); + /** * Sets the given area to be decoded. This function should be called right after opj_read_header and before any tile header reading. * diff --git a/src/lib/openjp2/jp2.c b/src/lib/openjp2/jp2.c index 84d39327..81d03480 100644 --- a/src/lib/openjp2/jp2.c +++ b/src/lib/openjp2/jp2.c @@ -1607,6 +1607,11 @@ OPJ_BOOL opj_jp2_decode(opj_jp2_t *jp2, return OPJ_FALSE; } + if (jp2->j2k->m_specific_param.m_decoder.m_numcomps_to_decode) { + /* Bypass all JP2 component transforms */ + return OPJ_TRUE; + } + if (!jp2->ignore_pclr_cmap_cdef) { if (!opj_jp2_check_color(p_image, &(jp2->color), p_manager)) { return OPJ_FALSE; @@ -3069,6 +3074,16 @@ void opj_jp2_destroy(opj_jp2_t *jp2) } } +OPJ_BOOL opj_jp2_set_decoded_components(opj_jp2_t *p_jp2, + OPJ_UINT32 numcomps, + const OPJ_UINT32* comps_indices, + opj_event_mgr_t * p_manager) +{ + return opj_j2k_set_decoded_components(p_jp2->j2k, + numcomps, comps_indices, + p_manager); +} + OPJ_BOOL opj_jp2_set_decode_area(opj_jp2_t *p_jp2, opj_image_t* p_image, OPJ_INT32 p_start_x, OPJ_INT32 p_start_y, @@ -3100,6 +3115,11 @@ OPJ_BOOL opj_jp2_get_tile(opj_jp2_t *p_jp2, return OPJ_FALSE; } + if (p_jp2->j2k->m_specific_param.m_decoder.m_numcomps_to_decode) { + /* Bypass all JP2 component transforms */ + return OPJ_TRUE; + } + if (!opj_jp2_check_color(p_image, &(p_jp2->color), p_manager)) { return OPJ_FALSE; } diff --git a/src/lib/openjp2/jp2.h b/src/lib/openjp2/jp2.h index 3ff66ebe..34abd511 100644 --- a/src/lib/openjp2/jp2.h +++ b/src/lib/openjp2/jp2.h @@ -235,6 +235,12 @@ Decoding parameters are returned in jp2->j2k->cp. */ void opj_jp2_setup_decoder(opj_jp2_t *jp2, opj_dparameters_t *parameters); +/** Allocates worker threads for the compressor/decompressor. + * + * @param jp2 JP2 decompressor handle + * @param num_threads Number of threads. + * @return OPJ_TRUE in case of success. + */ OPJ_BOOL opj_jp2_set_threads(opj_jp2_t *jp2, OPJ_UINT32 num_threads); /** @@ -327,6 +333,21 @@ OPJ_BOOL opj_jp2_read_header(opj_stream_private_t *p_stream, opj_image_t ** p_image, opj_event_mgr_t * p_manager); +/** Sets the indices of the components to decode. + * + * @param jp2 JP2 decompressor handle + * @param numcomps Number of components to decode. + * @param comps_indices Array of num_compts indices (numbering starting at 0) + * corresponding to the components to decode. + * @param p_manager Event manager; + * + * @return OPJ_TRUE in case of success. + */ +OPJ_BOOL opj_jp2_set_decoded_components(opj_jp2_t *jp2, + OPJ_UINT32 numcomps, + const OPJ_UINT32* comps_indices, + opj_event_mgr_t * p_manager); + /** * Reads a tile header. * @param p_jp2 the jpeg2000 codec. diff --git a/src/lib/openjp2/openjpeg.c b/src/lib/openjp2/openjpeg.c index 4e649a74..5ba205e1 100644 --- a/src/lib/openjp2/openjpeg.c +++ b/src/lib/openjp2/openjpeg.c @@ -245,6 +245,12 @@ opj_codec_t* OPJ_CALLCONV opj_create_decompress(OPJ_CODEC_FORMAT p_format) OPJ_UINT32 res_factor, struct opj_event_mgr * p_manager)) opj_j2k_set_decoded_resolution_factor; + l_codec->m_codec_data.m_decompression.opj_set_decoded_components = + (OPJ_BOOL(*)(void * p_codec, + OPJ_UINT32 numcomps, + const OPJ_UINT32 * comps_indices, + struct opj_event_mgr * p_manager)) opj_j2k_set_decoded_components; + l_codec->opj_set_threads = (OPJ_BOOL(*)(void * p_codec, OPJ_UINT32 num_threads)) opj_j2k_set_threads; @@ -327,6 +333,12 @@ opj_codec_t* OPJ_CALLCONV opj_create_decompress(OPJ_CODEC_FORMAT p_format) OPJ_UINT32 res_factor, opj_event_mgr_t * p_manager)) opj_jp2_set_decoded_resolution_factor; + l_codec->m_codec_data.m_decompression.opj_set_decoded_components = + (OPJ_BOOL(*)(void * p_codec, + OPJ_UINT32 numcomps, + const OPJ_UINT32 * comps_indices, + struct opj_event_mgr * p_manager)) opj_jp2_set_decoded_components; + l_codec->opj_set_threads = (OPJ_BOOL(*)(void * p_codec, OPJ_UINT32 num_threads)) opj_jp2_set_threads; @@ -426,6 +438,27 @@ OPJ_BOOL OPJ_CALLCONV opj_read_header(opj_stream_t *p_stream, return OPJ_FALSE; } + +OPJ_BOOL OPJ_CALLCONV opj_set_decoded_components(opj_codec_t *p_codec, + OPJ_UINT32 numcomps, + const OPJ_UINT32* comps_indices) +{ + if (p_codec) { + opj_codec_private_t * l_codec = (opj_codec_private_t *) p_codec; + + if (! l_codec->is_decompressor) { + return OPJ_FALSE; + } + + return l_codec->m_codec_data.m_decompression.opj_set_decoded_components( + l_codec->m_codec, + numcomps, + comps_indices, + &(l_codec->m_event_mgr)); + } + return OPJ_FALSE; +} + OPJ_BOOL OPJ_CALLCONV opj_decode(opj_codec_t *p_codec, opj_stream_t *p_stream, opj_image_t* p_image) diff --git a/src/lib/openjp2/openjpeg.h b/src/lib/openjp2/openjpeg.h index 79e0bb4f..2888e4d1 100644 --- a/src/lib/openjp2/openjpeg.h +++ b/src/lib/openjp2/openjpeg.h @@ -1336,6 +1336,29 @@ OPJ_API OPJ_BOOL OPJ_CALLCONV opj_read_header(opj_stream_t *p_stream, opj_codec_t *p_codec, opj_image_t **p_image); + +/** Restrict the number of components to decode. + * + * This function should be called after opj_read_header(). + * + * Normally all the components are decoded. This call enables to restrict + * the set of decoded components to the specified indices. + * Note that neither the MCT transform nor JP2 channel transformatios will be applied. + * + * Note: opj_decode_tile_data() should not be used together with opj_set_decoded_components(). + * + * @param p_codec the jpeg2000 codec to read. + * @param numcomps Size of the comps_indices array. + * @param comps_indices Array of numcomps values representing the indices + * of the components to decode (relative to the + * codestream, starting at 0) + * + * @return OPJ_TRUE in case of success. + */ +OPJ_API OPJ_BOOL OPJ_CALLCONV opj_set_decoded_components(opj_codec_t *p_codec, + OPJ_UINT32 numcomps, + const OPJ_UINT32* comps_indices); + /** * Sets the given area to be decoded. This function should be called right after opj_read_header and before any tile header reading. * @@ -1452,6 +1475,8 @@ OPJ_API OPJ_BOOL OPJ_CALLCONV opj_read_tile_header(opj_codec_t *p_codec, * Reads a tile data. This function is compulsory and allows one to decode tile data. opj_read_tile_header should be called before. * The user may need to refer to the image got by opj_read_header to understand the size being taken by the tile. * + * Note: opj_decode_tile_data() should not be used together with opj_set_decoded_components(). + * * @param p_codec the jpeg2000 codec. * @param p_tile_index the index of the tile being decoded, this should be the value set by opj_read_tile_header. * @param p_data pointer to a memory block that will hold the decoded data. diff --git a/src/lib/openjp2/opj_codec.h b/src/lib/openjp2/opj_codec.h index 2dbeac96..b962b121 100644 --- a/src/lib/openjp2/opj_codec.h +++ b/src/lib/openjp2/opj_codec.h @@ -111,6 +111,11 @@ typedef struct opj_codec_private { OPJ_UINT32 res_factor, opj_event_mgr_t * p_manager); + /** Set the decoded components */ + OPJ_BOOL(*opj_set_decoded_components)(void * p_codec, + OPJ_UINT32 num_comps, + const OPJ_UINT32* comps_indices, + opj_event_mgr_t * p_manager); } m_decompression; /** diff --git a/src/lib/openjp2/tcd.c b/src/lib/openjp2/tcd.c index fad9d23c..1dd15405 100644 --- a/src/lib/openjp2/tcd.c +++ b/src/lib/openjp2/tcd.c @@ -679,6 +679,9 @@ void opj_tcd_destroy(opj_tcd_t *tcd) opj_free(tcd->tcd_image); tcd->tcd_image = 00; } + + opj_free(tcd->used_component); + opj_free(tcd); } } @@ -1439,6 +1442,8 @@ OPJ_BOOL opj_tcd_decode_tile(opj_tcd_t *p_tcd, OPJ_UINT32 win_y0, OPJ_UINT32 win_x1, OPJ_UINT32 win_y1, + OPJ_UINT32 numcomps_to_decode, + const OPJ_UINT32 *comps_indices, OPJ_BYTE *p_src, OPJ_UINT32 p_max_length, OPJ_UINT32 p_tile_no, @@ -1457,7 +1462,27 @@ OPJ_BOOL opj_tcd_decode_tile(opj_tcd_t *p_tcd, p_tcd->win_y1 = win_y1; p_tcd->whole_tile_decoding = OPJ_TRUE; + opj_free(p_tcd->used_component); + p_tcd->used_component = NULL; + + if (numcomps_to_decode) { + OPJ_BOOL* used_component = (OPJ_BOOL*) opj_calloc(sizeof(OPJ_BOOL), + p_tcd->image->numcomps); + if (used_component == NULL) { + return OPJ_FALSE; + } + for (compno = 0; compno < numcomps_to_decode; compno++) { + used_component[ comps_indices[compno] ] = OPJ_TRUE; + } + + p_tcd->used_component = used_component; + } + for (compno = 0; compno < p_tcd->image->numcomps; compno++) { + if (p_tcd->used_component != NULL && !p_tcd->used_component[compno]) { + continue; + } + if (!opj_tcd_is_whole_tilecomp_decoding(p_tcd, compno)) { p_tcd->whole_tile_decoding = OPJ_FALSE; break; @@ -1475,6 +1500,10 @@ OPJ_BOOL opj_tcd_decode_tile(opj_tcd_t *p_tcd, OPJ_SIZE_T res_w = (OPJ_SIZE_T)(l_res->x1 - l_res->x0); OPJ_SIZE_T res_h = (OPJ_SIZE_T)(l_res->y1 - l_res->y0); + if (p_tcd->used_component != NULL && !p_tcd->used_component[compno]) { + continue; + } + /* issue 733, l_data_size == 0U, probably something wrong should be checked before getting here */ if (res_h > 0 && res_w > SIZE_MAX / res_h) { opj_event_msg(p_manager, EVT_ERROR, @@ -1506,6 +1535,11 @@ OPJ_BOOL opj_tcd_decode_tile(opj_tcd_t *p_tcd, OPJ_UINT32 resno; opj_tcd_tilecomp_t* tilec = &(p_tcd->tcd_image->tiles->comps[compno]); opj_image_comp_t* image_comp = &(p_tcd->image->comps[compno]); + + if (p_tcd->used_component != NULL && !p_tcd->used_component[compno]) { + continue; + } + /* Compute the intersection of the area of interest, expressed in tile coordinates */ /* with the tile coordinates */ tilec->win_x0 = opj_uint_max( @@ -1600,6 +1634,10 @@ OPJ_BOOL opj_tcd_decode_tile(opj_tcd_t *p_tcd, opj_image_data_free(tilec->data_win); tilec->data_win = NULL; + if (p_tcd->used_component != NULL && !p_tcd->used_component[compno]) { + continue; + } + if (w > 0 && h > 0) { if (w > SIZE_MAX / h) { opj_event_msg(p_manager, EVT_ERROR, @@ -1916,14 +1954,17 @@ static OPJ_BOOL opj_tcd_t1_decode(opj_tcd_t *p_tcd, opj_event_mgr_t *p_manager) check_pterm = OPJ_TRUE; } - for (compno = 0; compno < l_tile->numcomps; ++compno) { + for (compno = 0; compno < l_tile->numcomps; + ++compno, ++l_tile_comp, ++l_tccp) { + if (p_tcd->used_component != NULL && !p_tcd->used_component[compno]) { + continue; + } + opj_t1_decode_cblks(p_tcd, &ret, l_tile_comp, l_tccp, p_manager, p_manager_mutex, check_pterm); if (!ret) { break; } - ++l_tile_comp; - ++l_tccp; } opj_thread_pool_wait_completion(p_tcd->thread_pool, 0); @@ -1942,7 +1983,11 @@ static OPJ_BOOL opj_tcd_dwt_decode(opj_tcd_t *p_tcd) opj_tccp_t * l_tccp = p_tcd->tcp->tccps; opj_image_comp_t * l_img_comp = p_tcd->image->comps; - for (compno = 0; compno < l_tile->numcomps; compno++) { + for (compno = 0; compno < l_tile->numcomps; + compno++, ++l_tile_comp, ++l_img_comp, ++l_tccp) { + if (p_tcd->used_component != NULL && !p_tcd->used_component[compno]) { + continue; + } if (l_tccp->qmfbid == 1) { if (! opj_dwt_decode(p_tcd, l_tile_comp, @@ -1956,9 +2001,6 @@ static OPJ_BOOL opj_tcd_dwt_decode(opj_tcd_t *p_tcd) } } - ++l_tile_comp; - ++l_img_comp; - ++l_tccp; } return OPJ_TRUE; @@ -1971,7 +2013,7 @@ static OPJ_BOOL opj_tcd_mct_decode(opj_tcd_t *p_tcd, opj_event_mgr_t *p_manager) opj_tcd_tilecomp_t * l_tile_comp = l_tile->comps; OPJ_UINT32 l_samples, i; - if (! l_tcp->mct) { + if (l_tcp->mct == 0 || p_tcd->used_component != NULL) { return OPJ_TRUE; } @@ -2132,7 +2174,13 @@ static OPJ_BOOL opj_tcd_dc_level_shift_decode(opj_tcd_t *p_tcd) l_tccp = p_tcd->tcp->tccps; l_img_comp = p_tcd->image->comps; - for (compno = 0; compno < l_tile->numcomps; compno++) { + for (compno = 0; compno < l_tile->numcomps; + compno++, ++l_img_comp, ++l_tccp, ++l_tile_comp) { + + if (p_tcd->used_component != NULL && !p_tcd->used_component[compno]) { + continue; + } + l_res = l_tile_comp->resolutions + l_img_comp->resno_decoded; if (!p_tcd->whole_tile_decoding) { @@ -2191,10 +2239,6 @@ static OPJ_BOOL opj_tcd_dc_level_shift_decode(opj_tcd_t *p_tcd) l_current_ptr += l_stride; } } - - ++l_img_comp; - ++l_tccp; - ++l_tile_comp; } return OPJ_TRUE; diff --git a/src/lib/openjp2/tcd.h b/src/lib/openjp2/tcd.h index 63c22c45..e3214c1d 100644 --- a/src/lib/openjp2/tcd.h +++ b/src/lib/openjp2/tcd.h @@ -280,6 +280,8 @@ typedef struct opj_tcd { OPJ_UINT32 win_y1; /** Only valid for decoding. Whether the whole tile is decoded, or just the region in win_x0/win_y0/win_x1/win_y1 */ OPJ_BOOL whole_tile_decoding; + /* Array of size image->numcomps indicating if a component must be decoded. NULL if all components must be decoded */ + OPJ_BOOL* used_component; } opj_tcd_t; /** @name Exported functions */ @@ -381,6 +383,10 @@ Decode a tile from a buffer into a raw image @param win_y0 Upper left y of region to decode (in grid coordinates) @param win_x1 Lower right x of region to decode (in grid coordinates) @param win_y1 Lower right y of region to decode (in grid coordinates) +@param numcomps_to_decode Size of the comps_indices array, or 0 if decoding all components. +@param comps_indices Array of numcomps values representing the indices + of the components to decode (relative to the + codestream, starting at 0). Or NULL if decoding all components. @param src Source buffer @param len Length of source buffer @param tileno Number that identifies one of the tiles to be decoded @@ -392,6 +398,8 @@ OPJ_BOOL opj_tcd_decode_tile(opj_tcd_t *tcd, OPJ_UINT32 win_y0, OPJ_UINT32 win_x1, OPJ_UINT32 win_y1, + OPJ_UINT32 numcomps_to_decode, + const OPJ_UINT32 *comps_indices, OPJ_BYTE *src, OPJ_UINT32 len, OPJ_UINT32 tileno, diff --git a/tests/nonregression/md5refs.txt b/tests/nonregression/md5refs.txt index e95a10f0..6076538b 100644 --- a/tests/nonregression/md5refs.txt +++ b/tests/nonregression/md5refs.txt @@ -310,3 +310,9 @@ d1bb7f93f4c0eb984b2e9c54e544b7e9 broken.jpc_1.pgx b704ad4c0cfefffd78c20a54f5541265 dwt_interleave_h.gsr105.jp2_d_1_1_33_33_0.pgx 9d7fe43cd7a50b7bbaf712926ee11980 dwt_interleave_h.gsr105.jp2_d_1_1_33_33_1.pgx 0960b580f991ff10f693b24aa41ad58b dwt_interleave_h.gsr105.jp2_d_1_1_33_33_2.pgx +fa7382fd8b2e788b28b807e200dd95b9 file1.jp2-c0.tif +ed79b7fe443955cdefba2b039ddc846a file1.jp2-c0_1_2.tif +ac8f6ab3acc9c692ed7c41bd62a0e1e8 file1.jp2-c0-r1.tif +fbfcf662b6f7549574b2885490fbcf12 file1.jp2-c0-d10_20_30_40.tif +fa7382fd8b2e788b28b807e200dd95b9 file1.jp2-c0-t0.tif +ac8f6ab3acc9c692ed7c41bd62a0e1e8 file1.jp2-c0-t0-r1.tif diff --git a/tests/nonregression/test_suite.ctest.in b/tests/nonregression/test_suite.ctest.in index 718d0599..31ae777b 100644 --- a/tests/nonregression/test_suite.ctest.in +++ b/tests/nonregression/test_suite.ctest.in @@ -605,3 +605,21 @@ opj_decompress -i @INPUT_NR_PATH@/issue979.j2k -o @TEMP_PATH@/issue979.j2k.pgx # https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=3115 opj_decompress -i @INPUT_NR_PATH@/dwt_interleave_h.gsr105.jp2 -o @TEMP_PATH@/dwt_interleave_h.gsr105.jp2_d_1_1_33_33.pgx -d 1,1,33,33 + +# partial component decoding with opj_decode(): one component +opj_decompress -i @INPUT_CONF_PATH@/file1.jp2 -o @TEMP_PATH@/file1.jp2-c0.tif -c 0 +# partial component decoding with opj_decode(): 3 components without MCT +opj_decompress -i @INPUT_CONF_PATH@/file1.jp2 -o @TEMP_PATH@/file1.jp2-c0_1_2.tif -c 0,1,2 +# partial component decoding with opj_decode() and opj_set_decode_area() +opj_decompress -i @INPUT_CONF_PATH@/file1.jp2 -o @TEMP_PATH@/file1.jp2-c0-d10_20_30_40.tif -c 0 -d 10,20,30,40 +# partial component decoding with opj_decode() and reduced resolution +opj_decompress -i @INPUT_CONF_PATH@/file1.jp2 -o @TEMP_PATH@/file1.jp2-c0-r1.tif -c 0 -r 1 +# partial component decoding with opj_get_decoded_tile() +opj_decompress -i @INPUT_CONF_PATH@/file1.jp2 -o @TEMP_PATH@/file1.jp2-c0-t0.tif -c 0 -t 0 +# partial component decoding with opj_get_decoded_tile() and reduced resolution +opj_decompress -i @INPUT_CONF_PATH@/file1.jp2 -o @TEMP_PATH@/file1.jp2-c0-t0-r1.tif -c 0 -t 0 -r 1 + +# try to map the same component several times +!opj_decompress -i @INPUT_CONF_PATH@/file1.jp2 -o @TEMP_PATH@/file1.jp2-c0_0.tif -c 0,0 +# try to map an invalid component +!opj_decompress -i @INPUT_CONF_PATH@/file1.jp2 -o @TEMP_PATH@/file1.jp2-c10.tif -c 10