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