| OLD | NEW |
| (Empty) | |
| 1 /* timepng.c |
| 2 * |
| 3 * Copyright (c) 2013,2016 John Cunningham Bowler |
| 4 * |
| 5 * Last changed in libpng 1.6.22 [(PENDING RELEASE)] |
| 6 * |
| 7 * This code is released under the libpng license. |
| 8 * For conditions of distribution and use, see the disclaimer |
| 9 * and license in png.h |
| 10 * |
| 11 * Load an arbitrary number of PNG files (from the command line, or, if there |
| 12 * are no arguments on the command line, from stdin) then run a time test by |
| 13 * reading each file by row or by image (possibly with transforms in the latter |
| 14 * case). The only output is a time as a floating point number of seconds with |
| 15 * 9 decimal digits. |
| 16 */ |
| 17 #define _POSIX_C_SOURCE 199309L /* for clock_gettime */ |
| 18 |
| 19 #include <stdlib.h> |
| 20 #include <stdio.h> |
| 21 #include <string.h> |
| 22 #include <errno.h> |
| 23 #include <limits.h> |
| 24 |
| 25 #include <time.h> |
| 26 |
| 27 #if defined(HAVE_CONFIG_H) && !defined(PNG_NO_CONFIG_H) |
| 28 # include <config.h> |
| 29 #endif |
| 30 |
| 31 /* Define the following to use this test against your installed libpng, rather |
| 32 * than the one being built here: |
| 33 */ |
| 34 #ifdef PNG_FREESTANDING_TESTS |
| 35 # include <png.h> |
| 36 #else |
| 37 # include "../../png.h" |
| 38 #endif |
| 39 |
| 40 /* The following is to support direct compilation of this file as C++ */ |
| 41 #ifdef __cplusplus |
| 42 # define voidcast(type, value) static_cast<type>(value) |
| 43 #else |
| 44 # define voidcast(type, value) (value) |
| 45 #endif /* __cplusplus */ |
| 46 |
| 47 #if ((defined(PNG_SEQUENTIAL_READ_SUPPORTED)) && defined(PNG_STDIO_SUPPORTED)\ |
| 48 && defined(PNG_EASY_ACCESS_SUPPORTED) && defined(PNG_INFO_IMAGE_SUPPORTED)) |
| 49 typedef struct |
| 50 { |
| 51 FILE *input; |
| 52 FILE *output; |
| 53 } io_data; |
| 54 |
| 55 static PNG_CALLBACK(void, read_and_copy, |
| 56 (png_structp png_ptr, png_bytep buffer, png_size_t cb)) |
| 57 { |
| 58 io_data *io = (io_data*)png_get_io_ptr(png_ptr); |
| 59 |
| 60 if (fread(buffer, cb, 1, io->input) != 1) |
| 61 png_error(png_ptr, strerror(errno)); |
| 62 |
| 63 if (fwrite(buffer, cb, 1, io->output) != 1) |
| 64 { |
| 65 perror("temporary file"); |
| 66 fprintf(stderr, "temporary file PNG write failed\n"); |
| 67 exit(1); |
| 68 } |
| 69 } |
| 70 |
| 71 static void read_by_row(png_structp png_ptr, png_infop info_ptr, |
| 72 FILE *write_ptr, FILE *read_ptr) |
| 73 { |
| 74 /* These don't get freed on error, this is fine; the program immediately |
| 75 * exits. |
| 76 */ |
| 77 png_bytep row = NULL, display = NULL; |
| 78 io_data io_copy; |
| 79 |
| 80 if (write_ptr != NULL) |
| 81 { |
| 82 /* Set up for a copy to the temporary file: */ |
| 83 io_copy.input = read_ptr; |
| 84 io_copy.output = write_ptr; |
| 85 png_set_read_fn(png_ptr, &io_copy, read_and_copy); |
| 86 } |
| 87 |
| 88 png_read_info(png_ptr, info_ptr); |
| 89 |
| 90 { |
| 91 png_size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr); |
| 92 |
| 93 row = voidcast(png_bytep,malloc(rowbytes)); |
| 94 display = voidcast(png_bytep,malloc(rowbytes)); |
| 95 |
| 96 if (row == NULL || display == NULL) |
| 97 png_error(png_ptr, "OOM allocating row buffers"); |
| 98 |
| 99 { |
| 100 png_uint_32 height = png_get_image_height(png_ptr, info_ptr); |
| 101 int passes = png_set_interlace_handling(png_ptr); |
| 102 int pass; |
| 103 |
| 104 png_start_read_image(png_ptr); |
| 105 |
| 106 for (pass = 0; pass < passes; ++pass) |
| 107 { |
| 108 png_uint_32 y = height; |
| 109 |
| 110 /* NOTE: this trashes the row each time; interlace handling won't |
| 111 * work, but this avoids memory thrashing for speed testing and is |
| 112 * somewhat representative of an application that works row-by-row. |
| 113 */ |
| 114 while (y-- > 0) |
| 115 png_read_row(png_ptr, row, display); |
| 116 } |
| 117 } |
| 118 } |
| 119 |
| 120 /* Make sure to read to the end of the file: */ |
| 121 png_read_end(png_ptr, info_ptr); |
| 122 |
| 123 /* Free this up: */ |
| 124 free(row); |
| 125 free(display); |
| 126 } |
| 127 |
| 128 static PNG_CALLBACK(void, no_warnings, (png_structp png_ptr, |
| 129 png_const_charp warning)) |
| 130 { |
| 131 (void)png_ptr; |
| 132 (void)warning; |
| 133 } |
| 134 |
| 135 static int read_png(FILE *fp, png_int_32 transforms, FILE *write_file) |
| 136 { |
| 137 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,0,0, |
| 138 no_warnings); |
| 139 png_infop info_ptr = NULL; |
| 140 |
| 141 if (png_ptr == NULL) |
| 142 return 0; |
| 143 |
| 144 if (setjmp(png_jmpbuf(png_ptr))) |
| 145 { |
| 146 png_destroy_read_struct(&png_ptr, &info_ptr, NULL); |
| 147 return 0; |
| 148 } |
| 149 |
| 150 # ifdef PNG_BENIGN_ERRORS_SUPPORTED |
| 151 png_set_benign_errors(png_ptr, 1/*allowed*/); |
| 152 # endif |
| 153 png_init_io(png_ptr, fp); |
| 154 |
| 155 info_ptr = png_create_info_struct(png_ptr); |
| 156 |
| 157 if (info_ptr == NULL) |
| 158 png_error(png_ptr, "OOM allocating info structure"); |
| 159 |
| 160 if (transforms < 0) |
| 161 read_by_row(png_ptr, info_ptr, write_file, fp); |
| 162 |
| 163 else |
| 164 png_read_png(png_ptr, info_ptr, transforms, NULL/*params*/); |
| 165 |
| 166 png_destroy_read_struct(&png_ptr, &info_ptr, NULL); |
| 167 return 1; |
| 168 } |
| 169 |
| 170 static int mytime(struct timespec *t) |
| 171 { |
| 172 /* Do the timing using clock_gettime and the per-process timer. */ |
| 173 if (!clock_gettime(CLOCK_PROCESS_CPUTIME_ID, t)) |
| 174 return 1; |
| 175 |
| 176 perror("CLOCK_PROCESS_CPUTIME_ID"); |
| 177 fprintf(stderr, "timepng: could not get the time\n"); |
| 178 return 0; |
| 179 } |
| 180 |
| 181 static int perform_one_test(FILE *fp, int nfiles, png_int_32 transforms) |
| 182 { |
| 183 int i; |
| 184 struct timespec before, after; |
| 185 |
| 186 /* Clear out all errors: */ |
| 187 rewind(fp); |
| 188 |
| 189 if (mytime(&before)) |
| 190 { |
| 191 for (i=0; i<nfiles; ++i) |
| 192 { |
| 193 if (read_png(fp, transforms, NULL/*write*/)) |
| 194 { |
| 195 if (ferror(fp)) |
| 196 { |
| 197 perror("temporary file"); |
| 198 fprintf(stderr, "file %d: error reading PNG data\n", i); |
| 199 return 0; |
| 200 } |
| 201 } |
| 202 |
| 203 else |
| 204 { |
| 205 perror("temporary file"); |
| 206 fprintf(stderr, "file %d: error from libpng\n", i); |
| 207 return 0; |
| 208 } |
| 209 } |
| 210 } |
| 211 |
| 212 else |
| 213 return 0; |
| 214 |
| 215 if (mytime(&after)) |
| 216 { |
| 217 /* Work out the time difference and print it - this is the only output, |
| 218 * so flush it immediately. |
| 219 */ |
| 220 unsigned long s = after.tv_sec - before.tv_sec; |
| 221 long ns = after.tv_nsec - before.tv_nsec; |
| 222 |
| 223 if (ns < 0) |
| 224 { |
| 225 --s; |
| 226 ns += 1000000000; |
| 227 |
| 228 if (ns < 0) |
| 229 { |
| 230 fprintf(stderr, "timepng: bad clock from kernel\n"); |
| 231 return 0; |
| 232 } |
| 233 } |
| 234 |
| 235 printf("%lu.%.9ld\n", s, ns); |
| 236 fflush(stdout); |
| 237 if (ferror(stdout)) |
| 238 { |
| 239 fprintf(stderr, "timepng: error writing output\n"); |
| 240 return 0; |
| 241 } |
| 242 |
| 243 /* Successful return */ |
| 244 return 1; |
| 245 } |
| 246 |
| 247 else |
| 248 return 0; |
| 249 } |
| 250 |
| 251 static int add_one_file(FILE *fp, char *name) |
| 252 { |
| 253 FILE *ip = fopen(name, "rb"); |
| 254 |
| 255 if (ip != NULL) |
| 256 { |
| 257 /* Read the file using libpng; this detects errors and also deals with |
| 258 * files which contain data beyond the end of the file. |
| 259 */ |
| 260 int ok = 0; |
| 261 fpos_t pos; |
| 262 |
| 263 if (fgetpos(fp, &pos)) |
| 264 { |
| 265 /* Fatal error reading the start: */ |
| 266 perror("temporary file"); |
| 267 fprintf(stderr, "temporary file fgetpos error\n"); |
| 268 exit(1); |
| 269 } |
| 270 |
| 271 if (read_png(ip, -1/*by row*/, fp/*output*/)) |
| 272 { |
| 273 if (ferror(ip)) |
| 274 { |
| 275 perror(name); |
| 276 fprintf(stderr, "%s: read error\n", name); |
| 277 } |
| 278 |
| 279 else |
| 280 ok = 1; /* read ok */ |
| 281 } |
| 282 |
| 283 else |
| 284 fprintf(stderr, "%s: file not added\n", name); |
| 285 |
| 286 (void)fclose(ip); |
| 287 |
| 288 /* An error in the output is fatal; exit immediately: */ |
| 289 if (ferror(fp)) |
| 290 { |
| 291 perror("temporary file"); |
| 292 fprintf(stderr, "temporary file write error\n"); |
| 293 exit(1); |
| 294 } |
| 295 |
| 296 if (ok) |
| 297 return 1; |
| 298 |
| 299 /* Did not read the file successfully, simply rewind the temporary |
| 300 * file. This must happen after the ferror check above to avoid clearing |
| 301 * the error. |
| 302 */ |
| 303 if (fsetpos(fp, &pos)) |
| 304 { |
| 305 perror("temporary file"); |
| 306 fprintf(stderr, "temporary file fsetpos error\n"); |
| 307 exit(1); |
| 308 } |
| 309 } |
| 310 |
| 311 else |
| 312 { |
| 313 /* file open error: */ |
| 314 perror(name); |
| 315 fprintf(stderr, "%s: open failed\n", name); |
| 316 } |
| 317 |
| 318 return 0; /* file not added */ |
| 319 } |
| 320 |
| 321 static void |
| 322 usage(FILE *fp) |
| 323 { |
| 324 if (fp != NULL) fclose(fp); |
| 325 |
| 326 fprintf(stderr, |
| 327 "Usage:\n" |
| 328 " timepng --assemble <assembly> {files}\n" |
| 329 " Read the files into <assembly>, output the count. Options are ignored.\n" |
| 330 " timepng --dissemble <assembly> <count> [options]\n" |
| 331 " Time <count> files from <assembly>, additional files may not be given.\n" |
| 332 " Otherwise:\n" |
| 333 " Read the files into a temporary file and time the decode\n" |
| 334 "Transforms:\n" |
| 335 " --by-image: read by image with png_read_png\n" |
| 336 " --<transform>: implies by-image, use PNG_TRANSFORM_<transform>\n" |
| 337 " Otherwise: read by row using png_read_row (to a single row buffer)\n" |
| 338 /* ISO C90 string length max 509 */);fprintf(stderr, |
| 339 "{files}:\n" |
| 340 " PNG files to copy into the assembly and time. Invalid files are skipped\n" |
| 341 " with appropriate error messages. If no files are given the list of files\n" |
| 342 " is read from stdin with each file name terminated by a newline\n" |
| 343 "Output:\n" |
| 344 " For --assemble the output is the name of the assembly file followed by the\n" |
| 345 " count of the files it contains; the arguments for --dissemble. Otherwise\n" |
| 346 " the output is the total decode time in seconds.\n"); |
| 347 |
| 348 exit(99); |
| 349 } |
| 350 |
| 351 int main(int argc, char **argv) |
| 352 { |
| 353 int ok = 0; |
| 354 int err = 0; |
| 355 int nfiles = 0; |
| 356 int transforms = -1; /* by row */ |
| 357 const char *assembly = NULL; |
| 358 FILE *fp; |
| 359 |
| 360 if (argc > 2 && strcmp(argv[1], "--assemble") == 0) |
| 361 { |
| 362 /* Just build the test file, argv[2] is the file name. */ |
| 363 assembly = argv[2]; |
| 364 fp = fopen(assembly, "wb"); |
| 365 if (fp == NULL) |
| 366 { |
| 367 perror(assembly); |
| 368 fprintf(stderr, "timepng --assemble %s: could not open for write\n", |
| 369 assembly); |
| 370 usage(NULL); |
| 371 } |
| 372 |
| 373 argv += 2; |
| 374 argc -= 2; |
| 375 } |
| 376 |
| 377 else if (argc > 3 && strcmp(argv[1], "--dissemble") == 0) |
| 378 { |
| 379 fp = fopen(argv[2], "rb"); |
| 380 |
| 381 if (fp == NULL) |
| 382 { |
| 383 perror(argv[2]); |
| 384 fprintf(stderr, "timepng --dissemble %s: could not open for read\n", |
| 385 argv[2]); |
| 386 usage(NULL); |
| 387 } |
| 388 |
| 389 nfiles = atoi(argv[3]); |
| 390 if (nfiles <= 0) |
| 391 { |
| 392 fprintf(stderr, |
| 393 "timepng --dissemble <file> <count>: %s is not a count\n", |
| 394 argv[3]); |
| 395 exit(99); |
| 396 } |
| 397 #ifdef __COVERITY__ |
| 398 else |
| 399 { |
| 400 nfiles &= PNG_UINT_31_MAX; |
| 401 } |
| 402 #endif |
| 403 |
| 404 argv += 3; |
| 405 argc -= 3; |
| 406 } |
| 407 |
| 408 else /* Else use a temporary file */ |
| 409 { |
| 410 #ifndef __COVERITY__ |
| 411 fp = tmpfile(); |
| 412 #else |
| 413 /* Experimental. Coverity says tmpfile() is insecure because it |
| 414 * generates predictable names. |
| 415 * |
| 416 * It is possible to satisfy Coverity by using mkstemp(); however, |
| 417 * any platform supporting mkstemp() undoubtedly has a secure tmpfile() |
| 418 * implementation as well, and doesn't need the fix. Note that |
| 419 * the fix won't work on platforms that don't support mkstemp(). |
| 420 * |
| 421 * https://www.securecoding.cert.org/confluence/display/c/ |
| 422 * FIO21-C.+Do+not+create+temporary+files+in+shared+directories |
| 423 * says that most historic implementations of tmpfile() provide |
| 424 * only a limited number of possible temporary file names |
| 425 * (usually 26) before file names are recycled. That article also |
| 426 * provides a secure solution that unfortunately depends upon mkstemp(). |
| 427 */ |
| 428 char tmpfile[] = "timepng-XXXXXX"; |
| 429 int filedes; |
| 430 umask(0177); |
| 431 filedes = mkstemp(tmpfile); |
| 432 if (filedes < 0) |
| 433 fp = NULL; |
| 434 else |
| 435 { |
| 436 fp = fdopen(filedes,"w+"); |
| 437 /* Hide the filename immediately and ensure that the file does |
| 438 * not exist after the program ends |
| 439 */ |
| 440 (void) unlink(tmpfile); |
| 441 } |
| 442 #endif |
| 443 |
| 444 if (fp == NULL) |
| 445 { |
| 446 perror("tmpfile"); |
| 447 fprintf(stderr, "timepng: could not open the temporary file\n"); |
| 448 exit(1); /* not a user error */ |
| 449 } |
| 450 } |
| 451 |
| 452 /* Handle the transforms: */ |
| 453 while (argc > 1 && argv[1][0] == '-' && argv[1][1] == '-') |
| 454 { |
| 455 const char *opt = *++argv + 2; |
| 456 |
| 457 --argc; |
| 458 |
| 459 /* Transforms turn on the by-image processing and maybe set some |
| 460 * transforms: |
| 461 */ |
| 462 if (transforms == -1) |
| 463 transforms = PNG_TRANSFORM_IDENTITY; |
| 464 |
| 465 if (strcmp(opt, "by-image") == 0) |
| 466 { |
| 467 /* handled above */ |
| 468 } |
| 469 |
| 470 # define OPT(name) else if (strcmp(opt, #name) == 0)\ |
| 471 transforms |= PNG_TRANSFORM_ ## name |
| 472 |
| 473 OPT(STRIP_16); |
| 474 OPT(STRIP_ALPHA); |
| 475 OPT(PACKING); |
| 476 OPT(PACKSWAP); |
| 477 OPT(EXPAND); |
| 478 OPT(INVERT_MONO); |
| 479 OPT(SHIFT); |
| 480 OPT(BGR); |
| 481 OPT(SWAP_ALPHA); |
| 482 OPT(SWAP_ENDIAN); |
| 483 OPT(INVERT_ALPHA); |
| 484 OPT(STRIP_FILLER); |
| 485 OPT(STRIP_FILLER_BEFORE); |
| 486 OPT(STRIP_FILLER_AFTER); |
| 487 OPT(GRAY_TO_RGB); |
| 488 OPT(EXPAND_16); |
| 489 OPT(SCALE_16); |
| 490 |
| 491 else |
| 492 { |
| 493 fprintf(stderr, "timepng %s: unrecognized transform\n", opt); |
| 494 usage(fp); |
| 495 } |
| 496 } |
| 497 |
| 498 /* Handle the files: */ |
| 499 if (argc > 1 && nfiles > 0) |
| 500 usage(fp); /* Additional files not valid with --dissemble */ |
| 501 |
| 502 else if (argc > 1) |
| 503 { |
| 504 int i; |
| 505 |
| 506 for (i=1; i<argc; ++i) |
| 507 { |
| 508 if (nfiles == INT_MAX) |
| 509 { |
| 510 fprintf(stderr, "%s: skipped, too many files\n", argv[i]); |
| 511 break; |
| 512 } |
| 513 |
| 514 else if (add_one_file(fp, argv[i])) |
| 515 ++nfiles; |
| 516 } |
| 517 } |
| 518 |
| 519 else if (nfiles == 0) /* Read from stdin withoout --dissemble */ |
| 520 { |
| 521 char filename[FILENAME_MAX+1]; |
| 522 |
| 523 while (fgets(filename, FILENAME_MAX+1, stdin)) |
| 524 { |
| 525 size_t len = strlen(filename); |
| 526 |
| 527 if (filename[len-1] == '\n') |
| 528 { |
| 529 filename[len-1] = 0; |
| 530 if (nfiles == INT_MAX) |
| 531 { |
| 532 fprintf(stderr, "%s: skipped, too many files\n", filename); |
| 533 break; |
| 534 } |
| 535 |
| 536 else if (add_one_file(fp, filename)) |
| 537 ++nfiles; |
| 538 } |
| 539 |
| 540 else |
| 541 { |
| 542 fprintf(stderr, "timepng: file name too long: ...%s\n", |
| 543 filename+len-32); |
| 544 err = 1; |
| 545 break; |
| 546 } |
| 547 } |
| 548 |
| 549 if (ferror(stdin)) |
| 550 { |
| 551 fprintf(stderr, "timepng: stdin: read error\n"); |
| 552 err = 1; |
| 553 } |
| 554 } |
| 555 |
| 556 /* Perform the test, or produce the --assemble output: */ |
| 557 if (!err) |
| 558 { |
| 559 if (nfiles > 0) |
| 560 { |
| 561 if (assembly != NULL) |
| 562 { |
| 563 if (fflush(fp) && !ferror(fp) && fclose(fp)) |
| 564 { |
| 565 perror(assembly); |
| 566 fprintf(stderr, "%s: close failed\n", assembly); |
| 567 } |
| 568 |
| 569 else |
| 570 { |
| 571 printf("%s %d\n", assembly, nfiles); |
| 572 fflush(stdout); |
| 573 ok = !ferror(stdout); |
| 574 } |
| 575 } |
| 576 |
| 577 else |
| 578 { |
| 579 ok = perform_one_test(fp, nfiles, transforms); |
| 580 (void)fclose(fp); |
| 581 } |
| 582 } |
| 583 |
| 584 else |
| 585 usage(fp); |
| 586 } |
| 587 |
| 588 else |
| 589 (void)fclose(fp); |
| 590 |
| 591 /* Exit code 0 on success. */ |
| 592 return ok == 0; |
| 593 } |
| 594 #else /* !sufficient support */ |
| 595 int main(void) { return 77; } |
| 596 #endif /* !sufficient support */ |
| OLD | NEW |