pebble/tests/fw/graphics/test_graphics_context_mask.c
2025-01-27 11:38:16 -08:00

699 lines
29 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 "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 <stdio.h>
// 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);
}