diff options
Diffstat (limited to 'editor/pnmmontage.c')
-rw-r--r-- | editor/pnmmontage.c | 772 |
1 files changed, 496 insertions, 276 deletions
diff --git a/editor/pnmmontage.c b/editor/pnmmontage.c index 9eb2d7be..4ccff497 100644 --- a/editor/pnmmontage.c +++ b/editor/pnmmontage.c @@ -10,19 +10,136 @@ * implied warranty. */ +#define _BSD_SOURCE /* Make sure strdup() is in <string.h> */ +#include <assert.h> #include <limits.h> #include <string.h> -#include "pam.h" -#include "shhopt.h" -#include "nstring.h" +#include "pm_c_util.h" #include "mallocvar.h" +#include "nstring.h" +#include "shhopt.h" +#include "pam.h" + + + +struct cmdlineInfo { + const char * header; + const char * data; + const char * prefix; + unsigned int quality; + unsigned int quality2; + unsigned int nFiles; + const char ** inFileName; +}; + + + +static void +parseCommandLine(int argc, const char ** argv, + struct cmdlineInfo * const cmdlineP) { +/*---------------------------------------------------------------------------- + parse program command line described in Unix standard form by argc + and argv. Return the information in the options as *cmdlineP. + + If command line is internally inconsistent (invalid options, etc.), + issue error message to stderr and abort program. + + Note that the strings we return are stored in the storage that + was passed to us as the argv array. We also trash *argv. +-----------------------------------------------------------------------------*/ + optEntry * option_def; + /* Instructions to OptParseOptions3 on how to parse our options. */ + optStruct3 opt; + unsigned int dataSpec, headerSpec, prefixSpec, qualitySpec; + unsigned int option_def_index; + unsigned int i; + unsigned int q[10]; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENTRY */ + OPTENT3( 0, "data", OPT_STRING, &cmdlineP->data, &dataSpec, 0); + OPTENT3( 0, "header", OPT_STRING, &cmdlineP->header, &headerSpec, 0); + OPTENT3('q', "quality", OPT_UINT, &cmdlineP->quality, &qualitySpec, 0); + OPTENT3('p', "prefix", OPT_STRING, &cmdlineP->prefix, &prefixSpec, 0); + OPTENT3('0', "0", OPT_FLAG, NULL, &q[0], 0); + OPTENT3('1', "1", OPT_FLAG, NULL, &q[1], 0); + OPTENT3('2', "2", OPT_FLAG, NULL, &q[2], 0); + OPTENT3('3', "3", OPT_FLAG, NULL, &q[3], 0); + OPTENT3('4', "4", OPT_FLAG, NULL, &q[4], 0); + OPTENT3('5', "5", OPT_FLAG, NULL, &q[5], 0); + OPTENT3('6', "6", OPT_FLAG, NULL, &q[6], 0); + OPTENT3('7', "7", OPT_FLAG, NULL, &q[7], 0); + OPTENT3('8', "8", OPT_FLAG, NULL, &q[8], 0); + OPTENT3('9', "9", OPT_FLAG, NULL, &q[9], 0); + + opt.opt_table = option_def; + opt.short_allowed = FALSE; + opt.allowNegNum = FALSE; + + optParseOptions3(&argc, (char**)argv, opt, sizeof(opt), 0); + + if (!dataSpec) + cmdlineP->data = NULL; + if (!headerSpec) + cmdlineP->header = NULL; + if (!prefixSpec) + cmdlineP->prefix = ""; + if (!qualitySpec) + cmdlineP->quality = 200; + + + /* cmdlineP->quality2 is the greatest number from the --1, --2, etc. + options, or 5 if none of those are specified. + */ + cmdlineP->quality2 = 5; /* initial value */ + for (i = 0; i < 10; ++i) { + if (q[i]) + cmdlineP->quality2 = i; + } + + cmdlineP->nFiles = argc-1; -typedef struct { int f[sizeof(int) * 8 + 1]; } factorset; -typedef struct { int x; int y; } coord; + MALLOCARRAY_NOFAIL(cmdlineP->inFileName, argc-1); -static int qfactor = 200; -static int quality = 5; + for (i = 0; i < argc-1; ++i) { + if (cmdlineP->data && strchr(argv[i+1], ':')) + pm_error("Filename '%s' contains a \":\", which is forbidden " + "with -data", argv[i+1]); + else + cmdlineP->inFileName[i] = strdup(argv[1+1]); + } +} + + + +typedef struct { + int f[sizeof(int) * 8 + 1]; +} factorset; + +typedef struct { + int x; int y; +} coord; + +typedef struct { + coord ul; + coord size; +} rectangle; + +static coord +lr(rectangle const r) { +/*---------------------------------------------------------------------------- + Return the coordinates of the lower right corner of 'r' + (i.e. the pixel just beyond the lowest rightmost one). +-----------------------------------------------------------------------------*/ + coord retval; + + retval.x = r.ul.x + r.size.x; + retval.y = r.ul.y + r.size.y; + + return retval; +} static factorset factor(int n) @@ -63,109 +180,151 @@ gcd(int n, int m) return (g); } -static __inline__ int imax(int n, int m) { return (n > m ? n : m); } -static int -checkcollision(coord *locs, coord *szs, coord *cloc, coord *csz, int n) -{ - int i; - for (i = 0; i < n; ++i) - { - if ((locs[i].x < cloc->x + csz->x) && - (locs[i].y < cloc->y + csz->y) && - (locs[i].x + szs[i].x > cloc->x) && - (locs[i].y + szs[i].y > cloc->y)) - return (1); - } - return (0); + +static bool +overlaps(rectangle const a, + rectangle const b) { + + return + (a.ul.x < lr(b).x && a.ul.y < lr(b).y) && + (lr(a).x > b.ul.x && lr(a).y > b.ul.y); +} + + + +static bool +collides(rectangle const test, + const rectangle * const fieldList, + unsigned int const n) { +/*---------------------------------------------------------------------------- + Return true iff the rectangle 'test' overlaps any of the 'n' rectangles + fieldList[]. +-----------------------------------------------------------------------------*/ + unsigned int i; + + for (i = 0; i < n; ++i) + if (overlaps(fieldList[i], test)) + return true; + + return false; } + + static void -recursefindpack(coord *current, coord currentsz, coord *set, - coord *best, int minarea, int *maxarea, - int depth, int n, int xinc, int yinc) -{ - coord c; - if (depth == n) - { - if (currentsz.x * currentsz.y < *maxarea) - { - memcpy(best, current, sizeof(coord) * n); - *maxarea = currentsz.x * currentsz.y; - } - return; - } +recursefindpack(rectangle * const current, + coord const currentsz, + coord * const best, + unsigned int const minarea, + unsigned int * const maxareaP, + unsigned int const depth, + unsigned int const n, + unsigned int const xinc, + unsigned int const yinc, + unsigned int const quality, + unsigned int const qfactor) { + + if (depth == n) { + if (currentsz.x * currentsz.y < *maxareaP) { + unsigned int i; + for (i = 0; i < n; ++i) + best[i] = current[i].ul; + *maxareaP = currentsz.x * currentsz.y; + } + } else { + unsigned int i; - for (current[depth].x = 0; - imax(current[depth].x + set[depth].x, currentsz.x) * - imax(currentsz.y, set[depth].y) < *maxarea; - current[depth].x += xinc) - { - for (current[depth].y = 0; - imax(current[depth].x + set[depth].x, currentsz.x) * - imax(currentsz.y, current[depth].y + set[depth].y) < *maxarea; - current[depth].y += yinc) - { - c.x = imax(current[depth].x + set[depth].x, currentsz.x); - c.y = imax(current[depth].y + set[depth].y, currentsz.y); - if (!checkcollision(current, set, ¤t[depth], &set[depth], depth)) - { - recursefindpack(current, c, set, best, minarea, maxarea, - depth + 1, n, xinc, yinc); - if (*maxarea <= minarea) - return; - } + rectangle * const newP = ¤t[depth]; + + for (i = 0; ; ++i) { + for (newP->ul.x = 0, newP->ul.y = i * yinc; + newP->ul.y <= i * yinc;) { + + coord c; + + c.x = MAX(lr(*newP).x, currentsz.x); + c.y = MAX(lr(*newP).y, currentsz.y); + pm_message("current = (%u.%u, %u.%u) new = (%u.%u, %u.%u)", + current[0].ul.x, current[0].size.x, + current[0].ul.y, current[0].size.y, + newP->ul.x, newP->size.x, + newP->ul.y, newP->size.y); + if (!collides(*newP, current, depth)) { + pm_message("Depth %u: Doesn't collide at i=%u", depth,i); + recursefindpack(current, c, best, minarea, maxareaP, + depth + 1, n, xinc, yinc, + quality, qfactor); + if (*maxareaP <= minarea) + return; + } + if (newP->ul.x == (i - 1) * xinc) + newP->ul.y = 0; + if (newP->ul.x < i * xinc) + newP->ul.x += xinc; + else + newP->ul.y += yinc; + } + } } - } } + + static void -findpack(struct pam *imgs, int n, coord *coords) -{ - int minarea; - int i; - int rdiv; - int cdiv; - int minx = -1; - int miny = -1; - coord *current; - coord *set; - int z = INT_MAX; - coord c = { 0, 0 }; - - if (quality > 1) - { - for (minarea = i = 0; i < n; ++i) - minarea += imgs[i].height * imgs[i].width, - minx = imax(minx, imgs[i].width), - miny = imax(miny, imgs[i].height); +findpack(struct pam * const imgs, + unsigned int const n, + coord * const coords, + unsigned int const quality, + unsigned int const qfactor) { - minarea = minarea * qfactor / 100; - } - else - { - minarea = INT_MAX - 1; - } + int minarea; + int i; + int rdiv; + int cdiv; + int minx; + int miny; + rectangle * current; + unsigned int z; + coord c; + + minx = -1; miny = -1; /* initial value */ + z = UINT_MAX; /* initial value */ + c.x = 0; c.y = 0; /* initial value */ + + if (quality > 1) { + unsigned int realMinarea; + for (realMinarea = i = 0; i < n; ++i) + realMinarea += imgs[i].height * imgs[i].width, + minx = MAX(minx, imgs[i].width), + miny = MAX(miny, imgs[i].height); + + minarea = realMinarea * qfactor / 100; + } else { + minarea = INT_MAX - 1; + } - /* It's relatively easy to show that, if all the images - * are multiples of a particular size, then a best - * packing will always align the images on a grid of - * that size. - * - * This speeds computation immensely. - */ - for (rdiv = imgs[0].height, i = 1; i < n; ++i) - rdiv = gcd(imgs[i].height, rdiv); - - for (cdiv = imgs[0].width, i = 1; i < n; ++i) - cdiv = gcd(imgs[i].width, cdiv); - - MALLOCARRAY(current, n); - MALLOCARRAY(set, n); - for (i = 0; i < n; ++i) - set[i].x = imgs[i].width, - set[i].y = imgs[i].height; - recursefindpack(current, c, set, coords, minarea, &z, 0, n, cdiv, rdiv); + /* It's relatively easy to show that, if all the images + * are multiples of a particular size, then a best + * packing will always align the images on a grid of + * that size. + * + * This speeds computation immensely. + */ + for (rdiv = imgs[0].height, i = 1; i < n; ++i) + rdiv = gcd(imgs[i].height, rdiv); + + for (cdiv = imgs[0].width, i = 1; i < n; ++i) + cdiv = gcd(imgs[i].width, cdiv); + + MALLOCARRAY(current, n); + + for (i = 0; i < n; ++i) { + current[i].size.x = imgs[i].width; + current[i].size.y = imgs[i].height; + } + recursefindpack(current, c, coords, minarea, &z, 0, n, cdiv, rdiv, + quality, qfactor); } @@ -237,203 +396,264 @@ writePam(struct pam * const outpamP, -int -main(int argc, char **argv) -{ - struct pam *imgs; - struct pam outimg; - struct pam p; - int nfiles; - int i, j; - unsigned int q[10]; - coord *coords; - const char *headfname = NULL; - const char *datafname = NULL; - const char *prefix = ""; - FILE *header; - FILE *data; - char **names; - char *c; - - optEntry *option_def = malloc(100*sizeof(optEntry)); - /* Instructions to OptParseOptions3 on how to parse our options. - */ - optStruct3 opt; - - unsigned int option_def_index; - - option_def_index = 0; /* incremented by OPTENTRY */ - OPTENT3( 0, "data", OPT_STRING, &datafname, NULL, 0); - OPTENT3( 0, "header", OPT_STRING, &headfname, NULL, 0); - OPTENT3('q', "quality", OPT_UINT, &qfactor, NULL, 0); - OPTENT3('p', "prefix", OPT_STRING, &prefix, NULL, 0); - OPTENT3('0', "0", OPT_FLAG, NULL, &q[0], 0); - OPTENT3('1', "1", OPT_FLAG, NULL, &q[1], 0); - OPTENT3('2', "2", OPT_FLAG, NULL, &q[2], 0); - OPTENT3('3', "3", OPT_FLAG, NULL, &q[3], 0); - OPTENT3('4', "4", OPT_FLAG, NULL, &q[4], 0); - OPTENT3('5', "5", OPT_FLAG, NULL, &q[5], 0); - OPTENT3('6', "6", OPT_FLAG, NULL, &q[6], 0); - OPTENT3('7', "7", OPT_FLAG, NULL, &q[7], 0); - OPTENT3('8', "8", OPT_FLAG, NULL, &q[8], 0); - OPTENT3('9', "9", OPT_FLAG, NULL, &q[9], 0); - - opt.opt_table = option_def; - opt.short_allowed = FALSE; - opt.allowNegNum = FALSE; - - pnm_init(&argc, argv); - - /* Check for flags. */ - optParseOptions3(&argc, argv, opt, sizeof(opt), 0); - - if (headfname) - header = pm_openw(headfname); - - if (datafname) - data = pm_openw(datafname); - - for (i = 0; i < 10; ++i) - { - if (q[i]) - { - quality = i; - switch (quality) - { - case 0: case 1: break; - case 2: case 3: case 4: case 5: case 6: - qfactor = 100 * (8 - quality); - break; - case 7: qfactor = 150; break; - case 8: qfactor = 125; break; - case 9: qfactor = 100; break; - } +static void +writeData(FILE * const dataFileP, + unsigned int const width, + unsigned int const height, + unsigned int const nfiles, + const char ** const names, + const coord * const coords, + const struct pam * const imgs) { + + unsigned int i; + + fprintf(dataFileP, ":0:0:%u:%u\n", width, height); + + for (i = 0; i < nfiles; ++i) { + fprintf(dataFileP, "%s:%u:%u:%u:%u\n", names[i], coords[i].x, + coords[i].y, imgs[i].width, imgs[i].height); } - } +} - if (1 < argc) - nfiles = argc - 1; - else - nfiles = 1; - MALLOCARRAY(imgs, nfiles); - MALLOCARRAY(coords, nfiles); - MALLOCARRAY(names, nfiles); - - if (!imgs || !coords || !names) - pm_error("out of memory"); - if (1 < argc) - { - for (i = 0; i < nfiles; ++i) - { - if (strchr(argv[i+1], ':')) - { - imgs[i].file = pm_openr(strchr(argv[i+1], ':') + 1); - *strchr(argv[i+1], ':') = 0; - names[i] = argv[i+1]; - } - else - { - imgs[i].file = pm_openr(argv[i+1]); - names[i] = argv[i+1]; - } +static void +writeHeader(FILE * const headerFileP, + const char * const prefix, + unsigned int const width, + unsigned int const height, + unsigned int const nfiles, + const char ** const names, + const coord * const coords, + const struct pam * imgs) { + + unsigned int i; + + fprintf(headerFileP, "#define %sOVERALLX %u\n", prefix, width); + + fprintf(headerFileP, "#define %sOVERALLY %u\n", prefix, height); + + fprintf(headerFileP, "\n"); + + for (i = 0; i < nfiles; ++i) { + char * const buffer = strdup(names[i]); + coord const coord = coords[i]; + struct pam const img = imgs[i]; + + unsigned int j; + + *strchr(buffer, '.') = 0; + for (j = 0; buffer[j]; ++j) { + if (ISLOWER(buffer[j])) + buffer[j] = TOUPPER(buffer[j]); + } + fprintf(headerFileP, "#define %s%sX %u\n", + prefix, buffer, coord.x); + + fprintf(headerFileP, "#define %s%sY %u\n", + prefix, buffer, coord.y); + + fprintf(headerFileP, "#define %s%sSZX %u\n", + prefix, buffer, img.width); + + fprintf(headerFileP, "#define %s%sSZY %u\n", + prefix, buffer, img.height); + + fprintf(headerFileP, "\n"); } - } - else - { - imgs[0].file = stdin; - } +} - pnm_readpaminit(imgs[0].file, &imgs[0], PAM_STRUCT_SIZE(tuple_type)); - outimg.maxval = imgs[0].maxval; - outimg.format = imgs[0].format; - memcpy(outimg.tuple_type, imgs[0].tuple_type, sizeof(imgs[0].tuple_type)); - outimg.depth = imgs[0].depth; - for (i = 1; i < nfiles; ++i) - { - pnm_readpaminit(imgs[i].file, &imgs[i], PAM_STRUCT_SIZE(tuple_type)); - if (PAM_FORMAT_TYPE(imgs[i].format) > PAM_FORMAT_TYPE(outimg.format)) - outimg.format = imgs[i].format, - memcpy(outimg.tuple_type, imgs[i].tuple_type, - sizeof(imgs[i].tuple_type)); - outimg.maxval = imax(imgs[i].maxval, outimg.maxval); - outimg.depth = imax(imgs[i].depth, outimg.depth); - } - for (i = 0; i < nfiles - 1; ++i) - for (j = i + 1; j < nfiles; ++j) - if (imgs[j].width * imgs[j].height > imgs[i].width * imgs[i].height) - p = imgs[i], imgs[i] = imgs[j], imgs[j] = p, - c = names[i], names[i] = names[j], names[j] = c; +static void +sortImagesByArea(unsigned int const nfiles, + struct pam * const imgs, + const char ** const names) { +/*---------------------------------------------------------------------------- + Sort the images described by 'imgs' and 'names' in place, from largest + area to smallest. +-----------------------------------------------------------------------------*/ + /* Bubble sort */ + + unsigned int i; + + for (i = 0; i < nfiles - 1; ++i) { + unsigned int j; + for (j = i + 1; j < nfiles; ++j) { + if (imgs[j].width * imgs[j].height > + imgs[i].width * imgs[i].height) { + + struct pam p; + const char * c; + + p = imgs[i]; imgs[i] = imgs[j]; imgs[j] = p; + c = names[i]; names[i] = names[j]; names[j] = c; + } + } + } +} - findpack(imgs, nfiles, coords); - outimg.height = outimg.width = 0; - for (i = 0; i < nfiles; ++i) - { - outimg.width = imax(outimg.width, imgs[i].width + coords[i].x); - outimg.height = imax(outimg.height, imgs[i].height + coords[i].y); - } - outimg.size = sizeof(outimg); - outimg.len = sizeof(outimg); - outimg.file = stdout; - outimg.bytes_per_sample = 0; - for (i = outimg.maxval; i; i >>= 8) - ++outimg.bytes_per_sample; +static void +computeOutputType(sample * const maxvalP, + int * const formatP, + char * const tupleTypeP, + unsigned int * const depthP, + unsigned int const nfiles, + const struct pam * const imgs) { + + unsigned int i; + + sample maxval; + int format; + const char * tupleType; + unsigned int depth; + + assert(nfiles > 0); + + /* initial guesses */ + maxval = imgs[0].maxval; + format = imgs[0].format; + depth = imgs[0].depth; + tupleType = imgs[0].tuple_type; + + for (i = 1; i < nfiles; ++i) { + if (PAM_FORMAT_TYPE(imgs[i].format) > PAM_FORMAT_TYPE(format)) { + format = imgs[i].format; + tupleType = imgs[i].tuple_type; + } + maxval = MAX(maxval, imgs[i].maxval); + depth = MAX(depth, imgs[i].depth); + } + + *maxvalP = maxval; + *formatP = format; + *depthP = depth; + memcpy(tupleTypeP, tupleType, sizeof(imgs[0].tuple_type)); +} - writePam(&outimg, nfiles, coords, imgs); - if (datafname) - { - fprintf(data, ":0:0:%u:%u\n", outimg.width, outimg.height); - for (i = 0; i < nfiles; ++i) - { - fprintf(data, "%s:%u:%u:%u:%u\n", names[i], coords[i].x, - coords[i].y, imgs[i].width, imgs[i].height); +static void +computeOutputDimensions(int * const widthP, + int * const heightP, + unsigned int const nfiles, + const struct pam * const imgs, + const coord * const coords) { + + unsigned int widthGuess, heightGuess; + unsigned int i; + + widthGuess = 0; /* initial value */ + heightGuess = 0; /* initial value */ + + for (i = 0; i < nfiles; ++i) { + widthGuess = MAX(widthGuess, imgs[i].width + coords[i].x); + heightGuess = MAX(heightGuess, imgs[i].height + coords[i].y); } - } - if (headfname) - { - fprintf(header, "#define %sOVERALLX %u\n" - "#define %sOVERALLY %u\n" - "\n", - prefix, outimg.width, - prefix, outimg.height); + *widthP = widthGuess; + *heightP = heightGuess; +} - for (i = 0; i < nfiles; ++i) - { - *strchr(names[i], '.') = 0; - for (j = 0; names[i][j]; ++j) - { - if (ISLOWER(names[i][j])) - names[i][j] = TOUPPER(names[i][j]); - } - fprintf(header, "#define %s%sX %u\n" - "#define %s%sY %u\n" - "#define %s%sSZX %u\n" - "#define %s%sSZY %u\n" - "\n", - prefix, names[i], coords[i].x, - prefix, names[i], coords[i].y, - prefix, names[i], imgs[i].width, - prefix, names[i], imgs[i].height); + + +int +main(int argc, const char **argv) { + + struct cmdlineInfo cmdline; + struct pam * imgs; + struct pam outimg; + unsigned int nfiles; + coord * coords; + FILE * header; + FILE * data; + const char ** names; + unsigned int i; + unsigned int qfactor; /* In per cent */ + + pm_proginit(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + header = cmdline.header ? pm_openw(cmdline.header) : NULL; + data = cmdline.data ? pm_openw(cmdline.data) : NULL; + + switch (cmdline.quality2) { + case 0: case 1: + qfactor = cmdline.quality; + break; + case 2: case 3: case 4: case 5: case 6: + qfactor = 100 * (8 - cmdline.quality2); + break; + case 7: qfactor = 150; break; + case 8: qfactor = 125; break; + case 9: qfactor = 100; break; + default: pm_error("Internal error - impossible value of 'quality2': %u", + cmdline.quality2); } - } - for (i = 0; i < nfiles; ++i) - pm_close(imgs[i].file); - pm_close(stdout); + nfiles = cmdline.nFiles > 0 ? cmdline.nFiles : 1; - if (headfname) - pm_close(header); + MALLOCARRAY(imgs, nfiles); + MALLOCARRAY(coords, nfiles); + MALLOCARRAY(names, nfiles); + + if (!imgs || !coords || !names) + pm_error("out of memory"); + + if (cmdline.nFiles > 0) { + unsigned int i; + + for (i = 0; i < cmdline.nFiles; ++i) { + imgs[i].file = pm_openr(cmdline.inFileName[i]); + names[i] = strdup(cmdline.inFileName[i]); + } + } else { + imgs[0].file = stdin; + names[0] = strdup("stdin"); + } + + for (i = 0; i < nfiles; ++i) + pnm_readpaminit(imgs[i].file, &imgs[i], PAM_STRUCT_SIZE(tuple_type)); - if (datafname) - pm_close(data); + sortImagesByArea(nfiles, imgs, names); - return 0; + findpack(imgs, nfiles, coords, cmdline.quality2, qfactor); + + computeOutputType(&outimg.maxval, &outimg.format, outimg.tuple_type, + &outimg.depth, nfiles, imgs); + + computeOutputDimensions(&outimg.width, &outimg.height, nfiles, + imgs, coords); + + pnm_setminallocationdepth(&outimg, outimg.depth); + + outimg.size = sizeof(outimg); + outimg.len = sizeof(outimg); + outimg.file = stdout; + outimg.bytes_per_sample = 0; + for (i = outimg.maxval; i; i >>= 8) + ++outimg.bytes_per_sample; + + writePam(&outimg, nfiles, coords, imgs); + + if (data) + writeData(data, outimg.width, outimg.height, + nfiles, names, coords, imgs); + + if (header) + writeHeader(header, cmdline.prefix, outimg.width, outimg.height, + nfiles, names, coords, imgs); + + for (i = 0; i < nfiles; ++i) + pm_close(imgs[i].file); + pm_close(stdout); + if (header) + pm_close(header); + if (data) + pm_close(data); + + return 0; } |