/* * 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 #include #include #include // 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)); } }