/* * 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/framebuffer.h" #include "applib/ui/window_private.h" #include "applib/ui/layer.h" #include "clar.h" #include "util.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_draw_pixel_${BIT_DEPTH_NAME}__initialize(void) { fb = malloc(sizeof(FrameBuffer)); framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS}); } // Teardown void test_graphics_draw_pixel_${BIT_DEPTH_NAME}__cleanup(void) { free(fb); } // Tests //////////////////////////////////// void inside_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_stroke_color(ctx, GColorBlack); graphics_draw_pixel(ctx, GPoint(5, 5)); } void white_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_stroke_color(ctx, GColorWhite); graphics_draw_pixel(ctx, GPoint(5, 5)); } void clear_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_stroke_color(ctx, GColorClear); graphics_draw_pixel(ctx, GPoint(5, 5)); } void outside_x_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_stroke_color(ctx, GColorBlack); graphics_draw_pixel(ctx, GPoint(15, 5)); } void outside_nx_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_stroke_color(ctx, GColorBlack); graphics_draw_pixel(ctx, GPoint(-5, 5)); } void outside_y_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_stroke_color(ctx, GColorBlack); graphics_draw_pixel(ctx, GPoint(5, 15)); } void outside_ny_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_stroke_color(ctx, GColorBlack); graphics_draw_pixel(ctx, GPoint(5, -5)); } void outside_x_y_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_stroke_color(ctx, GColorBlack); graphics_draw_pixel(ctx, GPoint(15, 15)); } void outside_nx_y_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_stroke_color(ctx, GColorBlack); graphics_draw_pixel(ctx, GPoint(-5, 15)); } void outside_x_ny_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_stroke_color(ctx, GColorBlack); graphics_draw_pixel(ctx, GPoint(15, -5)); } void outside_nx_ny_layer_update_callback(Layer* me, GContext* ctx) { graphics_context_set_stroke_color(ctx, GColorBlack); graphics_draw_pixel(ctx, GPoint(-5, -5)); } void test_graphics_draw_pixel_${BIT_DEPTH_NAME}__origin_layer(void) { GContext ctx; Layer layer; test_graphics_context_init(&ctx, fb); layer_init(&layer, &GRect(0, 0, 10, 10)); layer_set_update_proc(&layer, &inside_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_pixel_inside_origin_layer.${BIT_DEPTH_NAME}.pbi")); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &outside_x_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(framebuffer_is_empty("outside_x_origin_layer", ctx.parent_framebuffer, GColorWhite)); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &outside_y_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(framebuffer_is_empty("outside_y_origin_layer", ctx.parent_framebuffer, GColorWhite)); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &outside_x_y_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(framebuffer_is_empty("outside_x_y_origin_layer", ctx.parent_framebuffer, GColorWhite)); } void test_graphics_draw_pixel_${BIT_DEPTH_NAME}__offset_layer(void) { GContext ctx; Layer layer; test_graphics_context_init(&ctx, fb); layer_init(&layer, &GRect(10, 10, 10, 10)); layer_set_update_proc(&layer, &inside_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_pixel_inside_offset_layer.${BIT_DEPTH_NAME}.pbi")); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &outside_x_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(framebuffer_is_empty("outside_x_offset_layer", ctx.parent_framebuffer, GColorWhite)); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &outside_nx_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(framebuffer_is_empty("outside_nx_offset_layer", ctx.parent_framebuffer, GColorWhite)); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &outside_y_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(framebuffer_is_empty("outside_y_offset_layer", ctx.parent_framebuffer, GColorWhite)); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &outside_ny_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(framebuffer_is_empty("outside_ny_offset_layer", ctx.parent_framebuffer, GColorWhite)); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &outside_x_y_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(framebuffer_is_empty("outside_x_y_offset_layer", ctx.parent_framebuffer, GColorWhite)); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &outside_nx_y_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(framebuffer_is_empty("outside_nx_y_offset_layer", ctx.parent_framebuffer, GColorWhite)); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &outside_x_ny_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(framebuffer_is_empty("outside_x_ny_offset_layer", ctx.parent_framebuffer, GColorWhite)); test_graphics_context_reset(&ctx, fb); layer_set_update_proc(&layer, &outside_nx_ny_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(framebuffer_is_empty("outside_nx_ny_offset_layer", ctx.parent_framebuffer, GColorWhite)); } void test_graphics_draw_pixel_${BIT_DEPTH_NAME}__clear(void) { GContext ctx; Layer layer; test_graphics_context_init(&ctx, fb); layer_init(&layer, &GRect(0, 0, 10, 10)); layer_set_update_proc(&layer, &inside_layer_update_callback); layer_render_tree(&layer, &ctx); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_pixel_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, "draw_pixel_inside_origin_layer.${BIT_DEPTH_NAME}.pbi")); layer_set_update_proc(&layer, &clear_layer_update_callback); layer_render_tree(&layer, &ctx); #if SCREEN_COLOR_DEPTH_BITS == 8 cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_pixel_clear.8bit.pbi")); #else cl_check(framebuffer_is_empty("clear_over_black", ctx.parent_framebuffer, GColorWhite)); #endif } #define BOX_OFFSET_X 8 #define BOX_OFFSET_Y 4 #define COLUMN_OFFSET_X 32 // Draws an 8x4 rectangle starting a point p static void prv_draw_box(GContext *ctx, GPoint p) { graphics_draw_pixel(ctx, GPoint(p.x + 0, p.y)); graphics_draw_pixel(ctx, GPoint(p.x + 1, p.y)); graphics_draw_pixel(ctx, GPoint(p.x + 2, p.y)); graphics_draw_pixel(ctx, GPoint(p.x + 3, p.y)); graphics_draw_pixel(ctx, GPoint(p.x + 4, p.y)); graphics_draw_pixel(ctx, GPoint(p.x + 5, p.y)); graphics_draw_pixel(ctx, GPoint(p.x + 6, p.y)); graphics_draw_pixel(ctx, GPoint(p.x + 7, p.y)); graphics_draw_pixel(ctx, GPoint(p.x + 0, p.y + 1)); graphics_draw_pixel(ctx, GPoint(p.x + 1, p.y + 1)); graphics_draw_pixel(ctx, GPoint(p.x + 2, p.y + 1)); graphics_draw_pixel(ctx, GPoint(p.x + 3, p.y + 1)); graphics_draw_pixel(ctx, GPoint(p.x + 4, p.y + 1)); graphics_draw_pixel(ctx, GPoint(p.x + 5, p.y + 1)); graphics_draw_pixel(ctx, GPoint(p.x + 6, p.y + 1)); graphics_draw_pixel(ctx, GPoint(p.x + 7, p.y + 1)); graphics_draw_pixel(ctx, GPoint(p.x + 0, p.y + 2)); graphics_draw_pixel(ctx, GPoint(p.x + 1, p.y + 2)); graphics_draw_pixel(ctx, GPoint(p.x + 2, p.y + 2)); graphics_draw_pixel(ctx, GPoint(p.x + 3, p.y + 2)); graphics_draw_pixel(ctx, GPoint(p.x + 4, p.y + 2)); graphics_draw_pixel(ctx, GPoint(p.x + 5, p.y + 2)); graphics_draw_pixel(ctx, GPoint(p.x + 6, p.y + 2)); graphics_draw_pixel(ctx, GPoint(p.x + 7, p.y + 2)); graphics_draw_pixel(ctx, GPoint(p.x + 0, p.y + 3)); graphics_draw_pixel(ctx, GPoint(p.x + 1, p.y + 3)); graphics_draw_pixel(ctx, GPoint(p.x + 2, p.y + 3)); graphics_draw_pixel(ctx, GPoint(p.x + 3, p.y + 3)); graphics_draw_pixel(ctx, GPoint(p.x + 4, p.y + 3)); graphics_draw_pixel(ctx, GPoint(p.x + 5, p.y + 3)); graphics_draw_pixel(ctx, GPoint(p.x + 6, p.y + 3)); graphics_draw_pixel(ctx, GPoint(p.x + 7, p.y + 3)); } // Draws two columns of colors (first 32 colors in first column, second 32 colors in second column) // Offsets the two color columns based on input transparency static void prv_draw_boxes(GContext *ctx, uint8_t transparency) { int column = 3 - transparency; for (uint8_t color_index = 0; color_index < 64; color_index++) { uint8_t color_col = (color_index < 32) ? 0 : 1; ctx->draw_state.stroke_color = (GColor) { .argb = ((transparency << 6) | color_index) }; prv_draw_box(ctx, GPoint((4 + (BOX_OFFSET_X * color_col) + (COLUMN_OFFSET_X * column)), (BOX_OFFSET_Y + ((color_index - (32 * color_col)) * 4 )))); } } #define ORIGIN_RECT_NO_CLIP GRect(0, 0, 144, 168) void test_graphics_draw_pixel_8bit__transparent(void) { GContext ctx; test_graphics_context_init(&ctx, fb); setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1); graphics_context_set_fill_color(&ctx, GColorBlack); graphics_fill_rect(&ctx, &ORIGIN_RECT_NO_CLIP); // No transparency prv_draw_boxes(&ctx, 3); // 33% transparency prv_draw_boxes(&ctx, 2); // 66% transparency - should draw nothing according to current implementation prv_draw_boxes(&ctx, 1); // 100% transparency - should draw nothing according to current implementation prv_draw_boxes(&ctx, 0); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_pixel_transparent.8bit.pbi")); } #define CLIP_RECT_DRAW_BOX GRect(10, 10, 40, 40) #define CLIP_RECT_CLIP_BOX GRect(10, 10, 20, 20) static void prv_draw_pixels(GContext *ctx, bool aa, uint8_t sw) { test_graphics_context_reset(ctx, fb); setup_test_aa_sw(ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, aa, sw); graphics_context_set_stroke_color(ctx, GColorBlack); // Left boundary graphics_draw_pixel(ctx, GPoint(-1, 5)); graphics_draw_pixel(ctx, GPoint(0, 10)); graphics_draw_pixel(ctx, GPoint(1, 15)); // Right boundary graphics_draw_pixel(ctx, GPoint(19, 5)); graphics_draw_pixel(ctx, GPoint(20, 10)); graphics_draw_pixel(ctx, GPoint(21, 15)); // Top boundary graphics_draw_pixel(ctx, GPoint(5, -1)); graphics_draw_pixel(ctx, GPoint(10, 0)); graphics_draw_pixel(ctx, GPoint(15, 1)); // Bottom boundary graphics_draw_pixel(ctx, GPoint(5, 19)); graphics_draw_pixel(ctx, GPoint(10, 20)); graphics_draw_pixel(ctx, GPoint(15, 21)); } void test_graphics_draw_pixel_${BIT_DEPTH_NAME}__clipping_rect(void) { GContext ctx; test_graphics_context_init(&ctx, fb); // Draw pixels around boundaries of clipping box - AA false, SW 1 prv_draw_pixels(&ctx, false, 1); cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_pixel_clip_rect.${BIT_DEPTH_NAME}.pbi")); }