Skip to content

Instantly share code, notes, and snippets.

@DigiTec
Created November 26, 2012 06:40
Show Gist options
  • Save DigiTec/4146881 to your computer and use it in GitHub Desktop.
Save DigiTec/4146881 to your computer and use it in GitHub Desktop.
CSS Image Sprite Packer
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
public class PackImagesApplication
{
private static string cssFormat =
@"#{0}_{1} {{
width: {2}px;
height: {3}px;
background-position: -{4}px -{5}px;
background-image: url({6});
}}
#{0}_{1}_half {{
width: {7}px;
height: {8}px;
background-position: -{9}px -{10}px;
background-image: url({6});
background-size: {11}px {12}px
}}
#{0}_{1}_double {{
width: {13}px;
height: {14}px;
background-position: -{15}px -{16}px;
background-image: url({6});
background-size: {17}px {18}px
}}";
public class Run
{
public List<Image> Images = new List<Image>();
public long RunHeight;
public long RunWidth;
}
private static void Main(string[] args)
{
uint maximumWidth = 1024;
uint maximumHeight = 1024;
long fileSize = 0;
List<string> importDirectories = new List<string>();
string outputFile = null;
string cssFile = null;
for(int i = 0; i < args.Length; i++)
{
string arg = args[i];
if ( arg.StartsWith("/o:") )
{
outputFile = arg.Substring(3);
}
else if ( arg.StartsWith("/h:") )
{
maximumHeight = uint.Parse(arg.Substring(3));
}
else if ( arg.StartsWith("/w:") )
{
maximumWidth = uint.Parse(arg.Substring(3));
}
else if ( arg.StartsWith("/css:") )
{
cssFile = arg.Substring(5);
}
else
{
importDirectories.Add(arg);
}
}
if ( outputFile == null )
{
Console.WriteLine("Usage: PackImages /o:out.png [/h:maxHeight] [/w:maxWidth] [/css:out.css] [dirs...]");
return;
}
if ( cssFile == null )
{
cssFile = Path.ChangeExtension(outputFile, ".css");
}
List<Image> images = new List<Image>();
if ( outputFile != null )
{
for(int i = 0; i < importDirectories.Count; i++)
{
DirectoryInfo di = new DirectoryInfo(importDirectories[i]);
if ( !di.Exists )
{
throw new Exception("Directory doesn't exist " + importDirectories[i]);
}
FileInfo[] fis = di.GetFiles("*.png");
foreach(FileInfo fi in fis)
{
fileSize += fi.Length;
Image image = Image.FromFile(fi.FullName);
image.Tag = fi.FullName;
images.Add(image);
}
}
}
List<Run> ImageRuns = new List<Run>();
long totalImageHeight = 0;
long totalImageWidth = 0;
images.Sort((x,y) => { return x.Width.CompareTo(y.Width); });
Run currentRun = new Run();
long currentWidth = 0;
foreach(Image image in images)
{
if ( currentWidth + image.Width >= maximumWidth )
{
if (currentRun.Images.Count == 0)
{
throw new Exception("An image is too large for spriting");
}
currentRun.RunWidth = currentWidth;
totalImageWidth = Math.Max(currentRun.RunWidth, totalImageWidth);
totalImageHeight += currentRun.RunHeight;
ImageRuns.Add(currentRun);
currentRun = new Run();
currentWidth = 0;
}
currentRun.Images.Add(image);
currentRun.RunHeight = Math.Max(currentRun.RunHeight, image.Height);
currentWidth += image.Width;
}
if (currentRun.Images.Count > 0)
{
currentRun.RunWidth = currentWidth;
totalImageWidth = Math.Max(currentRun.RunWidth, totalImageWidth);
totalImageHeight += currentRun.RunHeight;
ImageRuns.Add(currentRun);
}
Console.WriteLine("Size before pack is {0}", fileSize);
Console.WriteLine("Final image size is {0},{1} with {2} images in {3} runs", totalImageWidth, totalImageHeight, images.Count, ImageRuns.Count);
string debugHtmlFile = Path.ChangeExtension(cssFile, ".htm");
using(StreamWriter swDebug = new StreamWriter(debugHtmlFile))
{
swDebug.WriteLine("<!DOCTYPE HTML><html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"{0}\" /></head><body>", Path.GetFileName(cssFile));
using(StreamWriter sw = new StreamWriter(cssFile))
{
Bitmap bmpPack = new Bitmap((int) totalImageWidth, (int) totalImageHeight);
using(Graphics gfx = Graphics.FromImage(bmpPack))
{
long currentY = 0;
foreach(Run imageRun in ImageRuns)
{
long currentX = 0;
foreach(Image image in imageRun.Images)
{
// Render the image
gfx.DrawImage(image, currentX, currentY);
// Build the CSS for this image
string fileName = image.Tag as string;
if ( fileName != null )
{
string imageName = Path.GetFileNameWithoutExtension(fileName);
string imagePack = Path.GetFileNameWithoutExtension(outputFile);
string imageUrl = Path.GetFileName(outputFile);
sw.WriteLine(cssFormat,
imagePack, imageName, image.Width, image.Height, currentX, currentY, imageUrl,
image.Width / 2, image.Height / 2, currentX / 2, currentY / 2, totalImageWidth / 2, totalImageHeight / 2,
image.Width * 2, image.Height * 2, currentX * 2, currentY * 2, totalImageWidth * 2, totalImageHeight * 2);
swDebug.WriteLine("<div id=\"{0}_{1}\"></div>", imagePack, imageName);
swDebug.WriteLine("<div id=\"{0}_{1}_half\"></div>", imagePack, imageName);
swDebug.WriteLine("<div id=\"{0}_{1}_double\"></div>", imagePack, imageName);
}
else
{
throw new Exception("Image has a null tag, no name, can't emit CSS");
}
// Update our current offsets
currentX += image.Width;
}
currentY += imageRun.RunHeight;
}
bmpPack.Save(outputFile, ImageFormat.Png);
}
}
swDebug.WriteLine("</body></html>");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment