1 /*
2  * Copyright (C)2009-2014, 2016-2019 D. R. Commander.  All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * - Redistributions of source code must retain the above copyright notice,
8  *   this list of conditions and the following disclaimer.
9  * - Redistributions in binary form must reproduce the above copyright notice,
10  *   this list of conditions and the following disclaimer in the documentation
11  *   and/or other materials provided with the distribution.
12  * - Neither the name of the libjpeg-turbo Project nor the names of its
13  *   contributors may be used to endorse or promote products derived from this
14  *   software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 import java.io.*;
30 import java.awt.image.*;
31 import javax.imageio.*;
32 import java.util.*;
33 import org.libjpegturbo.turbojpeg.*;
34 
35 final class TJBench {
36 
TJBench()37   private TJBench() {}
38 
39   private static int flags = 0, quiet = 0, pf = TJ.PF_BGR, yuvPad = 1;
40   private static boolean compOnly, decompOnly, doTile, doYUV, write = true;
41 
42   static final String[] PIXFORMATSTR = {
43     "RGB", "BGR", "RGBX", "BGRX", "XBGR", "XRGB", "GRAY"
44   };
45 
46   static final String[] SUBNAME_LONG = {
47     "4:4:4", "4:2:2", "4:2:0", "GRAY", "4:4:0", "4:1:1"
48   };
49 
50   static final String[] SUBNAME = {
51     "444", "422", "420", "GRAY", "440", "411"
52   };
53 
54   static final String[] CSNAME = {
55     "RGB", "YCbCr", "GRAY", "CMYK", "YCCK"
56   };
57 
58   private static TJScalingFactor sf;
59   private static int xformOp = TJTransform.OP_NONE, xformOpt = 0;
60   private static double benchTime = 5.0, warmup = 1.0;
61 
62 
getTime()63   static double getTime() {
64     return (double)System.nanoTime() / 1.0e9;
65   }
66 
67 
68   private static String tjErrorMsg;
69   private static int tjErrorCode = -1;
70 
handleTJException(TJException e)71   static void handleTJException(TJException e) throws TJException {
72     String errorMsg = e.getMessage();
73     int errorCode = e.getErrorCode();
74 
75     if ((flags & TJ.FLAG_STOPONWARNING) == 0 &&
76         errorCode == TJ.ERR_WARNING) {
77       if (tjErrorMsg == null || !tjErrorMsg.equals(errorMsg) ||
78           tjErrorCode != errorCode) {
79         tjErrorMsg = errorMsg;
80         tjErrorCode = errorCode;
81         System.out.println("WARNING: " + errorMsg);
82       }
83     } else
84       throw e;
85   }
86 
87 
formatName(int subsamp, int cs)88   static String formatName(int subsamp, int cs) {
89     if (cs == TJ.CS_YCbCr)
90       return SUBNAME_LONG[subsamp];
91     else if (cs == TJ.CS_YCCK)
92       return CSNAME[cs] + " " + SUBNAME_LONG[subsamp];
93     else
94       return CSNAME[cs];
95   }
96 
97 
sigFig(double val, int figs)98   static String sigFig(double val, int figs) {
99     String format;
100     int digitsAfterDecimal = figs - (int)Math.ceil(Math.log10(Math.abs(val)));
101 
102     if (digitsAfterDecimal < 1)
103       format = new String("%.0f");
104     else
105       format = new String("%." + digitsAfterDecimal + "f");
106     return String.format(format, val);
107   }
108 
109 
loadImage(String fileName, int[] w, int[] h, int pixelFormat)110   static byte[] loadImage(String fileName, int[] w, int[] h, int pixelFormat)
111                           throws Exception {
112     BufferedImage img = ImageIO.read(new File(fileName));
113 
114     if (img == null)
115       throw new Exception("Could not read " + fileName);
116     w[0] = img.getWidth();
117     h[0] = img.getHeight();
118 
119     int[] rgb = img.getRGB(0, 0, w[0], h[0], null, 0, w[0]);
120     int ps = TJ.getPixelSize(pixelFormat);
121     int rindex = TJ.getRedOffset(pixelFormat);
122     int gindex = TJ.getGreenOffset(pixelFormat);
123     int bindex = TJ.getBlueOffset(pixelFormat);
124     byte[] dstBuf = new byte[w[0] * h[0] * ps];
125     int pixels = w[0] * h[0], dstPtr = 0, rgbPtr = 0;
126 
127     while (pixels-- > 0) {
128       dstBuf[dstPtr + rindex] = (byte)((rgb[rgbPtr] >> 16) & 0xff);
129       dstBuf[dstPtr + gindex] = (byte)((rgb[rgbPtr] >> 8) & 0xff);
130       dstBuf[dstPtr + bindex] = (byte)(rgb[rgbPtr] & 0xff);
131       dstPtr += ps;
132       rgbPtr++;
133     }
134     return dstBuf;
135   }
136 
137 
saveImage(String fileName, byte[] srcBuf, int w, int h, int pixelFormat)138   static void saveImage(String fileName, byte[] srcBuf, int w, int h,
139                         int pixelFormat) throws Exception {
140     BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
141     int pixels = w * h, srcPtr = 0;
142     int ps = TJ.getPixelSize(pixelFormat);
143     int rindex = TJ.getRedOffset(pixelFormat);
144     int gindex = TJ.getGreenOffset(pixelFormat);
145     int bindex = TJ.getBlueOffset(pixelFormat);
146 
147     for (int y = 0; y < h; y++) {
148       for (int x = 0; x < w; x++, srcPtr += ps) {
149         int pixel = (srcBuf[srcPtr + rindex] & 0xff) << 16 |
150                     (srcBuf[srcPtr + gindex] & 0xff) << 8 |
151                     (srcBuf[srcPtr + bindex] & 0xff);
152 
153         img.setRGB(x, y, pixel);
154       }
155     }
156     ImageIO.write(img, "bmp", new File(fileName));
157   }
158 
159 
160   /* Decompression test */
decomp(byte[] srcBuf, byte[][] jpegBuf, int[] jpegSize, byte[] dstBuf, int w, int h, int subsamp, int jpegQual, String fileName, int tilew, int tileh)161   static void decomp(byte[] srcBuf, byte[][] jpegBuf, int[] jpegSize,
162                      byte[] dstBuf, int w, int h, int subsamp, int jpegQual,
163                      String fileName, int tilew, int tileh) throws Exception {
164     String qualStr = new String(""), sizeStr, tempStr;
165     TJDecompressor tjd;
166     double elapsed, elapsedDecode;
167     int ps = TJ.getPixelSize(pf), i, iter = 0;
168     int scaledw = sf.getScaled(w);
169     int scaledh = sf.getScaled(h);
170     int pitch = scaledw * ps;
171     YUVImage yuvImage = null;
172 
173     if (jpegQual > 0)
174       qualStr = new String("_Q" + jpegQual);
175 
176     tjd = new TJDecompressor();
177 
178     if (dstBuf == null)
179       dstBuf = new byte[pitch * scaledh];
180 
181     /* Set the destination buffer to gray so we know whether the decompressor
182        attempted to write to it */
183     Arrays.fill(dstBuf, (byte)127);
184 
185     if (doYUV) {
186       int width = doTile ? tilew : scaledw;
187       int height = doTile ? tileh : scaledh;
188 
189       yuvImage = new YUVImage(width, yuvPad, height, subsamp);
190       Arrays.fill(yuvImage.getBuf(), (byte)127);
191     }
192 
193     /* Benchmark */
194     iter = -1;
195     elapsed = elapsedDecode = 0.0;
196     while (true) {
197       int tile = 0;
198       double start = getTime();
199 
200       for (int y = 0; y < h; y += tileh) {
201         for (int x = 0; x < w; x += tilew, tile++) {
202           int width = doTile ? Math.min(tilew, w - x) : scaledw;
203           int height = doTile ? Math.min(tileh, h - y) : scaledh;
204 
205           try {
206             tjd.setSourceImage(jpegBuf[tile], jpegSize[tile]);
207           } catch (TJException e) { handleTJException(e); }
208           if (doYUV) {
209             yuvImage.setBuf(yuvImage.getBuf(), width, yuvPad, height, subsamp);
210             try {
211               tjd.decompressToYUV(yuvImage, flags);
212             } catch (TJException e) { handleTJException(e); }
213             double startDecode = getTime();
214             tjd.setSourceImage(yuvImage);
215             try {
216               tjd.decompress(dstBuf, x, y, width, pitch, height, pf, flags);
217             } catch (TJException e) { handleTJException(e); }
218             if (iter >= 0)
219               elapsedDecode += getTime() - startDecode;
220           } else {
221             try {
222               tjd.decompress(dstBuf, x, y, width, pitch, height, pf, flags);
223             } catch (TJException e) { handleTJException(e); }
224           }
225         }
226       }
227       elapsed += getTime() - start;
228       if (iter >= 0) {
229         iter++;
230         if (elapsed >= benchTime)
231           break;
232       } else if (elapsed >= warmup) {
233         iter = 0;
234         elapsed = elapsedDecode = 0.0;
235       }
236     }
237     if (doYUV)
238       elapsed -= elapsedDecode;
239 
240     tjd = null;
241     for (i = 0; i < jpegBuf.length; i++)
242       jpegBuf[i] = null;
243     jpegBuf = null;  jpegSize = null;
244     System.gc();
245 
246     if (quiet != 0) {
247       System.out.format("%-6s%s",
248                         sigFig((double)(w * h) / 1000000. *
249                                (double)iter / elapsed, 4),
250                         quiet == 2 ? "\n" : "  ");
251       if (doYUV)
252         System.out.format("%s\n",
253                           sigFig((double)(w * h) / 1000000. *
254                                  (double)iter / elapsedDecode, 4));
255       else if (quiet != 2)
256         System.out.print("\n");
257     } else {
258       System.out.format("%s --> Frame rate:         %f fps\n",
259                         (doYUV ? "Decomp to YUV" : "Decompress   "),
260                         (double)iter / elapsed);
261       System.out.format("                  Throughput:         %f Megapixels/sec\n",
262                         (double)(w * h) / 1000000. * (double)iter / elapsed);
263       if (doYUV) {
264         System.out.format("YUV Decode    --> Frame rate:         %f fps\n",
265                           (double)iter / elapsedDecode);
266         System.out.format("                  Throughput:         %f Megapixels/sec\n",
267                           (double)(w * h) / 1000000. *
268                           (double)iter / elapsedDecode);
269       }
270     }
271 
272     if (!write) return;
273 
274     if (sf.getNum() != 1 || sf.getDenom() != 1)
275       sizeStr = new String(sf.getNum() + "_" + sf.getDenom());
276     else if (tilew != w || tileh != h)
277       sizeStr = new String(tilew + "x" + tileh);
278     else
279       sizeStr = new String("full");
280     if (decompOnly)
281       tempStr = new String(fileName + "_" + sizeStr + ".bmp");
282     else
283       tempStr = new String(fileName + "_" + SUBNAME[subsamp] + qualStr +
284                            "_" + sizeStr + ".bmp");
285 
286     saveImage(tempStr, dstBuf, scaledw, scaledh, pf);
287     int ndx = tempStr.lastIndexOf('.');
288     tempStr = new String(tempStr.substring(0, ndx) + "-err.bmp");
289     if (srcBuf != null && sf.getNum() == 1 && sf.getDenom() == 1) {
290       if (quiet == 0)
291         System.out.println("Compression error written to " + tempStr + ".");
292       if (subsamp == TJ.SAMP_GRAY) {
293         for (int y = 0, index = 0; y < h; y++, index += pitch) {
294           for (int x = 0, index2 = index; x < w; x++, index2 += ps) {
295             int rindex = index2 + TJ.getRedOffset(pf);
296             int gindex = index2 + TJ.getGreenOffset(pf);
297             int bindex = index2 + TJ.getBlueOffset(pf);
298             int lum = (int)((double)(srcBuf[rindex] & 0xff) * 0.299 +
299                             (double)(srcBuf[gindex] & 0xff) * 0.587 +
300                             (double)(srcBuf[bindex] & 0xff) * 0.114 + 0.5);
301 
302             if (lum > 255) lum = 255;
303             if (lum < 0) lum = 0;
304             dstBuf[rindex] = (byte)Math.abs((dstBuf[rindex] & 0xff) - lum);
305             dstBuf[gindex] = (byte)Math.abs((dstBuf[gindex] & 0xff) - lum);
306             dstBuf[bindex] = (byte)Math.abs((dstBuf[bindex] & 0xff) - lum);
307           }
308         }
309       } else {
310         for (int y = 0; y < h; y++)
311           for (int x = 0; x < w * ps; x++)
312             dstBuf[pitch * y + x] =
313               (byte)Math.abs((dstBuf[pitch * y + x] & 0xff) -
314                              (srcBuf[pitch * y + x] & 0xff));
315       }
316       saveImage(tempStr, dstBuf, w, h, pf);
317     }
318   }
319 
320 
fullTest(byte[] srcBuf, int w, int h, int subsamp, int jpegQual, String fileName)321   static void fullTest(byte[] srcBuf, int w, int h, int subsamp, int jpegQual,
322                        String fileName) throws Exception {
323     TJCompressor tjc;
324     byte[] tmpBuf;
325     byte[][] jpegBuf;
326     int[] jpegSize;
327     double start, elapsed, elapsedEncode;
328     int totalJpegSize = 0, tilew, tileh, i, iter;
329     int ps = TJ.getPixelSize(pf);
330     int ntilesw = 1, ntilesh = 1, pitch = w * ps;
331     String pfStr = PIXFORMATSTR[pf];
332     YUVImage yuvImage = null;
333 
334     tmpBuf = new byte[pitch * h];
335 
336     if (quiet == 0)
337       System.out.format(">>>>>  %s (%s) <--> JPEG %s Q%d  <<<<<\n", pfStr,
338                         (flags & TJ.FLAG_BOTTOMUP) != 0 ?
339                         "Bottom-up" : "Top-down",
340                         SUBNAME_LONG[subsamp], jpegQual);
341 
342     tjc = new TJCompressor();
343 
344     for (tilew = doTile ? 8 : w, tileh = doTile ? 8 : h; ;
345          tilew *= 2, tileh *= 2) {
346       if (tilew > w)
347         tilew = w;
348       if (tileh > h)
349         tileh = h;
350       ntilesw = (w + tilew - 1) / tilew;
351       ntilesh = (h + tileh - 1) / tileh;
352 
353       jpegBuf = new byte[ntilesw * ntilesh][TJ.bufSize(tilew, tileh, subsamp)];
354       jpegSize = new int[ntilesw * ntilesh];
355 
356       /* Compression test */
357       if (quiet == 1)
358         System.out.format("%-4s (%s)  %-5s    %-3d   ", pfStr,
359                           (flags & TJ.FLAG_BOTTOMUP) != 0 ? "BU" : "TD",
360                           SUBNAME_LONG[subsamp], jpegQual);
361       for (i = 0; i < h; i++)
362         System.arraycopy(srcBuf, w * ps * i, tmpBuf, pitch * i, w * ps);
363       tjc.setJPEGQuality(jpegQual);
364       tjc.setSubsamp(subsamp);
365 
366       if (doYUV) {
367         yuvImage = new YUVImage(tilew, yuvPad, tileh, subsamp);
368         Arrays.fill(yuvImage.getBuf(), (byte)127);
369       }
370 
371       /* Benchmark */
372       iter = -1;
373       elapsed = elapsedEncode = 0.0;
374       while (true) {
375         int tile = 0;
376 
377         totalJpegSize = 0;
378         start = getTime();
379         for (int y = 0; y < h; y += tileh) {
380           for (int x = 0; x < w; x += tilew, tile++) {
381             int width = Math.min(tilew, w - x);
382             int height = Math.min(tileh, h - y);
383 
384             tjc.setSourceImage(srcBuf, x, y, width, pitch, height, pf);
385             if (doYUV) {
386               double startEncode = getTime();
387 
388               yuvImage.setBuf(yuvImage.getBuf(), width, yuvPad, height,
389                               subsamp);
390               tjc.encodeYUV(yuvImage, flags);
391               if (iter >= 0)
392                 elapsedEncode += getTime() - startEncode;
393               tjc.setSourceImage(yuvImage);
394             }
395             tjc.compress(jpegBuf[tile], flags);
396             jpegSize[tile] = tjc.getCompressedSize();
397             totalJpegSize += jpegSize[tile];
398           }
399         }
400         elapsed += getTime() - start;
401         if (iter >= 0) {
402           iter++;
403           if (elapsed >= benchTime)
404             break;
405         } else if (elapsed >= warmup) {
406           iter = 0;
407           elapsed = elapsedEncode = 0.0;
408         }
409       }
410       if (doYUV)
411         elapsed -= elapsedEncode;
412 
413       if (quiet == 1)
414         System.out.format("%-5d  %-5d   ", tilew, tileh);
415       if (quiet != 0) {
416         if (doYUV)
417           System.out.format("%-6s%s",
418                             sigFig((double)(w * h) / 1000000. *
419                                    (double)iter / elapsedEncode, 4),
420                             quiet == 2 ? "\n" : "  ");
421         System.out.format("%-6s%s",
422                           sigFig((double)(w * h) / 1000000. *
423                                  (double)iter / elapsed, 4),
424                           quiet == 2 ? "\n" : "  ");
425         System.out.format("%-6s%s",
426                           sigFig((double)(w * h * ps) / (double)totalJpegSize,
427                                  4),
428                           quiet == 2 ? "\n" : "  ");
429       } else {
430         System.out.format("\n%s size: %d x %d\n", doTile ? "Tile" : "Image",
431                           tilew, tileh);
432         if (doYUV) {
433           System.out.format("Encode YUV    --> Frame rate:         %f fps\n",
434                             (double)iter / elapsedEncode);
435           System.out.format("                  Output image size:  %d bytes\n",
436                             yuvImage.getSize());
437           System.out.format("                  Compression ratio:  %f:1\n",
438                             (double)(w * h * ps) / (double)yuvImage.getSize());
439           System.out.format("                  Throughput:         %f Megapixels/sec\n",
440                             (double)(w * h) / 1000000. *
441                             (double)iter / elapsedEncode);
442           System.out.format("                  Output bit stream:  %f Megabits/sec\n",
443                             (double)yuvImage.getSize() * 8. / 1000000. *
444                             (double)iter / elapsedEncode);
445         }
446         System.out.format("%s --> Frame rate:         %f fps\n",
447                           doYUV ? "Comp from YUV" : "Compress     ",
448                           (double)iter / elapsed);
449         System.out.format("                  Output image size:  %d bytes\n",
450                           totalJpegSize);
451         System.out.format("                  Compression ratio:  %f:1\n",
452                           (double)(w * h * ps) / (double)totalJpegSize);
453         System.out.format("                  Throughput:         %f Megapixels/sec\n",
454                           (double)(w * h) / 1000000. * (double)iter / elapsed);
455         System.out.format("                  Output bit stream:  %f Megabits/sec\n",
456                           (double)totalJpegSize * 8. / 1000000. *
457                           (double)iter / elapsed);
458       }
459       if (tilew == w && tileh == h && write) {
460         String tempStr = fileName + "_" + SUBNAME[subsamp] + "_" + "Q" +
461                          jpegQual + ".jpg";
462         FileOutputStream fos = new FileOutputStream(tempStr);
463 
464         fos.write(jpegBuf[0], 0, jpegSize[0]);
465         fos.close();
466         if (quiet == 0)
467           System.out.println("Reference image written to " + tempStr);
468       }
469 
470       /* Decompression test */
471       if (!compOnly)
472         decomp(srcBuf, jpegBuf, jpegSize, tmpBuf, w, h, subsamp, jpegQual,
473                fileName, tilew, tileh);
474 
475       if (tilew == w && tileh == h) break;
476     }
477   }
478 
479 
decompTest(String fileName)480   static void decompTest(String fileName) throws Exception {
481     TJTransformer tjt;
482     byte[][] jpegBuf = null;
483     byte[] srcBuf;
484     int[] jpegSize = null;
485     int totalJpegSize;
486     double start, elapsed;
487     int ps = TJ.getPixelSize(pf), tile, x, y, iter;
488     // Original image
489     int w = 0, h = 0, ntilesw = 1, ntilesh = 1, subsamp = -1, cs = -1;
490     // Transformed image
491     int tw, th, ttilew, ttileh, tntilesw, tntilesh, tsubsamp;
492 
493     FileInputStream fis = new FileInputStream(fileName);
494     int srcSize = (int)fis.getChannel().size();
495     srcBuf = new byte[srcSize];
496     fis.read(srcBuf, 0, srcSize);
497     fis.close();
498 
499     int index = fileName.lastIndexOf('.');
500     if (index >= 0)
501       fileName = new String(fileName.substring(0, index));
502 
503     tjt = new TJTransformer();
504 
505     try {
506       tjt.setSourceImage(srcBuf, srcSize);
507     } catch (TJException e) { handleTJException(e); }
508     w = tjt.getWidth();
509     h = tjt.getHeight();
510     subsamp = tjt.getSubsamp();
511     cs = tjt.getColorspace();
512 
513     if (quiet == 1) {
514       System.out.println("All performance values in Mpixels/sec\n");
515       System.out.format("Bitmap     JPEG   JPEG     %s  %s   Xform   Comp    Decomp  ",
516                         (doTile ? "Tile " : "Image"),
517                         (doTile ? "Tile " : "Image"));
518       if (doYUV)
519         System.out.print("Decode");
520       System.out.print("\n");
521       System.out.print("Format     CS     Subsamp  Width  Height  Perf    Ratio   Perf    ");
522       if (doYUV)
523         System.out.print("Perf");
524       System.out.println("\n");
525     } else if (quiet == 0)
526       System.out.format(">>>>>  JPEG %s --> %s (%s)  <<<<<\n",
527                         formatName(subsamp, cs), PIXFORMATSTR[pf],
528                         (flags & TJ.FLAG_BOTTOMUP) != 0 ?
529                         "Bottom-up" : "Top-down");
530 
531     for (int tilew = doTile ? 16 : w, tileh = doTile ? 16 : h; ;
532          tilew *= 2, tileh *= 2) {
533       if (tilew > w)
534         tilew = w;
535       if (tileh > h)
536         tileh = h;
537       ntilesw = (w + tilew - 1) / tilew;
538       ntilesh = (h + tileh - 1) / tileh;
539 
540       tw = w;  th = h;  ttilew = tilew;  ttileh = tileh;
541       if (quiet == 0) {
542         System.out.format("\n%s size: %d x %d", (doTile ? "Tile" : "Image"),
543                           ttilew, ttileh);
544         if (sf.getNum() != 1 || sf.getDenom() != 1)
545           System.out.format(" --> %d x %d", sf.getScaled(tw),
546                             sf.getScaled(th));
547         System.out.println("");
548       } else if (quiet == 1) {
549         System.out.format("%-4s (%s)  %-5s  %-5s    ", PIXFORMATSTR[pf],
550                           (flags & TJ.FLAG_BOTTOMUP) != 0 ? "BU" : "TD",
551                           CSNAME[cs], SUBNAME_LONG[subsamp]);
552         System.out.format("%-5d  %-5d   ", tilew, tileh);
553       }
554 
555       tsubsamp = subsamp;
556       if (doTile || xformOp != TJTransform.OP_NONE || xformOpt != 0) {
557         if (xformOp == TJTransform.OP_TRANSPOSE ||
558             xformOp == TJTransform.OP_TRANSVERSE ||
559             xformOp == TJTransform.OP_ROT90 ||
560             xformOp == TJTransform.OP_ROT270) {
561           tw = h;  th = w;  ttilew = tileh;  ttileh = tilew;
562         }
563 
564         if ((xformOpt & TJTransform.OPT_GRAY) != 0)
565           tsubsamp = TJ.SAMP_GRAY;
566         if (xformOp == TJTransform.OP_HFLIP ||
567             xformOp == TJTransform.OP_ROT180)
568           tw = tw - (tw % TJ.getMCUWidth(tsubsamp));
569         if (xformOp == TJTransform.OP_VFLIP ||
570             xformOp == TJTransform.OP_ROT180)
571           th = th - (th % TJ.getMCUHeight(tsubsamp));
572         if (xformOp == TJTransform.OP_TRANSVERSE ||
573             xformOp == TJTransform.OP_ROT90)
574           tw = tw - (tw % TJ.getMCUHeight(tsubsamp));
575         if (xformOp == TJTransform.OP_TRANSVERSE ||
576             xformOp == TJTransform.OP_ROT270)
577           th = th - (th % TJ.getMCUWidth(tsubsamp));
578         tntilesw = (tw + ttilew - 1) / ttilew;
579         tntilesh = (th + ttileh - 1) / ttileh;
580 
581         if (xformOp == TJTransform.OP_TRANSPOSE ||
582             xformOp == TJTransform.OP_TRANSVERSE ||
583             xformOp == TJTransform.OP_ROT90 ||
584             xformOp == TJTransform.OP_ROT270) {
585           if (tsubsamp == TJ.SAMP_422)
586             tsubsamp = TJ.SAMP_440;
587           else if (tsubsamp == TJ.SAMP_440)
588             tsubsamp = TJ.SAMP_422;
589         }
590 
591         TJTransform[] t = new TJTransform[tntilesw * tntilesh];
592         jpegBuf =
593           new byte[tntilesw * tntilesh][TJ.bufSize(ttilew, ttileh, subsamp)];
594 
595         for (y = 0, tile = 0; y < th; y += ttileh) {
596           for (x = 0; x < tw; x += ttilew, tile++) {
597             t[tile] = new TJTransform();
598             t[tile].width = Math.min(ttilew, tw - x);
599             t[tile].height = Math.min(ttileh, th - y);
600             t[tile].x = x;
601             t[tile].y = y;
602             t[tile].op = xformOp;
603             t[tile].options = xformOpt | TJTransform.OPT_TRIM;
604             if ((t[tile].options & TJTransform.OPT_NOOUTPUT) != 0 &&
605                 jpegBuf[tile] != null)
606               jpegBuf[tile] = null;
607           }
608         }
609 
610         iter = -1;
611         elapsed = 0.;
612         while (true) {
613           start = getTime();
614           try {
615             tjt.transform(jpegBuf, t, flags);
616           } catch (TJException e) { handleTJException(e); }
617           jpegSize = tjt.getTransformedSizes();
618           elapsed += getTime() - start;
619           if (iter >= 0) {
620             iter++;
621             if (elapsed >= benchTime)
622               break;
623           } else if (elapsed >= warmup) {
624             iter = 0;
625             elapsed = 0.0;
626           }
627         }
628         t = null;
629 
630         for (tile = 0, totalJpegSize = 0; tile < tntilesw * tntilesh; tile++)
631           totalJpegSize += jpegSize[tile];
632 
633         if (quiet != 0) {
634           System.out.format("%-6s%s%-6s%s",
635                             sigFig((double)(w * h) / 1000000. / elapsed, 4),
636                             quiet == 2 ? "\n" : "  ",
637                             sigFig((double)(w * h * ps) /
638                                    (double)totalJpegSize, 4),
639                             quiet == 2 ? "\n" : "  ");
640         } else if (quiet == 0) {
641           System.out.format("Transform     --> Frame rate:         %f fps\n",
642                             1.0 / elapsed);
643           System.out.format("                  Output image size:  %d bytes\n",
644                             totalJpegSize);
645           System.out.format("                  Compression ratio:  %f:1\n",
646                             (double)(w * h * ps) / (double)totalJpegSize);
647           System.out.format("                  Throughput:         %f Megapixels/sec\n",
648                             (double)(w * h) / 1000000. / elapsed);
649           System.out.format("                  Output bit stream:  %f Megabits/sec\n",
650                             (double)totalJpegSize * 8. / 1000000. / elapsed);
651         }
652       } else {
653         if (quiet == 1)
654           System.out.print("N/A     N/A     ");
655         jpegBuf = new byte[1][TJ.bufSize(ttilew, ttileh, subsamp)];
656         jpegSize = new int[1];
657         jpegBuf[0] = srcBuf;
658         jpegSize[0] = srcSize;
659       }
660 
661       if (w == tilew)
662         ttilew = tw;
663       if (h == tileh)
664         ttileh = th;
665       if ((xformOpt & TJTransform.OPT_NOOUTPUT) == 0)
666         decomp(null, jpegBuf, jpegSize, null, tw, th, tsubsamp, 0,
667                fileName, ttilew, ttileh);
668       else if (quiet == 1)
669         System.out.println("N/A");
670 
671       jpegBuf = null;
672       jpegSize = null;
673 
674       if (tilew == w && tileh == h) break;
675     }
676   }
677 
678 
usage()679   static void usage() throws Exception {
680     int i;
681     TJScalingFactor[] scalingFactors = TJ.getScalingFactors();
682     int nsf = scalingFactors.length;
683     String className = new TJBench().getClass().getName();
684 
685     System.out.println("\nUSAGE: java " + className);
686     System.out.println("       <Inputfile (BMP)> <Quality> [options]\n");
687     System.out.println("       java " + className);
688     System.out.println("       <Inputfile (JPG)> [options]\n");
689     System.out.println("Options:\n");
690     System.out.println("-alloc = Dynamically allocate JPEG image buffers");
691     System.out.println("-bottomup = Test bottom-up compression/decompression");
692     System.out.println("-tile = Test performance of the codec when the image is encoded as separate");
693     System.out.println("     tiles of varying sizes.");
694     System.out.println("-rgb, -bgr, -rgbx, -bgrx, -xbgr, -xrgb =");
695     System.out.println("     Test the specified color conversion path in the codec (default = BGR)");
696     System.out.println("-fastupsample = Use the fastest chrominance upsampling algorithm available in");
697     System.out.println("     the underlying codec");
698     System.out.println("-fastdct = Use the fastest DCT/IDCT algorithms available in the underlying");
699     System.out.println("     codec");
700     System.out.println("-accuratedct = Use the most accurate DCT/IDCT algorithms available in the");
701     System.out.println("     underlying codec");
702     System.out.println("-progressive = Use progressive entropy coding in JPEG images generated by");
703     System.out.println("     compression and transform operations.");
704     System.out.println("-subsamp <s> = When testing JPEG compression, this option specifies the level");
705     System.out.println("     of chrominance subsampling to use (<s> = 444, 422, 440, 420, 411, or");
706     System.out.println("     GRAY).  The default is to test Grayscale, 4:2:0, 4:2:2, and 4:4:4 in");
707     System.out.println("     sequence.");
708     System.out.println("-quiet = Output results in tabular rather than verbose format");
709     System.out.println("-yuv = Test YUV encoding/decoding functions");
710     System.out.println("-yuvpad <p> = If testing YUV encoding/decoding, this specifies the number of");
711     System.out.println("     bytes to which each row of each plane in the intermediate YUV image is");
712     System.out.println("     padded (default = 1)");
713     System.out.println("-scale M/N = Scale down the width/height of the decompressed JPEG image by a");
714     System.out.print  ("     factor of M/N (M/N = ");
715     for (i = 0; i < nsf; i++) {
716       System.out.format("%d/%d", scalingFactors[i].getNum(),
717                         scalingFactors[i].getDenom());
718       if (nsf == 2 && i != nsf - 1)
719         System.out.print(" or ");
720       else if (nsf > 2) {
721         if (i != nsf - 1)
722           System.out.print(", ");
723         if (i == nsf - 2)
724           System.out.print("or ");
725       }
726       if (i % 8 == 0 && i != 0)
727         System.out.print("\n     ");
728     }
729     System.out.println(")");
730     System.out.println("-hflip, -vflip, -transpose, -transverse, -rot90, -rot180, -rot270 =");
731     System.out.println("     Perform the corresponding lossless transform prior to");
732     System.out.println("     decompression (these options are mutually exclusive)");
733     System.out.println("-grayscale = Perform lossless grayscale conversion prior to decompression");
734     System.out.println("     test (can be combined with the other transforms above)");
735     System.out.println("-copynone = Do not copy any extra markers (including EXIF and ICC profile data)");
736     System.out.println("     when transforming the image.");
737     System.out.println("-benchtime <t> = Run each benchmark for at least <t> seconds (default = 5.0)");
738     System.out.println("-warmup <t> = Run each benchmark for <t> seconds (default = 1.0) prior to");
739     System.out.println("     starting the timer, in order to prime the caches and thus improve the");
740     System.out.println("     consistency of the results.");
741     System.out.println("-componly = Stop after running compression tests.  Do not test decompression.");
742     System.out.println("-nowrite = Do not write reference or output images (improves consistency");
743     System.out.println("     of performance measurements.)");
744     System.out.println("-stoponwarning = Immediately discontinue the current");
745     System.out.println("     compression/decompression/transform operation if the underlying codec");
746     System.out.println("     throws a warning (non-fatal error)\n");
747     System.out.println("NOTE:  If the quality is specified as a range (e.g. 90-100), a separate");
748     System.out.println("test will be performed for all quality values in the range.\n");
749     System.exit(1);
750   }
751 
752 
main(String[] argv)753   public static void main(String[] argv) {
754     byte[] srcBuf = null;
755     int w = 0, h = 0, minQual = -1, maxQual = -1;
756     int minArg = 1, retval = 0;
757     int subsamp = -1;
758 
759     try {
760 
761       if (argv.length < minArg)
762         usage();
763 
764       String tempStr = argv[0].toLowerCase();
765       if (tempStr.endsWith(".jpg") || tempStr.endsWith(".jpeg"))
766         decompOnly = true;
767 
768       System.out.println("");
769 
770       if (!decompOnly) {
771         minArg = 2;
772         if (argv.length < minArg)
773           usage();
774         try {
775           minQual = Integer.parseInt(argv[1]);
776         } catch (NumberFormatException e) {}
777         if (minQual < 1 || minQual > 100)
778           throw new Exception("Quality must be between 1 and 100.");
779         int dashIndex = argv[1].indexOf('-');
780         if (dashIndex > 0 && argv[1].length() > dashIndex + 1) {
781           try {
782             maxQual = Integer.parseInt(argv[1].substring(dashIndex + 1));
783           } catch (NumberFormatException e) {}
784         }
785         if (maxQual < 1 || maxQual > 100)
786           maxQual = minQual;
787       }
788 
789       if (argv.length > minArg) {
790         for (int i = minArg; i < argv.length; i++) {
791           if (argv[i].equalsIgnoreCase("-tile")) {
792             doTile = true;  xformOpt |= TJTransform.OPT_CROP;
793           } else if (argv[i].equalsIgnoreCase("-fastupsample")) {
794             System.out.println("Using fast upsampling code\n");
795             flags |= TJ.FLAG_FASTUPSAMPLE;
796           } else if (argv[i].equalsIgnoreCase("-fastdct")) {
797             System.out.println("Using fastest DCT/IDCT algorithm\n");
798             flags |= TJ.FLAG_FASTDCT;
799           } else if (argv[i].equalsIgnoreCase("-accuratedct")) {
800             System.out.println("Using most accurate DCT/IDCT algorithm\n");
801             flags |= TJ.FLAG_ACCURATEDCT;
802           } else if (argv[i].equalsIgnoreCase("-progressive")) {
803             System.out.println("Using progressive entropy coding\n");
804             flags |= TJ.FLAG_PROGRESSIVE;
805           } else if (argv[i].equalsIgnoreCase("-rgb"))
806             pf = TJ.PF_RGB;
807           else if (argv[i].equalsIgnoreCase("-rgbx"))
808             pf = TJ.PF_RGBX;
809           else if (argv[i].equalsIgnoreCase("-bgr"))
810             pf = TJ.PF_BGR;
811           else if (argv[i].equalsIgnoreCase("-bgrx"))
812             pf = TJ.PF_BGRX;
813           else if (argv[i].equalsIgnoreCase("-xbgr"))
814             pf = TJ.PF_XBGR;
815           else if (argv[i].equalsIgnoreCase("-xrgb"))
816             pf = TJ.PF_XRGB;
817           else if (argv[i].equalsIgnoreCase("-bottomup"))
818             flags |= TJ.FLAG_BOTTOMUP;
819           else if (argv[i].equalsIgnoreCase("-quiet"))
820             quiet = 1;
821           else if (argv[i].equalsIgnoreCase("-qq"))
822             quiet = 2;
823           else if (argv[i].equalsIgnoreCase("-scale") && i < argv.length - 1) {
824             int temp1 = 0, temp2 = 0;
825             boolean match = false, scanned = true;
826             Scanner scanner = new Scanner(argv[++i]).useDelimiter("/");
827 
828             try {
829               temp1 = scanner.nextInt();
830               temp2 = scanner.nextInt();
831             } catch (Exception e) {}
832             if (temp2 <= 0) temp2 = 1;
833             if (temp1 > 0) {
834               TJScalingFactor[] scalingFactors = TJ.getScalingFactors();
835 
836               for (int j = 0; j < scalingFactors.length; j++) {
837                 if ((double)temp1 / (double)temp2 ==
838                     (double)scalingFactors[j].getNum() /
839                     (double)scalingFactors[j].getDenom()) {
840                   sf = scalingFactors[j];
841                   match = true;  break;
842                 }
843               }
844               if (!match) usage();
845             } else
846               usage();
847           } else if (argv[i].equalsIgnoreCase("-hflip"))
848             xformOp = TJTransform.OP_HFLIP;
849           else if (argv[i].equalsIgnoreCase("-vflip"))
850             xformOp = TJTransform.OP_VFLIP;
851           else if (argv[i].equalsIgnoreCase("-transpose"))
852             xformOp = TJTransform.OP_TRANSPOSE;
853           else if (argv[i].equalsIgnoreCase("-transverse"))
854             xformOp = TJTransform.OP_TRANSVERSE;
855           else if (argv[i].equalsIgnoreCase("-rot90"))
856             xformOp = TJTransform.OP_ROT90;
857           else if (argv[i].equalsIgnoreCase("-rot180"))
858             xformOp = TJTransform.OP_ROT180;
859           else if (argv[i].equalsIgnoreCase("-rot270"))
860             xformOp = TJTransform.OP_ROT270;
861           else if (argv[i].equalsIgnoreCase("-grayscale"))
862             xformOpt |= TJTransform.OPT_GRAY;
863           else if (argv[i].equalsIgnoreCase("-nooutput"))
864             xformOpt |= TJTransform.OPT_NOOUTPUT;
865           else if (argv[i].equalsIgnoreCase("-copynone"))
866             xformOpt |= TJTransform.OPT_COPYNONE;
867           else if (argv[i].equalsIgnoreCase("-benchtime") &&
868                    i < argv.length - 1) {
869             double temp = -1;
870 
871             try {
872               temp = Double.parseDouble(argv[++i]);
873             } catch (NumberFormatException e) {}
874             if (temp > 0.0)
875               benchTime = temp;
876             else
877               usage();
878           } else if (argv[i].equalsIgnoreCase("-warmup") &&
879                      i < argv.length - 1) {
880             double temp = -1;
881 
882             try {
883               temp = Double.parseDouble(argv[++i]);
884             } catch (NumberFormatException e) {}
885             if (temp >= 0.0) {
886               warmup = temp;
887               System.out.format("Warmup time = %.1f seconds\n\n", warmup);
888             } else
889               usage();
890           } else if (argv[i].equalsIgnoreCase("-yuv")) {
891             System.out.println("Testing YUV planar encoding/decoding\n");
892             doYUV = true;
893           } else if (argv[i].equalsIgnoreCase("-yuvpad") &&
894                      i < argv.length - 1) {
895             int temp = 0;
896 
897             try {
898               temp = Integer.parseInt(argv[++i]);
899             } catch (NumberFormatException e) {}
900             if (temp >= 1)
901               yuvPad = temp;
902           } else if (argv[i].equalsIgnoreCase("-subsamp") &&
903                      i < argv.length - 1) {
904             i++;
905             if (argv[i].toUpperCase().startsWith("G"))
906               subsamp = TJ.SAMP_GRAY;
907             else if (argv[i].equals("444"))
908               subsamp = TJ.SAMP_444;
909             else if (argv[i].equals("422"))
910               subsamp = TJ.SAMP_422;
911             else if (argv[i].equals("440"))
912               subsamp = TJ.SAMP_440;
913             else if (argv[i].equals("420"))
914               subsamp = TJ.SAMP_420;
915             else if (argv[i].equals("411"))
916               subsamp = TJ.SAMP_411;
917           } else if (argv[i].equalsIgnoreCase("-componly"))
918             compOnly = true;
919           else if (argv[i].equalsIgnoreCase("-nowrite"))
920             write = false;
921           else if (argv[i].equalsIgnoreCase("-stoponwarning"))
922             flags |= TJ.FLAG_STOPONWARNING;
923           else usage();
924         }
925       }
926 
927       if (sf == null)
928         sf = new TJScalingFactor(1, 1);
929 
930       if ((sf.getNum() != 1 || sf.getDenom() != 1) && doTile) {
931         System.out.println("Disabling tiled compression/decompression tests, because those tests do not");
932         System.out.println("work when scaled decompression is enabled.");
933         doTile = false;
934       }
935 
936       if (!decompOnly) {
937         int[] width = new int[1], height = new int[1];
938 
939         srcBuf = loadImage(argv[0], width, height, pf);
940         w = width[0];  h = height[0];
941         int index = -1;
942         if ((index = argv[0].lastIndexOf('.')) >= 0)
943           argv[0] = argv[0].substring(0, index);
944       }
945 
946       if (quiet == 1 && !decompOnly) {
947         System.out.println("All performance values in Mpixels/sec\n");
948         System.out.format("Bitmap     JPEG     JPEG  %s  %s   ",
949                           (doTile ? "Tile " : "Image"),
950                           (doTile ? "Tile " : "Image"));
951         if (doYUV)
952           System.out.print("Encode  ");
953         System.out.print("Comp    Comp    Decomp  ");
954         if (doYUV)
955           System.out.print("Decode");
956         System.out.print("\n");
957         System.out.print("Format     Subsamp  Qual  Width  Height  ");
958         if (doYUV)
959           System.out.print("Perf    ");
960         System.out.print("Perf    Ratio   Perf    ");
961         if (doYUV)
962           System.out.print("Perf");
963         System.out.println("\n");
964       }
965 
966       if (decompOnly) {
967         decompTest(argv[0]);
968         System.out.println("");
969         System.exit(retval);
970       }
971 
972       System.gc();
973       if (subsamp >= 0 && subsamp < TJ.NUMSAMP) {
974         for (int i = maxQual; i >= minQual; i--)
975           fullTest(srcBuf, w, h, subsamp, i, argv[0]);
976         System.out.println("");
977       } else {
978         for (int i = maxQual; i >= minQual; i--)
979           fullTest(srcBuf, w, h, TJ.SAMP_GRAY, i, argv[0]);
980         System.out.println("");
981         System.gc();
982         for (int i = maxQual; i >= minQual; i--)
983           fullTest(srcBuf, w, h, TJ.SAMP_420, i, argv[0]);
984         System.out.println("");
985         System.gc();
986         for (int i = maxQual; i >= minQual; i--)
987           fullTest(srcBuf, w, h, TJ.SAMP_422, i, argv[0]);
988         System.out.println("");
989         System.gc();
990         for (int i = maxQual; i >= minQual; i--)
991           fullTest(srcBuf, w, h, TJ.SAMP_444, i, argv[0]);
992         System.out.println("");
993       }
994 
995     } catch (Exception e) {
996       if (e instanceof TJException) {
997         TJException tje = (TJException)e;
998 
999         System.out.println((tje.getErrorCode() == TJ.ERR_WARNING ?
1000                             "WARNING: " : "ERROR: ") + tje.getMessage());
1001       } else
1002         System.out.println("ERROR: " + e.getMessage());
1003       e.printStackTrace();
1004       retval = -1;
1005     }
1006 
1007     System.exit(retval);
1008   }
1009 
1010 }
1011