Allow several repeated calls to opj_set_decode_area() and opj_decode() for single...
authorEven Rouault <even.rouault@spatialys.com>
Fri, 1 Sep 2017 14:30:48 +0000 (16:30 +0200)
committerEven Rouault <even.rouault@spatialys.com>
Fri, 1 Sep 2017 14:30:48 +0000 (16:30 +0200)
* 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
src/lib/openjp2/openjpeg.h
src/lib/openjp2/t1.c
tests/CMakeLists.txt
tests/test_decode_area.c

index f1a894a4bf2bfc8e3a830ea2caa842d232140e24..e548fefc776568dcb242e37962f0c4a15556b98d 100644 (file)
@@ -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];
index 21755b48d5a091b80d84043941eca0da295f236f..7020d37d28e483ab565b4753cdf9575bc2b43163 100644 (file)
@@ -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).
index 44a2f2437e61a7740945d3bd5c8866ae07ebd849..0277f8cc73260dd287ac9d96459408b171afefba 100644 (file)
@@ -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,
index 579e066b80846260fc6cb52986593fa6c7c571c4..95b7529158a7a310139b3e4e24101ebd0b40e42e 100644 (file)
@@ -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.
index e773bc667111e900927f62518155e7f399bd1f1b..922299080d6ba917950a7d2b2f3fe345fc9011a7 100644 (file)
@@ -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) {