// example args: -input.path=D:\some\path\to\images\here -filter=*.jpg -prefix=team -output.extension=jpg -output.path= -output.resize=240 static void Main(string[] args) { string folder = null; string filter = "*.*"; string atlasPath = Directory.GetCurrentDirectory(); string cssPath = null; string jsonPath = null; string xmlPath = null; string csvPath = null; string atlasExtension = "jpg"; string prefix = null; int resize = 0; var globalMargin = new Margin(); bool normalize = false; foreach (var entry in args) { var arg = entry; if (!arg.StartsWith("-")) { Console.WriteLine("Invalid argument: " + arg); return; } arg = arg.Substring(1); var temp = arg.Split(new char[] { '=' }, 2); var key = temp[0].ToLower(); var val = temp.Length == 2 ? temp[1] : null; switch (key) { case "input.path": folder = val; break; case "input.filter": filter = val; break; case "prefix": prefix = val; break; case "atlas.extension": atlasExtension = val.Replace(".", ""); break; case "atlas.path": atlasPath = val; break; case "resize": resize = int.Parse(val); break; case "css.path": cssPath = val; break; case "json.path": jsonPath = val; break; case "xml.path": xmlPath = val; break; case "csv.path": csvPath = val; break; case "margin.X": globalMargin.X = int.Parse(val); break; case "margin.Y": globalMargin.Y = int.Parse(val); break; case "margin": globalMargin.X = int.Parse(val); globalMargin.Y = globalMargin.X; break; case "normalize": normalize = bool.Parse(val); break; } } if (normalize && resize > 0) { Console.WriteLine("Normalize and resize options are mutually exclusive."); return; } FixPath(ref atlasPath); FixPath(ref cssPath); FixPath(ref jsonPath); FixPath(ref xmlPath); FixPath(ref csvPath); if (folder == null) { Console.WriteLine("Please specify a folder. Eg: -input.path=some_path"); return; } if (prefix == null) { Console.WriteLine("Please specify a atlas prefix. Eg: --prefix=something"); return; } var outPicName = prefix + "." + atlasExtension; var outCSSName = prefix + ".css"; var files = Directory.GetFiles(folder, filter); var images = new Dictionary <string, Bitmap>(); int count = 0; int avgWidth = 0; int avgHeight = 0; int maxWidth = 0; int maxHeight = 0; foreach (var file in files) { var img = new Bitmap(Bitmap.FromFile(file)); if (resize != 0) { img = new Bitmap(img, new Size(resize, resize)); } images[file] = img; count++; maxWidth = Math.Max(maxWidth, img.Width); maxHeight = Math.Max(maxHeight, img.Height); avgWidth += img.Width; avgHeight += img.Height; } int maxSize = Math.Max(maxHeight, maxWidth); avgWidth /= count; avgHeight /= count; int side = (int)Math.Ceiling(Math.Sqrt(count)); int atlasWidth = avgWidth * side; int atlasHeight = avgHeight * side; int tot; int tries = 0; RectanglePacker <string> packer; var margins = new Dictionary <string, Margin>(); if (normalize) { foreach (var file in files) { var img = images[file]; var margin = new Margin() { X = (maxSize - img.Width) / 2 + globalMargin.X, Y = (maxSize - img.Height) / 2 + globalMargin.Y, }; margins[file] = margin; } } else { foreach (var file in files) { margins[file] = globalMargin; } } do { packer = new RectanglePacker <string>(); foreach (var entry in images) { var margin = margins[entry.Key]; packer.AddRect(entry.Value.Width + margin.X * 2, entry.Value.Height + margin.Y * 2, entry.Key); } tot = packer.Pack(0, 0, atlasWidth, atlasHeight); if (tot == 0) { break; } if (tries % 2 == 0) { atlasWidth *= 2; } else { atlasHeight *= 2; } tries++; if (tries > 5) { break; } } while (true); if (tot == 0) { var output = new Bitmap(atlasWidth, atlasHeight); using (Graphics g = Graphics.FromImage(output)) { foreach (var file in files) { int x, y; packer.GetRect(file, out x, out y); var margin = margins[file]; x += margin.X; y += margin.Y; var img = images[file]; g.DrawImage(img, x, y, img.Width, img.Height); Console.WriteLine("Merged " + file); } } output.Save(atlasPath + outPicName); Console.WriteLine("Generated " + atlasPath + outPicName); if (cssPath != null) { var sb = new StringBuilder(); int index = 0; foreach (var file in files) { var name = Path.GetFileNameWithoutExtension(file).ToLower(); if (index > 0) { sb.Append(", "); } if (index % side == side - 1) { sb.AppendLine(); } sb.Append($".{prefix}-{name}"); index++; } sb.Append('{'); sb.AppendLine(); sb.AppendLine($"\tbackground-image: url('{outPicName}');"); sb.AppendLine("\tbackground-repeat: no-repeat;"); sb.Append('}'); sb.AppendLine(); foreach (var file in files) { int x, y; packer.GetRect(file, out x, out y); var margin = margins[file]; x += margin.X; y += margin.Y; var name = Path.GetFileNameWithoutExtension(file).ToLower(); var img = images[file]; sb.AppendLine(); sb.AppendLine("." + prefix + "-" + name + " {"); sb.AppendLine($"\twidth: {img.Width}px;"); sb.AppendLine($"\theight: {img.Height}px;"); sb.AppendLine($"\tbackground-position: -{x}px -{y}px;"); sb.Append('}'); sb.AppendLine(); } File.WriteAllText(cssPath + outCSSName, sb.ToString()); Console.WriteLine("Generated " + cssPath + outCSSName); } if (xmlPath != null || csvPath != null || jsonPath != null) { if (jsonPath != null) { var node = ExportToNode(files, images, margins, packer, false); var json = JSONWriter.WriteToString(node); File.WriteAllText(jsonPath + prefix + ".json", json); } if (xmlPath != null) { var node = ExportToNode(files, images, margins, packer, true); var xml = XMLWriter.WriteToString(node); File.WriteAllText(xmlPath + prefix + ".xml", xml); } if (csvPath != null) { var node = ExportToNode(files, images, margins, packer, false); var csv = CSVWriter.WriteToString(node); File.WriteAllText(csvPath + prefix + ".csv", csv); } } } else { Console.WriteLine("Failed packing..."); } }