pebble/tests/fw/graphics/test_gbitmap_sequence.c
2025-01-27 11:38:16 -08:00

345 lines
14 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "applib/graphics/gbitmap_sequence.h"
#include "applib/graphics/graphics.h"
#include "clar.h"
#include "util.h"
#include <util/size.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
// Test files are Creative Commons 0 (ie. Public Domain) from
// http://opengameart.org/content/game-character-blue-flappy-bird-sprite-sheets
// http://opengameart.org/content/pixel-puncher-sprites
// http://opengameart.org/content/spike-man-monster
// http://opengameart.org/content/platformer-baddies
// http://opengameart.org/content/greyscale-special-effects-%E2%80%94-various-dimensions-and-flavours
// http://opengameart.org/content/bouncing-ball-guy
// http://opengameart.org/content/medals-3
// http://opengameart.org/content/open-pixel-platformer-tiles-sprites
#if SCREEN_COLOR_DEPTH_BITS == 8
#include "applib/graphics/8_bit/framebuffer.c"
#elif SCREEN_COLOR_DEPTH_BITS == 1
#include "applib/graphics/1_bit/framebuffer.c"
#endif
// Fakes
////////////////////////////////////
#include "fake_resource_syscalls.h"
#include "fake_app_timer.h"
// Stubs
////////////////////////////////////
#include "stubs_applib_resource.h"
#include "stubs_app_state.h"
#include "stubs_logging.h"
#include "stubs_heap.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_print.h"
#include "stubs_queue.h"
#include "stubs_resources.h"
#include "stubs_serial.h"
#include "stubs_ui_window.h"
#define GET_PBI_NAME(x) prv_get_image_name(__func__, x, "pbi")
#define GET_APNG_NAME prv_get_image_name(__func__, 0, "apng")
// Used to work around __func__ not being a string literal (necessary for macro concatenation)
static const char *prv_get_image_name(const char* func_name, int index, const char *extension) {
char *filename = malloc(PATH_STRING_LENGTH);
if (index) {
snprintf(filename, PATH_STRING_LENGTH, "%s_%u.%s", func_name, index, extension);
} else {
snprintf(filename, PATH_STRING_LENGTH, "%s.%s", func_name, extension);
}
return filename;
}
// Tests
////////////////////////////////////
// Reference PNGs reside in "tests/test_images/"
// and are created at build time, with the files copied to TEST_IMAGES_PATH
// and a separate PBI generated by bitmapgen.py from the PNG copied to TEST_IMAGES_PATH
// Tests APNG file with 2-bit color, 6 frames and loop = infinite
// Tests start, stop, reset and loop
// Result:
// - gbitmaps matches platform decoded APNG
void test_gbitmap_sequence__color_2bit_bouncing_ball(void) {
#if PLATFROM_SPALDING
uint32_t resource_id = sys_resource_load_file_as_resource(TEST_IMAGES_PATH, GET_APNG_NAME);
cl_assert(resource_id != UINT32_MAX);
GBitmapSequence *bitmap_sequence = gbitmap_sequence_create_with_resource(resource_id);
cl_assert(bitmap_sequence);
GBitmap *bitmap = gbitmap_create_blank(gbitmap_sequence_get_bitmap_size(bitmap_sequence),
GBitmapFormat8Bit);
cl_assert(bitmap);
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(1)));
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(2)));
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(3)));
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(4)));
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(5)));
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(6)));
// Test loop around
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(1)));
#endif
}
// Tests APNG file with 8-bit color, 88 frames of delay 100ms each and loop = 2
// Tests gbitmap_sequence_update_bitmap_by_elapsed
// Result:
// - gbitmaps matches platform decoded APNG
void test_gbitmap_sequence__color_8bit_fight(void) {
#if PLATFORM_SPALDING
bool status = false;
uint32_t resource_id = sys_resource_load_file_as_resource(TEST_IMAGES_PATH, GET_APNG_NAME);
cl_assert(resource_id != UINT32_MAX);
GBitmapSequence *bitmap_sequence = gbitmap_sequence_create_with_resource(resource_id);
cl_assert(bitmap_sequence);
GBitmap *bitmap = gbitmap_create_blank(gbitmap_sequence_get_bitmap_size(bitmap_sequence),
GBitmapFormat8Bit);
cl_assert(bitmap);
// first frame always shows at the beginning (0 ms)
status = gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 0);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(1)));
// each frame has delay 100ms, so (33 - 1) * 100 ms shows 33rd frame
status = gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, (33 - 1) * 100);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(33)));
status = gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, (60 - 1) * 100);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(60)));
// Since we have loop count 2 and 88 frames, see if we can go to elapsed at 2nd loop correctly
status = gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap,
88 * 100 + (49 - 1) * 100);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(49)));
// Since loop only equals 2, make sure that huge elapsed time leaves us at final frame
status = gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 12345 * 100);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(88)));
#endif
}
// Tests APNG file with 8-bit color, 5 frames and loop = 1
// Tests gbitmap_sequence_update_bitmap_by_elapsed with 0 delay frames for 1, 4 and 5
// and 2 & 3 have delay 1000ms
// Result:
// - gbitmaps matches platform decoded APNG
void test_gbitmap_sequence__color_8bit_coin(void) {
#if PLATFORM_SPALDING
uint32_t resource_id = sys_resource_load_file_as_resource(TEST_IMAGES_PATH, GET_APNG_NAME);
cl_assert(resource_id != UINT32_MAX);
GBitmapSequence *bitmap_sequence = gbitmap_sequence_create_with_resource(resource_id);
cl_assert(bitmap_sequence);
GBitmap *bitmap = gbitmap_create_blank(gbitmap_sequence_get_bitmap_size(bitmap_sequence),
GBitmapFormat8Bit);
cl_assert(bitmap);
// Since frame 1 has 0 delay, it renders then immediately we render frame 2
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 0);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(2)));
// Frame 2 has delay 1000 ms, so at time 1000 ms, we expect frame 3
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 1 * 1000);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(3)));
// Frame 3 has delay 1000 ms, frame 4 and 5 have zero delay, so at time 2000 ms == frame 5
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 2 * 1000);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(5)));
// Since loop only equals 1, make sure that huge elapsed time leaves us at final frame
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 12345 * 1000);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(5)));
#endif
}
// Tests APNG file with 8-bit color, 5 frames and loop = 1
// Tests gbitmap_sequence_update_bitmap_by_elapsed with 0 delay frames for 1, 4 and 5
// and 2 & 3 have delay 1000ms
// Result:
// - gbitmaps matches platform decoded APNG
void test_gbitmap_sequence__color_8bit_coin_round(void) {
#if PLATFORM_SPALDING
uint32_t resource_id = sys_resource_load_file_as_resource(
TEST_IMAGES_PATH, "test_gbitmap_sequence__color_8bit_coin.apng");
cl_assert(resource_id != UINT32_MAX);
GBitmapSequence *bitmap_sequence = gbitmap_sequence_create_with_resource(resource_id);
cl_assert(bitmap_sequence);
GBitmap *bitmap = gbitmap_create_blank(GSize(180, 180), GBitmapFormat8BitCircular);
cl_assert(bitmap);
// Since frame 1 has 0 delay, it renders then immediately we render frame 2
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 0);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(2)));
// Frame 2 has delay 1000 ms, so at time 1000 ms, we expect frame 3
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 1 * 1000);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(3)));
// Frame 3 has delay 1000 ms, frame 4 and 5 have zero delay, so at time 2000 ms == frame 5
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 2 * 1000);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(5)));
// Since loop only equals 1, make sure that huge elapsed time leaves us at final frame
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 12345 * 1000);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(5)));
#endif
}
// Tests APNG file with 8-bit color, 5 frames and loop infinite, size 64x64
// Tests gbitmap_sequence for bitmap bounds offset and DISPOSE_OP_BACKGROUND
// Result:
// - gbitmaps matches platform decoded APNG
void test_gbitmap_sequence__color_8bit_bounds(void) {
#if PLATFORM_SPALDING
uint32_t resource_id = sys_resource_load_file_as_resource(TEST_IMAGES_PATH, GET_APNG_NAME);
cl_assert(resource_id != UINT32_MAX);
GRect orig_bounds = GRect(0, 0, 180, 180);
int xshift = 71;
int yshift = 39;
GRect shift_bounds = GRect(xshift, yshift,
orig_bounds.size.w - xshift,
orig_bounds.size.h - yshift);
GBitmapSequence *bitmap_sequence = gbitmap_sequence_create_with_resource(resource_id);
cl_assert(bitmap_sequence);
GBitmap *bitmap = gbitmap_create_blank(orig_bounds.size, GBitmapFormat8BitCircular);
cl_assert(bitmap);
// Shift the bounds when updating
gbitmap_set_bounds(bitmap, shift_bounds);
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
// set the original bounds to do whole-image comparison
gbitmap_set_bounds(bitmap, orig_bounds);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(1)));
gbitmap_set_bounds(bitmap, shift_bounds);
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
gbitmap_set_bounds(bitmap, orig_bounds);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(2)));
gbitmap_set_bounds(bitmap, shift_bounds);
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
gbitmap_set_bounds(bitmap, orig_bounds);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(3)));
gbitmap_set_bounds(bitmap, shift_bounds);
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
gbitmap_set_bounds(bitmap, orig_bounds);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(4)));
gbitmap_set_bounds(bitmap, shift_bounds);
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
gbitmap_set_bounds(bitmap, orig_bounds);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(5)));
#endif
}
// Tests APNG file with 8-bit color, originally broke the updated upng+tinflate code
// Result:
// - gbitmaps matches platform decoded APNG
void test_gbitmap_sequence__color_8bit_yoshi(void) {
#if PLATFORM_SPALDING
bool status = false;
uint32_t resource_id = sys_resource_load_file_as_resource(TEST_IMAGES_PATH, GET_APNG_NAME);
cl_assert(resource_id != UINT32_MAX);
GBitmapSequence *bitmap_sequence = gbitmap_sequence_create_with_resource(resource_id);
GBitmap *bitmap = gbitmap_create_blank(gbitmap_sequence_get_bitmap_size(bitmap_sequence),
GBitmapFormat8Bit);
status = gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(1)));
status = gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(2)));
status = gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(3)));
#endif
}
void test_gbitmap_sequence__1bit_to_1bit_notification(void) {
uint32_t resource_id = sys_resource_load_file_as_resource(TEST_IMAGES_PATH, GET_APNG_NAME);
cl_assert(resource_id != UINT32_MAX);
GBitmapSequence *bitmap_sequence = gbitmap_sequence_create_with_resource(resource_id);
GBitmap *bitmap = gbitmap_create_blank(gbitmap_sequence_get_bitmap_size(bitmap_sequence),
GBitmapFormat1Bit);
// We're interested in checking the following frames
const int check_frames[] = { 1, 4, 23, 24, 25, 31, 75 };
int current_frame = 0;
for (int i = 0; i < ARRAY_LENGTH(check_frames); ++i) {
// Advance to the next frame we're interested in.
for (; current_frame < check_frames[i]; ++current_frame) {
bool status = gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_assert_equal_b(status, true);
}
printf("Checking %u\n", current_frame);
// Don't use GET_PBI_NAME, it doesn't like not using anything other than an integer literal
char filename_buffer[128];
snprintf(filename_buffer, sizeof(filename_buffer), "%s_%u.pbi", __func__, current_frame);
cl_check(gbitmap_pbi_eq(bitmap, filename_buffer));
}
}