/* * 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/graphics.h" #include "applib/graphics/graphics_circle_private.h" #include "applib/graphics/framebuffer.h" #include "util/trig.h" #include "applib/ui/window_private.h" #include "applib/ui/layer.h" #include "util/size.h" #include "clar.h" #include "util.h" #include "pebble_asserts.h" #include // Helper Functions //////////////////////////////////// #include "test_graphics.h" #include "${BIT_DEPTH_NAME}/test_framebuffer.h" // Stubs //////////////////////////////////// #include "graphics_common_stubs.h" #include "stubs_applib_resource.h" static FrameBuffer *fb = NULL; // Setup void test_graphics_fill_circle_${BIT_DEPTH_NAME}__initialize(void) { fb = malloc(sizeof(FrameBuffer)); framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS}); } // Teardown void test_graphics_fill_circle_${BIT_DEPTH_NAME}__cleanup(void) { free(fb); } // Tests //////////////////////////////////// void inside_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_fill_color(ctx, GColorBlack); graphics_context_set_antialiased(ctx, false); graphics_fill_circle(ctx, GPoint(14, 14), 12); } void white_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_fill_color(ctx, GColorWhite); graphics_context_set_antialiased(ctx, false); graphics_fill_circle(ctx, GPoint(14, 14), 12); } void clear_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_fill_color(ctx, GColorClear); graphics_context_set_antialiased(ctx, false); graphics_fill_circle(ctx, GPoint(14, 14), 12); } void across_x_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_fill_color(ctx, GColorBlack); graphics_context_set_antialiased(ctx, false); graphics_fill_circle(ctx, GPoint(28, 14), 12); } void across_nx_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_fill_color(ctx, GColorBlack); graphics_context_set_antialiased(ctx, false); graphics_fill_circle(ctx, GPoint(-14, 14), 12); } void across_y_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_fill_color(ctx, GColorBlack); graphics_context_set_antialiased(ctx, false); graphics_fill_circle(ctx, GPoint(14, 28), 12); } void across_ny_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_fill_color(ctx, GColorBlack); graphics_context_set_antialiased(ctx, false); graphics_fill_circle(ctx, GPoint(14, -14), 12); } void test_graphics_fill_circle_${BIT_DEPTH_NAME}__origin_layer(void) { GContext ctx; Layer layer; test_graphics_context_init(&ctx, fb); layer_init(&layer, &GRect(0, 0, 28, 28)); layer_set_update_proc(&layer, &inside_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_inside_origin_layer.${BIT_DEPTH_NAME}.pbi")); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &across_x_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_x_origin_layer.${BIT_DEPTH_NAME}.pbi")); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &across_nx_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_nx_origin_layer.${BIT_DEPTH_NAME}.pbi")); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &across_y_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_y_origin_layer.${BIT_DEPTH_NAME}.pbi")); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &across_ny_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_ny_origin_layer.${BIT_DEPTH_NAME}.pbi")); } #define RADIUS_BIG 15 #define RADIUS_MEDIUM 8 #define RADIUS_MIN_CALCULATED 3 #define RADIUS_MAX_PRECOMPUTED 2 #define RADIUS_SMALL 1 #define RADIUS_NONE 0 #define ORIGIN_RECT_NO_CLIP GRect(0, 0, 144, 168) #define ORIGIN_RECT_CLIP_XY GRect(0, 0, 30, 40) #define ORIGIN_RECT_CLIP_NXNY GRect(0, 0, 30, 40) #define CENTER_OF_ORIGIN_RECT GPoint(20, 25) #define CENTER_OF_ORIGIN_RECT_NXNY GPoint(10, 15) void test_graphics_fill_circle_${BIT_DEPTH_NAME}__origin_layer_aa(void) { GContext ctx; test_graphics_context_init(&ctx, fb); // Big circles setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_BIG); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r16_no_clip.${BIT_DEPTH_NAME}.pbi")); setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1); graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_BIG); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r16_clip_xy.${BIT_DEPTH_NAME}.pbi")); setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 1); graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_BIG); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r16_clip_nxny.${BIT_DEPTH_NAME}.pbi")); // Medium circles setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r8_no_clip.${BIT_DEPTH_NAME}.pbi")); setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1); graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r8_clip_xy.${BIT_DEPTH_NAME}.pbi")); setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 1); graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_MEDIUM); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r8_clip_nxny.${BIT_DEPTH_NAME}.pbi")); // Small circles setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_SMALL); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r1_no_clip.${BIT_DEPTH_NAME}.pbi")); setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1); graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_SMALL); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r1_clip_xy.${BIT_DEPTH_NAME}.pbi")); setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 1); graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_SMALL); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r1_clip_nxny.${BIT_DEPTH_NAME}.pbi")); // Testing of the special cases for radius: // Radius of 3 - starting point for calculated edges setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_MIN_CALCULATED); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r3_no_clip.${BIT_DEPTH_NAME}.pbi")); // Radius of 2 - ending point for precomputed edges setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_MAX_PRECOMPUTED); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r2_no_clip.${BIT_DEPTH_NAME}.pbi")); // No circle setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_NONE); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r0_no_clip.${BIT_DEPTH_NAME}.pbi")); } void test_graphics_fill_circle_${BIT_DEPTH_NAME}__offset_layer(void) { GContext ctx; Layer layer; test_graphics_context_init(&ctx, fb); layer_init(&layer, &GRect(10, 15, 28, 28)); layer_set_update_proc(&layer, &inside_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_inside_offset_layer.${BIT_DEPTH_NAME}.pbi")); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &across_x_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_x_offset_layer.${BIT_DEPTH_NAME}.pbi")); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &across_nx_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_nx_offset_layer.${BIT_DEPTH_NAME}.pbi")); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &across_y_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_y_offset_layer.${BIT_DEPTH_NAME}.pbi")); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &across_ny_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_ny_offset_layer.${BIT_DEPTH_NAME}.pbi")); } #define OFFSET_RECT_NO_CLIP GRect(10, 10, 40, 50) #define OFFSET_RECT_CLIP_XY GRect(10, 10, 30, 40) #define OFFSET_RECT_CLIP_NXNY GRect(0, 0, 30, 40) #define CENTER_OF_OFFSET_RECT GPoint(10, 15) #define CENTER_OF_OFFSET_RECT_NXNY GPoint(0, 5) void test_graphics_fill_circle_${BIT_DEPTH_NAME}__offset_layer_aa(void) { GContext ctx; test_graphics_context_init(&ctx, fb); // Big circles setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 1); graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_BIG); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r16_no_clip.${BIT_DEPTH_NAME}.pbi")); setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 1); graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_BIG); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r16_clip_xy.${BIT_DEPTH_NAME}.pbi")); setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 1); graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT_NXNY, RADIUS_BIG); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r16_clip_nxny.${BIT_DEPTH_NAME}.pbi")); // Medium circles setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 1); graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_MEDIUM); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r8_no_clip.${BIT_DEPTH_NAME}.pbi")); setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 1); graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_MEDIUM); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r8_clip_xy.${BIT_DEPTH_NAME}.pbi")); setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 1); graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT_NXNY, RADIUS_MEDIUM); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r8_clip_nxny.${BIT_DEPTH_NAME}.pbi")); // Small circles setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 1); graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_SMALL); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r1_no_clip.${BIT_DEPTH_NAME}.pbi")); setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 1); graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_SMALL); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r1_clip_xy.${BIT_DEPTH_NAME}.pbi")); setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 1); graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT_NXNY, RADIUS_SMALL); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r1_clip_nxny.${BIT_DEPTH_NAME}.pbi")); } void test_graphics_fill_circle_${BIT_DEPTH_NAME}__quadrants_aa(void) { #if PBL_COLOR GContext ctx; test_graphics_context_init(&ctx, fb); typedef struct { char *filename_part; GCornerMask mask; } TestConfig; TestConfig test_config[] = { { .filename_part = "quad_top_left", .mask = GCornerTopLeft, }, { .filename_part = "quad_top_right", .mask = GCornerTopRight, }, { .filename_part = "quad_bottom_right", .mask = GCornerBottomRight, }, { .filename_part = "quad_bottom_left", .mask = GCornerBottomLeft, }, { .filename_part = "quads_top", .mask = GCornersTop, }, { .filename_part = "quads_bottom", .mask = GCornersBottom, }, { .filename_part = "quads_right", .mask = GCornersRight, }, { .filename_part = "quads_left", .mask = GCornersLeft, }, }; // note: not the prettiest, fast a quick way to render all the scenarios Nitin was interested in for (int i = 0; i < ARRAY_LENGTH(test_config); i++) { TestConfig c = test_config[i]; setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); GPoint pt = CENTER_OF_ORIGIN_RECT; // draw multiple quads with different radiuses for (int r = 1; r <= 15; r++) { graphics_internal_circle_quadrant_fill_aa(&ctx, pt, r, c.mask); // center point follows a grid pt.x += 30; if (pt.x > 120) { pt.x = CENTER_OF_ORIGIN_RECT.x; pt.y += 30; } } // construct file name and create meaningful assert description char filename[100]; snprintf(filename, sizeof(filename), "fill_circle_offset_aa_%s.${BIT_DEPTH_NAME}.pbi", c.filename_part); cl_check_(gbitmap_pbi_eq(&ctx.dest_bitmap, filename), filename); } #endif } void test_graphics_fill_circle_${BIT_DEPTH_NAME}__color(void) { GContext ctx; Layer layer; test_graphics_context_init(&ctx, fb); layer_init(&layer, &GRect(0, 0, 28, 28)); layer_set_update_proc(&layer, &inside_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_inside_origin_layer.${BIT_DEPTH_NAME}.pbi")); layer_set_update_proc(&layer, &white_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(framebuffer_is_empty("white_over_black", ctx.parent_framebuffer, GColorWhite)); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &inside_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_inside_origin_layer.${BIT_DEPTH_NAME}.pbi")); layer_set_update_proc(&layer, &clear_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(framebuffer_is_empty("clear_over_black", ctx.parent_framebuffer, GColorWhite)); } #define TO_TRIG(deg) (((deg) * TRIG_MAX_ANGLE) / 360) void test_graphics_fill_circle_${BIT_DEPTH_NAME}__radial(void){ GContext ctx; test_graphics_context_init(&ctx, fb); // Pacman uint32_t angle_end = TRIG_MAX_ANGLE + (TRIG_MAX_ANGLE / 8); uint32_t angle_start = (TRIG_MAX_ANGLE / 8) * 3; setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); graphics_fill_radial_internal(&ctx, CENTER_OF_ORIGIN_RECT, 0, RADIUS_BIG, angle_start, angle_end); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_origin_aa_pacman.${BIT_DEPTH_NAME}.pbi")); // Letter C setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); graphics_fill_radial_internal(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, RADIUS_BIG, angle_start, angle_end); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_origin_aa_letter_c.${BIT_DEPTH_NAME}.pbi")); // Negative angles - uses same resource image as result should be identical angle_start -= TRIG_MAX_ANGLE; angle_end -= TRIG_MAX_ANGLE; // Pacman setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); graphics_fill_radial_internal(&ctx, CENTER_OF_ORIGIN_RECT, 0, RADIUS_BIG, angle_start, angle_end); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_origin_aa_pacman.${BIT_DEPTH_NAME}.pbi")); // Letter C setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); graphics_fill_radial_internal(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, RADIUS_BIG, angle_start, angle_end); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_origin_aa_letter_c.${BIT_DEPTH_NAME}.pbi")); // table with most popular angles to test typedef struct { char *filename_part; uint32_t angle; } TestAngles; TestAngles test_angles[] = { { .filename_part = "__1_degrees", .angle = TRIG_MAX_ANGLE / 360, }, { .filename_part = "__6_degrees", .angle = TRIG_MAX_ANGLE / 60, }, { .filename_part = "_30_degrees", .angle = TRIG_MAX_ANGLE / 12, }, { .filename_part = "_45_degrees", .angle = TRIG_MAX_ANGLE / 8, }, { .filename_part = "_90_degrees", .angle = TRIG_MAX_ANGLE / 4, }, { .filename_part = "181_degrees", .angle = TRIG_MAX_ANGLE / 2 + TRIG_MAX_ANGLE / 360, } }; for (int i = 0; i < ARRAY_LENGTH(test_angles); i++) { setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); GPoint pt = CENTER_OF_ORIGIN_RECT; uint16_t inner_radius = 0; uint16_t outer_radius = 10; for (int r = 1; r <= 8; r++) { graphics_fill_radial_internal(&ctx, pt, inner_radius, outer_radius, 0, test_angles[i].angle); inner_radius += 1; outer_radius += 3; pt.x += outer_radius * 2; if (pt.x > 120) { pt.x = CENTER_OF_ORIGIN_RECT.x; pt.y += outer_radius * 2; } } char filename[100]; snprintf(filename, sizeof(filename), "fill_radial_offset_aa_end_angle_%s.${BIT_DEPTH_NAME}.pbi", test_angles[i].filename_part); cl_check_(gbitmap_pbi_eq(&ctx.dest_bitmap, filename), filename); } for (int i = 0; i < ARRAY_LENGTH(test_angles); i++) { setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); GPoint pt = CENTER_OF_ORIGIN_RECT; uint16_t inner_radius = 0; uint16_t outer_radius = 10; for (int r = 1; r <= 8; r++) { graphics_fill_radial_internal(&ctx, pt, inner_radius, outer_radius, test_angles[i].angle, TRIG_MAX_ANGLE); inner_radius += 1; outer_radius += 3; pt.x += outer_radius * 2; if (pt.x > 120) { pt.x = CENTER_OF_ORIGIN_RECT.x; pt.y += outer_radius * 2; } } char filename[100]; snprintf(filename, sizeof(filename), "fill_radial_offset_aa_start_angle_%s.${BIT_DEPTH_NAME}.pbi", test_angles[i].filename_part); cl_check_(gbitmap_pbi_eq(&ctx.dest_bitmap, filename), filename); } // table with radiuses typedef struct { char *filename_part; int radius; } TestRadiuses; TestRadiuses test_radiuses[] = { { .filename_part = "_inner_0", .radius = 0, }, { .filename_part = "_inner_20", .radius = 15, } }; // table with quadrants typedef struct { char *filename_part; int angle_start; int angle_end; } TestQuadrants; TestQuadrants test_quadrants[] = { { .filename_part = "_part", .angle_start = (TO_TRIG(-45)), .angle_end = (TO_TRIG(-45)), }, { .filename_part = "_two_parts", .angle_start = 0, .angle_end = 0, }, { .filename_part = "_quadrant_and_two_parts", .angle_start = 0, .angle_end = (TO_TRIG(90)), } }; #if PBL_COLOR //Colors table GColor colors[4] = { GColorBlack, GColorRed, GColorBlue, GColorGreen, }; #endif uint16_t outer_radius = 30; int32_t twelveth_of_angle = TRIG_MAX_ANGLE / 12; int32_t quarter_of_angle = TRIG_MAX_ANGLE / 4; GPoint center = GPoint(72, 84); // Cases for quadrant joints for (int i = 0; i < ARRAY_LENGTH(test_radiuses); i++) { for (int j = 0; j < ARRAY_LENGTH(test_quadrants); j++) { setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); int offset_angle = 0; for (int r = 0; r < 4; r++) { int offset = (((r + 1) % 4 < 2) ? -20 : 20); GPoint pt = GPoint(center.x + ((r % 2 == 0) ? 0 : offset), center.y + ((r % 2 == 0) ? offset * 2 : 0)); #if PBL_COLOR graphics_context_set_fill_color(&ctx, colors[r]); #endif graphics_fill_radial_internal(&ctx, pt, test_radiuses[i].radius, outer_radius, test_quadrants[j].angle_start + offset_angle - twelveth_of_angle, test_quadrants[j].angle_end + offset_angle + twelveth_of_angle); offset_angle += quarter_of_angle; } char filename[100]; snprintf(filename, sizeof(filename), "fill_radial_aa_joints_%s%s.${BIT_DEPTH_NAME}.pbi", test_radiuses[i].filename_part, test_quadrants[j].filename_part); cl_check_(gbitmap_pbi_eq(&ctx.dest_bitmap, filename), filename); } } } void test_graphics_fill_circle_${BIT_DEPTH_NAME}__radial_precise(void){ GContext ctx; test_graphics_context_init(&ctx, fb); // letter C uint32_t angle_end = TRIG_MAX_ANGLE + (TRIG_MAX_ANGLE / 8); uint32_t angle_start = (TRIG_MAX_ANGLE / 8) * 3; GPointPrecise center = GPointPrecise(CENTER_OF_ORIGIN_RECT.x * 8, CENTER_OF_ORIGIN_RECT.y * 8); Fixed_S16_3 radius_inner = (Fixed_S16_3){.integer = RADIUS_MEDIUM}; Fixed_S16_3 radius_outer = (Fixed_S16_3){.integer = RADIUS_BIG}; // Drawing setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); graphics_fill_radial_precise_internal(&ctx, center, radius_inner, radius_outer, angle_start, angle_end); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_origin_aa_precise_letter_c.${BIT_DEPTH_NAME}.pbi")); //Make the points utilise precision powers center.x.raw_value += 4; center.y.raw_value += 4; radius_inner.raw_value += 4; radius_outer.raw_value += 4; // Drawing setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); graphics_fill_radial_precise_internal(&ctx, center, radius_inner, radius_outer, angle_start, angle_end); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_origin_aa_precise_halfs_letter_c.${BIT_DEPTH_NAME}.pbi")); } typedef struct { char *filename_part; int angle_start; int angle_end; } TestRadialAnglesConfigs; TestRadialAnglesConfigs test_radial_angles[] = { { .filename_part = "_part", .angle_start = (TO_TRIG(-45)), .angle_end = (TO_TRIG(-45)), }, { .filename_part = "_two_parts", .angle_start = 0, .angle_end = 0, }, { .filename_part = "_quadrant_and_two_parts", .angle_start = 0, .angle_end = (TO_TRIG(90)), }, { .filename_part = "_two_quadrants_and_two_parts", .angle_start = 0, .angle_end = (TO_TRIG(180)), }, { .filename_part = "_three_quadrants_and_two_parts", .angle_start = 0, .angle_end = (TO_TRIG(270)), }, { .filename_part = "_full", .angle_start = 0, .angle_end = TRIG_MAX_ANGLE, } }; typedef struct { char *filename_part; int16_t width; int16_t height; GOvalScaleMode scale_mode; int16_t inset; } TestRadialGRectConfigs; TestRadialGRectConfigs test_radial_rects[] = { { .filename_part = "_even_rect_fill", .width = 40, .height = 40, .scale_mode = GOvalScaleModeFillCircle, .inset = 10, }, { .filename_part = "_even_rect_fit", .width = 40, .height = 40, .scale_mode = GOvalScaleModeFitCircle, .inset = 10, }, { .filename_part = "_odd_rect_fill", .width = 41, .height = 41, .scale_mode = GOvalScaleModeFillCircle, .inset = 10, }, { .filename_part = "_odd_rect_fit", .width = 41, .height = 41, .scale_mode = GOvalScaleModeFitCircle, .inset = 10, }, { .filename_part = "_even_rect_fill_no_middle", .width = 40, .height = 40, .scale_mode = GOvalScaleModeFillCircle, .inset = 20, }, { .filename_part = "_even_rect_fit_no_middle", .width = 40, .height = 40, .scale_mode = GOvalScaleModeFitCircle, .inset = 20, }, { .filename_part = "_odd_rect_fill_no_middle", .width = 41, .height = 41, .scale_mode = GOvalScaleModeFillCircle, .inset = 21, }, { .filename_part = "_odd_rect_fit_no_middle", .width = 41, .height = 41, .scale_mode = GOvalScaleModeFitCircle, .inset = 21, }, }; void prv_draw_radial_in_rect_debugged(GContext *ctx, int16_t width, int16_t height, GOvalScaleMode scale_mode, int16_t inset, int32_t angle_start, int32_t angle_end){ int offset_angle = 0; int32_t twelveth_of_angle = TRIG_MAX_ANGLE / 12; GPoint center = GPoint(72, 84); for (int i=0; i<4; i++) { GRect rect = GRect(center.x - (width/2), center.y - (height / 2), width, height); int offset_x = (((i + 1) % 4 < 2) ? -(width * 2 / 3) : (width * 2 / 3)); int offset_y = ((i % 4 < 2) ? -(height * 2 / 3) : (height * 2 / 3)); rect.origin.x += offset_x; rect.origin.y += offset_y; const GRect bigger_rect = GRect(rect.origin.x - 1, rect.origin.y - 1, rect.size.w + 2, rect.size.h + 2); graphics_context_set_stroke_color(ctx, PBL_IF_COLOR_ELSE(GColorGreen, GColorWhite)); graphics_draw_rect(ctx, &rect); graphics_context_set_stroke_color(ctx, PBL_IF_COLOR_ELSE(GColorRed, GColorBlack)); graphics_draw_rect(ctx, &bigger_rect); graphics_context_set_stroke_color(ctx, GColorBlack); graphics_fill_radial(ctx, rect, scale_mode, inset, angle_start + offset_angle - twelveth_of_angle, angle_end + offset_angle + twelveth_of_angle); offset_angle += TRIG_MAX_ANGLE / 4; } } void test_graphics_fill_circle_${BIT_DEPTH_NAME}__radial_grect(void) { GContext ctx; test_graphics_context_init(&ctx, fb); for (int rect_id=0; rect_id < ARRAY_LENGTH(test_radial_rects); rect_id++) { for (int angle_id=0; angle_id < ARRAY_LENGTH(test_radial_angles); angle_id++) { setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); prv_draw_radial_in_rect_debugged(&ctx, test_radial_rects[rect_id].width, test_radial_rects[rect_id].height, test_radial_rects[rect_id].scale_mode, test_radial_rects[rect_id].inset, test_radial_angles[angle_id].angle_start, test_radial_angles[angle_id].angle_end); char filename[100]; snprintf(filename, sizeof(filename), "fill_radial%s%s.${BIT_DEPTH_NAME}.pbi", test_radial_rects[rect_id].filename_part, test_radial_angles[angle_id].filename_part); cl_check_(gbitmap_pbi_eq(&ctx.dest_bitmap, filename), filename); } } } void prv_test_dithering_color(GContext *ctx, GColor color) { const uint32_t angle_end = DEG_TO_TRIGANGLE(405); const uint32_t angle_start = DEG_TO_TRIGANGLE(135); GRect rect = GRect(10, 10, 40, 40); setup_test_aa_sw(ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1); graphics_context_set_fill_color(ctx, color); // Circle graphics_fill_radial(ctx, rect, GOvalScaleModeFitCircle, 50, 0, TRIG_MAX_ANGLE); // Pacman rect.origin.y += 50; graphics_fill_radial(ctx, rect, GOvalScaleModeFitCircle, 50, angle_start, angle_end); // Letter C rect.origin.y += 50; graphics_fill_radial(ctx, rect, GOvalScaleModeFitCircle, 10, angle_start, angle_end);; // Following SHOULD NOT be dithered into grayscale: graphics_context_set_stroke_color(ctx, color); // Circle: GPoint point = GPoint(95, 56); graphics_draw_circle(ctx, point, 20); // Line: GPoint p1 = GPoint(75, 140); GPoint p2 = GPoint(115, 140); graphics_draw_line(ctx, p1, p2); // Stroked Circle: graphics_context_set_stroke_width(ctx, 12); point.y += 52; graphics_draw_circle(ctx, point, 20); // Stroked Line: p1.y += 10; p2.y += 10; graphics_draw_line(ctx, p1, p2); // Stroked line turning into circle: point.y = 20; graphics_draw_circle(ctx, point, 5); } void test_graphics_fill_circle_${BIT_DEPTH_NAME}__dithering_grayscale(void) { GContext ctx; test_graphics_context_init(&ctx, fb); prv_test_dithering_color(&ctx, GColorWhite); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_dither_GColorWhite.${BIT_DEPTH_NAME}.pbi")); test_graphics_context_init(&ctx, fb); prv_test_dithering_color(&ctx, GColorLightGray); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_dither_GColorLightGray.${BIT_DEPTH_NAME}.pbi")); test_graphics_context_init(&ctx, fb); prv_test_dithering_color(&ctx, GColorDarkGray); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_dither_GColorDarkGray.${BIT_DEPTH_NAME}.pbi")); test_graphics_context_init(&ctx, fb); prv_test_dithering_color(&ctx, GColorBlack); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_dither_GColorBlack.${BIT_DEPTH_NAME}.pbi")); test_graphics_context_init(&ctx, fb); prv_test_dithering_color(&ctx, GColorJaegerGreen); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_dither_GColorJaegerGreen.${BIT_DEPTH_NAME}.pbi")); test_graphics_context_init(&ctx, fb); prv_test_dithering_color(&ctx, GColorOrange); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_dither_GColorOrange.${BIT_DEPTH_NAME}.pbi")); }