Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/util-tests/animated_image_tests.cpp
4802 views
1
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "util/animated_image.h"
5
6
#include "common/error.h"
7
8
#include <gtest/gtest.h>
9
#include <vector>
10
11
namespace {
12
// Test fixture for AnimatedImage tests
13
class AnimatedImageTest : public ::testing::Test
14
{
15
protected:
16
// Helper method to create test images with a pattern
17
AnimatedImage CreateTestImage(u32 width, u32 height, u32 frames = 1)
18
{
19
AnimatedImage img(width, height, frames, {1, 10});
20
for (u32 f = 0; f < frames; f++)
21
{
22
for (u32 y = 0; y < height; y++)
23
{
24
for (u32 x = 0; x < width; x++)
25
{
26
img.GetPixels(f)[y * width + x] = (x + y + f) | 0xFF000000; // Make pixel opaque
27
}
28
}
29
}
30
return img;
31
}
32
};
33
34
} // namespace
35
36
// Constructor Tests
37
TEST_F(AnimatedImageTest, DefaultConstructor)
38
{
39
AnimatedImage img;
40
EXPECT_FALSE(img.IsValid());
41
EXPECT_EQ(img.GetWidth(), 0u);
42
EXPECT_EQ(img.GetHeight(), 0u);
43
EXPECT_EQ(img.GetFrames(), 0u);
44
}
45
46
TEST_F(AnimatedImageTest, ParameterizedConstructor)
47
{
48
const u32 width = 100;
49
const u32 height = 80;
50
const u32 frames = 5;
51
AnimatedImage::FrameDelay delay{1, 10};
52
53
AnimatedImage img(width, height, frames, delay);
54
EXPECT_TRUE(img.IsValid());
55
EXPECT_EQ(img.GetWidth(), width);
56
EXPECT_EQ(img.GetHeight(), height);
57
EXPECT_EQ(img.GetFrames(), frames);
58
EXPECT_EQ(img.GetFrameSize(), width * height);
59
60
// Check all frames have the same delay
61
for (u32 i = 0; i < frames; i++)
62
{
63
EXPECT_EQ(img.GetFrameDelay(i).numerator, delay.numerator);
64
EXPECT_EQ(img.GetFrameDelay(i).denominator, delay.denominator);
65
}
66
}
67
68
TEST_F(AnimatedImageTest, CopyConstructor)
69
{
70
auto original = CreateTestImage(50, 40, 2);
71
AnimatedImage copy(original);
72
73
EXPECT_EQ(copy.GetWidth(), original.GetWidth());
74
EXPECT_EQ(copy.GetHeight(), original.GetHeight());
75
EXPECT_EQ(copy.GetFrames(), original.GetFrames());
76
77
// Check pixels match
78
for (u32 f = 0; f < original.GetFrames(); f++)
79
{
80
for (u32 i = 0; i < original.GetFrameSize(); i++)
81
{
82
EXPECT_EQ(copy.GetPixels(f)[i], original.GetPixels(f)[i]);
83
}
84
}
85
}
86
87
TEST_F(AnimatedImageTest, MoveConstructor)
88
{
89
auto original = CreateTestImage(50, 40, 2);
90
const u32 width = original.GetWidth();
91
const u32 height = original.GetHeight();
92
const u32 frames = original.GetFrames();
93
94
// Store original pixel data to compare later
95
std::vector<std::vector<u32>> pixel_copies;
96
for (u32 f = 0; f < frames; f++)
97
{
98
pixel_copies.push_back(std::vector<u32>(original.GetPixels(f), original.GetPixels(f) + original.GetFrameSize()));
99
}
100
101
AnimatedImage moved(std::move(original));
102
103
EXPECT_FALSE(original.IsValid()); // Original should be invalid after move
104
EXPECT_EQ(moved.GetWidth(), width);
105
EXPECT_EQ(moved.GetHeight(), height);
106
EXPECT_EQ(moved.GetFrames(), frames);
107
108
// Check pixels were moved correctly
109
for (u32 f = 0; f < frames; f++)
110
{
111
for (u32 i = 0; i < width * height; i++)
112
{
113
EXPECT_EQ(moved.GetPixels(f)[i], pixel_copies[f][i]);
114
}
115
}
116
}
117
118
// Assignment Operator Tests
119
TEST_F(AnimatedImageTest, CopyAssignment)
120
{
121
auto original = CreateTestImage(60, 50, 3);
122
AnimatedImage copy;
123
copy = original;
124
125
EXPECT_EQ(copy.GetWidth(), original.GetWidth());
126
EXPECT_EQ(copy.GetHeight(), original.GetHeight());
127
EXPECT_EQ(copy.GetFrames(), original.GetFrames());
128
129
// Check pixels match
130
for (u32 f = 0; f < original.GetFrames(); f++)
131
{
132
for (u32 i = 0; i < original.GetFrameSize(); i++)
133
{
134
EXPECT_EQ(copy.GetPixels(f)[i], original.GetPixels(f)[i]);
135
}
136
}
137
}
138
139
TEST_F(AnimatedImageTest, MoveAssignment)
140
{
141
auto original = CreateTestImage(60, 50, 3);
142
const u32 width = original.GetWidth();
143
const u32 height = original.GetHeight();
144
const u32 frames = original.GetFrames();
145
146
std::vector<std::vector<u32>> pixel_copies;
147
for (u32 f = 0; f < frames; f++)
148
{
149
pixel_copies.push_back(std::vector<u32>(original.GetPixels(f), original.GetPixels(f) + original.GetFrameSize()));
150
}
151
152
AnimatedImage moved;
153
moved = std::move(original);
154
155
EXPECT_FALSE(original.IsValid()); // Original should be invalid after move
156
EXPECT_EQ(moved.GetWidth(), width);
157
EXPECT_EQ(moved.GetHeight(), height);
158
EXPECT_EQ(moved.GetFrames(), frames);
159
160
// Check pixels were moved correctly
161
for (u32 f = 0; f < frames; f++)
162
{
163
for (u32 i = 0; i < width * height; i++)
164
{
165
EXPECT_EQ(moved.GetPixels(f)[i], pixel_copies[f][i]);
166
}
167
}
168
}
169
170
// Pixel Access Tests
171
TEST_F(AnimatedImageTest, PixelAccess)
172
{
173
const u32 width = 10;
174
const u32 height = 8;
175
AnimatedImage img(width, height, 1, {1, 10});
176
177
// Test direct pixel access
178
for (u32 y = 0; y < height; y++)
179
{
180
for (u32 x = 0; x < width; x++)
181
{
182
img.GetPixels(0)[y * width + x] = 0xFF000000u | (x + y * width);
183
}
184
}
185
186
// Verify pixels
187
for (u32 y = 0; y < height; y++)
188
{
189
for (u32 x = 0; x < width; x++)
190
{
191
EXPECT_EQ(img.GetPixels(0)[y * width + x], 0xFF000000u | (x + y * width));
192
EXPECT_EQ(img.GetRowPixels(0, y)[x], 0xFF000000u | (x + y * width));
193
}
194
}
195
196
// Test GetPixelsSpan
197
auto span = img.GetPixelsSpan(0);
198
for (u32 i = 0; i < width * height; i++)
199
{
200
EXPECT_EQ(span[i], 0xFF000000u | i);
201
}
202
}
203
204
TEST_F(AnimatedImageTest, SetPixels)
205
{
206
const u32 width = 10;
207
const u32 height = 8;
208
AnimatedImage img(width, height, 1, {1, 10});
209
210
// Create source pixels
211
std::vector<u32> src_pixels(width * height);
212
for (u32 i = 0; i < width * height; i++)
213
{
214
src_pixels[i] = 0xFF000000 | i;
215
}
216
217
// Copy with SetPixels
218
img.SetPixels(0, src_pixels.data(), width * sizeof(u32));
219
220
// Verify pixels
221
for (u32 i = 0; i < width * height; i++)
222
{
223
EXPECT_EQ(img.GetPixels(0)[i], 0xFF000000u | i);
224
}
225
}
226
227
TEST_F(AnimatedImageTest, SetDelay)
228
{
229
AnimatedImage img(10, 10, 2, {1, 10});
230
231
AnimatedImage::FrameDelay delay{2, 20};
232
img.SetDelay(1, delay);
233
234
EXPECT_EQ(img.GetFrameDelay(1).numerator, 2);
235
EXPECT_EQ(img.GetFrameDelay(1).denominator, 20);
236
237
// First frame should be unchanged
238
EXPECT_EQ(img.GetFrameDelay(0).numerator, 1);
239
EXPECT_EQ(img.GetFrameDelay(0).denominator, 10);
240
}
241
242
// Image Manipulation Tests
243
TEST_F(AnimatedImageTest, Resize)
244
{
245
AnimatedImage img = CreateTestImage(10, 8, 2);
246
AnimatedImage img2 = CreateTestImage(10, 8, 2);
247
248
// Resize to larger dimensions, preserving content
249
img.Resize(20, 16, 3, {1, 10}, true);
250
251
EXPECT_EQ(img.GetWidth(), 20u);
252
EXPECT_EQ(img.GetHeight(), 16u);
253
EXPECT_EQ(img.GetFrames(), 3u);
254
255
// Check that original content is preserved
256
for (u32 f = 0; f < 2; f++)
257
{
258
for (u32 y = 0; y < 8; y++)
259
{
260
for (u32 x = 0; x < 10; x++)
261
{
262
EXPECT_EQ(img.GetRowPixels(f, y)[x] & 0xFFu, (x + y + f) & 0xFFu);
263
}
264
}
265
}
266
267
// Check that new areas are zeroed
268
for (u32 f = 0; f < 2; f++)
269
{
270
for (u32 y = 0; y < 8; y++)
271
{
272
for (u32 x = 10; x < 20; x++)
273
{
274
EXPECT_EQ(img.GetRowPixels(f, y)[x], 0u);
275
}
276
}
277
for (u32 y = 8; y < 16; y++)
278
{
279
for (u32 x = 0; x < 20; x++)
280
{
281
EXPECT_EQ(img.GetRowPixels(f, y)[x], 0u);
282
}
283
}
284
}
285
286
// Check third frame has the specified default delay
287
EXPECT_EQ(img.GetFrameDelay(2).numerator, 1u);
288
EXPECT_EQ(img.GetFrameDelay(2).denominator, 10u);
289
}
290
291
TEST_F(AnimatedImageTest, Clear)
292
{
293
AnimatedImage img = CreateTestImage(10, 8, 2);
294
295
img.Clear();
296
297
// Dimensions should remain the same
298
EXPECT_EQ(img.GetWidth(), 10u);
299
EXPECT_EQ(img.GetHeight(), 8u);
300
EXPECT_EQ(img.GetFrames(), 2u);
301
302
// All pixels should be zeroed
303
for (u32 f = 0; f < img.GetFrames(); f++)
304
{
305
for (u32 i = 0; i < img.GetFrameSize(); i++)
306
{
307
EXPECT_EQ(img.GetPixels(f)[i], 0u);
308
}
309
}
310
}
311
312
TEST_F(AnimatedImageTest, Invalidate)
313
{
314
AnimatedImage img = CreateTestImage(10, 8, 2);
315
316
img.Invalidate();
317
318
EXPECT_FALSE(img.IsValid());
319
EXPECT_EQ(img.GetWidth(), 0u);
320
EXPECT_EQ(img.GetHeight(), 0u);
321
EXPECT_EQ(img.GetFrames(), 0u);
322
}
323
324
TEST_F(AnimatedImageTest, TakePixels)
325
{
326
AnimatedImage img = CreateTestImage(10, 8, 2);
327
const u32 expected_size = 10 * 8 * 2;
328
329
auto pixels = img.TakePixels();
330
331
// Image should be invalidated
332
EXPECT_FALSE(img.IsValid());
333
EXPECT_EQ(img.GetWidth(), 0u);
334
EXPECT_EQ(img.GetHeight(), 0u);
335
EXPECT_EQ(img.GetFrames(), 0u);
336
337
// Pixel storage should have the expected size
338
EXPECT_EQ(pixels.size(), expected_size);
339
}
340
341
// File Operations Tests
342
TEST_F(AnimatedImageTest, LoadSavePNG)
343
{
344
// Create test image
345
AnimatedImage original = CreateTestImage(20, 16, 1);
346
347
// Set specific pixel patterns for verification
348
original.GetPixels(0)[0] = 0xFF0000FFu; // Blue
349
original.GetPixels(0)[1] = 0xFF00FF00u; // Green
350
original.GetPixels(0)[2] = 0xFFFF0000u; // Red
351
352
// Save to file
353
auto buffer = original.SaveToBuffer("test_image.png");
354
ASSERT_TRUE(buffer.has_value());
355
356
// Load the image back
357
AnimatedImage loaded;
358
ASSERT_TRUE(loaded.LoadFromBuffer("test_image.png", buffer.value()));
359
360
// Compare dimensions
361
EXPECT_EQ(loaded.GetWidth(), original.GetWidth());
362
EXPECT_EQ(loaded.GetHeight(), original.GetHeight());
363
EXPECT_EQ(loaded.GetFrames(), 1u);
364
365
// Compare specific pixel colors (ignoring alpha variations)
366
EXPECT_EQ(loaded.GetPixels(0)[0] & 0xFFFFFFu, 0x0000FFu); // Blue
367
EXPECT_EQ(loaded.GetPixels(0)[1] & 0xFFFFFFu, 0x00FF00u); // Green
368
EXPECT_EQ(loaded.GetPixels(0)[2] & 0xFFFFFFu, 0xFF0000u); // Red
369
}
370
371
TEST_F(AnimatedImageTest, LoadSaveMultiFramePNG)
372
{
373
// Create multi-frame test image
374
AnimatedImage original = CreateTestImage(20, 16, 2);
375
376
// Set different delays for frames
377
original.SetDelay(0, {1, 10});
378
original.SetDelay(1, {2, 20});
379
380
// Save to file
381
auto buffer = original.SaveToBuffer("test_anim.png");
382
ASSERT_TRUE(buffer.has_value());
383
384
// Load back
385
AnimatedImage loaded;
386
ASSERT_TRUE(loaded.LoadFromBuffer("test_anim.png", buffer.value()));
387
388
// Compare dimensions and frame count
389
EXPECT_EQ(loaded.GetWidth(), original.GetWidth());
390
EXPECT_EQ(loaded.GetHeight(), original.GetHeight());
391
EXPECT_EQ(loaded.GetFrames(), original.GetFrames());
392
393
// Compare frame delays
394
EXPECT_EQ(loaded.GetFrameDelay(0).numerator, 1u);
395
EXPECT_EQ(loaded.GetFrameDelay(0).denominator, 10u);
396
EXPECT_EQ(loaded.GetFrameDelay(1).numerator, 2u);
397
EXPECT_EQ(loaded.GetFrameDelay(1).denominator, 20u);
398
}
399
400
TEST_F(AnimatedImageTest, SaveLoadBuffer)
401
{
402
AnimatedImage original = CreateTestImage(20, 16, 1);
403
404
// Save to buffer
405
auto buffer = original.SaveToBuffer("test.png");
406
ASSERT_TRUE(buffer.has_value());
407
EXPECT_GT(buffer->size(), 0u);
408
409
// Load from buffer
410
AnimatedImage loaded;
411
ASSERT_TRUE(loaded.LoadFromBuffer("test.png", *buffer));
412
413
// Compare dimensions
414
EXPECT_EQ(loaded.GetWidth(), original.GetWidth());
415
EXPECT_EQ(loaded.GetHeight(), original.GetHeight());
416
417
// Compare some pixels (ignoring alpha)
418
for (u32 i = 0; i < std::min(10u, original.GetFrameSize()); i++)
419
{
420
EXPECT_EQ(loaded.GetPixels(0)[i] & 0xFFFFFF, original.GetPixels(0)[i] & 0xFFFFFF);
421
}
422
}
423
424
TEST_F(AnimatedImageTest, ErrorHandling)
425
{
426
AnimatedImage img;
427
Error err;
428
429
// Try loading non-existent file
430
EXPECT_FALSE(img.LoadFromFile("non_existent_file.png", &err));
431
EXPECT_TRUE(err.IsValid());
432
433
// Try loading file with invalid extension
434
EXPECT_FALSE(img.LoadFromFile("test.invalid", &err));
435
EXPECT_TRUE(err.IsValid());
436
}
437
438
TEST_F(AnimatedImageTest, CalculatePitch)
439
{
440
EXPECT_EQ(AnimatedImage::CalculatePitch(10, 5), 10 * sizeof(u32));
441
EXPECT_EQ(AnimatedImage::CalculatePitch(100, 200), 100 * sizeof(u32));
442
}
443
444
// Multiple frame handling and frame delay tests
445
TEST_F(AnimatedImageTest, MultipleFrameDelays)
446
{
447
const u32 width = 32;
448
const u32 height = 24;
449
const u32 frames = 5;
450
AnimatedImage img(width, height, frames, {1, 10});
451
452
// Set different delays for each frame
453
img.SetDelay(0, {1, 10});
454
img.SetDelay(1, {2, 20});
455
img.SetDelay(2, {3, 30});
456
img.SetDelay(3, {4, 40});
457
img.SetDelay(4, {5, 50});
458
459
// Verify each frame has the correct delay
460
EXPECT_EQ(img.GetFrameDelay(0).numerator, 1u);
461
EXPECT_EQ(img.GetFrameDelay(0).denominator, 10u);
462
EXPECT_EQ(img.GetFrameDelay(1).numerator, 2u);
463
EXPECT_EQ(img.GetFrameDelay(1).denominator, 20u);
464
EXPECT_EQ(img.GetFrameDelay(2).numerator, 3u);
465
EXPECT_EQ(img.GetFrameDelay(2).denominator, 30u);
466
EXPECT_EQ(img.GetFrameDelay(3).numerator, 4u);
467
EXPECT_EQ(img.GetFrameDelay(3).denominator, 40u);
468
EXPECT_EQ(img.GetFrameDelay(4).numerator, 5u);
469
EXPECT_EQ(img.GetFrameDelay(4).denominator, 50u);
470
}
471
472
TEST_F(AnimatedImageTest, PreserveFrameDelaysOnResize)
473
{
474
const u32 width = 16;
475
const u32 height = 16;
476
const u32 frames = 3;
477
478
AnimatedImage img(width, height, frames, {1, 10});
479
480
// Set unique delays for each frame
481
img.SetDelay(0, {5, 25});
482
img.SetDelay(1, {10, 50});
483
img.SetDelay(2, {15, 75});
484
485
// Resize with fewer frames - should preserve existing frame delays
486
img.Resize(32, 32, 2, {20, 100}, true);
487
488
EXPECT_EQ(img.GetWidth(), 32u);
489
EXPECT_EQ(img.GetHeight(), 32u);
490
EXPECT_EQ(img.GetFrames(), 2u);
491
492
// Original frame delays should be preserved
493
EXPECT_EQ(img.GetFrameDelay(0).numerator, 5u);
494
EXPECT_EQ(img.GetFrameDelay(0).denominator, 25u);
495
EXPECT_EQ(img.GetFrameDelay(1).numerator, 10u);
496
EXPECT_EQ(img.GetFrameDelay(1).denominator, 50u);
497
498
// Resize with more frames - new frames should use the default delay
499
img.Resize(32, 32, 4, {20, 100}, true);
500
501
EXPECT_EQ(img.GetFrames(), 4u);
502
503
// Original frame delays should still be preserved
504
EXPECT_EQ(img.GetFrameDelay(0).numerator, 5u);
505
EXPECT_EQ(img.GetFrameDelay(0).denominator, 25u);
506
EXPECT_EQ(img.GetFrameDelay(1).numerator, 10u);
507
EXPECT_EQ(img.GetFrameDelay(1).denominator, 50u);
508
509
// New frames should have the default delay
510
EXPECT_EQ(img.GetFrameDelay(2).numerator, 20u);
511
EXPECT_EQ(img.GetFrameDelay(2).denominator, 100u);
512
EXPECT_EQ(img.GetFrameDelay(3).numerator, 20u);
513
EXPECT_EQ(img.GetFrameDelay(3).denominator, 100u);
514
}
515
516
TEST_F(AnimatedImageTest, IndividualFrameModification)
517
{
518
const u32 width = 8;
519
const u32 height = 8;
520
const u32 frames = 3;
521
522
AnimatedImage img(width, height, frames, {1, 10});
523
524
// Set distinct patterns for each frame
525
for (u32 f = 0; f < frames; f++)
526
{
527
for (u32 y = 0; y < height; y++)
528
{
529
for (u32 x = 0; x < width; x++)
530
{
531
// Frame 0: all red, Frame 1: all green, Frame 2: all blue
532
u32 color = (f == 0) ? 0xFF0000FFu : ((f == 1) ? 0xFF00FF00u : 0xFFFF0000u);
533
img.GetPixels(f)[y * width + x] = color;
534
}
535
}
536
}
537
538
// Verify each frame has the correct pattern
539
for (u32 y = 0; y < height; y++)
540
{
541
for (u32 x = 0; x < width; x++)
542
{
543
EXPECT_EQ(img.GetPixels(0)[y * width + x], 0xFF0000FFu); // Red
544
EXPECT_EQ(img.GetPixels(1)[y * width + x], 0xFF00FF00u); // Green
545
EXPECT_EQ(img.GetPixels(2)[y * width + x], 0xFFFF0000u); // Blue
546
}
547
}
548
549
// Modify only the middle frame
550
for (u32 y = 0; y < height; y++)
551
{
552
for (u32 x = 0; x < width; x++)
553
{
554
img.GetPixels(1)[y * width + x] = 0xFFFFFFFFu; // White
555
}
556
}
557
558
// Verify only the middle frame was changed
559
for (u32 y = 0; y < height; y++)
560
{
561
for (u32 x = 0; x < width; x++)
562
{
563
EXPECT_EQ(img.GetPixels(0)[y * width + x], 0xFF0000FFu); // Still red
564
EXPECT_EQ(img.GetPixels(1)[y * width + x], 0xFFFFFFFFu); // Now white
565
EXPECT_EQ(img.GetPixels(2)[y * width + x], 0xFFFF0000u); // Still blue
566
}
567
}
568
}
569
570
TEST_F(AnimatedImageTest, MultiFrameAnimationRoundTrip)
571
{
572
const u32 width = 24;
573
const u32 height = 24;
574
const u32 frames = 4;
575
576
// Create a test animation with 4 frames, each with different content and timing
577
AnimatedImage original(width, height, frames, {1, 10});
578
579
// Frame 0: Red with delay 1/10
580
std::memset(original.GetPixels(0), 0, width * height * sizeof(u32));
581
for (u32 i = 0; i < width * height; i++)
582
{
583
original.GetPixels(0)[i] = 0xFF0000FFu; // Red
584
}
585
original.SetDelay(0, {1, 10});
586
587
// Frame 1: Green with delay 2/20
588
std::memset(original.GetPixels(1), 0, width * height * sizeof(u32));
589
for (u32 i = 0; i < width * height; i++)
590
{
591
original.GetPixels(1)[i] = 0xFF00FF00u; // Green
592
}
593
original.SetDelay(1, {2, 20});
594
595
// Frame 2: Blue with delay 3/30
596
std::memset(original.GetPixels(2), 0, width * height * sizeof(u32));
597
for (u32 i = 0; i < width * height; i++)
598
{
599
original.GetPixels(2)[i] = 0xFFFF0000u; // Blue
600
}
601
original.SetDelay(2, {3, 30});
602
603
// Frame 3: Yellow with delay 4/40
604
std::memset(original.GetPixels(3), 0, width * height * sizeof(u32));
605
for (u32 i = 0; i < width * height; i++)
606
{
607
original.GetPixels(3)[i] = 0xFF00FFFFu; // Yellow
608
}
609
original.SetDelay(3, {4, 40});
610
611
// Save to buffer
612
auto buffer = original.SaveToBuffer("test_animation.png");
613
ASSERT_TRUE(buffer.has_value());
614
615
// Load back from buffer
616
AnimatedImage loaded;
617
ASSERT_TRUE(loaded.LoadFromBuffer("test_animation.png", *buffer));
618
619
// Verify dimensions and frame count
620
EXPECT_EQ(loaded.GetWidth(), width);
621
EXPECT_EQ(loaded.GetHeight(), height);
622
EXPECT_EQ(loaded.GetFrames(), frames);
623
624
// Verify frame delays
625
EXPECT_EQ(loaded.GetFrameDelay(0).numerator, 1u);
626
EXPECT_EQ(loaded.GetFrameDelay(0).denominator, 10u);
627
EXPECT_EQ(loaded.GetFrameDelay(1).numerator, 2u);
628
EXPECT_EQ(loaded.GetFrameDelay(1).denominator, 20u);
629
EXPECT_EQ(loaded.GetFrameDelay(2).numerator, 3u);
630
EXPECT_EQ(loaded.GetFrameDelay(2).denominator, 30u);
631
EXPECT_EQ(loaded.GetFrameDelay(3).numerator, 4u);
632
EXPECT_EQ(loaded.GetFrameDelay(3).denominator, 40u);
633
634
// Verify frame contents (sampling first pixel of each frame)
635
EXPECT_EQ(loaded.GetPixels(0)[0] & 0xFFFFFFu, 0x0000FFu); // Red
636
EXPECT_EQ(loaded.GetPixels(1)[0] & 0xFFFFFFu, 0x00FF00u); // Green
637
EXPECT_EQ(loaded.GetPixels(2)[0] & 0xFFFFFFu, 0xFF0000u); // Blue
638
EXPECT_EQ(loaded.GetPixels(3)[0] & 0xFFFFFFu, 0x00FFFFu); // Yellow
639
}
640
641
TEST_F(AnimatedImageTest, MaximumAndZeroDelays)
642
{
643
const u32 width = 16;
644
const u32 height = 16;
645
const u32 frames = 4;
646
647
AnimatedImage img(width, height, frames, {1, 10});
648
649
// Set extreme delay values
650
img.SetDelay(0, {0, 1}); // Zero numerator (minimum)
651
img.SetDelay(1, {65535, 1}); // Maximum numerator (u16 max)
652
img.SetDelay(2, {1, 65535}); // Maximum denominator (u16 max)
653
img.SetDelay(3, {0, 65535}); // Both extreme
654
655
// Verify delay values were set correctly
656
EXPECT_EQ(img.GetFrameDelay(0).numerator, 0u);
657
EXPECT_EQ(img.GetFrameDelay(0).denominator, 1u);
658
EXPECT_EQ(img.GetFrameDelay(1).numerator, 65535u);
659
EXPECT_EQ(img.GetFrameDelay(1).denominator, 1u);
660
EXPECT_EQ(img.GetFrameDelay(2).numerator, 1u);
661
EXPECT_EQ(img.GetFrameDelay(2).denominator, 65535u);
662
EXPECT_EQ(img.GetFrameDelay(3).numerator, 0u);
663
EXPECT_EQ(img.GetFrameDelay(3).denominator, 65535u);
664
665
// Save to buffer and load back to verify these values are preserved
666
auto buffer = img.SaveToBuffer("test_delays.png");
667
ASSERT_TRUE(buffer.has_value());
668
669
AnimatedImage loaded;
670
ASSERT_TRUE(loaded.LoadFromBuffer("test_delays.png", *buffer));
671
672
// Verify delays are preserved
673
EXPECT_EQ(loaded.GetFrameDelay(0).numerator, 0u);
674
EXPECT_EQ(loaded.GetFrameDelay(0).denominator, 1u);
675
EXPECT_EQ(loaded.GetFrameDelay(1).numerator, 65535u);
676
EXPECT_EQ(loaded.GetFrameDelay(1).denominator, 1u);
677
EXPECT_EQ(loaded.GetFrameDelay(2).numerator, 1u);
678
EXPECT_EQ(loaded.GetFrameDelay(2).denominator, 65535u);
679
EXPECT_EQ(loaded.GetFrameDelay(3).numerator, 0u);
680
EXPECT_EQ(loaded.GetFrameDelay(3).denominator, 65535u);
681
}
682
683
TEST_F(AnimatedImageTest, ResizeBetweenSingleAndMultipleFrames)
684
{
685
// Start with a single frame
686
AnimatedImage img(16, 16, 1, {1, 10});
687
EXPECT_EQ(img.GetFrames(), 1u);
688
689
// Fill with a pattern
690
for (u32 i = 0; i < img.GetFrameSize(); i++)
691
{
692
img.GetPixels(0)[i] = 0xFF000000u | i;
693
}
694
695
// Resize to multiple frames
696
img.Resize(16, 16, 3, {2, 20}, true);
697
EXPECT_EQ(img.GetFrames(), 3u);
698
699
// Verify original frame is preserved
700
for (u32 i = 0; i < img.GetFrameSize(); i++)
701
{
702
EXPECT_EQ(img.GetPixels(0)[i], 0xFF000000u | i);
703
}
704
705
// Fill second frame with different pattern
706
for (u32 i = 0; i < img.GetFrameSize(); i++)
707
{
708
img.GetPixels(1)[i] = 0xFF000000u | (i * 2);
709
}
710
711
// Resize back to single frame
712
img.Resize(16, 16, 1, {3, 30}, true);
713
EXPECT_EQ(img.GetFrames(), 1u);
714
715
// Verify first frame is still preserved
716
for (u32 i = 0; i < img.GetFrameSize(); i++)
717
{
718
EXPECT_EQ(img.GetPixels(0)[i], 0xFF000000u | i);
719
}
720
}
721
722