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 TJBench()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 getTime()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 handleTJException(TJException e)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 formatName(int subsamp, int cs)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 sigFig(double val, int figs)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 loadImage(String fileName, int[] w, int[] h, int pixelFormat)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 saveImage(String fileName, byte[] srcBuf, int w, int h, int pixelFormat)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 */ decomp(byte[] srcBuf, byte[][] jpegBuf, int[] jpegSize, byte[] dstBuf, int w, int h, int subsamp, int jpegQual, String fileName, int tilew, int tileh)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 fullTest(byte[] srcBuf, int w, int h, int subsamp, int jpegQual, String fileName)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 decompTest(String fileName)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 usage()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 main(String[] argv)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 } 1027