/* * Copyright (C)2011-2012, 2014-2015, 2017-2018, 2022-2024 D. R. Commander. * All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of the libjpeg-turbo Project nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * This program demonstrates how to use the TurboJPEG C API to approximate the * functionality of the IJG's djpeg program. djpeg features that are not * covered: * * - OS/2 BMP, GIF, and Targa output file formats [legacy feature] * - Color quantization and dithering [legacy feature] * - The floating-point IDCT method [legacy feature] * - Extracting an ICC color management profile * - Progress reporting * - Skipping rows (i.e. exclusive rather than inclusive partial decompression) * - Debug output */ import java.io.*; import java.util.*; import org.libjpegturbo.turbojpeg.*; @SuppressWarnings("checkstyle:JavadocType") final class TJDecomp { private TJDecomp() {} static final String CLASS_NAME = new TJDecomp().getClass().getName(); private static boolean isCropped(java.awt.Rectangle cr) { return (cr.x != 0 || cr.y != 0 || cr.width != 0 || cr.height != 0); } private static String tjErrorMsg; private static int tjErrorCode = -1; static void handleTJException(TJException e, int stopOnWarning) throws TJException { String errorMsg = e.getMessage(); int errorCode = e.getErrorCode(); if (stopOnWarning != 1 && errorCode == TJ.ERR_WARNING) { if (tjErrorMsg == null || !tjErrorMsg.equals(errorMsg) || tjErrorCode != errorCode) { tjErrorMsg = errorMsg; tjErrorCode = errorCode; System.out.println("WARNING: " + errorMsg); } } else throw e; } static void usage() { int i; TJScalingFactor[] scalingFactors = TJ.getScalingFactors(); int numScalingFactors = scalingFactors.length; System.out.println("\nUSAGE: java [Java options] " + CLASS_NAME + " [options] \n"); System.out.println("The output image will be in Windows BMP or PBMPLUS (PPM/PGM) format, depending"); System.out.println("on the file extension.\n"); System.out.println("GENERAL OPTIONS (CAN BE ABBREVBIATED)"); System.out.println("-------------------------------------"); System.out.println("-icc FILE"); System.out.println(" Extract the ICC (International Color Consortium) color profile from the"); System.out.println(" JPEG image to the specified file"); System.out.println("-strict"); System.out.println(" Treat all warnings as fatal; abort immediately if incomplete or corrupt"); System.out.println(" data is encountered in the JPEG image, rather than trying to salvage the"); System.out.println(" rest of the image\n"); System.out.println("LOSSY JPEG OPTIONS (CAN BE ABBREVIATED)"); System.out.println("---------------------------------------"); System.out.println("-crop WxH+X+Y"); System.out.println(" Decompress only the specified region of the JPEG image. (W, H, X, and Y"); System.out.println(" are the width, height, left boundary, and upper boundary of the region, all"); System.out.println(" specified relative to the scaled image dimensions.) If necessary, X will"); System.out.println(" be shifted left to the nearest iMCU boundary, and W will be increased"); System.out.println(" accordingly."); System.out.println("-dct fast"); System.out.println(" Use less accurate IDCT algorithm [legacy feature]"); System.out.println("-dct int"); System.out.println(" Use more accurate IDCT algorithm [default]"); System.out.println("-grayscale"); System.out.println(" Decompress a full-color JPEG image into a grayscale output image"); System.out.println("-maxmemory N"); System.out.println(" Memory limit (in megabytes) for intermediate buffers used with progressive"); System.out.println(" JPEG decompression [default = no limit]"); System.out.println("-maxscans N"); System.out.println(" Refuse to decompress progressive JPEG images that have more than N scans"); System.out.println("-nosmooth"); System.out.println(" Use the fastest chrominance upsampling algorithm available"); System.out.println("-rgb"); System.out.println(" Decompress a grayscale JPEG image into a full-color output image"); System.out.println("-scale M/N"); System.out.println(" Scale the width/height of the JPEG image by a factor of M/N when"); System.out.print(" decompressing it (M/N = "); for (i = 0; i < numScalingFactors; i++) { System.out.format("%d/%d", scalingFactors[i].getNum(), scalingFactors[i].getDenom()); if (numScalingFactors == 2 && i != numScalingFactors - 1) System.out.print(" or "); else if (numScalingFactors > 2) { if (i != numScalingFactors - 1) System.out.print(", "); if (i == numScalingFactors - 2) System.out.print("or "); } if (i % 8 == 0 && i != 0) System.out.print("\n "); } System.out.println(")\n"); System.exit(1); } static boolean matchArg(String arg, String string, int minChars) { if (arg.length() > string.length() || arg.length() < minChars) return false; int cmpChars = Math.max(arg.length(), minChars); string = string.substring(0, cmpChars); return arg.equalsIgnoreCase(string); } public static void main(String[] argv) { int exitStatus = 0; TJDecompressor tjd = null; FileInputStream fis = null; FileOutputStream fos = null; try { int i; int colorspace, fastDCT = -1, fastUpsample = -1, maxMemory = -1, maxScans = -1, pixelFormat = TJ.PF_UNKNOWN, precision, stopOnWarning = -1, subsamp; java.awt.Rectangle croppingRegion = TJ.UNCROPPED; String iccFilename = null; TJScalingFactor scalingFactor = TJ.UNSCALED; int width, height; byte[] jpegBuf, iccBuf = null; Object dstBuf = null; for (i = 0; i < argv.length; i++) { if (matchArg(argv[i], "-crop", 2) && i < argv.length - 1) { int tempWidth = -1, tempHeight = -1, tempX = -1, tempY = -1; Scanner scanner = new Scanner(argv[++i]).useDelimiter("x|X|\\+"); try { tempWidth = scanner.nextInt(); tempHeight = scanner.nextInt(); tempX = scanner.nextInt(); tempY = scanner.nextInt(); } catch (Exception e) {} if (tempWidth < 1 || tempHeight < 1 || tempX < 0 || tempY < 0) usage(); croppingRegion.width = tempWidth; croppingRegion.height = tempHeight; croppingRegion.x = tempX; croppingRegion.y = tempY; } else if (matchArg(argv[i], "-dct", 2) && i < argv.length - 1) { i++; if (matchArg(argv[i], "fast", 1)) fastDCT = 1; else if (!matchArg(argv[i], "int", 1)) usage(); } else if (matchArg(argv[i], "-grayscale", 2) || matchArg(argv[i], "-greyscale", 2)) pixelFormat = TJ.PF_GRAY; else if (matchArg(argv[i], "-icc", 2) && i < argv.length - 1) iccFilename = argv[++i]; else if (matchArg(argv[i], "-maxscans", 5) && i < argv.length - 1) { int temp = -1; try { temp = Integer.parseInt(argv[++i]); } catch (NumberFormatException e) {} if (temp < 0) usage(); maxScans = temp; } else if (matchArg(argv[i], "-maxmemory", 2) && i < argv.length - 1) { int temp = -1; try { temp = Integer.parseInt(argv[++i]); } catch (NumberFormatException e) {} if (temp < 0) usage(); maxMemory = temp; } else if (matchArg(argv[i], "-nosmooth", 2)) fastUpsample = 1; else if (matchArg(argv[i], "-rgb", 2)) pixelFormat = TJ.PF_RGB; else if (matchArg(argv[i], "-strict", 3)) stopOnWarning = 1; else if (matchArg(argv[i], "-scale", 2) && i < argv.length - 1) { int tempNum = 0, tempDenom = 0; boolean match = false, scanned = true; Scanner scanner = new Scanner(argv[++i]).useDelimiter("/"); try { tempNum = scanner.nextInt(); tempDenom = scanner.nextInt(); } catch (Exception e) {} if (tempNum < 1 || tempDenom < 1) usage(); TJScalingFactor[] scalingFactors = TJ.getScalingFactors(); for (int j = 0; j < scalingFactors.length; j++) { if ((double)tempNum / (double)tempDenom == (double)scalingFactors[j].getNum() / (double)scalingFactors[j].getDenom()) { scalingFactor = scalingFactors[j]; match = true; break; } } if (!match) usage(); } else break; } if (i != argv.length - 2) usage(); tjd = new TJDecompressor(); if (stopOnWarning >= 0) tjd.set(TJ.PARAM_STOPONWARNING, stopOnWarning); if (fastUpsample >= 0) tjd.set(TJ.PARAM_FASTUPSAMPLE, fastUpsample); if (fastDCT >= 0) tjd.set(TJ.PARAM_FASTDCT, fastDCT); if (maxScans >= 0) tjd.set(TJ.PARAM_SCANLIMIT, maxScans); if (maxMemory >= 0) tjd.set(TJ.PARAM_MAXMEMORY, maxMemory); File jpegFile = new File(argv[i++]); fis = new FileInputStream(jpegFile); int jpegSize = fis.available(); if (jpegSize < 1) throw new Exception("Input file contains no data"); jpegBuf = new byte[jpegSize]; fis.read(jpegBuf); fis.close(); fis = null; try { tjd.setSourceImage(jpegBuf, jpegSize); } catch (TJException e) { handleTJException(e, stopOnWarning); } subsamp = tjd.get(TJ.PARAM_SUBSAMP); width = tjd.get(TJ.PARAM_JPEGWIDTH); height = tjd.get(TJ.PARAM_JPEGHEIGHT); precision = tjd.get(TJ.PARAM_PRECISION); colorspace = tjd.get(TJ.PARAM_COLORSPACE); if (iccFilename != null) { try { iccBuf = tjd.getICCProfile(); } catch (TJException e) { handleTJException(e, stopOnWarning); } if (iccBuf != null) { File iccFile = new File(iccFilename); fos = new FileOutputStream(iccFile); fos.write(iccBuf, 0, iccBuf.length); } } if (pixelFormat == TJ.PF_UNKNOWN) { if (colorspace == TJ.CS_GRAY) pixelFormat = TJ.PF_GRAY; else if (colorspace == TJ.CS_CMYK || colorspace == TJ.CS_YCCK) pixelFormat = TJ.PF_CMYK; else pixelFormat = TJ.PF_RGB; } if (tjd.get(TJ.PARAM_LOSSLESS) == 0) { tjd.setScalingFactor(scalingFactor); width = scalingFactor.getScaled(width); height = scalingFactor.getScaled(height); if (isCropped(croppingRegion)) { int adjustment; if (subsamp == TJ.SAMP_UNKNOWN) throw new Exception("Could not determine subsampling level of JPEG image"); adjustment = croppingRegion.x % scalingFactor.getScaled(TJ.getMCUWidth(subsamp)); croppingRegion.x -= adjustment; croppingRegion.width += adjustment; tjd.setCroppingRegion(croppingRegion); width = croppingRegion.width; height = croppingRegion.height; } } if (precision <= 8) dstBuf = new byte[width * height * TJ.getPixelSize(pixelFormat)]; else dstBuf = new short[width * height * TJ.getPixelSize(pixelFormat)]; try { if (precision <= 8) tjd.decompress8((byte[])dstBuf, 0, 0, 0, pixelFormat); else if (precision <= 12) tjd.decompress12((short[])dstBuf, 0, 0, 0, pixelFormat); else tjd.decompress16((short[])dstBuf, 0, 0, 0, pixelFormat); } catch (TJException e) { handleTJException(e, stopOnWarning); } tjd.saveImage(argv[i], dstBuf, 0, 0, width, 0, height, pixelFormat); } catch (Exception e) { e.printStackTrace(); exitStatus = -1; } try { if (tjd != null) tjd.close(); if (fis != null) fis.close(); if (fos != null) fos.close(); } catch (Exception e) {} System.exit(exitStatus); } };