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