diff --git a/tools/decoupe-voiture/README.md b/tools/decoupe-voiture/README.md new file mode 100644 index 0000000..8bd7618 --- /dev/null +++ b/tools/decoupe-voiture/README.md @@ -0,0 +1,2 @@ +- Installe le D +- `dub` \ No newline at end of file diff --git a/tools/decoupe-voiture/dub.json b/tools/decoupe-voiture/dub.json new file mode 100644 index 0000000..5c0bfd8 --- /dev/null +++ b/tools/decoupe-voiture/dub.json @@ -0,0 +1,15 @@ +{ + "name": "decoupe-voiture", + "description": "Decoupe un asset selon un certain pattern.", + "license": "BSL-1.0", + + "dependencies": + { + "gamut": "~>3.0" + }, + + "subConfigurations": + { + "gamut": "boost+mit" + } +} diff --git a/tools/decoupe-voiture/patterns/classic_complete_48x48.json b/tools/decoupe-voiture/patterns/classic_complete_48x48.json new file mode 100644 index 0000000..1cd5a37 --- /dev/null +++ b/tools/decoupe-voiture/patterns/classic_complete_48x48.json @@ -0,0 +1,13 @@ +{ + "input-size": "3984x2160", + "output-size": "3456x960", + + "default-size": "48x48", + + "copyRects": [ + + { "from": "0,0-48x48", "to": "0,0" }, + { "from": "0,0-48x48", "to": "0,0" } + + ] +} \ No newline at end of file diff --git a/tools/decoupe-voiture/source/main.d b/tools/decoupe-voiture/source/main.d new file mode 100644 index 0000000..635f0b2 --- /dev/null +++ b/tools/decoupe-voiture/source/main.d @@ -0,0 +1,260 @@ +module main; + +import std.stdio; +import std.json; +import std.file; +import std.conv; +import std.string; +import gamut; + + +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 inputSize = parseSize(patternFile, "input-size"); + Size outputSize = parseSize(patternFile, "output-size"); + + 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); + + Size defaultSize = parseSize(patternFile, "default-size"); + + 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) +{ + string r = parent[keyName].str; + return parsePoint(r); +} + +Point parsePoint(string r) +{ + 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 = to!int(r[0..cPos]); + int y = to!int(r[cPos+1..$]); + return Point(x, y); +} + +Size parseSize(JSONValue parent, string keyName) +{ + return parseSize(parent[keyName].str); +} + +Size parseSize(string r) +{ + 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 = to!int(r[0..xPos]); + int h = to!int(r[xPos+1..$]); + 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); + size = defaultSize; + } + else + { + pos = parsePoint(r[0..mPos]); + size = parseSize(r[mPos+1..$]); + } + + return Rect(pos, size); +} + + \ No newline at end of file