mirror of
https://github.com/google/pebble.git
synced 2025-03-23 04:02:19 +00:00
450 lines
15 KiB
C
450 lines
15 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 "clar.h"
|
|
|
|
#include "applib/graphics/graphics.h"
|
|
#include "applib/graphics/framebuffer.h"
|
|
#include "applib/ui/bitmap_layer.h"
|
|
#include "applib/ui/window_private.h"
|
|
#include "util/size.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
// Helper Functions
|
|
////////////////////////////////////
|
|
|
|
#include "test_graphics.h"
|
|
#include "../graphics/util.h"
|
|
|
|
#if (SCREEN_COLOR_DEPTH_BITS == 1)
|
|
#include "1bit/test_framebuffer.h"
|
|
#elif (SCREEN_COLOR_DEPTH_BITS == 8)
|
|
#include "8bit/test_framebuffer.h"
|
|
#else
|
|
#error "Unrecognized SCREEN_COLOR_DEPTH_BITS"
|
|
#endif
|
|
|
|
|
|
// Stubs
|
|
////////////////////////////////////
|
|
|
|
#include "graphics_common_stubs.h"
|
|
#include "stubs_applib_resource.h"
|
|
|
|
// Setup
|
|
////////////////////////////////////
|
|
|
|
static GBitmap test_image;
|
|
static GContext s_ctx;
|
|
static FrameBuffer *s_fb = NULL;
|
|
|
|
void test_graphics_draw_bitmap__initialize(void) {
|
|
s_fb = malloc(sizeof(FrameBuffer));
|
|
framebuffer_init(s_fb, &(GSize) {DISP_COLS, DISP_ROWS});
|
|
test_graphics_context_init(&s_ctx, s_fb);
|
|
read_pbi("no_litter_crop.png.pbi", &test_image);
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__cleanup(void) {
|
|
free(s_fb);
|
|
free(test_image.addr);
|
|
}
|
|
|
|
// Layout Test Support
|
|
////////////////////////////////////
|
|
|
|
#define TEST_LAYER_SIZE (GSize(64, 110))
|
|
#define TEST_LAYER_OFFSET_ORIGIN (GPoint(80, 55))
|
|
|
|
static GPoint s_layer_test_image_bounds_offset;
|
|
|
|
static void prv_layer_test_update_proc(Layer *layer, GContext *ctx) {
|
|
GRect destination = test_image.bounds;
|
|
destination.origin = s_layer_test_image_bounds_offset;
|
|
graphics_context_set_compositing_mode(ctx, GCompOpAssign);
|
|
graphics_draw_bitmap_in_rect(ctx, &test_image, &destination);
|
|
}
|
|
|
|
static void prv_layer_test(GPoint layer_origin, GPoint image_bounds_offset) {
|
|
Layer layer;
|
|
const GRect layer_frame = (GRect) {
|
|
.origin = layer_origin,
|
|
.size = TEST_LAYER_SIZE,
|
|
};
|
|
layer_init(&layer, &layer_frame);
|
|
layer_set_update_proc(&layer, prv_layer_test_update_proc);
|
|
|
|
s_layer_test_image_bounds_offset = image_bounds_offset;
|
|
|
|
layer_render_tree(&layer, &s_ctx);
|
|
}
|
|
|
|
static void prv_origin_layer_test(GPoint image_bounds_offset) {
|
|
prv_layer_test(GPointZero, image_bounds_offset);
|
|
}
|
|
|
|
static void prv_offset_layer_test(GPoint image_bounds_offset) {
|
|
prv_layer_test(TEST_LAYER_OFFSET_ORIGIN, image_bounds_offset);
|
|
}
|
|
|
|
static void prv_bitmap_layer_test(GPoint frame_origin, GPoint bounds_origin) {
|
|
BitmapLayer bitmap_layer;
|
|
const GRect bitmap_layer_frame = (GRect) {
|
|
.origin = frame_origin,
|
|
.size = TEST_LAYER_SIZE,
|
|
};
|
|
bitmap_layer_init(&bitmap_layer, &bitmap_layer_frame);
|
|
Layer *layer = bitmap_layer_get_layer(&bitmap_layer);
|
|
|
|
GRect bitmap_layer_bounds = bitmap_layer_frame;
|
|
bitmap_layer_bounds.origin = bounds_origin;
|
|
layer_set_bounds(layer, &bitmap_layer_bounds);
|
|
|
|
bitmap_layer_set_bitmap(&bitmap_layer, &test_image);
|
|
bitmap_layer_set_compositing_mode(&bitmap_layer, GCompOpAssign);
|
|
layer_render_tree(bitmap_layer_get_layer(&bitmap_layer), &s_ctx);
|
|
}
|
|
|
|
static void prv_origin_bitmap_layer_test(GPoint bounds_origin) {
|
|
prv_bitmap_layer_test(GPointZero, bounds_origin);
|
|
}
|
|
|
|
static void prv_offset_bitmap_layer_test(GPoint bounds_origin) {
|
|
prv_bitmap_layer_test(TEST_LAYER_OFFSET_ORIGIN, bounds_origin);
|
|
}
|
|
|
|
// Composite Test Support
|
|
////////////////////////////////////
|
|
|
|
static GBitmap *prv_create_bitmap_from_png_file(const char *png_filename_without_extension) {
|
|
GBitmap *result = NULL;
|
|
|
|
char png_file_path[strlen(CLAR_FIXTURE_PATH) + 1 + strlen(GRAPHICS_FIXTURE_PATH) + 1 +
|
|
strlen(png_filename_without_extension) + 1];
|
|
sprintf(png_file_path, "%s/%s/%s.png", CLAR_FIXTURE_PATH, GRAPHICS_FIXTURE_PATH,
|
|
png_filename_without_extension);
|
|
|
|
FILE *fp = fopen(png_file_path, "r");
|
|
if (fp) {
|
|
// Go to the end of the file to discover the size of the file
|
|
if (fseek(fp, 0, SEEK_END) != 0) {
|
|
goto cleanup;
|
|
}
|
|
const long end_of_file = ftell(fp);
|
|
const size_t file_size = (size_t)end_of_file;
|
|
if (end_of_file == -1) {
|
|
goto cleanup;
|
|
}
|
|
uint8_t *png_data = malloc((size_t)file_size);
|
|
if (!png_data) {
|
|
goto cleanup;
|
|
}
|
|
|
|
// Go back to the start of the file and read it into the buffer
|
|
if (fseek(fp, 0L, SEEK_SET) != 0) {
|
|
goto cleanup;
|
|
}
|
|
cl_assert_equal_i(fread(png_data, 1, file_size, fp), file_size);
|
|
|
|
// Create a GBitmap using the PNG data
|
|
result = gbitmap_create_from_png_data(png_data, file_size);
|
|
if (result) {
|
|
free(png_data);
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
fclose(fp);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GBitmap *prv_create_bitmap_from_pbi_file(const char *pbi_filename_without_extension) {
|
|
// Create a static GBitmap to avoid modifying the read_pbi() function to return a GBitmap
|
|
static GBitmap result;
|
|
|
|
char pbi_filename[PATH_STRING_LENGTH] = {0};
|
|
snprintf(pbi_filename, PATH_STRING_LENGTH, "%s.pbi", pbi_filename_without_extension);
|
|
|
|
if (!read_pbi(pbi_filename, &result)) {
|
|
return NULL;
|
|
} else {
|
|
return &result;
|
|
}
|
|
}
|
|
|
|
typedef GBitmap *(*CompositeTestGBitmapCreateFunc)(const char *filename);
|
|
|
|
typedef struct CompositeTest {
|
|
const char *test_name;
|
|
GBitmapFormat expected_test_image_bitmap_format;
|
|
CompositeTestGBitmapCreateFunc bitmap_create_func;
|
|
bool need_to_destroy_bitmap;
|
|
} CompositeTest;
|
|
|
|
static const CompositeTest s_composite_tests[] = {
|
|
{
|
|
.test_name = "1bitBW",
|
|
.expected_test_image_bitmap_format = GBitmapFormat1Bit,
|
|
.bitmap_create_func = prv_create_bitmap_from_pbi_file,
|
|
.need_to_destroy_bitmap = false,
|
|
},
|
|
{
|
|
.test_name = "2bitTrns",
|
|
.expected_test_image_bitmap_format = GBitmapFormat2BitPalette,
|
|
.bitmap_create_func = prv_create_bitmap_from_png_file,
|
|
.need_to_destroy_bitmap = true,
|
|
},
|
|
#if PBL_COLOR
|
|
{
|
|
.test_name = "4bitTrns",
|
|
.expected_test_image_bitmap_format = GBitmapFormat4BitPalette,
|
|
.bitmap_create_func = prv_create_bitmap_from_png_file,
|
|
.need_to_destroy_bitmap = true,
|
|
},
|
|
{
|
|
.test_name = "8bitTrns",
|
|
.expected_test_image_bitmap_format = GBitmapFormat8Bit,
|
|
.bitmap_create_func = prv_create_bitmap_from_png_file,
|
|
.need_to_destroy_bitmap = true,
|
|
},
|
|
#endif
|
|
};
|
|
|
|
#define COMPOSITE_TEST_IMAGE_SIZE_WIDTH (100)
|
|
|
|
#define COMPOSITE_TEST_OFFSET_X (COMPOSITE_TEST_IMAGE_SIZE_WIDTH / 2)
|
|
#define COMPOSITE_TEST_OFFSET_Y (0)
|
|
|
|
static bool prv_gbitmap_format_and_compositing_mode_combo_is_valid(GBitmapFormat bitmap_format,
|
|
GCompOp compositing_mode) {
|
|
return !((bitmap_format != GBitmapFormat1Bit) &&
|
|
((compositing_mode == GCompOpAssignInverted) ||
|
|
(compositing_mode == GCompOpOr) ||
|
|
(compositing_mode == GCompOpAnd) ||
|
|
(compositing_mode == GCompOpClear)));
|
|
}
|
|
|
|
static void prv_composite_test_draw_bitmap(GContext *ctx, GBitmap *bitmap, GPoint offset,
|
|
GCompOp compositing_mode) {
|
|
GRect destination = bitmap->bounds;
|
|
destination.origin = gpoint_add(destination.origin, offset);
|
|
graphics_context_set_compositing_mode(ctx, GCompOpAssign);
|
|
graphics_draw_bitmap_in_rect(ctx, bitmap, &destination);
|
|
destination.origin = gpoint_add(destination.origin, GPoint(COMPOSITE_TEST_OFFSET_X,
|
|
COMPOSITE_TEST_OFFSET_Y));
|
|
graphics_context_set_compositing_mode(ctx, compositing_mode);
|
|
graphics_draw_bitmap_in_rect(ctx, bitmap, &destination);
|
|
}
|
|
|
|
static void prv_composite_test(const char *unit_test_name, GCompOp compositing_mode) {
|
|
for (size_t i = 0; i < ARRAY_LENGTH(s_composite_tests); i++) {
|
|
const CompositeTest *test_data = &s_composite_tests[i];
|
|
|
|
// Skip invalid GBitmapFormat and GCompOp combinations
|
|
if (!prv_gbitmap_format_and_compositing_mode_combo_is_valid(
|
|
test_data->expected_test_image_bitmap_format, compositing_mode)) {
|
|
break;
|
|
}
|
|
|
|
framebuffer_clear(s_fb);
|
|
|
|
char test_image_filename[PATH_STRING_LENGTH] = {0};
|
|
snprintf(test_image_filename, PATH_STRING_LENGTH, "test_graphics_draw_bitmap_%s_test_image",
|
|
test_data->test_name);
|
|
|
|
GBitmap *bitmap = test_data->bitmap_create_func(test_image_filename);
|
|
cl_assert(bitmap);
|
|
cl_assert(gbitmap_get_format(bitmap) == test_data->expected_test_image_bitmap_format);
|
|
|
|
// Draw the two variations of the test image at GPointZero
|
|
prv_composite_test_draw_bitmap(&s_ctx, bitmap, GPointZero, compositing_mode);
|
|
|
|
// Then redraw the two variations offset so the bottom right edge of the right variation
|
|
// is aligned with the bottom right edge of the framebuffer
|
|
const GPoint framebuffer_bottom_right_point =
|
|
GPoint(grect_get_max_x(&s_ctx.dest_bitmap.bounds),
|
|
grect_get_max_y(&s_ctx.dest_bitmap.bounds));
|
|
GPoint offset_point = gpoint_sub(framebuffer_bottom_right_point,
|
|
GPoint(bitmap->bounds.size.w, bitmap->bounds.size.h));
|
|
offset_point = gpoint_sub(offset_point, GPoint(COMPOSITE_TEST_OFFSET_X,
|
|
COMPOSITE_TEST_OFFSET_Y));
|
|
prv_composite_test_draw_bitmap(&s_ctx, bitmap, offset_point, compositing_mode);
|
|
|
|
// Check the result
|
|
char unit_test_result_image_file_base_name[PATH_STRING_LENGTH] = {0};
|
|
snprintf(unit_test_result_image_file_base_name, PATH_STRING_LENGTH,
|
|
"%s_%s", unit_test_name, test_data->test_name);
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap,
|
|
namecat(unit_test_result_image_file_base_name, ".pbi")));
|
|
|
|
if (test_data->need_to_destroy_bitmap) {
|
|
gbitmap_destroy(bitmap);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tests
|
|
////////////////////////////////////
|
|
|
|
void test_graphics_draw_bitmap__origin_layer_inside(void) {
|
|
prv_origin_layer_test(GPointZero);
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__origin_layer_across_x(void) {
|
|
prv_origin_layer_test(GPoint(25, 0));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__origin_layer_across_nx(void) {
|
|
prv_origin_layer_test(GPoint(-25, 0));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__origin_layer_across_y(void) {
|
|
prv_origin_layer_test(GPoint(0, 40));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__origin_layer_across_ny(void) {
|
|
prv_origin_layer_test(GPoint(0, -40));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__origin_bitmap_layer_inside(void) {
|
|
prv_origin_bitmap_layer_test(GPointZero);
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__origin_bitmap_layer_across_x(void) {
|
|
prv_origin_bitmap_layer_test(GPoint(25, 0));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__origin_bitmap_layer_across_nx(void) {
|
|
prv_origin_bitmap_layer_test(GPoint(-25, 0));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__origin_bitmap_layer_across_y(void) {
|
|
prv_origin_bitmap_layer_test(GPoint(0, 75));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__origin_bitmap_layer_across_ny(void) {
|
|
prv_origin_bitmap_layer_test(GPoint(0, -25));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__offset_layer_inside(void) {
|
|
prv_offset_layer_test(GPointZero);
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__offset_layer_across_x(void) {
|
|
prv_offset_layer_test(GPoint(25, 0));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__offset_layer_across_nx(void) {
|
|
prv_offset_layer_test(GPoint(-25, 0));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__offset_layer_across_y(void) {
|
|
prv_offset_layer_test(GPoint(0, 40));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__offset_layer_across_ny(void) {
|
|
prv_offset_layer_test(GPoint(0, -40));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__offset_bitmap_layer_inside(void) {
|
|
prv_offset_bitmap_layer_test(GPointZero);
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__offset_bitmap_layer_across_x(void) {
|
|
prv_offset_bitmap_layer_test(GPoint(25, 0));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__offset_bitmap_layer_across_nx(void) {
|
|
prv_offset_bitmap_layer_test(GPoint(-25, 0));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__offset_bitmap_layer_across_y(void) {
|
|
prv_offset_bitmap_layer_test(GPoint(0, 75));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__offset_bitmap_layer_across_ny(void) {
|
|
prv_offset_bitmap_layer_test(GPoint(0, -25));
|
|
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__composite_assign(void) {
|
|
prv_composite_test(__func__, GCompOpAssign);
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__composite_assign_inverted(void) {
|
|
prv_composite_test(__func__, GCompOpAssignInverted);
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__composite_or(void) {
|
|
prv_composite_test(__func__, GCompOpOr);
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__composite_and(void) {
|
|
prv_composite_test(__func__, GCompOpAnd);
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__composite_clear(void) {
|
|
prv_composite_test(__func__, GCompOpClear);
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__composite_set(void) {
|
|
prv_composite_test(__func__, GCompOpSet);
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__composite_tint(void) {
|
|
graphics_context_set_tint_color(&s_ctx, GColorOrange);
|
|
prv_composite_test(__func__, GCompOpTint);
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__composite_tint_luminance_black_opaque(void) {
|
|
graphics_context_set_tint_color(&s_ctx, GColorBlack);
|
|
prv_composite_test(__func__, GCompOpTintLuminance);
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__composite_tint_luminance_black_semitransparent(void) {
|
|
GColor tint_color = GColorBlack;
|
|
tint_color.a = 2;
|
|
// We have to set the tint color directly in the GContext because
|
|
// graphics_context_set_tint_color() calls gcolor_closest_opaque() on the color you give it
|
|
s_ctx.draw_state.tint_color = tint_color;
|
|
prv_composite_test(__func__, GCompOpTintLuminance);
|
|
}
|
|
|
|
void test_graphics_draw_bitmap__composite_tint_luminance_blue_opaque(void) {
|
|
graphics_context_set_tint_color(&s_ctx, GColorBlue);
|
|
prv_composite_test(__func__, GCompOpTintLuminance);
|
|
}
|