/* Copyright (C) 2001-2019 Peter Selinger. This file is part of Potrace. It is free software and it is covered by the GNU General Public License. See the file COPYING for details. */ /* mkbitmap.c: a standalone program for converting greymaps to bitmaps while optionally applying the following enhancements: highpass filter (evening out background gradients), lowpass filter (smoothing foreground details), interpolated scaling, inversion. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "greymap.h" #include "bitmap_io.h" #include "platform.h" #define SAFE_CALLOC(var, n, typ) \ if ((var = (typ *)calloc(n, sizeof(typ))) == NULL) goto calloc_error /* structure to hold command line options */ struct info_s { char *outfile; /* output file */ char **infiles; /* input files */ int infilecount; /* how many input files? */ int invert; /* invert input? */ int highpass; /* use highpass filter? */ double lambda; /* highpass filter radius */ int lowpass; /* use lowpass filter? */ double lambda1; /* lowpass filter radius */ int scale; /* scaling factor */ int linear; /* linear scaling? */ int bilevel; /* convert to bilevel? */ double level; /* cutoff grey level */ const char *outext; /* default output file extension */ }; typedef struct info_s info_t; static info_t info; /* apply lowpass filter (an approximate Gaussian blur) to greymap. Lambda is the standard deviation of the kernel of the filter (i.e., the approximate filter radius). */ static void lowpass(greymap_t *gm, double lambda) { double f, g; double c, d; double B; int x, y; if (gm->h == 0 || gm->w == 0) { return; } /* calculate filter coefficients from given lambda */ B = 1+2/(lambda*lambda); c = B-sqrt(B*B-1); d = 1-c; for (y=0; yh; y++) { /* apply low-pass filter to row y */ /* left-to-right */ f = g = 0; for (x=0; xw; x++) { f = f*c + GM_UGET(gm, x, y)*d; g = g*c + f*d; GM_UPUT(gm, x, y, g); } /* right-to-left */ for (x=gm->w-1; x>=0; x--) { f = f*c + GM_UGET(gm, x, y)*d; g = g*c + f*d; GM_UPUT(gm, x, y, g); } /* left-to-right mop-up */ for (x=0; xw; x++) { f = f*c; g = g*c + f*d; if (f+g < 1/255.0) { break; } GM_UPUT(gm, x, y, GM_UGET(gm, x, y)+g); } } for (x=0; xw; x++) { /* apply low-pass filter to column x */ /* bottom-to-top */ f = g = 0; for (y=0; yh; y++) { f = f*c + GM_UGET(gm, x, y)*d; g = g*c + f*d; GM_UPUT(gm, x, y, g); } /* top-to-bottom */ for (y=gm->h-1; y>=0; y--) { f = f*c + GM_UGET(gm, x, y)*d; g = g*c + f*d; GM_UPUT(gm, x, y, g); } /* bottom-to-top mop-up */ for (y=0; yh; y++) { f = f*c; g = g*c + f*d; if (f+g < 1/255.0) { break; } GM_UPUT(gm, x, y, GM_UGET(gm, x, y)+g); } } } /* apply highpass filter to greymap. Return 0 on success, 1 on error with errno set. */ static int highpass(greymap_t *gm, double lambda) { greymap_t *gm1; double f; int x, y; if (gm->h == 0 || gm->w == 0) { return 0; } /* create a copy */ gm1 = gm_dup(gm); if (!gm1) { return 1; } /* apply lowpass filter to the copy */ lowpass(gm1, lambda); /* subtract copy from original */ for (y=0; yh; y++) { for (x=0; xw; x++) { f = GM_UGET(gm, x, y); f -= GM_UGET(gm1, x, y); f += 128; /* normalize! */ GM_UPUT(gm, x, y, f); } } gm_free(gm1); return 0; } /* Convert greymap to bitmap by using cutoff threshold c (0=black, 1=white). On error, return NULL with errno set. */ static potrace_bitmap_t *threshold(greymap_t *gm, double c) { int w, h; potrace_bitmap_t *bm_out = NULL; double c1; int x, y; double p; w = gm->w; h = gm->h; /* allocate output bitmap */ bm_out = bm_new(w, h); if (!bm_out) { return NULL; } /* thresholding */ c1 = c * 255; for (y=0; yw; h = gm->h; /* allocate output bitmap/greymap */ if (bilevel) { bm_out = bm_new(w*s, h*s); if (!bm_out) { return NULL; } c1 = c * 255; } else { gm_out = gm_new(w*s, h*s); if (!gm_out) { return NULL; } } /* interpolate */ for (i=0; i= c1 && p01 >= c1 && p10 >= c1 && p11 >= c1) { continue; } } /* the general case */ for (x=0; xw; h = gm->h; /* allocate output bitmap/greymap */ if (bilevel) { bm_out = bm_new(w*s, h*s); if (!bm_out) { goto calloc_error; } c1 = c * 255; } else { gm_out = gm_new(w*s, h*s); if (!gm_out) { goto calloc_error; } } /* pre-calculate interpolation polynomials */ for (k=0; k=w) { break; } for (i=0; i<3; i++) { for (k=0; k0) { /* end of file */ return; } fprintf(stderr, "" MKBITMAP ": %s: empty file\n", infile); exit(2); case -4: /* wrong magic */ if (count>0) { fprintf(stderr, "" MKBITMAP ": %s: warning: junk at end of file\n", infile); return; } fprintf(stderr, "" MKBITMAP ": %s: file format not recognized\n", infile); fprintf(stderr, "Possible input file formats are: pnm (pbm, pgm, ppm), bmp.\n"); exit(2); case 1: /* unexpected end of file */ fprintf(stderr, "" MKBITMAP ": %s: warning: premature end of file\n", infile); break; } if (info.invert) { for (y=0; yh; y++) { for (x=0; xw; x++) { GM_UPUT(gm, x, y, 255-GM_UGET(gm, x, y)); } } } if (info.highpass) { r = highpass(gm, info.lambda); if (r) { fprintf(stderr, "" MKBITMAP ": %s: %s\n", infile, strerror(errno)); exit(2); } } if (info.lowpass) { lowpass(gm, info.lambda1); } if (info.scale == 1 && info.bilevel) { /* no interpolation necessary */ sm = threshold(gm, info.level); gm_free(gm); } else if (info.scale == 1) { sm = gm; } else if (info.linear) { /* linear interpolation */ sm = interpolate_linear(gm, info.scale, info.bilevel, info.level); gm_free(gm); } else { /* cubic interpolation */ sm = interpolate_cubic(gm, info.scale, info.bilevel, info.level); gm_free(gm); } if (!sm) { fprintf(stderr, "" MKBITMAP ": %s: %s\n", infile, strerror(errno)); exit(2); } if (info.bilevel) { bm = (potrace_bitmap_t *)sm; bm_writepbm(fout, bm); bm_free(bm); } else { gm = (greymap_t *)sm; gm_writepgm(fout, gm, NULL, 1, GM_MODE_POSITIVE, 1.0); gm_free(gm); } } } /* ---------------------------------------------------------------------- */ /* some info functions and option processing */ static int license(FILE *f) { fprintf(f, "This program is free software; you can redistribute it and/or modify\n" "it under the terms of the GNU General Public License as published by\n" "the Free Software Foundation; either version 2 of the License, or\n" "(at your option) any later version.\n" "\n" "This program is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n" "\n" "You should have received a copy of the GNU General Public License\n" "along with this program; if not, write to the Free Software Foundation\n" "Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n" ); return 0; } static int usage(FILE *f) { fprintf(f, "Usage: " MKBITMAP " [options] [file...]\n"); fprintf(f, "Options:\n"); fprintf(f, " -h, --help - print this help message and exit\n"); fprintf(f, " -v, --version - print version info and exit\n"); fprintf(f, " -l, --license - print license info and exit\n"); fprintf(f, " -o, --output - output to file\n"); fprintf(f, " -x, --nodefaults - turn off default options\n"); fprintf(f, "Inversion:\n"); fprintf(f, " -i, --invert - invert the input (undo 'blackboard' effect)\n"); fprintf(f, "Highpass filtering:\n"); fprintf(f, " -f, --filter - apply highpass filter with radius n (default 4)\n"); fprintf(f, " -n, --nofilter - no highpass filtering\n"); fprintf(f, " -b, --blur - apply lowpass filter with radius n (default: none)\n"); fprintf(f, "Scaling:\n"); fprintf(f, " -s, --scale - scale by integer factor n (default 2)\n"); fprintf(f, " -1, --linear - use linear interpolation\n"); fprintf(f, " -3, --cubic - use cubic interpolation (default)\n"); fprintf(f, "Thresholding:\n"); fprintf(f, " -t, --threshold - set threshold for bilevel conversion (default 0.45)\n"); fprintf(f, " -g, --grey - no bilevel conversion, output a greymap\n"); fprintf(f, "\n"); fprintf(f, "Possible input file formats are: pnm (pbm, pgm, ppm), bmp.\n"); fprintf(f, "The default options are: -f 4 -s 2 -3 -t 0.45\n"); return 0; } static struct option longopts[] = { {"help", 0, 0, 'h'}, {"version", 0, 0, 'v'}, {"license", 0, 0, 'l'}, {"output", 1, 0, 'o'}, {"reset", 0, 0, 'x'}, {"invert", 0, 0, 'i'}, {"filter", 1, 0, 'f'}, {"nofilter", 0, 0, 'n'}, {"blur", 1, 0, 'b'}, {"scale", 1, 0, 's'}, {"linear", 0, 0, '1'}, {"cubic", 0, 0, '3'}, {"grey", 0, 0, 'g'}, {"threshold", 1, 0, 't'}, {0, 0, 0, 0} }; static const char *shortopts = "hvlo:xif:nb:s:13gt:"; /* process options. On error, print error message to stderr and exit with code 1 */ static void dopts(int ac, char *av[]) { int c; char *p; /* set defaults for command line parameters */ info.outfile = NULL; /* output file */ info.infiles = NULL; /* input files */ info.infilecount = 0; /* how many input files? */ info.invert = 0; /* invert input? */ info.highpass = 1; /* use highpass filter? */ info.lambda = 4; /* highpass filter radius */ info.lowpass = 0; /* use lowpass filter? */ info.lambda1 = 0; /* lowpass filter radius */ info.scale = 2; /* scaling factor */ info.linear = 0; /* linear scaling? */ info.bilevel = 1; /* convert to bilevel? */ info.level = 0.45; /* cutoff grey level */ info.outext = ".pbm"; /* output file extension */ while ((c = getopt_long(ac, av, shortopts, longopts, NULL)) != -1) { switch (c) { case 'h': fprintf(stdout, "" MKBITMAP " " VERSION ". Transforms images into bitmaps with scaling and filtering.\n\n"); usage(stdout); exit(0); break; case 'v': fprintf(stdout, "" MKBITMAP " " VERSION ". Copyright (C) 2001-2019 Peter Selinger.\n"); exit(0); break; case 'l': fprintf(stdout, "" MKBITMAP " " VERSION ". Copyright (C) 2001-2019 Peter Selinger.\n\n"); license(stdout); exit(0); break; case 'o': free(info.outfile); info.outfile = strdup(optarg); if (!info.outfile) { fprintf(stderr, "" MKBITMAP ": %s\n", strerror(errno)); exit(2); } break; case 'x': info.invert = 0; info.highpass = 0; info.scale = 1; info.bilevel = 0; info.outext = ".pgm"; break; case 'i': info.invert = 1; break; case 'f': info.highpass = 1; info.lambda = strtod(optarg, &p); if (*p || info.lambda<0) { fprintf(stderr, "" MKBITMAP ": invalid filter radius -- %s\n", optarg); exit(1); } break; case 'n': info.highpass = 0; break; case 'b': info.lowpass = 1; info.lambda1 = strtod(optarg, &p); if (*p || info.lambda1<0) { fprintf(stderr, "" MKBITMAP ": invalid filter radius -- %s\n", optarg); exit(1); } break; case 's': info.scale = strtol(optarg, &p, 0); if (*p || info.scale<=0) { fprintf(stderr, "" MKBITMAP ": invalid scaling factor -- %s\n", optarg); exit(1); } break; case '1': info.linear = 1; break; case '3': info.linear = 0; break; case 'g': info.bilevel = 0; info.outext = ".pgm"; break; case 't': info.bilevel = 1; info.outext = ".pbm"; info.level = strtod(optarg, &p); if (*p || info.level<0) { fprintf(stderr, "" MKBITMAP ": invalid threshold -- %s\n", optarg); exit(1); } break; case '?': fprintf(stderr, "Try --help for more info\n"); exit(1); break; default: fprintf(stderr, "" MKBITMAP ": Unimplemented option -- %c\n", c); exit(1); } } info.infiles = &av[optind]; info.infilecount = ac-optind; return; } /* ---------------------------------------------------------------------- */ /* auxiliary functions for file handling */ /* open a file for reading. Return stdin if filename is NULL or "-" */ static FILE *my_fopen_read(const char *filename) { if (filename == NULL || strcmp(filename, "-") == 0) { return stdin; } return fopen(filename, "rb"); } /* open a file for writing. Return stdout if filename is NULL or "-" */ static FILE *my_fopen_write(const char *filename) { if (filename == NULL || strcmp(filename, "-") == 0) { return stdout; } return fopen(filename, "wb"); } /* close a file, but do nothing is filename is NULL or "-" */ static void my_fclose(FILE *f, const char *filename) { if (filename == NULL || strcmp(filename, "-") == 0) { return; } fclose(f); } /* make output filename from input filename. Return an allocated value. */ static char *make_outfilename(const char *infile, const char *ext) { char *outfile; char *p; if (strcmp(infile, "-") == 0) { return strdup("-"); } outfile = (char *) malloc(strlen(infile)+strlen(ext)+5); if (!outfile) { return NULL; } strcpy(outfile, infile); p = strrchr(outfile, '.'); if (p) { *p = 0; } strcat(outfile, ext); /* check that input and output filenames are different */ if (strcmp(infile, outfile) == 0) { strcpy(outfile, infile); strcat(outfile, "-out"); } return outfile; } /* ---------------------------------------------------------------------- */ /* Main function */ int main(int ac, char *av[]) { FILE *fin, *fout; int i; char *outfile; /* platform-specific initializations, e.g., set file i/o to binary */ platform_init(); /* process options */ dopts(ac, av); /* there are several ways to call us: mkbitmap -- stdin to stdout mkbitmap -o outfile -- stdin to outfile mkbitmap file... -- encode each file and generate outfile names mkbitmap file... -o outfile -- concatenate files and write to outfile */ if (info.infilecount == 0 && info.outfile == NULL) { /* stdin to stdout */ process_file(stdin, stdout, "stdin", "stdout"); return 0; } else if (info.infilecount == 0) { /* stdin to outfile */ fout = my_fopen_write(info.outfile); if (!fout) { fprintf(stderr, "" MKBITMAP ": %s: %s\n", info.outfile, strerror(errno)); exit(2); } process_file(stdin, fout, "stdin", info.outfile); my_fclose(fout, info.outfile); free(info.outfile); return 0; } else if (info.outfile == NULL) { /* infiles -> multiple outfiles */ for (i=0; i