turbojpeg-mp.c


Log

Author Commit Date CI Message
DRC dfde1f85 2024-03-08T12:09:23 Fix (and test) more Clang 14 compiler warnings -Woverlength-strings, -Wshift-negative-value, -Wsign-compare
DRC 905ec0fa 2024-03-08T10:29:27 Avoid tautological comparisons Several TurboJPEG functions store their return value in an unsigned long long intermediate and compare it against the maximum value of unsigned long or size_t in order to avoid integer overflow. However, such comparisons are tautological (always true, i.e. redundant) unless the size of unsigned long or size_t is less than the size of unsigned long long. Explicitly guarding the comparisons with #if avoids compiler warnings with -Wtautological-constant-in-range-compare in Clang and also makes it clear to the reader that the comparisons are only intended for 32-bit code. Refer to #752
DRC 55d342c7 2023-11-16T15:36:47 TurboJPEG: Expose/extend hidden "max pixels" param TJPARAM_MAXPIXELS was previously hidden and used only for fuzz testing, but it is potentially useful for calling applications as well, particularly if they want to guard against excessive memory consumption by the tj3LoadImage*() functions. The parameter has also been extended to decompression and lossless transformation functions/methods, mainly as a convenience. (It was already possible for calling applications to impose their own JPEG image size limits by reading the JPEG header prior to decompressing or transforming the image.)
DRC a27bad65 2023-11-14T14:47:40 tj3Transform(): Ensure JPEG hdr successfully read Because of the TurboJPEG 3 API overhaul, the legacy decompression and lossless transformation functions now wrap the new TurboJPEG 3 functions. For performance reasons, we don't want to read the JPEG header more than once during the same operation, so the wrapped functions do not read the header if it has already been read by a wrapper function. Initially the TurboJPEG 3 functions used a state variable to track whether the header had already been read, but b94041390c477a02b3cab79d0cc0ef321dc889e8 made this more robust by using the libjpeg global decompression state instead. If a wrapper function has already read the JPEG header successfully, then the global decompression state will be DSTATE_READY, and the logic introduced in b94041390c477a02b3cab79d0cc0ef321dc889e8 will prevent the header from being read again. A subtle issue arises because tj3DecompressHeader() does not call jpeg_abort_decompress() if jpeg_read_header() fails. (That is arguably a bug, but it has existed since the very first implementation of the function.) Depending on the nature of the failure, this can cause tj3DecompressHeader() to return an error code and leave the libjpeg global decompression state set to DSTATE_INHEADER. If a misbehaved application ignored the error and subsequently called a TurboJPEG decompression or lossless transformation function, then the function would fail to read the JPEG header because the global decompression state was greater than DSTATE_START. In the case of the decompression functions, this was innocuous, because jpeg_calc_output_dimensions() and jpeg_start_decompress() both sanity check the global decompression state. However, it was possible for a misbehaved application to call tj3DecompressHeader() with junk data, ignore the return value, and pass the same junk data into tj3Transform(). Because tj3DecompressHeader() left the global decompression state set to DSTATE_INHEADER, tj3Transform() failed to detect the junk data (because it didn't try to read the JPEG header), and it called jtransform_request_workspace() with dinfo->image_width and dinfo->image_height still initialized to 0. Because jtransform_request_workspace() does not sanity check the decompression state, a division-by-zero error occurred with certain combinations of transform options in which TJXOPT_TRIM or TJXOPT_CROP was specified. However, it should be noted that TJXOPT_TRIM and TJXOPT_CROP cannot be expected to work properly without foreknowledge of the JPEG source image dimensions, which cannot be gained except by calling tj3DecompressHeader() successfully. Thus, a calling application is inviting trouble if it does not check the return value of tj3DecompressHeader() and sanity check the JPEG source image dimensions before calling tj3Transform(). This commit softens the failure, but the failure is still due to improper API usage.
DRC df9dbff8 2023-11-11T12:25:03 TurboJPEG: New param to limit virt array mem usage This corresponds to max_memory_to_use in the jpeg_memory_mgr struct in the libjpeg API, except that the TurboJPEG parameter is specified in megabytes. Because this is 2023 and computers with less than 1 MB of memory are not a thing (at least not within the scope of libjpeg-turbo support), it isn't useful to allow a limit less than 1 MB to be specified. Furthermore, because TurboJPEG parameters are signed integers, if we allowed the memory limit to be specified in bytes, then it would be impossible to specify a limit larger than 2 GB on 64-bit machines. Because max_memory_to_use is a long signed integer, effectively we can specify a limit of up to 2 petabytes on 64-bit machines if the TurboJPEG parameter is specified in megabytes. (2 PB should be enough for anybody, right?) This commit also bumps the TurboJPEG API version to 3.0.1. Since the TurboJPEG API version no longer tracks the libjpeg-turbo version, it makes sense to increment the API revision number when adding constants, to increment the minor version number when adding functions, and to increment the major version number for a complete overhaul. This commit also removes the vestigial TJ_NUMPARAM macro, which was never defined because it proved unnecessary. Partially implements #735
DRC bf248a50 2023-11-11T15:14:07 tj3Compress*(): Free virt arrays if mem limit hit This is very subtle, but if a user specifies a libjpeg virtual array memory limit via the JPEGMEM environment variable and one of the tj3Compress*() functions hits that limit, the libjpeg error handler will be invoked in jpeg_start_compress() (more specifically in realize_virt_arrays() in jinit_compress_master()) before the libjpeg global compression state can be incremented. Thus, jpeg_abort_compress() will not be called before the tj3Compress*() function exits, the unrealized virtual arrays will not be freed, and if the TurboJPEG compression instance is reused, those unrealized virtual arrays will count against the specified memory limit. This could cause subsequent compression operations that require smaller virtual arrays (or even no virtual arrays at all) to fail when they would otherwise succeed. In reality, the vast majority of calling programs would abort and free the TurboJPEG compression instance if one of the tj3Compress*() functions failed, but TJBench is a rare exception. This issue does not bear documenting because of its subtlety and rarity and because JPEGMEM is not a documented feature of the TurboJPEG API. Note that the issue does not exist in the tj3Encode*() and tj3Decode*() functions, because realize_virt_arrays() is never called in the body of those functions. The issue also does not exist in the tj3Decompress*() and tj3Transform() functions, because those functions ensure that the JPEG header is read (and thus the libjpeg global decompression state is incremented) prior to calling a function that calls realize_virt_arrays() (i.e. jpeg_start_decompress() or jpeg_read_coefficients().) If realize_virt_arrays() failed in the body of jpeg_write_coefficients(), then tj3Transform() would abort without calling jpeg_abort_compress(). However, since jpeg_start_compress() is never called in the body of tj3Transform(), no virtual arrays are ever requested from the compression object, so failing to call jpeg_abort_compress() would be innocuous.
DRC d0015876 2023-01-27T13:08:04 Fix build if [CD]_LOSSLESS_SUPPORTED undefined (regression introduced by fc01f4673b71c0b833c59c21e8c4478a9c4bcf21)
DRC 54f571f8 2023-01-27T12:31:44 TurboJPEG: Clean up/repurpose THROWI() macro We already had a use case for two integer arguments, and the new use cases benefit from two integer arguments as well.
DRC b9404139 2023-01-27T07:20:10 TurboJPEG: Robustify JPEG header prefetching In decompression and transform functions, use the libjpeg API state rather than a TurboJPEG instance variable to determine whether jpeg_mem_src_tj() and jpeg_read_header() have already been called by a wrapper function.
DRC fc01f467 2023-01-05T06:36:46 TurboJPEG 3 API overhaul (ChangeLog update forthcoming) - Prefix all function names with "tj3" and remove version suffixes from function names. (Future API overhauls will increment the prefix to "tj4", etc., thus retaining backward API/ABI compatibility without versioning each individual function.) - Replace stateless boolean flags (including TJ*FLAG_ARITHMETIC and TJ*FLAG_LOSSLESS, which were never released) with stateful integer parameters, the value of which persists between function calls. * Use parameters for the JPEG quality and subsampling as well, in order to eliminate the awkwardness of specifying function arguments that weren't relevant for lossless compression. * tj3DecompressHeader() now stores all relevant information about the JPEG image, including the width, height, subsampling type, entropy coding type, etc. in parameters rather than returning that information in its arguments. * TJ*FLAG_LIMITSCANS has been reimplemented as an integer parameter (TJ*PARAM_SCANLIMIT) that allows the number of scans to be specified. - Use the const keyword for all pointer arguments to unmodified buffers, as well as for both dimensions of 2D pointers. Addresses #395. - Use size_t rather than unsigned long to represent buffer sizes, since unsigned long is a 32-bit type on Windows. Addresses #24. - Return 0 from all buffer size functions if an error occurs, rather than awkwardly trying to return -1 in an unsigned data type. - Implement 12-bit and 16-bit data precision using dedicated compression, decompression, and image I/O functions/methods. * Suffix the names of all data-precision-specific functions with 8, 12, or 16. * Because the YUV functions are intended to be used for video, they are currently only implemented with 8-bit data precision, but they can be expanded to 12-bit data precision in the future, if necessary. * Extend TJUnitTest and TJBench to test 12-bit and 16-bit data precision, using a new -precision option. * Add appropriate regression tests for all of the above to the 'test' target. * Extend tjbenchtest to test 12-bit and 16-bit data precision, and add separate 'tjtest12' and 'tjtest16' targets. * BufferedImage I/O in the Java API is currently limited to 8-bit data precision, since the BufferedImage class does not straightforwardly support higher data precisions. * Extend the PPM reader to convert 12-bit and 16-bit PBMPLUS files to grayscale or CMYK pixels, as it already does for 8-bit files. - Properly accommodate lossless JPEG using dedicated parameters (TJ*PARAM_LOSSLESS, TJ*PARAM_LOSSLESSPSV, and TJ*PARAM_LOSSLESSPT), rather than using a flag and awkwardly repurposing the JPEG quality. Update TJBench to properly reflect whether a JPEG image is lossless. - Re-organize the TJBench usage screen. - Update the Java docs using Java 11, to improve the formatting and eliminate HTML frames. - Use the accurate integer DCT algorithm by default for both compression and decompression, since the "fast" algorithm is a legacy feature, it does not pass the ISO compliance tests, and it is not actually faster on modern x86 CPUs. * Remove the -accuratedct option from TJBench and TJExample. - Re-implement the 'tjtest' target using a CMake script that enables the appropriate tests, depending on the data precision and whether or not the Java API is part of the build. - Consolidate the C and Java versions of tjbenchtest into one script. - Consolidate the C and Java versions of tjexampletest into one script. - Combine all initialization functions into a single function (tj3Init()) that accepts an integer parameter specifying the subsystems to initialize. - Enable decompression scaling explicitly, using a new function/method (tj3SetScalingFactor()/TJDecompressor.setScalingFactor()), rather than implicitly using awkward "desired width"/"desired height" parameters. - Introduce a new macro/constant (TJUNSCALED/TJ.UNSCALED) that maps to a scaling factor of 1/1. - Implement partial image decompression, using a new function/method (tj3SetCroppingRegion()/TJDecompressor.setCroppingRegion()) and TJBench option (-crop). Extend tjbenchtest to test the new feature. Addresses #1. - Allow the JPEG colorspace to be specified explicitly when compressing, using a new parameter (TJ*PARAM_COLORSPACE). This allows JPEG images with the RGB and CMYK colorspaces to be created. - Remove the error/difference image feature from TJBench. Identical images to the ones that TJBench created can be generated using ImageMagick with 'magick composite <original_image> <output_image> -compose difference <diff_image>' - Handle JPEG images with unknown subsampling types. TJ*PARAM_SUBSAMP is set to TJ*SAMP_UNKNOWN (== -1) for such images, but they can still be decompressed fully into packed-pixel images or losslessly transformed (with the exception of lossless cropping.) They cannot be partially decompressed or decompressed into planar YUV images. Note also that TJBench, due to its lack of support for imperfect transforms, requires that the subsampling type be known when rotating, flipping, or transversely transposing an image. Addresses #436 - The Java version of TJBench now has identical functionality to the C version. This was accomplished by (somewhat hackishly) calling the TurboJPEG C image I/O functions through JNI and copying the pixels between the C heap and the Java heap. - Add parameters (TJ*PARAM_RESTARTROWS and TJ*PARAM_RESTARTBLOCKS) and a TJBench option (-restart) to allow the restart marker interval to be specified when compressing. Eliminate the undocumented TJ_RESTART environment variable. - Add a parameter (TJ*PARAM_OPTIMIZE), a transform option (TJ*OPT_OPTIMIZE), and a TJBench option (-optimize) to allow optimized baseline Huffman coding to be specified when compressing. Eliminate the undocumented TJ_OPTIMIZE environment variable. - Add parameters (TJ*PARAM_XDENSITY, TJ*PARAM_DENSITY, and TJ*DENSITYUNITS) to allow the pixel density to be specified when compressing or saving a Windows BMP image and to be queried when decompressing or loading a Windows BMP image. Addresses #77. - Refactor the fuzz targets to use the new API. * Extend decompression coverage to 12-bit and 16-bit data precision. * Replace the awkward cjpeg12 and cjpeg16 targets with proper TurboJPEG-based compress12, compress12-lossless, and compress16-lossless targets - Fix innocuous UBSan warnings uncovered by the new fuzzers. - Implement previous versions of the TurboJPEG API by wrapping the new functions (tested by running the 2.1.x versions of TJBench, via tjbenchtest, and TJUnitTest against the new implementation.) * Remove all JNI functions for deprecated Java methods and implement the deprecated methods using pure Java wrappers. It should be understood that backward API compatibility in Java applies only to the Java classes and that one cannot mix and match a JAR file from one version of libjpeg-turbo with a JNI library from another version. - tj3Destroy() now silently accepts a NULL handle. - tj3Alloc() and tj3Free() now return/accept void pointers, as malloc() and free() do. - The image I/O functions now accept a TurboJPEG instance handle, which is used to transmit/receive parameters and to receive error information. Closes #517