Files
chaussette.sale/tools/decoupe-voiture/source/main.d
Guillaume Piolat 07604388c0 Pfiou
2025-03-22 11:57:45 +01:00

269 lines
6.8 KiB
D

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 <pattern>] 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();
}