Browse Source

Bugfixed sprite packer, and implemented image outputting.

tags/v0.1
Starbeamrainbowlabs 3 years ago
parent
commit
c4e9e8e306

+ 0
- 137
SpritePacker/Packer.cs View File

@@ -1,137 +0,0 @@
1
-using System;
2
-using System.Collections.Generic;
3
-using System.Drawing;
4
-using System.Security.Cryptography;
5
-using System.IO;
6
-using System.Configuration;
7
-using System.Drawing.Text;
8
-
9
-namespace SpritePacker
10
-{
11
-	public class Packer
12
-	{
13
-		private List<Sprite> sprites = new List<Sprite>();
14
-
15
-		public Packer()
16
-		{
17
-			
18
-		}
19
-
20
-		public void Add(Sprite sprite)
21
-		{
22
-			sprites.Add(sprite);
23
-		}
24
-		public bool Remove(Sprite sprite)
25
-		{
26
-			return sprites.Remove(sprite);
27
-		}
28
-
29
-		public void Arrange()
30
-		{
31
-			List<Sprite> arrangedSprites = new List<Sprite>();
32
-			foreach(Sprite cspr in sprites)
33
-			{
34
-				Point scanLines = Point.Empty;
35
-				Point nextScanLines = new Point(int.MaxValue, int.MaxValue);
36
-				while(true)
37
-				{
38
-					if (!cspr.IntersectsWith(arrangedSprites))
39
-						break;
40
-
41
-					// Scan along the X axis
42
-					cspr.X = 0;
43
-					cspr.Y = scanLines.Y;
44
-
45
-					bool foundPosition = false;
46
-					while(cspr.X < scanLines.X)
47
-					{
48
-						if (!cspr.IntersectsWith(arrangedSprites))
49
-						{
50
-							foundPosition = true;
51
-							break;
52
-						}
53
-
54
-						// Get the edge furthest to the right
55
-						List<Sprite> problems = cspr.GetIntersectors(arrangedSprites);
56
-						Sprite rightProblem = problems[0];
57
-						foreach (Sprite probSpr in problems)
58
-						{
59
-							if (probSpr.Right > rightProblem.Right)
60
-								rightProblem = probSpr;
61
-							if (probSpr.Top < nextScanLines.Y)
62
-								nextScanLines.Y = probSpr.Top + 1;
63
-						}
64
-
65
-						// Move up to the position furthest to the right
66
-						// NOTE: We may need to add one here.
67
-						cspr.X = rightProblem.Right + 1;
68
-
69
-					}
70
-
71
-					if (!foundPosition)
72
-					{
73
-						// We didn't find anything along the x axis - let's scan the y axis next
74
-						cspr.X = scanLines.X;
75
-						cspr.Y = 0;
76
-						while (cspr.Y < scanLines.Y)
77
-						{
78
-							if (!cspr.IntersectsWith(arrangedSprites))
79
-							{
80
-								foundPosition = true;
81
-								break;
82
-							}
83
-
84
-							// Get the edge furthest downwards
85
-							List<Sprite> problems = cspr.GetIntersectors(arrangedSprites);
86
-							Sprite downProblem = problems[0];
87
-							foreach (Sprite probSpr in problems)
88
-							{
89
-								if (probSpr.Bottom > downProblem.Bottom)
90
-									downProblem = probSpr;
91
-								if (probSpr.Left < nextScanLines.X)
92
-									nextScanLines.X = probSpr.Left + 1;
93
-							}
94
-							// Move up to the position furthest downwards
95
-							cspr.Y = downProblem.Bottom + 1;
96
-						}
97
-					}
98
-
99
-					// If we found a new position, then we don't need to move the scan lines up and try again
100
-					if (foundPosition)
101
-						break;
102
-
103
-					// Make sure that the next scan lines are sane
104
-					if (nextScanLines.X == int.MaxValue)
105
-						nextScanLines.X = scanLines.X;
106
-					if (nextScanLines.Y == int.MaxValue)
107
-						nextScanLines.Y = scanLines.Y;
108
-
109
-					// If the next scan lines and the current scan lines are identical,
110
-					// then something is very wrong
111
-					if(nextScanLines.Equals(scanLines))
112
-						throw new Exception("Failed to find the next set of lines to scan!");
113
-
114
-					// Move the scan lines up to the next nearest ones we've found
115
-					scanLines = nextScanLines;
116
-					nextScanLines = new Point(int.MaxValue, int.MaxValue);
117
-				}
118
-
119
-				arrangedSprites.Add(cspr);
120
-			}
121
-
122
-			// We don't need to copy the list of arranged sprites across to the main list here
123
-			// because Sprite is a class and classes are passed by _reference_.
124
-		}
125
-
126
-		public override string ToString()
127
-		{
128
-			string result = string.Format("SpritePacker:");
129
-
130
-			foreach (Sprite spr in sprites)
131
-				result += string.Format("\t{0}\n", spr);
132
-
133
-			return result;
134
-		}
135
-	}
136
-}
137
-

+ 11
- 3
SpritePacker/Program.cs View File

@@ -23,7 +23,9 @@ namespace SpritePacker
23 23
 		private static string versionTextFilename = "SpritePacker.Resources.VersionText.txt";
24 24
 		private static string commitHashFilename = "SpritePacker.latest-commit-hash.txt";
25 25
 
26
-		private static Packer spritePacker = new Packer();
26
+		private static SpritePacker spritePacker;
27
+
28
+		private static string outputFilename = "spritesheet.png";
27 29
 		private static List<string> values = new List<string>();
28 30
 
29 31
 		public static bool Verbose = false;
@@ -60,11 +62,15 @@ namespace SpritePacker
60 62
 			switch(programMode)
61 63
 			{
62 64
 				case ProgramMode.Normal:
63
-					if(values.Count == 0)
64
-					{
65
+					if (values.Count == 0) {
65 66
 						Console.Error.WriteLine("Error: No filenames specified!");
66 67
 						return 1;
67 68
 					}
69
+
70
+					// Set the output filename to be equal to the first filename in the list
71
+					outputFilename = values[0];
72
+					values.RemoveAt(0);
73
+
68 74
 					RunNormal();
69 75
 					break;
70 76
 				case ProgramMode.DisplayHelpText:
@@ -89,9 +95,11 @@ namespace SpritePacker
89 95
 
90 96
 		public static void RunNormal()
91 97
 		{
98
+			spritePacker = new SpritePacker(Verbose);
92 99
 			addSprites(values);
93 100
 			spritePacker.Arrange();
94 101
 			Console.WriteLine(spritePacker.ToString());
102
+			spritePacker.Output(outputFilename);
95 103
 		}
96 104
 
97 105
 		private static void addSprites(List<string> filenames)

+ 1
- 1
SpritePacker/Resources/HelpText.txt View File

@@ -1,5 +1,5 @@
1 1
 SpritePacker {version}, by Starbeamrainbowlabs
2
-Built at {build-date} from git commit {commit-hash}
2
+Built at {build-date}
3 3
 
4 4
 Usage:
5 5
     mono ./SpritePacker.exe [flags] [filenames]

+ 12
- 2
SpritePacker/Sprite.cs View File

@@ -1,6 +1,7 @@
1 1
 using System;
2 2
 using System.Collections.Generic;
3 3
 using System.Drawing;
4
+using System.Runtime.Remoting.Messaging;
4 5
 
5 6
 namespace SpritePacker
6 7
 {
@@ -8,6 +9,7 @@ namespace SpritePacker
8 9
 	{
9 10
 		private Rectangle area;
10 11
 		private string filename;
12
+		private Image image;
11 13
 
12 14
 		public Rectangle Area
13 15
 		{
@@ -19,7 +21,9 @@ namespace SpritePacker
19 21
 			get { return filename; }
20 22
 			set { filename = value; }
21 23
 		}
24
+		public Image Image { get { return image; } }
22 25
 
26
+		public Point Location { get { return new Point(X, Y); } }
23 27
 		public int X
24 28
 		{
25 29
 			get { return area.X; }
@@ -45,11 +49,15 @@ namespace SpritePacker
45 49
 		public int Left { get { return area.Left; } }
46 50
 		public int Right { get { return area.Right; } }
47 51
 
52
+		public int AreaSize { get { return area.Width * area.Height; } }
53
+
48 54
 		public Sprite(string inFilename)
49 55
 		{
50 56
 			Filename = inFilename;
51
-			// TODO: Fill in the area automagically based on the given image
52
-			throw new NotImplementedException("Todo: Fill in the area automagically based on the given image");
57
+
58
+			image = Image.FromFile(filename);
59
+			Width = image.Width;
60
+			Height = image.Height;
53 61
 		}
54 62
 
55 63
 		public static int GetLargestSize(List<Sprite> sprList)
@@ -68,6 +76,8 @@ namespace SpritePacker
68 76
 			List<Sprite> result = new List<Sprite>();
69 77
 			foreach(Sprite spr in spriteList)
70 78
 			{
79
+				if (spr == this) // Don't attempt to check for collisions with ourselves
80
+					continue;
71 81
 				if (spr.IntersectsWith(this))
72 82
 					result.Add(spr);
73 83
 			}

+ 226
- 0
SpritePacker/SpritePacker.cs View File

@@ -0,0 +1,226 @@
1
+using System;
2
+using System.Collections.Generic;
3
+using System.Drawing;
4
+using System.Security.Cryptography;
5
+using System.IO;
6
+using System.Configuration;
7
+using System.Drawing.Text;
8
+using System.Drawing.Imaging;
9
+
10
+namespace SpritePacker
11
+{
12
+	public class SpritePacker
13
+	{
14
+		/// <summary>
15
+		/// A list of all the sprites added to the sprite packer.
16
+		/// </summary>
17
+		private List<Sprite> sprites = new List<Sprite>();
18
+
19
+		public bool Verbose { get; private set; }
20
+
21
+		public SpritePacker(bool inVerbose = false)
22
+		{
23
+			Verbose = inVerbose;
24
+		}
25
+
26
+		/// <summary>
27
+		/// Adds a sprite to the sprite packer.
28
+		/// </summary>
29
+		/// <param name="sprite">Sprite.</param>
30
+		public void Add(Sprite sprite)
31
+		{
32
+			Console.WriteLine("Adding {0}.", sprite);
33
+			sprites.Add(sprite);
34
+		}
35
+		/// <summary>
36
+		/// Adds a sprite to the sprite packer.
37
+		/// </summary>
38
+		/// <param name="sprite">Whether a sprite was actually removed or not.</param>
39
+		public bool Remove(Sprite sprite)
40
+		{
41
+			return sprites.Remove(sprite);
42
+		}
43
+		/// <summary>
44
+		/// Clears the list of sprites added to the current sprite packer.
45
+		/// </summary>
46
+		public void Clear()
47
+		{
48
+			sprites.Clear();
49
+		}
50
+
51
+		/// <summary>
52
+		/// Packs all the added sprites in as small a area as possible.
53
+		/// Note that this operation may potentially be very computationally expensive.
54
+		/// </summary>
55
+		public void Arrange()
56
+		{
57
+			sortBySize();
58
+
59
+			List<Sprite> arrangedSprites = new List<Sprite>();
60
+			foreach(Sprite cspr in sprites)
61
+			{
62
+				if(Verbose) Console.WriteLine("Attempting to place {0}.", cspr);
63
+				Point scanLines = Point.Empty;
64
+				Point nextScanLines = new Point(int.MaxValue, int.MaxValue);
65
+				while(true)
66
+				{
67
+					if (!cspr.IntersectsWith(arrangedSprites))
68
+						break;
69
+
70
+					if(Verbose) Console.WriteLine("Scan lines: {0}", scanLines);
71
+
72
+					if(Verbose) Console.WriteLine("Scanning X...");
73
+					// Scan along the X axis
74
+					cspr.X = 0;
75
+					cspr.Y = scanLines.Y;
76
+
77
+					bool foundPosition = false;
78
+					while(cspr.X <= scanLines.X)
79
+					{
80
+						if(Verbose) Console.Write("Position: {0} ", cspr.X);
81
+
82
+						if (!cspr.IntersectsWith(arrangedSprites))
83
+						{
84
+							if(Verbose) Console.WriteLine("Found position on the X axis.");
85
+							foundPosition = true;
86
+							break;
87
+						}
88
+
89
+						// Get the edge furthest to the right
90
+						List<Sprite> problems = cspr.GetIntersectors(arrangedSprites);
91
+						Sprite rightProblem = problems[0];
92
+						foreach (Sprite probSpr in problems)
93
+						{
94
+							if (probSpr.Right > rightProblem.Right)
95
+								rightProblem = probSpr;
96
+							// If the current problem's bottom edge is less than the bottom of the next scan line,
97
+							// move the next scan line up a bit.
98
+							// Also make sure that the next scan line and the current scan line don't touch or cross.
99
+							if (probSpr.Bottom < nextScanLines.Y && probSpr.Bottom > scanLines.Y)
100
+								nextScanLines.Y = probSpr.Bottom + 1;
101
+						}
102
+						if(Verbose) Console.WriteLine("Found rightmost problem: {0}", rightProblem);
103
+
104
+						// Move up to the position furthest to the right
105
+						cspr.X = rightProblem.Right + 1;
106
+					}
107
+
108
+					if (!foundPosition)
109
+					{
110
+						if(Verbose) Console.WriteLine("Failed to find anything on the X axis. Scanning Y...");
111
+						// We didn't find anything along the x axis - let's scan the y axis next
112
+						cspr.X = scanLines.X;
113
+						cspr.Y = 0;
114
+						while (cspr.Y <= scanLines.Y)
115
+						{
116
+							if(Verbose) Console.Write("Position: {0} ", cspr.Y);
117
+							if (!cspr.IntersectsWith(arrangedSprites))
118
+							{
119
+								if(Verbose) Console.WriteLine("Found position on the Y axis.");
120
+								foundPosition = true;
121
+								break;
122
+							}
123
+
124
+							// Get the edge furthest downwards
125
+							List<Sprite> problems = cspr.GetIntersectors(arrangedSprites);
126
+							Sprite downProblem = problems[0];
127
+							foreach (Sprite probSpr in problems)
128
+							{
129
+								if (probSpr.Bottom > downProblem.Bottom)
130
+									downProblem = probSpr;
131
+								// If the current problem's right edge is further in than the current next scan line,
132
+								// move the enxt scan line up to meet it.
133
+								// Also make sure that the next scan line and the current scan line don't touch or cross.
134
+								if (probSpr.Right < nextScanLines.X && probSpr.Right > scanLines.X)
135
+									nextScanLines.X = probSpr.Right + 1;
136
+							}
137
+							if(Verbose) Console.WriteLine("Found downProblem {0}", downProblem);
138
+
139
+							// Move up to the position furthest downwards
140
+							cspr.Y = downProblem.Bottom + 1;
141
+						}
142
+					}
143
+
144
+					// If we found a new position, then we don't need to move the scan lines up and try again
145
+					if (foundPosition)
146
+						break;
147
+
148
+					if(Verbose) Console.WriteLine("Failed to find a position along the current scan lines.");
149
+					if(Verbose) Console.WriteLine("Next candidate scan lines: {0}", nextScanLines);
150
+
151
+					// Make sure that the next scan lines are sane
152
+					if (nextScanLines.X == int.MaxValue)
153
+						nextScanLines.X = scanLines.X;
154
+					if (nextScanLines.Y == int.MaxValue)
155
+						nextScanLines.Y = scanLines.Y;
156
+
157
+					if(Verbose) Console.WriteLine("Actual next scan lines: {0}", nextScanLines);
158
+
159
+					// If the next scan lines and the current scan lines are identical,
160
+					// then something is very wrong
161
+					if(nextScanLines.Equals(scanLines))
162
+						throw new Exception("Failed to find the next set of lines to scan!");
163
+
164
+					// Move the scan lines up to the next nearest ones we've found
165
+					scanLines = nextScanLines;
166
+					nextScanLines = new Point(int.MaxValue, int.MaxValue);
167
+				}
168
+
169
+				arrangedSprites.Add(cspr);
170
+				
171
+				if(Verbose) Console.WriteLine("Finished positioning {0}.", cspr);
172
+			}
173
+
174
+			// We don't need to copy the list of arranged sprites across to the main list here
175
+			// because Sprite is a class and classes are passed by _reference_.
176
+		}
177
+
178
+		public void Output(string outputFilename)
179
+		{
180
+			// Calculate the size of the image we are about to output
181
+			Point imageSize = new Point(0, 0);
182
+			foreach(Sprite spr in sprites)
183
+			{
184
+				if (spr.Bottom > imageSize.Y)
185
+					imageSize.Y = spr.Bottom;
186
+				if (spr.Right > imageSize.X)
187
+					imageSize.X = spr.Right;
188
+			}
189
+
190
+			Bitmap finalImage = new Bitmap(imageSize.X, imageSize.Y, PixelFormat.Format32bppArgb);
191
+			finalImage.MakeTransparent();
192
+			Graphics context = Graphics.FromImage(finalImage);
193
+			foreach(Sprite spr in sprites)
194
+			{
195
+				context.DrawImage(spr.Image, spr.Location);
196
+			}
197
+			finalImage.Save(outputFilename);
198
+
199
+			context.Dispose();
200
+			finalImage.Dispose();
201
+		}
202
+
203
+		/// <summary>
204
+		/// Sorts the sprites by size.
205
+		/// </summary>
206
+		private void sortBySize()
207
+		{
208
+			sprites.Sort((a, b) => -a.AreaSize.CompareTo(b.AreaSize));
209
+		}
210
+
211
+		/// <summary>
212
+		/// Returns a string that represents the current sprite packer.
213
+		/// </summary>
214
+		/// <returns>A string that represents the current sprite packer.</returns>
215
+		public override string ToString()
216
+		{
217
+			string result = string.Format("SpritePacker:") + Environment.NewLine;
218
+
219
+			foreach (Sprite spr in sprites)
220
+				result += string.Format("\t{0}\n", spr);
221
+
222
+			return result;
223
+		}
224
+	}
225
+}
226
+

+ 2
- 1
SpritePacker/SpritePacker.csproj View File

@@ -19,6 +19,7 @@
19 19
     <WarningLevel>4</WarningLevel>
20 20
     <Externalconsole>true</Externalconsole>
21 21
     <PlatformTarget>x86</PlatformTarget>
22
+    <Commandlineparameters>result.png /tmp/*.png</Commandlineparameters>
22 23
   </PropertyGroup>
23 24
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
24 25
     <DebugType>full</DebugType>
@@ -39,9 +40,9 @@
39 40
   <ItemGroup>
40 41
     <Compile Include="Program.cs" />
41 42
     <Compile Include="Properties\AssemblyInfo.cs" />
42
-    <Compile Include="Packer.cs" />
43 43
     <Compile Include="Sprite.cs" />
44 44
     <Compile Include="Utilities.cs" />
45
+    <Compile Include="SpritePacker.cs" />
45 46
   </ItemGroup>
46 47
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
47 48
   <ItemGroup>

Loading…
Cancel
Save