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