2 * Copyright (C)2009-2014, 2016-2019, 2021-2023 D. R. Commander.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
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.
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.
32 import java.awt.image.*;
33 import javax.imageio.*;
36 import org.libjpegturbo.turbojpeg.*;
42 private static boolean stopOnWarning, bottomUp, fastUpsample, fastDCT,
43 optimize, progressive, limitScans, arithmetic, lossless;
44 private static int precision = 8, quiet = 0, pf = TJ.PF_BGR, yuvAlign = 1,
45 restartIntervalBlocks, restartIntervalRows = 0;
46 private static boolean compOnly, decompOnly, doTile, doYUV, write = true,
49 static final String[] PIXFORMATSTR = {
50 "RGB", "BGR", "RGBX", "BGRX", "XBGR", "XRGB", "GRAY", "", "", "", "",
54 static final String[] SUBNAME_LONG = {
55 "4:4:4", "4:2:2", "4:2:0", "GRAY", "4:4:0", "4:1:1", "4:4:1"
58 static final String[] SUBNAME = {
59 "444", "422", "420", "GRAY", "440", "411", "441"
62 static final String[] CSNAME = {
63 "RGB", "YCbCr", "GRAY", "CMYK", "YCCK"
66 private static TJScalingFactor sf = TJ.UNSCALED;
67 private static java.awt.Rectangle cr = TJ.UNCROPPED;
68 private static int xformOp = TJTransform.OP_NONE, xformOpt = 0;
69 private static double benchTime = 5.0, warmup = 1.0;
72 private static class DummyDCTFilter implements TJCustomFilter {
73 public void customFilter(ShortBuffer coeffBuffer, Rectangle bufferRegion,
74 Rectangle planeRegion, int componentID,
75 int transformID, TJTransform transform) {
76 for (int i = 0; i < bufferRegion.width * bufferRegion.height; i++)
77 coeffBuffer.put(i, (short)(-coeffBuffer.get(i)));
81 private static DummyDCTFilter customFilter;
84 @SuppressWarnings("checkstyle:HiddenField")
85 private static boolean isCropped(java.awt.Rectangle cr) {
86 return (cr.x != 0 || cr.y != 0 || cr.width != 0 || cr.height != 0);
89 private static int getCroppedWidth(int width) {
91 return (cr.width != 0 ? cr.width : sf.getScaled(width) - cr.x);
93 return sf.getScaled(width);
96 private static int getCroppedHeight(int height) {
98 return (cr.height != 0 ? cr.height : sf.getScaled(height) - cr.y);
100 return sf.getScaled(height);
104 static double getTime() {
105 return (double)System.nanoTime() / 1.0e9;
109 private static String tjErrorMsg;
110 private static int tjErrorCode = -1;
112 static void handleTJException(TJException e) throws TJException {
113 String errorMsg = e.getMessage();
114 int errorCode = e.getErrorCode();
116 if (!stopOnWarning && errorCode == TJ.ERR_WARNING) {
117 if (tjErrorMsg == null || !tjErrorMsg.equals(errorMsg) ||
118 tjErrorCode != errorCode) {
119 tjErrorMsg = errorMsg;
120 tjErrorCode = errorCode;
121 System.out.println("WARNING: " + errorMsg);
128 static String formatName(int subsamp, int cs) {
131 return String.format("%-2d/LOSSLESS ", precision);
132 else if (subsamp == TJ.SAMP_UNKNOWN)
133 return String.format("%-2d/%-5s ", precision, CSNAME[cs]);
135 return String.format("%-2d/%-5s/%-5s", precision, CSNAME[cs],
136 SUBNAME_LONG[subsamp]);
140 else if (subsamp == TJ.SAMP_UNKNOWN)
143 return CSNAME[cs] + " " + SUBNAME_LONG[subsamp];
148 static String sigFig(double val, int figs) {
150 int digitsAfterDecimal = figs - (int)Math.ceil(Math.log10(Math.abs(val)));
152 if (digitsAfterDecimal < 1)
153 format = new String("%.0f");
155 format = new String("%." + digitsAfterDecimal + "f");
156 return String.format(format, val);
160 /* Decompression test */
161 static void decomp(byte[][] jpegBufs, int[] jpegSizes, Object dstBuf, int w,
162 int h, int subsamp, int jpegQual, String fileName,
163 int tilew, int tileh) throws Exception {
164 String qualStr = new String(""), sizeStr, tempStr;
166 double elapsed, elapsedDecode;
167 int ps = TJ.getPixelSize(pf), i, iter = 0;
168 int scaledw, scaledh, pitch;
169 YUVImage yuvImage = null;
174 scaledw = sf.getScaled(w);
175 scaledh = sf.getScaled(h);
178 qualStr = new String((lossless ? "_PSV" : "_Q") + jpegQual);
180 tjd = new TJDecompressor();
181 tjd.set(TJ.PARAM_STOPONWARNING, stopOnWarning ? 1 : 0);
182 tjd.set(TJ.PARAM_BOTTOMUP, bottomUp ? 1 : 0);
183 tjd.set(TJ.PARAM_FASTUPSAMPLE, fastUpsample ? 1 : 0);
184 tjd.set(TJ.PARAM_FASTDCT, fastDCT ? 1 : 0);
185 tjd.set(TJ.PARAM_SCANLIMIT, limitScans ? 500 : 0);
189 tjd.setSourceImage(jpegBufs[0], jpegSizes[0]);
190 } catch (TJException e) { handleTJException(e); }
192 tjd.setScalingFactor(sf);
193 tjd.setCroppingRegion(cr);
195 scaledw = cr.width != 0 ? cr.width : scaledw - cr.x;
196 scaledh = cr.height != 0 ? cr.height : scaledh - cr.y;
198 pitch = scaledw * ps;
200 if (dstBuf == null) {
201 if ((long)pitch * (long)scaledh > (long)Integer.MAX_VALUE)
202 throw new Exception("Image is too large");
204 dstBuf = new byte[pitch * scaledh];
206 dstBuf = new short[pitch * scaledh];
209 /* Set the destination buffer to gray so we know whether the decompressor
210 attempted to write to it */
212 Arrays.fill((byte[])dstBuf, (byte)127);
213 else if (precision == 12)
214 Arrays.fill((short[])dstBuf, (short)2047);
216 Arrays.fill((short[])dstBuf, (short)32767);
219 int width = doTile ? tilew : scaledw;
220 int height = doTile ? tileh : scaledh;
222 yuvImage = new YUVImage(width, yuvAlign, height, subsamp);
223 Arrays.fill(yuvImage.getBuf(), (byte)127);
228 elapsed = elapsedDecode = 0.0;
231 double start = getTime();
233 for (int y = 0; y < h; y += tileh) {
234 for (int x = 0; x < w; x += tilew, tile++) {
235 int width = doTile ? Math.min(tilew, w - x) : scaledw;
236 int height = doTile ? Math.min(tileh, h - y) : scaledh;
239 tjd.setSourceImage(jpegBufs[tile], jpegSizes[tile]);
240 } catch (TJException e) { handleTJException(e); }
242 yuvImage.setBuf(yuvImage.getBuf(), width, yuvAlign, height,
245 tjd.decompressToYUV(yuvImage);
246 } catch (TJException e) { handleTJException(e); }
247 double startDecode = getTime();
248 tjd.setSourceImage(yuvImage);
250 tjd.decompress8((byte[])dstBuf, x, y, pitch, pf);
251 } catch (TJException e) { handleTJException(e); }
253 elapsedDecode += getTime() - startDecode;
257 tjd.decompress8((byte[])dstBuf, x, y, pitch, pf);
258 else if (precision == 12)
259 tjd.decompress12((short[])dstBuf, x, y, pitch, pf);
261 tjd.decompress16((short[])dstBuf, x, y, pitch, pf);
262 } catch (TJException e) { handleTJException(e); }
266 elapsed += getTime() - start;
269 if (elapsed >= benchTime)
271 } else if (elapsed >= warmup) {
273 elapsed = elapsedDecode = 0.0;
277 elapsed -= elapsedDecode;
279 for (i = 0; i < jpegBufs.length; i++)
281 jpegBufs = null; jpegSizes = null;
285 System.out.format("%-6s%s",
286 sigFig((double)(w * h) / 1000000. *
287 (double)iter / elapsed, 4),
288 quiet == 2 ? "\n" : " ");
290 System.out.format("%s\n",
291 sigFig((double)(w * h) / 1000000. *
292 (double)iter / elapsedDecode, 4));
294 System.out.print("\n");
296 System.out.format("%s --> Frame rate: %f fps\n",
297 (doYUV ? "Decomp to YUV" : "Decompress "),
298 (double)iter / elapsed);
299 System.out.format(" Throughput: %f Megapixels/sec\n",
300 (double)(w * h) / 1000000. * (double)iter / elapsed);
302 System.out.format("YUV Decode --> Frame rate: %f fps\n",
303 (double)iter / elapsedDecode);
304 System.out.format(" Throughput: %f Megapixels/sec\n",
305 (double)(w * h) / 1000000. *
306 (double)iter / elapsedDecode);
312 if (sf.getNum() != 1 || sf.getDenom() != 1)
313 sizeStr = new String(sf.getNum() + "_" + sf.getDenom());
314 else if (tilew != w || tileh != h)
315 sizeStr = new String(tilew + "x" + tileh);
317 sizeStr = new String("full");
319 tempStr = new String(fileName + "_" + sizeStr + (bmp ? ".bmp" : ".ppm"));
321 tempStr = new String(fileName + "_" +
322 (lossless ? "LOSSLS" : SUBNAME[subsamp]) + qualStr +
323 "_" + sizeStr + (bmp ? ".bmp" : ".ppm"));
325 tjd.saveImage(precision, tempStr, dstBuf, scaledw, 0, scaledh, pf);
329 static void fullTest(TJCompressor tjc, Object srcBuf, int w, int h,
330 int subsamp, int jpegQual, String fileName)
335 double start, elapsed, elapsedEncode;
336 int totalJpegSize = 0, tilew, tileh, i, iter;
337 int ps = TJ.getPixelSize(pf);
338 int ntilesw = 1, ntilesh = 1, pitch = w * ps;
339 String pfStr = PIXFORMATSTR[pf];
340 YUVImage yuvImage = null;
342 if ((long)pitch * (long)h > (long)Integer.MAX_VALUE)
343 throw new Exception("Image is too large");
345 tmpBuf = new byte[pitch * h];
347 tmpBuf = new short[pitch * h];
350 System.out.format(">>>>> %s (%s) <--> %d-bit JPEG (%s %s%d) <<<<<\n",
351 pfStr, bottomUp ? "Bottom-up" : "Top-down", precision,
352 lossless ? "Lossless" : SUBNAME_LONG[subsamp],
353 lossless ? "PSV" : "Q", jpegQual);
355 tjc.set(TJ.PARAM_SUBSAMP, subsamp);
356 tjc.set(TJ.PARAM_FASTDCT, fastDCT ? 1 : 0);
357 tjc.set(TJ.PARAM_OPTIMIZE, optimize ? 1 : 0);
358 tjc.set(TJ.PARAM_PROGRESSIVE, progressive ? 1 : 0);
359 tjc.set(TJ.PARAM_ARITHMETIC, arithmetic ? 1 : 0);
360 tjc.set(TJ.PARAM_LOSSLESS, lossless ? 1 : 0);
362 tjc.set(TJ.PARAM_LOSSLESSPSV, jpegQual);
364 tjc.set(TJ.PARAM_QUALITY, jpegQual);
365 tjc.set(TJ.PARAM_RESTARTBLOCKS, restartIntervalBlocks);
366 tjc.set(TJ.PARAM_RESTARTROWS, restartIntervalRows);
368 for (tilew = doTile ? 8 : w, tileh = doTile ? 8 : h; ;
369 tilew *= 2, tileh *= 2) {
374 ntilesw = (w + tilew - 1) / tilew;
375 ntilesh = (h + tileh - 1) / tileh;
378 new byte[ntilesw * ntilesh][TJ.bufSize(tilew, tileh, subsamp)];
379 jpegSizes = new int[ntilesw * ntilesh];
381 /* Compression test */
383 System.out.format("%-4s(%s) %-2d/%-6s %-3d ", pfStr,
384 bottomUp ? "BU" : "TD", precision,
385 lossless ? "LOSSLS" : SUBNAME_LONG[subsamp],
387 if (precision == 8) {
388 for (i = 0; i < h; i++)
389 System.arraycopy((byte[])srcBuf, w * ps * i, (byte[])tmpBuf,
392 for (i = 0; i < h; i++)
393 System.arraycopy((short[])srcBuf, w * ps * i, (short[])tmpBuf,
398 yuvImage = new YUVImage(tilew, yuvAlign, tileh, subsamp);
399 Arrays.fill(yuvImage.getBuf(), (byte)127);
404 elapsed = elapsedEncode = 0.0;
410 for (int y = 0; y < h; y += tileh) {
411 for (int x = 0; x < w; x += tilew, tile++) {
412 int width = Math.min(tilew, w - x);
413 int height = Math.min(tileh, h - y);
416 tjc.setSourceImage((byte[])srcBuf, x, y, width, pitch, height,
418 else if (precision == 12)
419 tjc.setSourceImage12((short[])srcBuf, x, y, width, pitch, height,
422 tjc.setSourceImage16((short[])srcBuf, x, y, width, pitch, height,
425 double startEncode = getTime();
427 yuvImage.setBuf(yuvImage.getBuf(), width, yuvAlign, height,
429 tjc.encodeYUV(yuvImage);
431 elapsedEncode += getTime() - startEncode;
432 tjc.setSourceImage(yuvImage);
434 tjc.compress(jpegBufs[tile]);
435 jpegSizes[tile] = tjc.getCompressedSize();
436 totalJpegSize += jpegSizes[tile];
439 elapsed += getTime() - start;
442 if (elapsed >= benchTime)
444 } else if (elapsed >= warmup) {
446 elapsed = elapsedEncode = 0.0;
450 elapsed -= elapsedEncode;
453 System.out.format("%-5d %-5d ", tilew, tileh);
456 System.out.format("%-6s%s",
457 sigFig((double)(w * h) / 1000000. *
458 (double)iter / elapsedEncode, 4),
459 quiet == 2 ? "\n" : " ");
460 System.out.format("%-6s%s",
461 sigFig((double)(w * h) / 1000000. *
462 (double)iter / elapsed, 4),
463 quiet == 2 ? "\n" : " ");
464 System.out.format("%-6s%s",
465 sigFig((double)(w * h * ps) / (double)totalJpegSize,
467 quiet == 2 ? "\n" : " ");
469 System.out.format("\n%s size: %d x %d\n", doTile ? "Tile" : "Image",
472 System.out.format("Encode YUV --> Frame rate: %f fps\n",
473 (double)iter / elapsedEncode);
474 System.out.format(" Output image size: %d bytes\n",
476 System.out.format(" Compression ratio: %f:1\n",
477 (double)(w * h * ps) / (double)yuvImage.getSize());
478 System.out.format(" Throughput: %f Megapixels/sec\n",
479 (double)(w * h) / 1000000. *
480 (double)iter / elapsedEncode);
481 System.out.format(" Output bit stream: %f Megabits/sec\n",
482 (double)yuvImage.getSize() * 8. / 1000000. *
483 (double)iter / elapsedEncode);
485 System.out.format("%s --> Frame rate: %f fps\n",
486 doYUV ? "Comp from YUV" : "Compress ",
487 (double)iter / elapsed);
488 System.out.format(" Output image size: %d bytes\n",
490 System.out.format(" Compression ratio: %f:1\n",
491 (double)(w * h * ps) / (double)totalJpegSize);
492 System.out.format(" Throughput: %f Megapixels/sec\n",
493 (double)(w * h) / 1000000. * (double)iter / elapsed);
494 System.out.format(" Output bit stream: %f Megabits/sec\n",
495 (double)totalJpegSize * 8. / 1000000. *
496 (double)iter / elapsed);
498 if (tilew == w && tileh == h && write) {
499 String tempStr = fileName + "_" +
500 (lossless ? "LOSSLS" : SUBNAME[subsamp]) + "_" +
501 (lossless ? "PSV" : "Q") + jpegQual + ".jpg";
502 FileOutputStream fos = new FileOutputStream(tempStr);
504 fos.write(jpegBufs[0], 0, jpegSizes[0]);
507 System.out.println("Reference image written to " + tempStr);
510 /* Decompression test */
512 decomp(jpegBufs, jpegSizes, tmpBuf, w, h, subsamp, jpegQual, fileName,
515 System.out.println("N/A");
517 if (tilew == w && tileh == h) break;
522 static void decompTest(String fileName) throws Exception {
524 byte[][] jpegBufs = null;
526 int[] jpegSizes = null;
528 double start, elapsed;
529 int ps = TJ.getPixelSize(pf), tile, x, y, iter;
531 int w = 0, h = 0, ntilesw = 1, ntilesh = 1, subsamp = -1, cs = -1;
533 int minTile = 16, tw, th, ttilew, ttileh, tntilesw, tntilesh, tsubsamp;
535 FileInputStream fis = new FileInputStream(fileName);
536 if (fis.getChannel().size() > (long)Integer.MAX_VALUE)
537 throw new Exception("Image is too large");
538 int srcSize = (int)fis.getChannel().size();
539 srcBuf = new byte[srcSize];
540 fis.read(srcBuf, 0, srcSize);
543 int index = fileName.lastIndexOf('.');
545 fileName = new String(fileName.substring(0, index));
547 tjt = new TJTransformer();
548 tjt.set(TJ.PARAM_STOPONWARNING, stopOnWarning ? 1 : 0);
549 tjt.set(TJ.PARAM_BOTTOMUP, bottomUp ? 1 : 0);
550 tjt.set(TJ.PARAM_FASTUPSAMPLE, fastUpsample ? 1 : 0);
551 tjt.set(TJ.PARAM_FASTDCT, fastDCT ? 1 : 0);
552 tjt.set(TJ.PARAM_SCANLIMIT, limitScans ? 500 : 0);
555 tjt.setSourceImage(srcBuf, srcSize);
556 } catch (TJException e) { handleTJException(e); }
559 subsamp = tjt.get(TJ.PARAM_SUBSAMP);
560 precision = tjt.get(TJ.PARAM_PRECISION);
561 cs = tjt.get(TJ.PARAM_COLORSPACE);
562 if (tjt.get(TJ.PARAM_PROGRESSIVE) == 1)
563 System.out.println("JPEG image uses progressive entropy coding\n");
564 if (tjt.get(TJ.PARAM_ARITHMETIC) == 1)
565 System.out.println("JPEG image uses arithmetic entropy coding\n");
566 tjt.set(TJ.PARAM_PROGRESSIVE, progressive ? 1 : 0);
567 tjt.set(TJ.PARAM_ARITHMETIC, arithmetic ? 1 : 0);
569 if (cs == TJ.CS_YCCK || cs == TJ.CS_CMYK) {
570 pf = TJ.PF_CMYK; ps = TJ.getPixelSize(pf);
573 if (tjt.get(TJ.PARAM_LOSSLESS) != 0)
576 tjt.setScalingFactor(sf);
577 tjt.setCroppingRegion(cr);
580 System.out.println("All performance values in Mpixels/sec\n");
581 System.out.format("Pixel JPEG %s %s Xform Comp Decomp ",
582 (doTile ? "Tile " : "Image"),
583 (doTile ? "Tile " : "Image"));
585 System.out.print("Decode");
586 System.out.print("\n");
587 System.out.print("Format Format Width Height Perf Ratio Perf ");
589 System.out.print("Perf");
590 System.out.println("\n");
591 } else if (quiet == 0)
592 System.out.format(">>>>> %d-bit JPEG (%s) --> %s (%s) <<<<<\n",
593 precision, formatName(subsamp, cs), PIXFORMATSTR[pf],
594 bottomUp ? "Bottom-up" : "Top-down");
597 if (subsamp == TJ.SAMP_UNKNOWN)
598 throw new Exception("Could not determine subsampling level of JPEG image");
599 minTile = Math.max(TJ.getMCUWidth(subsamp), TJ.getMCUHeight(subsamp));
601 for (int tilew = doTile ? minTile : w, tileh = doTile ? minTile : h; ;
602 tilew *= 2, tileh *= 2) {
607 ntilesw = (w + tilew - 1) / tilew;
608 ntilesh = (h + tileh - 1) / tileh;
610 tw = w; th = h; ttilew = tilew; ttileh = tileh;
612 System.out.format("\n%s size: %d x %d", (doTile ? "Tile" : "Image"),
614 if (sf.getNum() != 1 || sf.getDenom() != 1 || isCropped(cr))
615 System.out.format(" --> %d x %d", getCroppedWidth(tw),
616 getCroppedHeight(th));
617 System.out.println("");
618 } else if (quiet == 1) {
619 System.out.format("%-4s(%s) %-14s ", PIXFORMATSTR[pf],
620 bottomUp ? "BU" : "TD", formatName(subsamp, cs));
621 System.out.format("%-5d %-5d ", getCroppedWidth(tilew),
622 getCroppedHeight(tileh));
626 if (doTile || xformOp != TJTransform.OP_NONE || xformOpt != 0 ||
627 customFilter != null) {
628 if (xformOp == TJTransform.OP_TRANSPOSE ||
629 xformOp == TJTransform.OP_TRANSVERSE ||
630 xformOp == TJTransform.OP_ROT90 ||
631 xformOp == TJTransform.OP_ROT270) {
632 tw = h; th = w; ttilew = tileh; ttileh = tilew;
635 if (xformOp != TJTransform.OP_NONE &&
636 xformOp != TJTransform.OP_TRANSPOSE && subsamp == TJ.SAMP_UNKNOWN)
637 throw new Exception("Could not determine subsampling level of JPEG image");
638 if ((xformOpt & TJTransform.OPT_GRAY) != 0)
639 tsubsamp = TJ.SAMP_GRAY;
640 if (xformOp == TJTransform.OP_HFLIP ||
641 xformOp == TJTransform.OP_ROT180)
642 tw = tw - (tw % TJ.getMCUWidth(tsubsamp));
643 if (xformOp == TJTransform.OP_VFLIP ||
644 xformOp == TJTransform.OP_ROT180)
645 th = th - (th % TJ.getMCUHeight(tsubsamp));
646 if (xformOp == TJTransform.OP_TRANSVERSE ||
647 xformOp == TJTransform.OP_ROT90)
648 tw = tw - (tw % TJ.getMCUHeight(tsubsamp));
649 if (xformOp == TJTransform.OP_TRANSVERSE ||
650 xformOp == TJTransform.OP_ROT270)
651 th = th - (th % TJ.getMCUWidth(tsubsamp));
652 tntilesw = (tw + ttilew - 1) / ttilew;
653 tntilesh = (th + ttileh - 1) / ttileh;
655 if (xformOp == TJTransform.OP_TRANSPOSE ||
656 xformOp == TJTransform.OP_TRANSVERSE ||
657 xformOp == TJTransform.OP_ROT90 ||
658 xformOp == TJTransform.OP_ROT270) {
659 if (tsubsamp == TJ.SAMP_422)
660 tsubsamp = TJ.SAMP_440;
661 else if (tsubsamp == TJ.SAMP_440)
662 tsubsamp = TJ.SAMP_422;
663 else if (tsubsamp == TJ.SAMP_411)
664 tsubsamp = TJ.SAMP_441;
665 else if (tsubsamp == TJ.SAMP_441)
666 tsubsamp = TJ.SAMP_411;
669 TJTransform[] t = new TJTransform[tntilesw * tntilesh];
671 new byte[tntilesw * tntilesh][TJ.bufSize(ttilew, ttileh, subsamp)];
673 for (y = 0, tile = 0; y < th; y += ttileh) {
674 for (x = 0; x < tw; x += ttilew, tile++) {
675 t[tile] = new TJTransform();
676 t[tile].width = Math.min(ttilew, tw - x);
677 t[tile].height = Math.min(ttileh, th - y);
680 t[tile].op = xformOp;
681 t[tile].options = xformOpt | TJTransform.OPT_TRIM;
682 t[tile].cf = customFilter;
683 if ((t[tile].options & TJTransform.OPT_NOOUTPUT) != 0 &&
684 jpegBufs[tile] != null)
685 jpegBufs[tile] = null;
694 tjt.transform(jpegBufs, t);
695 } catch (TJException e) { handleTJException(e); }
696 jpegSizes = tjt.getTransformedSizes();
697 elapsed += getTime() - start;
700 if (elapsed >= benchTime)
702 } else if (elapsed >= warmup) {
709 for (tile = 0, totalJpegSize = 0; tile < tntilesw * tntilesh; tile++)
710 totalJpegSize += jpegSizes[tile];
713 System.out.format("%-6s%s%-6s%s",
714 sigFig((double)(w * h) / 1000000. / elapsed, 4),
715 quiet == 2 ? "\n" : " ",
716 sigFig((double)(w * h * ps) /
717 (double)totalJpegSize, 4),
718 quiet == 2 ? "\n" : " ");
720 System.out.format("Transform --> Frame rate: %f fps\n",
722 System.out.format(" Output image size: %d bytes\n",
724 System.out.format(" Compression ratio: %f:1\n",
725 (double)(w * h * ps) / (double)totalJpegSize);
726 System.out.format(" Throughput: %f Megapixels/sec\n",
727 (double)(w * h) / 1000000. / elapsed);
728 System.out.format(" Output bit stream: %f Megabits/sec\n",
729 (double)totalJpegSize * 8. / 1000000. / elapsed);
733 System.out.print("N/A N/A ");
734 jpegBufs = new byte[1][TJ.bufSize(ttilew, ttileh, subsamp)];
735 jpegSizes = new int[1];
736 jpegBufs[0] = srcBuf;
737 jpegSizes[0] = srcSize;
744 if ((xformOpt & TJTransform.OPT_NOOUTPUT) == 0)
745 decomp(jpegBufs, jpegSizes, null, tw, th, tsubsamp, 0, fileName,
748 System.out.println("N/A");
753 if (tilew == w && tileh == h) break;
758 static void usage() throws Exception {
760 TJScalingFactor[] scalingFactors = TJ.getScalingFactors();
761 int nsf = scalingFactors.length;
762 String className = new TJBench().getClass().getName();
764 System.out.println("\nUSAGE: java " + className);
765 System.out.println(" <Inputimage (BMP|PPM)> <Quality or PSV> [options]\n");
766 System.out.println(" java " + className);
767 System.out.println(" <Inputimage (JPG)> [options]");
769 System.out.println("\nGENERAL OPTIONS");
770 System.out.println("---------------");
771 System.out.println("-benchtime T = Run each benchmark for at least T seconds [default = 5.0]");
772 System.out.println("-bmp = Use Windows Bitmap format for output images [default = PPM]");
773 System.out.println(" ** 8-bit data precision only **");
774 System.out.println("-bottomup = Use bottom-up row order for packed-pixel source/destination buffers");
775 System.out.println("-componly = Stop after running compression tests. Do not test decompression.");
776 System.out.println("-lossless = Generate lossless JPEG images when compressing (implies");
777 System.out.println(" -subsamp 444). PSV is the predictor selection value (1-7).");
778 System.out.println("-nowrite = Do not write reference or output images (improves consistency of");
779 System.out.println(" benchmark results)");
780 System.out.println("-rgb, -bgr, -rgbx, -bgrx, -xbgr, -xrgb =");
781 System.out.println(" Use the specified pixel format for packed-pixel source/destination buffers");
782 System.out.println(" [default = BGR]");
783 System.out.println("-cmyk = Indirectly test YCCK JPEG compression/decompression");
784 System.out.println(" (use the CMYK pixel format for packed-pixel source/destination buffers)");
785 System.out.println("-precision N = Use N-bit data precision when compressing [N is 8, 12, or 16;");
786 System.out.println(" default = 8; if N is 16, then -lossless must also be specified]");
787 System.out.println(" (-precision 12 implies -optimize unless -arithmetic is also specified)");
788 System.out.println("-quiet = Output results in tabular rather than verbose format");
789 System.out.println("-restart N = When compressing, add a restart marker every N MCU rows (lossy) or");
790 System.out.println(" N sample rows (lossless) [default = 0 (no restart markers)]. Append 'B'");
791 System.out.println(" to specify the restart marker interval in MCU blocks (lossy) or samples");
792 System.out.println(" (lossless).");
793 System.out.println("-stoponwarning = Immediately discontinue the current");
794 System.out.println(" compression/decompression/transform operation if a warning (non-fatal");
795 System.out.println(" error) occurs");
796 System.out.println("-tile = Compress/transform the input image into separate JPEG tiles of varying");
797 System.out.println(" sizes (useful for measuring JPEG overhead)");
798 System.out.println("-warmup T = Run each benchmark for T seconds [default = 1.0] prior to starting");
799 System.out.println(" the timer, in order to prime the caches and thus improve the consistency");
800 System.out.println(" of the benchmark results");
802 System.out.println("\nLOSSY JPEG OPTIONS");
803 System.out.println("------------------");
804 System.out.println("-arithmetic = Use arithmetic entropy coding in JPEG images generated by");
805 System.out.println(" compression and transform operations (can be combined with -progressive)");
806 System.out.println("-crop WxH+X+Y = Decompress only the specified region of the JPEG image, where W");
807 System.out.println(" and H are the width and height of the region (0 = maximum possible width");
808 System.out.println(" or height) and X and Y are the left and upper boundary of the region, all");
809 System.out.println(" specified relative to the scaled image dimensions. X must be divible by");
810 System.out.println(" the scaled MCU width.");
811 System.out.println("-fastdct = Use the fastest DCT/IDCT algorithm available");
812 System.out.println("-fastupsample = Use the fastest chrominance upsampling algorithm available");
813 System.out.println("-optimize = Use optimized baseline entropy coding in JPEG images generated by");
814 System.out.println(" compession and transform operations");
815 System.out.println("-progressive = Use progressive entropy coding in JPEG images generated by");
816 System.out.println(" compression and transform operations (can be combined with -arithmetic;");
817 System.out.println(" implies -optimize unless -arithmetic is also specified)");
818 System.out.println("-limitscans = Refuse to decompress or transform progressive JPEG images that");
819 System.out.println(" have an unreasonably large number of scans");
820 System.out.println("-scale M/N = When decompressing, scale the width/height of the JPEG image by a");
821 System.out.print(" factor of M/N (M/N = ");
822 for (i = 0; i < nsf; i++) {
823 System.out.format("%d/%d", scalingFactors[i].getNum(),
824 scalingFactors[i].getDenom());
825 if (nsf == 2 && i != nsf - 1)
826 System.out.print(" or ");
829 System.out.print(", ");
831 System.out.print("or ");
833 if (i % 8 == 0 && i != 0)
834 System.out.print("\n ");
836 System.out.println(")");
837 System.out.println("-subsamp S = When compressing, use the specified level of chrominance");
838 System.out.println(" subsampling (S = 444, 422, 440, 420, 411, 441, or GRAY) [default = test");
839 System.out.println(" Grayscale, 4:2:0, 4:2:2, and 4:4:4 in sequence]");
840 System.out.println("-hflip, -vflip, -transpose, -transverse, -rot90, -rot180, -rot270 =");
841 System.out.println(" Perform the specified lossless transform operation on the input image");
842 System.out.println(" prior to decompression (these operations are mutually exclusive)");
843 System.out.println("-grayscale = Transform the input image into a grayscale JPEG image prior to");
844 System.out.println(" decompression (can be combined with the other transform operations above)");
845 System.out.println("-copynone = Do not copy any extra markers (including EXIF and ICC profile data)");
846 System.out.println(" when transforming the input image");
847 System.out.println("-yuv = Compress from/decompress to intermediate planar YUV images");
848 System.out.println(" ** 8-bit data precision only **");
849 System.out.println("-yuvpad N = The number of bytes by which each row in each plane of an");
850 System.out.println(" intermediate YUV image is evenly divisible (N must be a power of 2)");
851 System.out.println(" [default = 1]");
853 System.out.println("\nNOTE: If the quality/PSV is specified as a range (e.g. 90-100 or 1-4), a");
854 System.out.println("separate test will be performed for all values in the range.\n");
859 public static void main(String[] argv) {
860 Object srcBuf = null;
861 int w = 0, h = 0, minQual = -1, maxQual = -1;
862 int minArg = 1, retval = 0;
864 TJCompressor tjc = null;
868 if (argv.length < minArg)
871 String tempStr = argv[0].toLowerCase();
872 if (tempStr.endsWith(".jpg") || tempStr.endsWith(".jpeg"))
874 if (tempStr.endsWith(".bmp"))
877 System.out.println("");
881 if (argv.length < minArg)
883 String[] quals = argv[1].split("-", 2);
885 minQual = Integer.parseInt(quals[0]);
886 } catch (NumberFormatException e) {}
887 if (quals.length > 1) {
889 maxQual = Integer.parseInt(quals[1]);
890 } catch (NumberFormatException e) {}
892 if (maxQual < minQual)
896 if (argv.length > minArg) {
897 for (int i = minArg; i < argv.length; i++) {
898 if (argv[i].equalsIgnoreCase("-tile")) {
899 doTile = true; xformOpt |= TJTransform.OPT_CROP;
900 } else if (argv[i].equalsIgnoreCase("-precision") &&
901 i < argv.length - 1) {
905 temp = Integer.parseInt(argv[++i]);
906 } catch (NumberFormatException e) {}
907 if (temp == 8 || temp == 12 || temp == 16)
911 } else if (argv[i].equalsIgnoreCase("-fastupsample")) {
912 System.out.println("Using fastest upsampling algorithm\n");
914 } else if (argv[i].equalsIgnoreCase("-fastdct")) {
915 System.out.println("Using fastest DCT/IDCT algorithm\n");
917 } else if (argv[i].equalsIgnoreCase("-optimize")) {
918 System.out.println("Using optimized baseline entropy coding\n");
920 xformOpt |= TJTransform.OPT_OPTIMIZE;
921 } else if (argv[i].equalsIgnoreCase("-progressive")) {
922 System.out.println("Using progressive entropy coding\n");
924 xformOpt |= TJTransform.OPT_PROGRESSIVE;
925 } else if (argv[i].equalsIgnoreCase("-arithmetic")) {
926 System.out.println("Using arithmetic entropy coding\n");
928 xformOpt |= TJTransform.OPT_ARITHMETIC;
929 } else if (argv[i].equalsIgnoreCase("-lossless")) {
931 subsamp = TJ.SAMP_444;
932 } else if (argv[i].equalsIgnoreCase("-rgb"))
934 else if (argv[i].equalsIgnoreCase("-rgbx"))
936 else if (argv[i].equalsIgnoreCase("-bgr"))
938 else if (argv[i].equalsIgnoreCase("-bgrx"))
940 else if (argv[i].equalsIgnoreCase("-xbgr"))
942 else if (argv[i].equalsIgnoreCase("-xrgb"))
944 else if (argv[i].equalsIgnoreCase("-cmyk"))
946 else if (argv[i].equalsIgnoreCase("-bottomup"))
948 else if (argv[i].equalsIgnoreCase("-quiet"))
950 else if (argv[i].equalsIgnoreCase("-qq"))
952 else if (argv[i].equalsIgnoreCase("-scale") && i < argv.length - 1) {
953 int temp1 = 0, temp2 = 0;
954 boolean match = false, scanned = true;
955 Scanner scanner = new Scanner(argv[++i]).useDelimiter("/");
958 temp1 = scanner.nextInt();
959 temp2 = scanner.nextInt();
960 } catch (Exception e) {}
961 if (temp2 <= 0) temp2 = 1;
963 TJScalingFactor[] scalingFactors = TJ.getScalingFactors();
965 for (int j = 0; j < scalingFactors.length; j++) {
966 if ((double)temp1 / (double)temp2 ==
967 (double)scalingFactors[j].getNum() /
968 (double)scalingFactors[j].getDenom()) {
969 sf = scalingFactors[j];
976 } else if (argv[i].equalsIgnoreCase("-crop") &&
977 i < argv.length - 1) {
978 int temp1 = -1, temp2 = -1, temp3 = -1, temp4 = -1;
979 Scanner scanner = new Scanner(argv[++i]).useDelimiter("x|\\+");
982 temp1 = scanner.nextInt();
983 temp2 = scanner.nextInt();
984 temp3 = scanner.nextInt();
985 temp4 = scanner.nextInt();
986 } catch (Exception e) {}
988 if (temp1 < 0 || temp2 < 0 || temp3 < 0 || temp4 < 0)
990 cr.width = temp1; cr.height = temp2; cr.x = temp3; cr.y = temp4;
991 } else if (argv[i].equalsIgnoreCase("-hflip"))
992 xformOp = TJTransform.OP_HFLIP;
993 else if (argv[i].equalsIgnoreCase("-vflip"))
994 xformOp = TJTransform.OP_VFLIP;
995 else if (argv[i].equalsIgnoreCase("-transpose"))
996 xformOp = TJTransform.OP_TRANSPOSE;
997 else if (argv[i].equalsIgnoreCase("-transverse"))
998 xformOp = TJTransform.OP_TRANSVERSE;
999 else if (argv[i].equalsIgnoreCase("-rot90"))
1000 xformOp = TJTransform.OP_ROT90;
1001 else if (argv[i].equalsIgnoreCase("-rot180"))
1002 xformOp = TJTransform.OP_ROT180;
1003 else if (argv[i].equalsIgnoreCase("-rot270"))
1004 xformOp = TJTransform.OP_ROT270;
1005 else if (argv[i].equalsIgnoreCase("-grayscale"))
1006 xformOpt |= TJTransform.OPT_GRAY;
1007 else if (argv[i].equalsIgnoreCase("-custom"))
1008 customFilter = new DummyDCTFilter();
1009 else if (argv[i].equalsIgnoreCase("-nooutput"))
1010 xformOpt |= TJTransform.OPT_NOOUTPUT;
1011 else if (argv[i].equalsIgnoreCase("-copynone"))
1012 xformOpt |= TJTransform.OPT_COPYNONE;
1013 else if (argv[i].equalsIgnoreCase("-benchtime") &&
1014 i < argv.length - 1) {
1018 temp = Double.parseDouble(argv[++i]);
1019 } catch (NumberFormatException e) {}
1024 } else if (argv[i].equalsIgnoreCase("-warmup") &&
1025 i < argv.length - 1) {
1029 temp = Double.parseDouble(argv[++i]);
1030 } catch (NumberFormatException e) {}
1033 System.out.format("Warmup time = %.1f seconds\n\n", warmup);
1036 } else if (argv[i].equalsIgnoreCase("-bmp"))
1038 else if (argv[i].equalsIgnoreCase("-yuv")) {
1039 System.out.println("Testing planar YUV encoding/decoding\n");
1041 } else if (argv[i].equalsIgnoreCase("-yuvpad") &&
1042 i < argv.length - 1) {
1046 temp = Integer.parseInt(argv[++i]);
1047 } catch (NumberFormatException e) {}
1048 if (temp >= 1 && (temp & (temp - 1)) == 0)
1052 } else if (argv[i].equalsIgnoreCase("-subsamp") &&
1053 i < argv.length - 1) {
1055 if (argv[i].toUpperCase().startsWith("G"))
1056 subsamp = TJ.SAMP_GRAY;
1057 else if (argv[i].equals("444"))
1058 subsamp = TJ.SAMP_444;
1059 else if (argv[i].equals("422"))
1060 subsamp = TJ.SAMP_422;
1061 else if (argv[i].equals("440"))
1062 subsamp = TJ.SAMP_440;
1063 else if (argv[i].equals("420"))
1064 subsamp = TJ.SAMP_420;
1065 else if (argv[i].equals("411"))
1066 subsamp = TJ.SAMP_411;
1067 else if (argv[i].equals("441"))
1068 subsamp = TJ.SAMP_441;
1071 } else if (argv[i].equalsIgnoreCase("-componly"))
1073 else if (argv[i].equalsIgnoreCase("-nowrite"))
1075 else if (argv[i].equalsIgnoreCase("-limitscans"))
1077 else if (argv[i].equalsIgnoreCase("-restart") &&
1078 i < argv.length - 1) {
1080 String arg = argv[++i];
1081 Scanner scanner = new Scanner(arg).useDelimiter("b|B");
1084 temp = scanner.nextInt();
1085 } catch (Exception e) {}
1087 if (temp < 0 || temp > 65535 || scanner.hasNext())
1089 if (arg.endsWith("B") || arg.endsWith("b"))
1090 restartIntervalBlocks = temp;
1092 restartIntervalRows = temp;
1093 } else if (argv[i].equalsIgnoreCase("-stoponwarning"))
1094 stopOnWarning = true;
1099 if (precision == 16 && !lossless)
1100 throw new Exception("-lossless must be specified along with -precision 16");
1101 if (precision != 8 && doYUV)
1102 throw new Exception("-yuv requires 8-bit data precision");
1103 if (lossless && doYUV)
1104 throw new Exception("ERROR: -lossless and -yuv are incompatible");
1106 if ((sf.getNum() != 1 || sf.getDenom() != 1) && doTile) {
1107 System.out.println("Disabling tiled compression/decompression tests, because those tests do not");
1108 System.out.println("work when scaled decompression is enabled.\n");
1110 xformOpt &= (~TJTransform.OPT_CROP);
1113 if (isCropped(cr)) {
1115 throw new Exception("ERROR: Partial image decompression can only be enabled for JPEG input images");
1117 System.out.println("Disabling tiled compression/decompression tests, because those tests do not");
1118 System.out.println("work when partial image decompression is enabled.\n");
1120 xformOpt &= (~TJTransform.OPT_CROP);
1123 throw new Exception("ERROR: -crop and -yuv are incompatible");
1127 int[] width = new int[1], height = new int[1],
1128 pixelFormat = new int[1];
1130 tjc = new TJCompressor();
1131 tjc.set(TJ.PARAM_STOPONWARNING, stopOnWarning ? 1 : 0);
1132 tjc.set(TJ.PARAM_BOTTOMUP, bottomUp ? 1 : 0);
1134 pixelFormat[0] = pf;
1135 srcBuf = tjc.loadImage(precision, argv[0], width, 1, height,
1137 w = width[0]; h = height[0]; pf = pixelFormat[0];
1139 if ((index = argv[0].lastIndexOf('.')) >= 0)
1140 argv[0] = argv[0].substring(0, index);
1143 if (quiet == 1 && !decompOnly) {
1144 System.out.println("All performance values in Mpixels/sec\n");
1145 System.out.format("Pixel JPEG JPEG %s %s ",
1146 (doTile ? "Tile " : "Image"),
1147 (doTile ? "Tile " : "Image"));
1149 System.out.print("Encode ");
1150 System.out.print("Comp Comp Decomp ");
1152 System.out.print("Decode");
1153 System.out.print("\n");
1154 System.out.format("Format Format %s Width Height ",
1155 lossless ? "PSV " : "Qual");
1157 System.out.print("Perf ");
1158 System.out.print("Perf Ratio Perf ");
1160 System.out.print("Perf");
1161 System.out.println("\n");
1165 decompTest(argv[0]);
1166 System.out.println("");
1167 System.exit(retval);
1172 if (minQual < 1 || minQual > 7 || maxQual < 1 || maxQual > 7)
1173 throw new Exception("PSV must be between 1 and 7.");
1175 if (minQual < 1 || minQual > 100 || maxQual < 1 || maxQual > 100)
1176 throw new Exception("Quality must be between 1 and 100.");
1178 if (subsamp >= 0 && subsamp < TJ.NUMSAMP) {
1179 for (int i = maxQual; i >= minQual; i--)
1180 fullTest(tjc, srcBuf, w, h, subsamp, i, argv[0]);
1181 System.out.println("");
1183 if (pf != TJ.PF_CMYK) {
1184 for (int i = maxQual; i >= minQual; i--)
1185 fullTest(tjc, srcBuf, w, h, TJ.SAMP_GRAY, i, argv[0]);
1186 System.out.println("");
1189 for (int i = maxQual; i >= minQual; i--)
1190 fullTest(tjc, srcBuf, w, h, TJ.SAMP_420, i, argv[0]);
1191 System.out.println("");
1193 for (int i = maxQual; i >= minQual; i--)
1194 fullTest(tjc, srcBuf, w, h, TJ.SAMP_422, i, argv[0]);
1195 System.out.println("");
1197 for (int i = maxQual; i >= minQual; i--)
1198 fullTest(tjc, srcBuf, w, h, TJ.SAMP_444, i, argv[0]);
1199 System.out.println("");
1202 } catch (Exception e) {
1203 if (e instanceof TJException) {
1204 TJException tje = (TJException)e;
1206 System.out.println((tje.getErrorCode() == TJ.ERR_WARNING ?
1207 "WARNING: " : "ERROR: ") + tje.getMessage());
1209 System.out.println("ERROR: " + e.getMessage());
1210 e.printStackTrace();
1214 System.exit(retval);