summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'libpng/contrib/libtests/timepng.c')
-rw-r--r--libpng/contrib/libtests/timepng.c608
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 */