int main(int argc, char * const argv[]) { bool steps = false; string stepPrefix = "/tmp/steps"; bool approximate = false; string approximationFilename; typedef uint8_t C; typedef ColorImage<C> ColorImage; typedef shared_ptr<ColorImage> ColorImageRef; typedef RGBPixel<C> RGBPixel; typedef GreyImage<C> GreyImage; typedef shared_ptr<GreyImage> GreyImageRef; typedef PenColor<C> PenColor; typedef Pen<C> Pen; typedef Carousel<C> Carousel; Carousel carousel; list< Output<Pen>* > outputs; int argn = 1; size_t equals = string::npos; Rotation rotation = None; int offsetX = 0, offsetY = 0; double scale = 1.0; bool dither = true; int threads = 1; for (; argn < argc && argv[argn][0] == '-' && strlen(argv[argn]) > 1; argn++) { string arg(argv[argn]); D(cerr << "arg[" << argn << "] == '" << arg << "'" << endl << flush); string penSpec; if (0 == arg.find("-pen")) { Pen pen; if (4 == arg.find("=")) { penSpec = arg.substr(5); } else { ++argn; if (!(argn < argc)) { cerr << "missing argument to '-pen'" << endl << flush; exit(1); } penSpec = argv[argn]; } size_t start = 0; while (start != string::npos) { D(cerr << "start=" << start << endl << flush); size_t comma = penSpec.find(',', start); string part; if (comma != string::npos) { part = penSpec.substr(start, comma - start); start = comma + 1; } else { part = penSpec.substr(start); start = string::npos; } D(cerr << "part='" << part << "'" << endl << flush); if ((equals = part.find('=')) == string::npos) { cerr << "pen specification part '" << part << "' missing '='" << endl << flush; exit(1); } else { string name = part.substr(0, equals); string value = part.substr(equals + 1); D(cerr << "name='" << name << "', value='" << value << "'" << endl << flush); switch(name[0]) { case 'c': // colour pen.color() = PenColor::parse(value); break; case 'r': // radius pen.r() = getInt(value); break; case 'm': // minimum size that can be drawn pen.rMin() = getInt(value); break; case 'i': // HPGL Index pen.hpglIndex() = getInt(value); break; case 'a': // hatch angle pen.hatchAngle() = getDouble(value); break; case 'p': // hatch phase pen.hatchPhase() = getDouble(value); break; } } } // now add this pen to the carousel carousel.addPen(pen); } else if (0 == arg.find("-rotate")) { string rotationSpec; if (7 == arg.find("=")) { rotationSpec = arg.substr(8); } else { ++argn; if (!(argn < argc)) { cerr << "missing argument to '-rotate'" << endl << flush; exit(1); } rotationSpec = argv[argn]; } switch (rotationSpec[0]) { case 'l': case 'L': rotation = Left; break; case 'r': case 'R': rotation = Right; break; case 'u': case 'U': rotation = UpsideDown; break; default: cerr << "unrecognized rotation '" << arg << "'" << endl << flush; exit(1); } } else if (0 == arg.find("-offset")) { string offset; if (7 == arg.find("=")) { offset = arg.substr(8); } else { ++argn; if (!(argn < argc)) { cerr << "missing argument to '-offset'" << endl << flush; exit(1); } offset = argv[argn]; } size_t delim = offset.find_first_of("x,"); if (delim == string::npos) { // no delimiter - offset both x & y by the same amount offsetX = offsetY = getInt(offset); } else { offsetX = getInt(offset.substr(0, delim)); offsetY = getInt(offset.substr(delim+1)); } } else if (0 == arg.find("-steps")) { steps = true; if (6 == arg.find("=")) { stepPrefix = arg.substr(7); } } else if (0 == arg.find("-approx")) { approximate = true; if (7 == arg.find("=")) { approximationFilename = arg.substr(8); } else { approximationFilename = "approximation.png"; } } else if (0 == arg.find("-nodither")) { dither = false; } else if (0 == arg.find("-out")) { size_t eqPos; string outputFile; if (string::npos != (eqPos = arg.find("="))) { outputFile = arg.substr(eqPos + 1); arg = arg.substr(0, eqPos); } else { ++argn; if (!(argn < argc)) { cerr << "missing argument to '" << arg << "'" << endl << flush; exit(1); } outputFile = argv[argn]; } string outputType = arg.substr(4); if (0 == outputType.length()) { outputType = "ha"; // HPGL, absolute coordinates } std::transform(outputType.begin(), outputType.end(), outputType.begin(), ::tolower); outputs.push_back(makeOutput<Pen>(outputType, outputFile)); } else if (0 == arg.find("-scale")) { string scaleString; if (6 == arg.find("=")) { scaleString = arg.substr(7); } else { ++argn; if (!(argn < argc)) { cerr << "missing argument to '-scale'" << endl << flush; exit(1); } scaleString = argv[argn]; } scale = getDouble(scaleString); } else if (0 == arg.find("-threads")) { string threadsArg; if (8 == arg.find("=")) { threadsArg = arg.substr(9); } else { ++argn; if (!(argn < argc)) { cerr << "missing argument to '-threads'" << endl << flush; exit(1); } threadsArg = argv[argn]; } threads = getInt(threadsArg); } else { cerr << "Unrecognized argument '" << arg << "'" << endl << flush; exit(1); } } string inputFile; if (argn == argc - 1) { inputFile = argv[argn]; } else if (argn == argc) { // default: is inputFile = "-"; } else { cerr << "Extra arguments after input file '" << argv[argn] << "'!"; cerr << endl << flush; exit(1); } // If there's no specified carousel, use a default one. if (carousel.size() == 0) { cerr << "No pens specified, using default carousel" << endl << flush; // populate the carousel with a default set of pens: // black, red, green blue, cyan, magenta, yellow, orange; // each at 0.35 mm diameter = 0.175 mm radius = 7 units radius Pen black(1.0, 1.0, 1.0, 3); black.hpglIndex() = 1; black.hatchAngle() = 1.0 / 25.0; Pen red(0.0, 1.0, 1.0, 3); red.hpglIndex() = 2; red.hatchAngle() = 4.0 / 25.0; Pen green(1.0, 0.0, 1.0, 3); green.hpglIndex() = 3; green.hatchAngle() = 7.0 / 25.0; Pen blue(1.0, 1.0, 0.0, 3); blue.hpglIndex() = 4; blue.hatchAngle() = 10.0 / 25.0; Pen orange(0.0, 0.5, 1.0, 3); orange.hpglIndex() = 8; orange.hatchAngle() = 13.0 / 25.0; Pen cyan(1.0, 0.0, 0.0, 3); cyan.hpglIndex() = 5; cyan.hatchAngle() = 16.0 / 25.0; Pen magenta(0.0, 1.0, 0.0, 3); magenta.hpglIndex() = 6; magenta.hatchAngle() = 19.0 / 25.0; Pen yellow(0.0, 0.0, 1.0, 3); yellow.hpglIndex() = 7; yellow.hatchAngle() = 22.0 / 25.0; // put the pens into the carousel in a suitable order carousel.addPen(black); carousel.addPen(orange); carousel.addPen(red); carousel.addPen(green); carousel.addPen(blue); carousel.addPen(cyan); carousel.addPen(magenta); carousel.addPen(yellow); } // if there's no output specified, use a default one. if (outputs.size() == 0) { cerr << "No output specified, writing HPGL to standard output" << endl << flush; outputs.push_back(new HPGLAbsoluteOutput<Pen>(cout)); } FILE *f; if ("-" == inputFile) { cerr << "* Reading image from standard input" << endl << flush; f = stdin; } else { cerr << "* Reading image '" << inputFile << "'" << endl << flush; f = fopen(inputFile.c_str(), "r"); if (f == NULL) { cerr << "unable to open input file '" << inputFile << "'!" << endl << flush; exit(1); } } ColorImageRef colored(ColorImage::readPng(f)); if (scale != 1.0) { colored = scaleImage(colored, scale); } Approximator *approximator = NULL; if (approximate) { approximator = new Approximator(colored->width(), colored->height()); approximator->approximation().copyRes(colored); RGBPixel whitePixel(1.0, 1.0, 1.0); approximator->approximation().setAll(whitePixel); } map<Pen, Chains> ditheredByPen; double dTheta = 1.0 / carousel.size(); double theta = 0.1; PlotterPathExtractor extractor; shared_ptr<Workers> workers = make_shared<Workers>(threads); extractor.setOut(&cerr); extractor.setDither(dither); extractor.setWorkers(workers); Stepper *stepper; if (steps) { extractor.setStepper(stepper = new Stepper(stepPrefix)); } if (approximate) { extractor.setApproximator(approximator); } int p = 0; for (Carousel::iterator c = carousel.begin(); c != carousel.end(); ++c) { const PenColor &color = *c; cerr << "- Separating out colour " << color << endl; GreyImageRef separated = colored->separateAndSubtract(color); if (steps) { cerr << " . writing separation" << endl; separated->writePng(stepper->makeName("separation.png", color.unparse().c_str())); } if (approximate) { approximator->setPenColor(color); } list<Pen> &pens = carousel.pensWithColor(color); extractor.outlineHatchThinDither(separated, pens, ditheredByPen); ++p; theta += dTheta; } IMatrix transform = IMatrix::identity(); cerr << "initial transform: " << transform << endl << flush; cerr << "offsetX = " << offsetX << ", offsetY = " << offsetY << endl << flush; if (offsetX != 0 || offsetY != 0) { transform = transform.concat(IMatrix::translate(offsetX, offsetY)); } cerr << "after offset: " << transform << endl << flush; cerr << "rotation = " << rotation << endl << flush; if (rotation == Left) { transform = transform.concat(IMatrix::pageLeft(colored->width(), colored->height())); } else if (rotation == Right) { transform = transform.concat(IMatrix::pageRight(colored->width(), colored->height())); } else if (rotation == UpsideDown) { transform = transform.concat(IMatrix::pageUpsideDown(colored->width(), colored->height())); } cerr << "after rotation: " << transform << endl << flush; int maxX = numeric_limits<int>::min(), maxY = numeric_limits<int>::min(); int minX = numeric_limits<int>::max(), minY = numeric_limits<int>::max(); for (int x = 0; x < colored->width(); x += colored->width()-1) { for (int y = 0; y < colored->height(); y += colored->height()-1) { IPoint p(x, y); p.transform(transform); minX = min(p.x(), minX); maxX = max(p.x(), maxX); minY = min(p.y(), minY); maxY = max(p.y(), maxY); } } cerr << "minX = " << minX << ", minY = " << minY << ", maxX = " << maxX << ", maxY = " << maxY << endl << flush; // Finally, adjust for the top-to-bottom Y of our bitmaps // vs the bottom-to-top Y of our output devices IMatrix adjustment(1, 0, 0, -1, 0, colored->height()); transform = transform.concat(adjustment); cerr << "after adjustment: " << transform << endl << flush; for (list< Output<Pen>* >::const_iterator outIter = outputs.begin(); outIter != outputs.end(); ++outIter) { cerr << "Writing " << (**outIter) << endl << flush; (*outIter)->open(); (*outIter)->beginPage(maxX + 1, maxY + 1); } for (map<Pen, Chains>::iterator di = ditheredByPen.begin(); di != ditheredByPen.end(); ++di) { const Pen &pen = di->first; Chains &dithered = di->second; if (transform != IMatrix::identity()) { cerr << "- applying transform " << transform << endl << flush; dithered.transform(transform); } else { cerr << "- skipping identity transform " << transform << endl << flush; } for (list< Output<Pen>* >::const_iterator outIter = outputs.begin(); outIter != outputs.end(); ++outIter) { (*outIter)->setPen(pen); (*outIter)->outputChains(dithered); } } for (list< Output<Pen>* >::const_iterator outIter = outputs.begin(); outIter != outputs.end(); ++outIter) { (*outIter)->endPage(); (*outIter)->close(); delete (*outIter); } outputs.clear(); if (approximate) { cerr << " . drawing final approximation" << endl; approximator->approximation().writePng(approximationFilename.c_str()); delete approximator; approximator = NULL; } }