Skip to content

Commit ef03843

Browse files
authored
Merge pull request #2 from TohnoCoding/bounding-box-normalization
Bounding box normalization post detection processing to stabilize boundaries when outputting to map.
2 parents c3cfce9 + d8c5d0f commit ef03843

File tree

1 file changed

+75
-4
lines changed

1 file changed

+75
-4
lines changed

SpriteImageParser/Parser.cs

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@ public class Parser
99
/// Detects sprite regions in a given image represented as a 2D array of pixels.
1010
/// </summary>
1111
/// <param name="image">The 2D array of pixels to detect sprites in.</param>
12+
/// <param name="yTolerance">The variance in the Y axis to account for when grouping
13+
/// sprites together in a single row. (Plus/Minus N amount of pixels.)</param>
1214
/// <param name="transparencyMask">Optional; if provided, this pixel value will be taken
1315
/// as the transparency color for non-transparent images.</param>
1416
/// <returns>A list of <see cref="SpriteRegion"/>s with the locations and dimensions
1517
/// of all sprites found in the image.</returns>
16-
public static List<SpriteRegion> DetectSpritesInImage(Pixel[,] image, Pixel? transparencyMask = null)
18+
public static List<SpriteRegion> DetectSpritesInImage(
19+
Pixel[,] image,
20+
int yTolerance = 3,
21+
Pixel? transparencyMask = null)
1722
{
1823
var detectedSprites = new List<SpriteRegion>(); // return value
1924
int width = image.GetLength(0);
@@ -40,9 +45,75 @@ public static List<SpriteRegion> DetectSpritesInImage(Pixel[,] image, Pixel? tra
4045
else { visited[x, y] = true; }
4146
}
4247
}
43-
return detectedSprites.OrderBy(r => r.Y).ThenBy(r => r.X).ToList();
48+
detectedSprites = [.. detectedSprites.OrderBy(r => r.Y).ThenBy(r => r.X)];
49+
50+
var groupedRows = GroupByRow(detectedSprites, yTolerance);
51+
52+
NormalizeSpriteRows(groupedRows);
53+
54+
return [.. groupedRows.SelectMany(row => row).OrderBy(r => r.Y).ThenBy(r => r.X)];
4455
}
4556

57+
58+
/// <summary>
59+
/// Groups sprite regions by their Y coordinate, allowing for a specified tolerance.
60+
/// </summary>
61+
/// <param name="regions">The full list of detected sprite regions in an image.</param>
62+
/// <param name="yTolerance">The Y-axis potential variance to account for when grouping
63+
/// sprite groups.</param>
64+
/// <returns>A matrix of lists of <see cref="SpriteRegion"/> representing each row of
65+
/// sprites.</returns>
66+
private static List<List<SpriteRegion>> GroupByRow(List<SpriteRegion> regions, int yTolerance)
67+
{
68+
var rows = new List<List<SpriteRegion>>();
69+
foreach (var region in regions)
70+
{
71+
bool added = false;
72+
foreach (var row in rows)
73+
{
74+
// Instead of comparing to just row[0], compare to all regions in the row
75+
if (row.Any(r => Math.Abs(r.Y - region.Y) <= yTolerance))
76+
{
77+
row.Add(region);
78+
added = true;
79+
break;
80+
}
81+
}
82+
if (!added) { rows.Add([region]); }
83+
}
84+
return rows;
85+
}
86+
87+
88+
/// <summary>
89+
/// Normalizes the sprite regions in each row to have the same width and height, based
90+
/// on the dimensions of the largest sprite in the row.
91+
/// </summary>
92+
/// <param name="groupedRows">The list of sprite rows (represented as lists of
93+
/// <see cref="SpriteRegion"/>) that forms the spritesheet.</param>
94+
private static void NormalizeSpriteRows(List<List<SpriteRegion>> groupedRows)
95+
{
96+
foreach (var row in groupedRows)
97+
{
98+
int maxWidth = row.Max(r => r.Width);
99+
int maxHeight = row.Max(r => r.Height);
100+
int maxBottom = row.Max(r => r.Y + r.Height);
101+
for (int i = 0; i < row.Count; i++)
102+
{
103+
var sprite = row[i];
104+
int newY = maxBottom - maxHeight;
105+
int yOffset = sprite.Y - newY;
106+
int xOffset = (maxWidth - sprite.Width) / 2;
107+
sprite.Y -= yOffset; // move up or down to align bottom
108+
sprite.X -= xOffset; // move left or right to center
109+
sprite.Width = maxWidth;
110+
sprite.Height = maxHeight;
111+
row[i] = sprite; // reassign the modified struct
112+
}
113+
}
114+
}
115+
116+
46117
/// <summary>
47118
/// Performs a flood fill algorithm to find the bounding box of a sprite region.
48119
/// </summary>
@@ -92,13 +163,13 @@ private static SpriteRegion FloodFill(Pixel[,] image, int startX, int startY, bo
92163
}
93164
return new SpriteRegion
94165
{
95-
X = minX,
96-
Y = minY,
166+
X = minX, Y = minY,
97167
Width = maxX - minX + 1,
98168
Height = maxY - minY + 1
99169
};
100170
}
101171

172+
102173
/// <summary>
103174
/// Gets the neighboring pixels of a given pixel in an image.
104175
/// </summary>

0 commit comments

Comments
 (0)