diff options
Diffstat (limited to 'libpng/contrib/libtests/timepng.c')
-rw-r--r-- | libpng/contrib/libtests/timepng.c | 608 |
1 files changed, 608 insertions, 0 deletions
diff --git a/libpng/contrib/libtests/timepng.c b/libpng/contrib/libtests/timepng.c new file mode 100644 index 00000000..3bcfde5a --- /dev/null +++ b/libpng/contrib/libtests/timepng.c @@ -0,0 +1,608 @@ +/* timepng.c + * + * Copyright (c) 2013,2016 John Cunningham Bowler + * + * Last changed in libpng 1.6.22 [May 26, 2016] + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * Load an arbitrary number of PNG files (from the command line, or, if there + * are no arguments on the command line, from stdin) then run a time test by + * reading each file by row or by image (possibly with transforms in the latter + * case). The only output is a time as a floating point number of seconds with + * 9 decimal digits. + */ +#define _POSIX_C_SOURCE 199309L /* for clock_gettime */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <limits.h> + +#include <time.h> + +#if defined(HAVE_CONFIG_H) && !defined(PNG_NO_CONFIG_H) +# include <config.h> +#endif + +/* Define the following to use this test against your installed libpng, rather + * than the one being built here: + */ +#ifdef PNG_FREESTANDING_TESTS +# include <png.h> +#else +# include "../../png.h" +#endif + +/* The following is to support direct compilation of this file as C++ */ +#ifdef __cplusplus +# define voidcast(type, value) static_cast<type>(value) +#else +# define voidcast(type, value) (value) +#endif /* __cplusplus */ + +/* 'CLOCK_PROCESS_CPUTIME_ID' is one of the clock timers for clock_gettime. It + * need not be supported even when clock_gettime is available. It returns the + * 'CPU' time the process has consumed. 'CPU' time is assumed to include time + * when the CPU is actually blocked by a pending cache fill but not time + * waiting for page faults. The attempt is to get a measure of the actual time + * the implementation takes to read a PNG ignoring the potentially very large IO + * overhead. + */ +#if defined (CLOCK_PROCESS_CPUTIME_ID) && defined(PNG_STDIO_SUPPORTED) &&\ + defined(PNG_EASY_ACCESS_SUPPORTED) &&\ + (PNG_LIBPNG_VER >= 10700 ? defined(PNG_READ_PNG_SUPPORTED) :\ + defined (PNG_SEQUENTIAL_READ_SUPPORTED) &&\ + defined(PNG_INFO_IMAGE_SUPPORTED)) + +typedef struct +{ + FILE *input; + FILE *output; +} io_data; + +static PNG_CALLBACK(void, read_and_copy, + (png_structp png_ptr, png_bytep buffer, png_size_t cb)) +{ + io_data *io = (io_data*)png_get_io_ptr(png_ptr); + + if (fread(buffer, cb, 1, io->input) != 1) + png_error(png_ptr, strerror(errno)); + + if (fwrite(buffer, cb, 1, io->output) != 1) + { + perror("temporary file"); + fprintf(stderr, "temporary file PNG write failed\n"); + exit(1); + } +} + +static void read_by_row(png_structp png_ptr, png_infop info_ptr, + FILE *write_ptr, FILE *read_ptr) +{ + /* These don't get freed on error, this is fine; the program immediately + * exits. + */ + png_bytep row = NULL, display = NULL; + io_data io_copy; + + if (write_ptr != NULL) + { + /* Set up for a copy to the temporary file: */ + io_copy.input = read_ptr; + io_copy.output = write_ptr; + png_set_read_fn(png_ptr, &io_copy, read_and_copy); + } + + png_read_info(png_ptr, info_ptr); + + { + png_size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr); + + row = voidcast(png_bytep,malloc(rowbytes)); + display = voidcast(png_bytep,malloc(rowbytes)); + + if (row == NULL || display == NULL) + png_error(png_ptr, "OOM allocating row buffers"); + + { + png_uint_32 height = png_get_image_height(png_ptr, info_ptr); + int passes = png_set_interlace_handling(png_ptr); + int pass; + + png_start_read_image(png_ptr); + + for (pass = 0; pass < passes; ++pass) + { + png_uint_32 y = height; + + /* NOTE: this trashes the row each time; interlace handling won't + * work, but this avoids memory thrashing for speed testing and is + * somewhat representative of an application that works row-by-row. + */ + while (y-- > 0) + png_read_row(png_ptr, row, display); + } + } + } + + /* Make sure to read to the end of the file: */ + png_read_end(png_ptr, info_ptr); + + /* Free this up: */ + free(row); + free(display); +} + +static PNG_CALLBACK(void, no_warnings, (png_structp png_ptr, + png_const_charp warning)) +{ + (void)png_ptr; + (void)warning; +} + +static int read_png(FILE *fp, png_int_32 transforms, FILE *write_file) +{ + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,0,0, + no_warnings); + png_infop info_ptr = NULL; + + if (png_ptr == NULL) + return 0; + + if (setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 0; + } + +# ifdef PNG_BENIGN_ERRORS_SUPPORTED + png_set_benign_errors(png_ptr, 1/*allowed*/); +# endif + png_init_io(png_ptr, fp); + + info_ptr = png_create_info_struct(png_ptr); + + if (info_ptr == NULL) + png_error(png_ptr, "OOM allocating info structure"); + + if (transforms < 0) + read_by_row(png_ptr, info_ptr, write_file, fp); + + else + png_read_png(png_ptr, info_ptr, transforms, NULL/*params*/); + + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 1; +} + +static int mytime(struct timespec *t) +{ + /* Do the timing using clock_gettime and the per-process timer. */ + if (!clock_gettime(CLOCK_PROCESS_CPUTIME_ID, t)) + return 1; + + perror("CLOCK_PROCESS_CPUTIME_ID"); + fprintf(stderr, "timepng: could not get the time\n"); + return 0; +} + +static int perform_one_test(FILE *fp, int nfiles, png_int_32 transforms) +{ + int i; + struct timespec before, after; + + /* Clear out all errors: */ + rewind(fp); + + if (mytime(&before)) + { + for (i=0; i<nfiles; ++i) + { + if (read_png(fp, transforms, NULL/*write*/)) + { + if (ferror(fp)) + { + perror("temporary file"); + fprintf(stderr, "file %d: error reading PNG data\n", i); + return 0; + } + } + + else + { + perror("temporary file"); + fprintf(stderr, "file %d: error from libpng\n", i); + return 0; + } + } + } + + else + return 0; + + if (mytime(&after)) + { + /* Work out the time difference and print it - this is the only output, + * so flush it immediately. + */ + unsigned long s = after.tv_sec - before.tv_sec; + long ns = after.tv_nsec - before.tv_nsec; + + if (ns < 0) + { + --s; + ns += 1000000000; + + if (ns < 0) + { + fprintf(stderr, "timepng: bad clock from kernel\n"); + return 0; + } + } + + printf("%lu.%.9ld\n", s, ns); + fflush(stdout); + if (ferror(stdout)) + { + fprintf(stderr, "timepng: error writing output\n"); + return 0; + } + + /* Successful return */ + return 1; + } + + else + return 0; +} + +static int add_one_file(FILE *fp, char *name) +{ + FILE *ip = fopen(name, "rb"); + + if (ip != NULL) + { + /* Read the file using libpng; this detects errors and also deals with + * files which contain data beyond the end of the file. + */ + int ok = 0; + fpos_t pos; + + if (fgetpos(fp, &pos)) + { + /* Fatal error reading the start: */ + perror("temporary file"); + fprintf(stderr, "temporary file fgetpos error\n"); + exit(1); + } + + if (read_png(ip, -1/*by row*/, fp/*output*/)) + { + if (ferror(ip)) + { + perror(name); + fprintf(stderr, "%s: read error\n", name); + } + + else + ok = 1; /* read ok */ + } + + else + fprintf(stderr, "%s: file not added\n", name); + + (void)fclose(ip); + + /* An error in the output is fatal; exit immediately: */ + if (ferror(fp)) + { + perror("temporary file"); + fprintf(stderr, "temporary file write error\n"); + exit(1); + } + + if (ok) + return 1; + + /* Did not read the file successfully, simply rewind the temporary + * file. This must happen after the ferror check above to avoid clearing + * the error. + */ + if (fsetpos(fp, &pos)) + { + perror("temporary file"); + fprintf(stderr, "temporary file fsetpos error\n"); + exit(1); + } + } + + else + { + /* file open error: */ + perror(name); + fprintf(stderr, "%s: open failed\n", name); + } + + return 0; /* file not added */ +} + +static void +usage(FILE *fp) +{ + if (fp != NULL) fclose(fp); + + fprintf(stderr, +"Usage:\n" +" timepng --assemble <assembly> {files}\n" +" Read the files into <assembly>, output the count. Options are ignored.\n" +" timepng --dissemble <assembly> <count> [options]\n" +" Time <count> files from <assembly>, additional files may not be given.\n" +" Otherwise:\n" +" Read the files into a temporary file and time the decode\n" +"Transforms:\n" +" --by-image: read by image with png_read_png\n" +" --<transform>: implies by-image, use PNG_TRANSFORM_<transform>\n" +" Otherwise: read by row using png_read_row (to a single row buffer)\n" + /* ISO C90 string length max 509 */);fprintf(stderr, +"{files}:\n" +" PNG files to copy into the assembly and time. Invalid files are skipped\n" +" with appropriate error messages. If no files are given the list of files\n" +" is read from stdin with each file name terminated by a newline\n" +"Output:\n" +" For --assemble the output is the name of the assembly file followed by the\n" +" count of the files it contains; the arguments for --dissemble. Otherwise\n" +" the output is the total decode time in seconds.\n"); + + exit(99); +} + +int main(int argc, char **argv) +{ + int ok = 0; + int err = 0; + int nfiles = 0; + int transforms = -1; /* by row */ + const char *assembly = NULL; + FILE *fp; + + if (argc > 2 && strcmp(argv[1], "--assemble") == 0) + { + /* Just build the test file, argv[2] is the file name. */ + assembly = argv[2]; + fp = fopen(assembly, "wb"); + if (fp == NULL) + { + perror(assembly); + fprintf(stderr, "timepng --assemble %s: could not open for write\n", + assembly); + usage(NULL); + } + + argv += 2; + argc -= 2; + } + + else if (argc > 3 && strcmp(argv[1], "--dissemble") == 0) + { + fp = fopen(argv[2], "rb"); + + if (fp == NULL) + { + perror(argv[2]); + fprintf(stderr, "timepng --dissemble %s: could not open for read\n", + argv[2]); + usage(NULL); + } + + nfiles = atoi(argv[3]); + if (nfiles <= 0) + { + fprintf(stderr, + "timepng --dissemble <file> <count>: %s is not a count\n", + argv[3]); + exit(99); + } +#ifdef __COVERITY__ + else + { + nfiles &= PNG_UINT_31_MAX; + } +#endif + + argv += 3; + argc -= 3; + } + + else /* Else use a temporary file */ + { +#ifndef __COVERITY__ + fp = tmpfile(); +#else + /* Experimental. Coverity says tmpfile() is insecure because it + * generates predictable names. + * + * It is possible to satisfy Coverity by using mkstemp(); however, + * any platform supporting mkstemp() undoubtedly has a secure tmpfile() + * implementation as well, and doesn't need the fix. Note that + * the fix won't work on platforms that don't support mkstemp(). + * + * https://www.securecoding.cert.org/confluence/display/c/ + * FIO21-C.+Do+not+create+temporary+files+in+shared+directories + * says that most historic implementations of tmpfile() provide + * only a limited number of possible temporary file names + * (usually 26) before file names are recycled. That article also + * provides a secure solution that unfortunately depends upon mkstemp(). + */ + char tmpfile[] = "timepng-XXXXXX"; + int filedes; + umask(0177); + filedes = mkstemp(tmpfile); + if (filedes < 0) + fp = NULL; + else + { + fp = fdopen(filedes,"w+"); + /* Hide the filename immediately and ensure that the file does + * not exist after the program ends + */ + (void) unlink(tmpfile); + } +#endif + + if (fp == NULL) + { + perror("tmpfile"); + fprintf(stderr, "timepng: could not open the temporary file\n"); + exit(1); /* not a user error */ + } + } + + /* Handle the transforms: */ + while (argc > 1 && argv[1][0] == '-' && argv[1][1] == '-') + { + const char *opt = *++argv + 2; + + --argc; + + /* Transforms turn on the by-image processing and maybe set some + * transforms: + */ + if (transforms == -1) + transforms = PNG_TRANSFORM_IDENTITY; + + if (strcmp(opt, "by-image") == 0) + { + /* handled above */ + } + +# define OPT(name) else if (strcmp(opt, #name) == 0)\ + transforms |= PNG_TRANSFORM_ ## name + + OPT(STRIP_16); + OPT(STRIP_ALPHA); + OPT(PACKING); + OPT(PACKSWAP); + OPT(EXPAND); + OPT(INVERT_MONO); + OPT(SHIFT); + OPT(BGR); + OPT(SWAP_ALPHA); + OPT(SWAP_ENDIAN); + OPT(INVERT_ALPHA); + OPT(STRIP_FILLER); + OPT(STRIP_FILLER_BEFORE); + OPT(STRIP_FILLER_AFTER); + OPT(GRAY_TO_RGB); + OPT(EXPAND_16); + OPT(SCALE_16); + + else + { + fprintf(stderr, "timepng %s: unrecognized transform\n", opt); + usage(fp); + } + } + + /* Handle the files: */ + if (argc > 1 && nfiles > 0) + usage(fp); /* Additional files not valid with --dissemble */ + + else if (argc > 1) + { + int i; + + for (i=1; i<argc; ++i) + { + if (nfiles == INT_MAX) + { + fprintf(stderr, "%s: skipped, too many files\n", argv[i]); + break; + } + + else if (add_one_file(fp, argv[i])) + ++nfiles; + } + } + + else if (nfiles == 0) /* Read from stdin withoout --dissemble */ + { + char filename[FILENAME_MAX+1]; + + while (fgets(filename, FILENAME_MAX+1, stdin)) + { + size_t len = strlen(filename); + + if (filename[len-1] == '\n') + { + filename[len-1] = 0; + if (nfiles == INT_MAX) + { + fprintf(stderr, "%s: skipped, too many files\n", filename); + break; + } + + else if (add_one_file(fp, filename)) + ++nfiles; + } + + else + { + fprintf(stderr, "timepng: file name too long: ...%s\n", + filename+len-32); + err = 1; + break; + } + } + + if (ferror(stdin)) + { + fprintf(stderr, "timepng: stdin: read error\n"); + err = 1; + } + } + + /* Perform the test, or produce the --assemble output: */ + if (!err) + { + if (nfiles > 0) + { + if (assembly != NULL) + { + if (fflush(fp) && !ferror(fp) && fclose(fp)) + { + perror(assembly); + fprintf(stderr, "%s: close failed\n", assembly); + } + + else + { + printf("%s %d\n", assembly, nfiles); + fflush(stdout); + ok = !ferror(stdout); + } + } + + else + { + ok = perform_one_test(fp, nfiles, transforms); + (void)fclose(fp); + } + } + + else + usage(fp); + } + + else + (void)fclose(fp); + + /* Exit code 0 on success. */ + return ok == 0; +} +#else /* !sufficient support */ +int main(void) { return 77; } +#endif /* !sufficient support */ |