Files
mozjpeg/tjexample.c
DRC 837e471a90 tjexample.c: Fix error when decompressing
(regression introduced by 300a344d65)

300a344d65 fixed the recompression code
path but also broke the pure decompression code path, because the fix
caused the TurboJPEG decompression instance to be destroyed before
tj3SaveImage() could use it.  Furthermore, the fix in
300a344d65 prevented pixel density
information from being transferred from the input image to the output
image when recompressing.  This commit does the following:

- Modify tjexample.c so that a single TurboJPEG instance is initialized
  for lossless transformation and shared by all code paths.  In addition
  to fixing both of the aforementioned issues, this makes the code more
  readable.

- Extend tjexampletest to test the recompression code path, thus
  ensuring that the issues fixed by this commit and
  300a344d65 are not reintroduced.

- Modify tjexample.c to remove redundant fclose(), tj3Destroy(), and
  tj3Free() calls.
2023-11-14 12:08:02 -05:00

408 lines
15 KiB
C

/*
* Copyright (C)2011-2012, 2014-2015, 2017, 2019, 2021-2023
* D. R. Commander. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the libjpeg-turbo Project nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This program demonstrates how to compress, decompress, and transform JPEG
* images using the TurboJPEG C API
*/
#ifdef _MSC_VER
#define _CRT_SECURE_NO_DEPRECATE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <turbojpeg.h>
#ifdef _WIN32
#define strcasecmp stricmp
#define strncasecmp strnicmp
#endif
#define THROW(action, message) { \
printf("ERROR in line %d while %s:\n%s\n", __LINE__, action, message); \
retval = -1; goto bailout; \
}
#define THROW_TJ(action) THROW(action, tj3GetErrorStr(tjInstance))
#define THROW_UNIX(action) THROW(action, strerror(errno))
#define DEFAULT_SUBSAMP TJSAMP_444
#define DEFAULT_QUALITY 95
const char *subsampName[TJ_NUMSAMP] = {
"4:4:4", "4:2:2", "4:2:0", "Grayscale", "4:4:0", "4:1:1", "4:4:1"
};
const char *colorspaceName[TJ_NUMCS] = {
"RGB", "YCbCr", "GRAY", "CMYK", "YCCK"
};
tjscalingfactor *scalingFactors = NULL;
int numScalingFactors = 0;
/* DCT filter example. This produces a negative of the image. */
static int customFilter(short *coeffs, tjregion arrayRegion,
tjregion planeRegion, int componentIndex,
int transformIndex, tjtransform *transform)
{
int i;
for (i = 0; i < arrayRegion.w * arrayRegion.h; i++)
coeffs[i] = -coeffs[i];
return 0;
}
static void usage(char *programName)
{
int i;
printf("\nUSAGE: %s <Input image> <Output image> [options]\n\n",
programName);
printf("Input and output images can be in Windows BMP or PBMPLUS (PPM/PGM) format. If\n");
printf("either filename ends in a .jpg extension, then the TurboJPEG API will be used\n");
printf("to compress or decompress the image.\n\n");
printf("Compression Options (used if the output image is a JPEG image)\n");
printf("--------------------------------------------------------------\n\n");
printf("-subsamp <444|422|420|gray> = Apply this level of chrominance subsampling when\n");
printf(" compressing the output image. The default is to use the same level of\n");
printf(" subsampling as in the input image, if the input image is also a JPEG\n");
printf(" image, or to use grayscale if the input image is a grayscale non-JPEG\n");
printf(" image, or to use %s subsampling otherwise.\n\n",
subsampName[DEFAULT_SUBSAMP]);
printf("-q <1-100> = Compress the output image with this JPEG quality level\n");
printf(" (default = %d).\n\n", DEFAULT_QUALITY);
printf("Decompression Options (used if the input image is a JPEG image)\n");
printf("---------------------------------------------------------------\n\n");
printf("-scale M/N = Scale the input image by a factor of M/N when decompressing it.\n");
printf("(M/N = ");
for (i = 0; i < numScalingFactors; i++) {
printf("%d/%d", scalingFactors[i].num, scalingFactors[i].denom);
if (numScalingFactors == 2 && i != numScalingFactors - 1)
printf(" or ");
else if (numScalingFactors > 2) {
if (i != numScalingFactors - 1)
printf(", ");
if (i == numScalingFactors - 2)
printf("or ");
}
}
printf(")\n\n");
printf("-hflip, -vflip, -transpose, -transverse, -rot90, -rot180, -rot270 =\n");
printf(" Perform one of these lossless transform operations on the input image\n");
printf(" prior to decompressing it (these options are mutually exclusive.)\n\n");
printf("-grayscale = Perform lossless grayscale conversion on the input image prior\n");
printf(" to decompressing it (can be combined with the other transform operations\n");
printf(" above.)\n\n");
printf("-crop WxH+X+Y = Perform lossless cropping on the input image prior to\n");
printf(" decompressing it. X and Y specify the upper left corner of the cropping\n");
printf(" region, and W and H specify the width and height of the cropping region.\n");
printf(" X and Y must be evenly divible by the MCU block size (8x8 if the input\n");
printf(" image was compressed using no subsampling or grayscale, 16x8 if it was\n");
printf(" compressed using 4:2:2 subsampling, or 16x16 if it was compressed using\n");
printf(" 4:2:0 subsampling.)\n\n");
printf("General Options\n");
printf("---------------\n\n");
printf("-fastupsample = Use the fastest chrominance upsampling algorithm available\n\n");
printf("-fastdct = Use the fastest DCT/IDCT algorithm available\n\n");
exit(1);
}
int main(int argc, char **argv)
{
tjscalingfactor scalingFactor = TJUNSCALED;
int outSubsamp = -1, outQual = -1;
tjtransform xform;
int fastUpsample = 0, fastDCT = 0;
int width, height;
char *inFormat, *outFormat;
FILE *jpegFile = NULL;
unsigned char *imgBuf = NULL, *jpegBuf = NULL;
int retval = 0, i, pixelFormat = TJPF_UNKNOWN;
tjhandle tjInstance = NULL;
if ((scalingFactors = tj3GetScalingFactors(&numScalingFactors)) == NULL)
THROW_TJ("getting scaling factors");
memset(&xform, 0, sizeof(tjtransform));
if (argc < 3)
usage(argv[0]);
/* Parse arguments. */
for (i = 3; i < argc; i++) {
if (!strncasecmp(argv[i], "-sc", 3) && i < argc - 1) {
int match = 0, temp1 = 0, temp2 = 0, j;
if (sscanf(argv[++i], "%d/%d", &temp1, &temp2) < 2)
usage(argv[0]);
for (j = 0; j < numScalingFactors; j++) {
if ((double)temp1 / (double)temp2 == (double)scalingFactors[j].num /
(double)scalingFactors[j].denom) {
scalingFactor = scalingFactors[j];
match = 1;
break;
}
}
if (match != 1)
usage(argv[0]);
} else if (!strncasecmp(argv[i], "-su", 3) && i < argc - 1) {
i++;
if (!strncasecmp(argv[i], "g", 1))
outSubsamp = TJSAMP_GRAY;
else if (!strcasecmp(argv[i], "444"))
outSubsamp = TJSAMP_444;
else if (!strcasecmp(argv[i], "422"))
outSubsamp = TJSAMP_422;
else if (!strcasecmp(argv[i], "420"))
outSubsamp = TJSAMP_420;
else
usage(argv[0]);
} else if (!strncasecmp(argv[i], "-q", 2) && i < argc - 1) {
outQual = atoi(argv[++i]);
if (outQual < 1 || outQual > 100)
usage(argv[0]);
} else if (!strncasecmp(argv[i], "-g", 2))
xform.options |= TJXOPT_GRAY;
else if (!strcasecmp(argv[i], "-hflip"))
xform.op = TJXOP_HFLIP;
else if (!strcasecmp(argv[i], "-vflip"))
xform.op = TJXOP_VFLIP;
else if (!strcasecmp(argv[i], "-transpose"))
xform.op = TJXOP_TRANSPOSE;
else if (!strcasecmp(argv[i], "-transverse"))
xform.op = TJXOP_TRANSVERSE;
else if (!strcasecmp(argv[i], "-rot90"))
xform.op = TJXOP_ROT90;
else if (!strcasecmp(argv[i], "-rot180"))
xform.op = TJXOP_ROT180;
else if (!strcasecmp(argv[i], "-rot270"))
xform.op = TJXOP_ROT270;
else if (!strcasecmp(argv[i], "-custom"))
xform.customFilter = customFilter;
else if (!strncasecmp(argv[i], "-c", 2) && i < argc - 1) {
if (sscanf(argv[++i], "%dx%d+%d+%d", &xform.r.w, &xform.r.h, &xform.r.x,
&xform.r.y) < 4 ||
xform.r.x < 0 || xform.r.y < 0 || xform.r.w < 1 || xform.r.h < 1)
usage(argv[0]);
xform.options |= TJXOPT_CROP;
} else if (!strcasecmp(argv[i], "-fastupsample")) {
printf("Using fast upsampling code\n");
fastUpsample = 1;
} else if (!strcasecmp(argv[i], "-fastdct")) {
printf("Using fastest DCT/IDCT algorithm\n");
fastDCT = 1;
} else usage(argv[0]);
}
/* Determine input and output image formats based on file extensions. */
inFormat = strrchr(argv[1], '.');
outFormat = strrchr(argv[2], '.');
if (inFormat == NULL || outFormat == NULL || strlen(inFormat) < 2 ||
strlen(outFormat) < 2)
usage(argv[0]);
inFormat = &inFormat[1];
outFormat = &outFormat[1];
if ((tjInstance = tj3Init(TJINIT_TRANSFORM)) == NULL)
THROW_TJ("creating TurboJPEG instance");
if (!strcasecmp(inFormat, "jpg")) {
/* Input image is a JPEG image. Decompress and/or transform it. */
long size;
int inSubsamp, inColorspace;
int doTransform = (xform.op != TJXOP_NONE || xform.options != 0 ||
xform.customFilter != NULL);
size_t jpegSize;
/* Read the JPEG file into memory. */
if ((jpegFile = fopen(argv[1], "rb")) == NULL)
THROW_UNIX("opening input file");
if (fseek(jpegFile, 0, SEEK_END) < 0 || ((size = ftell(jpegFile)) < 0) ||
fseek(jpegFile, 0, SEEK_SET) < 0)
THROW_UNIX("determining input file size");
if (size == 0)
THROW("determining input file size", "Input file contains no data");
jpegSize = size;
if ((jpegBuf = tj3Alloc(jpegSize)) == NULL)
THROW_UNIX("allocating JPEG buffer");
if (fread(jpegBuf, jpegSize, 1, jpegFile) < 1)
THROW_UNIX("reading input file");
fclose(jpegFile); jpegFile = NULL;
if (doTransform) {
/* Transform it. */
unsigned char *dstBuf = NULL; /* Dynamically allocate the JPEG buffer */
size_t dstSize = 0;
xform.options |= TJXOPT_TRIM;
if (tj3Transform(tjInstance, jpegBuf, jpegSize, 1, &dstBuf, &dstSize,
&xform) < 0) {
tj3Free(dstBuf);
THROW_TJ("transforming input image");
}
tj3Free(jpegBuf);
jpegBuf = dstBuf;
jpegSize = dstSize;
}
if (tj3Set(tjInstance, TJPARAM_FASTUPSAMPLE, fastUpsample) < 0)
THROW_TJ("setting TJPARAM_FASTUPSAMPLE");
if (tj3Set(tjInstance, TJPARAM_FASTDCT, fastDCT) < 0)
THROW_TJ("setting TJPARAM_FASTDCT");
if (tj3DecompressHeader(tjInstance, jpegBuf, jpegSize) < 0)
THROW_TJ("reading JPEG header");
width = tj3Get(tjInstance, TJPARAM_JPEGWIDTH);
height = tj3Get(tjInstance, TJPARAM_JPEGHEIGHT);
inSubsamp = tj3Get(tjInstance, TJPARAM_SUBSAMP);
inColorspace = tj3Get(tjInstance, TJPARAM_COLORSPACE);
if (tj3Get(tjInstance, TJPARAM_LOSSLESS))
scalingFactor = TJUNSCALED;
printf("%s Image: %d x %d pixels, %s subsampling, %s colorspace\n",
(doTransform ? "Transformed" : "Input"), width, height,
subsampName[inSubsamp], colorspaceName[inColorspace]);
if (!strcasecmp(outFormat, "jpg") && doTransform &&
scalingFactor.num == 1 && scalingFactor.denom == 1 && outSubsamp < 0 &&
outQual < 0) {
/* Input image has been transformed, and no re-compression options
have been selected. Write the transformed image to disk and exit. */
if ((jpegFile = fopen(argv[2], "wb")) == NULL)
THROW_UNIX("opening output file");
if (fwrite(jpegBuf, jpegSize, 1, jpegFile) < 1)
THROW_UNIX("writing output file");
goto bailout;
}
/* Scaling and/or a non-JPEG output image format and/or compression options
have been selected, so we need to decompress the input/transformed
image. */
if (tj3SetScalingFactor(tjInstance, scalingFactor) < 0)
THROW_TJ("setting scaling factor");
width = TJSCALED(width, scalingFactor);
height = TJSCALED(height, scalingFactor);
if (outSubsamp < 0)
outSubsamp = inSubsamp;
pixelFormat = TJPF_BGRX;
if ((unsigned long long)width * height * tjPixelSize[pixelFormat] >
(unsigned long long)((size_t)-1))
THROW("allocating uncompressed image buffer", "Image is too large");
if ((imgBuf =
(unsigned char *)malloc(sizeof(unsigned char) * width * height *
tjPixelSize[pixelFormat])) == NULL)
THROW_UNIX("allocating uncompressed image buffer");
if (tj3Decompress8(tjInstance, jpegBuf, jpegSize, imgBuf, 0,
pixelFormat) < 0)
THROW_TJ("decompressing JPEG image");
tj3Free(jpegBuf); jpegBuf = NULL;
} else {
/* Input image is not a JPEG image. Load it into memory. */
if ((imgBuf = tj3LoadImage8(tjInstance, argv[1], &width, 1, &height,
&pixelFormat)) == NULL)
THROW_TJ("loading input image");
if (outSubsamp < 0) {
if (pixelFormat == TJPF_GRAY)
outSubsamp = TJSAMP_GRAY;
else
outSubsamp = TJSAMP_444;
}
printf("Input Image: %d x %d pixels\n", width, height);
}
printf("Output Image (%s): %d x %d pixels", outFormat, width, height);
if (!strcasecmp(outFormat, "jpg")) {
/* Output image format is JPEG. Compress the uncompressed image. */
size_t jpegSize = 0;
jpegBuf = NULL; /* Dynamically allocate the JPEG buffer */
if (outQual < 0)
outQual = DEFAULT_QUALITY;
printf(", %s subsampling, quality = %d\n", subsampName[outSubsamp],
outQual);
if (tj3Set(tjInstance, TJPARAM_SUBSAMP, outSubsamp) < 0)
THROW_TJ("setting TJPARAM_SUBSAMP");
if (tj3Set(tjInstance, TJPARAM_QUALITY, outQual) < 0)
THROW_TJ("setting TJPARAM_QUALITY");
if (tj3Set(tjInstance, TJPARAM_FASTDCT, fastDCT) < 0)
THROW_TJ("setting TJPARAM_FASTDCT");
if (tj3Compress8(tjInstance, imgBuf, width, 0, height, pixelFormat,
&jpegBuf, &jpegSize) < 0)
THROW_TJ("compressing image");
/* Write the JPEG image to disk. */
if ((jpegFile = fopen(argv[2], "wb")) == NULL)
THROW_UNIX("opening output file");
if (fwrite(jpegBuf, jpegSize, 1, jpegFile) < 1)
THROW_UNIX("writing output file");
} else {
/* Output image format is not JPEG. Save the uncompressed image
directly to disk. */
printf("\n");
if (tj3SaveImage8(tjInstance, argv[2], imgBuf, width, 0, height,
pixelFormat) < 0)
THROW_TJ("saving output image");
}
bailout:
tj3Free(imgBuf);
tj3Destroy(tjInstance);
tj3Free(jpegBuf);
if (jpegFile) fclose(jpegFile);
return retval;
}