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