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