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("Divise-texture-par-3"); stderr.writeln("l'indispensable outil de division par 3\n"); stderr.writeln("usage: divise-texture-par-3 input.png output.png\n"); stderr.writeln(); stderr.writeln("Arguments:"); stderr.writeln(" --help Shows this help."); stderr.writeln("A noter qu'un fichier diff.png est cree qui montre pourquoi on peut pas réduire par 3 en rouge."); } int main(string[] args) { try { string input = null; string output = null; bool showHelp = false; for(int i = 1; i < args.length; ++i) { string arg = args[i]; 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, "diff.png"); } catch(Exception e) { writefln("error: %s", e.message); usage(); return 1; } return 0; } struct Color { ubyte r, g, b, a; } void processFile(string inputPath, string outputPath, string diffPath) { Image input; input.loadFromFile(inputPath); if (!input.isValid) throw new Exception("cant load " ~ inputPath); input.convertTo(PixelType.rgba8); if ( (input.width % 3) || (input.height % 3)) throw new Exception("input is not a multiple of 3 in size"); int w = input.width; int h = input.height; int w3 = input.width / 3; int h3 = input.height / 3; // show outlier pixels // compare each pixel with pixel at center of 3x3 patch, make it RED if not // else keep original color toned down 0.5x Image diff; diff.create(w, h, PixelType.rgba8); bool successOfDownscale = true; for(int y = 0; y < h; ++y) { Color[] scanInput = cast(Color[]) input.scanline(y); Color[] scanDiff = cast(Color[]) diff.scanline(y); // Copy input to diff, darkened for(int x = 0; x < w; ++x) { Color c = scanInput[x]; c.r /= 2; c.g /= 2; c.b /= 2; scanDiff[x] = c; } int refY = 1 + 3 * (y / 3); Color[] scanInputRef = cast(Color[]) input.scanline(refY); for(int x = 0; x < w; ++x) { int refX = 1 + 3 * (x / 3); Color centerPixel = scanInputRef[refX]; Color thisPixel = scanInput[x]; if (!isSameColor(thisPixel, centerPixel)) { scanDiff[x] = Color(255, 0, 0, 255); successOfDownscale = false; } else { scanDiff[x].g = 255; // green = OK } } } if (!diff.saveToFile(diffPath)) { throw new Exception("Couln't save " ~ diffPath); } // Output image Image output; output.create(w3, h3, PixelType.rgba8); // if the diff image isn't all black, there are errors that need to be fixed // always create the divided by 3 image // this is just sampling in the middle for(int y = 0; y < h3; ++y) { Color[] scanInput = cast(Color[]) input.scanline(y*3); Color[] scanOutput = cast(Color[]) output.scanline(y); for(int x = 0; x < w3; ++x) { // Take the first pixel of the 3x3 patch that isn't fully transparent Color found; for (int yy = 0; yy < 3; ++yy) { for (int xx = 0; xx < 3; ++xx) { Color c = (cast(Color[]) input.scanline(y*3+yy))[x*3+xx]; if (c.a != 0) { found = c; goto okay; } } } okay: scanOutput[x] = found; } } if (!output.saveToFile(outputPath)) { throw new Exception("Couln't save " ~ outputPath); } if (successOfDownscale) { writeln("SUCCESS"); writeln("Image reduced by 3x, do not forget to use GIMP to make it palettized again!"); } else { writeln("FAILURE"); writeln("See diff.png for the problem in input image that prevents its reduction."); writeln("Not everything is align to 3x3 pixel patchs."); } } bool isSameColor(Color a, Color b) { if ((a.a == 0) && (b.a == 0)) return true; // do not check colors of transparent pixels else return a == b; }