mirror of
https://github.com/google/pebble.git
synced 2025-03-23 04:02:19 +00:00
699 lines
29 KiB
C
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);
|
|
}
|