From b3f0abe377f2dd83396c9d0d4176f684c122af3f Mon Sep 17 00:00:00 2001 From: DRC Date: Fri, 6 Sep 2024 10:23:02 -0400 Subject: [PATCH] TJ: Calc. xformed buf sizes based on dst. subsamp With respect to tj3Transform(), this addresses an oversight from bb1d540a807783a3db8b85bab2993d70b1330287. Note to self: A convenience function/method for computing the worst-case transformed JPEG size for a particular transform would be nice. --- doc/html/group___turbo_j_p_e_g.html | 2 +- fuzz/transform.cc | 4 +- java/TJBench.java | 48 +++++++++--------- java/doc/member-search-index.zip | Bin 1951 -> 1951 bytes .../libjpegturbo/turbojpeg/TJTransformer.html | 3 +- java/doc/package-search-index.zip | Bin 237 -> 237 bytes java/doc/type-search-index.zip | Bin 311 -> 311 bytes .../libjpegturbo/turbojpeg/TJTransformer.java | 26 ++++++++-- tjbench.c | 33 ++++++------ turbojpeg-jni.c | 4 ++ turbojpeg.c | 31 ++++++----- turbojpeg.h | 8 +-- 12 files changed, 92 insertions(+), 67 deletions(-) diff --git a/doc/html/group___turbo_j_p_e_g.html b/doc/html/group___turbo_j_p_e_g.html index 75321f91..42c30095 100644 --- a/doc/html/group___turbo_j_p_e_g.html +++ b/doc/html/group___turbo_j_p_e_g.html @@ -3173,7 +3173,7 @@ If you choose option 1, then *jpegSize should be set to the size of dstBufspointer to an array of n byte buffers. dstBufs[i] will receive a JPEG image that has been transformed using the parameters in transforms[i]. TurboJPEG has the ability to reallocate the JPEG destination buffer to accommodate the size of the transformed JPEG image. Thus, you can choose to:
  1. pre-allocate the JPEG destination buffer with an arbitrary size using tj3Alloc() and let TurboJPEG grow the buffer as needed,
  2. set dstBufs[i] to NULL to tell TurboJPEG to allocate the buffer for you, or
  3. -
  4. pre-allocate the buffer to a "worst case" size determined by calling tj3JPEGBufSize() with the transformed or cropped width and height and the level of subsampling used in the destination image. Under normal circumstances, this should ensure that the buffer never has to be re-allocated. (Setting TJPARAM_NOREALLOC guarantees that it won't be.) Note, however, that there are some rare cases (such as transforming images with a large amount of embedded Exif or ICC profile data) in which the transformed JPEG image will be larger than the worst-case size, and TJPARAM_NOREALLOC cannot be used in those cases.
  5. +
  6. pre-allocate the buffer to a "worst case" size determined by calling tj3JPEGBufSize() with the transformed or cropped width and height and the level of subsampling used in the destination image (taking into account grayscale conversion and transposition of the width and height.) Under normal circumstances, this should ensure that the buffer never has to be re-allocated. (Setting TJPARAM_NOREALLOC guarantees that it won't be.) Note, however, that there are some rare cases (such as transforming images with a large amount of embedded Exif or ICC profile data) in which the transformed JPEG image will be larger than the worst-case size, and TJPARAM_NOREALLOC cannot be used in those cases unless the embedded data is discarded using TJXOPT_COPYNONE.
If you choose option 1, then dstSizes[i] should be set to the size of your pre-allocated buffer. In any case, unless you have set TJPARAM_NOREALLOC, you should always check dstBufs[i] upon return from this function, as it may have changed. dstSizespointer to an array of n size_t variables that will receive the actual sizes (in bytes) of each transformed JPEG image. If dstBufs[i] points to a pre-allocated buffer, then dstSizes[i] should be set to the size of the buffer. Upon return, dstSizes[i] will contain the size of the transformed JPEG image (in bytes.) diff --git a/fuzz/transform.cc b/fuzz/transform.cc index ba3acf2e..6497121f 100644 --- a/fuzz/transform.cc +++ b/fuzz/transform.cc @@ -102,11 +102,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) TJXOPT_OPTIMIZE; dstBufs[0] = (unsigned char *)tj3Alloc(tj3JPEGBufSize((height + 1) / 2, (width + 1) / 2, - jpegSubsamp)); + TJSAMP_GRAY)); if (!dstBufs[0]) goto bailout; - maxBufSize = tj3JPEGBufSize((height + 1) / 2, (width + 1) / 2, jpegSubsamp); + maxBufSize = tj3JPEGBufSize((height + 1) / 2, (width + 1) / 2, TJSAMP_GRAY); if (tj3Transform(handle, data, size, 1, dstBufs, dstSizes, transforms) == 0) { diff --git a/java/TJBench.java b/java/TJBench.java index e42142c5..5be141a3 100644 --- a/java/TJBench.java +++ b/java/TJBench.java @@ -629,6 +629,22 @@ final class TJBench { } tsubsamp = subsamp; + if ((xformOpt & TJTransform.OPT_GRAY) != 0) + tsubsamp = TJ.SAMP_GRAY; + if (xformOp == TJTransform.OP_TRANSPOSE || + xformOp == TJTransform.OP_TRANSVERSE || + xformOp == TJTransform.OP_ROT90 || + xformOp == TJTransform.OP_ROT270) { + if (tsubsamp == TJ.SAMP_422) + tsubsamp = TJ.SAMP_440; + else if (tsubsamp == TJ.SAMP_440) + tsubsamp = TJ.SAMP_422; + else if (tsubsamp == TJ.SAMP_411) + tsubsamp = TJ.SAMP_441; + else if (tsubsamp == TJ.SAMP_441) + tsubsamp = TJ.SAMP_411; + } + if (doTile || xformOp != TJTransform.OP_NONE || xformOpt != 0 || customFilter != null) { if (xformOp == TJTransform.OP_TRANSPOSE || @@ -641,40 +657,22 @@ final class TJBench { if (xformOp != TJTransform.OP_NONE && xformOp != TJTransform.OP_TRANSPOSE && subsamp == TJ.SAMP_UNKNOWN) throw new Exception("Could not determine subsampling level of JPEG image"); - if ((xformOpt & TJTransform.OPT_GRAY) != 0) - tsubsamp = TJ.SAMP_GRAY; if (xformOp == TJTransform.OP_HFLIP || + xformOp == TJTransform.OP_TRANSVERSE || + xformOp == TJTransform.OP_ROT90 || xformOp == TJTransform.OP_ROT180) tw = tw - (tw % TJ.getMCUWidth(tsubsamp)); if (xformOp == TJTransform.OP_VFLIP || - xformOp == TJTransform.OP_ROT180) - th = th - (th % TJ.getMCUHeight(tsubsamp)); - if (xformOp == TJTransform.OP_TRANSVERSE || - xformOp == TJTransform.OP_ROT90) - tw = tw - (tw % TJ.getMCUHeight(tsubsamp)); - if (xformOp == TJTransform.OP_TRANSVERSE || + xformOp == TJTransform.OP_TRANSVERSE || + xformOp == TJTransform.OP_ROT180 || xformOp == TJTransform.OP_ROT270) - th = th - (th % TJ.getMCUWidth(tsubsamp)); + th = th - (th % TJ.getMCUHeight(tsubsamp)); tntilesw = (tw + ttilew - 1) / ttilew; tntilesh = (th + ttileh - 1) / ttileh; - if (xformOp == TJTransform.OP_TRANSPOSE || - xformOp == TJTransform.OP_TRANSVERSE || - xformOp == TJTransform.OP_ROT90 || - xformOp == TJTransform.OP_ROT270) { - if (tsubsamp == TJ.SAMP_422) - tsubsamp = TJ.SAMP_440; - else if (tsubsamp == TJ.SAMP_440) - tsubsamp = TJ.SAMP_422; - else if (tsubsamp == TJ.SAMP_411) - tsubsamp = TJ.SAMP_441; - else if (tsubsamp == TJ.SAMP_441) - tsubsamp = TJ.SAMP_411; - } - TJTransform[] t = new TJTransform[tntilesw * tntilesh]; jpegBufs = - new byte[tntilesw * tntilesh][TJ.bufSize(ttilew, ttileh, subsamp)]; + new byte[tntilesw * tntilesh][TJ.bufSize(ttilew, ttileh, tsubsamp)]; for (y = 0, tile = 0; y < th; y += ttileh) { for (x = 0; x < tw; x += ttilew, tile++) { @@ -737,7 +735,7 @@ final class TJBench { } else { if (quiet == 1) System.out.print("N/A N/A "); - jpegBufs = new byte[1][TJ.bufSize(ttilew, ttileh, subsamp)]; + jpegBufs = new byte[1][TJ.bufSize(ttilew, ttileh, tsubsamp)]; jpegSizes = new int[1]; jpegBufs[0] = srcBuf; jpegSizes[0] = srcSize; diff --git a/java/doc/member-search-index.zip b/java/doc/member-search-index.zip index a67ea75cd420e0344b54be3f0a3d0aab51ee3bdf..ee5589b05a241774d553d7a01d437b1e3c060b0c 100644 GIT binary patch delta 30 kcmbQwKcAmBz?+#xgn@&DgMlqXZ6mJ}J2Q};9K`Mm092|4djJ3c delta 30 kcmbQwKcAmBz?+#xgn@&DgW=I;)s4JP?94!VauB;O0B-CD?*IS* diff --git a/java/doc/org/libjpegturbo/turbojpeg/TJTransformer.html b/java/doc/org/libjpegturbo/turbojpeg/TJTransformer.html index b767e6de..0ae19bf3 100644 --- a/java/doc/org/libjpegturbo/turbojpeg/TJTransformer.html +++ b/java/doc/org/libjpegturbo/turbojpeg/TJTransformer.html @@ -379,7 +379,8 @@ extends TJ.bufSize() to determine the maximum size for each buffer based on the transformed or cropped width and height and the level - of subsampling used in the destination image. + of subsampling used in the destination image (taking into account + grayscale conversion and transposition of the width and height.)
transforms - an array of TJTransform instances, each of which specifies the transform parameters and/or cropping region for the corresponding transformed JPEG image
diff --git a/java/doc/package-search-index.zip b/java/doc/package-search-index.zip index 3b023f2025418cbe1a5410782f3ded45efaa53f1..3fae6c966c3f9f6bd5f53734373641edf5df5aa7 100644 GIT binary patch delta 28 hcmaFM_?D43z?+#xgn@&DgMlqXZ6a?!Gl-h+2LM{$24(;N delta 28 hcmaFM_?D43z?+#xgn@&DgW=I;)rq|Q%phvM9{_Q32-*Mu diff --git a/java/doc/type-search-index.zip b/java/doc/type-search-index.zip index 381ebb0c03461e0bb19d012ed43d78afdb150ab6..e6b014e02575271721fa4a7457ea5bf4273f594e 100644 GIT binary patch delta 28 hcmdnaw4I4Jz?+#xgn@&DgMlqXZ6fa-W)Stp8vs(R29p2) delta 28 hcmdnaw4I4Jz?+#xgn@&DgW=I;)rq`!m_gJVZvbgQ2?qcG diff --git a/java/org/libjpegturbo/turbojpeg/TJTransformer.java b/java/org/libjpegturbo/turbojpeg/TJTransformer.java index 84bf3dfc..68848812 100644 --- a/java/org/libjpegturbo/turbojpeg/TJTransformer.java +++ b/java/org/libjpegturbo/turbojpeg/TJTransformer.java @@ -89,7 +89,8 @@ public class TJTransformer extends TJDecompressor { * transformed using the parameters in transforms[i]. Use * {@link TJ#bufSize TJ.bufSize()} to determine the maximum size for each * buffer based on the transformed or cropped width and height and the level - * of subsampling used in the destination image. + * of subsampling used in the destination image (taking into account + * grayscale conversion and transposition of the width and height.) * * @param transforms an array of {@link TJTransform} instances, each of * which specifies the transform parameters and/or cropping region for the @@ -130,14 +131,33 @@ public class TJTransformer extends TJDecompressor { byte[][] dstBufs = new byte[transforms.length][]; if (getWidth() < 1 || getHeight() < 1) throw new IllegalStateException("JPEG buffer not initialized"); - checkSubsampling(); + int srcSubsamp = get(TJ.PARAM_SUBSAMP); for (int i = 0; i < transforms.length; i++) { int w = getWidth(), h = getHeight(); + int dstSubsamp = srcSubsamp; + + if ((transforms[i].options & TJTransform.OPT_GRAY) != 0) + dstSubsamp = TJ.SAMP_GRAY; + if (transforms[i].op == TJTransform.OP_TRANSPOSE || + transforms[i].op == TJTransform.OP_TRANSVERSE || + transforms[i].op == TJTransform.OP_ROT90 || + transforms[i].op == TJTransform.OP_ROT270) { + w = getHeight(); h = getWidth(); + if (dstSubsamp == TJ.SAMP_422) + dstSubsamp = TJ.SAMP_440; + else if (dstSubsamp == TJ.SAMP_440) + dstSubsamp = TJ.SAMP_422; + else if (dstSubsamp == TJ.SAMP_411) + dstSubsamp = TJ.SAMP_441; + else if (dstSubsamp == TJ.SAMP_441) + dstSubsamp = TJ.SAMP_411; + } + if ((transforms[i].options & TJTransform.OPT_CROP) != 0) { if (transforms[i].width != 0) w = transforms[i].width; if (transforms[i].height != 0) h = transforms[i].height; } - dstBufs[i] = new byte[TJ.bufSize(w, h, get(TJ.PARAM_SUBSAMP))]; + dstBufs[i] = new byte[TJ.bufSize(w, h, dstSubsamp)]; } TJDecompressor[] tjd = new TJDecompressor[transforms.length]; transform(dstBufs, transforms); diff --git a/tjbench.c b/tjbench.c index ccf4a896..b91c36eb 100644 --- a/tjbench.c +++ b/tjbench.c @@ -738,6 +738,15 @@ static int decompTest(char *fileName) THROW_UNIX("allocating JPEG size array"); memset(jpegSizes, 0, sizeof(size_t) * ntilesw * ntilesh); + tsubsamp = (xformOpt & TJXOPT_GRAY) ? TJSAMP_GRAY : subsamp; + if (xformOp == TJXOP_TRANSPOSE || xformOp == TJXOP_TRANSVERSE || + xformOp == TJXOP_ROT90 || xformOp == TJXOP_ROT270) { + if (tsubsamp == TJSAMP_422) tsubsamp = TJSAMP_440; + else if (tsubsamp == TJSAMP_440) tsubsamp = TJSAMP_422; + else if (tsubsamp == TJSAMP_411) tsubsamp = TJSAMP_441; + else if (tsubsamp == TJSAMP_441) tsubsamp = TJSAMP_411; + } + if (noRealloc && (doTile || xformOp != TJXOP_NONE || xformOpt != 0 || customFilter)) { for (i = 0; i < ntilesw * ntilesh; i++) { @@ -745,9 +754,9 @@ static int decompTest(char *fileName) if (xformOp == TJXOP_TRANSPOSE || xformOp == TJXOP_TRANSVERSE || xformOp == TJXOP_ROT90 || xformOp == TJXOP_ROT270) - jpegBufSize = tj3JPEGBufSize(tileh, tilew, subsamp); + jpegBufSize = tj3JPEGBufSize(tileh, tilew, tsubsamp); else - jpegBufSize = tj3JPEGBufSize(tilew, tileh, subsamp); + jpegBufSize = tj3JPEGBufSize(tilew, tileh, tsubsamp); if (jpegBufSize == 0) THROW_TJG(); if ((jpegBufs[i] = tj3Alloc(jpegBufSize)) == NULL) @@ -767,7 +776,6 @@ static int decompTest(char *fileName) printf("%-5d %-5d ", CROPPED_WIDTH(tilew), CROPPED_HEIGHT(tileh)); } - tsubsamp = subsamp; if (doTile || xformOp != TJXOP_NONE || xformOpt != 0 || customFilter) { if ((t = (tjtransform *)malloc(sizeof(tjtransform) * ntilesw * ntilesh)) == NULL) @@ -782,26 +790,15 @@ static int decompTest(char *fileName) subsamp == TJSAMP_UNKNOWN) THROW("transforming", "Could not determine subsampling level of JPEG image"); - if (xformOpt & TJXOPT_GRAY) tsubsamp = TJSAMP_GRAY; - if (xformOp == TJXOP_HFLIP || xformOp == TJXOP_ROT180) + if (xformOp == TJXOP_HFLIP || xformOp == TJXOP_TRANSVERSE || + xformOp == TJXOP_ROT90 || xformOp == TJXOP_ROT180) tw = tw - (tw % tjMCUWidth[tsubsamp]); - if (xformOp == TJXOP_VFLIP || xformOp == TJXOP_ROT180) + if (xformOp == TJXOP_VFLIP || xformOp == TJXOP_TRANSVERSE || + xformOp == TJXOP_ROT180 || xformOp == TJXOP_ROT270) th = th - (th % tjMCUHeight[tsubsamp]); - if (xformOp == TJXOP_TRANSVERSE || xformOp == TJXOP_ROT90) - tw = tw - (tw % tjMCUHeight[tsubsamp]); - if (xformOp == TJXOP_TRANSVERSE || xformOp == TJXOP_ROT270) - th = th - (th % tjMCUWidth[tsubsamp]); tntilesw = (tw + ttilew - 1) / ttilew; tntilesh = (th + ttileh - 1) / ttileh; - if (xformOp == TJXOP_TRANSPOSE || xformOp == TJXOP_TRANSVERSE || - xformOp == TJXOP_ROT90 || xformOp == TJXOP_ROT270) { - if (tsubsamp == TJSAMP_422) tsubsamp = TJSAMP_440; - else if (tsubsamp == TJSAMP_440) tsubsamp = TJSAMP_422; - else if (tsubsamp == TJSAMP_411) tsubsamp = TJSAMP_441; - else if (tsubsamp == TJSAMP_441) tsubsamp = TJSAMP_411; - } - for (row = 0, tile = 0; row < tntilesh; row++) { for (col = 0; col < tntilesw; col++, tile++) { t[tile].r.w = min(ttilew, tw - col * ttilew); diff --git a/turbojpeg-jni.c b/turbojpeg-jni.c index 5c6f6176..68e5739a 100644 --- a/turbojpeg-jni.c +++ b/turbojpeg-jni.c @@ -1218,6 +1218,10 @@ JNIEXPORT jintArray JNICALL Java_org_libjpegturbo_turbojpeg_TJTransformer_transf if (t[i].op == TJXOP_TRANSPOSE || t[i].op == TJXOP_TRANSVERSE || t[i].op == TJXOP_ROT90 || t[i].op == TJXOP_ROT270) { w = jpegHeight; h = jpegWidth; + if (dstSubsamp == TJSAMP_422) dstSubsamp = TJSAMP_440; + else if (dstSubsamp == TJSAMP_440) dstSubsamp = TJSAMP_422; + else if (dstSubsamp == TJSAMP_411) dstSubsamp = TJSAMP_441; + else if (dstSubsamp == TJSAMP_441) dstSubsamp = TJSAMP_411; } if (t[i].r.w != 0) w = t[i].r.w; if (t[i].r.h != 0) h = t[i].r.h; diff --git a/turbojpeg.c b/turbojpeg.c index b047db10..9e622ae1 100644 --- a/turbojpeg.c +++ b/turbojpeg.c @@ -2740,26 +2740,25 @@ DLLEXPORT int tj3Transform(tjhandle handle, const unsigned char *jpegBuf, for (i = 0; i < n; i++) { int dstSubsamp = (t[i].options & TJXOPT_GRAY) ? TJSAMP_GRAY : srcSubsamp; + if (t[i].op == TJXOP_TRANSPOSE || t[i].op == TJXOP_TRANSVERSE || + t[i].op == TJXOP_ROT90 || t[i].op == TJXOP_ROT270) { + if (dstSubsamp == TJSAMP_422) dstSubsamp = TJSAMP_440; + else if (dstSubsamp == TJSAMP_440) dstSubsamp = TJSAMP_422; + else if (dstSubsamp == TJSAMP_411) dstSubsamp = TJSAMP_441; + else if (dstSubsamp == TJSAMP_441) dstSubsamp = TJSAMP_411; + } + if (!jtransform_request_workspace(dinfo, &xinfo[i])) THROW("Transform is not perfect"); if (xinfo[i].crop) { if (dstSubsamp == TJSAMP_UNKNOWN) THROW("Could not determine subsampling level of JPEG image"); - if (t[i].op == TJXOP_TRANSPOSE || t[i].op == TJXOP_TRANSVERSE || - t[i].op == TJXOP_ROT90 || t[i].op == TJXOP_ROT270) { - if ((t[i].r.x % tjMCUHeight[dstSubsamp]) != 0 || - (t[i].r.y % tjMCUWidth[dstSubsamp]) != 0) - THROWI("To crop this JPEG image, x must be a multiple of %d\n" - "and y must be a multiple of %d.", tjMCUHeight[dstSubsamp], - tjMCUWidth[dstSubsamp]); - } else { - if ((t[i].r.x % tjMCUWidth[dstSubsamp]) != 0 || - (t[i].r.y % tjMCUHeight[dstSubsamp]) != 0) - THROWI("To crop this JPEG image, x must be a multiple of %d\n" - "and y must be a multiple of %d.", tjMCUWidth[dstSubsamp], - tjMCUHeight[dstSubsamp]); - } + if ((t[i].r.x % tjMCUWidth[dstSubsamp]) != 0 || + (t[i].r.y % tjMCUHeight[dstSubsamp]) != 0) + THROWI("To crop this JPEG image, x must be a multiple of %d\n" + "and y must be a multiple of %d.", tjMCUWidth[dstSubsamp], + tjMCUHeight[dstSubsamp]); } } @@ -2772,6 +2771,10 @@ DLLEXPORT int tj3Transform(tjhandle handle, const unsigned char *jpegBuf, if (t[i].op == TJXOP_TRANSPOSE || t[i].op == TJXOP_TRANSVERSE || t[i].op == TJXOP_ROT90 || t[i].op == TJXOP_ROT270) { dstWidth = dinfo->image_height; dstHeight = dinfo->image_width; + if (dstSubsamp == TJSAMP_422) dstSubsamp = TJSAMP_440; + else if (dstSubsamp == TJSAMP_440) dstSubsamp = TJSAMP_422; + else if (dstSubsamp == TJSAMP_411) dstSubsamp = TJSAMP_441; + else if (dstSubsamp == TJSAMP_441) dstSubsamp = TJSAMP_411; } if (xinfo[i].crop) { diff --git a/turbojpeg.h b/turbojpeg.h index 934e4b05..0d505df9 100644 --- a/turbojpeg.h +++ b/turbojpeg.h @@ -2065,13 +2065,15 @@ DLLEXPORT int tj3DecodeYUV8(tjhandle handle, const unsigned char *srcBuf, * you, or * -# pre-allocate the buffer to a "worst case" size determined by calling * #tj3JPEGBufSize() with the transformed or cropped width and height and the - * level of subsampling used in the destination image. Under normal - * circumstances, this should ensure that the buffer never has to be + * level of subsampling used in the destination image (taking into account + * grayscale conversion and transposition of the width and height.) Under + * normal circumstances, this should ensure that the buffer never has to be * re-allocated. (Setting #TJPARAM_NOREALLOC guarantees that it won't be.) * Note, however, that there are some rare cases (such as transforming images * with a large amount of embedded Exif or ICC profile data) in which the * transformed JPEG image will be larger than the worst-case size, and - * #TJPARAM_NOREALLOC cannot be used in those cases. + * #TJPARAM_NOREALLOC cannot be used in those cases unless the embedded data is + * discarded using #TJXOPT_COPYNONE. * . * If you choose option 1, then `dstSizes[i]` should be set to the size of your * pre-allocated buffer. In any case, unless you have set #TJPARAM_NOREALLOC,