Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions IconHelper.Test/ProcessImageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -376,4 +376,26 @@ public void PreservesTransparencyOfTheSourceArtwork()
Assert.AreEqual(0, image[15, 15].A, "The transparent notch should survive processing.");
Assert.AreEqual(255, image[2, 2].A, "The surrounding artwork should remain opaque.");
}

[TestMethod]
public void ReducesAnAnimatedInputToItsFirstFrame()
{
// An animated GIF, APNG or WebP decodes to one frame per animation step. Only the root frame
// used to get the coverage pass, so the rest were written out as an animated PNG of raw
// greyscale, cropped to the root frame's bounds.
using Image<Rgba32> image = TestImages.Blank(64, 64);
TestImages.FillRect(image, 20, 20, 20, 20, OpaqueWhite);
using (Image<Rgba32> second = TestImages.Blank(64, 64))
{
TestImages.FillRect(second, 5, 5, 55, 55, OpaqueWhite);
image.Frames.AddFrame(second.Frames.RootFrame);
}

IconHelper.ProcessImage(image, Color.FromBytes(255, 0, 0), 32, 0);

Assert.AreEqual(1, image.Frames.Count, "The output should be a single static frame.");
Assert.AreEqual(20, image.Width, "The bounds should come from the first frame's artwork.");
Assert.AreEqual(new Rgba32(255, 0, 0, 255), image[10, 10]);
Assert.AreEqual(new Rgba32(255, 0, 0, 255), image[0, 0]);
}
}
9 changes: 9 additions & 0 deletions IconHelper/IconHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,21 @@ internal static BatchResult ProcessDirectory(Arguments args, Color color)
/// Reduces an icon to a coverage mask: every pixel carries the flat target colour and the shape
/// lives entirely in the alpha channel. The result is trimmed of its transparent margins, centred
/// on a square canvas and scaled down to at most <paramref name="size"/> pixels.
/// An animated input is reduced to its first frame, since the output is a static icon.
/// The image is mutated in place.
/// </summary>
internal static void ProcessImage(Image<Rgba32> image, Color color, int size, int padding)
{
Ensure.NotNull(image);

// An animated GIF, APNG or WebP loads with one frame per step. Mutate applies to every frame,
// but ProcessPixelRows below reads and writes the root frame alone, so the other frames would
// skip the coverage pass and be saved as an animated PNG of raw greyscale. Drop them first.
while (image.Frames.Count > 1)
{
image.Frames.RemoveFrame(image.Frames.Count - 1);
}

// The semantic Color stores linear channels as doubles. Encode to sRGB bytes once here rather
// than per pixel, both for speed and so the pass below stays plain byte arithmetic.
(byte colorR, byte colorG, byte colorB, byte _) = color.ToBytes();
Expand Down
Loading