declarative tool to copy PNG subrects to make a new image
This commit is contained in:
2
tools/decoupe-voiture/README.md
Normal file
2
tools/decoupe-voiture/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
- Installe le D
|
||||
- `dub`
|
||||
15
tools/decoupe-voiture/dub.json
Normal file
15
tools/decoupe-voiture/dub.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
13
tools/decoupe-voiture/patterns/classic_complete_48x48.json
Normal file
13
tools/decoupe-voiture/patterns/classic_complete_48x48.json
Normal file
@@ -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" }
|
||||
|
||||
]
|
||||
}
|
||||
260
tools/decoupe-voiture/source/main.d
Normal file
260
tools/decoupe-voiture/source/main.d
Normal file
@@ -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 <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 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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user