| 1 |
/** |
|---|
| 2 |
* |
|---|
| 3 |
*/ |
|---|
| 4 |
module recoder; |
|---|
| 5 |
|
|---|
| 6 |
version(Windows) |
|---|
| 7 |
{ |
|---|
| 8 |
pragma(lib, "zlib"); |
|---|
| 9 |
} |
|---|
| 10 |
|
|---|
| 11 |
import lodepng.Decode, |
|---|
| 12 |
lodepng.Encode; |
|---|
| 13 |
|
|---|
| 14 |
import tango.util.Arguments, |
|---|
| 15 |
tango.io.Stdout, |
|---|
| 16 |
tango.io.File, |
|---|
| 17 |
tango.stdc.stdio, |
|---|
| 18 |
tango.text.Ascii, |
|---|
| 19 |
tango.io.FilePath, |
|---|
| 20 |
tango.io.FileSystem, |
|---|
| 21 |
tango.util.PathUtil; |
|---|
| 22 |
|
|---|
| 23 |
/+ |
|---|
| 24 |
TODO |
|---|
| 25 |
verbose output |
|---|
| 26 |
ask for overwriting / renaming |
|---|
| 27 |
option aliasing |
|---|
| 28 |
+/ |
|---|
| 29 |
|
|---|
| 30 |
char[] description = "Recoder: utility to re-encode png files. Test program for the lodepng library. usage: recode pngfile1 [pngfile2] [options]\n"; |
|---|
| 31 |
char[][] options = |
|---|
| 32 |
[ |
|---|
| 33 |
"h", "help", "\tdisplay this help message", |
|---|
| 34 |
"r", "reencode", "keep only known information, this will lose unknown chunks", |
|---|
| 35 |
"s", "shrink", "\tdiscard as much data as possible while still keeping original pixels", |
|---|
| 36 |
"t", "test", "\ttest run, do not save final image" , |
|---|
| 37 |
"d", "dump", "\tonly reads and saves chunks, doesn't decode.", |
|---|
| 38 |
"f", "force", "\tdo not ask for confirmation when overwriting files", |
|---|
| 39 |
]; |
|---|
| 40 |
//"c=x", "color=x", "\tconvert source to color format x:\n\t\t 2 - 24-bit RGB color format\n\t\t 6 - 32-bit RGBA color format\n\t\t 0 - chooses format 2 if this is a lossless operation, otherwise 6\n" |
|---|
| 41 |
enum : int |
|---|
| 42 |
{ |
|---|
| 43 |
shrink = 1, recode, dump |
|---|
| 44 |
} |
|---|
| 45 |
|
|---|
| 46 |
int main(char[][] args) |
|---|
| 47 |
{ |
|---|
| 48 |
auto arguments = new Arguments(args, ["source", "target"], |
|---|
| 49 |
[ |
|---|
| 50 |
["h", "help"], |
|---|
| 51 |
["r", "reencode"], |
|---|
| 52 |
["s", "shrink"], |
|---|
| 53 |
["t", "test"], |
|---|
| 54 |
["d", "dump"], |
|---|
| 55 |
["f", "force"] |
|---|
| 56 |
] |
|---|
| 57 |
); |
|---|
| 58 |
char[] sourceFile = arguments["source"].length ? arguments["source"][0] : null; |
|---|
| 59 |
char[] targetFile = arguments["target"].length ? arguments["target"][0] : null; |
|---|
| 60 |
|
|---|
| 61 |
addValidations(arguments); |
|---|
| 62 |
arguments.validate(); |
|---|
| 63 |
|
|---|
| 64 |
if ("h" in arguments || sourceFile.length == 0) |
|---|
| 65 |
{ |
|---|
| 66 |
printHelp(); |
|---|
| 67 |
return 0; |
|---|
| 68 |
} |
|---|
| 69 |
|
|---|
| 70 |
if (targetFile.length == 0) |
|---|
| 71 |
targetFile = sourceFile.dup; |
|---|
| 72 |
|
|---|
| 73 |
bool verbose = "v" in arguments; |
|---|
| 74 |
bool test = "t" in arguments; |
|---|
| 75 |
bool force = "f" in arguments; |
|---|
| 76 |
int method = chooseMethod(arguments); |
|---|
| 77 |
|
|---|
| 78 |
if ((new FilePath(targetFile)).exists() && !force) |
|---|
| 79 |
{ |
|---|
| 80 |
char response; |
|---|
| 81 |
while (true) |
|---|
| 82 |
{ |
|---|
| 83 |
Stdout.format("Are you sure you want to overwrite {} (y/n)?", targetFile).newline; |
|---|
| 84 |
response = getchar(); |
|---|
| 85 |
if (response == 'n' || response == 'N') |
|---|
| 86 |
return 0; |
|---|
| 87 |
else if (response == 'y' || response == 'Y') |
|---|
| 88 |
break; |
|---|
| 89 |
} |
|---|
| 90 |
} |
|---|
| 91 |
|
|---|
| 92 |
ubyte[] pngBytes = cast(ubyte[]) (new File(sourceFile)).read(); |
|---|
| 93 |
PngInfo info; |
|---|
| 94 |
ubyte[] result; |
|---|
| 95 |
|
|---|
| 96 |
void recodeImage() |
|---|
| 97 |
{ |
|---|
| 98 |
auto imgBytes = pngBytes.decode(info); |
|---|
| 99 |
result = imgBytes.encode(Settings(info, false)); |
|---|
| 100 |
} |
|---|
| 101 |
|
|---|
| 102 |
void shrinkImage() |
|---|
| 103 |
{ |
|---|
| 104 |
auto imgBytes = pngBytes.decode(info); |
|---|
| 105 |
info.parseText = false; |
|---|
| 106 |
auto settings = Settings(info, true); |
|---|
| 107 |
settings.compressionLevel = 9; |
|---|
| 108 |
result = imgBytes.encode(settings); |
|---|
| 109 |
|
|---|
| 110 |
} |
|---|
| 111 |
|
|---|
| 112 |
void dumpImage() |
|---|
| 113 |
{ |
|---|
| 114 |
Chunk[] chunks; |
|---|
| 115 |
info.image = pngBytes.readHeader(); |
|---|
| 116 |
pngBytes.iterateChunks(info.image, (ref Chunk chunk) |
|---|
| 117 |
{ |
|---|
| 118 |
if (parseChunk(chunk, info) == false) |
|---|
| 119 |
chunks ~= chunk.dup; |
|---|
| 120 |
return 0; |
|---|
| 121 |
}); |
|---|
| 122 |
|
|---|
| 123 |
result = encode(null, Settings(info, false), chunks); |
|---|
| 124 |
} |
|---|
| 125 |
|
|---|
| 126 |
if (method == dump) |
|---|
| 127 |
dumpImage(); |
|---|
| 128 |
else if (method == shrink) |
|---|
| 129 |
shrinkImage(); |
|---|
| 130 |
else if (method == recode) |
|---|
| 131 |
recodeImage(); |
|---|
| 132 |
else |
|---|
| 133 |
Stdout("?").newline; |
|---|
| 134 |
|
|---|
| 135 |
if (!test) |
|---|
| 136 |
(new File(targetFile)).write(result); |
|---|
| 137 |
|
|---|
| 138 |
|
|---|
| 139 |
return 0; |
|---|
| 140 |
} |
|---|
| 141 |
|
|---|
| 142 |
|
|---|
| 143 |
void printHelp() |
|---|
| 144 |
{ |
|---|
| 145 |
Stdout(description).newline; |
|---|
| 146 |
for (int i = 0; i < options.length - 3; i+=3) |
|---|
| 147 |
Stdout.format(" -{}\t--{}\t{}", options[i], options[i+1], options[i+2]).newline; |
|---|
| 148 |
} |
|---|
| 149 |
|
|---|
| 150 |
|
|---|
| 151 |
void addValidations(ref Arguments arguments) |
|---|
| 152 |
{ |
|---|
| 153 |
for (int i = 0; i < options.length - 3; i+=3) |
|---|
| 154 |
{ |
|---|
| 155 |
arguments.addValidation(options[i], false, false); |
|---|
| 156 |
arguments.addValidation(options[i + 1], false, false); |
|---|
| 157 |
} |
|---|
| 158 |
//arguments.addValidation("c", false, true); |
|---|
| 159 |
//arguments.addValidation("color", false, true); |
|---|
| 160 |
} |
|---|
| 161 |
|
|---|
| 162 |
|
|---|
| 163 |
int chooseMethod(Arguments arguments) |
|---|
| 164 |
{ |
|---|
| 165 |
int method = 0; |
|---|
| 166 |
|
|---|
| 167 |
if ("s" in arguments) |
|---|
| 168 |
method = shrink; |
|---|
| 169 |
if ("r" in arguments) |
|---|
| 170 |
{ |
|---|
| 171 |
if (method != 0) |
|---|
| 172 |
throw new Exception("options r and s are mutually exclusive"); |
|---|
| 173 |
method = recode; |
|---|
| 174 |
} |
|---|
| 175 |
if ("d" in arguments) |
|---|
| 176 |
{ |
|---|
| 177 |
if (method != 0) |
|---|
| 178 |
throw new Exception("options mutually exclusive"); |
|---|
| 179 |
method = dump; |
|---|
| 180 |
} |
|---|
| 181 |
if (method == 0) |
|---|
| 182 |
method = dump; |
|---|
| 183 |
return method; |
|---|
| 184 |
} |
|---|
| 185 |
|
|---|
| 186 |
|
|---|
| 187 |
void printHeader(PngImage image) |
|---|
| 188 |
{ |
|---|
| 189 |
Stdout("Source image properties:").newline; |
|---|
| 190 |
Stdout.format(" width: {}", image.width).newline; |
|---|
| 191 |
Stdout.format(" height: {}", image.height).newline; |
|---|
| 192 |
Stdout.format(" color format: {}", image.colorType).newline; |
|---|
| 193 |
} |
|---|