388 lines
14 KiB
C
388 lines
14 KiB
C
/*
|
|
* Copyright (C) 2011-2012, 2014-2015, 2017, 2019, 2021-2026 D. R. Commander
|
|
*
|
|
* 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 use the TurboJPEG C API to approximate the
|
|
* functionality of the IJG's djpeg program. djpeg features that are not
|
|
* covered:
|
|
*
|
|
* - OS/2 BMP, GIF, and Targa output file formats [legacy feature]
|
|
* - Color quantization and dithering [legacy feature]
|
|
* - The floating-point IDCT method [legacy feature]
|
|
* - Extracting an ICC color management profile
|
|
* - Progress reporting
|
|
* - Skipping rows (i.e. exclusive rather than inclusive partial decompression)
|
|
* - Debug output
|
|
*/
|
|
|
|
#ifdef _MSC_VER
|
|
#define _CRT_SECURE_NO_DEPRECATE
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#if !defined(_MSC_VER) || _MSC_VER > 1600
|
|
#include <stdint.h>
|
|
#endif
|
|
#include "turbojpeg.h"
|
|
|
|
|
|
#ifdef _WIN32
|
|
#define strncasecmp strnicmp
|
|
#endif
|
|
|
|
#ifndef max
|
|
#define max(a, b) ((a) > (b) ? (a) : (b))
|
|
#endif
|
|
|
|
#define MATCH_ARG(arg, string, minChars) \
|
|
!strncasecmp(arg, string, max(strlen(arg), minChars))
|
|
|
|
#define IS_CROPPED(cr) (cr.x != 0 || cr.y != 0 || cr.w != 0 || cr.h != 0)
|
|
|
|
#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) { \
|
|
int errorCode = tj3GetErrorCode(tjInstance); \
|
|
printf("%s in line %d while %s:\n%s\n", \
|
|
errorCode == TJERR_WARNING ? "WARNING" : "ERROR", __LINE__, action, \
|
|
tj3GetErrorStr(tjInstance)); \
|
|
if (errorCode == TJERR_FATAL || stopOnWarning == 1) { \
|
|
retval = -1; goto bailout; \
|
|
} \
|
|
}
|
|
|
|
#define THROW_UNIX(action) THROW(action, strerror(errno))
|
|
|
|
|
|
static tjscalingfactor *scalingFactors = NULL;
|
|
static int numScalingFactors = 0;
|
|
|
|
|
|
static void usage(char *programName)
|
|
{
|
|
int i;
|
|
|
|
printf("\nUSAGE: %s [options] <JPEG image> <Output image>\n\n", programName);
|
|
|
|
printf("The output image will be in Windows BMP or PBMPLUS (PPM/PGM) format, depending\n");
|
|
printf("on the file extension.\n\n");
|
|
|
|
printf("GENERAL OPTIONS (CAN BE ABBREVBIATED)\n");
|
|
printf("-------------------------------------\n");
|
|
printf("-icc FILE\n");
|
|
printf(" Extract the ICC (International Color Consortium) color profile from the\n");
|
|
printf(" JPEG image to the specified file\n");
|
|
printf("-noicc\n");
|
|
printf(" Do not transfer the embedded ICC profile (if any) from the JPEG image to a\n");
|
|
printf(" PNG output image\n");
|
|
printf("-strict\n");
|
|
printf(" Treat all warnings as fatal; abort immediately if incomplete or corrupt\n");
|
|
printf(" data is encountered in the JPEG image, rather than trying to salvage the\n");
|
|
printf(" rest of the image\n\n");
|
|
|
|
printf("LOSSY JPEG OPTIONS (CAN BE ABBREVIATED)\n");
|
|
printf("---------------------------------------\n");
|
|
printf("-crop WxH+X+Y\n");
|
|
printf(" Decompress only the specified region of the JPEG image. (W, H, X, and Y\n");
|
|
printf(" are the width, height, left boundary, and upper boundary of the region, all\n");
|
|
printf(" specified relative to the scaled image dimensions.) If necessary, X will\n");
|
|
printf(" be shifted left to the nearest iMCU boundary, and W will be increased\n");
|
|
printf(" accordingly.\n");
|
|
printf("-dct fast\n");
|
|
printf(" Use less accurate IDCT algorithm [legacy feature]\n");
|
|
printf("-dct int\n");
|
|
printf(" Use more accurate IDCT algorithm [default]\n");
|
|
printf("-grayscale\n");
|
|
printf(" Decompress a full-color JPEG image into a grayscale output image\n");
|
|
printf("-maxmemory N\n");
|
|
printf(" Memory limit (in megabytes) for intermediate buffers used with progressive\n");
|
|
printf(" JPEG decompression [default = no limit]\n");
|
|
printf("-maxscans N\n");
|
|
printf(" Refuse to decompress progressive JPEG images that have more than N scans\n");
|
|
printf("-nosmooth\n");
|
|
printf(" Use the fastest chrominance upsampling algorithm available\n");
|
|
printf("-precision 12\n");
|
|
printf(" Decompress an 8-bit-per-sample JPEG image into a 12-bit-per-sample output\n");
|
|
printf(" image [useful for shadow recovery]\n");
|
|
printf("-rgb\n");
|
|
printf(" Decompress a grayscale JPEG image into a full-color output image\n");
|
|
printf("-scale M/N\n");
|
|
printf(" Scale the width/height of the JPEG image by a factor of M/N when\n");
|
|
printf(" decompressing it (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 ");
|
|
}
|
|
if (i % 8 == 0 && i != 0) printf("\n ");
|
|
}
|
|
printf(")\n\n");
|
|
|
|
exit(1);
|
|
}
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int i, retval = 0;
|
|
int colorspace, fastDCT = -1, fastUpsample = -1, jpegPrecision, lossless,
|
|
maxMemory = -1, maxScans = -1, noICC = 0, pixelFormat = TJPF_UNKNOWN,
|
|
precision = -1, stopOnWarning = -1, subsamp;
|
|
tjregion croppingRegion = TJUNCROPPED;
|
|
tjscalingfactor scalingFactor = TJUNSCALED;
|
|
char *iccFilename = NULL;
|
|
tjhandle tjInstance = NULL;
|
|
FILE *jpegFile = NULL, *iccFile = NULL;
|
|
long size = 0;
|
|
size_t jpegSize, sampleSize, iccSize;
|
|
int width, height;
|
|
unsigned char *jpegBuf = NULL, *iccBuf = NULL;
|
|
void *dstBuf = NULL;
|
|
|
|
if ((scalingFactors = tj3GetScalingFactors(&numScalingFactors)) == NULL)
|
|
THROW_TJ("getting scaling factors");
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
if (MATCH_ARG(argv[i], "-crop", 2) && i < argc - 1) {
|
|
char tempc = -1;
|
|
|
|
if (sscanf(argv[++i], "%d%c%d+%d+%d", &croppingRegion.w, &tempc,
|
|
&croppingRegion.h, &croppingRegion.x,
|
|
&croppingRegion.y) != 5 || croppingRegion.w < 1 ||
|
|
(tempc != 'x' && tempc != 'X') || croppingRegion.h < 1 ||
|
|
croppingRegion.x < 0 || croppingRegion.y < 0)
|
|
usage(argv[0]);
|
|
} else if (MATCH_ARG(argv[i], "-dct", 2) && i < argc - 1) {
|
|
i++;
|
|
if (MATCH_ARG(argv[i], "fast", 1))
|
|
fastDCT = 1;
|
|
else if (!MATCH_ARG(argv[i], "int", 1))
|
|
usage(argv[0]);
|
|
} else if (MATCH_ARG(argv[i], "-grayscale", 2) ||
|
|
MATCH_ARG(argv[i], "-greyscale", 2))
|
|
pixelFormat = TJPF_GRAY;
|
|
else if (MATCH_ARG(argv[i], "-icc", 2) && i < argc - 1)
|
|
iccFilename = argv[++i];
|
|
else if (MATCH_ARG(argv[i], "-maxscans", 5) && i < argc - 1) {
|
|
int tempi = atoi(argv[++i]);
|
|
|
|
if (tempi < 0) usage(argv[0]);
|
|
maxScans = tempi;
|
|
} else if (MATCH_ARG(argv[i], "-maxmemory", 2) && i < argc - 1) {
|
|
int tempi = atoi(argv[++i]);
|
|
|
|
if (tempi < 0) usage(argv[0]);
|
|
maxMemory = tempi;
|
|
} else if (MATCH_ARG(argv[i], "-noicc", 4))
|
|
noICC = 1;
|
|
else if (MATCH_ARG(argv[i], "-nosmooth", 2))
|
|
fastUpsample = 1;
|
|
else if (MATCH_ARG(argv[i], "-precision", 4) && i < argc - 1) {
|
|
int tempi = atoi(argv[++i]);
|
|
|
|
if (tempi != 12)
|
|
usage(argv[0]);
|
|
precision = tempi;
|
|
} else if (MATCH_ARG(argv[i], "-rgb", 2))
|
|
pixelFormat = TJPF_RGB;
|
|
else if (MATCH_ARG(argv[i], "-strict", 3))
|
|
stopOnWarning = 1;
|
|
else if (MATCH_ARG(argv[i], "-scale", 2) && i < argc - 1) {
|
|
int match = 0, temp_num = 0, temp_denom = 0, j;
|
|
|
|
if (sscanf(argv[++i], "%d/%d", &temp_num, &temp_denom) < 2)
|
|
usage(argv[0]);
|
|
if (temp_num < 1 || temp_denom < 1)
|
|
usage(argv[0]);
|
|
for (j = 0; j < numScalingFactors; j++) {
|
|
if ((double)temp_num / (double)temp_denom ==
|
|
(double)scalingFactors[j].num / (double)scalingFactors[j].denom) {
|
|
scalingFactor = scalingFactors[j];
|
|
match = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (match != 1)
|
|
usage(argv[0]);
|
|
} else break;
|
|
}
|
|
|
|
if (i != argc - 2)
|
|
usage(argv[0]);
|
|
|
|
if ((tjInstance = tj3Init(TJINIT_DECOMPRESS)) == NULL)
|
|
THROW_TJ("creating TurboJPEG instance");
|
|
|
|
if (stopOnWarning >= 0 &&
|
|
tj3Set(tjInstance, TJPARAM_STOPONWARNING, stopOnWarning) < 0)
|
|
THROW_TJ("setting TJPARAM_STOPONWARNING");
|
|
if (fastUpsample >= 0 &&
|
|
tj3Set(tjInstance, TJPARAM_FASTUPSAMPLE, fastUpsample) < 0)
|
|
THROW_TJ("setting TJPARAM_FASTUPSAMPLE");
|
|
if (fastDCT >= 0 && tj3Set(tjInstance, TJPARAM_FASTDCT, fastDCT) < 0)
|
|
THROW_TJ("setting TJPARAM_FASTDCT");
|
|
if (maxScans >= 0 && tj3Set(tjInstance, TJPARAM_SCANLIMIT, maxScans) < 0)
|
|
THROW_TJ("setting TJPARAM_SCANLIMIT");
|
|
if (maxMemory >= 0 && tj3Set(tjInstance, TJPARAM_MAXMEMORY, maxMemory) < 0)
|
|
THROW_TJ("setting TJPARAM_MAXMEMORY");
|
|
if (noICC && tj3Set(tjInstance, TJPARAM_SAVEMARKERS, 0) < 0)
|
|
THROW_TJ("setting TJPARAM_SAVEMARKERS");
|
|
|
|
if ((jpegFile = fopen(argv[i++], "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 = (unsigned char *)malloc(jpegSize)) == NULL)
|
|
THROW_UNIX("allocating JPEG buffer");
|
|
if (fread(jpegBuf, jpegSize, 1, jpegFile) < 1)
|
|
THROW_UNIX("reading input file");
|
|
fclose(jpegFile); jpegFile = NULL;
|
|
|
|
if (tj3DecompressHeader(tjInstance, jpegBuf, jpegSize) < 0)
|
|
THROW_TJ("reading JPEG header");
|
|
subsamp = tj3Get(tjInstance, TJPARAM_SUBSAMP);
|
|
width = tj3Get(tjInstance, TJPARAM_JPEGWIDTH);
|
|
height = tj3Get(tjInstance, TJPARAM_JPEGHEIGHT);
|
|
jpegPrecision = tj3Get(tjInstance, TJPARAM_PRECISION);
|
|
colorspace = tj3Get(tjInstance, TJPARAM_COLORSPACE);
|
|
lossless = tj3Get(tjInstance, TJPARAM_LOSSLESS);
|
|
if (precision == -1 || lossless || jpegPrecision != 8)
|
|
precision = jpegPrecision;
|
|
sampleSize = (precision <= 8 ? 1 : 2);
|
|
|
|
if (iccFilename) {
|
|
if (tj3GetICCProfile(tjInstance, &iccBuf, &iccSize) < 0) {
|
|
THROW_TJ("getting ICC profile");
|
|
} else {
|
|
if ((iccFile = fopen(iccFilename, "wb")) == NULL)
|
|
THROW_UNIX("opening ICC file");
|
|
if (fwrite(iccBuf, iccSize, 1, iccFile) < 1)
|
|
THROW_UNIX("writing ICC profile");
|
|
tj3Free(iccBuf); iccBuf = NULL;
|
|
fclose(iccFile); iccFile = NULL;
|
|
}
|
|
}
|
|
|
|
if (pixelFormat == TJPF_UNKNOWN) {
|
|
if (colorspace == TJCS_GRAY)
|
|
pixelFormat = TJPF_GRAY;
|
|
else if (colorspace == TJCS_CMYK || colorspace == TJCS_YCCK)
|
|
pixelFormat = TJPF_CMYK;
|
|
else
|
|
pixelFormat = TJPF_RGB;
|
|
}
|
|
|
|
if (!lossless) {
|
|
if (tj3SetScalingFactor(tjInstance, scalingFactor) < 0)
|
|
THROW_TJ("setting scaling factor");
|
|
width = TJSCALED(width, scalingFactor);
|
|
height = TJSCALED(height, scalingFactor);
|
|
|
|
if (IS_CROPPED(croppingRegion)) {
|
|
int adjustment;
|
|
|
|
if (subsamp == TJSAMP_UNKNOWN)
|
|
THROW("adjusting cropping region",
|
|
"Could not determine subsampling level of JPEG image");
|
|
adjustment =
|
|
croppingRegion.x % TJSCALED(tjMCUWidth[subsamp], scalingFactor);
|
|
croppingRegion.x -= adjustment;
|
|
croppingRegion.w += adjustment;
|
|
if (tj3SetCroppingRegion(tjInstance, croppingRegion) < 0)
|
|
THROW_TJ("setting cropping region");
|
|
width = croppingRegion.w;
|
|
height = croppingRegion.h;
|
|
}
|
|
}
|
|
|
|
#if ULLONG_MAX > SIZE_MAX
|
|
if ((unsigned long long)width * height * tjPixelSize[pixelFormat] *
|
|
sampleSize > (unsigned long long)((size_t)-1))
|
|
THROW("allocating uncompressed image buffer", "Image is too large");
|
|
#endif
|
|
if ((dstBuf =
|
|
(unsigned char *)malloc(width * height * tjPixelSize[pixelFormat] *
|
|
sampleSize)) == NULL)
|
|
THROW_UNIX("allocating uncompressed image buffer");
|
|
|
|
if (precision <= 8) {
|
|
if (tj3Decompress8(tjInstance, jpegBuf, jpegSize, dstBuf, 0,
|
|
pixelFormat) < 0)
|
|
THROW_TJ("decompressing JPEG image");
|
|
} else if (precision <= 12) {
|
|
if (tj3Decompress12(tjInstance, jpegBuf, jpegSize, dstBuf, 0,
|
|
pixelFormat) < 0)
|
|
THROW_TJ("decompressing JPEG image");
|
|
} else {
|
|
if (tj3Decompress16(tjInstance, jpegBuf, jpegSize, dstBuf, 0,
|
|
pixelFormat) < 0)
|
|
THROW_TJ("decompressing JPEG image");
|
|
}
|
|
tj3Free(jpegBuf); jpegBuf = NULL;
|
|
|
|
if (precision <= 8) {
|
|
if (tj3SaveImage8(tjInstance, argv[i], dstBuf, width, 0, height,
|
|
pixelFormat) < 0)
|
|
THROW_TJ("saving output image");
|
|
} else if (precision <= 12) {
|
|
if (tj3SaveImage12(tjInstance, argv[i], dstBuf, width, 0, height,
|
|
pixelFormat) < 0)
|
|
THROW_TJ("saving output image");
|
|
} else {
|
|
if (tj3SaveImage16(tjInstance, argv[i], dstBuf, width, 0, height,
|
|
pixelFormat) < 0)
|
|
THROW_TJ("saving output image");
|
|
}
|
|
|
|
bailout:
|
|
tj3Destroy(tjInstance);
|
|
if (jpegFile) fclose(jpegFile);
|
|
tj3Free(jpegBuf);
|
|
tj3Free(iccBuf);
|
|
if (iccFile) fclose(iccFile);
|
|
free(dstBuf);
|
|
return retval;
|
|
}
|