From 0ae3cba3404674bbe2028ea9a801301a4c951b33 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 1 Sep 2017 16:30:48 +0200 Subject: [PATCH] Allow several repeated calls to opj_set_decode_area() and opj_decode() for single-tiled images * Only works for single-tiled images --> will error out cleanly, as currently in other cases * Save re-reading the codestream for the tile, and re-use code-blocks of the previous decoding pass. * Future improvements might involve improving opj_decompress, and the image writing logic, to use this strategy. --- src/lib/openjp2/j2k.c | 62 ++++++++---- src/lib/openjp2/openjpeg.h | 6 ++ src/lib/openjp2/t1.c | 42 +++++++- tests/CMakeLists.txt | 4 + tests/test_decode_area.c | 200 ++++++++++++++++++++++++++++++++----- 5 files changed, 268 insertions(+), 46 deletions(-) diff --git a/src/lib/openjp2/j2k.c b/src/lib/openjp2/j2k.c index f1a894a4..e548fefc 100644 --- a/src/lib/openjp2/j2k.c +++ b/src/lib/openjp2/j2k.c @@ -9147,10 +9147,15 @@ OPJ_BOOL opj_j2k_set_decode_area(opj_j2k_t *p_j2k, OPJ_BOOL ret; OPJ_UINT32 it_comp; + if (p_j2k->m_cp.tw == 1 && p_j2k->m_cp.th == 1 && + &p_j2k->m_cp.tcps[0].m_data != NULL) { + /* In the case of a single-tiled image whose codestream we have already */ + /* ingested, go on */ + } /* Check if we are read the main header */ - if (p_j2k->m_specific_param.m_decoder.m_state != J2K_STATE_TPHSOT) { + else if (p_j2k->m_specific_param.m_decoder.m_state != J2K_STATE_TPHSOT) { opj_event_msg(p_manager, EVT_ERROR, - "Need to decode the main header before begin to decode the remaining codestream"); + "Need to decode the main header before begin to decode the remaining codestream.\n"); return OPJ_FALSE; } @@ -10508,20 +10513,27 @@ static OPJ_BOOL opj_j2k_decode_tiles(opj_j2k_t *p_j2k, } for (;;) { - if (! opj_j2k_read_tile_header(p_j2k, - &l_current_tile_no, - NULL, - &l_tile_x0, &l_tile_y0, - &l_tile_x1, &l_tile_y1, - &l_nb_comps, - &l_go_on, - p_stream, - p_manager)) { - return OPJ_FALSE; - } + if (p_j2k->m_cp.tw == 1 && p_j2k->m_cp.th == 1 && + p_j2k->m_cp.tcps[0].m_data != NULL) { + l_current_tile_no = 0; + p_j2k->m_current_tile_number = 0; + p_j2k->m_specific_param.m_decoder.m_state |= J2K_STATE_DATA; + } else { + if (! opj_j2k_read_tile_header(p_j2k, + &l_current_tile_no, + NULL, + &l_tile_x0, &l_tile_y0, + &l_tile_x1, &l_tile_y1, + &l_nb_comps, + &l_go_on, + p_stream, + p_manager)) { + return OPJ_FALSE; + } - if (! l_go_on) { - break; + if (! l_go_on) { + break; + } } if (! opj_j2k_decode_tile(p_j2k, l_current_tile_no, NULL, 0, @@ -10538,7 +10550,16 @@ static OPJ_BOOL opj_j2k_decode_tiles(opj_j2k_t *p_j2k, p_j2k->m_output_image)) { return OPJ_FALSE; } - opj_j2k_tcp_data_destroy(&p_j2k->m_cp.tcps[l_current_tile_no]); + + if (p_j2k->m_cp.tw == 1 && p_j2k->m_cp.th == 1 && + !(p_j2k->m_output_image->x0 == p_j2k->m_private_image->x0 && + p_j2k->m_output_image->y0 == p_j2k->m_private_image->y0 && + p_j2k->m_output_image->x1 == p_j2k->m_private_image->x1 && + p_j2k->m_output_image->y1 == p_j2k->m_private_image->y1)) { + /* Keep current tcp data */ + } else { + opj_j2k_tcp_data_destroy(&p_j2k->m_cp.tcps[l_current_tile_no]); + } opj_event_msg(p_manager, EVT_INFO, "Image data has been updated with tile %d.\n\n", l_current_tile_no + 1); @@ -10738,9 +10759,11 @@ OPJ_BOOL opj_j2k_decode(opj_j2k_t * p_j2k, } } - p_j2k->m_output_image = opj_image_create0(); - if (!(p_j2k->m_output_image)) { - return OPJ_FALSE; + if (p_j2k->m_output_image == NULL) { + p_j2k->m_output_image = opj_image_create0(); + if (!(p_j2k->m_output_image)) { + return OPJ_FALSE; + } } opj_copy_image_header(p_image, p_j2k->m_output_image); @@ -10760,6 +10783,7 @@ OPJ_BOOL opj_j2k_decode(opj_j2k_t * p_j2k, 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]; diff --git a/src/lib/openjp2/openjpeg.h b/src/lib/openjp2/openjpeg.h index 21755b48..7020d37d 100644 --- a/src/lib/openjp2/openjpeg.h +++ b/src/lib/openjp2/openjpeg.h @@ -1340,6 +1340,12 @@ OPJ_API OPJ_BOOL OPJ_CALLCONV opj_read_header(opj_stream_t *p_stream, * that is to say at the highest resolution level, even if requesting the image at lower * resolution levels. * + * Generally opj_set_decode_area() should be followed by opj_decode(), and the + * codec cannot be re-used. + * In the particular case of an image made of a single tile, several sequences of + * calls to opoj_set_decode_area() and opj_decode() are allowed, and will bring + * performance improvements when reading an image by chunks. + * * @param p_codec the jpeg2000 codec. * @param p_image the decoded image previously setted by opj_read_header * @param p_start_x the left position of the rectangle to decode (in image coordinates). diff --git a/src/lib/openjp2/t1.c b/src/lib/openjp2/t1.c index 44a2f243..0277f8cc 100644 --- a/src/lib/openjp2/t1.c +++ b/src/lib/openjp2/t1.c @@ -1668,6 +1668,11 @@ static void opj_t1_clbl_decode_processor(void* user_data, opj_tls_t* tls) } } + /* Both can be non NULL if for example decoding a full tile and then */ + /* partially a tile. In which case partial decoding should be the */ + /* priority */ + assert((cblk->decoded_data != NULL) || (tilec->data != NULL)); + if (cblk->decoded_data) { if (tccp->qmfbid == 1) { for (j = 0; j < cblk_h; ++j) { @@ -1763,6 +1768,17 @@ void opj_t1_decode_cblks(opj_tcd_t* tcd, (OPJ_UINT32)precinct->y0, (OPJ_UINT32)precinct->x1, (OPJ_UINT32)precinct->y1)) { + for (cblkno = 0; cblkno < precinct->cw * precinct->ch; ++cblkno) { + opj_tcd_cblk_dec_t* cblk = &precinct->cblks.dec[cblkno]; + if (cblk->decoded_data) { +#ifdef DEBUG_VERBOSE + printf("Discarding codeblock %d,%d at resno=%d, bandno=%d\n", + cblk->x0, cblk->y0, resno, bandno); +#endif + opj_free(cblk->decoded_data); + cblk->decoded_data = NULL; + } + } continue; } @@ -1770,8 +1786,6 @@ void opj_t1_decode_cblks(opj_tcd_t* tcd, opj_tcd_cblk_dec_t* cblk = &precinct->cblks.dec[cblkno]; opj_t1_cblk_decode_processing_job_t* job; - assert(cblk->decoded_data == NULL); - if (!opj_tcd_is_subband_area_of_interest(tcd, tilec->compno, resno, @@ -1780,15 +1794,34 @@ void opj_t1_decode_cblks(opj_tcd_t* tcd, (OPJ_UINT32)cblk->y0, (OPJ_UINT32)cblk->x1, (OPJ_UINT32)cblk->y1)) { + if (cblk->decoded_data) { +#ifdef DEBUG_VERBOSE + printf("Discarding codeblock %d,%d at resno=%d, bandno=%d\n", + cblk->x0, cblk->y0, resno, bandno); +#endif + opj_free(cblk->decoded_data); + cblk->decoded_data = NULL; + } continue; } if (!tcd->whole_tile_decoding) { OPJ_UINT32 cblk_w = (OPJ_UINT32)(cblk->x1 - cblk->x0); OPJ_UINT32 cblk_h = (OPJ_UINT32)(cblk->y1 - cblk->y0); + if (cblk->decoded_data != NULL) { +#ifdef DEBUG_VERBOSE + printf("Reusing codeblock %d,%d at resno=%d, bandno=%d\n", + cblk->x0, cblk->y0, resno, bandno); +#endif + continue; + } if (cblk_w == 0 || cblk_h == 0) { continue; } +#ifdef DEBUG_VERBOSE + printf("Decoding codeblock %d,%d at resno=%d, bandno=%d\n", + cblk->x0, cblk->y0, resno, bandno); +#endif /* Zero-init required */ cblk->decoded_data = opj_calloc(1, cblk_w * cblk_h * sizeof(OPJ_INT32)); if (cblk->decoded_data == NULL) { @@ -1803,6 +1836,11 @@ void opj_t1_decode_cblks(opj_tcd_t* tcd, *pret = OPJ_FALSE; return; } + } else if (cblk->decoded_data) { + /* Not sure if that code path can happen, but better be */ + /* safe than sorry */ + opj_free(cblk->decoded_data); + cblk->decoded_data = NULL; } job = (opj_t1_cblk_decode_processing_job_t*) opj_calloc(1, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 579e066b..95b75291 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -103,6 +103,10 @@ add_test(NAME tda_prep_irreversible_203_201_17_19_no_precinct COMMAND test_tile_ add_test(NAME tda_irreversible_203_201_17_19_no_precinct COMMAND test_decode_area -q irreversible_203_201_17_19_no_precinct.j2k) set_property(TEST tda_irreversible_203_201_17_19_no_precinct APPEND PROPERTY DEPENDS tda_prep_irreversible_203_201_17_19_no_precinct) +add_test(NAME tda_prep_strip COMMAND test_tile_encoder 1 256 256 256 256 8 0 tda_single_tile.j2k) +add_test(NAME tda_strip COMMAND test_decode_area -q -strip_height 3 -strip_check tda_single_tile.j2k) +set_property(TEST tda_strip APPEND PROPERTY DEPENDS tda_prep_strip) + add_executable(include_openjpeg include_openjpeg.c) # No image send to the dashboard if lib PNG is not available. diff --git a/tests/test_decode_area.c b/tests/test_decode_area.c index e773bc66..92229908 100644 --- a/tests/test_decode_area.c +++ b/tests/test_decode_area.c @@ -99,31 +99,13 @@ static void info_callback(const char *msg, void *client_data) /*fprintf(stdout, "[INFO] %s", msg);*/ } -opj_image_t* decode( - OPJ_BOOL quiet, - const char* input_file, - OPJ_INT32 x0, - OPJ_INT32 y0, - OPJ_INT32 x1, - OPJ_INT32 y1, - OPJ_UINT32* ptilew, - OPJ_UINT32* ptileh, - OPJ_UINT32* pcblkw, - OPJ_UINT32* pcblkh) +static opj_codec_t* create_codec_and_stream(const char* input_file, + opj_stream_t** pOutStream) { opj_dparameters_t l_param; opj_codec_t * l_codec = NULL; - opj_image_t * l_image = NULL; opj_stream_t * l_stream = NULL; - if (!quiet) { - if (x0 != 0 || x1 != 0 || y0 != 0 || y1 != 0) { - printf("Decoding %d,%d,%d,%d\n", x0, y0, x1, y1); - } else { - printf("Decoding full image\n"); - } - } - l_stream = opj_stream_create_default_file_stream(input_file, OPJ_TRUE); if (!l_stream) { fprintf(stderr, "ERROR -> failed to create the stream from the file\n"); @@ -168,6 +150,40 @@ opj_image_t* decode( return NULL; } + *pOutStream = l_stream; + return l_codec; +} + + +opj_image_t* decode( + OPJ_BOOL quiet, + const char* input_file, + OPJ_INT32 x0, + OPJ_INT32 y0, + OPJ_INT32 x1, + OPJ_INT32 y1, + OPJ_UINT32* ptilew, + OPJ_UINT32* ptileh, + OPJ_UINT32* pcblkw, + OPJ_UINT32* pcblkh) +{ + opj_codec_t * l_codec = NULL; + opj_image_t * l_image = NULL; + opj_stream_t * l_stream = NULL; + + if (!quiet) { + if (x0 != 0 || x1 != 0 || y0 != 0 || y1 != 0) { + printf("Decoding %d,%d,%d,%d\n", x0, y0, x1, y1); + } else { + printf("Decoding full image\n"); + } + } + + l_codec = create_codec_and_stream(input_file, &l_stream); + if (l_codec == NULL) { + return NULL; + } + /* Read the main header of the codestream and if necessary the JP2 boxes*/ if (! opj_read_header(l_stream, l_codec, &l_image)) { fprintf(stderr, "ERROR -> failed to read the header\n"); @@ -226,6 +242,122 @@ opj_image_t* decode( return l_image; } +int decode_by_strip(OPJ_BOOL quiet, + const char* input_file, + OPJ_UINT32 strip_height, + opj_image_t* full_image) +{ + /* OPJ_UINT32 tilew, tileh; */ + opj_codec_t * l_codec = NULL; + opj_image_t * l_image = NULL; + opj_stream_t * l_stream = NULL; + OPJ_UINT32 x0, y0, x1, y1, y; + + l_codec = create_codec_and_stream(input_file, &l_stream); + if (l_codec == NULL) { + return 1; + } + + /* Read the main header of the codestream and if necessary the JP2 boxes*/ + if (! opj_read_header(l_stream, l_codec, &l_image)) { + fprintf(stderr, "ERROR -> failed to read the header\n"); + opj_stream_destroy(l_stream); + opj_destroy_codec(l_codec); + return 1; + } + + x0 = l_image->x0; + y0 = l_image->y0; + x1 = l_image->x1; + y1 = l_image->y1; + for (y = y0; y < y1; y += strip_height) { + OPJ_UINT32 h_req = strip_height; + if (y + h_req > y1) { + h_req = y1 - y; + } + if (!quiet) { + printf("Decoding %u...%u\n", y, y + h_req); + } + if (!opj_set_decode_area(l_codec, l_image, (OPJ_INT32)x0, (OPJ_INT32)y, + (OPJ_INT32)x1, (OPJ_INT32)(y + h_req))) { + fprintf(stderr, "ERROR -> failed to set the decoded area\n"); + opj_stream_destroy(l_stream); + opj_destroy_codec(l_codec); + opj_image_destroy(l_image); + return 1; + } + + /* Get the decoded image */ + if (!(opj_decode(l_codec, l_stream, l_image))) { + fprintf(stderr, "ERROR -> failed to decode image!\n"); + opj_stream_destroy(l_stream); + opj_destroy_codec(l_codec); + opj_image_destroy(l_image); + return 1; + } + + if (full_image) { + OPJ_UINT32 y_check, x; + OPJ_UINT32 compno; + for (compno = 0; compno < l_image->numcomps; compno ++) { + for (y_check = 0; y_check < h_req; y_check++) { + for (x = x0; x < x1; x++) { + OPJ_INT32 sub_image_val = + l_image->comps[compno].data[y_check * (x1 - x0) + x]; + OPJ_INT32 image_val = + full_image->comps[compno].data[(y + y_check) * (x1 - x0) + x]; + if (sub_image_val != image_val) { + fprintf(stderr, + "Difference found at subimage pixel (%u,%u) " + "of compno=%u: got %d, expected %d\n", + x, y_check + y, compno, sub_image_val, image_val); + return 1; + } + } + } + } + } + + } + + /* If image is small enough, try a final whole image read */ + if (x1 - x0 < 10000 && y1 - y0 < 10000) { + if (!quiet) { + printf("Decoding full image\n"); + } + if (!opj_set_decode_area(l_codec, l_image, (OPJ_INT32)x0, (OPJ_INT32)y0, + (OPJ_INT32)x1, (OPJ_INT32)y1)) { + fprintf(stderr, "ERROR -> failed to set the decoded area\n"); + opj_stream_destroy(l_stream); + opj_destroy_codec(l_codec); + opj_image_destroy(l_image); + return 1; + } + + /* Get the decoded image */ + if (!(opj_decode(l_codec, l_stream, l_image))) { + fprintf(stderr, "ERROR -> failed to decode image!\n"); + opj_stream_destroy(l_stream); + opj_destroy_codec(l_codec); + opj_image_destroy(l_image); + return 1; + } + } + + if (! opj_end_decompress(l_codec, l_stream)) { + opj_stream_destroy(l_stream); + opj_destroy_codec(l_codec); + opj_image_destroy(l_image); + return 1; + } + + + opj_stream_destroy(l_stream); + opj_destroy_codec(l_codec); + opj_image_destroy(l_image); + return 0; +} + OPJ_BOOL check_consistency(opj_image_t* p_image, opj_image_t* p_sub_image) { OPJ_UINT32 compno; @@ -273,10 +405,13 @@ int main(int argc, char** argv) OPJ_UINT32 step_x, step_y; OPJ_BOOL quiet = OPJ_FALSE; OPJ_UINT32 nsteps = 100; + OPJ_UINT32 strip_height = 0; + OPJ_BOOL strip_check = OPJ_FALSE; if (argc < 2) { fprintf(stderr, - "Usage: test_decode_area [-q] [-steps n] input_file_jp2_or_jk2 [x0 y0 x1 y1]\n"); + "Usage: test_decode_area [-q] [-steps n] input_file_jp2_or_jk2 [x0 y0 x1 y1]\n" + "or : test_decode_area [-q] [-strip_height h] [-strip_check] input_file_jp2_or_jk2\n"); return 1; } @@ -288,6 +423,11 @@ int main(int argc, char** argv) } else if (strcmp(argv[iarg], "-steps") == 0 && iarg + 1 < argc) { nsteps = (OPJ_UINT32)atoi(argv[iarg + 1]); iarg ++; + } else if (strcmp(argv[iarg], "-strip_height") == 0 && iarg + 1 < argc) { + strip_height = (OPJ_UINT32)atoi(argv[iarg + 1]); + iarg ++; + } else if (strcmp(argv[iarg], "-strip_check") == 0) { + strip_check = OPJ_TRUE; } else if (input_file == NULL) { input_file = argv[iarg]; } else if (iarg + 3 < argc) { @@ -300,10 +440,20 @@ int main(int argc, char** argv) } } - l_image = decode(quiet, input_file, 0, 0, 0, 0, - &tilew, &tileh, &cblkw, &cblkh); - if (!l_image) { - return 1; + if (!strip_height || strip_check) { + l_image = decode(quiet, input_file, 0, 0, 0, 0, + &tilew, &tileh, &cblkw, &cblkh); + if (!l_image) { + return 1; + } + } + + if (strip_height) { + int ret = decode_by_strip(quiet, input_file, strip_height, l_image); + if (l_image) { + opj_image_destroy(l_image); + } + return ret; } if (da_x0 != 0 || da_x1 != 0 || da_y0 != 0 || da_y1 != 0) {