• 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 
482       if (tilew == w && tileh == h) break;
483     }
484   }
485 
486 
decompTest(String fileName)487   static void decompTest(String fileName) throws Exception {
488     TJTransformer tjt;
489     byte[][] jpegBuf = null;
490     byte[] srcBuf;
491     int[] jpegSize = null;
492     int totalJpegSize;
493     double start, elapsed;
494     int ps = TJ.getPixelSize(pf), tile, x, y, iter;
495     // Original image
496     int w = 0, h = 0, ntilesw = 1, ntilesh = 1, subsamp = -1, cs = -1;
497     // Transformed image
498     int tw, th, ttilew, ttileh, tntilesw, tntilesh, tsubsamp;
499 
500     FileInputStream fis = new FileInputStream(fileName);
501     if (fis.getChannel().size() > (long)Integer.MAX_VALUE)
502       throw new Exception("Image is too large");
503     int srcSize = (int)fis.getChannel().size();
504     srcBuf = new byte[srcSize];
505     fis.read(srcBuf, 0, srcSize);
506     fis.close();
507 
508     int index = fileName.lastIndexOf('.');
509     if (index >= 0)
510       fileName = new String(fileName.substring(0, index));
511 
512     tjt = new TJTransformer();
513 
514     try {
515       tjt.setSourceImage(srcBuf, srcSize);
516     } catch (TJException e) { handleTJException(e); }
517     w = tjt.getWidth();
518     h = tjt.getHeight();
519     subsamp = tjt.getSubsamp();
520     cs = tjt.getColorspace();
521 
522     if (quiet == 1) {
523       System.out.println("All performance values in Mpixels/sec\n");
524       System.out.format("Bitmap     JPEG   JPEG     %s  %s   Xform   Comp    Decomp  ",
525                         (doTile ? "Tile " : "Image"),
526                         (doTile ? "Tile " : "Image"));
527       if (doYUV)
528         System.out.print("Decode");
529       System.out.print("\n");
530       System.out.print("Format     CS     Subsamp  Width  Height  Perf    Ratio   Perf    ");
531       if (doYUV)
532         System.out.print("Perf");
533       System.out.println("\n");
534     } else if (quiet == 0)
535       System.out.format(">>>>>  JPEG %s --> %s (%s)  <<<<<\n",
536                         formatName(subsamp, cs), PIXFORMATSTR[pf],
537                         (flags & TJ.FLAG_BOTTOMUP) != 0 ?
538                         "Bottom-up" : "Top-down");
539 
540     for (int tilew = doTile ? 16 : w, tileh = doTile ? 16 : h; ;
541          tilew *= 2, tileh *= 2) {
542       if (tilew > w)
543         tilew = w;
544       if (tileh > h)
545         tileh = h;
546       ntilesw = (w + tilew - 1) / tilew;
547       ntilesh = (h + tileh - 1) / tileh;
548 
549       tw = w;  th = h;  ttilew = tilew;  ttileh = tileh;
550       if (quiet == 0) {
551         System.out.format("\n%s size: %d x %d", (doTile ? "Tile" : "Image"),
552                           ttilew, ttileh);
553         if (sf.getNum() != 1 || sf.getDenom() != 1)
554           System.out.format(" --> %d x %d", sf.getScaled(tw),
555                             sf.getScaled(th));
556         System.out.println("");
557       } else if (quiet == 1) {
558         System.out.format("%-4s (%s)  %-5s  %-5s    ", PIXFORMATSTR[pf],
559                           (flags & TJ.FLAG_BOTTOMUP) != 0 ? "BU" : "TD",
560                           CSNAME[cs], SUBNAME_LONG[subsamp]);
561         System.out.format("%-5d  %-5d   ", tilew, tileh);
562       }
563 
564       tsubsamp = subsamp;
565       if (doTile || xformOp != TJTransform.OP_NONE || xformOpt != 0) {
566         if (xformOp == TJTransform.OP_TRANSPOSE ||
567             xformOp == TJTransform.OP_TRANSVERSE ||
568             xformOp == TJTransform.OP_ROT90 ||
569             xformOp == TJTransform.OP_ROT270) {
570           tw = h;  th = w;  ttilew = tileh;  ttileh = tilew;
571         }
572 
573         if ((xformOpt & TJTransform.OPT_GRAY) != 0)
574           tsubsamp = TJ.SAMP_GRAY;
575         if (xformOp == TJTransform.OP_HFLIP ||
576             xformOp == TJTransform.OP_ROT180)
577           tw = tw - (tw % TJ.getMCUWidth(tsubsamp));
578         if (xformOp == TJTransform.OP_VFLIP ||
579             xformOp == TJTransform.OP_ROT180)
580           th = th - (th % TJ.getMCUHeight(tsubsamp));
581         if (xformOp == TJTransform.OP_TRANSVERSE ||
582             xformOp == TJTransform.OP_ROT90)
583           tw = tw - (tw % TJ.getMCUHeight(tsubsamp));
584         if (xformOp == TJTransform.OP_TRANSVERSE ||
585             xformOp == TJTransform.OP_ROT270)
586           th = th - (th % TJ.getMCUWidth(tsubsamp));
587         tntilesw = (tw + ttilew - 1) / ttilew;
588         tntilesh = (th + ttileh - 1) / ttileh;
589 
590         if (xformOp == TJTransform.OP_TRANSPOSE ||
591             xformOp == TJTransform.OP_TRANSVERSE ||
592             xformOp == TJTransform.OP_ROT90 ||
593             xformOp == TJTransform.OP_ROT270) {
594           if (tsubsamp == TJ.SAMP_422)
595             tsubsamp = TJ.SAMP_440;
596           else if (tsubsamp == TJ.SAMP_440)
597             tsubsamp = TJ.SAMP_422;
598         }
599 
600         TJTransform[] t = new TJTransform[tntilesw * tntilesh];
601         jpegBuf =
602           new byte[tntilesw * tntilesh][TJ.bufSize(ttilew, ttileh, subsamp)];
603 
604         for (y = 0, tile = 0; y < th; y += ttileh) {
605           for (x = 0; x < tw; x += ttilew, tile++) {
606             t[tile] = new TJTransform();
607             t[tile].width = Math.min(ttilew, tw - x);
608             t[tile].height = Math.min(ttileh, th - y);
609             t[tile].x = x;
610             t[tile].y = y;
611             t[tile].op = xformOp;
612             t[tile].options = xformOpt | TJTransform.OPT_TRIM;
613             if ((t[tile].options & TJTransform.OPT_NOOUTPUT) != 0 &&
614                 jpegBuf[tile] != null)
615               jpegBuf[tile] = null;
616           }
617         }
618 
619         iter = -1;
620         elapsed = 0.;
621         while (true) {
622           start = getTime();
623           try {
624             tjt.transform(jpegBuf, t, flags);
625           } catch (TJException e) { handleTJException(e); }
626           jpegSize = tjt.getTransformedSizes();
627           elapsed += getTime() - start;
628           if (iter >= 0) {
629             iter++;
630             if (elapsed >= benchTime)
631               break;
632           } else if (elapsed >= warmup) {
633             iter = 0;
634             elapsed = 0.0;
635           }
636         }
637         t = null;
638 
639         for (tile = 0, totalJpegSize = 0; tile < tntilesw * tntilesh; tile++)
640           totalJpegSize += jpegSize[tile];
641 
642         if (quiet != 0) {
643           System.out.format("%-6s%s%-6s%s",
644                             sigFig((double)(w * h) / 1000000. / elapsed, 4),
645                             quiet == 2 ? "\n" : "  ",
646                             sigFig((double)(w * h * ps) /
647                                    (double)totalJpegSize, 4),
648                             quiet == 2 ? "\n" : "  ");
649         } else if (quiet == 0) {
650           System.out.format("Transform     --> Frame rate:         %f fps\n",
651                             1.0 / elapsed);
652           System.out.format("                  Output image size:  %d bytes\n",
653                             totalJpegSize);
654           System.out.format("                  Compression ratio:  %f:1\n",
655                             (double)(w * h * ps) / (double)totalJpegSize);
656           System.out.format("                  Throughput:         %f Megapixels/sec\n",
657                             (double)(w * h) / 1000000. / elapsed);
658           System.out.format("                  Output bit stream:  %f Megabits/sec\n",
659                             (double)totalJpegSize * 8. / 1000000. / elapsed);
660         }
661       } else {
662         if (quiet == 1)
663           System.out.print("N/A     N/A     ");
664         jpegBuf = new byte[1][TJ.bufSize(ttilew, ttileh, subsamp)];
665         jpegSize = new int[1];
666         jpegBuf[0] = srcBuf;
667         jpegSize[0] = srcSize;
668       }
669 
670       if (w == tilew)
671         ttilew = tw;
672       if (h == tileh)
673         ttileh = th;
674       if ((xformOpt & TJTransform.OPT_NOOUTPUT) == 0)
675         decomp(null, jpegBuf, jpegSize, null, tw, th, tsubsamp, 0,
676                fileName, ttilew, ttileh);
677       else if (quiet == 1)
678         System.out.println("N/A");
679 
680       jpegBuf = null;
681       jpegSize = null;
682 
683       if (tilew == w && tileh == h) break;
684     }
685   }
686 
687 
usage()688   static void usage() throws Exception {
689     int i;
690     TJScalingFactor[] scalingFactors = TJ.getScalingFactors();
691     int nsf = scalingFactors.length;
692     String className = new TJBench().getClass().getName();
693 
694     System.out.println("\nUSAGE: java " + className);
695     System.out.println("       <Inputfile (BMP)> <Quality> [options]\n");
696     System.out.println("       java " + className);
697     System.out.println("       <Inputfile (JPG)> [options]\n");
698     System.out.println("Options:\n");
699     System.out.println("-alloc = Dynamically allocate JPEG image buffers");
700     System.out.println("-bottomup = Test bottom-up compression/decompression");
701     System.out.println("-tile = Test performance of the codec when the image is encoded as separate");
702     System.out.println("     tiles of varying sizes.");
703     System.out.println("-rgb, -bgr, -rgbx, -bgrx, -xbgr, -xrgb =");
704     System.out.println("     Test the specified color conversion path in the codec (default = BGR)");
705     System.out.println("-fastupsample = Use the fastest chrominance upsampling algorithm available in");
706     System.out.println("     the underlying codec");
707     System.out.println("-fastdct = Use the fastest DCT/IDCT algorithms available in the underlying");
708     System.out.println("     codec");
709     System.out.println("-accuratedct = Use the most accurate DCT/IDCT algorithms available in the");
710     System.out.println("     underlying codec");
711     System.out.println("-progressive = Use progressive entropy coding in JPEG images generated by");
712     System.out.println("     compression and transform operations.");
713     System.out.println("-subsamp <s> = When testing JPEG compression, this option specifies the level");
714     System.out.println("     of chrominance subsampling to use (<s> = 444, 422, 440, 420, 411, or");
715     System.out.println("     GRAY).  The default is to test Grayscale, 4:2:0, 4:2:2, and 4:4:4 in");
716     System.out.println("     sequence.");
717     System.out.println("-quiet = Output results in tabular rather than verbose format");
718     System.out.println("-yuv = Test YUV encoding/decoding functions");
719     System.out.println("-yuvpad <p> = If testing YUV encoding/decoding, this specifies the number of");
720     System.out.println("     bytes to which each row of each plane in the intermediate YUV image is");
721     System.out.println("     padded (default = 1)");
722     System.out.println("-scale M/N = Scale down the width/height of the decompressed JPEG image by a");
723     System.out.print  ("     factor of M/N (M/N = ");
724     for (i = 0; i < nsf; i++) {
725       System.out.format("%d/%d", scalingFactors[i].getNum(),
726                         scalingFactors[i].getDenom());
727       if (nsf == 2 && i != nsf - 1)
728         System.out.print(" or ");
729       else if (nsf > 2) {
730         if (i != nsf - 1)
731           System.out.print(", ");
732         if (i == nsf - 2)
733           System.out.print("or ");
734       }
735       if (i % 8 == 0 && i != 0)
736         System.out.print("\n     ");
737     }
738     System.out.println(")");
739     System.out.println("-hflip, -vflip, -transpose, -transverse, -rot90, -rot180, -rot270 =");
740     System.out.println("     Perform the corresponding lossless transform prior to");
741     System.out.println("     decompression (these options are mutually exclusive)");
742     System.out.println("-grayscale = Perform lossless grayscale conversion prior to decompression");
743     System.out.println("     test (can be combined with the other transforms above)");
744     System.out.println("-copynone = Do not copy any extra markers (including EXIF and ICC profile data)");
745     System.out.println("     when transforming the image.");
746     System.out.println("-benchtime <t> = Run each benchmark for at least <t> seconds (default = 5.0)");
747     System.out.println("-warmup <t> = Run each benchmark for <t> seconds (default = 1.0) prior to");
748     System.out.println("     starting the timer, in order to prime the caches and thus improve the");
749     System.out.println("     consistency of the results.");
750     System.out.println("-componly = Stop after running compression tests.  Do not test decompression.");
751     System.out.println("-nowrite = Do not write reference or output images (improves consistency");
752     System.out.println("     of performance measurements.)");
753     System.out.println("-stoponwarning = Immediately discontinue the current");
754     System.out.println("     compression/decompression/transform operation if the underlying codec");
755     System.out.println("     throws a warning (non-fatal error)\n");
756     System.out.println("NOTE:  If the quality is specified as a range (e.g. 90-100), a separate");
757     System.out.println("test will be performed for all quality values in the range.\n");
758     System.exit(1);
759   }
760 
761 
main(String[] argv)762   public static void main(String[] argv) {
763     byte[] srcBuf = null;
764     int w = 0, h = 0, minQual = -1, maxQual = -1;
765     int minArg = 1, retval = 0;
766     int subsamp = -1;
767 
768     try {
769 
770       if (argv.length < minArg)
771         usage();
772 
773       String tempStr = argv[0].toLowerCase();
774       if (tempStr.endsWith(".jpg") || tempStr.endsWith(".jpeg"))
775         decompOnly = true;
776 
777       System.out.println("");
778 
779       if (!decompOnly) {
780         minArg = 2;
781         if (argv.length < minArg)
782           usage();
783         try {
784           minQual = Integer.parseInt(argv[1]);
785         } catch (NumberFormatException e) {}
786         if (minQual < 1 || minQual > 100)
787           throw new Exception("Quality must be between 1 and 100.");
788         int dashIndex = argv[1].indexOf('-');
789         if (dashIndex > 0 && argv[1].length() > dashIndex + 1) {
790           try {
791             maxQual = Integer.parseInt(argv[1].substring(dashIndex + 1));
792           } catch (NumberFormatException e) {}
793         }
794         if (maxQual < 1 || maxQual > 100)
795           maxQual = minQual;
796       }
797 
798       if (argv.length > minArg) {
799         for (int i = minArg; i < argv.length; i++) {
800           if (argv[i].equalsIgnoreCase("-tile")) {
801             doTile = true;  xformOpt |= TJTransform.OPT_CROP;
802           } else if (argv[i].equalsIgnoreCase("-fastupsample")) {
803             System.out.println("Using fast upsampling code\n");
804             flags |= TJ.FLAG_FASTUPSAMPLE;
805           } else if (argv[i].equalsIgnoreCase("-fastdct")) {
806             System.out.println("Using fastest DCT/IDCT algorithm\n");
807             flags |= TJ.FLAG_FASTDCT;
808           } else if (argv[i].equalsIgnoreCase("-accuratedct")) {
809             System.out.println("Using most accurate DCT/IDCT algorithm\n");
810             flags |= TJ.FLAG_ACCURATEDCT;
811           } else if (argv[i].equalsIgnoreCase("-progressive")) {
812             System.out.println("Using progressive entropy coding\n");
813             flags |= TJ.FLAG_PROGRESSIVE;
814           } else if (argv[i].equalsIgnoreCase("-rgb"))
815             pf = TJ.PF_RGB;
816           else if (argv[i].equalsIgnoreCase("-rgbx"))
817             pf = TJ.PF_RGBX;
818           else if (argv[i].equalsIgnoreCase("-bgr"))
819             pf = TJ.PF_BGR;
820           else if (argv[i].equalsIgnoreCase("-bgrx"))
821             pf = TJ.PF_BGRX;
822           else if (argv[i].equalsIgnoreCase("-xbgr"))
823             pf = TJ.PF_XBGR;
824           else if (argv[i].equalsIgnoreCase("-xrgb"))
825             pf = TJ.PF_XRGB;
826           else if (argv[i].equalsIgnoreCase("-bottomup"))
827             flags |= TJ.FLAG_BOTTOMUP;
828           else if (argv[i].equalsIgnoreCase("-quiet"))
829             quiet = 1;
830           else if (argv[i].equalsIgnoreCase("-qq"))
831             quiet = 2;
832           else if (argv[i].equalsIgnoreCase("-scale") && i < argv.length - 1) {
833             int temp1 = 0, temp2 = 0;
834             boolean match = false, scanned = true;
835             Scanner scanner = new Scanner(argv[++i]).useDelimiter("/");
836 
837             try {
838               temp1 = scanner.nextInt();
839               temp2 = scanner.nextInt();
840             } catch (Exception e) {}
841             if (temp2 <= 0) temp2 = 1;
842             if (temp1 > 0) {
843               TJScalingFactor[] scalingFactors = TJ.getScalingFactors();
844 
845               for (int j = 0; j < scalingFactors.length; j++) {
846                 if ((double)temp1 / (double)temp2 ==
847                     (double)scalingFactors[j].getNum() /
848                     (double)scalingFactors[j].getDenom()) {
849                   sf = scalingFactors[j];
850                   match = true;  break;
851                 }
852               }
853               if (!match) usage();
854             } else
855               usage();
856           } else if (argv[i].equalsIgnoreCase("-hflip"))
857             xformOp = TJTransform.OP_HFLIP;
858           else if (argv[i].equalsIgnoreCase("-vflip"))
859             xformOp = TJTransform.OP_VFLIP;
860           else if (argv[i].equalsIgnoreCase("-transpose"))
861             xformOp = TJTransform.OP_TRANSPOSE;
862           else if (argv[i].equalsIgnoreCase("-transverse"))
863             xformOp = TJTransform.OP_TRANSVERSE;
864           else if (argv[i].equalsIgnoreCase("-rot90"))
865             xformOp = TJTransform.OP_ROT90;
866           else if (argv[i].equalsIgnoreCase("-rot180"))
867             xformOp = TJTransform.OP_ROT180;
868           else if (argv[i].equalsIgnoreCase("-rot270"))
869             xformOp = TJTransform.OP_ROT270;
870           else if (argv[i].equalsIgnoreCase("-grayscale"))
871             xformOpt |= TJTransform.OPT_GRAY;
872           else if (argv[i].equalsIgnoreCase("-nooutput"))
873             xformOpt |= TJTransform.OPT_NOOUTPUT;
874           else if (argv[i].equalsIgnoreCase("-copynone"))
875             xformOpt |= TJTransform.OPT_COPYNONE;
876           else if (argv[i].equalsIgnoreCase("-benchtime") &&
877                    i < argv.length - 1) {
878             double temp = -1;
879 
880             try {
881               temp = Double.parseDouble(argv[++i]);
882             } catch (NumberFormatException e) {}
883             if (temp > 0.0)
884               benchTime = temp;
885             else
886               usage();
887           } else if (argv[i].equalsIgnoreCase("-warmup") &&
888                      i < argv.length - 1) {
889             double temp = -1;
890 
891             try {
892               temp = Double.parseDouble(argv[++i]);
893             } catch (NumberFormatException e) {}
894             if (temp >= 0.0) {
895               warmup = temp;
896               System.out.format("Warmup time = %.1f seconds\n\n", warmup);
897             } else
898               usage();
899           } else if (argv[i].equalsIgnoreCase("-yuv")) {
900             System.out.println("Testing YUV planar encoding/decoding\n");
901             doYUV = true;
902           } else if (argv[i].equalsIgnoreCase("-yuvpad") &&
903                      i < argv.length - 1) {
904             int temp = 0;
905 
906             try {
907               temp = Integer.parseInt(argv[++i]);
908             } catch (NumberFormatException e) {}
909             if (temp >= 1)
910               yuvPad = temp;
911           } else if (argv[i].equalsIgnoreCase("-subsamp") &&
912                      i < argv.length - 1) {
913             i++;
914             if (argv[i].toUpperCase().startsWith("G"))
915               subsamp = TJ.SAMP_GRAY;
916             else if (argv[i].equals("444"))
917               subsamp = TJ.SAMP_444;
918             else if (argv[i].equals("422"))
919               subsamp = TJ.SAMP_422;
920             else if (argv[i].equals("440"))
921               subsamp = TJ.SAMP_440;
922             else if (argv[i].equals("420"))
923               subsamp = TJ.SAMP_420;
924             else if (argv[i].equals("411"))
925               subsamp = TJ.SAMP_411;
926           } else if (argv[i].equalsIgnoreCase("-componly"))
927             compOnly = true;
928           else if (argv[i].equalsIgnoreCase("-nowrite"))
929             write = false;
930           else if (argv[i].equalsIgnoreCase("-stoponwarning"))
931             flags |= TJ.FLAG_STOPONWARNING;
932           else usage();
933         }
934       }
935 
936       if (sf == null)
937         sf = new TJScalingFactor(1, 1);
938 
939       if ((sf.getNum() != 1 || sf.getDenom() != 1) && doTile) {
940         System.out.println("Disabling tiled compression/decompression tests, because those tests do not");
941         System.out.println("work when scaled decompression is enabled.");
942         doTile = false;
943       }
944 
945       if (!decompOnly) {
946         int[] width = new int[1], height = new int[1];
947 
948         srcBuf = loadImage(argv[0], width, height, pf);
949         w = width[0];  h = height[0];
950         int index = -1;
951         if ((index = argv[0].lastIndexOf('.')) >= 0)
952           argv[0] = argv[0].substring(0, index);
953       }
954 
955       if (quiet == 1 && !decompOnly) {
956         System.out.println("All performance values in Mpixels/sec\n");
957         System.out.format("Bitmap     JPEG     JPEG  %s  %s   ",
958                           (doTile ? "Tile " : "Image"),
959                           (doTile ? "Tile " : "Image"));
960         if (doYUV)
961           System.out.print("Encode  ");
962         System.out.print("Comp    Comp    Decomp  ");
963         if (doYUV)
964           System.out.print("Decode");
965         System.out.print("\n");
966         System.out.print("Format     Subsamp  Qual  Width  Height  ");
967         if (doYUV)
968           System.out.print("Perf    ");
969         System.out.print("Perf    Ratio   Perf    ");
970         if (doYUV)
971           System.out.print("Perf");
972         System.out.println("\n");
973       }
974 
975       if (decompOnly) {
976         decompTest(argv[0]);
977         System.out.println("");
978         System.exit(retval);
979       }
980 
981       System.gc();
982       if (subsamp >= 0 && subsamp < TJ.NUMSAMP) {
983         for (int i = maxQual; i >= minQual; i--)
984           fullTest(srcBuf, w, h, subsamp, i, argv[0]);
985         System.out.println("");
986       } else {
987         for (int i = maxQual; i >= minQual; i--)
988           fullTest(srcBuf, w, h, TJ.SAMP_GRAY, i, argv[0]);
989         System.out.println("");
990         System.gc();
991         for (int i = maxQual; i >= minQual; i--)
992           fullTest(srcBuf, w, h, TJ.SAMP_420, i, argv[0]);
993         System.out.println("");
994         System.gc();
995         for (int i = maxQual; i >= minQual; i--)
996           fullTest(srcBuf, w, h, TJ.SAMP_422, i, argv[0]);
997         System.out.println("");
998         System.gc();
999         for (int i = maxQual; i >= minQual; i--)
1000           fullTest(srcBuf, w, h, TJ.SAMP_444, i, argv[0]);
1001         System.out.println("");
1002       }
1003 
1004     } catch (Exception e) {
1005       if (e instanceof TJException) {
1006         TJException tje = (TJException)e;
1007 
1008         System.out.println((tje.getErrorCode() == TJ.ERR_WARNING ?
1009                             "WARNING: " : "ERROR: ") + tje.getMessage());
1010       } else
1011         System.out.println("ERROR: " + e.getMessage());
1012       e.printStackTrace();
1013       retval = -1;
1014     }
1015 
1016     System.exit(retval);
1017   }
1018 
1019 }
1020