module main; import std.stdio; import std.json; import std.file; import std.conv; import std.string; import gamut; import expression; void usage() { stderr.writeln(); stderr.writeln("Decoupe-Voiture"); stderr.writeln("l'indispensable outil de decoupe de voiture\n"); stderr.writeln("usage: decoupe-voiture input.png [-p ] output.png\n"); stderr.writeln(); stderr.writeln("Arguments:"); stderr.writeln(" -p Pattern to execute, default = classic_complete_48x48"); stderr.writeln(" See patterns/ for availability."); stderr.writeln(" --help Shows this help."); stderr.writeln(); } int main(string[] args) { try { string input = null; string output = null; string pattern = "classic_complete_48x48"; bool showHelp = false; for(int i = 1; i < args.length; ++i) { string arg = args[i]; if (arg == "-p" || arg == "--pattern") { ++i; pattern = args[i]; } else if (arg == "-h" || arg == "--help") { showHelp = true; } else { if (input) { if (output) throw new Exception("Too many files provided"); else output = arg; } else input = arg; } } if (showHelp || input is null || output is null) { usage(); return 0; } processFile(input, output, pattern); } catch(Exception e) { writefln("error: %s", e.message); usage(); return 1; } return 0; } struct Point { int x, y; } struct Size { int w, h; } struct Rect { Point pos; Size size; } void processFile(string inputPath, string outputPath, string pattern) { alias jsonTrue = JSONType.true_; alias jsonFalse = JSONType.false_; // try to open pattern file string patternPath = "patterns/" ~ pattern ~ ".json"; JSONValue patternFile = parseJSON(cast(string)(std.file.read(patternPath))); Image input; input.loadFromFile(inputPath); if (!input.isValid) throw new Exception("cant load " ~ inputPath); input.convertTo(PixelType.rgba8); Size defaultSize = parseSize(patternFile, "default-size", Size(16, 16)); Size inputSize = parseSize(patternFile, "input-size", defaultSize); Size outputSize = parseSize(patternFile, "output-size", defaultSize); if (inputSize.w != input.width || inputSize.h != input.height) throw new Exception("input size mismatch"); Image output; output.create(outputSize.w, outputSize.h, PixelType.rgba8); void copyRect(Rect src, Rect dst) { rectInsideImage(input, src); rectInsideImage(output, dst); int w = src.size.w; int h = src.size.h; for (int y = 0; y < h; ++y) { ubyte[] srcScan = cast(ubyte[]) input.scanline(src.pos.y + y); ubyte[] dstScan = cast(ubyte[]) output.scanline(dst.pos.y + y); int bytes = w * 4; int ii = 4 * src.pos.x; int oo = 4 * dst.pos.x; dstScan[oo .. oo+bytes] = srcScan[ii .. ii+bytes]; } } foreach(JSONValue tr; patternFile["copyRects"].array) { Rect from = parseRect(tr, "from", defaultSize); Rect to = parseRect(tr, "to", defaultSize); // If from rectangle is smaller than to rectangle, // center the dest rectangle inside the given destination if (from.size.w < to.size.w) { int marginX = (to.size.w - from.size.w) / 2; to.pos.x += marginX; to.size.w = from.size.w; } if (from.size.h < to.size.h) { int marginY = (to.size.h - from.size.h) / 2; to.pos.y += marginY; to.size.h = from.size.h; } if (from.size != to.size) throw new Exception(format("rectangle size mismatch for key: %s", tr)); //writefln("DEBUG: copy %s to %s", from, to); copyRect(from, to); } writeln("All rectangles copied"); int encodeFlags = 0; bool r = output.saveToFile(outputPath, encodeFlags); if (!r) { throw new Exception("Couldn't save file " ~ outputPath); } } void rectInsideImage(ref Image i, Rect r) { if (r.pos.x < 0) throw new Exception("Rectangle x is < 0"); if (r.pos.y < 0) throw new Exception("Rectangle y is < 0"); if (r.size.w < 0) throw new Exception("Rectangle width is < 0"); if (r.size.h < 0) throw new Exception("Rectangle height is < 0"); if (r.pos.x + r.size.w > i.width) throw new Exception("Rectangle exceeds width of image"); if (r.pos.y + r.size.h > i.height) throw new Exception("Rectangle exceeds height of image"); } Point parsePoint(JSONValue parent, string keyName, Size defaultSize) { string r = parent[keyName].str; return parsePoint(r, defaultSize); } Point parsePoint(string r, Size defaultSize) { r = strip(r); int cPos = cast(int) r.indexOf(","); if (cPos == -1) throw new Exception("Point should follow this in format: 4,5"); int x = parseIntegerExpression(r[0..cPos], defaultSize); int y = parseIntegerExpression(r[cPos+1..$], defaultSize); return Point(x, y); } Size parseSize(JSONValue parent, string keyName, Size defaultSize) { return parseSize(parent[keyName].str, defaultSize); } Size parseSize(string r, Size defaultSize) { r = strip(r); int xPos = cast(int) r.indexOf("x"); if (xPos == -1) throw new Exception("Size should follow this in format: 48x48"); int w = parseIntegerExpression(r[0..xPos], defaultSize); int h = parseIntegerExpression(r[xPos+1..$], defaultSize); return Size(w, h); } Rect parseRect(JSONValue parent, string keyName, Size defaultSize) { string r = parent[keyName].str; return parseRect(r, defaultSize); } Rect parseRect(string r, Size defaultSize) { r = strip(r); int mPos = cast(int) r.indexOf("--"); Size size; Point pos; if (mPos == -1) { pos = parsePoint(r, defaultSize); size = defaultSize; } else { // U+2014 is 3 bytes long pos = parsePoint(r[0..mPos], defaultSize); size = parseSize(r[mPos+2..$], defaultSize); } return Rect(pos, size); } int parseIntegerExpression(string source, Size defaultSize) { source = strip(source); auto e = compileExpression!int(source); //e["TX"] = defaultSize.w; //e["TY"] = defaultSize.h; return e(); }