/* * 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_private.h" #include "applib/graphics/graphics_private_raw.h" #include "applib/graphics/graphics_private_raw_mask.h" #include "applib/graphics/gtypes.h" #include "applib/graphics/framebuffer.h" #include "applib/ui/window_private.h" #include "applib/ui/layer.h" #include "util/graphics.h" #include "util/trig.h" #include "clar.h" #include "util.h" #include // Helper Includes //////////////////////////////////// #include "test_graphics.h" #include "test_graphics_mask.h" #include "8bit/test_framebuffer.h" // Stubs //////////////////////////////////// #include "graphics_common_stubs.h" //////////////////////////////////////////////////////////////////////////////////////////////////// // Setup and Teardown //////////////////////////////////////////////////////////////////////////////////////////////////// static FrameBuffer *s_fb = NULL; static GContext *s_ctx = NULL; static GBitmap *s_dest_bitmap = NULL; // Setup void test_graphics_context_mask__initialize(void) { s_fb = malloc(sizeof(*s_fb)); s_ctx = malloc(sizeof(*s_ctx)); framebuffer_init(s_fb, &(GSize) {DISP_COLS, DISP_ROWS}); test_graphics_context_init(s_ctx, s_fb); framebuffer_clear(s_fb); } void test_graphics_context_mask__cleanup(void) { free(s_ctx); free(s_fb); gbitmap_destroy(s_dest_bitmap); s_dest_bitmap = NULL; } //////////////////////////////////////////////////////////////////////////////////////////////////// // Helpers //////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////// // COMMON HELPERS // /////////////////////////////////////// #define CHECK_EXPECTED_TEST_IMAGE(ctx) (cl_check(gbitmap_pbi_eq(&ctx->dest_bitmap, TEST_PBI_FILE))) static const int16_t num_pixels_per_mask_value = 5; static const int16_t num_mask_values = 4; static const int16_t num_src_colors = 256; // 8-bit color static const int16_t num_dest_colors = 64; // Framebuffer ignores alpha, so only 6-bit color static void prv_prepare_canvas(GContext *ctx, GSize desired_size) { s_dest_bitmap = gbitmap_create_blank(desired_size, GBitmapFormat8Bit); ctx->dest_bitmap = *s_dest_bitmap; ctx->draw_state.clip_box.size = desired_size; ctx->draw_state.drawing_box.size = desired_size; } /////////////////////////////////////// // RECORDING HORIZONTAL LINE HELPERS // /////////////////////////////////////// static void prv_prepare_canvas_for_hline_recording_test(GContext *ctx) { const GSize bitmap_size = GSize(num_mask_values * num_pixels_per_mask_value, num_src_colors); prv_prepare_canvas(ctx, bitmap_size); } static GDrawMask *prv_create_mask_for_hline_recording_test(GContext *ctx) { // The initial transparency doesn't really matter since we're about to overwrite it const bool transparent = false; GDrawMask *mask = graphics_context_mask_create(ctx, transparent); cl_assert(mask); const GSize mask_size = ctx->dest_bitmap.bounds.size; for (int16_t x = 0; x < mask_size.w; x++) { const uint8_t mask_pixel_value = (uint8_t)(x / num_pixels_per_mask_value); for (int16_t y = 0; y < mask_size.h; y++) { test_graphics_context_mask_set_value_for_coordinate(ctx, mask, mask_pixel_value, GPoint(x, y)); } } return mask; } typedef void (*HorizontalClippingMaskRecordFunc)(GContext *ctx, int16_t y, int16_t x1, int16_t x2, GColor color); static void prv_mask_record_hline_test_pattern(HorizontalClippingMaskRecordFunc record_func) { GContext *ctx = s_ctx; prv_prepare_canvas_for_hline_recording_test(ctx); graphics_context_set_antialiased(ctx, true); GDrawMask *mask = prv_create_mask_for_hline_recording_test(ctx); cl_assert(graphics_context_mask_record(ctx, mask)); for (int16_t y = 0; y < num_src_colors; y++) { GColor src_color = (GColor) { .argb = (uint8_t)y }; for (int mask_value_index = 0; mask_value_index < num_mask_values; mask_value_index++) { int16_t x1 = (int16_t)(mask_value_index * num_pixels_per_mask_value); int16_t x2 = (int16_t)(x1 + num_pixels_per_mask_value - 1); record_func(ctx, y, x1, x2, src_color); } } cl_assert(graphics_context_mask_record(ctx, NULL)); test_graphics_context_mask_debug(ctx, mask); graphics_context_mask_destroy(ctx, mask); } /////////////////////////////////////// // RECORDING VERTICAL LINE HELPERS // /////////////////////////////////////// static void prv_prepare_canvas_for_vline_recording_test(GContext *ctx) { const GSize bitmap_size = GSize(num_src_colors, num_mask_values * num_pixels_per_mask_value); prv_prepare_canvas(ctx, bitmap_size); } static GDrawMask *prv_create_mask_for_vline_recording_test(GContext *ctx) { // The initial transparency doesn't really matter since we're about to overwrite it const bool transparent = false; GDrawMask *mask = graphics_context_mask_create(ctx, transparent); cl_assert(mask); const GSize mask_size = ctx->dest_bitmap.bounds.size; for (int16_t y = 0; y < mask_size.h; y++) { const uint8_t mask_pixel_value = (uint8_t)(y / num_pixels_per_mask_value); for (int16_t x = 0; x < mask_size.w; x++) { test_graphics_context_mask_set_value_for_coordinate(ctx, mask, mask_pixel_value, GPoint(x, y)); } } return mask; } typedef void (*VerticalClippingMaskRecordFunc)(GContext *ctx, int16_t x, int16_t y1, int16_t y2, GColor color); static void prv_mask_record_vline_test_pattern(VerticalClippingMaskRecordFunc record_func) { GContext *ctx = s_ctx; prv_prepare_canvas_for_vline_recording_test(ctx); graphics_context_set_antialiased(ctx, true); GDrawMask *mask = prv_create_mask_for_vline_recording_test(ctx); cl_assert(graphics_context_mask_record(ctx, mask)); for (int16_t x = 0; x < num_src_colors; x++) { GColor src_color = (GColor) { .argb = (uint8_t)x }; for (int mask_value_index = 0; mask_value_index < num_mask_values; mask_value_index++) { int16_t y1 = (int16_t)(mask_value_index * num_pixels_per_mask_value); int16_t y2 = (int16_t)(y1 + num_pixels_per_mask_value - 1); record_func(ctx, x, y1, y2, src_color); } } cl_assert(graphics_context_mask_record(ctx, NULL)); test_graphics_context_mask_debug(ctx, mask); graphics_context_mask_destroy(ctx, mask); } /////////////////////////////////////// // APPLYING HORIZONTAL LINE HELPERS // /////////////////////////////////////// static const int16_t hline_applying_test_column_width = num_mask_values * num_pixels_per_mask_value; static void prv_prepare_canvas_for_hline_applying_test(GContext *ctx) { const GSize bitmap_size = GSize(num_dest_colors * hline_applying_test_column_width, num_src_colors); prv_prepare_canvas(ctx, bitmap_size); // Fill the canvas so each column (of width hline_applying_test_column_width) is set to one of the // different possible dest_colors for (int16_t y = 0; y < bitmap_size.h; y++) { for (int16_t column_index = 0; column_index < num_dest_colors; column_index++) { const int16_t starting_x = column_index * hline_applying_test_column_width; for (int16_t x = starting_x; x < starting_x + hline_applying_test_column_width; x++) { ctx->draw_state.stroke_color = (GColor) { .argb = (uint8_t)(column_index | 0b11000000) }; graphics_draw_pixel(ctx, GPoint(x, y)); } } } } static GDrawMask *prv_create_mask_for_hline_applying_test(GContext *ctx) { // The initial transparency doesn't really matter since we're about to overwrite it const bool transparent = false; GDrawMask *mask = graphics_context_mask_create(ctx, transparent); cl_assert(mask); const GSize mask_size = ctx->dest_bitmap.bounds.size; for (int16_t x = 0; x < mask_size.w; x++) { const uint8_t mask_pixel_value = (uint8_t)((x % hline_applying_test_column_width) / num_pixels_per_mask_value); for (int16_t y = 0; y < mask_size.h; y++) { test_graphics_context_mask_set_value_for_coordinate(ctx, mask, mask_pixel_value, GPoint(x, y)); } } return mask; } typedef void (*HorizontalClippingMaskApplyFunc)(GContext *ctx, int16_t y, int16_t x1, int16_t x2, GColor color); static void prv_mask_apply_hline_test_pattern(HorizontalClippingMaskApplyFunc apply_func) { GContext *ctx = s_ctx; prv_prepare_canvas_for_hline_applying_test(ctx); graphics_context_set_antialiased(ctx, true); GDrawMask *mask = prv_create_mask_for_hline_applying_test(ctx); cl_assert(graphics_context_mask_use(ctx, mask)); for (int16_t y = 0; y < num_src_colors; y++) { GColor src_color = (GColor) { .argb = (uint8_t)y }; for (int dest_color_index = 0; dest_color_index < num_dest_colors; dest_color_index++) { for (int mask_value_index = 0; mask_value_index < num_mask_values; mask_value_index++) { int16_t x1 = (int16_t)((dest_color_index * hline_applying_test_column_width) + (mask_value_index * num_pixels_per_mask_value)); int16_t x2 = (int16_t)(x1 + num_pixels_per_mask_value - 1); apply_func(ctx, y, x1, x2, src_color); } } } graphics_context_mask_destroy(ctx, mask); } ////////////////////////////////////// // APPLYING VERTICAL LINE HELPERS // ////////////////////////////////////// static const int16_t vline_applying_test_row_height = hline_applying_test_column_width; static void prv_prepare_canvas_for_vline_applying_test(GContext *ctx) { const GSize bitmap_size = GSize(num_src_colors, num_dest_colors * hline_applying_test_column_width); prv_prepare_canvas(ctx, bitmap_size); // Fill the canvas so each row (of width vline_applying_test_row_height) is set to one of the // different possible dest_colors for (int16_t x = 0; x < bitmap_size.w; x++) { for (int16_t row_index = 0; row_index < num_dest_colors; row_index++) { const int16_t starting_y = row_index * vline_applying_test_row_height; for (int16_t y = starting_y; y < starting_y + vline_applying_test_row_height; y++) { ctx->draw_state.stroke_color = (GColor) { .argb = (uint8_t)(row_index | 0b11000000) }; graphics_draw_pixel(ctx, GPoint(x, y)); } } } } static GDrawMask *prv_create_mask_for_vline_applying_test(GContext *ctx) { // The initial transparency doesn't really matter since we're about to overwrite it const bool transparent = false; GDrawMask *mask = graphics_context_mask_create(ctx, transparent); cl_assert(mask); const GSize mask_size = ctx->dest_bitmap.bounds.size; for (int16_t y = 0; y < mask_size.h; y++) { const uint8_t mask_pixel_value = (uint8_t)((y % vline_applying_test_row_height) / num_pixels_per_mask_value); for (int16_t x = 0; x < mask_size.w; x++) { test_graphics_context_mask_set_value_for_coordinate(ctx, mask, mask_pixel_value, GPoint(x, y)); } } return mask; } typedef void (*VerticalClippingMaskApplyFunc)(GContext *ctx, int16_t x, int16_t y1, int16_t y2, GColor color); static void prv_mask_apply_vline_test_pattern(VerticalClippingMaskApplyFunc apply_func) { GContext *ctx = s_ctx; prv_prepare_canvas_for_vline_applying_test(ctx); graphics_context_set_antialiased(ctx, true); GDrawMask *mask = prv_create_mask_for_vline_applying_test(ctx); cl_assert(graphics_context_mask_use(ctx, mask)); for (int16_t x = 0; x < num_src_colors; x++) { GColor src_color = (GColor) { .argb = (uint8_t)x }; for (int dest_color_index = 0; dest_color_index < num_dest_colors; dest_color_index++) { for (int mask_value_index = 0; mask_value_index < num_mask_values; mask_value_index++) { int16_t y1 = (int16_t)((dest_color_index * vline_applying_test_row_height) + (mask_value_index * num_pixels_per_mask_value)); int16_t y2 = (int16_t)(y1 + num_pixels_per_mask_value - 1); apply_func(ctx, x, y1, y2, src_color); } } } graphics_context_mask_destroy(ctx, mask); } //////////////////////////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////// // RECORDING HORIZONTAL LINE TESTS // ///////////////////////////////////// // These tests initialize a mask that has 4 columns, each of width `num_pixels_per_mask_value`. // Each of these columns in the mask is initialized to have one of the 4 possible mask values. Then, // the test iterates over the mask's 256 rows, each of which uses a different src color // (256 possibilities for 8-bit color) to draw a horizontal line (using the test's specified raw // drawing function) for each column into the mask (recording). void prv_mask_recording_assign_horizontal_line(GContext *ctx, int16_t y, Fixed_S16_3 x1, Fixed_S16_3 x2, GColor color); static void prv_hline_pattern_record_assign_horizontal_line_raw(GContext *ctx, int16_t y, int16_t x1, int16_t x2, GColor color) { // x1 and x2 here will be the integer start/end of the line, so we need to adjust them so we see // the same blending on the first and last pixel x2--; const Fixed_S16_3 x1_fixed = (Fixed_S16_3) { .integer = x1, .fraction = 4, }; const Fixed_S16_3 x2_fixed = (Fixed_S16_3) { .integer = x2, .fraction = 4, }; prv_mask_recording_assign_horizontal_line(ctx, y, x1_fixed, x2_fixed, color); } void test_graphics_context_mask__record_assign_horizontal_line_raw(void) { prv_mask_record_hline_test_pattern(prv_hline_pattern_record_assign_horizontal_line_raw); CHECK_EXPECTED_TEST_IMAGE(s_ctx); }; void prv_mask_recording_blend_horizontal_line_raw(GContext *ctx, int16_t y, int16_t x1, int16_t x2, GColor color); void test_graphics_context_mask__record_blend_horizontal_line_raw(void) { prv_mask_record_hline_test_pattern(prv_mask_recording_blend_horizontal_line_raw); CHECK_EXPECTED_TEST_IMAGE(s_ctx); }; void prv_mask_recording_assign_horizontal_line_delta_raw(GContext *ctx, int16_t y, Fixed_S16_3 x1, Fixed_S16_3 x2, uint8_t left_aa_offset, uint8_t right_aa_offset, int16_t clip_box_min_x, int16_t clip_box_max_x, GColor color); static void prv_hline_pattern_record_assign_horizontal_line_delta_raw(GContext *ctx, int16_t y, int16_t x1, int16_t x2, GColor color) { const Fixed_S16_3 x1_fixed = (Fixed_S16_3) { .integer = x1 }; Fixed_S16_3 x2_fixed = (Fixed_S16_3) { .integer = x2 }; const uint8_t gradient_width = (uint8_t)((x2 - x1) / 2); x2_fixed.integer -= gradient_width; const int16_t clip_box_min_x = ctx->draw_state.clip_box.origin.x; const int16_t clip_box_max_x = (int16_t)(grect_get_max_x(&ctx->draw_state.clip_box) - 1); prv_mask_recording_assign_horizontal_line_delta_raw(ctx, y, x1_fixed, x2_fixed, gradient_width, gradient_width, clip_box_min_x, clip_box_max_x, color); } void test_graphics_context_mask__record_assign_horizontal_line_delta_raw(void) { prv_mask_record_hline_test_pattern(prv_hline_pattern_record_assign_horizontal_line_delta_raw); CHECK_EXPECTED_TEST_IMAGE(s_ctx); }; ///////////////////////////////////// // RECORDING VERTICAL LINE TESTS // ///////////////////////////////////// // These tests initialize a mask that has 4 rows, each of height `num_pixels_per_mask_value`. // Each of these rows in the mask is initialized to have one of the 4 possible mask values. Then, // the test iterates over the mask's 256 columns, each of which uses a different src color // (256 possibilities for 8-bit color) to draw a vertical line (using the test's specified raw // drawing function) for each row into the mask (recording). void prv_mask_recording_assign_vertical_line(GContext *ctx, int16_t x, Fixed_S16_3 y1, Fixed_S16_3 y2, GColor color); static void prv_vline_pattern_record_assign_vertical_line(GContext *ctx, int16_t x, int16_t y1, int16_t y2, GColor color) { // y1 and y2 here will be the integer start/end of the line, so we need to adjust them so we see // the same blending on the first and last pixel y2--; const Fixed_S16_3 y1_fixed = (Fixed_S16_3) { .integer = y1, .fraction = 4, }; const Fixed_S16_3 y2_fixed = (Fixed_S16_3) { .integer = y2, .fraction = 4, }; prv_mask_recording_assign_vertical_line(ctx, x, y1_fixed, y2_fixed, color); } void test_graphics_context_mask__record_assign_vertical_line_raw(void) { prv_mask_record_vline_test_pattern(prv_vline_pattern_record_assign_vertical_line); CHECK_EXPECTED_TEST_IMAGE(s_ctx); }; void prv_mask_recording_blend_vertical_line_raw(GContext *ctx, int16_t x, int16_t y1, int16_t y2, GColor color); void test_graphics_context_mask__record_blend_vertical_line_raw(void) { prv_mask_record_vline_test_pattern(prv_mask_recording_blend_vertical_line_raw); CHECK_EXPECTED_TEST_IMAGE(s_ctx); }; ///////////////////////////////////// // APPLYING HORIZONTAL LINE TESTS // ///////////////////////////////////// // These tests initialize a mask that has 4 * 64 columns, each of width `num_pixels_per_mask_value`. // Each quad grouping of these columns in the mask is initialized to have one of the 4 possible mask // values. Additionally, the test initializes the framebuffer to have 64 columns, each of width // `4 * num_pixels_per_mask_value`, where each column is one of the 64 possible dest_colors (the // framebuffer ignores alpha so 6-bit color). Then, the test activates the mask and iterates over // the framebuffer's 256 rows, each of which uses a different src color // (256 possibilities for 8-bit color) to draw a horizontal line (using the test's specified raw // drawing function) for each `num_pixels_per_mask_value`-wide column. void prv_assign_horizontal_line_raw(GContext *ctx, int16_t y, Fixed_S16_3 x1, Fixed_S16_3 x2, GColor color); static void prv_hline_pattern_apply_assign_horizontal_line_raw(GContext *ctx, int16_t y, int16_t x1, int16_t x2, GColor color) { // x1 and x2 here will be the integer start/end of the line, so we need to adjust them so we see // the same blending on the first and last pixel x2--; const Fixed_S16_3 x1_fixed = (Fixed_S16_3) { .integer = x1, .fraction = 4, }; const Fixed_S16_3 x2_fixed = (Fixed_S16_3) { .integer = x2, .fraction = 4, }; prv_assign_horizontal_line_raw(ctx, y, x1_fixed, x2_fixed, color); } void test_graphics_context_mask__apply_assign_horizontal_line_raw(void) { prv_mask_apply_hline_test_pattern(prv_hline_pattern_apply_assign_horizontal_line_raw); CHECK_EXPECTED_TEST_IMAGE(s_ctx); }; void prv_blend_horizontal_line_raw(GContext *ctx, int16_t y, int16_t x1, int16_t x2, GColor color); void test_graphics_context_mask__apply_blend_horizontal_line_raw(void) { prv_mask_apply_hline_test_pattern(prv_blend_horizontal_line_raw); CHECK_EXPECTED_TEST_IMAGE(s_ctx); }; void prv_assign_horizontal_line_delta_raw(GContext *ctx, int16_t y, Fixed_S16_3 x1, Fixed_S16_3 x2, uint8_t left_aa_offset, uint8_t right_aa_offset, int16_t clip_box_min_x, int16_t clip_box_max_x, GColor color); static void prv_hline_pattern_apply_assign_horizontal_line_delta_raw(GContext *ctx, int16_t y, int16_t x1, int16_t x2, GColor color) { // FIXME PBL-34552: This test produces an incorrect image, see JIRA const Fixed_S16_3 x1_fixed = (Fixed_S16_3) { .integer = x1 }; Fixed_S16_3 x2_fixed = (Fixed_S16_3) { .integer = x2 }; const uint8_t gradient_width = (uint8_t)((x2 - x1) / 2); x2_fixed.integer -= gradient_width; const int16_t clip_box_min_x = ctx->draw_state.clip_box.origin.x; const int16_t clip_box_max_x = (int16_t)(grect_get_max_x(&ctx->draw_state.clip_box) - 1); prv_assign_horizontal_line_delta_raw(ctx, y, x1_fixed, x2_fixed, gradient_width, gradient_width, clip_box_min_x, clip_box_max_x, color); } void test_graphics_context_mask__apply_assign_horizontal_line_delta_raw(void) { prv_mask_apply_hline_test_pattern(prv_hline_pattern_apply_assign_horizontal_line_delta_raw); CHECK_EXPECTED_TEST_IMAGE(s_ctx); }; ////////////////////////////////// // APPLYING VERTICAL LINE TESTS // ////////////////////////////////// // These tests initialize a mask that has 4 * 64 rows, each of height // `vline_applying_test_row_height`. Each quad grouping of these rows in the mask is initialized to // have one of the 4 possible mask values. Additionally, the test initializes the framebuffer to // have 64 rows, each of height `4 * num_pixels_per_mask_value`, where each row is one of the 64 // possible dest_colors (the framebuffer ignores alpha so 6-bit color). Then, the test activates the // mask and iterates over the framebuffer's 256 columns, each of which uses a different src color // (256 possibilities for 8-bit color) to draw a vertical line (using the test's specified raw // drawing function) for each `num_pixels_per_mask_value`-tall row. void prv_assign_vertical_line_raw(GContext *ctx, int16_t x, Fixed_S16_3 y1, Fixed_S16_3 y2, GColor color); static void prv_vline_pattern_apply_assign_vertical_line(GContext *ctx, int16_t x, int16_t y1, int16_t y2, GColor color) { // y1 and y2 here will be the integer start/end of the line, so we need to adjust them so we see // the same blending on the first and last pixel y2--; const Fixed_S16_3 y1_fixed = (Fixed_S16_3) { .integer = y1, .fraction = 4, }; const Fixed_S16_3 y2_fixed = (Fixed_S16_3) { .integer = y2, .fraction = 4, }; prv_assign_vertical_line_raw(ctx, x, y1_fixed, y2_fixed, color); } void test_graphics_context_mask__apply_assign_vertical_line_raw(void) { prv_mask_apply_vline_test_pattern(prv_vline_pattern_apply_assign_vertical_line); CHECK_EXPECTED_TEST_IMAGE(s_ctx); }; void prv_blend_vertical_line_raw(GContext *ctx, int16_t x, int16_t y1, int16_t y2, GColor color); void test_graphics_context_mask__apply_blend_vertical_line_raw(void) { // FIXME PBL-34141: This test produces an incorrect image, see JIRA prv_mask_apply_vline_test_pattern(prv_blend_vertical_line_raw); CHECK_EXPECTED_TEST_IMAGE(s_ctx); }; ///////////////// // BASIC TESTS // ///////////////// // These tests test the basic functionality of the clipping mask API. static void prv_verify_mask_pixel_values(const GContext *ctx, const GDrawMask *mask, uint8_t expected_value) { // Assumes rectangular framebuffer cl_assert(ctx->dest_bitmap.info.format == GBitmapFormat8Bit); const GSize framebuffer_size = ctx->dest_bitmap.bounds.size; for (int16_t x = 0; x < framebuffer_size.w; x++) { for (int16_t y = 0; y < framebuffer_size.h; y++) { cl_assert_equal_i(test_graphics_context_mask_get_value_for_coordinate(ctx, mask, GPoint(x, y)), expected_value); } } } void test_graphics_context_mask__basic_create(void) { GContext *ctx = s_ctx; // Should be safe to call create with NULL values cl_assert_equal_p(graphics_context_mask_create(NULL, NULL), NULL); GDrawMask *transparent_mask = graphics_context_mask_create(ctx, true /* transparent */); cl_assert(transparent_mask); // Verify all mask pixels are initialized to be transparent (0) prv_verify_mask_pixel_values(ctx, transparent_mask, 0); graphics_context_mask_destroy(ctx, transparent_mask); GDrawMask *opaque_mask = graphics_context_mask_create(ctx, false /* transparent */); cl_assert(opaque_mask); // Verify all mask pixels are initialized to be opaque (3) prv_verify_mask_pixel_values(ctx, opaque_mask, 3); graphics_context_mask_destroy(ctx, opaque_mask); } //extern const GDrawRawImplementation g_mask_recording_draw_implementation; void test_graphics_context_mask__basic_record(void) { GContext *ctx = s_ctx; // Should be safe to call record with NULL values, will return false cl_assert(!graphics_context_mask_record(NULL, NULL)); // Should start with default draw implementation cl_assert_equal_p(ctx->draw_state.draw_implementation, &g_default_draw_implementation); GDrawMask *mask1 = graphics_context_mask_create(ctx, true /* transparent */); cl_assert(mask1); cl_assert(graphics_context_mask_record(ctx, mask1)); // Should have switched to mask recording draw implementation cl_assert_equal_p(ctx->draw_state.draw_implementation, &g_mask_recording_draw_implementation); // Should have attached mask1 to GContext cl_assert_equal_p(ctx->draw_state.draw_mask, mask1); GDrawMask *mask2 = graphics_context_mask_create(ctx, true /* transparent */); cl_assert(mask2); cl_assert(graphics_context_mask_record(ctx, mask2)); // Should still be on mask recording draw implementation cl_assert_equal_p(ctx->draw_state.draw_implementation, &g_mask_recording_draw_implementation); // Should now have mask2 attached to GContext cl_assert_equal_p(ctx->draw_state.draw_mask, mask2); // Calling record with NULL should reset draw impl to default and mask in GContext to NULL cl_assert(graphics_context_mask_record(ctx, NULL)); cl_assert_equal_p(ctx->draw_state.draw_implementation, &g_default_draw_implementation); cl_assert_equal_p(ctx->draw_state.draw_mask, NULL); graphics_context_mask_destroy(ctx, mask1); graphics_context_mask_destroy(ctx, mask2); } void test_graphics_context_mask__basic_use(void) { GContext *ctx = s_ctx; // Should be safe to call use with NULL values, will return false cl_assert(!graphics_context_mask_use(NULL, NULL)); // Should start with default draw implementation cl_assert_equal_p(ctx->draw_state.draw_implementation, &g_default_draw_implementation); GDrawMask *mask1 = graphics_context_mask_create(ctx, true /* transparent */); cl_assert(mask1); cl_assert(graphics_context_mask_use(ctx, mask1)); // Should still be on default draw implementation cl_assert_equal_p(ctx->draw_state.draw_implementation, &g_default_draw_implementation); // Should have attached mask1 to GContext cl_assert_equal_p(ctx->draw_state.draw_mask, mask1); GDrawMask *mask2 = graphics_context_mask_create(ctx, true /* transparent */); cl_assert(mask2); cl_assert(graphics_context_mask_use(ctx, mask2)); // Should still be on default draw implementation cl_assert_equal_p(ctx->draw_state.draw_implementation, &g_default_draw_implementation); // Should now have mask2 attached to GContext cl_assert_equal_p(ctx->draw_state.draw_mask, mask2); // Calling use with NULL should reset draw impl to default and mask in GContext to NULL cl_assert(graphics_context_mask_use(ctx, NULL)); cl_assert_equal_p(ctx->draw_state.draw_implementation, &g_default_draw_implementation); cl_assert_equal_p(ctx->draw_state.draw_mask, NULL); graphics_context_mask_destroy(ctx, mask1); graphics_context_mask_destroy(ctx, mask2); } void test_graphics_context_mask__basic_destroy(void) { // Should be safe to call destroy on NULL values graphics_context_mask_destroy(NULL, NULL); }