diff --git a/ChangeLog.md b/ChangeLog.md index 176264dd..5560622e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -101,6 +101,14 @@ TurboJPEG API libraries. This facilitates using libjpeg-turbo with CMake's target_link_libraries(turbojpeg_program_static PUBLIC libjpeg-turbo::turbojpeg-static) +11. Since the Unisys LZW patent has long expired, cjpeg and djpeg can now +read/write both LZW-compressed and uncompressed GIF files (feature ported from +jpeg-6a and jpeg-9d.) + +12. jpegtran now includes the `-wipe` and `-drop` options from jpeg-9a and +jpeg-9d, as well as the ability to expand the image size using the `-crop` +option. Refer to jpegtran.1 or usage.txt for more details. + 2.0.6 ===== diff --git a/README.ijg b/README.ijg index d681cf12..9453c195 100644 --- a/README.ijg +++ b/README.ijg @@ -128,7 +128,7 @@ with respect to this software, its quality, accuracy, merchantability, or fitness for a particular purpose. This software is provided "AS IS", and you, its user, assume the entire risk as to its quality and accuracy. -This software is copyright (C) 1991-2016, Thomas G. Lane, Guido Vollbeding. +This software is copyright (C) 1991-2020, Thomas G. Lane, Guido Vollbeding. All Rights Reserved except as specified below. Permission is hereby granted to use, copy, modify, and distribute this @@ -159,19 +159,6 @@ commercial products, provided that all warranty or liability claims are assumed by the product vendor. -The IJG distribution formerly included code to read and write GIF files. -To avoid entanglement with the Unisys LZW patent (now expired), GIF reading -support has been removed altogether, and the GIF writer has been simplified -to produce "uncompressed GIFs". This technique does not use the LZW -algorithm; the resulting GIF files are larger than usual, but are readable -by all standard GIF decoders. - -We are required to state that - "The Graphics Interchange Format(c) is the Copyright property of - CompuServe Incorporated. GIF(sm) is a Service Mark property of - CompuServe Incorporated." - - REFERENCES ========== diff --git a/cderror.h b/cderror.h index 25f95d3e..a386b693 100644 --- a/cderror.h +++ b/cderror.h @@ -42,7 +42,7 @@ JMESSAGE(JMSG_FIRSTADDONCODE = 1000, NULL) /* Must be first entry! */ #ifdef BMP_SUPPORTED JMESSAGE(JERR_BMP_BADCMAP, "Unsupported BMP colormap format") -JMESSAGE(JERR_BMP_BADDEPTH, "Only 8- and 24-bit BMP files are supported") +JMESSAGE(JERR_BMP_BADDEPTH, "Only 8-, 24-, and 32-bit BMP files are supported") JMESSAGE(JERR_BMP_BADHEADER, "Invalid BMP file: bad header length") JMESSAGE(JERR_BMP_BADPLANES, "Invalid BMP file: biPlanes not equal to 1") JMESSAGE(JERR_BMP_COLORSPACE, "BMP output must be grayscale or RGB") @@ -50,9 +50,9 @@ JMESSAGE(JERR_BMP_COMPRESSED, "Sorry, compressed BMPs not yet supported") JMESSAGE(JERR_BMP_EMPTY, "Empty BMP image") JMESSAGE(JERR_BMP_NOT, "Not a BMP file - does not start with BM") JMESSAGE(JERR_BMP_OUTOFRANGE, "Numeric value out of range in BMP file") -JMESSAGE(JTRC_BMP, "%ux%u 24-bit BMP image") +JMESSAGE(JTRC_BMP, "%ux%u %d-bit BMP image") JMESSAGE(JTRC_BMP_MAPPED, "%ux%u 8-bit colormapped BMP image") -JMESSAGE(JTRC_BMP_OS2, "%ux%u 24-bit OS2 BMP image") +JMESSAGE(JTRC_BMP_OS2, "%ux%u %d-bit OS2 BMP image") JMESSAGE(JTRC_BMP_OS2_MAPPED, "%ux%u 8-bit colormapped OS2 BMP image") #endif /* BMP_SUPPORTED */ diff --git a/cdjpeg.h b/cdjpeg.h index d283d425..23eb3e48 100644 --- a/cdjpeg.h +++ b/cdjpeg.h @@ -3,6 +3,7 @@ * * This file was part of the Independent JPEG Group's software: * Copyright (C) 1994-1997, Thomas G. Lane. + * Modified 2019 by Guido Vollbeding. * libjpeg-turbo Modifications: * Copyright (C) 2017, 2019, D. R. Commander. * For conditions of distribution and use, see the accompanying README.ijg @@ -104,7 +105,7 @@ EXTERN(cjpeg_source_ptr) jinit_read_bmp(j_compress_ptr cinfo, EXTERN(djpeg_dest_ptr) jinit_write_bmp(j_decompress_ptr cinfo, boolean is_os2, boolean use_inversion_array); EXTERN(cjpeg_source_ptr) jinit_read_gif(j_compress_ptr cinfo); -EXTERN(djpeg_dest_ptr) jinit_write_gif(j_decompress_ptr cinfo); +EXTERN(djpeg_dest_ptr) jinit_write_gif(j_decompress_ptr cinfo, boolean is_lzw); EXTERN(cjpeg_source_ptr) jinit_read_ppm(j_compress_ptr cinfo); EXTERN(djpeg_dest_ptr) jinit_write_ppm(j_decompress_ptr cinfo); EXTERN(cjpeg_source_ptr) jinit_read_targa(j_compress_ptr cinfo); diff --git a/change.log b/change.log index f090d778..e4d0ddc4 100644 --- a/change.log +++ b/change.log @@ -6,6 +6,25 @@ reference. Please see ChangeLog.md for information specific to libjpeg-turbo. CHANGE LOG for Independent JPEG Group's JPEG software +Version 9d 12-Jan-2020 +----------------------- + +Restore GIF read and write support from libjpeg version 6a. +Thank to Wolfgang Werner (W.W.) Heinz for suggestion. + +Add jpegtran -drop option; add options to the crop extension and wipe +to fill the extra area with content from the source image region, +instead of gray out. + + +Version 9c 14-Jan-2018 +----------------------- + +jpegtran: add an option to the -wipe switch to fill the region +with the average of adjacent blocks, instead of gray out. +Thank to Caitlyn Feddock and Maddie Ziegler for inspiration. + + Version 9b 17-Jan-2016 ----------------------- @@ -13,6 +32,13 @@ Document 'f' specifier for jpegtran -crop specification. Thank to Michele Martone for suggestion. +Version 9a 19-Jan-2014 +----------------------- + +Add jpegtran -wipe option and extension for -crop. +Thank to Andrew Senior, David Clunie, and Josef Schmid for suggestion. + + Version 9 13-Jan-2013 ---------------------- @@ -138,11 +164,6 @@ Huffman tables being used. Huffman tables are checked for validity much more carefully than before. -To avoid the Unisys LZW patent, djpeg's GIF output capability has been -changed to produce "uncompressed GIFs", and cjpeg's GIF input capability -has been removed altogether. We're not happy about it either, but there -seems to be no good alternative. - The configure script now supports building libjpeg as a shared library on many flavors of Unix (all the ones that GNU libtool knows how to build shared libraries for). Use "./configure --enable-shared" to diff --git a/cjpeg.1 b/cjpeg.1 index 0bf6a5ec..5782e8b8 100644 --- a/cjpeg.1 +++ b/cjpeg.1 @@ -16,7 +16,7 @@ cjpeg \- compress an image file to a JPEG file compresses the named image file, or the standard input if no file is named, and produces a JPEG/JFIF file on the standard output. The currently supported input file formats are: PPM (PBMPLUS color -format), PGM (PBMPLUS grayscale format), BMP, and Targa. +format), PGM (PBMPLUS grayscale format), BMP, GIF, and Targa. .SH OPTIONS All switch names may be abbreviated; for example, .B \-grayscale @@ -41,10 +41,10 @@ Scale quantization tables to adjust image quality. Quality is 0 (worst) to .TP .B \-grayscale Create monochrome JPEG file from color input. Be sure to use this switch when -compressing a grayscale BMP file, because +compressing a grayscale BMP or GIF file, because .B cjpeg -isn't bright enough to notice whether a BMP file uses only shades of gray. -By saying +isn't bright enough to notice whether a BMP or GIF file uses only shades of +gray. By saying .BR \-grayscale, you'll get a smaller JPEG file that takes less time to process. .TP @@ -343,11 +343,6 @@ This file was modified by The libjpeg-turbo Project to include only information relevant to libjpeg-turbo, to wordsmith certain sections, and to describe features not present in libjpeg. .SH ISSUES -Support for GIF input files was removed in cjpeg v6b due to concerns over -the Unisys LZW patent. Although this patent expired in 2006, cjpeg still -lacks GIF support, for these historical reasons. (Conversion of GIF files to -JPEG is usually a bad idea anyway, since GIF is a 256-color format.) -.PP Not all variants of BMP and Targa file formats are supported. .PP The diff --git a/djpeg.1 b/djpeg.1 index f442471b..f6a6f434 100644 --- a/djpeg.1 +++ b/djpeg.1 @@ -80,9 +80,20 @@ is specified, or if the JPEG file is grayscale; otherwise, 24-bit full-color format is emitted. .TP .B \-gif -Select GIF output format. Since GIF does not support more than 256 colors, +Select GIF output format (LZW-compressed). Since GIF does not support more +than 256 colors, .B \-colors 256 -is assumed (unless you specify a smaller number of colors). +is assumed (unless you specify a smaller number of colors). If you specify +.BR \-fast, +the default number of colors is 216. +.TP +.B \-gif0 +Select GIF output format (uncompressed). Since GIF does not support more than +256 colors, +.B \-colors 256 +is assumed (unless you specify a smaller number of colors). If you specify +.BR \-fast, +the default number of colors is 216. .TP .B \-os2 Select BMP output format (OS/2 1.x flavor). 8-bit colormapped format is @@ -305,10 +316,3 @@ Independent JPEG Group This file was modified by The libjpeg-turbo Project to include only information relevant to libjpeg-turbo, to wordsmith certain sections, and to describe features not present in libjpeg. -.SH ISSUES -Support for compressed GIF output files was removed in djpeg v6b due to -concerns over the Unisys LZW patent. Although this patent expired in 2006, -djpeg still lacks compressed GIF support, for these historical reasons. -(Conversion of JPEG files to GIF is usually a bad idea anyway, since GIF is a -256-color format.) The uncompressed GIF files that djpeg generates are larger -than they should be, but they are readable by standard GIF decoders. diff --git a/djpeg.c b/djpeg.c index fb015730..7aa346b6 100644 --- a/djpeg.c +++ b/djpeg.c @@ -3,7 +3,7 @@ * * This file was part of the Independent JPEG Group's software: * Copyright (C) 1991-1997, Thomas G. Lane. - * Modified 2013 by Guido Vollbeding. + * Modified 2013-2019 by Guido Vollbeding. * libjpeg-turbo Modifications: * Copyright (C) 2010-2011, 2013-2017, 2019-2020, D. R. Commander. * Copyright (C) 2015, Google, Inc. @@ -68,7 +68,8 @@ static const char * const cdjpeg_message_table[] = { typedef enum { FMT_BMP, /* BMP format (Windows flavor) */ - FMT_GIF, /* GIF format */ + FMT_GIF, /* GIF format (LZW-compressed) */ + FMT_GIF0, /* GIF format (uncompressed) */ FMT_OS2, /* BMP format (OS/2 flavor) */ FMT_PPM, /* PPM/PGM (PBMPLUS formats) */ FMT_TARGA, /* Targa format */ @@ -129,8 +130,10 @@ usage(void) (DEFAULT_FMT == FMT_BMP ? " (default)" : "")); #endif #ifdef GIF_SUPPORTED - fprintf(stderr, " -gif Select GIF output format%s\n", + fprintf(stderr, " -gif Select GIF output format (LZW-compressed)%s\n", (DEFAULT_FMT == FMT_GIF ? " (default)" : "")); + fprintf(stderr, " -gif0 Select GIF output format (uncompressed)%s\n", + (DEFAULT_FMT == FMT_GIF0 ? " (default)" : "")); #endif #ifdef BMP_SUPPORTED fprintf(stderr, " -os2 Select BMP output format (OS/2 style)%s\n", @@ -227,7 +230,7 @@ parse_switches(j_decompress_ptr cinfo, int argc, char **argv, arg++; /* advance past switch marker character */ if (keymatch(arg, "bmp", 1)) { - /* BMP output format. */ + /* BMP output format (Windows flavor). */ requested_fmt = FMT_BMP; } else if (keymatch(arg, "colors", 1) || keymatch(arg, "colours", 1) || @@ -298,9 +301,13 @@ parse_switches(j_decompress_ptr cinfo, int argc, char **argv, cinfo->do_fancy_upsampling = FALSE; } else if (keymatch(arg, "gif", 1)) { - /* GIF output format. */ + /* GIF output format (LZW-compressed). */ requested_fmt = FMT_GIF; + } else if (keymatch(arg, "gif0", 4)) { + /* GIF output format (uncompressed). */ + requested_fmt = FMT_GIF0; + } else if (keymatch(arg, "grayscale", 2) || keymatch(arg, "greyscale", 2)) { /* Force monochrome output. */ @@ -680,7 +687,10 @@ main(int argc, char **argv) #endif #ifdef GIF_SUPPORTED case FMT_GIF: - dest_mgr = jinit_write_gif(&cinfo); + dest_mgr = jinit_write_gif(&cinfo, TRUE); + break; + case FMT_GIF0: + dest_mgr = jinit_write_gif(&cinfo, FALSE); break; #endif #ifdef PPM_SUPPORTED diff --git a/jerror.h b/jerror.h index 933a3690..4476df2c 100644 --- a/jerror.h +++ b/jerror.h @@ -207,6 +207,10 @@ JMESSAGE(JWRN_ARITH_BAD_CODE, "Corrupt JPEG data: bad arithmetic code") #endif #endif JMESSAGE(JWRN_BOGUS_ICC, "Corrupt JPEG data: bad ICC marker") +#if JPEG_LIB_VERSION < 70 +JMESSAGE(JERR_BAD_DROP_SAMPLING, + "Component index %d: mismatching sampling ratio %d:%d, %d:%d, %c") +#endif #ifdef JMAKE_ENUM_LIST @@ -252,6 +256,15 @@ JMESSAGE(JWRN_BOGUS_ICC, "Corrupt JPEG data: bad ICC marker") (cinfo)->err->msg_parm.i[2] = (p3), \ (cinfo)->err->msg_parm.i[3] = (p4), \ (*(cinfo)->err->error_exit) ((j_common_ptr)(cinfo))) +#define ERREXIT6(cinfo, code, p1, p2, p3, p4, p5, p6) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (cinfo)->err->msg_parm.i[3] = (p4), \ + (cinfo)->err->msg_parm.i[4] = (p5), \ + (cinfo)->err->msg_parm.i[5] = (p6), \ + (*(cinfo)->err->error_exit) ((j_common_ptr)(cinfo))) #define ERREXITS(cinfo, code, str) \ ((cinfo)->err->msg_code = (code), \ strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ diff --git a/jidctint.c b/jidctint.c index 98425d5f..b9974d4d 100644 --- a/jidctint.c +++ b/jidctint.c @@ -3,7 +3,7 @@ * * This file was part of the Independent JPEG Group's software: * Copyright (C) 1991-1998, Thomas G. Lane. - * Modification developed 2002-2009 by Guido Vollbeding. + * Modification developed 2002-2018 by Guido Vollbeding. * libjpeg-turbo Modifications: * Copyright (C) 2015, D. R. Commander. * For conditions of distribution and use, see the accompanying README.ijg @@ -417,7 +417,7 @@ jpeg_idct_islow(j_decompress_ptr cinfo, jpeg_component_info *compptr, /* * Perform dequantization and inverse DCT on one block of coefficients, - * producing a 7x7 output block. + * producing a reduced-size 7x7 output block. * * Optimized algorithm with 12 multiplications in the 1-D kernel. * cK represents sqrt(2) * cos(K*pi/14). @@ -1258,7 +1258,7 @@ jpeg_idct_10x10(j_decompress_ptr cinfo, jpeg_component_info *compptr, /* * Perform dequantization and inverse DCT on one block of coefficients, - * producing a 11x11 output block. + * producing an 11x11 output block. * * Optimized algorithm with 24 multiplications in the 1-D kernel. * cK represents sqrt(2) * cos(K*pi/22). @@ -2398,7 +2398,7 @@ jpeg_idct_16x16(j_decompress_ptr cinfo, jpeg_component_info *compptr, tmp0 = DEQUANTIZE(inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0]); tmp0 = LEFT_SHIFT(tmp0, CONST_BITS); /* Add fudge factor here for final descale. */ - tmp0 += 1 << (CONST_BITS - PASS1_BITS - 1); + tmp0 += ONE << (CONST_BITS - PASS1_BITS - 1); z1 = DEQUANTIZE(inptr[DCTSIZE * 4], quantptr[DCTSIZE * 4]); tmp1 = MULTIPLY(z1, FIX(1.306562965)); /* c4[16] = c2[8] */ diff --git a/jpegtran.1 b/jpegtran.1 index 3eb472d9..da7a2669 100644 --- a/jpegtran.1 +++ b/jpegtran.1 @@ -1,4 +1,4 @@ -.TH JPEGTRAN 1 "18 December 2019" +.TH JPEGTRAN 1 "26 October 2020" .SH NAME jpegtran \- lossless transformation of JPEG files .SH SYNOPSIS @@ -161,13 +161,13 @@ to do a perfect rotation, if available, or an approximated one if not. .PP This version of \fBjpegtran\fR also offers a lossless crop option, which discards data outside of a given image region but losslessly preserves what is -inside. Like the rotate and flip transforms, lossless crop is restricted by the -current JPEG format; the upper left corner of the selected region must fall on -an iMCU boundary. If it doesn't, then it is silently moved up and/or left to -the nearest iMCU boundary (the lower right corner is unchanged.) Thus, the +inside. Like the rotate and flip transforms, lossless crop is restricted by +the current JPEG format; the upper left corner of the selected region must fall +on an iMCU boundary. If it doesn't, then it is silently moved up and/or left +to the nearest iMCU boundary (the lower right corner is unchanged.) Thus, the output image covers at least the requested region, but it may cover more. The -adjustment of the region dimensions may be optionally disabled by attaching -an 'f' character ("force") to the width or height number. +adjustment of the region dimensions may be optionally disabled by attaching an +'f' character ("force") to the width or height number. The image can be losslessly cropped by giving the switch: .TP @@ -180,6 +180,47 @@ left corner of the selected region must fall on an iMCU boundary. If it doesn't, then it is silently moved up and/or left to the nearest iMCU boundary (the lower right corner is unchanged.) .PP +If W or H is larger than the width/height of the input image, then the output +image is expanded in size, and the expanded region is filled in with zeros +(neutral gray). Attaching an 'f' character ("flatten") to the width number +will cause each block in the expanded region to be filled in with the DC +coefficient of the nearest block in the input image rather than grayed out. +Attaching an 'r' character ("reflect") to the width number will cause the +expanded region to be filled in with repeated reflections of the input image +rather than grayed out. +.PP +A complementary lossless wipe option is provided to discard (gray out) data +inside a given image region while losslessly preserving what is outside: +.TP +.B \-wipe WxH+X+Y +Wipe (gray out) a rectangular region of width W and height H from the input +image, starting at point X,Y. +.PP +Attaching an 'f' character ("flatten") to the width number will cause the +region to be filled with the average of adjacent blocks rather than grayed out. +If the wipe region and the region outside the wipe region, when adjusted to the +nearest iMCU boundary, form two horizontally adjacent rectangles, then +attaching an 'r' character ("reflect") to the width number will cause the wipe +region to be filled with repeated reflections of the outside region rather than +grayed out. +.PP +A lossless drop option is also provided, which allows another JPEG image to be +inserted ("dropped") into the input image data at a given position, replacing +the existing image data at that position: +.TP +.B \-drop +X+Y filename +Drop (insert) another image at point X,Y +.PP +Both the input image and the drop image must have the same subsampling level. +It is best if they also have the same quantization (quality.) Otherwise, the +quantization of the output image will be adapted to accommodate the higher of +the input image quality and the drop image quality. The trim option can be +used with the drop option to requantize the drop image to match the input +image. Note that a grayscale image can be dropped into a full-color image or +vice versa, as long as the full-color image has no vertical subsampling. If +the input image is grayscale and the drop image is full-color, then the +chrominance channels from the drop image will be discarded. +.PP Other not-strictly-lossless transformation switches are: .TP .B \-grayscale diff --git a/jpegtran.c b/jpegtran.c index a02922be..861e1456 100644 --- a/jpegtran.c +++ b/jpegtran.c @@ -2,7 +2,7 @@ * jpegtran.c * * This file was part of the Independent JPEG Group's software: - * Copyright (C) 1995-2010, Thomas G. Lane, Guido Vollbeding. + * Copyright (C) 1995-2019, Thomas G. Lane, Guido Vollbeding. * libjpeg-turbo Modifications: * Copyright (C) 2010, 2014, 2017, 2019, D. R. Commander. * For conditions of distribution and use, see the accompanying README.ijg @@ -43,6 +43,7 @@ static const char *progname; /* program name for error messages */ static char *icc_filename; /* for -icc switch */ JDIMENSION max_scans; /* for -maxscans switch */ static char *outfilename; /* for -outfile switch */ +static char *dropfilename; /* for -drop switch */ boolean report; /* for -report switch */ boolean strict; /* for -strict switch */ static JCOPY_OPTION copyoption; /* -copy switch */ @@ -72,9 +73,10 @@ usage(void) #endif fprintf(stderr, "Switches for modifying the image:\n"); #if TRANSFORMS_SUPPORTED - fprintf(stderr, " -crop WxH+X+Y Crop to a rectangular subarea\n"); - fprintf(stderr, " -grayscale Reduce to grayscale (omit color data)\n"); + fprintf(stderr, " -crop WxH+X+Y Crop to a rectangular region\n"); + fprintf(stderr, " -drop +X+Y filename Drop (insert) another image\n"); fprintf(stderr, " -flip [horizontal|vertical] Mirror image (left-right or top-bottom)\n"); + fprintf(stderr, " -grayscale Reduce to grayscale (omit color data)\n"); fprintf(stderr, " -perfect Fail if there is non-transformable edge blocks\n"); fprintf(stderr, " -rotate [90|180|270] Rotate image (degrees clockwise)\n"); #endif @@ -82,6 +84,8 @@ usage(void) fprintf(stderr, " -transpose Transpose image\n"); fprintf(stderr, " -transverse Transverse transpose image\n"); fprintf(stderr, " -trim Drop non-transformable edge blocks\n"); + fprintf(stderr, " with -drop: Requantize drop file to match source file\n"); + fprintf(stderr, " -wipe WxH+X+Y Wipe (gray out) a rectangular region\n"); #endif fprintf(stderr, "Switches for advanced users:\n"); #ifdef C_ARITH_CODING_SUPPORTED @@ -202,7 +206,8 @@ parse_switches(j_compress_ptr cinfo, int argc, char **argv, #if TRANSFORMS_SUPPORTED if (++argn >= argc) /* advance to next argument */ usage(); - if (!jtransform_parse_crop_spec(&transformoption, argv[argn])) { + if (transformoption.crop /* reject multiple crop/drop/wipe requests */ || + !jtransform_parse_crop_spec(&transformoption, argv[argn])) { fprintf(stderr, "%s: bogus -crop argument '%s'\n", progname, argv[argn]); exit(EXIT_FAILURE); @@ -211,6 +216,26 @@ parse_switches(j_compress_ptr cinfo, int argc, char **argv, select_transform(JXFORM_NONE); /* force an error */ #endif + } else if (keymatch(arg, "drop", 2)) { +#if TRANSFORMS_SUPPORTED + if (++argn >= argc) /* advance to next argument */ + usage(); + if (transformoption.crop /* reject multiple crop/drop/wipe requests */ || + !jtransform_parse_crop_spec(&transformoption, argv[argn]) || + transformoption.crop_width_set != JCROP_UNSET || + transformoption.crop_height_set != JCROP_UNSET) { + fprintf(stderr, "%s: bogus -drop argument '%s'\n", + progname, argv[argn]); + exit(EXIT_FAILURE); + } + if (++argn >= argc) /* advance to next argument */ + usage(); + dropfilename = argv[argn]; + select_transform(JXFORM_DROP); +#else + select_transform(JXFORM_NONE); /* force an error */ +#endif + } else if (keymatch(arg, "debug", 1) || keymatch(arg, "verbose", 1)) { /* Enable debug printouts. */ /* On first -d, print version identification */ @@ -371,6 +396,21 @@ parse_switches(j_compress_ptr cinfo, int argc, char **argv, /* Trim off any partial edge MCUs that the transform can't handle. */ transformoption.trim = TRUE; + } else if (keymatch(arg, "wipe", 1)) { +#if TRANSFORMS_SUPPORTED + if (++argn >= argc) /* advance to next argument */ + usage(); + if (transformoption.crop /* reject multiple crop/drop/wipe requests */ || + !jtransform_parse_crop_spec(&transformoption, argv[argn])) { + fprintf(stderr, "%s: bogus -wipe argument '%s'\n", + progname, argv[argn]); + exit(EXIT_FAILURE); + } + select_transform(JXFORM_WIPE); +#else + select_transform(JXFORM_NONE); /* force an error */ +#endif + } else { usage(); /* bogus switch */ } @@ -417,6 +457,11 @@ int main(int argc, char **argv) { struct jpeg_decompress_struct srcinfo; +#if TRANSFORMS_SUPPORTED + struct jpeg_decompress_struct dropinfo; + struct jpeg_error_mgr jdroperr; + FILE *drop_file; +#endif struct jpeg_compress_struct dstinfo; struct jpeg_error_mgr jsrcerr, jdsterr; struct cdjpeg_progress_mgr src_progress, dst_progress; @@ -452,7 +497,7 @@ main(int argc, char **argv) * values read here are mostly ignored; we will rescan the switches after * opening the input file. Also note that most of the switches affect the * destination JPEG object, so we parse into that and then copy over what - * needs to affects the source too. + * needs to affect the source too. */ file_index = parse_switches(&dstinfo, argc, argv, 0, FALSE); @@ -536,6 +581,21 @@ main(int argc, char **argv) src_progress.report = report; src_progress.max_scans = max_scans; } +#if TRANSFORMS_SUPPORTED + /* Open the drop file. */ + if (dropfilename != NULL) { + if ((drop_file = fopen(dropfilename, READ_BINARY)) == NULL) { + fprintf(stderr, "%s: can't open %s for reading\n", progname, + dropfilename); + exit(EXIT_FAILURE); + } + dropinfo.err = jpeg_std_error(&jdroperr); + jpeg_create_decompress(&dropinfo); + jpeg_stdio_src(&dropinfo, drop_file); + } else { + drop_file = NULL; + } +#endif /* Specify data source for decompression */ jpeg_stdio_src(&srcinfo, fp); @@ -546,6 +606,17 @@ main(int argc, char **argv) /* Read file header */ (void)jpeg_read_header(&srcinfo, TRUE); +#if TRANSFORMS_SUPPORTED + if (dropfilename != NULL) { + (void)jpeg_read_header(&dropinfo, TRUE); + transformoption.crop_width = dropinfo.image_width; + transformoption.crop_width_set = JCROP_POS; + transformoption.crop_height = dropinfo.image_height; + transformoption.crop_height_set = JCROP_POS; + transformoption.drop_ptr = &dropinfo; + } +#endif + /* Any space needed by a transform option must be requested before * jpeg_read_coefficients so that memory allocation will be done right. */ @@ -561,6 +632,12 @@ main(int argc, char **argv) /* Read source file as DCT coefficients */ src_coef_arrays = jpeg_read_coefficients(&srcinfo); +#if TRANSFORMS_SUPPORTED + if (dropfilename != NULL) { + transformoption.drop_coef_arrays = jpeg_read_coefficients(&dropinfo); + } +#endif + /* Initialize destination compression parameters from source values */ jpeg_copy_critical_parameters(&srcinfo, &dstinfo); @@ -621,12 +698,22 @@ main(int argc, char **argv) /* Finish compression and release memory */ jpeg_finish_compress(&dstinfo); jpeg_destroy_compress(&dstinfo); +#if TRANSFORMS_SUPPORTED + if (dropfilename != NULL) { + (void)jpeg_finish_decompress(&dropinfo); + jpeg_destroy_decompress(&dropinfo); + } +#endif (void)jpeg_finish_decompress(&srcinfo); jpeg_destroy_decompress(&srcinfo); /* Close output file, if we opened it */ if (fp != stdout) fclose(fp); +#if TRANSFORMS_SUPPORTED + if (drop_file != NULL) + fclose(drop_file); +#endif if (report) end_progress_monitor((j_common_ptr)&dstinfo); @@ -636,6 +723,11 @@ main(int argc, char **argv) free(icc_profile); /* All done. */ +#if TRANSFORMS_SUPPORTED + if (dropfilename != NULL) + exit(jsrcerr.num_warnings + jdroperr.num_warnings + + jdsterr.num_warnings ? EXIT_WARNING : EXIT_SUCCESS); +#endif exit(jsrcerr.num_warnings + jdsterr.num_warnings ? EXIT_WARNING : EXIT_SUCCESS); return 0; /* suppress no-return-value warnings */ diff --git a/jversion.h b/jversion.h index 4462b941..660f518d 100644 --- a/jversion.h +++ b/jversion.h @@ -2,7 +2,7 @@ * jversion.h * * This file was part of the Independent JPEG Group's software: - * Copyright (C) 1991-2012, Thomas G. Lane, Guido Vollbeding. + * Copyright (C) 1991-2020, Thomas G. Lane, Guido Vollbeding. * libjpeg-turbo Modifications: * Copyright (C) 2010, 2012-2020, D. R. Commander. * For conditions of distribution and use, see the accompanying README.ijg @@ -48,7 +48,7 @@ "Copyright (C) 2009, 2012 Pierre Ossman for Cendio AB\n" \ "Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies)\n" \ "Copyright (C) 1999-2006 MIYASAKA Masaru\n" \ - "Copyright (C) 1991-2017 Thomas G. Lane, Guido Vollbeding" + "Copyright (C) 1991-2020 Thomas G. Lane, Guido Vollbeding" #define JCOPYRIGHT_SHORT \ "Copyright (C) 1991-2020 The libjpeg-turbo Project and many others" diff --git a/rdbmp.c b/rdbmp.c index f3862852..6ba584af 100644 --- a/rdbmp.c +++ b/rdbmp.c @@ -12,7 +12,7 @@ * * This file contains routines to read input images in Microsoft "BMP" * format (MS Windows 3.x, OS/2 1.x, and OS/2 2.x flavors). - * Currently, only 8-bit and 24-bit images are supported, not 1-bit or + * Currently, only 8-, 24-, and 32-bit images are supported, not 1-bit or * 4-bit (feeding such low-depth images into JPEG would be silly anyway). * Also, we don't support RLE-compressed files. * @@ -61,7 +61,7 @@ typedef struct _bmp_source_struct { JDIMENSION source_row; /* Current source row number */ JDIMENSION row_width; /* Physical width of scanlines in file */ - int bits_per_pixel; /* remembers 8- or 24-bit format */ + int bits_per_pixel; /* remembers 8-, 24-, or 32-bit format */ int cmap_length; /* colormap length */ boolean use_inversion_array; /* TRUE = preload the whole image, which is @@ -469,7 +469,9 @@ start_input_bmp(j_compress_ptr cinfo, cjpeg_source_ptr sinfo) TRACEMS2(cinfo, 1, JTRC_BMP_OS2_MAPPED, biWidth, biHeight); break; case 24: /* RGB image */ - TRACEMS2(cinfo, 1, JTRC_BMP_OS2, biWidth, biHeight); + case 32: /* RGB image + Alpha channel */ + TRACEMS3(cinfo, 1, JTRC_BMP_OS2, biWidth, biHeight, + source->bits_per_pixel); break; default: ERREXIT(cinfo, JERR_BMP_BADDEPTH); @@ -496,10 +498,8 @@ start_input_bmp(j_compress_ptr cinfo, cjpeg_source_ptr sinfo) TRACEMS2(cinfo, 1, JTRC_BMP_MAPPED, biWidth, biHeight); break; case 24: /* RGB image */ - TRACEMS2(cinfo, 1, JTRC_BMP, biWidth, biHeight); - break; case 32: /* RGB image + Alpha channel */ - TRACEMS2(cinfo, 1, JTRC_BMP, biWidth, biHeight); + TRACEMS3(cinfo, 1, JTRC_BMP, biWidth, biHeight, source->bits_per_pixel); break; default: ERREXIT(cinfo, JERR_BMP_BADDEPTH); diff --git a/rdgif.c b/rdgif.c index ff9258d6..e1ea56cd 100644 --- a/rdgif.c +++ b/rdgif.c @@ -2,28 +2,656 @@ * rdgif.c * * Copyright (C) 1991-1997, Thomas G. Lane. + * Modified 2019 by Guido Vollbeding. * This file is part of the Independent JPEG Group's software. * For conditions of distribution and use, see the accompanying README.ijg * file. * * This file contains routines to read input images in GIF format. * - ***************************************************************************** - * NOTE: to avoid entanglements with Unisys' patent on LZW compression, * - * the ability to read GIF files has been removed from the IJG distribution. * - * Sorry about that. * - ***************************************************************************** - * - * We are required to state that - * "The Graphics Interchange Format(c) is the Copyright property of - * CompuServe Incorporated. GIF(sm) is a Service Mark property of - * CompuServe Incorporated." + * These routines may need modification for non-Unix environments or + * specialized applications. As they stand, they assume input from + * an ordinary stdio stream. They further assume that reading begins + * at the start of the file; start_input may need work if the + * user interface has already read some data (e.g., to determine that + * the file is indeed GIF format). + */ + +/* + * This code is loosely based on giftoppm from the PBMPLUS distribution + * of Feb. 1991. That file contains the following copyright notice: + * +-------------------------------------------------------------------+ + * | Copyright 1990, David Koblas. | + * | Permission to use, copy, modify, and distribute this software | + * | and its documentation for any purpose and without fee is hereby | + * | granted, provided that the above copyright notice appear in all | + * | copies and that both that copyright notice and this permission | + * | notice appear in supporting documentation. This software is | + * | provided "as is" without express or implied warranty. | + * +-------------------------------------------------------------------+ */ #include "cdjpeg.h" /* Common decls for cjpeg/djpeg applications */ #ifdef GIF_SUPPORTED + +/* Macros to deal with unsigned chars as efficiently as compiler allows */ + +typedef unsigned char U_CHAR; +#define UCH(x) ((int)(x)) + + +#define ReadOK(file, buffer, len) \ + (JFREAD(file, buffer, len) == ((size_t)(len))) + + +#define MAXCOLORMAPSIZE 256 /* max # of colors in a GIF colormap */ +#define NUMCOLORS 3 /* # of colors */ +#define CM_RED 0 /* color component numbers */ +#define CM_GREEN 1 +#define CM_BLUE 2 + +#define MAX_LZW_BITS 12 /* maximum LZW code size */ +#define LZW_TABLE_SIZE (1 << MAX_LZW_BITS) /* # of possible LZW symbols */ + +/* Macros for extracting header data --- note we assume chars may be signed */ + +#define LM_to_uint(array, offset) \ + ((unsigned int)UCH(array[offset]) + \ + (((unsigned int)UCH(array[offset + 1])) << 8)) + +#define BitSet(byte, bit) ((byte) & (bit)) +#define INTERLACE 0x40 /* mask for bit signifying interlaced image */ +#define COLORMAPFLAG 0x80 /* mask for bit signifying colormap presence */ + + +/* + * LZW decompression tables look like this: + * symbol_head[K] = prefix symbol of any LZW symbol K (0..LZW_TABLE_SIZE-1) + * symbol_tail[K] = suffix byte of any LZW symbol K (0..LZW_TABLE_SIZE-1) + * Note that entries 0..end_code of the above tables are not used, + * since those symbols represent raw bytes or special codes. + * + * The stack represents the not-yet-used expansion of the last LZW symbol. + * In the worst case, a symbol could expand to as many bytes as there are + * LZW symbols, so we allocate LZW_TABLE_SIZE bytes for the stack. + * (This is conservative since that number includes the raw-byte symbols.) + */ + + +/* Private version of data source object */ + +typedef struct { + struct cjpeg_source_struct pub; /* public fields */ + + j_compress_ptr cinfo; /* back link saves passing separate parm */ + + JSAMPARRAY colormap; /* GIF colormap (converted to my format) */ + + /* State for GetCode and LZWReadByte */ + U_CHAR code_buf[256 + 4]; /* current input data block */ + int last_byte; /* # of bytes in code_buf */ + int last_bit; /* # of bits in code_buf */ + int cur_bit; /* next bit index to read */ + boolean first_time; /* flags first call to GetCode */ + boolean out_of_blocks; /* TRUE if hit terminator data block */ + + int input_code_size; /* codesize given in GIF file */ + int clear_code, end_code; /* values for Clear and End codes */ + + int code_size; /* current actual code size */ + int limit_code; /* 2^code_size */ + int max_code; /* first unused code value */ + + /* Private state for LZWReadByte */ + int oldcode; /* previous LZW symbol */ + int firstcode; /* first byte of oldcode's expansion */ + + /* LZW symbol table and expansion stack */ + UINT16 *symbol_head; /* => table of prefix symbols */ + UINT8 *symbol_tail; /* => table of suffix bytes */ + UINT8 *symbol_stack; /* => stack for symbol expansions */ + UINT8 *sp; /* stack pointer */ + + /* State for interlaced image processing */ + boolean is_interlaced; /* TRUE if have interlaced image */ + jvirt_sarray_ptr interlaced_image; /* full image in interlaced order */ + JDIMENSION cur_row_number; /* need to know actual row number */ + JDIMENSION pass2_offset; /* # of pixel rows in pass 1 */ + JDIMENSION pass3_offset; /* # of pixel rows in passes 1&2 */ + JDIMENSION pass4_offset; /* # of pixel rows in passes 1,2,3 */ +} gif_source_struct; + +typedef gif_source_struct *gif_source_ptr; + + +/* Forward declarations */ +METHODDEF(JDIMENSION) get_pixel_rows(j_compress_ptr cinfo, + cjpeg_source_ptr sinfo); +METHODDEF(JDIMENSION) load_interlaced_image(j_compress_ptr cinfo, + cjpeg_source_ptr sinfo); +METHODDEF(JDIMENSION) get_interlaced_row(j_compress_ptr cinfo, + cjpeg_source_ptr sinfo); + + +LOCAL(int) +ReadByte(gif_source_ptr sinfo) +/* Read next byte from GIF file */ +{ + register FILE *infile = sinfo->pub.input_file; + register int c; + + if ((c = getc(infile)) == EOF) + ERREXIT(sinfo->cinfo, JERR_INPUT_EOF); + return c; +} + + +LOCAL(int) +GetDataBlock(gif_source_ptr sinfo, U_CHAR *buf) +/* Read a GIF data block, which has a leading count byte */ +/* A zero-length block marks the end of a data block sequence */ +{ + int count; + + count = ReadByte(sinfo); + if (count > 0) { + if (!ReadOK(sinfo->pub.input_file, buf, count)) + ERREXIT(sinfo->cinfo, JERR_INPUT_EOF); + } + return count; +} + + +LOCAL(void) +SkipDataBlocks(gif_source_ptr sinfo) +/* Skip a series of data blocks, until a block terminator is found */ +{ + U_CHAR buf[256]; + + while (GetDataBlock(sinfo, buf) > 0) + /* skip */; +} + + +LOCAL(void) +ReInitLZW(gif_source_ptr sinfo) +/* (Re)initialize LZW state; shared code for startup and Clear processing */ +{ + sinfo->code_size = sinfo->input_code_size + 1; + sinfo->limit_code = sinfo->clear_code << 1; /* 2^code_size */ + sinfo->max_code = sinfo->clear_code + 2; /* first unused code value */ + sinfo->sp = sinfo->symbol_stack; /* init stack to empty */ +} + + +LOCAL(void) +InitLZWCode(gif_source_ptr sinfo) +/* Initialize for a series of LZWReadByte (and hence GetCode) calls */ +{ + /* GetCode initialization */ + sinfo->last_byte = 2; /* make safe to "recopy last two bytes" */ + sinfo->code_buf[0] = 0; + sinfo->code_buf[1] = 0; + sinfo->last_bit = 0; /* nothing in the buffer */ + sinfo->cur_bit = 0; /* force buffer load on first call */ + sinfo->first_time = TRUE; + sinfo->out_of_blocks = FALSE; + + /* LZWReadByte initialization: */ + /* compute special code values (note that these do not change later) */ + sinfo->clear_code = 1 << sinfo->input_code_size; + sinfo->end_code = sinfo->clear_code + 1; + ReInitLZW(sinfo); +} + + +LOCAL(int) +GetCode(gif_source_ptr sinfo) +/* Fetch the next code_size bits from the GIF data */ +/* We assume code_size is less than 16 */ +{ + register int accum; + int offs, count; + + while (sinfo->cur_bit + sinfo->code_size > sinfo->last_bit) { + /* Time to reload the buffer */ + /* First time, share code with Clear case */ + if (sinfo->first_time) { + sinfo->first_time = FALSE; + return sinfo->clear_code; + } + if (sinfo->out_of_blocks) { + WARNMS(sinfo->cinfo, JWRN_GIF_NOMOREDATA); + return sinfo->end_code; /* fake something useful */ + } + /* preserve last two bytes of what we have -- assume code_size <= 16 */ + sinfo->code_buf[0] = sinfo->code_buf[sinfo->last_byte-2]; + sinfo->code_buf[1] = sinfo->code_buf[sinfo->last_byte-1]; + /* Load more bytes; set flag if we reach the terminator block */ + if ((count = GetDataBlock(sinfo, &sinfo->code_buf[2])) == 0) { + sinfo->out_of_blocks = TRUE; + WARNMS(sinfo->cinfo, JWRN_GIF_NOMOREDATA); + return sinfo->end_code; /* fake something useful */ + } + /* Reset counters */ + sinfo->cur_bit = (sinfo->cur_bit - sinfo->last_bit) + 16; + sinfo->last_byte = 2 + count; + sinfo->last_bit = sinfo->last_byte * 8; + } + + /* Form up next 24 bits in accum */ + offs = sinfo->cur_bit >> 3; /* byte containing cur_bit */ + accum = UCH(sinfo->code_buf[offs + 2]); + accum <<= 8; + accum |= UCH(sinfo->code_buf[offs + 1]); + accum <<= 8; + accum |= UCH(sinfo->code_buf[offs]); + + /* Right-align cur_bit in accum, then mask off desired number of bits */ + accum >>= (sinfo->cur_bit & 7); + sinfo->cur_bit += sinfo->code_size; + return accum & ((1 << sinfo->code_size) - 1); +} + + +LOCAL(int) +LZWReadByte(gif_source_ptr sinfo) +/* Read an LZW-compressed byte */ +{ + register int code; /* current working code */ + int incode; /* saves actual input code */ + + /* If any codes are stacked from a previously read symbol, return them */ + if (sinfo->sp > sinfo->symbol_stack) + return (int)(*(--sinfo->sp)); + + /* Time to read a new symbol */ + code = GetCode(sinfo); + + if (code == sinfo->clear_code) { + /* Reinit state, swallow any extra Clear codes, and */ + /* return next code, which is expected to be a raw byte. */ + ReInitLZW(sinfo); + do { + code = GetCode(sinfo); + } while (code == sinfo->clear_code); + if (code > sinfo->clear_code) { /* make sure it is a raw byte */ + WARNMS(sinfo->cinfo, JWRN_GIF_BADDATA); + code = 0; /* use something valid */ + } + /* make firstcode, oldcode valid! */ + sinfo->firstcode = sinfo->oldcode = code; + return code; + } + + if (code == sinfo->end_code) { + /* Skip the rest of the image, unless GetCode already read terminator */ + if (!sinfo->out_of_blocks) { + SkipDataBlocks(sinfo); + sinfo->out_of_blocks = TRUE; + } + /* Complain that there's not enough data */ + WARNMS(sinfo->cinfo, JWRN_GIF_ENDCODE); + /* Pad data with 0's */ + return 0; /* fake something usable */ + } + + /* Got normal raw byte or LZW symbol */ + incode = code; /* save for a moment */ + + if (code >= sinfo->max_code) { /* special case for not-yet-defined symbol */ + /* code == max_code is OK; anything bigger is bad data */ + if (code > sinfo->max_code) { + WARNMS(sinfo->cinfo, JWRN_GIF_BADDATA); + incode = 0; /* prevent creation of loops in symbol table */ + } + /* this symbol will be defined as oldcode/firstcode */ + *(sinfo->sp++) = (UINT8)sinfo->firstcode; + code = sinfo->oldcode; + } + + /* If it's a symbol, expand it into the stack */ + while (code >= sinfo->clear_code) { + *(sinfo->sp++) = sinfo->symbol_tail[code]; /* tail is a byte value */ + code = sinfo->symbol_head[code]; /* head is another LZW symbol */ + } + /* At this point code just represents a raw byte */ + sinfo->firstcode = code; /* save for possible future use */ + + /* If there's room in table... */ + if ((code = sinfo->max_code) < LZW_TABLE_SIZE) { + /* Define a new symbol = prev sym + head of this sym's expansion */ + sinfo->symbol_head[code] = (UINT16)sinfo->oldcode; + sinfo->symbol_tail[code] = (UINT8)sinfo->firstcode; + sinfo->max_code++; + /* Is it time to increase code_size? */ + if (sinfo->max_code >= sinfo->limit_code && + sinfo->code_size < MAX_LZW_BITS) { + sinfo->code_size++; + sinfo->limit_code <<= 1; /* keep equal to 2^code_size */ + } + } + + sinfo->oldcode = incode; /* save last input symbol for future use */ + return sinfo->firstcode; /* return first byte of symbol's expansion */ +} + + +LOCAL(void) +ReadColorMap(gif_source_ptr sinfo, int cmaplen, JSAMPARRAY cmap) +/* Read a GIF colormap */ +{ + int i; + + for (i = 0; i < cmaplen; i++) { +#if BITS_IN_JSAMPLE == 8 +#define UPSCALE(x) (x) +#else +#define UPSCALE(x) ((x) << (BITS_IN_JSAMPLE - 8)) +#endif + cmap[CM_RED][i] = (JSAMPLE)UPSCALE(ReadByte(sinfo)); + cmap[CM_GREEN][i] = (JSAMPLE)UPSCALE(ReadByte(sinfo)); + cmap[CM_BLUE][i] = (JSAMPLE)UPSCALE(ReadByte(sinfo)); + } +} + + +LOCAL(void) +DoExtension(gif_source_ptr sinfo) +/* Process an extension block */ +/* Currently we ignore 'em all */ +{ + int extlabel; + + /* Read extension label byte */ + extlabel = ReadByte(sinfo); + TRACEMS1(sinfo->cinfo, 1, JTRC_GIF_EXTENSION, extlabel); + /* Skip the data block(s) associated with the extension */ + SkipDataBlocks(sinfo); +} + + +/* + * Read the file header; return image size and component count. + */ + +METHODDEF(void) +start_input_gif(j_compress_ptr cinfo, cjpeg_source_ptr sinfo) +{ + gif_source_ptr source = (gif_source_ptr)sinfo; + U_CHAR hdrbuf[10]; /* workspace for reading control blocks */ + unsigned int width, height; /* image dimensions */ + int colormaplen, aspectRatio; + int c; + + /* Read and verify GIF Header */ + if (!ReadOK(source->pub.input_file, hdrbuf, 6)) + ERREXIT(cinfo, JERR_GIF_NOT); + if (hdrbuf[0] != 'G' || hdrbuf[1] != 'I' || hdrbuf[2] != 'F') + ERREXIT(cinfo, JERR_GIF_NOT); + /* Check for expected version numbers. + * If unknown version, give warning and try to process anyway; + * this is per recommendation in GIF89a standard. + */ + if ((hdrbuf[3] != '8' || hdrbuf[4] != '7' || hdrbuf[5] != 'a') && + (hdrbuf[3] != '8' || hdrbuf[4] != '9' || hdrbuf[5] != 'a')) + TRACEMS3(cinfo, 1, JTRC_GIF_BADVERSION, hdrbuf[3], hdrbuf[4], hdrbuf[5]); + + /* Read and decipher Logical Screen Descriptor */ + if (!ReadOK(source->pub.input_file, hdrbuf, 7)) + ERREXIT(cinfo, JERR_INPUT_EOF); + width = LM_to_uint(hdrbuf, 0); + height = LM_to_uint(hdrbuf, 2); + /* we ignore the color resolution, sort flag, and background color index */ + aspectRatio = UCH(hdrbuf[6]); + if (aspectRatio != 0 && aspectRatio != 49) + TRACEMS(cinfo, 1, JTRC_GIF_NONSQUARE); + + /* Allocate space to store the colormap */ + source->colormap = (*cinfo->mem->alloc_sarray) + ((j_common_ptr)cinfo, JPOOL_IMAGE, (JDIMENSION)MAXCOLORMAPSIZE, + (JDIMENSION)NUMCOLORS); + colormaplen = 0; /* indicate initialization */ + + /* Read global colormap if header indicates it is present */ + if (BitSet(hdrbuf[4], COLORMAPFLAG)) { + colormaplen = 2 << (hdrbuf[4] & 0x07); + ReadColorMap(source, colormaplen, source->colormap); + } + + /* Scan until we reach start of desired image. + * We don't currently support skipping images, but could add it easily. + */ + for (;;) { + c = ReadByte(source); + + if (c == ';') /* GIF terminator?? */ + ERREXIT(cinfo, JERR_GIF_IMAGENOTFOUND); + + if (c == '!') { /* Extension */ + DoExtension(source); + continue; + } + + if (c != ',') { /* Not an image separator? */ + WARNMS1(cinfo, JWRN_GIF_CHAR, c); + continue; + } + + /* Read and decipher Local Image Descriptor */ + if (!ReadOK(source->pub.input_file, hdrbuf, 9)) + ERREXIT(cinfo, JERR_INPUT_EOF); + /* we ignore top/left position info, also sort flag */ + width = LM_to_uint(hdrbuf, 4); + height = LM_to_uint(hdrbuf, 6); + source->is_interlaced = (BitSet(hdrbuf[8], INTERLACE) != 0); + + /* Read local colormap if header indicates it is present */ + /* Note: if we wanted to support skipping images, */ + /* we'd need to skip rather than read colormap for ignored images */ + if (BitSet(hdrbuf[8], COLORMAPFLAG)) { + colormaplen = 2 << (hdrbuf[8] & 0x07); + ReadColorMap(source, colormaplen, source->colormap); + } + + source->input_code_size = ReadByte(source); /* get min-code-size byte */ + if (source->input_code_size < 2 || source->input_code_size > 8) + ERREXIT1(cinfo, JERR_GIF_CODESIZE, source->input_code_size); + + /* Reached desired image, so break out of loop */ + /* If we wanted to skip this image, */ + /* we'd call SkipDataBlocks and then continue the loop */ + break; + } + + /* Prepare to read selected image: first initialize LZW decompressor */ + source->symbol_head = (UINT16 *) + (*cinfo->mem->alloc_large) ((j_common_ptr)cinfo, JPOOL_IMAGE, + LZW_TABLE_SIZE * sizeof(UINT16)); + source->symbol_tail = (UINT8 *) + (*cinfo->mem->alloc_large) ((j_common_ptr)cinfo, JPOOL_IMAGE, + LZW_TABLE_SIZE * sizeof(UINT8)); + source->symbol_stack = (UINT8 *) + (*cinfo->mem->alloc_large) ((j_common_ptr)cinfo, JPOOL_IMAGE, + LZW_TABLE_SIZE * sizeof(UINT8)); + InitLZWCode(source); + + /* + * If image is interlaced, we read it into a full-size sample array, + * decompressing as we go; then get_interlaced_row selects rows from the + * sample array in the proper order. + */ + if (source->is_interlaced) { + /* We request the virtual array now, but can't access it until virtual + * arrays have been allocated. Hence, the actual work of reading the + * image is postponed until the first call to get_pixel_rows. + */ + source->interlaced_image = (*cinfo->mem->request_virt_sarray) + ((j_common_ptr)cinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION)width, (JDIMENSION)height, (JDIMENSION)1); + if (cinfo->progress != NULL) { + cd_progress_ptr progress = (cd_progress_ptr)cinfo->progress; + progress->total_extra_passes++; /* count file input as separate pass */ + } + source->pub.get_pixel_rows = load_interlaced_image; + } else { + source->pub.get_pixel_rows = get_pixel_rows; + } + + /* Create compressor input buffer. */ + source->pub.buffer = (*cinfo->mem->alloc_sarray) + ((j_common_ptr)cinfo, JPOOL_IMAGE, (JDIMENSION)width * NUMCOLORS, + (JDIMENSION)1); + source->pub.buffer_height = 1; + + /* Pad colormap for safety. */ + for (c = colormaplen; c < source->clear_code; c++) { + source->colormap[CM_RED][c] = + source->colormap[CM_GREEN][c] = + source->colormap[CM_BLUE][c] = CENTERJSAMPLE; + } + + /* Return info about the image. */ + cinfo->in_color_space = JCS_RGB; + cinfo->input_components = NUMCOLORS; + cinfo->data_precision = BITS_IN_JSAMPLE; /* we always rescale data to this */ + cinfo->image_width = width; + cinfo->image_height = height; + + TRACEMS3(cinfo, 1, JTRC_GIF, width, height, colormaplen); +} + + +/* + * Read one row of pixels. + * This version is used for noninterlaced GIF images: + * we read directly from the GIF file. + */ + +METHODDEF(JDIMENSION) +get_pixel_rows(j_compress_ptr cinfo, cjpeg_source_ptr sinfo) +{ + gif_source_ptr source = (gif_source_ptr)sinfo; + register int c; + register JSAMPROW ptr; + register JDIMENSION col; + register JSAMPARRAY colormap = source->colormap; + + ptr = source->pub.buffer[0]; + for (col = cinfo->image_width; col > 0; col--) { + c = LZWReadByte(source); + *ptr++ = colormap[CM_RED][c]; + *ptr++ = colormap[CM_GREEN][c]; + *ptr++ = colormap[CM_BLUE][c]; + } + return 1; +} + + +/* + * Read one row of pixels. + * This version is used for the first call on get_pixel_rows when + * reading an interlaced GIF file: we read the whole image into memory. + */ + +METHODDEF(JDIMENSION) +load_interlaced_image(j_compress_ptr cinfo, cjpeg_source_ptr sinfo) +{ + gif_source_ptr source = (gif_source_ptr)sinfo; + register JSAMPROW sptr; + register JDIMENSION col; + JDIMENSION row; + cd_progress_ptr progress = (cd_progress_ptr)cinfo->progress; + + /* Read the interlaced image into the virtual array we've created. */ + for (row = 0; row < cinfo->image_height; row++) { + if (progress != NULL) { + progress->pub.pass_counter = (long)row; + progress->pub.pass_limit = (long)cinfo->image_height; + (*progress->pub.progress_monitor) ((j_common_ptr)cinfo); + } + sptr = *(*cinfo->mem->access_virt_sarray) + ((j_common_ptr)cinfo, source->interlaced_image, row, (JDIMENSION)1, + TRUE); + for (col = cinfo->image_width; col > 0; col--) { + *sptr++ = (JSAMPLE)LZWReadByte(source); + } + } + if (progress != NULL) + progress->completed_extra_passes++; + + /* Replace method pointer so subsequent calls don't come here. */ + source->pub.get_pixel_rows = get_interlaced_row; + /* Initialize for get_interlaced_row, and perform first call on it. */ + source->cur_row_number = 0; + source->pass2_offset = (cinfo->image_height + 7) / 8; + source->pass3_offset = source->pass2_offset + (cinfo->image_height + 3) / 8; + source->pass4_offset = source->pass3_offset + (cinfo->image_height + 1) / 4; + + return get_interlaced_row(cinfo, sinfo); +} + + +/* + * Read one row of pixels. + * This version is used for interlaced GIF images: + * we read from the virtual array. + */ + +METHODDEF(JDIMENSION) +get_interlaced_row(j_compress_ptr cinfo, cjpeg_source_ptr sinfo) +{ + gif_source_ptr source = (gif_source_ptr)sinfo; + register int c; + register JSAMPROW sptr, ptr; + register JDIMENSION col; + register JSAMPARRAY colormap = source->colormap; + JDIMENSION irow; + + /* Figure out which row of interlaced image is needed, and access it. */ + switch ((int)(source->cur_row_number & 7)) { + case 0: /* first-pass row */ + irow = source->cur_row_number >> 3; + break; + case 4: /* second-pass row */ + irow = (source->cur_row_number >> 3) + source->pass2_offset; + break; + case 2: /* third-pass row */ + case 6: + irow = (source->cur_row_number >> 2) + source->pass3_offset; + break; + default: /* fourth-pass row */ + irow = (source->cur_row_number >> 1) + source->pass4_offset; + } + sptr = *(*cinfo->mem->access_virt_sarray) + ((j_common_ptr)cinfo, source->interlaced_image, irow, (JDIMENSION)1, + FALSE); + /* Scan the row, expand colormap, and output */ + ptr = source->pub.buffer[0]; + for (col = cinfo->image_width; col > 0; col--) { + c = *sptr++; + *ptr++ = colormap[CM_RED][c]; + *ptr++ = colormap[CM_GREEN][c]; + *ptr++ = colormap[CM_BLUE][c]; + } + source->cur_row_number++; /* for next time */ + return 1; +} + + +/* + * Finish up at the end of the file. + */ + +METHODDEF(void) +finish_input_gif(j_compress_ptr cinfo, cjpeg_source_ptr sinfo) +{ + /* no work */ +} + + /* * The module selection routine for GIF format input. */ @@ -31,9 +659,18 @@ GLOBAL(cjpeg_source_ptr) jinit_read_gif(j_compress_ptr cinfo) { - fprintf(stderr, "GIF input is unsupported for legal reasons. Sorry.\n"); - exit(EXIT_FAILURE); - return NULL; /* keep compiler happy */ + gif_source_ptr source; + + /* Create module interface object */ + source = (gif_source_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE, + sizeof(gif_source_struct)); + source->cinfo = cinfo; /* make back link for subroutines */ + /* Fill in method ptrs, except get_pixel_rows which start_input sets */ + source->pub.start_input = start_input_gif; + source->pub.finish_input = finish_input_gif; + + return (cjpeg_source_ptr)source; } #endif /* GIF_SUPPORTED */ diff --git a/transupp.c b/transupp.c index 52265742..91e13d9f 100644 --- a/transupp.c +++ b/transupp.c @@ -2,7 +2,7 @@ * transupp.c * * This file was part of the Independent JPEG Group's software: - * Copyright (C) 1997-2011, Thomas G. Lane, Guido Vollbeding. + * Copyright (C) 1997-2019, Thomas G. Lane, Guido Vollbeding. * libjpeg-turbo Modifications: * Copyright (C) 2010, 2017, D. R. Commander. * For conditions of distribution and use, see the accompanying README.ijg @@ -88,6 +88,189 @@ */ +LOCAL(void) +dequant_comp(j_decompress_ptr cinfo, jpeg_component_info *compptr, + jvirt_barray_ptr coef_array, JQUANT_TBL *qtblptr1) +{ + JDIMENSION blk_x, blk_y; + int offset_y, k; + JQUANT_TBL *qtblptr; + JBLOCKARRAY buffer; + JBLOCKROW block; + JCOEFPTR ptr; + + qtblptr = compptr->quant_table; + for (blk_y = 0; blk_y < compptr->height_in_blocks; + blk_y += compptr->v_samp_factor) { + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr)cinfo, coef_array, blk_y, + (JDIMENSION)compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + block = buffer[offset_y]; + for (blk_x = 0; blk_x < compptr->width_in_blocks; blk_x++) { + ptr = block[blk_x]; + for (k = 0; k < DCTSIZE2; k++) + if (qtblptr->quantval[k] != qtblptr1->quantval[k]) + ptr[k] *= qtblptr->quantval[k] / qtblptr1->quantval[k]; + } + } + } +} + + +LOCAL(void) +requant_comp(j_decompress_ptr cinfo, jpeg_component_info *compptr, + jvirt_barray_ptr coef_array, JQUANT_TBL *qtblptr1) +{ + JDIMENSION blk_x, blk_y; + int offset_y, k; + JQUANT_TBL *qtblptr; + JBLOCKARRAY buffer; + JBLOCKROW block; + JCOEFPTR ptr; + JCOEF temp, qval; + + qtblptr = compptr->quant_table; + for (blk_y = 0; blk_y < compptr->height_in_blocks; + blk_y += compptr->v_samp_factor) { + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr)cinfo, coef_array, blk_y, + (JDIMENSION)compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + block = buffer[offset_y]; + for (blk_x = 0; blk_x < compptr->width_in_blocks; blk_x++) { + ptr = block[blk_x]; + for (k = 0; k < DCTSIZE2; k++) { + temp = qtblptr->quantval[k]; + qval = qtblptr1->quantval[k]; + if (temp != qval) { + temp *= ptr[k]; + /* The following quantization code is copied from jcdctmgr.c */ +#ifdef FAST_DIVIDE +#define DIVIDE_BY(a, b) a /= b +#else +#define DIVIDE_BY(a, b) if (a >= b) a /= b; else a = 0 +#endif + if (temp < 0) { + temp = -temp; + temp += qval >> 1; /* for rounding */ + DIVIDE_BY(temp, qval); + temp = -temp; + } else { + temp += qval >> 1; /* for rounding */ + DIVIDE_BY(temp, qval); + } + ptr[k] = temp; + } + } + } + } + } +} + + +/* + * Calculate largest common denominator using Euclid's algorithm. + */ +LOCAL(JCOEF) +largest_common_denominator(JCOEF a, JCOEF b) +{ + JCOEF c; + + do { + c = a % b; + a = b; + b = c; + } while (c); + + return a; +} + + +LOCAL(void) +adjust_quant(j_decompress_ptr srcinfo, jvirt_barray_ptr *src_coef_arrays, + j_decompress_ptr dropinfo, jvirt_barray_ptr *drop_coef_arrays, + boolean trim, j_compress_ptr dstinfo) +{ + jpeg_component_info *compptr1, *compptr2; + JQUANT_TBL *qtblptr1, *qtblptr2, *qtblptr3; + int ci, k; + + for (ci = 0; ci < dstinfo->num_components && ci < dropinfo->num_components; + ci++) { + compptr1 = srcinfo->comp_info + ci; + compptr2 = dropinfo->comp_info + ci; + qtblptr1 = compptr1->quant_table; + qtblptr2 = compptr2->quant_table; + for (k = 0; k < DCTSIZE2; k++) { + if (qtblptr1->quantval[k] != qtblptr2->quantval[k]) { + if (trim) + requant_comp(dropinfo, compptr2, drop_coef_arrays[ci], qtblptr1); + else { + qtblptr3 = dstinfo->quant_tbl_ptrs[compptr1->quant_tbl_no]; + for (k = 0; k < DCTSIZE2; k++) + if (qtblptr1->quantval[k] != qtblptr2->quantval[k]) + qtblptr3->quantval[k] = + largest_common_denominator(qtblptr1->quantval[k], + qtblptr2->quantval[k]); + dequant_comp(srcinfo, compptr1, src_coef_arrays[ci], qtblptr3); + dequant_comp(dropinfo, compptr2, drop_coef_arrays[ci], qtblptr3); + } + break; + } + } + } +} + + +LOCAL(void) +do_drop(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + j_decompress_ptr dropinfo, jvirt_barray_ptr *drop_coef_arrays, + JDIMENSION drop_width, JDIMENSION drop_height) +/* Drop (insert) the contents of another image into the source image. If the + * number of components in the drop image is smaller than the number of + * components in the destination image, then we fill in the remaining + * components with zero. This allows for dropping the contents of grayscale + * images into (arbitrarily sampled) color images. + */ +{ + JDIMENSION comp_width, comp_height; + JDIMENSION blk_y, x_drop_blocks, y_drop_blocks; + int ci, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + jpeg_component_info *compptr; + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = drop_width * compptr->h_samp_factor; + comp_height = drop_height * compptr->v_samp_factor; + x_drop_blocks = x_crop_offset * compptr->h_samp_factor; + y_drop_blocks = y_crop_offset * compptr->v_samp_factor; + for (blk_y = 0; blk_y < comp_height; blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr)srcinfo, src_coef_arrays[ci], blk_y + y_drop_blocks, + (JDIMENSION)compptr->v_samp_factor, TRUE); + if (ci < dropinfo->num_components) { + src_buffer = (*dropinfo->mem->access_virt_barray) + ((j_common_ptr)dropinfo, drop_coef_arrays[ci], blk_y, + (JDIMENSION)compptr->v_samp_factor, FALSE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + jcopy_block_row(src_buffer[offset_y], + dst_buffer[offset_y] + x_drop_blocks, comp_width); + } + } else { + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + MEMZERO(dst_buffer[offset_y] + x_drop_blocks, + comp_width * sizeof(JBLOCK)); + } + } + } + } +} + + LOCAL(void) do_crop(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, @@ -113,13 +296,422 @@ do_crop(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, ((j_common_ptr)srcinfo, dst_coef_arrays[ci], dst_blk_y, (JDIMENSION)compptr->v_samp_factor, TRUE); src_buffer = (*srcinfo->mem->access_virt_barray) - ((j_common_ptr)srcinfo, src_coef_arrays[ci], - dst_blk_y + y_crop_blocks, + ((j_common_ptr)srcinfo, src_coef_arrays[ci], dst_blk_y + y_crop_blocks, (JDIMENSION)compptr->v_samp_factor, FALSE); for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { jcopy_block_row(src_buffer[offset_y] + x_crop_blocks, - dst_buffer[offset_y], - compptr->width_in_blocks); + dst_buffer[offset_y], compptr->width_in_blocks); + } + } + } +} + + +LOCAL(void) +do_crop_ext_zero(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Crop. This is only used when no rotate/flip is requested with the crop. + * Extension: If the destination size is larger than the source, we fill in the + * expanded region with zero (neutral gray). Note that we also have to zero + * partial iMCUs at the right and bottom edge of the source image area in this + * case. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height; + JDIMENSION dst_blk_y, x_crop_blocks, y_crop_blocks; + int ci, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + jpeg_component_info *compptr; + + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size); + MCU_rows = srcinfo->output_height / + (dstinfo->max_v_samp_factor * dstinfo_min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr)srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION)compptr->v_samp_factor, TRUE); + if (dstinfo->_jpeg_height > srcinfo->output_height) { + if (dst_blk_y < y_crop_blocks || + dst_blk_y >= y_crop_blocks + comp_height) { + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + MEMZERO(dst_buffer[offset_y], + compptr->width_in_blocks * sizeof(JBLOCK)); + } + continue; + } + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr)srcinfo, src_coef_arrays[ci], + dst_blk_y - y_crop_blocks, (JDIMENSION)compptr->v_samp_factor, + FALSE); + } else { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr)srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, (JDIMENSION)compptr->v_samp_factor, + FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (dstinfo->_jpeg_width > srcinfo->output_width) { + if (x_crop_blocks > 0) { + MEMZERO(dst_buffer[offset_y], x_crop_blocks * sizeof(JBLOCK)); + } + jcopy_block_row(src_buffer[offset_y], + dst_buffer[offset_y] + x_crop_blocks, comp_width); + if (compptr->width_in_blocks > x_crop_blocks + comp_width) { + MEMZERO(dst_buffer[offset_y] + x_crop_blocks + comp_width, + (compptr->width_in_blocks - x_crop_blocks - comp_width) * + sizeof(JBLOCK)); + } + } else { + jcopy_block_row(src_buffer[offset_y] + x_crop_blocks, + dst_buffer[offset_y], compptr->width_in_blocks); + } + } + } + } +} + + +LOCAL(void) +do_crop_ext_flat(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Crop. This is only used when no rotate/flip is requested with the crop. + * Extension: The destination width is larger than the source, and we fill in + * the expanded region with the DC coefficient of the adjacent block. Note + * that we also have to fill partial iMCUs at the right and bottom edge of the + * source image area in this case. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height; + JDIMENSION dst_blk_x, dst_blk_y, x_crop_blocks, y_crop_blocks; + int ci, offset_y; + JCOEF dc; + JBLOCKARRAY src_buffer, dst_buffer; + jpeg_component_info *compptr; + + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size); + MCU_rows = srcinfo->output_height / + (dstinfo->max_v_samp_factor * dstinfo_min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr)srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION)compptr->v_samp_factor, TRUE); + if (dstinfo->_jpeg_height > srcinfo->output_height) { + if (dst_blk_y < y_crop_blocks || + dst_blk_y >= y_crop_blocks + comp_height) { + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + MEMZERO(dst_buffer[offset_y], + compptr->width_in_blocks * sizeof(JBLOCK)); + } + continue; + } + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr)srcinfo, src_coef_arrays[ci], + dst_blk_y - y_crop_blocks, (JDIMENSION)compptr->v_samp_factor, + FALSE); + } else { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr)srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, (JDIMENSION)compptr->v_samp_factor, + FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (x_crop_blocks > 0) { + MEMZERO(dst_buffer[offset_y], x_crop_blocks * sizeof(JBLOCK)); + dc = src_buffer[offset_y][0][0]; + for (dst_blk_x = 0; dst_blk_x < x_crop_blocks; dst_blk_x++) { + dst_buffer[offset_y][dst_blk_x][0] = dc; + } + } + jcopy_block_row(src_buffer[offset_y], + dst_buffer[offset_y] + x_crop_blocks, comp_width); + if (compptr->width_in_blocks > x_crop_blocks + comp_width) { + MEMZERO(dst_buffer[offset_y] + x_crop_blocks + comp_width, + (compptr->width_in_blocks - x_crop_blocks - comp_width) * + sizeof(JBLOCK)); + dc = src_buffer[offset_y][comp_width - 1][0]; + for (dst_blk_x = x_crop_blocks + comp_width; + dst_blk_x < compptr->width_in_blocks; dst_blk_x++) { + dst_buffer[offset_y][dst_blk_x][0] = dc; + } + } + } + } + } +} + + +LOCAL(void) +do_crop_ext_reflect(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + jvirt_barray_ptr *dst_coef_arrays) +/* Crop. This is only used when no rotate/flip is requested with the crop. + * Extension: The destination width is larger than the source, and we fill in + * the expanded region with repeated reflections of the source image. Note + * that we also have to fill partial iMCUs at the right and bottom edge of the + * source image area in this case. + */ +{ + JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, src_blk_x; + JDIMENSION dst_blk_x, dst_blk_y, x_crop_blocks, y_crop_blocks; + int ci, k, offset_y; + JBLOCKARRAY src_buffer, dst_buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + MCU_cols = srcinfo->output_width / + (dstinfo->max_h_samp_factor * dstinfo_min_DCT_h_scaled_size); + MCU_rows = srcinfo->output_height / + (dstinfo->max_v_samp_factor * dstinfo_min_DCT_v_scaled_size); + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + comp_width = MCU_cols * compptr->h_samp_factor; + comp_height = MCU_rows * compptr->v_samp_factor; + x_crop_blocks = x_crop_offset * compptr->h_samp_factor; + y_crop_blocks = y_crop_offset * compptr->v_samp_factor; + for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks; + dst_blk_y += compptr->v_samp_factor) { + dst_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr)srcinfo, dst_coef_arrays[ci], dst_blk_y, + (JDIMENSION)compptr->v_samp_factor, TRUE); + if (dstinfo->_jpeg_height > srcinfo->output_height) { + if (dst_blk_y < y_crop_blocks || + dst_blk_y >= y_crop_blocks + comp_height) { + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + MEMZERO(dst_buffer[offset_y], + compptr->width_in_blocks * sizeof(JBLOCK)); + } + continue; + } + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr)srcinfo, src_coef_arrays[ci], + dst_blk_y - y_crop_blocks, (JDIMENSION)compptr->v_samp_factor, + FALSE); + } else { + src_buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr)srcinfo, src_coef_arrays[ci], + dst_blk_y + y_crop_blocks, (JDIMENSION)compptr->v_samp_factor, + FALSE); + } + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + /* Copy source region */ + jcopy_block_row(src_buffer[offset_y], + dst_buffer[offset_y] + x_crop_blocks, comp_width); + if (x_crop_blocks > 0) { + /* Reflect to left */ + dst_row_ptr = dst_buffer[offset_y] + x_crop_blocks; + for (dst_blk_x = x_crop_blocks; dst_blk_x > 0;) { + src_row_ptr = dst_row_ptr; /* (re)set axis of reflection */ + for (src_blk_x = comp_width; src_blk_x > 0 && dst_blk_x > 0; + src_blk_x--, dst_blk_x--) { + dst_ptr = *(--dst_row_ptr); /* destination goes left */ + src_ptr = *src_row_ptr++; /* source goes right */ + /* This unrolled loop doesn't need to know which row it's on. */ + for (k = 0; k < DCTSIZE2; k += 2) { + *dst_ptr++ = *src_ptr++; /* copy even column */ + *dst_ptr++ = -(*src_ptr++); /* copy odd column with sign + change */ + } + } + } + } + if (compptr->width_in_blocks > x_crop_blocks + comp_width) { + /* Reflect to right */ + dst_row_ptr = dst_buffer[offset_y] + x_crop_blocks + comp_width; + for (dst_blk_x = compptr->width_in_blocks - x_crop_blocks - comp_width; + dst_blk_x > 0;) { + src_row_ptr = dst_row_ptr; /* (re)set axis of reflection */ + for (src_blk_x = comp_width; src_blk_x > 0 && dst_blk_x > 0; + src_blk_x--, dst_blk_x--) { + dst_ptr = *dst_row_ptr++; /* destination goes right */ + src_ptr = *(--src_row_ptr); /* source goes left */ + /* This unrolled loop doesn't need to know which row it's on. */ + for (k = 0; k < DCTSIZE2; k += 2) { + *dst_ptr++ = *src_ptr++; /* copy even column */ + *dst_ptr++ = -(*src_ptr++); /* copy odd column with sign + change */ + } + } + } + } + } + } + } +} + + +LOCAL(void) +do_wipe(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + JDIMENSION drop_width, JDIMENSION drop_height) +/* Wipe - discard image contents of specified region and fill with zero + * (neutral gray) + */ +{ + JDIMENSION x_wipe_blocks, wipe_width; + JDIMENSION y_wipe_blocks, wipe_bottom; + int ci, offset_y; + JBLOCKARRAY buffer; + jpeg_component_info *compptr; + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + x_wipe_blocks = x_crop_offset * compptr->h_samp_factor; + wipe_width = drop_width * compptr->h_samp_factor; + y_wipe_blocks = y_crop_offset * compptr->v_samp_factor; + wipe_bottom = drop_height * compptr->v_samp_factor + y_wipe_blocks; + for (; y_wipe_blocks < wipe_bottom; + y_wipe_blocks += compptr->v_samp_factor) { + buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr)srcinfo, src_coef_arrays[ci], y_wipe_blocks, + (JDIMENSION)compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + MEMZERO(buffer[offset_y] + x_wipe_blocks, wipe_width * sizeof(JBLOCK)); + } + } + } +} + + +LOCAL(void) +do_flatten(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, JDIMENSION y_crop_offset, + jvirt_barray_ptr *src_coef_arrays, + JDIMENSION drop_width, JDIMENSION drop_height) +/* Flatten - discard image contents of specified region, similarly to wipe, + * but fill with the average of adjacent blocks instead of zero. + */ +{ + JDIMENSION x_wipe_blocks, wipe_width, wipe_right; + JDIMENSION y_wipe_blocks, wipe_bottom, blk_x; + int ci, offset_y, dc_left_value, dc_right_value, average; + JBLOCKARRAY buffer; + jpeg_component_info *compptr; + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + x_wipe_blocks = x_crop_offset * compptr->h_samp_factor; + wipe_width = drop_width * compptr->h_samp_factor; + wipe_right = wipe_width + x_wipe_blocks; + y_wipe_blocks = y_crop_offset * compptr->v_samp_factor; + wipe_bottom = drop_height * compptr->v_samp_factor + y_wipe_blocks; + for (; y_wipe_blocks < wipe_bottom; + y_wipe_blocks += compptr->v_samp_factor) { + buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr)srcinfo, src_coef_arrays[ci], y_wipe_blocks, + (JDIMENSION)compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + MEMZERO(buffer[offset_y] + x_wipe_blocks, wipe_width * sizeof(JBLOCK)); + if (x_wipe_blocks > 0) { + dc_left_value = buffer[offset_y][x_wipe_blocks - 1][0]; + if (wipe_right < compptr->width_in_blocks) { + dc_right_value = buffer[offset_y][wipe_right][0]; + average = (dc_left_value + dc_right_value) >> 1; + } else { + average = dc_left_value; + } + } else if (wipe_right < compptr->width_in_blocks) { + average = buffer[offset_y][wipe_right][0]; + } else continue; + for (blk_x = x_wipe_blocks; blk_x < wipe_right; blk_x++) { + buffer[offset_y][blk_x][0] = (JCOEF)average; + } + } + } + } +} + + +LOCAL(void) +do_reflect(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, + JDIMENSION x_crop_offset, jvirt_barray_ptr *src_coef_arrays, + JDIMENSION drop_width, JDIMENSION drop_height) +/* Reflect - discard image contents of specified region, similarly to wipe, + * but fill with repeated reflections of the outside region instead of zero. + * NB: y_crop_offset is assumed to be zero. + */ +{ + JDIMENSION x_wipe_blocks, wipe_width; + JDIMENSION y_wipe_blocks, wipe_bottom; + JDIMENSION src_blk_x, dst_blk_x; + int ci, k, offset_y; + JBLOCKARRAY buffer; + JBLOCKROW src_row_ptr, dst_row_ptr; + JCOEFPTR src_ptr, dst_ptr; + jpeg_component_info *compptr; + + for (ci = 0; ci < dstinfo->num_components; ci++) { + compptr = dstinfo->comp_info + ci; + x_wipe_blocks = x_crop_offset * compptr->h_samp_factor; + wipe_width = drop_width * compptr->h_samp_factor; + wipe_bottom = drop_height * compptr->v_samp_factor; + for (y_wipe_blocks = 0; y_wipe_blocks < wipe_bottom; + y_wipe_blocks += compptr->v_samp_factor) { + buffer = (*srcinfo->mem->access_virt_barray) + ((j_common_ptr)srcinfo, src_coef_arrays[ci], y_wipe_blocks, + (JDIMENSION)compptr->v_samp_factor, TRUE); + for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) { + if (x_wipe_blocks > 0) { + /* Reflect from left */ + dst_row_ptr = buffer[offset_y] + x_wipe_blocks; + for (dst_blk_x = wipe_width; dst_blk_x > 0;) { + src_row_ptr = dst_row_ptr; /* (re)set axis of reflection */ + for (src_blk_x = x_wipe_blocks; + src_blk_x > 0 && dst_blk_x > 0; src_blk_x--, dst_blk_x--) { + dst_ptr = *dst_row_ptr++; /* destination goes right */ + src_ptr = *(--src_row_ptr); /* source goes left */ + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + *dst_ptr++ = *src_ptr++; /* copy even column */ + *dst_ptr++ = -(*src_ptr++); /* copy odd column with sign change */ + } + } + } + } else if (compptr->width_in_blocks > x_wipe_blocks + wipe_width) { + /* Reflect from right */ + dst_row_ptr = buffer[offset_y] + x_wipe_blocks + wipe_width; + for (dst_blk_x = wipe_width; dst_blk_x > 0;) { + src_row_ptr = dst_row_ptr; /* (re)set axis of reflection */ + src_blk_x = compptr->width_in_blocks - x_wipe_blocks - wipe_width; + for (; src_blk_x > 0 && dst_blk_x > 0; src_blk_x--, dst_blk_x--) { + dst_ptr = *(--dst_row_ptr); /* destination goes left */ + src_ptr = *src_row_ptr++; /* source goes right */ + /* this unrolled loop doesn't need to know which row it's on... */ + for (k = 0; k < DCTSIZE2; k += 2) { + *dst_ptr++ = *src_ptr++; /* copy even column */ + *dst_ptr++ = -(*src_ptr++); /* copy odd column with sign change */ + } + } + } + } else { + MEMZERO(buffer[offset_y] + x_wipe_blocks, + wipe_width * sizeof(JBLOCK)); + } } } } @@ -239,7 +831,7 @@ do_flip_h(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, /* this unrolled loop doesn't need to know which row it's on... */ for (k = 0; k < DCTSIZE2; k += 2) { *dst_ptr++ = *src_ptr++; /* copy even column */ - *dst_ptr++ = - *src_ptr++; /* copy odd column with sign change */ + *dst_ptr++ = -(*src_ptr++); /* copy odd column with sign change */ } } else { /* Copy last partial block(s) verbatim */ @@ -318,7 +910,7 @@ do_flip_v(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, *dst_ptr++ = *src_ptr++; /* copy odd row with sign change */ for (j = 0; j < DCTSIZE; j++) - *dst_ptr++ = - *src_ptr++; + *dst_ptr++ = -(*src_ptr++); } } } else { @@ -599,11 +1191,11 @@ do_rot_180(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, /* For even row, negate every odd column. */ for (j = 0; j < DCTSIZE; j += 2) { *dst_ptr++ = *src_ptr++; - *dst_ptr++ = - *src_ptr++; + *dst_ptr++ = -(*src_ptr++); } /* For odd row, negate every even column. */ for (j = 0; j < DCTSIZE; j += 2) { - *dst_ptr++ = - *src_ptr++; + *dst_ptr++ = -(*src_ptr++); *dst_ptr++ = *src_ptr++; } } @@ -614,7 +1206,7 @@ do_rot_180(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, for (j = 0; j < DCTSIZE; j++) *dst_ptr++ = *src_ptr++; for (j = 0; j < DCTSIZE; j++) - *dst_ptr++ = - *src_ptr++; + *dst_ptr++ = -(*src_ptr++); } } } @@ -630,7 +1222,7 @@ do_rot_180(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, src_row_ptr[comp_width - x_crop_blocks - dst_blk_x - 1]; for (i = 0; i < DCTSIZE2; i += 2) { *dst_ptr++ = *src_ptr++; - *dst_ptr++ = - *src_ptr++; + *dst_ptr++ = -(*src_ptr++); } } else { /* Any remaining right-edge blocks are only copied. */ @@ -786,7 +1378,7 @@ jt_read_integer(const char **strptr, JDIMENSION *result) * The routine returns TRUE if the spec string is valid, FALSE if not. * * The crop spec string should have the format - * [f]x[f]{+-}{+-} + * [{fr}]x[{fr}]{+-}{+-} * where width, height, xoffset, and yoffset are unsigned integers. * Each of the elements can be omitted to indicate a default value. * (A weakness of this style is that it is not possible to omit xoffset @@ -811,6 +1403,9 @@ jtransform_parse_crop_spec(jpeg_transform_info *info, const char *spec) if (*spec == 'f' || *spec == 'F') { spec++; info->crop_width_set = JCROP_FORCE; + } else if (*spec == 'r' || *spec == 'R') { + spec++; + info->crop_width_set = JCROP_REFLECT; } else info->crop_width_set = JCROP_POS; } @@ -822,6 +1417,9 @@ jtransform_parse_crop_spec(jpeg_transform_info *info, const char *spec) if (*spec == 'f' || *spec == 'F') { spec++; info->crop_height_set = JCROP_FORCE; + } else if (*spec == 'r' || *spec == 'R') { + spec++; + info->crop_height_set = JCROP_REFLECT; } else info->crop_height_set = JCROP_POS; } @@ -896,10 +1494,10 @@ jtransform_request_workspace(j_decompress_ptr srcinfo, jvirt_barray_ptr *coef_arrays; boolean need_workspace, transpose_it; jpeg_component_info *compptr; - JDIMENSION xoffset, yoffset; + JDIMENSION xoffset, yoffset, dtemp; JDIMENSION width_in_iMCUs, height_in_iMCUs; JDIMENSION width_in_blocks, height_in_blocks; - int ci, h_samp_factor, v_samp_factor; + int itemp, ci, h_samp_factor, v_samp_factor; /* Determine number of components in output image */ if (info->force_grayscale && @@ -985,39 +1583,129 @@ jtransform_request_workspace(j_decompress_ptr srcinfo, info->crop_xoffset = 0; /* default to +0 */ if (info->crop_yoffset_set == JCROP_UNSET) info->crop_yoffset = 0; /* default to +0 */ - if (info->crop_xoffset >= info->output_width || - info->crop_yoffset >= info->output_height) - ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); - if (info->crop_width_set == JCROP_UNSET) + if (info->crop_width_set == JCROP_UNSET) { + if (info->crop_xoffset >= info->output_width) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); info->crop_width = info->output_width - info->crop_xoffset; - if (info->crop_height_set == JCROP_UNSET) + } else { + /* Check for crop extension */ + if (info->crop_width > info->output_width) { + /* Crop extension does not work when transforming! */ + if (info->transform != JXFORM_NONE || + info->crop_xoffset >= info->crop_width || + info->crop_xoffset > info->crop_width - info->output_width) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + } else { + if (info->crop_xoffset >= info->output_width || + info->crop_width <= 0 || + info->crop_xoffset > info->output_width - info->crop_width) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + } + } + if (info->crop_height_set == JCROP_UNSET) { + if (info->crop_yoffset >= info->output_height) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); info->crop_height = info->output_height - info->crop_yoffset; - /* Ensure parameters are valid */ - if (info->crop_width <= 0 || info->crop_width > info->output_width || - info->crop_height <= 0 || info->crop_height > info->output_height || - info->crop_xoffset > info->output_width - info->crop_width || - info->crop_yoffset > info->output_height - info->crop_height) - ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + } else { + /* Check for crop extension */ + if (info->crop_height > info->output_height) { + /* Crop extension does not work when transforming! */ + if (info->transform != JXFORM_NONE || + info->crop_yoffset >= info->crop_height || + info->crop_yoffset > info->crop_height - info->output_height) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + } else { + if (info->crop_yoffset >= info->output_height || + info->crop_height <= 0 || + info->crop_yoffset > info->output_height - info->crop_height) + ERREXIT(srcinfo, JERR_BAD_CROP_SPEC); + } + } /* Convert negative crop offsets into regular offsets */ - if (info->crop_xoffset_set == JCROP_NEG) - xoffset = info->output_width - info->crop_width - info->crop_xoffset; - else + if (info->crop_xoffset_set != JCROP_NEG) xoffset = info->crop_xoffset; - if (info->crop_yoffset_set == JCROP_NEG) - yoffset = info->output_height - info->crop_height - info->crop_yoffset; + else if (info->crop_width > info->output_width) /* crop extension */ + xoffset = info->crop_width - info->output_width - info->crop_xoffset; else + xoffset = info->output_width - info->crop_width - info->crop_xoffset; + if (info->crop_yoffset_set != JCROP_NEG) yoffset = info->crop_yoffset; + else if (info->crop_height > info->output_height) /* crop extension */ + yoffset = info->crop_height - info->output_height - info->crop_yoffset; + else + yoffset = info->output_height - info->crop_height - info->crop_yoffset; /* Now adjust so that upper left corner falls at an iMCU boundary */ - if (info->crop_width_set == JCROP_FORCE) - info->output_width = info->crop_width; - else - info->output_width = - info->crop_width + (xoffset % info->iMCU_sample_width); - if (info->crop_height_set == JCROP_FORCE) - info->output_height = info->crop_height; - else - info->output_height = - info->crop_height + (yoffset % info->iMCU_sample_height); + switch (info->transform) { + case JXFORM_DROP: + /* Ensure the effective drop region will not exceed the requested */ + itemp = info->iMCU_sample_width; + dtemp = itemp - 1 - ((xoffset + itemp - 1) % itemp); + xoffset += dtemp; + if (info->crop_width <= dtemp) + info->drop_width = 0; + else if (xoffset + info->crop_width - dtemp == info->output_width) + /* Matching right edge: include partial iMCU */ + info->drop_width = (info->crop_width - dtemp + itemp - 1) / itemp; + else + info->drop_width = (info->crop_width - dtemp) / itemp; + itemp = info->iMCU_sample_height; + dtemp = itemp - 1 - ((yoffset + itemp - 1) % itemp); + yoffset += dtemp; + if (info->crop_height <= dtemp) + info->drop_height = 0; + else if (yoffset + info->crop_height - dtemp == info->output_height) + /* Matching bottom edge: include partial iMCU */ + info->drop_height = (info->crop_height - dtemp + itemp - 1) / itemp; + else + info->drop_height = (info->crop_height - dtemp) / itemp; + /* Check if sampling factors match for dropping */ + if (info->drop_width != 0 && info->drop_height != 0) + for (ci = 0; ci < info->num_components && + ci < info->drop_ptr->num_components; ci++) { + if (info->drop_ptr->comp_info[ci].h_samp_factor * + srcinfo->max_h_samp_factor != + srcinfo->comp_info[ci].h_samp_factor * + info->drop_ptr->max_h_samp_factor) + ERREXIT6(srcinfo, JERR_BAD_DROP_SAMPLING, ci, + info->drop_ptr->comp_info[ci].h_samp_factor, + info->drop_ptr->max_h_samp_factor, + srcinfo->comp_info[ci].h_samp_factor, + srcinfo->max_h_samp_factor, 'h'); + if (info->drop_ptr->comp_info[ci].v_samp_factor * + srcinfo->max_v_samp_factor != + srcinfo->comp_info[ci].v_samp_factor * + info->drop_ptr->max_v_samp_factor) + ERREXIT6(srcinfo, JERR_BAD_DROP_SAMPLING, ci, + info->drop_ptr->comp_info[ci].v_samp_factor, + info->drop_ptr->max_v_samp_factor, + srcinfo->comp_info[ci].v_samp_factor, + srcinfo->max_v_samp_factor, 'v'); + } + break; + case JXFORM_WIPE: + /* Ensure the effective wipe region will cover the requested */ + info->drop_width = (JDIMENSION)jdiv_round_up + ((long)(info->crop_width + (xoffset % info->iMCU_sample_width)), + (long)info->iMCU_sample_width); + info->drop_height = (JDIMENSION)jdiv_round_up + ((long)(info->crop_height + (yoffset % info->iMCU_sample_height)), + (long)info->iMCU_sample_height); + break; + default: + /* Ensure the effective crop region will cover the requested */ + if (info->crop_width_set == JCROP_FORCE || + info->crop_width > info->output_width) + info->output_width = info->crop_width; + else + info->output_width = + info->crop_width + (xoffset % info->iMCU_sample_width); + if (info->crop_height_set == JCROP_FORCE || + info->crop_height > info->output_height) + info->output_height = info->crop_height; + else + info->output_height = + info->crop_height + (yoffset % info->iMCU_sample_height); + } /* Save x/y offsets measured in iMCUs */ info->x_crop_offset = xoffset / info->iMCU_sample_width; info->y_crop_offset = yoffset / info->iMCU_sample_height; @@ -1033,7 +1721,9 @@ jtransform_request_workspace(j_decompress_ptr srcinfo, transpose_it = FALSE; switch (info->transform) { case JXFORM_NONE: - if (info->x_crop_offset != 0 || info->y_crop_offset != 0) + if (info->x_crop_offset != 0 || info->y_crop_offset != 0 || + info->output_width > srcinfo->output_width || + info->output_height > srcinfo->output_height) need_workspace = TRUE; /* No workspace needed if neither cropping nor transforming */ break; @@ -1087,6 +1777,10 @@ jtransform_request_workspace(j_decompress_ptr srcinfo, need_workspace = TRUE; transpose_it = TRUE; break; + case JXFORM_WIPE: + break; + case JXFORM_DROP: + break; } /* Allocate workspace if needed. @@ -1387,7 +2081,7 @@ jtransform_adjust_parameters(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, dstinfo->jpeg_height = info->output_height; #endif - /* Transpose destination image parameters */ + /* Transpose destination image parameters, adjust quantization */ switch (info->transform) { case JXFORM_TRANSPOSE: case JXFORM_TRANSVERSE: @@ -1399,6 +2093,12 @@ jtransform_adjust_parameters(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, #endif transpose_critical_parameters(dstinfo); break; + case JXFORM_DROP: + if (info->drop_width != 0 && info->drop_height != 0) + adjust_quant(srcinfo, src_coef_arrays, + info->drop_ptr, info->drop_coef_arrays, + info->trim, dstinfo); + break; default: #if JPEG_LIB_VERSION < 80 dstinfo->image_width = info->output_width; @@ -1465,7 +2165,23 @@ jtransform_execute_transform(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, */ switch (info->transform) { case JXFORM_NONE: - if (info->x_crop_offset != 0 || info->y_crop_offset != 0) + if (info->output_width > srcinfo->output_width || + info->output_height > srcinfo->output_height) { + if (info->output_width > srcinfo->output_width && + info->crop_width_set == JCROP_REFLECT) + do_crop_ext_reflect(srcinfo, dstinfo, + info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + else if (info->output_width > srcinfo->output_width && + info->crop_width_set == JCROP_FORCE) + do_crop_ext_flat(srcinfo, dstinfo, + info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + else + do_crop_ext_zero(srcinfo, dstinfo, + info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, dst_coef_arrays); + } else if (info->x_crop_offset != 0 || info->y_crop_offset != 0) do_crop(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, src_coef_arrays, dst_coef_arrays); break; @@ -1501,6 +2217,30 @@ jtransform_execute_transform(j_decompress_ptr srcinfo, j_compress_ptr dstinfo, do_rot_270(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, src_coef_arrays, dst_coef_arrays); break; + case JXFORM_WIPE: + if (info->crop_width_set == JCROP_REFLECT && + info->y_crop_offset == 0 && info->drop_height == + (JDIMENSION)jdiv_round_up + ((long)info->output_height, (long)info->iMCU_sample_height) && + (info->x_crop_offset == 0 || + info->x_crop_offset + info->drop_width == + (JDIMENSION)jdiv_round_up + ((long)info->output_width, (long)info->iMCU_sample_width))) + do_reflect(srcinfo, dstinfo, info->x_crop_offset, + src_coef_arrays, info->drop_width, info->drop_height); + else if (info->crop_width_set == JCROP_FORCE) + do_flatten(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, info->drop_width, info->drop_height); + else + do_wipe(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, info->drop_width, info->drop_height); + break; + case JXFORM_DROP: + if (info->drop_width != 0 && info->drop_height != 0) + do_drop(srcinfo, dstinfo, info->x_crop_offset, info->y_crop_offset, + src_coef_arrays, info->drop_ptr, info->drop_coef_arrays, + info->drop_width, info->drop_height); + break; } } diff --git a/transupp.h b/transupp.h index 80264cc2..ea6be1fc 100644 --- a/transupp.h +++ b/transupp.h @@ -2,7 +2,7 @@ * transupp.h * * This file was part of the Independent JPEG Group's software: - * Copyright (C) 1997-2011, Thomas G. Lane, Guido Vollbeding. + * Copyright (C) 1997-2019, Thomas G. Lane, Guido Vollbeding. * libjpeg-turbo Modifications: * Copyright (C) 2017, D. R. Commander. * For conditions of distribution and use, see the accompanying README.ijg @@ -62,6 +62,17 @@ * output image covers at least the requested region, but may cover more.) * The adjustment of the region dimensions may be optionally disabled. * + * A complementary lossless wipe option is provided to discard (gray out) data + * inside a given image region while losslessly preserving what is outside. + * A lossless drop option is also provided, which allows another JPEG image to + * be inserted ("dropped") into the source image data at a given position, + * replacing the existing image data at that position. Both the source image + * and the drop image must have the same subsampling level. It is best if they + * also have the same quantization (quality.) Otherwise, the quantization of + * the output image will be adapted to accommodate the higher of the source + * image quality and the drop image quality. The trim option can be used with + * the drop option to requantize the drop image to match the source image. + * * We also provide a lossless-resize option, which is kind of a lossless-crop * operation in the DCT coefficient block domain - it discards higher-order * coefficients and losslessly preserves lower-order coefficients of a @@ -92,20 +103,23 @@ typedef enum { JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */ JXFORM_ROT_90, /* 90-degree clockwise rotation */ JXFORM_ROT_180, /* 180-degree rotation */ - JXFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) */ + JXFORM_ROT_270, /* 270-degree clockwise (or 90 ccw) */ + JXFORM_WIPE, /* wipe */ + JXFORM_DROP /* drop */ } JXFORM_CODE; /* * Codes for crop parameters, which can individually be unspecified, * positive or negative for xoffset or yoffset, - * positive or forced for width or height. + * positive or force or reflect for width or height. */ typedef enum { JCROP_UNSET, JCROP_POS, JCROP_NEG, - JCROP_FORCE + JCROP_FORCE, + JCROP_REFLECT } JCROP_CODE; /* @@ -120,7 +134,7 @@ typedef struct { boolean perfect; /* if TRUE, fail if partial MCUs are requested */ boolean trim; /* if TRUE, trim partial MCUs as needed */ boolean force_grayscale; /* if TRUE, convert color image to grayscale */ - boolean crop; /* if TRUE, crop source image */ + boolean crop; /* if TRUE, crop or wipe source image, or drop */ boolean slow_hflip; /* For best performance, the JXFORM_FLIP_H transform normally modifies the source coefficients in place. Setting this to TRUE will instead use a slower, @@ -133,14 +147,18 @@ typedef struct { * These can be filled in by jtransform_parse_crop_spec(). */ JDIMENSION crop_width; /* Width of selected region */ - JCROP_CODE crop_width_set; /* (forced disables adjustment) */ + JCROP_CODE crop_width_set; /* (force-disables adjustment) */ JDIMENSION crop_height; /* Height of selected region */ - JCROP_CODE crop_height_set; /* (forced disables adjustment) */ + JCROP_CODE crop_height_set; /* (force-disables adjustment) */ JDIMENSION crop_xoffset; /* X offset of selected region */ JCROP_CODE crop_xoffset_set; /* (negative measures from right edge) */ JDIMENSION crop_yoffset; /* Y offset of selected region */ JCROP_CODE crop_yoffset_set; /* (negative measures from bottom edge) */ + /* Drop parameters: set by caller for drop request */ + j_decompress_ptr drop_ptr; + jvirt_barray_ptr *drop_coef_arrays; + /* Internal workspace: caller should not touch these */ int num_components; /* # of components in workspace */ jvirt_barray_ptr *workspace_coef_arrays; /* workspace for transformations */ @@ -148,6 +166,8 @@ typedef struct { JDIMENSION output_height; JDIMENSION x_crop_offset; /* destination crop offsets measured in iMCUs */ JDIMENSION y_crop_offset; + JDIMENSION drop_width; /* drop/wipe dimensions measured in iMCUs */ + JDIMENSION drop_height; int iMCU_sample_width; /* destination iMCU size */ int iMCU_sample_height; } jpeg_transform_info; diff --git a/usage.txt b/usage.txt index 03353347..1d9efcd7 100644 --- a/usage.txt +++ b/usage.txt @@ -50,9 +50,9 @@ or This syntax works on all systems, so it is useful for scripts. The currently supported image file formats are: PPM (PBMPLUS color format), -PGM (PBMPLUS grayscale format), BMP, and Targa. cjpeg recognizes the input -image format automatically, with the exception of some Targa files. You have -to tell djpeg which format to generate. +PGM (PBMPLUS grayscale format), BMP, GIF, and Targa. cjpeg recognizes the +input image format automatically, with the exception of some Targa files. You +have to tell djpeg which format to generate. JPEG files are in the defacto standard JFIF file format. There are other, less widely used JPEG-based file formats, but we don't support them. @@ -74,10 +74,10 @@ The basic command line switches for cjpeg are: -grayscale Create monochrome JPEG file from color input. Be sure to use this switch when compressing a grayscale - BMP file, because cjpeg isn't bright enough to notice - whether a BMP file uses only shades of gray. By - saying -grayscale, you'll get a smaller JPEG file that - takes less time to process. + BMP or GIF file, because cjpeg isn't bright enough to + notice whether a BMP or GIF file uses only shades of + gray. By saying -grayscale, you'll get a smaller JPEG + file that takes less time to process. -rgb Create RGB JPEG file. Using this switch suppresses the conversion from RGB @@ -288,10 +288,17 @@ The basic command line switches for djpeg are: is specified, or if the JPEG file is grayscale; otherwise, 24-bit full-color format is emitted. - -gif Select GIF output format. Since GIF does not support - more than 256 colors, -colors 256 is assumed (unless - you specify a smaller number of colors). If you - specify -fast, the default number of colors is 216. + -gif Select GIF output format (LZW-compressed). Since GIF + does not support more than 256 colors, -colors 256 is + assumed (unless you specify a smaller number of + colors). If you specify -fast, the default number of + colors is 216. + + -gif0 Select GIF output format (uncompressed). Since GIF + does not support more than 256 colors, -colors 256 is + assumed (unless you specify a smaller number of + colors). If you specify -fast, the default number of + colors is 216. -os2 Select BMP output format (OS/2 1.x flavor). 8-bit colormapped format is emitted if -colors or -grayscale @@ -400,11 +407,6 @@ quality settings to make very small JPEG files; the percentage improvement is often a lot more than it is on larger files. (At present, -optimize mode is always selected when generating progressive JPEG files.) -Support for GIF input files was removed in cjpeg v6b due to concerns over -the Unisys LZW patent. Although this patent expired in 2006, cjpeg still -lacks GIF support, for these historical reasons. (Conversion of GIF files to -JPEG is usually a bad idea anyway.) - HINTS FOR DJPEG @@ -419,10 +421,6 @@ When producing a color-quantized image, "-onepass -dither ordered" is fast but much lower quality than the default behavior. "-dither none" may give acceptable results in two-pass mode, but is seldom tolerable in one-pass mode. -To avoid the Unisys LZW patent (now expired), djpeg produces uncompressed GIF -files. These are larger than they should be, but are readable by standard GIF -decoders. - HINTS FOR BOTH PROGRAMS @@ -529,6 +527,43 @@ The image can be losslessly cropped by giving the switch: -crop WxH+X+Y Crop to a rectangular region of width W and height H, starting at point X,Y. +If W or H is larger than the width/height of the input image, then the output +image is expanded in size, and the expanded region is filled in with zeros +(neutral gray). Attaching an 'f' character ("flatten") to the width number +will cause each block in the expanded region to be filled in with the DC +coefficient of the nearest block in the input image rather than grayed out. +Attaching an 'r' character ("reflect") to the width number will cause the +expanded region to be filled in with repeated reflections of the input image +rather than grayed out. + +A complementary lossless wipe option is provided to discard (gray out) data +inside a given image region while losslessly preserving what is outside: + -wipe WxH+X+Y Wipe (gray out) a rectangular region of width W and + height H from the input image, starting at point X,Y. + +Attaching an 'f' character ("flatten") to the width number will cause the +region to be filled with the average of adjacent blocks rather than grayed out. +If the wipe region and the region outside the wipe region, when adjusted to the +nearest iMCU boundary, form two horizontally adjacent rectangles, then +attaching an 'r' character ("reflect") to the width number will cause the wipe +region to be filled with repeated reflections of the outside region rather than +grayed out. + +A lossless drop option is also provided, which allows another JPEG image to be +inserted ("dropped") into the input image data at a given position, replacing +the existing image data at that position: + -drop +X+Y filename Drop (insert) another image at point X,Y + +Both the input image and the drop image must have the same subsampling level. +It is best if they also have the same quantization (quality.) Otherwise, the +quantization of the output image will be adapted to accommodate the higher of +the input image quality and the drop image quality. The trim option can be +used with the drop option to requantize the drop image to match the input +image. Note that a grayscale image can be dropped into a full-color image or +vice versa, as long as the full-color image has no vertical subsampling. If +the input image is grayscale and the drop image is full-color, then the +chrominance channels from the drop image will be discarded. + Other not-strictly-lossless transformation switches are: -grayscale Force grayscale output. diff --git a/wrgif.c b/wrgif.c index 3c036cc5..82a24291 100644 --- a/wrgif.c +++ b/wrgif.c @@ -3,6 +3,7 @@ * * This file was part of the Independent JPEG Group's software: * Copyright (C) 1991-1997, Thomas G. Lane. + * Modified 2015-2019 by Guido Vollbeding. * libjpeg-turbo Modifications: * Copyright (C) 2015, 2017, D. R. Commander. * For conditions of distribution and use, see the accompanying README.ijg @@ -10,12 +11,6 @@ * * This file contains routines to write output images in GIF format. * - ************************************************************************** - * NOTE: to avoid entanglements with Unisys' patent on LZW compression, * - * this code has been modified to output "uncompressed GIF" files. * - * There is no trace of the LZW algorithm in this file. * - ************************************************************************** - * * These routines may need modification for non-Unix environments or * specialized applications. As they stand, they assume output to * an ordinary stdio stream. @@ -33,11 +28,6 @@ * copyright notice and this permission notice appear in supporting * documentation. This software is provided "as is" without express or * implied warranty. - * - * We are also required to state that - * "The Graphics Interchange Format(c) is the Copyright property of - * CompuServe Incorporated. GIF(sm) is a Service Mark property of - * CompuServe Incorporated." */ #include "cdjpeg.h" /* Common decls for cjpeg/djpeg applications */ @@ -45,6 +35,37 @@ #ifdef GIF_SUPPORTED +#define MAX_LZW_BITS 12 /* maximum LZW code size (4096 symbols) */ + +typedef INT16 code_int; /* must hold -1 .. 2**MAX_LZW_BITS */ + +#define LZW_TABLE_SIZE ((code_int)1 << MAX_LZW_BITS) + +#define HSIZE 5003 /* hash table size for 80% occupancy */ + +typedef int hash_int; /* must hold -2*HSIZE..2*HSIZE */ + +#define MAXCODE(n_bits) (((code_int)1 << (n_bits)) - 1) + + +/* + * The LZW hash table consists of two parallel arrays: + * hash_code[i] code of symbol in slot i, or 0 if empty slot + * hash_value[i] symbol's value; undefined if empty slot + * where slot values (i) range from 0 to HSIZE-1. The symbol value is + * its prefix symbol's code concatenated with its suffix character. + * + * Algorithm: use open addressing double hashing (no chaining) on the + * prefix code / suffix character combination. We do a variant of Knuth's + * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + * secondary probe. + */ + +typedef int hash_entry; /* must hold (code_int << 8) | byte */ + +#define HASH_ENTRY(prefix, suffix) ((((hash_entry)(prefix)) << 8) | (suffix)) + + /* Private version of data destination object */ typedef struct { @@ -54,14 +75,24 @@ typedef struct { /* State for packing variable-width codes into a bitstream */ int n_bits; /* current number of bits/code */ - int maxcode; /* maximum code, given n_bits */ - long cur_accum; /* holds bits not yet output */ + code_int maxcode; /* maximum code, given n_bits */ + int init_bits; /* initial n_bits ... restored after clear */ + int cur_accum; /* holds bits not yet output */ int cur_bits; /* # of bits in cur_accum */ + /* LZW string construction */ + code_int waiting_code; /* symbol not yet output; may be extendable */ + boolean first_byte; /* if TRUE, waiting_code is not valid */ + /* State for GIF code assignment */ - int ClearCode; /* clear code (doesn't change) */ - int EOFCode; /* EOF code (ditto) */ - int code_counter; /* counts output symbols */ + code_int ClearCode; /* clear code (doesn't change) */ + code_int EOFCode; /* EOF code (ditto) */ + code_int free_code; /* LZW: first not-yet-used symbol code */ + code_int code_counter; /* not LZW: counts output symbols */ + + /* LZW hash table */ + code_int *hash_code; /* => hash table of symbol codes */ + hash_entry *hash_value; /* => hash table of symbol values */ /* GIF data packet construction buffer */ int bytesinpkt; /* # of bytes in current packet */ @@ -71,9 +102,6 @@ typedef struct { typedef gif_dest_struct *gif_dest_ptr; -/* Largest value that will fit in N bits */ -#define MAXCODE(n_bits) ((1 << (n_bits)) - 1) - /* * Routines to package finished data bytes into GIF data blocks. @@ -105,7 +133,7 @@ flush_packet(gif_dest_ptr dinfo) /* Routine to convert variable-width codes into a byte stream */ LOCAL(void) -output(gif_dest_ptr dinfo, int code) +output(gif_dest_ptr dinfo, code_int code) /* Emit a code of n_bits bits */ /* Uses cur_accum and cur_bits to reblock into 8-bit bytes */ { @@ -117,74 +145,76 @@ output(gif_dest_ptr dinfo, int code) dinfo->cur_accum >>= 8; dinfo->cur_bits -= 8; } + + /* + * If the next entry is going to be too big for the code size, + * then increase it, if possible. We do this here to ensure + * that it's done in sync with the decoder's codesize increases. + */ + if (dinfo->free_code > dinfo->maxcode) { + dinfo->n_bits++; + if (dinfo->n_bits == MAX_LZW_BITS) + dinfo->maxcode = LZW_TABLE_SIZE; /* free_code will never exceed this */ + else + dinfo->maxcode = MAXCODE(dinfo->n_bits); + } } -/* The pseudo-compression algorithm. - * - * In this module we simply output each pixel value as a separate symbol; - * thus, no compression occurs. In fact, there is expansion of one bit per - * pixel, because we use a symbol width one bit wider than the pixel width. - * - * GIF ordinarily uses variable-width symbols, and the decoder will expect - * to ratchet up the symbol width after a fixed number of symbols. - * To simplify the logic and keep the expansion penalty down, we emit a - * GIF Clear code to reset the decoder just before the width would ratchet up. - * Thus, all the symbols in the output file will have the same bit width. - * Note that emitting the Clear codes at the right times is a mere matter of - * counting output symbols and is in no way dependent on the LZW patent. - * - * With a small basic pixel width (low color count), Clear codes will be - * needed very frequently, causing the file to expand even more. So this - * simplistic approach wouldn't work too well on bilevel images, for example. - * But for output of JPEG conversions the pixel width will usually be 8 bits - * (129 to 256 colors), so the overhead added by Clear symbols is only about - * one symbol in every 256. - */ +/* Compression initialization & termination */ + + +LOCAL(void) +clear_hash(gif_dest_ptr dinfo) +/* Fill the hash table with empty entries */ +{ + /* It's sufficient to zero hash_code[] */ + MEMZERO(dinfo->hash_code, HSIZE * sizeof(code_int)); +} + + +LOCAL(void) +clear_block(gif_dest_ptr dinfo) +/* Reset compressor and issue a Clear code */ +{ + clear_hash(dinfo); /* delete all the symbols */ + dinfo->free_code = dinfo->ClearCode + 2; + output(dinfo, dinfo->ClearCode); /* inform decoder */ + dinfo->n_bits = dinfo->init_bits; /* reset code size */ + dinfo->maxcode = MAXCODE(dinfo->n_bits); +} + LOCAL(void) compress_init(gif_dest_ptr dinfo, int i_bits) -/* Initialize pseudo-compressor */ +/* Initialize compressor */ { /* init all the state variables */ - dinfo->n_bits = i_bits; + dinfo->n_bits = dinfo->init_bits = i_bits; dinfo->maxcode = MAXCODE(dinfo->n_bits); - dinfo->ClearCode = (1 << (i_bits - 1)); + dinfo->ClearCode = ((code_int) 1 << (i_bits - 1)); dinfo->EOFCode = dinfo->ClearCode + 1; - dinfo->code_counter = dinfo->ClearCode + 2; + dinfo->code_counter = dinfo->free_code = dinfo->ClearCode + 2; + dinfo->first_byte = TRUE; /* no waiting symbol yet */ /* init output buffering vars */ dinfo->bytesinpkt = 0; dinfo->cur_accum = 0; dinfo->cur_bits = 0; + /* clear hash table */ + if (dinfo->hash_code != NULL) + clear_hash(dinfo); /* GIF specifies an initial Clear code */ output(dinfo, dinfo->ClearCode); } -LOCAL(void) -compress_pixel(gif_dest_ptr dinfo, int c) -/* Accept and "compress" one pixel value. - * The given value must be less than n_bits wide. - */ -{ - /* Output the given pixel value as a symbol. */ - output(dinfo, c); - /* Issue Clear codes often enough to keep the reader from ratcheting up - * its symbol size. - */ - if (dinfo->code_counter < dinfo->maxcode) { - dinfo->code_counter++; - } else { - output(dinfo, dinfo->ClearCode); - dinfo->code_counter = dinfo->ClearCode + 2; /* reset the counter */ - } -} - - LOCAL(void) compress_term(gif_dest_ptr dinfo) /* Clean up at end */ { + /* Flush out the buffered LZW code */ + if (!dinfo->first_byte) + output(dinfo, dinfo->waiting_code); /* Send an EOF code */ output(dinfo, dinfo->EOFCode); /* Flush the bit-packing buffer */ @@ -221,7 +251,7 @@ put_3bytes(gif_dest_ptr dinfo, int val) LOCAL(void) emit_header(gif_dest_ptr dinfo, int num_colors, JSAMPARRAY colormap) /* Output the GIF file header, including color map */ -/* If colormap==NULL, synthesize a grayscale colormap */ +/* If colormap == NULL, synthesize a grayscale colormap */ { int BitsPerPixel, ColorMapSize, InitCodeSize, FlagByte; int cshift = dinfo->cinfo->data_precision - 8; @@ -278,7 +308,7 @@ emit_header(gif_dest_ptr dinfo, int num_colors, JSAMPARRAY colormap) } } else { /* fill out the map to a power of 2 */ - put_3bytes(dinfo, 0); + put_3bytes(dinfo, CENTERJSAMPLE >> cshift); } } /* Write image separator and Image Descriptor */ @@ -292,7 +322,7 @@ emit_header(gif_dest_ptr dinfo, int num_colors, JSAMPARRAY colormap) /* Write Initial Code Size byte */ putc(InitCodeSize, dinfo->pub.output_file); - /* Initialize for "compression" of image data */ + /* Initialize for compression of image data */ compress_init(dinfo, InitCodeSize + 1); } @@ -318,17 +348,139 @@ start_output_gif(j_decompress_ptr cinfo, djpeg_dest_ptr dinfo) * In this module rows_supplied will always be 1. */ + +/* + * The LZW algorithm proper + */ + METHODDEF(void) -put_pixel_rows(j_decompress_ptr cinfo, djpeg_dest_ptr dinfo, - JDIMENSION rows_supplied) +put_LZW_pixel_rows(j_decompress_ptr cinfo, djpeg_dest_ptr dinfo, + JDIMENSION rows_supplied) { gif_dest_ptr dest = (gif_dest_ptr)dinfo; register JSAMPROW ptr; register JDIMENSION col; + code_int c; + register hash_int i; + register hash_int disp; + register hash_entry probe_value; ptr = dest->pub.buffer[0]; for (col = cinfo->output_width; col > 0; col--) { - compress_pixel(dest, *ptr++); + /* Accept and compress one 8-bit byte */ + c = (code_int)(*ptr++); + + if (dest->first_byte) { /* need to initialize waiting_code */ + dest->waiting_code = c; + dest->first_byte = FALSE; + continue; + } + + /* Probe hash table to see if a symbol exists for + * waiting_code followed by c. + * If so, replace waiting_code by that symbol and continue. + */ + i = ((hash_int)c << (MAX_LZW_BITS - 8)) + dest->waiting_code; + /* i is less than twice 2**MAX_LZW_BITS, therefore less than twice HSIZE */ + if (i >= HSIZE) + i -= HSIZE; + + probe_value = HASH_ENTRY(dest->waiting_code, c); + + if (dest->hash_code[i] == 0) { + /* hit empty slot; desired symbol not in table */ + output(dest, dest->waiting_code); + if (dest->free_code < LZW_TABLE_SIZE) { + dest->hash_code[i] = dest->free_code++; /* add symbol to hashtable */ + dest->hash_value[i] = probe_value; + } else + clear_block(dest); + dest->waiting_code = c; + continue; + } + if (dest->hash_value[i] == probe_value) { + dest->waiting_code = dest->hash_code[i]; + continue; + } + + if (i == 0) /* secondary hash (after G. Knott) */ + disp = 1; + else + disp = HSIZE - i; + for (;;) { + i -= disp; + if (i < 0) + i += HSIZE; + if (dest->hash_code[i] == 0) { + /* hit empty slot; desired symbol not in table */ + output(dest, dest->waiting_code); + if (dest->free_code < LZW_TABLE_SIZE) { + dest->hash_code[i] = dest->free_code++; /* add symbol to hashtable */ + dest->hash_value[i] = probe_value; + } else + clear_block(dest); + dest->waiting_code = c; + break; + } + if (dest->hash_value[i] == probe_value) { + dest->waiting_code = dest->hash_code[i]; + break; + } + } + } +} + + +/* + * The pseudo-compression algorithm. + * + * In this version we simply output each pixel value as a separate symbol; + * thus, no compression occurs. In fact, there is expansion of one bit per + * pixel, because we use a symbol width one bit wider than the pixel width. + * + * GIF ordinarily uses variable-width symbols, and the decoder will expect + * to ratchet up the symbol width after a fixed number of symbols. + * To simplify the logic and keep the expansion penalty down, we emit a + * GIF Clear code to reset the decoder just before the width would ratchet up. + * Thus, all the symbols in the output file will have the same bit width. + * Note that emitting the Clear codes at the right times is a mere matter of + * counting output symbols and is in no way dependent on the LZW algorithm. + * + * With a small basic pixel width (low color count), Clear codes will be + * needed very frequently, causing the file to expand even more. So this + * simplistic approach wouldn't work too well on bilevel images, for example. + * But for output of JPEG conversions the pixel width will usually be 8 bits + * (129 to 256 colors), so the overhead added by Clear symbols is only about + * one symbol in every 256. + */ + +METHODDEF(void) +put_raw_pixel_rows(j_decompress_ptr cinfo, djpeg_dest_ptr dinfo, + JDIMENSION rows_supplied) +{ + gif_dest_ptr dest = (gif_dest_ptr)dinfo; + register JSAMPROW ptr; + register JDIMENSION col; + code_int c; + + ptr = dest->pub.buffer[0]; + for (col = cinfo->output_width; col > 0; col--) { + c = (code_int)(*ptr++); + /* Accept and output one pixel value. + * The given value must be less than n_bits wide. + */ + + /* Output the given pixel value as a symbol. */ + output(dest, c); + /* Issue Clear codes often enough to keep the reader from ratcheting up + * its symbol size. + */ + if (dest->code_counter < dest->maxcode) { + dest->code_counter++; + } else { + output(dest, dest->ClearCode); + dest->code_counter = dest->ClearCode + 2; /* reset the counter */ + } } } @@ -342,7 +494,7 @@ finish_output_gif(j_decompress_ptr cinfo, djpeg_dest_ptr dinfo) { gif_dest_ptr dest = (gif_dest_ptr)dinfo; - /* Flush "compression" mechanism */ + /* Flush compression mechanism */ compress_term(dest); /* Write a zero-length data block to end the series */ putc(0, dest->pub.output_file); @@ -370,7 +522,7 @@ calc_buffer_dimensions_gif(j_decompress_ptr cinfo, djpeg_dest_ptr dinfo) */ GLOBAL(djpeg_dest_ptr) -jinit_write_gif(j_decompress_ptr cinfo) +jinit_write_gif(j_decompress_ptr cinfo, boolean is_lzw) { gif_dest_ptr dest; @@ -380,7 +532,6 @@ jinit_write_gif(j_decompress_ptr cinfo) sizeof(gif_dest_struct)); dest->cinfo = cinfo; /* make back link for subroutines */ dest->pub.start_output = start_output_gif; - dest->pub.put_pixel_rows = put_pixel_rows; dest->pub.finish_output = finish_output_gif; dest->pub.calc_buffer_dimensions = calc_buffer_dimensions_gif; @@ -407,6 +558,22 @@ jinit_write_gif(j_decompress_ptr cinfo) ((j_common_ptr)cinfo, JPOOL_IMAGE, cinfo->output_width, (JDIMENSION)1); dest->pub.buffer_height = 1; + if (is_lzw) { + dest->pub.put_pixel_rows = put_LZW_pixel_rows; + /* Allocate space for hash table */ + dest->hash_code = (code_int *) + (*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_IMAGE, + HSIZE * sizeof(code_int)); + dest->hash_value = (hash_entry *) + (*cinfo->mem->alloc_large) ((j_common_ptr)cinfo, JPOOL_IMAGE, + HSIZE * sizeof(hash_entry)); + } else { + dest->pub.put_pixel_rows = put_raw_pixel_rows; + /* Mark tables unused */ + dest->hash_code = NULL; + dest->hash_value = NULL; + } + return (djpeg_dest_ptr)dest; }