From cfc875a76586b0bbb277429b3700c0c5daee338a Mon Sep 17 00:00:00 2001 From: Anton Tykhyy Date: Tue, 6 Feb 2024 00:37:19 +0200 Subject: [PATCH 1/2] Make pix{Read,Write}MemJp2k work via direct memory access. * On platforms lacking open_memstream(), pix{Read,Write}MemJp2k() worked via a temporary file, but this overhead is unnecessary as openjpeg2000's opj_stream allows one to read/write directly from a memory buffer by supplying it with appropriate callbacks. * This commit makes pix{Read,Write}MemJp2k() use this more direct mechanism on all platforms. * In addition, since pixReadStreamJp2k() always read the whole stream into memory when it called fgetJp2kResolution(), this commit saves a bit of compute resources by in effect making pixReadStreamJp2k() into a wrapper for pixReadMemJp2k(). --- src/allheaders.h | 1 + src/jp2kheader.c | 44 +++++-- src/jp2kheaderstub.c | 8 ++ src/jp2kio.c | 294 +++++++++++++++++++++++++++++++++---------- 4 files changed, 271 insertions(+), 76 deletions(-) diff --git a/src/allheaders.h b/src/allheaders.h index 80f374afb..157364d16 100644 --- a/src/allheaders.h +++ b/src/allheaders.h @@ -1070,6 +1070,7 @@ LEPT_DLL extern l_ok readHeaderJp2k ( const char *filename, l_int32 *pw, l_int32 LEPT_DLL extern l_ok freadHeaderJp2k ( FILE *fp, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp, l_int32 *pcodec ); LEPT_DLL extern l_ok readHeaderMemJp2k ( const l_uint8 *data, size_t size, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp, l_int32 *pcodec ); LEPT_DLL extern l_int32 fgetJp2kResolution ( FILE *fp, l_int32 *pxres, l_int32 *pyres ); +LEPT_DLL extern l_ok readResolutionMemJp2k ( const l_uint8 *data, size_t size, l_int32 *pxres, l_int32 *pyres ); LEPT_DLL extern PIX * pixReadJp2k ( const char *filename, l_uint32 reduction, BOX *box, l_int32 hint, l_int32 debug ); LEPT_DLL extern PIX * pixReadStreamJp2k ( FILE *fp, l_uint32 reduction, BOX *box, l_int32 hint, l_int32 debug ); LEPT_DLL extern l_ok pixWriteJp2k ( const char *filename, PIX *pix, l_int32 quality, l_int32 nlevels, l_int32 hint, l_int32 debug ); diff --git a/src/jp2kheader.c b/src/jp2kheader.c index 66f0df9b1..c52e87649 100644 --- a/src/jp2kheader.c +++ b/src/jp2kheader.c @@ -274,35 +274,57 @@ fgetJp2kResolution(FILE *fp, l_int32 *pxres, l_int32 *pyres) { -l_uint8 xexp, yexp; l_uint8 *data; +size_t nbytes; +l_ok ok; + + if (!fp) + return ERROR_INT("stream not opened", __func__, 1); + + rewind(fp); + data = l_binaryReadStream(fp, &nbytes); + rewind(fp); + + ok = readResolutionMemJp2k(data, nbytes, pxres, pyres); + + LEPT_FREE(data); + return ok; +} + +/*! + * \brief readResolutionMemJp2k() + * + * \param[in] data const; jp2k-encoded + * \param[in] size of data + * \param[out] pxres [optional] + * \param[out] pyres [optional] + * \return 0 if OK, 1 on error + */ +l_ok +readResolutionMemJp2k(const l_uint8 *data, + size_t nbytes, + l_int32 *pxres, + l_int32 *pyres) +{ +l_uint8 xexp, yexp; l_uint16 xnum, ynum, xdenom, ydenom; /* these jp2k fields are 2-byte */ l_int32 loc, found; l_uint8 resc[4] = {0x72, 0x65, 0x73, 0x63}; /* 'resc' */ -size_t nbytes; l_float64 xres, yres, maxres; if (pxres) *pxres = 0; if (pyres) *pyres = 0; if (!pxres || !pyres) return ERROR_INT("&xres and &yres not both defined", __func__, 1); - if (!fp) - return ERROR_INT("stream not opened", __func__, 1); - - rewind(fp); - data = l_binaryReadStream(fp, &nbytes); - rewind(fp); /* Search for the start of the first capture resolution box: 'resc' */ arrayFindSequence(data, nbytes, resc, 4, &loc, &found); if (!found) { L_WARNING("image resolution not found\n", __func__); - LEPT_FREE(data); return 1; } if (nbytes < 80 || loc >= nbytes - 13) { L_WARNING("image resolution found without enough space\n", __func__); - LEPT_FREE(data); return 1; } @@ -318,7 +340,6 @@ l_float64 xres, yres, maxres; xdenom = convertOnLittleEnd16(xdenom); if (ydenom == 0 || xdenom == 0) { L_WARNING("bad data: ydenom or xdenom is 0\n", __func__); - LEPT_FREE(data); return 1; } yexp = data[loc + 12]; @@ -339,7 +360,6 @@ l_float64 xres, yres, maxres; *pxres = (l_int32)(xres + 0.5); } - LEPT_FREE(data); return 0; } diff --git a/src/jp2kheaderstub.c b/src/jp2kheaderstub.c index c8db2abd5..ecc540ce7 100644 --- a/src/jp2kheaderstub.c +++ b/src/jp2kheaderstub.c @@ -72,5 +72,13 @@ l_int32 fgetJp2kResolution(FILE *fp, l_int32 *pxres, l_int32 *pyres) return ERROR_INT("function not present", __func__, 1); } +/* ----------------------------------------------------------------------*/ + +l_ok readResolutionMemJp2k(const l_uint8 *data, size_t size, + l_int32 *pxres, l_int32 *pyres) +{ + return ERROR_INT("function not present", __func__, 1); +} + /* --------------------------------------------*/ #endif /* !USE_JP2KHEADER */ diff --git a/src/jp2kio.c b/src/jp2kio.c index 0ded2d5b6..7a1366dcb 100644 --- a/src/jp2kio.c +++ b/src/jp2kio.c @@ -111,6 +111,18 @@ #include #endif + /*! For in-memory encoding and decoding of JP2K */ +typedef struct OpjBuffer +{ + l_uint8 *data; /*!< data in the buffer */ + size_t size; /*!< size of buffer */ + size_t pos; /*!< position relative to beginning of buffer */ + size_t len; /*!< length of valid data in the buffer */ +} OpjBuffer; + + /* Static generator of opj_stream from a memory buffer. */ +static opj_stream_t *opjCreateMemoryStream(OpjBuffer *buf, l_int32 is_read); + /* Static generator of opj_stream from file stream. * In 2.0.1, this functionality is provided by * opj_stream_create_default_file_stream(), @@ -232,6 +244,34 @@ pixReadStreamJp2k(FILE *fp, l_int32 hint, l_int32 debug) { +const l_uint8 *data; +size_t size; +PIX *pix; + + if (!fp) + return (PIX *)ERROR_PTR("fp not defined", __func__, NULL); + + /* fgetJp2kResolution() would read the whole stream anyway, + * so we might as well start off by doing that */ + rewind(fp); + if ((data = l_binaryReadStream(fp, &size)) == NULL) + return (PIX *)ERROR_PTR("data not read", __func__, NULL); + + pix = pixReadMemJp2k(data, size, reduction, box, hint, debug); + + LEPT_FREE(data); + return pix; +} + + +static PIX * +pixReadMemJp2kCore(const l_uint8 *bytes, + size_t nbytes, + l_uint32 reduction, + BOX *box, + l_int32 hint, + l_int32 debug) +{ const char *opjVersion; l_int32 i, j, index, bx, by, bw, bh, val, rval, gval, bval, aval; l_int32 w, h, wpl, bps, spp, xres, yres, reduce, prec, colorspace; @@ -243,9 +283,7 @@ opj_image_t *image = NULL; opj_codec_t *l_codec = NULL; /* handle to decompressor */ opj_stream_t *l_stream = NULL; /* opj stream */ PIX *pix = NULL; - - if (!fp) - return (PIX *)ERROR_PTR("fp not defined", __func__, NULL); +OpjBuffer buffer; opjVersion = opj_version(); if (!opjVersion || opjVersion[0] == '\0') @@ -257,10 +295,8 @@ PIX *pix = NULL; } /* Get the resolution, bits/sample and codec type */ - rewind(fp); - fgetJp2kResolution(fp, &xres, &yres); - freadHeaderJp2k(fp, NULL, NULL, &bps, NULL, &codec); - rewind(fp); + readResolutionMemJp2k(bytes, nbytes, &xres, &yres); + readHeaderMemJp2k(bytes, nbytes, NULL, NULL, &bps, NULL, &codec); if (codec != L_J2K_CODEC && codec != L_JP2_CODEC) { L_ERROR("valid codec not identified\n", __func__); return NULL; @@ -310,9 +346,12 @@ PIX *pix = NULL; return NULL; } - /* Open decompression 'stream'. This uses our version of the - * function that was removed in 2.1. */ - if ((l_stream = opjCreateStream(fp, 1)) == NULL) { + /* Open decompression 'stream'. */ + buffer.data = (l_uint8 *)bytes; + buffer.size = nbytes; + buffer.len = nbytes; + buffer.pos = 0; + if ((l_stream = opjCreateMemoryStream(&buffer, 1)) == NULL) { L_ERROR("failed to open the stream\n", __func__); opj_destroy_codec(l_codec); return NULL; @@ -499,9 +538,9 @@ FILE *fp; /*! - * \brief pixWriteStreamJp2k() + * \brief pixWriteOpjStreamJp2k() * - * \param[in] fp file stream + * \param[in] l_stream OPJ stream * \param[in] pix any depth, cmap is OK * \param[in] quality SNR > 0; 0 for default (34); 100 for lossless * \param[in] nlevels resolution levels; 6 or 7; use 0 for default (6) @@ -514,8 +553,8 @@ FILE *fp; * (1) See pixWriteJp2k() for usage. * */ -l_ok -pixWriteStreamJp2k(FILE *fp, +static l_ok +pixWriteOpjStreamJp2k(opj_stream_t *l_stream, PIX *pix, l_int32 quality, l_int32 nlevels, @@ -528,11 +567,10 @@ l_float64 snr; const char *opjVersion; PIX *pixs; opj_cparameters_t parameters; /* compression parameters */ -opj_stream_t *l_stream = NULL; opj_codec_t* l_codec = NULL;; opj_image_t *image = NULL; - if (!fp) + if (!l_stream) return ERROR_INT("stream not open", __func__, 1); if (!pix) return ERROR_INT("pix not defined", __func__, 1); @@ -655,26 +693,14 @@ opj_image_t *image = NULL; /* Set the resolution (TBD) */ - /* Open compression stream for writing. This uses our version - * of the function that was removed in 2.1. */ - rewind(fp); - if ((l_stream = opjCreateStream(fp, 0)) == NULL) { - opj_destroy_codec(l_codec); - opj_image_destroy(image); - LEPT_FREE(parameters.cp_comment); - return ERROR_INT("failed to open l_stream\n", __func__, 1); - } - /* Encode the image */ if (!opj_start_compress(l_codec, image, l_stream)) { - opj_stream_destroy(l_stream); opj_destroy_codec(l_codec); opj_image_destroy(image); LEPT_FREE(parameters.cp_comment); return ERROR_INT("opj_start_compress failed\n", __func__, 1); } if (!opj_encode(l_codec, l_stream)) { - opj_stream_destroy(l_stream); opj_destroy_codec(l_codec); opj_image_destroy(image); LEPT_FREE(parameters.cp_comment); @@ -683,7 +709,6 @@ opj_image_t *image = NULL; success = opj_end_compress(l_codec, l_stream); /* Clean up */ - opj_stream_destroy(l_stream); opj_destroy_codec(l_codec); opj_image_destroy(image); LEPT_FREE(parameters.cp_comment); @@ -694,6 +719,53 @@ opj_image_t *image = NULL; } +/*! + * \brief pixWriteStreamJp2k() + * + * \param[in] fp file stream + * \param[in] pix any depth, cmap is OK + * \param[in] quality SNR > 0; 0 for default (34); 100 for lossless + * \param[in] nlevels <= 10 + * \param[in] codec L_JP2_CODEC or L_J2K_CODEC + * \param[in] hint a bitwise OR of L_JP2K_* values; 0 for default + * \param[in] debug output callback messages, etc + * \return 0 if OK, 1 on error + *
+ * Notes:
+ *      (1) See pixWriteJp2k() for usage.
+ * 
+ */ +l_ok +pixWriteStreamJp2k(FILE *fp, + PIX *pix, + l_int32 quality, + l_int32 nlevels, + l_int32 codec, + l_int32 hint, + l_int32 debug) +{ +l_ok ok; +opj_stream_t *l_stream; + + if (!fp) + return ERROR_INT("stream not open", __func__, 1); + + /* Open a compression stream for writing. In 2.0 we could use this: + * opj_stream_create_default_file_stream(fp, 0) + * but the file stream interface was removed in 2.1. */ + rewind(fp); + if ((l_stream = opjCreateStream(fp, 0)) == NULL) { + return ERROR_INT("failed to open l_stream\n", __func__, 1); + } + + ok = pixWriteOpjStreamJp2k(l_stream, pix, quality, nlevels, codec, hint, debug); + + /* Clean up */ + opj_stream_destroy(l_stream); + return ok; +} + + /*! * \brief pixConvertToOpjImage() * @@ -793,10 +865,7 @@ opj_image_cmptparm_t cmptparm[4]; * *
  * Notes:
- *      (1) This crashes when reading through the fmemopen cookie.
- *          Until this is fixed, which may take a long time, we use
- *          the file-based work-around.
- *      (2) See pixReadJp2k() for usage.
+ *      (1) See pixReadJp2k() for usage.
  * 
*/ PIX * @@ -807,16 +876,12 @@ pixReadMemJp2k(const l_uint8 *data, l_int32 hint, l_int32 debug) { -FILE *fp; PIX *pix; if (!data) return (PIX *)ERROR_PTR("data not defined", __func__, NULL); - if ((fp = fopenReadFromMemory(data, size)) == NULL) - return (PIX *)ERROR_PTR("stream not opened", __func__, NULL); - pix = pixReadStreamJp2k(fp, reduction, box, hint, debug); - fclose(fp); + pix = pixReadMemJp2kCore(data, size, reduction, box, hint, debug); if (!pix) L_ERROR("pix not read\n", __func__); return pix; } @@ -849,8 +914,9 @@ pixWriteMemJp2k(l_uint8 **pdata, l_int32 hint, l_int32 debug) { -l_int32 ret; -FILE *fp; +l_ok ok; +opj_stream_t *l_stream; +OpjBuffer buffer; if (pdata) *pdata = NULL; if (psize) *psize = 0; @@ -861,32 +927,132 @@ FILE *fp; if (!pix) return ERROR_INT("&pix not defined", __func__, 1 ); -#if HAVE_FMEMOPEN - if ((fp = open_memstream((char **)pdata, psize)) == NULL) - return ERROR_INT("stream not opened", __func__, 1); - ret = pixWriteStreamJp2k(fp, pix, quality, nlevels, L_JP2_CODEC, - hint, debug); - fputc('\0', fp); - fclose(fp); - *psize = *psize - 1; -#else - L_INFO("no fmemopen API --> work-around: writing to a temp file\n", __func__); - #ifdef _WIN32 - if ((fp = fopenWriteWinTempfile()) == NULL) - return ERROR_INT("tmpfile stream not opened", __func__, 1); - #else - if ((fp = tmpfile()) == NULL) - return ERROR_INT("tmpfile stream not opened", __func__, 1); - #endif /* _WIN32 */ - ret = pixWriteStreamJp2k(fp, pix, quality, nlevels, L_JP2_CODEC, - hint, debug); - rewind(fp); - *pdata = l_binaryReadStream(fp, psize); - fclose(fp); -#endif /* HAVE_FMEMOPEN */ - return ret; + buffer.pos = 0; + buffer.len = 0; + buffer.size = OPJ_J2K_STREAM_CHUNK_SIZE; + buffer.data = (l_uint8 *)LEPT_MALLOC(buffer.size); + if (!buffer.data) + return ERROR_INT("failed to allocate buffer", __func__, 1 ); + + if ((l_stream = opjCreateMemoryStream(&buffer, 0)) == NULL) { + return ERROR_INT("failed to open l_stream\n", __func__, 1); + } + + ok = pixWriteOpjStreamJp2k(l_stream, pix, quality, nlevels, L_JP2_CODEC, + hint, debug); + + /* Clean up */ + opj_stream_destroy(l_stream); + + if (!ok) { + *pdata = buffer.data; + *psize = buffer.len; + } else { + LEPT_FREE(buffer.data); + } + + return ok; +} + + +/*---------------------------------------------------------------------* + * Static functions for the memory stream interface * + *---------------------------------------------------------------------*/ +static OPJ_SIZE_T +opj_read_from_buffer(void *p_buffer, OPJ_SIZE_T p_nb_bytes, OpjBuffer *pbuf) { + if (pbuf->pos < 0 || pbuf->pos > pbuf->len) + return (OPJ_SIZE_T) - 1; + + OPJ_SIZE_T l_nb_read = pbuf->len - pbuf->pos; + if (l_nb_read > p_nb_bytes) + l_nb_read = p_nb_bytes; + memcpy(p_buffer, pbuf->data + pbuf->pos, l_nb_read); + pbuf->pos += l_nb_read; + return l_nb_read ? l_nb_read : (OPJ_SIZE_T) - 1; +} + +static OPJ_SIZE_T +opj_write_from_buffer(const void *p_buffer, OPJ_SIZE_T p_nb_bytes, OpjBuffer *pbuf) { + if (pbuf->pos < 0) + return 0; + + size_t newpos = pbuf->pos + p_nb_bytes; + if (newpos > pbuf->size) { + size_t oldsize = pbuf->size; + size_t newsize = oldsize * 2; + if (newsize < newpos) + newsize = newpos; + if (newsize <= 0) { + L_ERROR("buffer too large\n", __func__); + return 0; + } + + l_uint8 *newdata = (l_uint8 *)LEPT_REALLOC(pbuf->data, newsize); + if (!newdata) { + L_ERROR("out of memory\n", __func__); + return 0; + } + + /* clear out any garbage left by realloc */ + memset(newdata + oldsize, 0, newsize - oldsize); + pbuf->data = newdata; + pbuf->size = newsize; + } + + memcpy(pbuf->data + pbuf->pos, p_buffer, p_nb_bytes); + pbuf->pos = newpos; + if (pbuf->len < newpos) + pbuf->len = newpos; + return p_nb_bytes; +} + +static OPJ_OFF_T +opj_skip_from_buffer(OPJ_OFF_T offset, OpjBuffer *pbuf) { + pbuf->pos += offset; + return offset; } +static l_int32 +opj_seek_from_buffer(OPJ_OFF_T offset, OpjBuffer *pbuf) { + pbuf->pos = offset; + return 1; +} + + /* Static generator of opj_stream from memory buffer */ +static opj_stream_t * +opjCreateMemoryStream(OpjBuffer *pbuf, + l_int32 is_read_stream) +{ +opj_stream_t *l_stream; + + if (!pbuf) + return (opj_stream_t *)ERROR_PTR("pbuf not defined", __func__, NULL); + + l_stream = opj_stream_create(OPJ_J2K_STREAM_CHUNK_SIZE, is_read_stream); + if (!l_stream) + return (opj_stream_t *)ERROR_PTR("stream not made", __func__, NULL); + +#if OPJ_VERSION_MINOR == 0 + opj_stream_set_user_data(l_stream, pbuf); +#else + opj_stream_set_user_data(l_stream, pbuf, + (opj_stream_free_user_data_fn)NULL); +#endif + opj_stream_set_user_data_length(l_stream, pbuf->len); + opj_stream_set_read_function(l_stream, + (opj_stream_read_fn)opj_read_from_buffer); + opj_stream_set_skip_function(l_stream, + (opj_stream_skip_fn)opj_skip_from_buffer); + opj_stream_set_seek_function(l_stream, + (opj_stream_seek_fn)opj_seek_from_buffer); + + if (is_read_stream) + return l_stream; + + opj_stream_set_write_function(l_stream, + (opj_stream_write_fn)opj_write_from_buffer); + return l_stream; +} /*---------------------------------------------------------------------* * Static functions from opj 2.0 to retain file stream interface * From 863a608c88ef6fc68c1d59852b112f76c17eed1b Mon Sep 17 00:00:00 2001 From: Anton Tykhyy Date: Tue, 6 Feb 2024 01:12:36 +0200 Subject: [PATCH 2/2] Fix errors introduced by patch merge --- src/jp2kio.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/jp2kio.c b/src/jp2kio.c index 7a1366dcb..81f98eda0 100644 --- a/src/jp2kio.c +++ b/src/jp2kio.c @@ -244,9 +244,9 @@ pixReadStreamJp2k(FILE *fp, l_int32 hint, l_int32 debug) { -const l_uint8 *data; -size_t size; -PIX *pix; +l_uint8 *data; +size_t size; +PIX *pix; if (!fp) return (PIX *)ERROR_PTR("fp not defined", __func__, NULL); @@ -1032,12 +1032,8 @@ opj_stream_t *l_stream; if (!l_stream) return (opj_stream_t *)ERROR_PTR("stream not made", __func__, NULL); -#if OPJ_VERSION_MINOR == 0 - opj_stream_set_user_data(l_stream, pbuf); -#else opj_stream_set_user_data(l_stream, pbuf, (opj_stream_free_user_data_fn)NULL); -#endif opj_stream_set_user_data_length(l_stream, pbuf->len); opj_stream_set_read_function(l_stream, (opj_stream_read_fn)opj_read_from_buffer);