/* * 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 jpegtran program. jpegtran features that are not * covered: * * - Scan scripts * - Expanding the input image when cropping * - Wiping a region of the input image * - Dropping another JPEG image into the input image * - Progress reporting * - Treating warnings as non-fatal [limitation of the TurboJPEG Java API] * - Debug output */ import java.io.*; import java.util.*; import org.libjpegturbo.turbojpeg.*; @SuppressWarnings("checkstyle:JavadocType") final class TJTran { private TJTran() {} static final String CLASS_NAME = new TJTran().getClass().getName(); private static boolean isCropped(java.awt.Rectangle cr) { return (cr.x != 0 || cr.y != 0 || cr.width != 0 || cr.height != 0); } 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("This program reads the DCT coefficients from the lossy JPEG input image,"); System.out.println("optionally transforms them, and writes them to a lossy JPEG output image.\n"); System.out.println("OPTIONS (CAN BE ABBREVBIATED)"); System.out.println("-----------------------------"); System.out.println("-arithmetic"); System.out.println(" Use arithmetic entropy coding in the output image instead of Huffman"); System.out.println(" entropy coding (can be combined with -progressive)"); System.out.println("-copy all"); System.out.println(" Copy all extra markers (including comments, JFIF thumbnails, Exif data, and"); System.out.println(" ICC profile data) from the input image to the output image"); System.out.println("-copy comments"); System.out.println(" Do not copy any extra markers, except comment markers, from the input"); System.out.println(" image to the output image [default]"); System.out.println("-copy icc"); System.out.println(" Do not copy any extra markers, except ICC profile data, from the input"); System.out.println(" image to the output image"); System.out.println("-copy none"); System.out.println(" Do not copy any extra markers from the input image to the output image"); System.out.println("-crop WxH+X+Y"); System.out.println(" Include only the specified region of the input image. (W, H, X, and Y are"); System.out.println(" the width, height, left boundary, and upper boundary of the region, all"); System.out.println(" specified relative to the transformed image dimensions.) If necessary, X"); System.out.println(" and Y will be shifted up and left to the nearest iMCU boundary, and W and H"); System.out.println(" will be increased accordingly."); System.out.println("-flip {horizontal|vertical}, -rotate {90|180|270}, -transpose, -transverse"); System.out.println(" Perform the specified lossless transform operation (these options are"); System.out.println(" mutually exclusive)"); System.out.println("-grayscale"); System.out.println(" Create a grayscale output image from a full-color input image"); System.out.println("-icc FILE"); System.out.println(" Embed the ICC (International Color Consortium) color management profile"); System.out.println(" from the specified file into the output image"); System.out.println("-maxmemory N"); System.out.println(" Memory limit (in megabytes) for intermediate buffers used with progressive"); System.out.println(" JPEG compression, Huffman table optimization, and lossless transformation"); System.out.println(" [default = no limit]"); System.out.println("-maxscans N"); System.out.println(" Refuse to transform progressive JPEG images that have more than N scans"); System.out.println("-optimize"); System.out.println(" Use Huffman table optimization in the output image"); System.out.println("-perfect"); System.out.println(" Abort if the requested transform operation is imperfect (non-reversible.)"); System.out.println(" '-flip horizontal', '-rotate 180', '-rotate 270', and '-transverse' are"); System.out.println(" imperfect if the image width is not evenly divisible by the iMCU width."); System.out.println(" '-flip vertical', '-rotate 90', '-rotate 180', and '-transverse' are"); System.out.println(" imperfect if the image height is not evenly divisible by the iMCU height."); System.out.println("-progressive"); System.out.println(" Create a progressive output image instead of a single-scan output image"); System.out.println(" (can be combined with -arithmetic; implies -optimize unless -arithmetic is"); System.out.println(" also specified)"); System.out.println("-restart N"); System.out.println(" Add a restart marker every N MCU rows [default = 0 (no restart markers)]."); System.out.println(" Append 'B' to specify the restart marker interval in MCUs."); System.out.println("-trim"); System.out.println(" If necessary, trim the partial iMCUs at the right or bottom edge of the"); System.out.println(" image to make the requested transform perfect\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; TJTransformer tjt = null; FileInputStream fis = null; FileOutputStream fos = null; try { int i; int arithmetic = 0, maxMemory = -1, maxScans = -1, optimize = -1, progressive = 0, restartIntervalBlocks = -1, restartIntervalRows = -1, saveMarkers = 1, subsamp; TJTransform[] xform = new TJTransform[1]; xform[0] = new TJTransform(); String iccFilename = null; byte[] srcBuf, iccBuf; int width, height, iccSize = 0; byte[][] dstBuf; for (i = 0; i < argv.length; i++) { if (matchArg(argv[i], "-arithmetic", 2)) arithmetic = 1; else if (matchArg(argv[i], "-crop", 3) && 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(); xform[0].options |= TJTransform.OPT_CROP; xform[0].width = tempWidth; xform[0].height = tempHeight; xform[0].x = tempX; xform[0].y = tempY; } else if (matchArg(argv[i], "-copy", 2) && i < argv.length - 1) { i++; if (matchArg(argv[i], "all", 1)) saveMarkers = 2; else if (matchArg(argv[i], "icc", 1)) saveMarkers = 4; else if (matchArg(argv[i], "none", 1)) saveMarkers = 0; else if (!matchArg(argv[i], "comments", 1)) usage(); } else if (matchArg(argv[i], "-flip", 2) && i < argv.length - 1) { i++; if (matchArg(argv[i], "horizontal", 1)) xform[0].op = TJTransform.OP_HFLIP; else if (matchArg(argv[i], "vertical", 1)) xform[0].op = TJTransform.OP_VFLIP; else usage(); } else if (matchArg(argv[i], "-grayscale", 2) || matchArg(argv[i], "-greyscale", 2)) xform[0].options |= TJTransform.OPT_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], "-optimize", 2) || matchArg(argv[i], "-optimise", 2)) optimize = 1; else if (matchArg(argv[i], "-perfect", 3)) xform[0].options |= TJTransform.OPT_PERFECT; else if (matchArg(argv[i], "-progressive", 2)) progressive = 1; else if (matchArg(argv[i], "-rotate", 3) && i < argv.length - 1) { i++; if (matchArg(argv[i], "90", 2)) xform[0].op = TJTransform.OP_ROT90; else if (matchArg(argv[i], "180", 3)) xform[0].op = TJTransform.OP_ROT180; else if (matchArg(argv[i], "270", 3)) xform[0].op = TJTransform.OP_ROT270; else usage(); } else if (matchArg(argv[i], "-restart", 2) && i < argv.length - 1) { int temp = -1; String arg = argv[++i]; Scanner scanner = new Scanner(arg).useDelimiter("b|B"); try { temp = scanner.nextInt(); } catch (Exception e) {} if (temp < 0 || temp > 65535 || scanner.hasNext()) usage(); if (arg.endsWith("B") || arg.endsWith("b")) restartIntervalBlocks = temp; else restartIntervalRows = temp; } else if (matchArg(argv[i], "-transverse", 7)) xform[0].op = TJTransform.OP_TRANSVERSE; else if (matchArg(argv[i], "-trim", 4)) xform[0].options |= TJTransform.OPT_TRIM; else if (matchArg(argv[i], "-transpose", 2)) xform[0].op = TJTransform.OP_TRANSPOSE; else break; } if (i != argv.length - 2) usage(); if (iccFilename != null) { if (saveMarkers == 2) saveMarkers = 3; else if (saveMarkers == 4) saveMarkers = 0; } tjt = new TJTransformer(); if (optimize >= 0) tjt.set(TJ.PARAM_OPTIMIZE, optimize); if (maxScans >= 0) tjt.set(TJ.PARAM_SCANLIMIT, maxScans); if (restartIntervalBlocks >= 0) tjt.set(TJ.PARAM_RESTARTBLOCKS, restartIntervalBlocks); if (restartIntervalRows >= 0) tjt.set(TJ.PARAM_RESTARTROWS, restartIntervalRows); if (maxMemory >= 0) tjt.set(TJ.PARAM_MAXMEMORY, maxMemory); tjt.set(TJ.PARAM_SAVEMARKERS, saveMarkers); File inFile = new File(argv[i++]); fis = new FileInputStream(inFile); int srcSize = fis.available(); if (srcSize < 1) throw new Exception("Input file contains no data"); srcBuf = new byte[srcSize]; fis.read(srcBuf); fis.close(); fis = null; tjt.setSourceImage(srcBuf, srcSize); subsamp = tjt.get(TJ.PARAM_SUBSAMP); if ((xform[0].options & TJTransform.OPT_GRAY) != 0) subsamp = TJ.SAMP_GRAY; if (xform[0].op == TJTransform.OP_TRANSPOSE || xform[0].op == TJTransform.OP_TRANSVERSE || xform[0].op == TJTransform.OP_ROT90 || xform[0].op == TJTransform.OP_ROT270) { width = tjt.get(TJ.PARAM_JPEGHEIGHT); height = tjt.get(TJ.PARAM_JPEGWIDTH); if (subsamp == TJ.SAMP_422) subsamp = TJ.SAMP_440; else if (subsamp == TJ.SAMP_440) subsamp = TJ.SAMP_422; else if (subsamp == TJ.SAMP_411) subsamp = TJ.SAMP_441; else if (subsamp == TJ.SAMP_441) subsamp = TJ.SAMP_411; } else { width = tjt.get(TJ.PARAM_JPEGWIDTH); height = tjt.get(TJ.PARAM_JPEGHEIGHT); } if (progressive >= 0) tjt.set(TJ.PARAM_PROGRESSIVE, progressive); if (arithmetic >= 0) tjt.set(TJ.PARAM_ARITHMETIC, arithmetic); if (isCropped(xform[0])) { int xAdjust, yAdjust; if (subsamp == TJ.SAMP_UNKNOWN) throw new Exception("Could not determine subsampling level of input image"); xAdjust = xform[0].x % TJ.getMCUWidth(subsamp); yAdjust = xform[0].y % TJ.getMCUHeight(subsamp); xform[0].x -= xAdjust; xform[0].width += xAdjust; xform[0].y -= yAdjust; xform[0].height += yAdjust; } if (iccFilename != null) { File iccFile = new File(iccFilename); fis = new FileInputStream(iccFile); iccSize = fis.available(); if (iccSize < 1) throw new Exception("ICC profile contains no data"); iccBuf = new byte[iccSize]; fis.read(iccBuf); fis.close(); fis = null; tjt.setICCProfile(iccBuf); } TJDecompressor[] tjd = tjt.transform(xform); File outFile = new File(argv[i]); fos = new FileOutputStream(outFile); fos.write(tjd[0].getJPEGBuf(), 0, tjd[0].getJPEGSize()); } catch (Exception e) { e.printStackTrace(); exitStatus = -1; } try { if (fis != null) fis.close(); if (tjt != null) tjt.close(); if (fos != null) fos.close(); } catch (Exception e) {} System.exit(exitStatus); } };