1 /* 2 * Copyright (C)2011-2012, 2014-2015, 2017-2018 D. R. Commander. 3 * All Rights Reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * - Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * - Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * - Neither the name of the libjpeg-turbo Project nor the names of its 14 * contributors may be used to endorse or promote products derived from this 15 * software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", 18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 * POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 /* 31 * This program demonstrates how to compress, decompress, and transform JPEG 32 * images using the TurboJPEG Java API 33 */ 34 35 import java.io.*; 36 import java.awt.*; 37 import java.awt.image.*; 38 import java.nio.*; 39 import javax.imageio.*; 40 import javax.swing.*; 41 import org.libjpegturbo.turbojpeg.*; 42 43 44 @SuppressWarnings("checkstyle:JavadocType") 45 class TJExample implements TJCustomFilter { 46 47 static final String CLASS_NAME = 48 new TJExample().getClass().getName(); 49 50 static final int DEFAULT_SUBSAMP = TJ.SAMP_444; 51 static final int DEFAULT_QUALITY = 95; 52 53 54 static final String[] SUBSAMP_NAME = { 55 "4:4:4", "4:2:2", "4:2:0", "Grayscale", "4:4:0", "4:1:1" 56 }; 57 58 static final String[] COLORSPACE_NAME = { 59 "RGB", "YCbCr", "GRAY", "CMYK", "YCCK" 60 }; 61 62 63 /* DCT filter example. This produces a negative of the image. */ 64 65 @SuppressWarnings("checkstyle:JavadocMethod") customFilter(ShortBuffer coeffBuffer, Rectangle bufferRegion, Rectangle planeRegion, int componentIndex, int transformIndex, TJTransform transform)66 public void customFilter(ShortBuffer coeffBuffer, Rectangle bufferRegion, 67 Rectangle planeRegion, int componentIndex, 68 int transformIndex, TJTransform transform) 69 throws TJException { 70 for (int i = 0; i < bufferRegion.width * bufferRegion.height; i++) { 71 coeffBuffer.put(i, (short)(-coeffBuffer.get(i))); 72 } 73 } 74 75 usage()76 static void usage() throws Exception { 77 System.out.println("\nUSAGE: java [Java options] " + CLASS_NAME + 78 " <Input image> <Output image> [options]\n"); 79 80 System.out.println("Input and output images can be in any image format that the Java Image I/O"); 81 System.out.println("extensions understand. If either filename ends in a .jpg extension, then"); 82 System.out.println("the TurboJPEG API will be used to compress or decompress the image.\n"); 83 84 System.out.println("Compression Options (used if the output image is a JPEG image)"); 85 System.out.println("--------------------------------------------------------------\n"); 86 87 System.out.println("-subsamp <444|422|420|gray> = Apply this level of chrominance subsampling when"); 88 System.out.println(" compressing the output image. The default is to use the same level of"); 89 System.out.println(" subsampling as in the input image, if the input image is also a JPEG"); 90 System.out.println(" image, or to use grayscale if the input image is a grayscale non-JPEG"); 91 System.out.println(" image, or to use " + 92 SUBSAMP_NAME[DEFAULT_SUBSAMP] + 93 " subsampling otherwise.\n"); 94 95 System.out.println("-q <1-100> = Compress the output image with this JPEG quality level"); 96 System.out.println(" (default = " + DEFAULT_QUALITY + ").\n"); 97 98 System.out.println("Decompression Options (used if the input image is a JPEG image)"); 99 System.out.println("---------------------------------------------------------------\n"); 100 101 System.out.println("-scale M/N = Scale the input image by a factor of M/N when decompressing it."); 102 System.out.print("(M/N = "); 103 for (int i = 0; i < SCALING_FACTORS.length; i++) { 104 System.out.print(SCALING_FACTORS[i].getNum() + "/" + 105 SCALING_FACTORS[i].getDenom()); 106 if (SCALING_FACTORS.length == 2 && i != SCALING_FACTORS.length - 1) 107 System.out.print(" or "); 108 else if (SCALING_FACTORS.length > 2) { 109 if (i != SCALING_FACTORS.length - 1) 110 System.out.print(", "); 111 if (i == SCALING_FACTORS.length - 2) 112 System.out.print("or "); 113 } 114 } 115 System.out.println(")\n"); 116 117 System.out.println("-hflip, -vflip, -transpose, -transverse, -rot90, -rot180, -rot270 ="); 118 System.out.println(" Perform one of these lossless transform operations on the input image"); 119 System.out.println(" prior to decompressing it (these options are mutually exclusive.)\n"); 120 121 System.out.println("-grayscale = Perform lossless grayscale conversion on the input image prior"); 122 System.out.println(" to decompressing it (can be combined with the other transform operations"); 123 System.out.println(" above.)\n"); 124 125 System.out.println("-crop WxH+X+Y = Perform lossless cropping on the input image prior to"); 126 System.out.println(" decompressing it. X and Y specify the upper left corner of the cropping"); 127 System.out.println(" region, and W and H specify the width and height of the cropping region."); 128 System.out.println(" X and Y must be evenly divible by the MCU block size (8x8 if the input"); 129 System.out.println(" image was compressed using no subsampling or grayscale, 16x8 if it was"); 130 System.out.println(" compressed using 4:2:2 subsampling, or 16x16 if it was compressed using"); 131 System.out.println(" 4:2:0 subsampling.)\n"); 132 133 System.out.println("General Options"); 134 System.out.println("---------------\n"); 135 136 System.out.println("-display = Display output image (Output filename need not be specified in this"); 137 System.out.println(" case.)\n"); 138 139 System.out.println("-fastupsample = Use the fastest chrominance upsampling algorithm available in"); 140 System.out.println(" the underlying codec.\n"); 141 142 System.out.println("-fastdct = Use the fastest DCT/IDCT algorithms available in the underlying"); 143 System.out.println(" codec.\n"); 144 145 System.out.println("-accuratedct = Use the most accurate DCT/IDCT algorithms available in the"); 146 System.out.println(" underlying codec.\n"); 147 148 System.exit(1); 149 } 150 151 main(String[] argv)152 public static void main(String[] argv) { 153 154 try { 155 156 TJScalingFactor scalingFactor = new TJScalingFactor(1, 1); 157 int outSubsamp = -1, outQual = -1; 158 TJTransform xform = new TJTransform(); 159 boolean display = false; 160 int flags = 0; 161 int width, height; 162 String inFormat = "jpg", outFormat = "jpg"; 163 BufferedImage img = null; 164 byte[] imgBuf = null; 165 166 if (argv.length < 2) 167 usage(); 168 169 if (argv[1].substring(0, 2).equalsIgnoreCase("-d")) 170 display = true; 171 172 /* Parse arguments. */ 173 for (int i = 2; i < argv.length; i++) { 174 if (argv[i].length() < 2) 175 continue; 176 else if (argv[i].length() > 2 && 177 argv[i].substring(0, 3).equalsIgnoreCase("-sc") && 178 i < argv.length - 1) { 179 int match = 0; 180 String[] scaleArg = argv[++i].split("/"); 181 if (scaleArg.length == 2) { 182 TJScalingFactor tempsf = 183 new TJScalingFactor(Integer.parseInt(scaleArg[0]), 184 Integer.parseInt(scaleArg[1])); 185 for (int j = 0; j < SCALING_FACTORS.length; j++) { 186 if (tempsf.equals(SCALING_FACTORS[j])) { 187 scalingFactor = SCALING_FACTORS[j]; 188 match = 1; 189 break; 190 } 191 } 192 } 193 if (match != 1) 194 usage(); 195 } else if (argv[i].length() > 2 && 196 argv[i].substring(0, 3).equalsIgnoreCase("-su") && 197 i < argv.length - 1) { 198 i++; 199 if (argv[i].substring(0, 1).equalsIgnoreCase("g")) 200 outSubsamp = TJ.SAMP_GRAY; 201 else if (argv[i].equals("444")) 202 outSubsamp = TJ.SAMP_444; 203 else if (argv[i].equals("422")) 204 outSubsamp = TJ.SAMP_422; 205 else if (argv[i].equals("420")) 206 outSubsamp = TJ.SAMP_420; 207 else 208 usage(); 209 } else if (argv[i].substring(0, 2).equalsIgnoreCase("-q") && 210 i < argv.length - 1) { 211 outQual = Integer.parseInt(argv[++i]); 212 if (outQual < 1 || outQual > 100) 213 usage(); 214 } else if (argv[i].substring(0, 2).equalsIgnoreCase("-g")) 215 xform.options |= TJTransform.OPT_GRAY; 216 else if (argv[i].equalsIgnoreCase("-hflip")) 217 xform.op = TJTransform.OP_HFLIP; 218 else if (argv[i].equalsIgnoreCase("-vflip")) 219 xform.op = TJTransform.OP_VFLIP; 220 else if (argv[i].equalsIgnoreCase("-transpose")) 221 xform.op = TJTransform.OP_TRANSPOSE; 222 else if (argv[i].equalsIgnoreCase("-transverse")) 223 xform.op = TJTransform.OP_TRANSVERSE; 224 else if (argv[i].equalsIgnoreCase("-rot90")) 225 xform.op = TJTransform.OP_ROT90; 226 else if (argv[i].equalsIgnoreCase("-rot180")) 227 xform.op = TJTransform.OP_ROT180; 228 else if (argv[i].equalsIgnoreCase("-rot270")) 229 xform.op = TJTransform.OP_ROT270; 230 else if (argv[i].equalsIgnoreCase("-custom")) 231 xform.cf = new TJExample(); 232 else if (argv[i].length() > 2 && 233 argv[i].substring(0, 2).equalsIgnoreCase("-c") && 234 i < argv.length - 1) { 235 String[] cropArg = argv[++i].split("[x\\+]"); 236 if (cropArg.length != 4) 237 usage(); 238 xform.width = Integer.parseInt(cropArg[0]); 239 xform.height = Integer.parseInt(cropArg[1]); 240 xform.x = Integer.parseInt(cropArg[2]); 241 xform.y = Integer.parseInt(cropArg[3]); 242 if (xform.x < 0 || xform.y < 0 || xform.width < 1 || 243 xform.height < 1) 244 usage(); 245 xform.options |= TJTransform.OPT_CROP; 246 } else if (argv[i].substring(0, 2).equalsIgnoreCase("-d")) 247 display = true; 248 else if (argv[i].equalsIgnoreCase("-fastupsample")) { 249 System.out.println("Using fast upsampling code"); 250 flags |= TJ.FLAG_FASTUPSAMPLE; 251 } else if (argv[i].equalsIgnoreCase("-fastdct")) { 252 System.out.println("Using fastest DCT/IDCT algorithm"); 253 flags |= TJ.FLAG_FASTDCT; 254 } else if (argv[i].equalsIgnoreCase("-accuratedct")) { 255 System.out.println("Using most accurate DCT/IDCT algorithm"); 256 flags |= TJ.FLAG_ACCURATEDCT; 257 } else usage(); 258 } 259 260 /* Determine input and output image formats based on file extensions. */ 261 String[] inFileTokens = argv[0].split("\\."); 262 if (inFileTokens.length > 1) 263 inFormat = inFileTokens[inFileTokens.length - 1]; 264 String[] outFileTokens; 265 if (display) 266 outFormat = "bmp"; 267 else { 268 outFileTokens = argv[1].split("\\."); 269 if (outFileTokens.length > 1) 270 outFormat = outFileTokens[outFileTokens.length - 1]; 271 } 272 273 if (inFormat.equalsIgnoreCase("jpg")) { 274 /* Input image is a JPEG image. Decompress and/or transform it. */ 275 boolean doTransform = (xform.op != TJTransform.OP_NONE || 276 xform.options != 0 || xform.cf != null); 277 278 /* Read the JPEG file into memory. */ 279 File jpegFile = new File(argv[0]); 280 FileInputStream fis = new FileInputStream(jpegFile); 281 int jpegSize = fis.available(); 282 if (jpegSize < 1) { 283 System.out.println("Input file contains no data"); 284 System.exit(1); 285 } 286 byte[] jpegBuf = new byte[jpegSize]; 287 fis.read(jpegBuf); 288 fis.close(); 289 290 TJDecompressor tjd; 291 if (doTransform) { 292 /* Transform it. */ 293 TJTransformer tjt = new TJTransformer(jpegBuf); 294 TJTransform[] xforms = new TJTransform[1]; 295 xforms[0] = xform; 296 xforms[0].options |= TJTransform.OPT_TRIM; 297 TJDecompressor[] tjds = tjt.transform(xforms, 0); 298 tjd = tjds[0]; 299 tjt.close(); 300 } else 301 tjd = new TJDecompressor(jpegBuf); 302 303 width = tjd.getWidth(); 304 height = tjd.getHeight(); 305 int inSubsamp = tjd.getSubsamp(); 306 int inColorspace = tjd.getColorspace(); 307 308 System.out.println((doTransform ? "Transformed" : "Input") + 309 " Image (jpg): " + width + " x " + height + 310 " pixels, " + SUBSAMP_NAME[inSubsamp] + 311 " subsampling, " + COLORSPACE_NAME[inColorspace]); 312 313 if (outFormat.equalsIgnoreCase("jpg") && doTransform && 314 scalingFactor.isOne() && outSubsamp < 0 && outQual < 0) { 315 /* Input image has been transformed, and no re-compression options 316 have been selected. Write the transformed image to disk and 317 exit. */ 318 File outFile = new File(argv[1]); 319 FileOutputStream fos = new FileOutputStream(outFile); 320 fos.write(tjd.getJPEGBuf(), 0, tjd.getJPEGSize()); 321 fos.close(); 322 System.exit(0); 323 } 324 325 /* Scaling and/or a non-JPEG output image format and/or compression 326 options have been selected, so we need to decompress the 327 input/transformed image. */ 328 width = scalingFactor.getScaled(width); 329 height = scalingFactor.getScaled(height); 330 if (outSubsamp < 0) 331 outSubsamp = inSubsamp; 332 333 if (!outFormat.equalsIgnoreCase("jpg")) 334 img = tjd.decompress(width, height, BufferedImage.TYPE_INT_RGB, 335 flags); 336 else 337 imgBuf = tjd.decompress(width, 0, height, TJ.PF_BGRX, flags); 338 tjd.close(); 339 } else { 340 /* Input image is not a JPEG image. Load it into memory. */ 341 img = ImageIO.read(new File(argv[0])); 342 if (img == null) 343 throw new Exception("Input image type not supported."); 344 width = img.getWidth(); 345 height = img.getHeight(); 346 if (outSubsamp < 0) { 347 if (img.getType() == BufferedImage.TYPE_BYTE_GRAY) 348 outSubsamp = TJ.SAMP_GRAY; 349 else 350 outSubsamp = DEFAULT_SUBSAMP; 351 } 352 System.out.println("Input Image: " + width + " x " + height + 353 " pixels"); 354 } 355 System.gc(); 356 if (!display) 357 System.out.print("Output Image (" + outFormat + "): " + width + 358 " x " + height + " pixels"); 359 360 if (display) { 361 /* Display the uncompressed image */ 362 ImageIcon icon = new ImageIcon(img); 363 JLabel label = new JLabel(icon, JLabel.CENTER); 364 JOptionPane.showMessageDialog(null, label, "Output Image", 365 JOptionPane.PLAIN_MESSAGE); 366 } else if (outFormat.equalsIgnoreCase("jpg")) { 367 /* Output image format is JPEG. Compress the uncompressed image. */ 368 if (outQual < 0) 369 outQual = DEFAULT_QUALITY; 370 System.out.println(", " + SUBSAMP_NAME[outSubsamp] + 371 " subsampling, quality = " + outQual); 372 373 TJCompressor tjc = new TJCompressor(); 374 tjc.setSubsamp(outSubsamp); 375 tjc.setJPEGQuality(outQual); 376 if (img != null) 377 tjc.setSourceImage(img, 0, 0, 0, 0); 378 else 379 tjc.setSourceImage(imgBuf, 0, 0, width, 0, height, TJ.PF_BGRX); 380 byte[] jpegBuf = tjc.compress(flags); 381 int jpegSize = tjc.getCompressedSize(); 382 tjc.close(); 383 384 /* Write the JPEG image to disk. */ 385 File outFile = new File(argv[1]); 386 FileOutputStream fos = new FileOutputStream(outFile); 387 fos.write(jpegBuf, 0, jpegSize); 388 fos.close(); 389 } else { 390 /* Output image format is not JPEG. Save the uncompressed image 391 directly to disk. */ 392 System.out.print("\n"); 393 File outFile = new File(argv[1]); 394 ImageIO.write(img, outFormat, outFile); 395 } 396 397 } catch (Exception e) { 398 e.printStackTrace(); 399 System.exit(-1); 400 } 401 } 402 403 static final TJScalingFactor[] SCALING_FACTORS = 404 TJ.getScalingFactors(); 405 }; 406