mirror of
https://github.com/google/pebble.git
synced 2025-03-23 12:12:19 +00:00
459 lines
15 KiB
C
459 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/gtypes.h"
|
|
#include "applib/graphics/graphics.h"
|
|
#include "applib/graphics/gpath.h"
|
|
#include "util/trig.h"
|
|
#include "applib/ui/ui.h"
|
|
|
|
#include <string.h>
|
|
|
|
// Helper Functions
|
|
////////////////////////////////////
|
|
#include "util.h"
|
|
#include "test_graphics.h"
|
|
#include "${BIT_DEPTH_NAME}/test_framebuffer.h"
|
|
|
|
// Stubs
|
|
////////////////////////////////////
|
|
#include "graphics_common_stubs.h"
|
|
#include "stubs_applib_resource.h"
|
|
|
|
static const int16_t SCREEN_WIDTH = 144;
|
|
static const int16_t SCREEN_HEIGHT = 168;
|
|
static FrameBuffer *fb = NULL;
|
|
|
|
static const GPathInfo s_house_path_info = {
|
|
.num_points = 11,
|
|
.points = (GPoint []) {
|
|
{-40, 0}, {0, -40}, {40, 0}, {28, 0}, {28, 40}, {10, 40},
|
|
{10, 16}, {-10, 16}, {-10, 40}, {-28, 40}, {-28, 0},
|
|
},
|
|
};
|
|
|
|
static const GPathInfo s_bolt_path_info = {
|
|
.num_points = 6,
|
|
.points = (GPoint []) {{21, 0}, {14, 26}, {28, 26}, {7, 60}, {14, 34}, {0, 34}}
|
|
};
|
|
|
|
static const GPathInfo s_duplicates_path_info = {
|
|
6,
|
|
(GPoint[]) {
|
|
{40, 0},
|
|
{40, 0},
|
|
{0, 40},
|
|
{0, 40},
|
|
{80, 40},
|
|
{80, 40}
|
|
}
|
|
};
|
|
|
|
static const GPathInfo s_single_duplicate_path_info = {
|
|
2,
|
|
(GPoint[]) {
|
|
{40, 0},
|
|
{40, 0}
|
|
}
|
|
};
|
|
|
|
static const GPathInfo s_crossing_path_info = {
|
|
6,
|
|
(GPoint[]) {
|
|
{0, 40},
|
|
{20, 20},
|
|
{60, 60},
|
|
{80, 40},
|
|
{60, 20},
|
|
{20, 60}
|
|
}
|
|
};
|
|
|
|
static const GPathInfo s_infinite_path_info = {
|
|
16,
|
|
(GPoint[]) {
|
|
{-50, 0},
|
|
{-50, -60},
|
|
{10, -60},
|
|
{10, -20},
|
|
{-10, -20},
|
|
{-10, -40},
|
|
{-30, -40},
|
|
{-30, -20},
|
|
{50, -20},
|
|
{50, 40},
|
|
{-10, 40},
|
|
{-10, 0},
|
|
{10, 0},
|
|
{10, 20},
|
|
{30, 20},
|
|
{30, 0}
|
|
}
|
|
};
|
|
|
|
static const GPathInfo s_aa_clipping_path_info = {
|
|
.num_points = 4,
|
|
.points = (GPoint []) {{0,0}, {200, 0}, {200, 30}, {0, 30}}
|
|
};
|
|
|
|
static bool s_outline_mode = false;
|
|
static int s_path_angle = 0;
|
|
|
|
static GPath *s_house_path = NULL;
|
|
static GPath *s_bolt_path = NULL;
|
|
static GPath *s_duplicates_path = NULL;
|
|
static GPath *s_single_duplicate_path = NULL;
|
|
static GPath *s_crossing_path = NULL;
|
|
static GPath *s_infinite_path = NULL;
|
|
static GPath *s_current_path = NULL;
|
|
static GPath *s_aa_clipping_path = NULL;
|
|
|
|
static void prv_filled_update_proc(Layer *layer, GContext *ctx) {
|
|
graphics_context_set_stroke_color(ctx, GColorBlack);
|
|
|
|
#if 0 // Guidelines
|
|
int num_segments = 4;
|
|
int segment_width = SCREEN_WIDTH / num_segments;
|
|
int segment_height = SCREEN_HEIGHT / num_segments;
|
|
for (int i = 1; i < num_segments; ++i) {
|
|
graphics_draw_line(ctx,
|
|
GPoint(segment_width * i, 0),
|
|
GPoint(segment_width * i, SCREEN_HEIGHT));
|
|
graphics_draw_line(ctx,
|
|
GPoint(0, segment_height * i),
|
|
GPoint(SCREEN_WIDTH, segment_height * i));
|
|
}
|
|
#endif
|
|
|
|
gpath_rotate_to(s_current_path, s_path_angle * TRIG_MAX_ANGLE / 360);
|
|
|
|
if (s_outline_mode) {
|
|
graphics_context_set_stroke_color(ctx, GColorBlack);
|
|
gpath_draw_outline(ctx, s_current_path);
|
|
} else {
|
|
graphics_context_set_fill_color(ctx, GColorBlack);
|
|
gpath_draw_filled(ctx, s_current_path);
|
|
}
|
|
}
|
|
|
|
static void prv_reset(void) {
|
|
gpath_move_to(s_house_path, GPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
|
|
s_outline_mode = false;
|
|
s_path_angle = 0;
|
|
}
|
|
|
|
// setup and teardown
|
|
void test_graphics_gpath_${BIT_DEPTH_NAME}__initialize(void) {
|
|
fb = malloc(sizeof(FrameBuffer));
|
|
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
|
|
s_house_path = gpath_create(&s_house_path_info);
|
|
s_bolt_path = gpath_create(&s_bolt_path_info);
|
|
s_duplicates_path = gpath_create(&s_duplicates_path_info);
|
|
s_crossing_path = gpath_create(&s_crossing_path_info);
|
|
s_infinite_path = gpath_create(&s_infinite_path_info);
|
|
s_aa_clipping_path = gpath_create(&s_aa_clipping_path_info);
|
|
prv_reset();
|
|
}
|
|
|
|
void test_graphics_gpath_${BIT_DEPTH_NAME}__cleanup(void) {
|
|
free(fb);
|
|
gpath_destroy(s_house_path);
|
|
gpath_destroy(s_bolt_path);
|
|
gpath_destroy(s_duplicates_path);
|
|
gpath_destroy(s_infinite_path);
|
|
gpath_destroy(s_crossing_path);
|
|
gpath_destroy(s_aa_clipping_path);
|
|
}
|
|
|
|
// tests
|
|
void test_graphics_gpath_${BIT_DEPTH_NAME}__filled(void) {
|
|
GContext ctx;
|
|
Layer layer;
|
|
|
|
prv_reset();
|
|
s_current_path = s_house_path;
|
|
test_graphics_context_init(&ctx, fb);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled.${BIT_DEPTH_NAME}.pbi"));
|
|
}
|
|
|
|
void test_graphics_gpath_${BIT_DEPTH_NAME}__filled_clipped(void) {
|
|
GContext ctx;
|
|
Layer layer;
|
|
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_house_path;
|
|
ctx.draw_state.clip_box = GRect(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
|
|
"gpath_filled_top_clipped.${BIT_DEPTH_NAME}.pbi"));
|
|
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_house_path;
|
|
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
|
|
"gpath_filled_bottom_clipped.${BIT_DEPTH_NAME}.pbi"));
|
|
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_house_path;
|
|
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
|
|
"gpath_filled_left_clipped.${BIT_DEPTH_NAME}.pbi"));
|
|
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_house_path;
|
|
ctx.draw_state.clip_box = GRect(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
|
|
"gpath_filled_right_clipped.${BIT_DEPTH_NAME}.pbi"));
|
|
}
|
|
|
|
// outside with no clipping -- results should be identical to the regular filled test
|
|
void test_graphics_gpath_${BIT_DEPTH_NAME}__filled_outside(void) {
|
|
GContext ctx;
|
|
Layer layer;
|
|
|
|
printf("-- top\n");
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_house_path;
|
|
gpath_move_to(s_house_path, GPoint(SCREEN_WIDTH / 2, 0));
|
|
ctx.draw_state.drawing_box = GRect(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled.${BIT_DEPTH_NAME}.pbi"));
|
|
|
|
printf("-- bottom\n");
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_house_path;
|
|
gpath_move_to(s_house_path, GPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT));
|
|
ctx.draw_state.drawing_box = GRect(0, -SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled.${BIT_DEPTH_NAME}.pbi"));
|
|
|
|
printf("-- left\n");
|
|
prv_reset();
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_house_path;
|
|
gpath_move_to(s_house_path, GPoint(0, SCREEN_HEIGHT / 2));
|
|
ctx.draw_state.drawing_box = GRect(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled.${BIT_DEPTH_NAME}.pbi"));
|
|
|
|
printf("-- right\n");
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_house_path;
|
|
gpath_move_to(s_house_path, GPoint(SCREEN_WIDTH, SCREEN_HEIGHT / 2));
|
|
ctx.draw_state.drawing_box = GRect(-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled.${BIT_DEPTH_NAME}.pbi"));
|
|
}
|
|
|
|
// AA section
|
|
void test_graphics_gpath_${BIT_DEPTH_NAME}__filled_aa(void) {
|
|
GContext ctx;
|
|
Layer layer;
|
|
|
|
// House path - tests horizontal line edge case
|
|
prv_reset();
|
|
s_current_path = s_house_path;
|
|
test_graphics_context_init(&ctx, fb);
|
|
graphics_context_set_antialiased(&ctx, true);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled_aa.${BIT_DEPTH_NAME}.pbi"));
|
|
|
|
// Special case for two points that are duplicates...
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_single_duplicate_path;
|
|
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
|
|
graphics_context_set_antialiased(&ctx, true);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
|
|
"gpath_filled_single_duplicate_aa.${BIT_DEPTH_NAME}.pbi"));
|
|
|
|
}
|
|
|
|
void test_graphics_gpath_${BIT_DEPTH_NAME}__filled_clipped_aa(void) {
|
|
GContext ctx;
|
|
Layer layer;
|
|
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_house_path;
|
|
ctx.draw_state.clip_box = GRect(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
|
|
graphics_context_set_antialiased(&ctx, true);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
|
|
"gpath_filled_top_clipped_aa.${BIT_DEPTH_NAME}.pbi"));
|
|
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_house_path;
|
|
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
|
|
graphics_context_set_antialiased(&ctx, true);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
|
|
"gpath_filled_bottom_clipped_aa.${BIT_DEPTH_NAME}.pbi"));
|
|
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_house_path;
|
|
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
|
|
graphics_context_set_antialiased(&ctx, true);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
|
|
"gpath_filled_left_clipped_aa.${BIT_DEPTH_NAME}.pbi"));
|
|
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_house_path;
|
|
ctx.draw_state.clip_box = GRect(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
|
|
graphics_context_set_antialiased(&ctx, true);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
|
|
"gpath_filled_right_clipped_aa.${BIT_DEPTH_NAME}.pbi"));
|
|
}
|
|
|
|
// Additional test to check AA on edges - works only on 8bit
|
|
void test_graphics_gpath_8bit__filled_bolt_aa(void) {
|
|
// NOTE: Those tests are being performed only in 8bits due to differences caused by AA,
|
|
// performing them in both 1bit and 8bit would create differences on the edges
|
|
// and fail unit tests as a result
|
|
GContext ctx;
|
|
Layer layer;
|
|
|
|
// Bolt path - test antialiased edges
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_bolt_path;
|
|
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
|
|
graphics_context_set_antialiased(&ctx, true);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
|
|
"gpath_filled_bolt_aa.8bit.pbi"));
|
|
|
|
// Duplicate points in GPath test - makes sure theres no division by zero ;)
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_duplicates_path;
|
|
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
|
|
graphics_context_set_antialiased(&ctx, true);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
|
|
"gpath_filled_duplicates_aa.8bit.pbi"));
|
|
|
|
// Crossing path - makes sure algorithm works for path that crosses itself
|
|
prv_reset();
|
|
s_current_path = s_crossing_path;
|
|
test_graphics_context_init(&ctx, fb);
|
|
graphics_context_set_antialiased(&ctx, true);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled_crossing_aa.8bit.pbi"));
|
|
|
|
// Infinite path - shows an example where path seems to be crossing itself but
|
|
// in fact it does not
|
|
prv_reset();
|
|
s_current_path = s_infinite_path;
|
|
gpath_move_to(s_infinite_path, GPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
|
|
test_graphics_context_init(&ctx, fb);
|
|
graphics_context_set_antialiased(&ctx, true);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled_infinite_aa.8bit.pbi"));
|
|
|
|
// An angle of infinite path - here we see the spacing between the parts
|
|
prv_reset();
|
|
s_current_path = s_infinite_path;
|
|
gpath_move_to(s_infinite_path, GPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
|
|
s_path_angle = 45;
|
|
test_graphics_context_init(&ctx, fb);
|
|
graphics_context_set_antialiased(&ctx, true);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled_infinite_45_aa.8bit.pbi"));
|
|
|
|
// Another angle of infinite path
|
|
prv_reset();
|
|
s_current_path = s_infinite_path;
|
|
gpath_move_to(s_infinite_path, GPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
|
|
s_path_angle = 70;
|
|
test_graphics_context_init(&ctx, fb);
|
|
graphics_context_set_antialiased(&ctx, true);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled_infinite_70_aa.8bit.pbi"));
|
|
|
|
// House path - two edge cases for tipping points of the path
|
|
prv_reset();
|
|
s_current_path = s_house_path;
|
|
gpath_move_to(s_house_path, GPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
|
|
s_path_angle = 20;
|
|
test_graphics_context_init(&ctx, fb);
|
|
graphics_context_set_antialiased(&ctx, true);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled_house_20_aa.8bit.pbi"));
|
|
|
|
// This case demonstrates tipping point that is also the starting point of the path
|
|
prv_reset();
|
|
s_current_path = s_house_path;
|
|
gpath_move_to(s_house_path, GPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
|
|
s_path_angle = 105;
|
|
test_graphics_context_init(&ctx, fb);
|
|
graphics_context_set_antialiased(&ctx, true);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled_house_105_aa.8bit.pbi"));
|
|
|
|
// Safety
|
|
s_path_angle = 0;
|
|
}
|
|
|
|
void test_graphics_gpath_8bit__clipping_aa(void) {
|
|
// NOTE: This test verifies correct clipping of anti-aliased edges on gpaths, therefore
|
|
// it works only on 8bit
|
|
GContext ctx;
|
|
Layer layer;
|
|
|
|
prv_reset();
|
|
test_graphics_context_init(&ctx, fb);
|
|
s_current_path = s_aa_clipping_path;
|
|
s_path_angle = 17;
|
|
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
|
|
graphics_context_set_antialiased(&ctx, true);
|
|
prv_filled_update_proc(NULL, &ctx);
|
|
// NOTE: Expected result of this test is to have an antialiased stripe go through the screen,
|
|
// where antialiased edges are being nicely cut off on top and bottom of the stripe
|
|
// (antialiased gradient would dive into the stripe near screen edges), also top
|
|
// left corner is intentinally ending just before screen cuts it out to verify that
|
|
// fractional anti-aliasing is not bleeding into row before (would occur as pixels on
|
|
// right side of the screen)
|
|
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
|
|
"gpath_clipping_aa.8bit.pbi"));
|
|
|
|
// Safety
|
|
s_path_angle = 0;
|
|
}
|