Path: blob/master/src/util-tests/animated_image_tests.cpp
4802 views
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]>1// SPDX-License-Identifier: CC-BY-NC-ND-4.023#include "util/animated_image.h"45#include "common/error.h"67#include <gtest/gtest.h>8#include <vector>910namespace {11// Test fixture for AnimatedImage tests12class AnimatedImageTest : public ::testing::Test13{14protected:15// Helper method to create test images with a pattern16AnimatedImage CreateTestImage(u32 width, u32 height, u32 frames = 1)17{18AnimatedImage img(width, height, frames, {1, 10});19for (u32 f = 0; f < frames; f++)20{21for (u32 y = 0; y < height; y++)22{23for (u32 x = 0; x < width; x++)24{25img.GetPixels(f)[y * width + x] = (x + y + f) | 0xFF000000; // Make pixel opaque26}27}28}29return img;30}31};3233} // namespace3435// Constructor Tests36TEST_F(AnimatedImageTest, DefaultConstructor)37{38AnimatedImage img;39EXPECT_FALSE(img.IsValid());40EXPECT_EQ(img.GetWidth(), 0u);41EXPECT_EQ(img.GetHeight(), 0u);42EXPECT_EQ(img.GetFrames(), 0u);43}4445TEST_F(AnimatedImageTest, ParameterizedConstructor)46{47const u32 width = 100;48const u32 height = 80;49const u32 frames = 5;50AnimatedImage::FrameDelay delay{1, 10};5152AnimatedImage img(width, height, frames, delay);53EXPECT_TRUE(img.IsValid());54EXPECT_EQ(img.GetWidth(), width);55EXPECT_EQ(img.GetHeight(), height);56EXPECT_EQ(img.GetFrames(), frames);57EXPECT_EQ(img.GetFrameSize(), width * height);5859// Check all frames have the same delay60for (u32 i = 0; i < frames; i++)61{62EXPECT_EQ(img.GetFrameDelay(i).numerator, delay.numerator);63EXPECT_EQ(img.GetFrameDelay(i).denominator, delay.denominator);64}65}6667TEST_F(AnimatedImageTest, CopyConstructor)68{69auto original = CreateTestImage(50, 40, 2);70AnimatedImage copy(original);7172EXPECT_EQ(copy.GetWidth(), original.GetWidth());73EXPECT_EQ(copy.GetHeight(), original.GetHeight());74EXPECT_EQ(copy.GetFrames(), original.GetFrames());7576// Check pixels match77for (u32 f = 0; f < original.GetFrames(); f++)78{79for (u32 i = 0; i < original.GetFrameSize(); i++)80{81EXPECT_EQ(copy.GetPixels(f)[i], original.GetPixels(f)[i]);82}83}84}8586TEST_F(AnimatedImageTest, MoveConstructor)87{88auto original = CreateTestImage(50, 40, 2);89const u32 width = original.GetWidth();90const u32 height = original.GetHeight();91const u32 frames = original.GetFrames();9293// Store original pixel data to compare later94std::vector<std::vector<u32>> pixel_copies;95for (u32 f = 0; f < frames; f++)96{97pixel_copies.push_back(std::vector<u32>(original.GetPixels(f), original.GetPixels(f) + original.GetFrameSize()));98}99100AnimatedImage moved(std::move(original));101102EXPECT_FALSE(original.IsValid()); // Original should be invalid after move103EXPECT_EQ(moved.GetWidth(), width);104EXPECT_EQ(moved.GetHeight(), height);105EXPECT_EQ(moved.GetFrames(), frames);106107// Check pixels were moved correctly108for (u32 f = 0; f < frames; f++)109{110for (u32 i = 0; i < width * height; i++)111{112EXPECT_EQ(moved.GetPixels(f)[i], pixel_copies[f][i]);113}114}115}116117// Assignment Operator Tests118TEST_F(AnimatedImageTest, CopyAssignment)119{120auto original = CreateTestImage(60, 50, 3);121AnimatedImage copy;122copy = original;123124EXPECT_EQ(copy.GetWidth(), original.GetWidth());125EXPECT_EQ(copy.GetHeight(), original.GetHeight());126EXPECT_EQ(copy.GetFrames(), original.GetFrames());127128// Check pixels match129for (u32 f = 0; f < original.GetFrames(); f++)130{131for (u32 i = 0; i < original.GetFrameSize(); i++)132{133EXPECT_EQ(copy.GetPixels(f)[i], original.GetPixels(f)[i]);134}135}136}137138TEST_F(AnimatedImageTest, MoveAssignment)139{140auto original = CreateTestImage(60, 50, 3);141const u32 width = original.GetWidth();142const u32 height = original.GetHeight();143const u32 frames = original.GetFrames();144145std::vector<std::vector<u32>> pixel_copies;146for (u32 f = 0; f < frames; f++)147{148pixel_copies.push_back(std::vector<u32>(original.GetPixels(f), original.GetPixels(f) + original.GetFrameSize()));149}150151AnimatedImage moved;152moved = std::move(original);153154EXPECT_FALSE(original.IsValid()); // Original should be invalid after move155EXPECT_EQ(moved.GetWidth(), width);156EXPECT_EQ(moved.GetHeight(), height);157EXPECT_EQ(moved.GetFrames(), frames);158159// Check pixels were moved correctly160for (u32 f = 0; f < frames; f++)161{162for (u32 i = 0; i < width * height; i++)163{164EXPECT_EQ(moved.GetPixels(f)[i], pixel_copies[f][i]);165}166}167}168169// Pixel Access Tests170TEST_F(AnimatedImageTest, PixelAccess)171{172const u32 width = 10;173const u32 height = 8;174AnimatedImage img(width, height, 1, {1, 10});175176// Test direct pixel access177for (u32 y = 0; y < height; y++)178{179for (u32 x = 0; x < width; x++)180{181img.GetPixels(0)[y * width + x] = 0xFF000000u | (x + y * width);182}183}184185// Verify pixels186for (u32 y = 0; y < height; y++)187{188for (u32 x = 0; x < width; x++)189{190EXPECT_EQ(img.GetPixels(0)[y * width + x], 0xFF000000u | (x + y * width));191EXPECT_EQ(img.GetRowPixels(0, y)[x], 0xFF000000u | (x + y * width));192}193}194195// Test GetPixelsSpan196auto span = img.GetPixelsSpan(0);197for (u32 i = 0; i < width * height; i++)198{199EXPECT_EQ(span[i], 0xFF000000u | i);200}201}202203TEST_F(AnimatedImageTest, SetPixels)204{205const u32 width = 10;206const u32 height = 8;207AnimatedImage img(width, height, 1, {1, 10});208209// Create source pixels210std::vector<u32> src_pixels(width * height);211for (u32 i = 0; i < width * height; i++)212{213src_pixels[i] = 0xFF000000 | i;214}215216// Copy with SetPixels217img.SetPixels(0, src_pixels.data(), width * sizeof(u32));218219// Verify pixels220for (u32 i = 0; i < width * height; i++)221{222EXPECT_EQ(img.GetPixels(0)[i], 0xFF000000u | i);223}224}225226TEST_F(AnimatedImageTest, SetDelay)227{228AnimatedImage img(10, 10, 2, {1, 10});229230AnimatedImage::FrameDelay delay{2, 20};231img.SetDelay(1, delay);232233EXPECT_EQ(img.GetFrameDelay(1).numerator, 2);234EXPECT_EQ(img.GetFrameDelay(1).denominator, 20);235236// First frame should be unchanged237EXPECT_EQ(img.GetFrameDelay(0).numerator, 1);238EXPECT_EQ(img.GetFrameDelay(0).denominator, 10);239}240241// Image Manipulation Tests242TEST_F(AnimatedImageTest, Resize)243{244AnimatedImage img = CreateTestImage(10, 8, 2);245AnimatedImage img2 = CreateTestImage(10, 8, 2);246247// Resize to larger dimensions, preserving content248img.Resize(20, 16, 3, {1, 10}, true);249250EXPECT_EQ(img.GetWidth(), 20u);251EXPECT_EQ(img.GetHeight(), 16u);252EXPECT_EQ(img.GetFrames(), 3u);253254// Check that original content is preserved255for (u32 f = 0; f < 2; f++)256{257for (u32 y = 0; y < 8; y++)258{259for (u32 x = 0; x < 10; x++)260{261EXPECT_EQ(img.GetRowPixels(f, y)[x] & 0xFFu, (x + y + f) & 0xFFu);262}263}264}265266// Check that new areas are zeroed267for (u32 f = 0; f < 2; f++)268{269for (u32 y = 0; y < 8; y++)270{271for (u32 x = 10; x < 20; x++)272{273EXPECT_EQ(img.GetRowPixels(f, y)[x], 0u);274}275}276for (u32 y = 8; y < 16; y++)277{278for (u32 x = 0; x < 20; x++)279{280EXPECT_EQ(img.GetRowPixels(f, y)[x], 0u);281}282}283}284285// Check third frame has the specified default delay286EXPECT_EQ(img.GetFrameDelay(2).numerator, 1u);287EXPECT_EQ(img.GetFrameDelay(2).denominator, 10u);288}289290TEST_F(AnimatedImageTest, Clear)291{292AnimatedImage img = CreateTestImage(10, 8, 2);293294img.Clear();295296// Dimensions should remain the same297EXPECT_EQ(img.GetWidth(), 10u);298EXPECT_EQ(img.GetHeight(), 8u);299EXPECT_EQ(img.GetFrames(), 2u);300301// All pixels should be zeroed302for (u32 f = 0; f < img.GetFrames(); f++)303{304for (u32 i = 0; i < img.GetFrameSize(); i++)305{306EXPECT_EQ(img.GetPixels(f)[i], 0u);307}308}309}310311TEST_F(AnimatedImageTest, Invalidate)312{313AnimatedImage img = CreateTestImage(10, 8, 2);314315img.Invalidate();316317EXPECT_FALSE(img.IsValid());318EXPECT_EQ(img.GetWidth(), 0u);319EXPECT_EQ(img.GetHeight(), 0u);320EXPECT_EQ(img.GetFrames(), 0u);321}322323TEST_F(AnimatedImageTest, TakePixels)324{325AnimatedImage img = CreateTestImage(10, 8, 2);326const u32 expected_size = 10 * 8 * 2;327328auto pixels = img.TakePixels();329330// Image should be invalidated331EXPECT_FALSE(img.IsValid());332EXPECT_EQ(img.GetWidth(), 0u);333EXPECT_EQ(img.GetHeight(), 0u);334EXPECT_EQ(img.GetFrames(), 0u);335336// Pixel storage should have the expected size337EXPECT_EQ(pixels.size(), expected_size);338}339340// File Operations Tests341TEST_F(AnimatedImageTest, LoadSavePNG)342{343// Create test image344AnimatedImage original = CreateTestImage(20, 16, 1);345346// Set specific pixel patterns for verification347original.GetPixels(0)[0] = 0xFF0000FFu; // Blue348original.GetPixels(0)[1] = 0xFF00FF00u; // Green349original.GetPixels(0)[2] = 0xFFFF0000u; // Red350351// Save to file352auto buffer = original.SaveToBuffer("test_image.png");353ASSERT_TRUE(buffer.has_value());354355// Load the image back356AnimatedImage loaded;357ASSERT_TRUE(loaded.LoadFromBuffer("test_image.png", buffer.value()));358359// Compare dimensions360EXPECT_EQ(loaded.GetWidth(), original.GetWidth());361EXPECT_EQ(loaded.GetHeight(), original.GetHeight());362EXPECT_EQ(loaded.GetFrames(), 1u);363364// Compare specific pixel colors (ignoring alpha variations)365EXPECT_EQ(loaded.GetPixels(0)[0] & 0xFFFFFFu, 0x0000FFu); // Blue366EXPECT_EQ(loaded.GetPixels(0)[1] & 0xFFFFFFu, 0x00FF00u); // Green367EXPECT_EQ(loaded.GetPixels(0)[2] & 0xFFFFFFu, 0xFF0000u); // Red368}369370TEST_F(AnimatedImageTest, LoadSaveMultiFramePNG)371{372// Create multi-frame test image373AnimatedImage original = CreateTestImage(20, 16, 2);374375// Set different delays for frames376original.SetDelay(0, {1, 10});377original.SetDelay(1, {2, 20});378379// Save to file380auto buffer = original.SaveToBuffer("test_anim.png");381ASSERT_TRUE(buffer.has_value());382383// Load back384AnimatedImage loaded;385ASSERT_TRUE(loaded.LoadFromBuffer("test_anim.png", buffer.value()));386387// Compare dimensions and frame count388EXPECT_EQ(loaded.GetWidth(), original.GetWidth());389EXPECT_EQ(loaded.GetHeight(), original.GetHeight());390EXPECT_EQ(loaded.GetFrames(), original.GetFrames());391392// Compare frame delays393EXPECT_EQ(loaded.GetFrameDelay(0).numerator, 1u);394EXPECT_EQ(loaded.GetFrameDelay(0).denominator, 10u);395EXPECT_EQ(loaded.GetFrameDelay(1).numerator, 2u);396EXPECT_EQ(loaded.GetFrameDelay(1).denominator, 20u);397}398399TEST_F(AnimatedImageTest, SaveLoadBuffer)400{401AnimatedImage original = CreateTestImage(20, 16, 1);402403// Save to buffer404auto buffer = original.SaveToBuffer("test.png");405ASSERT_TRUE(buffer.has_value());406EXPECT_GT(buffer->size(), 0u);407408// Load from buffer409AnimatedImage loaded;410ASSERT_TRUE(loaded.LoadFromBuffer("test.png", *buffer));411412// Compare dimensions413EXPECT_EQ(loaded.GetWidth(), original.GetWidth());414EXPECT_EQ(loaded.GetHeight(), original.GetHeight());415416// Compare some pixels (ignoring alpha)417for (u32 i = 0; i < std::min(10u, original.GetFrameSize()); i++)418{419EXPECT_EQ(loaded.GetPixels(0)[i] & 0xFFFFFF, original.GetPixels(0)[i] & 0xFFFFFF);420}421}422423TEST_F(AnimatedImageTest, ErrorHandling)424{425AnimatedImage img;426Error err;427428// Try loading non-existent file429EXPECT_FALSE(img.LoadFromFile("non_existent_file.png", &err));430EXPECT_TRUE(err.IsValid());431432// Try loading file with invalid extension433EXPECT_FALSE(img.LoadFromFile("test.invalid", &err));434EXPECT_TRUE(err.IsValid());435}436437TEST_F(AnimatedImageTest, CalculatePitch)438{439EXPECT_EQ(AnimatedImage::CalculatePitch(10, 5), 10 * sizeof(u32));440EXPECT_EQ(AnimatedImage::CalculatePitch(100, 200), 100 * sizeof(u32));441}442443// Multiple frame handling and frame delay tests444TEST_F(AnimatedImageTest, MultipleFrameDelays)445{446const u32 width = 32;447const u32 height = 24;448const u32 frames = 5;449AnimatedImage img(width, height, frames, {1, 10});450451// Set different delays for each frame452img.SetDelay(0, {1, 10});453img.SetDelay(1, {2, 20});454img.SetDelay(2, {3, 30});455img.SetDelay(3, {4, 40});456img.SetDelay(4, {5, 50});457458// Verify each frame has the correct delay459EXPECT_EQ(img.GetFrameDelay(0).numerator, 1u);460EXPECT_EQ(img.GetFrameDelay(0).denominator, 10u);461EXPECT_EQ(img.GetFrameDelay(1).numerator, 2u);462EXPECT_EQ(img.GetFrameDelay(1).denominator, 20u);463EXPECT_EQ(img.GetFrameDelay(2).numerator, 3u);464EXPECT_EQ(img.GetFrameDelay(2).denominator, 30u);465EXPECT_EQ(img.GetFrameDelay(3).numerator, 4u);466EXPECT_EQ(img.GetFrameDelay(3).denominator, 40u);467EXPECT_EQ(img.GetFrameDelay(4).numerator, 5u);468EXPECT_EQ(img.GetFrameDelay(4).denominator, 50u);469}470471TEST_F(AnimatedImageTest, PreserveFrameDelaysOnResize)472{473const u32 width = 16;474const u32 height = 16;475const u32 frames = 3;476477AnimatedImage img(width, height, frames, {1, 10});478479// Set unique delays for each frame480img.SetDelay(0, {5, 25});481img.SetDelay(1, {10, 50});482img.SetDelay(2, {15, 75});483484// Resize with fewer frames - should preserve existing frame delays485img.Resize(32, 32, 2, {20, 100}, true);486487EXPECT_EQ(img.GetWidth(), 32u);488EXPECT_EQ(img.GetHeight(), 32u);489EXPECT_EQ(img.GetFrames(), 2u);490491// Original frame delays should be preserved492EXPECT_EQ(img.GetFrameDelay(0).numerator, 5u);493EXPECT_EQ(img.GetFrameDelay(0).denominator, 25u);494EXPECT_EQ(img.GetFrameDelay(1).numerator, 10u);495EXPECT_EQ(img.GetFrameDelay(1).denominator, 50u);496497// Resize with more frames - new frames should use the default delay498img.Resize(32, 32, 4, {20, 100}, true);499500EXPECT_EQ(img.GetFrames(), 4u);501502// Original frame delays should still be preserved503EXPECT_EQ(img.GetFrameDelay(0).numerator, 5u);504EXPECT_EQ(img.GetFrameDelay(0).denominator, 25u);505EXPECT_EQ(img.GetFrameDelay(1).numerator, 10u);506EXPECT_EQ(img.GetFrameDelay(1).denominator, 50u);507508// New frames should have the default delay509EXPECT_EQ(img.GetFrameDelay(2).numerator, 20u);510EXPECT_EQ(img.GetFrameDelay(2).denominator, 100u);511EXPECT_EQ(img.GetFrameDelay(3).numerator, 20u);512EXPECT_EQ(img.GetFrameDelay(3).denominator, 100u);513}514515TEST_F(AnimatedImageTest, IndividualFrameModification)516{517const u32 width = 8;518const u32 height = 8;519const u32 frames = 3;520521AnimatedImage img(width, height, frames, {1, 10});522523// Set distinct patterns for each frame524for (u32 f = 0; f < frames; f++)525{526for (u32 y = 0; y < height; y++)527{528for (u32 x = 0; x < width; x++)529{530// Frame 0: all red, Frame 1: all green, Frame 2: all blue531u32 color = (f == 0) ? 0xFF0000FFu : ((f == 1) ? 0xFF00FF00u : 0xFFFF0000u);532img.GetPixels(f)[y * width + x] = color;533}534}535}536537// Verify each frame has the correct pattern538for (u32 y = 0; y < height; y++)539{540for (u32 x = 0; x < width; x++)541{542EXPECT_EQ(img.GetPixels(0)[y * width + x], 0xFF0000FFu); // Red543EXPECT_EQ(img.GetPixels(1)[y * width + x], 0xFF00FF00u); // Green544EXPECT_EQ(img.GetPixels(2)[y * width + x], 0xFFFF0000u); // Blue545}546}547548// Modify only the middle frame549for (u32 y = 0; y < height; y++)550{551for (u32 x = 0; x < width; x++)552{553img.GetPixels(1)[y * width + x] = 0xFFFFFFFFu; // White554}555}556557// Verify only the middle frame was changed558for (u32 y = 0; y < height; y++)559{560for (u32 x = 0; x < width; x++)561{562EXPECT_EQ(img.GetPixels(0)[y * width + x], 0xFF0000FFu); // Still red563EXPECT_EQ(img.GetPixels(1)[y * width + x], 0xFFFFFFFFu); // Now white564EXPECT_EQ(img.GetPixels(2)[y * width + x], 0xFFFF0000u); // Still blue565}566}567}568569TEST_F(AnimatedImageTest, MultiFrameAnimationRoundTrip)570{571const u32 width = 24;572const u32 height = 24;573const u32 frames = 4;574575// Create a test animation with 4 frames, each with different content and timing576AnimatedImage original(width, height, frames, {1, 10});577578// Frame 0: Red with delay 1/10579std::memset(original.GetPixels(0), 0, width * height * sizeof(u32));580for (u32 i = 0; i < width * height; i++)581{582original.GetPixels(0)[i] = 0xFF0000FFu; // Red583}584original.SetDelay(0, {1, 10});585586// Frame 1: Green with delay 2/20587std::memset(original.GetPixels(1), 0, width * height * sizeof(u32));588for (u32 i = 0; i < width * height; i++)589{590original.GetPixels(1)[i] = 0xFF00FF00u; // Green591}592original.SetDelay(1, {2, 20});593594// Frame 2: Blue with delay 3/30595std::memset(original.GetPixels(2), 0, width * height * sizeof(u32));596for (u32 i = 0; i < width * height; i++)597{598original.GetPixels(2)[i] = 0xFFFF0000u; // Blue599}600original.SetDelay(2, {3, 30});601602// Frame 3: Yellow with delay 4/40603std::memset(original.GetPixels(3), 0, width * height * sizeof(u32));604for (u32 i = 0; i < width * height; i++)605{606original.GetPixels(3)[i] = 0xFF00FFFFu; // Yellow607}608original.SetDelay(3, {4, 40});609610// Save to buffer611auto buffer = original.SaveToBuffer("test_animation.png");612ASSERT_TRUE(buffer.has_value());613614// Load back from buffer615AnimatedImage loaded;616ASSERT_TRUE(loaded.LoadFromBuffer("test_animation.png", *buffer));617618// Verify dimensions and frame count619EXPECT_EQ(loaded.GetWidth(), width);620EXPECT_EQ(loaded.GetHeight(), height);621EXPECT_EQ(loaded.GetFrames(), frames);622623// Verify frame delays624EXPECT_EQ(loaded.GetFrameDelay(0).numerator, 1u);625EXPECT_EQ(loaded.GetFrameDelay(0).denominator, 10u);626EXPECT_EQ(loaded.GetFrameDelay(1).numerator, 2u);627EXPECT_EQ(loaded.GetFrameDelay(1).denominator, 20u);628EXPECT_EQ(loaded.GetFrameDelay(2).numerator, 3u);629EXPECT_EQ(loaded.GetFrameDelay(2).denominator, 30u);630EXPECT_EQ(loaded.GetFrameDelay(3).numerator, 4u);631EXPECT_EQ(loaded.GetFrameDelay(3).denominator, 40u);632633// Verify frame contents (sampling first pixel of each frame)634EXPECT_EQ(loaded.GetPixels(0)[0] & 0xFFFFFFu, 0x0000FFu); // Red635EXPECT_EQ(loaded.GetPixels(1)[0] & 0xFFFFFFu, 0x00FF00u); // Green636EXPECT_EQ(loaded.GetPixels(2)[0] & 0xFFFFFFu, 0xFF0000u); // Blue637EXPECT_EQ(loaded.GetPixels(3)[0] & 0xFFFFFFu, 0x00FFFFu); // Yellow638}639640TEST_F(AnimatedImageTest, MaximumAndZeroDelays)641{642const u32 width = 16;643const u32 height = 16;644const u32 frames = 4;645646AnimatedImage img(width, height, frames, {1, 10});647648// Set extreme delay values649img.SetDelay(0, {0, 1}); // Zero numerator (minimum)650img.SetDelay(1, {65535, 1}); // Maximum numerator (u16 max)651img.SetDelay(2, {1, 65535}); // Maximum denominator (u16 max)652img.SetDelay(3, {0, 65535}); // Both extreme653654// Verify delay values were set correctly655EXPECT_EQ(img.GetFrameDelay(0).numerator, 0u);656EXPECT_EQ(img.GetFrameDelay(0).denominator, 1u);657EXPECT_EQ(img.GetFrameDelay(1).numerator, 65535u);658EXPECT_EQ(img.GetFrameDelay(1).denominator, 1u);659EXPECT_EQ(img.GetFrameDelay(2).numerator, 1u);660EXPECT_EQ(img.GetFrameDelay(2).denominator, 65535u);661EXPECT_EQ(img.GetFrameDelay(3).numerator, 0u);662EXPECT_EQ(img.GetFrameDelay(3).denominator, 65535u);663664// Save to buffer and load back to verify these values are preserved665auto buffer = img.SaveToBuffer("test_delays.png");666ASSERT_TRUE(buffer.has_value());667668AnimatedImage loaded;669ASSERT_TRUE(loaded.LoadFromBuffer("test_delays.png", *buffer));670671// Verify delays are preserved672EXPECT_EQ(loaded.GetFrameDelay(0).numerator, 0u);673EXPECT_EQ(loaded.GetFrameDelay(0).denominator, 1u);674EXPECT_EQ(loaded.GetFrameDelay(1).numerator, 65535u);675EXPECT_EQ(loaded.GetFrameDelay(1).denominator, 1u);676EXPECT_EQ(loaded.GetFrameDelay(2).numerator, 1u);677EXPECT_EQ(loaded.GetFrameDelay(2).denominator, 65535u);678EXPECT_EQ(loaded.GetFrameDelay(3).numerator, 0u);679EXPECT_EQ(loaded.GetFrameDelay(3).denominator, 65535u);680}681682TEST_F(AnimatedImageTest, ResizeBetweenSingleAndMultipleFrames)683{684// Start with a single frame685AnimatedImage img(16, 16, 1, {1, 10});686EXPECT_EQ(img.GetFrames(), 1u);687688// Fill with a pattern689for (u32 i = 0; i < img.GetFrameSize(); i++)690{691img.GetPixels(0)[i] = 0xFF000000u | i;692}693694// Resize to multiple frames695img.Resize(16, 16, 3, {2, 20}, true);696EXPECT_EQ(img.GetFrames(), 3u);697698// Verify original frame is preserved699for (u32 i = 0; i < img.GetFrameSize(); i++)700{701EXPECT_EQ(img.GetPixels(0)[i], 0xFF000000u | i);702}703704// Fill second frame with different pattern705for (u32 i = 0; i < img.GetFrameSize(); i++)706{707img.GetPixels(1)[i] = 0xFF000000u | (i * 2);708}709710// Resize back to single frame711img.Resize(16, 16, 1, {3, 30}, true);712EXPECT_EQ(img.GetFrames(), 1u);713714// Verify first frame is still preserved715for (u32 i = 0; i < img.GetFrameSize(); i++)716{717EXPECT_EQ(img.GetPixels(0)[i], 0xFF000000u | i);718}719}720721722