Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson
2025-01-27 11:38:16 -08:00
committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions
+195
View File
@@ -0,0 +1,195 @@
/*
* 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.
*/
#pragma once
#include "applib/graphics/gtypes.h"
#include "applib/graphics/framebuffer.h"
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "../test_graphics.h"
typedef struct stat STAT_T;
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
#define IMAGE_DWORDS_PER_LINE 6
static void print_framebuffer_as_literal(const char* unit_name, const FrameBuffer* framebuffer, int col, int row, int cols, int rows) {
printf("\n-- %s image --\n", unit_name);
printf(" uint32_t image[] = {\n");
int num_words = 0;
for (int j = row; j < row+rows && j < FrameBuffer_MaxY; j++) {
bool j_more = j+1 < row+rows && j+1 < FrameBuffer_MaxY;
for (int i = col; i < col+cols && i < FRAMEBUFFER_WORDS_PER_ROW; i++) {
bool i_more = i+1 < col+cols && i+1 < FRAMEBUFFER_WORDS_PER_ROW;
num_words++;
if (num_words % IMAGE_DWORDS_PER_LINE == 1) { printf(" "); }
printf("0x%x", framebuffer->buffer[j*FRAMEBUFFER_WORDS_PER_ROW+i]);
if (i_more || j_more) {
printf(",");
if (num_words % IMAGE_DWORDS_PER_LINE == 0) { printf("\n"); }
}
}
}
printf("\n };\n");
}
static void fread_pbi_header(FILE* file, GBitmap* bitmap) {
fread(&bitmap->row_size_bytes, sizeof(bitmap->row_size_bytes), 1, file);
fread(&bitmap->info_flags, sizeof(bitmap->info_flags), 1, file);
fread(&bitmap->bounds, sizeof(bitmap->bounds), 1, file);
}
static uint32_t* fread_pbi(FILE* file, GBitmap* bitmap) {
fread_pbi_header(file, bitmap);
size_t image_size = bitmap->row_size_bytes/4 * bitmap->bounds.size.h;
uint32_t* buffer = bitmap->addr = (uint32_t*) malloc(image_size*4);
if (buffer) {
fread(buffer, 4, image_size, file);
return buffer;
}
return NULL;
}
static uint32_t* read_pbi(const char* filename, GBitmap* bitmap) {
char res_path[strlen(CLAR_FIXTURE_PATH) + 1 + strlen(GRAPHICS_FIXTURE_PATH) + 1 + strlen(filename) + 1];
sprintf(res_path, "%s/%s/%s", CLAR_FIXTURE_PATH, GRAPHICS_FIXTURE_PATH, filename);
FILE* file = fopen(res_path, "r");
if (!file) {
printf("\ncould not open %s for reading\n", res_path);
return NULL;
}
uint32_t* buffer = fread_pbi(file, bitmap);
fclose(file);
return buffer;
}
static void free_pbi(GBitmap* bitmap) {
if (bitmap->addr) {
free(bitmap->addr);
}
}
static void fwrite_screenshot_from_framebuffer(FILE* file, const FrameBuffer* framebuffer) {
uint16_t row_size_bytes = (FrameBuffer_MaxX + 31) / 32 * 4;
uint16_t info_flags = 1 << 1;
GRect bounds = GRect(0, 0, FrameBuffer_MaxX, FrameBuffer_MaxY);
fwrite(&row_size_bytes, sizeof(row_size_bytes), 1, file);
fwrite(&info_flags, sizeof(info_flags), 1, file);
fwrite(&bounds, sizeof(bounds), 1, file);
fwrite(framebuffer->buffer, sizeof(framebuffer->buffer[0]), row_size_bytes/4*FrameBuffer_MaxY, file);
}
static bool write_screenshot_from_framebuffer(const char* filename, const FrameBuffer* framebuffer) {
FILE* file = fopen(filename, "w");
if (!file) {
printf("\ncould not open %s for writing a screenshot\n", filename);
return false;
}
fwrite_screenshot_from_framebuffer(file, framebuffer);
fclose(file);
return true;
}
static bool framebuffer_eq_image_raw(const FrameBuffer* framebuffer, uint32_t* image, int col, int row, int cols, int rows) {
for (int j = row; j < row+rows && j < FrameBuffer_MaxY; j++) {
for (int i = col; i < col+cols && i < FRAMEBUFFER_WORDS_PER_ROW; i++) {
int fb_index = j*FRAMEBUFFER_WORDS_PER_ROW+i;
int img_index = (j-row)*cols+(i-col);
uint32_t number_of_bits = col < IMAGE_DWORDS_PER_LINE-1 ? 32 : DISP_COLS % 32;
uint32_t mask = number_of_bits == 32 ? -1 : ((1 << number_of_bits) - 1);
uint32_t fb_part = mask & framebuffer->buffer[fb_index];
uint32_t img_part = mask & image[img_index];
if (fb_part != img_part) {
printf("\nframebuffer[%d] != image[%d], (0x%x, 0x%x) col=%d row=%d\n",
fb_index, img_index, framebuffer->buffer[fb_index], image[img_index], i, j);
return false;
}
}
}
return true;
}
static bool framebuffer_eq_image(const char* unit_name, const FrameBuffer* framebuffer, uint32_t* image, int col, int row, int cols, int rows) {
#ifndef TEST_GRAPHICS_SILENT
print_framebuffer_as_literal(unit_name, framebuffer, col, row, cols, rows);
#endif
return framebuffer_eq_image_raw(framebuffer, image, col, row, cols, rows);
}
static bool framebuffer_eq(const char* unit_name, const FrameBuffer* framebuffer, FrameBuffer* other) {
return framebuffer_eq_image(unit_name, framebuffer, other->buffer, 0, 0, FRAMEBUFFER_WORDS_PER_ROW, FrameBuffer_MaxY);
}
static bool framebuffer_eq_screenshot_raw(const FrameBuffer* framebuffer, const char* filename) {
GBitmap bitmap;
FILE* file = fopen(filename, "r");
if (!file) {
printf("\nfailed to open %s\n", filename);
return false;
}
fread_pbi_header(file, &bitmap);
uint32_t buffer[bitmap.row_size_bytes/4 * bitmap.bounds.size.h];
fread(buffer, 1, sizeof(buffer), file);
fclose(file);
if (!framebuffer_eq_image_raw(framebuffer, buffer, 0, 0, bitmap.row_size_bytes/4, bitmap.bounds.size.h)) {
printf("\ndoes not match screenshot %s\n", filename);
return false;
}
return true;
}
static bool framebuffer_eq_screenshot(const FrameBuffer* framebuffer, const char* filename) {
char ref_path[strlen(CLAR_FIXTURE_PATH) + 1 + strlen(GRAPHICS_FIXTURE_PATH) + 1 + strlen(filename) + 1];
sprintf(ref_path, "%s/%s/%s", CLAR_FIXTURE_PATH, GRAPHICS_FIXTURE_PATH, filename);
STAT_T st;
if (stat(ref_path, &st) != 0 || !framebuffer_eq_screenshot_raw(framebuffer, ref_path)) {
char out_path[strlen(GRAPHICS_FIXTURE_OUT_PATH) + 1 + strlen(filename) + 1];
sprintf(out_path, "%s/%s", GRAPHICS_FIXTURE_OUT_PATH,filename);
write_screenshot_from_framebuffer(out_path, framebuffer);
char cwd[PATH_MAX];
if (!getcwd(cwd, PATH_MAX)) {
printf("\nfailed to get working directory\n");
return false;
}
printf("\ngenerated %s/%s\n", cwd, out_path);
return false;
}
return true;
}
static bool framebuffer_is_empty(const char* unit_name, FrameBuffer* framebuffer, GColor color) {
for (int j = 0; j < FrameBuffer_MaxY; j++) {
for (int i = 0; i < FRAMEBUFFER_WORDS_PER_ROW; i++) {
int fb_index = j*FRAMEBUFFER_WORDS_PER_ROW+i;
if (framebuffer->buffer[fb_index] != (gcolor_equal(color, GColorBlack) ? 0 : 0xffffffff)) {
printf("\nframebuffer[%d] is not empty(%d), has 0x%x, col=%d row=%d\n",
fb_index, color.argb, framebuffer->buffer[fb_index], i, j);
return false;
}
}
}
return true;
}
+216
View File
@@ -0,0 +1,216 @@
/*
* 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.
*/
#pragma once
#include "applib/graphics/gtypes.h"
#include "applib/graphics/framebuffer.h"
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "../test_graphics.h"
#include "../util.h"
typedef struct stat STAT_T;
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
#define IMAGE_DWORDS_PER_LINE 6
static void print_framebuffer_as_literal(const char* unit_name, const FrameBuffer* framebuffer,
int col, int row, int cols, int rows) {
printf("\n-- %s image --\n", unit_name);
printf(" uint8_t image[] = {\n");
for (int y = row; y < row+rows && y < FrameBuffer_MaxY; ++y) {
for (int x = col; x < col + cols && x < FrameBuffer_MaxX; ++x) {
printf("0x%x", framebuffer->buffer[y*FRAMEBUFFER_BYTES_PER_ROW + x]);
}
printf("\n");
}
printf("\n };\n");
}
static void fread_pbi_header(FILE* file, GBitmap* bitmap) {
fread(&bitmap->row_size_bytes, sizeof(bitmap->row_size_bytes), 1, file);
fread(&bitmap->info_flags, sizeof(bitmap->info_flags), 1, file);
fread(&bitmap->bounds, sizeof(bitmap->bounds), 1, file);
bitmap->info.format = GBitmapFormat1Bit;
}
static uint32_t* fread_pbi(FILE* file, GBitmap* bitmap) {
fread_pbi_header(file, bitmap);
size_t image_size = bitmap->row_size_bytes/4 * bitmap->bounds.size.h;
uint32_t* buffer = bitmap->addr = (uint32_t*) malloc(image_size*4);
if (buffer) {
fread(buffer, 4, image_size, file);
return buffer;
}
return NULL;
}
static uint32_t* read_pbi(const char* filename, GBitmap* bitmap) {
char res_path[strlen(CLAR_FIXTURE_PATH) + 1 + strlen(GRAPHICS_FIXTURE_PATH) + 1 + strlen(filename) + 1];
sprintf(res_path, "%s/%s/%s", CLAR_FIXTURE_PATH, GRAPHICS_FIXTURE_PATH, filename);
FILE* file = fopen(res_path, "r");
if (!file) {
printf("\ncould not open %s for reading\n", res_path);
return NULL;
}
uint32_t* buffer = fread_pbi(file, bitmap);
fclose(file);
return buffer;
}
static void free_pbi(GBitmap* bitmap) {
if (bitmap->addr) {
free(bitmap->addr);
}
}
static void fwrite_screenshot_from_framebuffer(FILE* file, const FrameBuffer* framebuffer) {
uint16_t row_size_bytes = FRAMEBUFFER_BYTES_PER_ROW;
uint16_t info_flags = 1 << 1;
GRect bounds = GRect(0, 0, FrameBuffer_MaxX, FrameBuffer_MaxY);
fwrite(&row_size_bytes, sizeof(row_size_bytes), 1, file);
fwrite(&info_flags, sizeof(info_flags), 1, file);
fwrite(&bounds, sizeof(bounds), 1, file);
fwrite(framebuffer->buffer, sizeof(framebuffer->buffer[0]), FRAMEBUFFER_SIZE_BYTES, file);
}
static bool write_screenshot_from_framebuffer(const char* filename, const FrameBuffer* framebuffer) {
FILE* file = fopen(filename, "w");
if (!file) {
printf("\ncould not open %s for writing a screenshot\n", filename);
return false;
}
fwrite_screenshot_from_framebuffer(file, framebuffer);
fclose(file);
return true;
}
static bool framebuffer_eq_image_raw(const FrameBuffer* framebuffer, uint8_t* image, int col,
int row, int cols, int rows) {
const uint8_t *fb = framebuffer->buffer;
for (int y = row; y < row+rows && y < FrameBuffer_MaxY; ++y) {
for (int x = col; x < col+cols && x < FrameBuffer_MaxX; ++x) {
int fb_index = y * FRAMEBUFFER_BYTES_PER_ROW + x;
int img_index = (y-row)*FRAMEBUFFER_BYTES_PER_ROW + (x-col);
if (fb[fb_index] != image[img_index]) {
printf("\nframebuffer[%d] != image[%d], (0x%x, 0x%x) col=%d row=%d\n",
fb_index, img_index, fb[fb_index], image[img_index], x, y);
return false;
}
}
}
return true;
}
static bool framebuffer_eq_image(const char* unit_name, const FrameBuffer* framebuffer,
uint8_t* image, int col, int row, int cols, int rows) {
#ifndef TEST_GRAPHICS_SILENT
print_framebuffer_as_literal(unit_name, framebuffer, col, row, cols, rows);
#endif
return framebuffer_eq_image_raw(framebuffer, image, col, row, cols, rows);
}
static bool framebuffer_eq(const char* unit_name, const FrameBuffer* framebuffer,
FrameBuffer* other) {
return framebuffer_eq_image(unit_name, framebuffer, other->buffer, 0, 0,
FRAMEBUFFER_BYTES_PER_ROW, FrameBuffer_MaxY);
}
static void convert_to_8bit_image(const uint32_t *in, GBitmap *bmp, uint8_t *out) {
for (int y = 0; y < bmp->bounds.size.h; ++y) {
for (int x = 0; x < bmp->bounds.size.w; ++x) {
int in_idx = y * (bmp->row_size_bytes/4) + (x / 32);
int bit_idx = x % 32;
bool set = in[in_idx] & (1 << bit_idx);
int out_idx = y * bmp->bounds.size.w + x;
out[out_idx] = (set) ? GColorWhite.argb : GColorBlack.argb;
}
}
}
static bool framebuffer_eq_screenshot_raw(const FrameBuffer* framebuffer, const char* filename) {
GBitmap bitmap;
FILE* file = fopen(filename, "r");
if (!file) {
printf("\nfailed to open %s\n", filename);
return false;
}
fread_pbi_header(file, &bitmap);
uint32_t buffer[bitmap.row_size_bytes/4 * bitmap.bounds.size.h];
fread(buffer, 1, sizeof(buffer), file);
fclose(file);
uint8_t image[bitmap.bounds.size.w * bitmap.bounds.size.h];
convert_to_8bit_image(buffer, &bitmap, image);
if (!framebuffer_eq_image_raw(framebuffer, image, 0, 0, bitmap.row_size_bytes,
bitmap.bounds.size.h)) {
bitmap.info.format = GBitmapFormat8Bit;
bitmap.row_size_bytes = FRAMEBUFFER_BYTES_PER_ROW;
printf("\ndoes not match screenshot %s\n", filename);
printf("Good:\n");
bitmap.addr = &image;
print_bitmap(&bitmap);
printf("Bad:\n");
bitmap.addr = (void*)&framebuffer->buffer;
print_bitmap(&bitmap);
return false;
}
return true;
}
static bool framebuffer_eq_screenshot(const FrameBuffer* framebuffer, const char* filename) {
char ref_path[strlen(CLAR_FIXTURE_PATH) + 1 + strlen(GRAPHICS_FIXTURE_PATH) + 1 + strlen(filename) + 1];
sprintf(ref_path, "%s/%s/%s", CLAR_FIXTURE_PATH, GRAPHICS_FIXTURE_PATH, filename);
STAT_T st;
if (stat(ref_path, &st) != 0 || !framebuffer_eq_screenshot_raw(framebuffer, ref_path)) {
char out_path[strlen(GRAPHICS_FIXTURE_OUT_PATH) + 1 + strlen(filename) + 1];
sprintf(out_path, "%s/%s", GRAPHICS_FIXTURE_OUT_PATH,filename);
write_screenshot_from_framebuffer(out_path, framebuffer);
char cwd[PATH_MAX];
if (!getcwd(cwd, PATH_MAX)) {
printf("\nfailed to get working directory\n");
return false;
}
printf("\ngenerated %s/%s\n", cwd, out_path);
return false;
}
return true;
}
static bool framebuffer_is_empty(const char* unit_name, FrameBuffer* framebuffer, GColor color) {
for (int j = 0; j < FrameBuffer_MaxY; j++) {
for (int i = 0; i < FRAMEBUFFER_BYTES_PER_ROW; i++) {
int fb_index = j*FRAMEBUFFER_BYTES_PER_ROW + i;
if (framebuffer->buffer[fb_index] != color.argb) {
printf("\nframebuffer[%d] is not empty(%d), has 0x%x, col=%d row=%d\n",
fb_index, color.argb, framebuffer->buffer[fb_index], i, j);
return false;
}
}
}
return true;
}
+29
View File
@@ -0,0 +1,29 @@
/*
* 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 "stubs_app_state.h"
#include "stubs_heap.h"
#include "stubs_language_ui.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_print.h"
#include "stubs_resources.h"
#include "stubs_serial.h"
#include "stubs_syscalls.h"
#include "stubs_ui_window.h"
#include "stubs_unobstructed_area.h"
+920
View File
@@ -0,0 +1,920 @@
/*
* 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/bitblt.h"
#include "applib/graphics/bitblt_private.h"
#include "applib/graphics/8_bit/framebuffer.h"
#include "clar.h"
#include "util.h"
#include <string.h>
#include <stdio.h>
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
#include "test_graphics.h"
static GContext ctx;
static FrameBuffer framebuffer;
// Helpers
////////////////////////////////////
static void prv_set_opacity(GBitmap *bmp, uint8_t opacity) {
GRect bounds = bmp->bounds;
for (uint32_t idx = 0; idx < bounds.size.w * bounds.size.h; idx++) {
((GColor *)(bmp->addr + idx))->a = opacity;
}
}
static GColor prv_next_color(GColor color) {
GColor8 result = (GColor8){.argb = (color.argb + 1) % 64};
result.a = color.a;
return result;
}
// Tests
////////////////////////////////////
// setup and teardown
void test_bitblt__initialize(void) {
framebuffer_init(&framebuffer, &(GSize) {DISP_COLS, DISP_ROWS});
test_graphics_context_init(&ctx, &framebuffer);
}
void test_bitblt__cleanup(void) {
}
// Test images reside in "tests/fw/graphics/test_images/".
// The wscript will convert them from PNGs in that directory to PBIs in the build directory.
// Naming conventions of these images tends to be '<test_name>.<bitdepth>.png'.
// For example:
// test_bitblt__8bit_assign would have:
// - test_bitblt__8bit_assign.8bit.png
// - test_bitblt__8bit_assign-expect.8bit.png
// Tests assign, from same size to same size.
// Setup:
// - Source is 10x10, white.
// - Dest is 10x10, green.
// Result:
// - All white.
void test_bitblt__8bit_compop(void) {
GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__8bit_assign.8bit.pbi");
uint8_t dest_data[src_bitmap->bounds.size.w*src_bitmap->bounds.size.h];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = src_bitmap->bounds.size.w,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = src_bitmap->bounds
};
// All compositing modes except GCompOpSet should be the same as GCompAssign
memset(dest_data, GColorGreen.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_assign-expect.8bit.pbi"));
memset(dest_data, GColorGreen.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpAssignInverted, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_assign-expect.8bit.pbi"));
memset(dest_data, GColorGreen.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpOr, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_assign-expect.8bit.pbi"));
memset(dest_data, GColorGreen.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpAnd, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_assign-expect.8bit.pbi"));
memset(dest_data, GColorGreen.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpClear, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_assign-expect.8bit.pbi"));
memset(dest_data, GColorGreen.argb, sizeof(dest_data));
// Update source bitmap to be semi-transparent
uint8_t *src_bitmap_buff = src_bitmap->addr;
for (int index = 0; index < src_bitmap->bounds.size.h * src_bitmap->row_size_bytes; index++) {
src_bitmap_buff[index] = src_bitmap_buff[index] & 0xbf;
}
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpSet, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_set-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Test GCompOpTint, from same size to size.
// Setup:
// - Source is a 10x10 square, white.
// - Destination is either a black or white square.
// Result:
// - When source is transparent or tint color is clear, dest is black.
// - When source is opaque, dest is blended blue
void test_bitblt__8bit_to_8bit_comptint(void) {
GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__8bit_assign.8bit.pbi");
GRect bounds = gbitmap_get_bounds(src_bitmap);
uint8_t dest_data[bounds.size.w * bounds.size.h];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = bounds.size.w,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = src_bitmap->bounds
};
uint8_t expect_bmp_data[bounds.size.w * bounds.size.h];
GBitmap expect_bmp = {
.addr = expect_bmp_data,
.row_size_bytes = bounds.size.w,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = src_bitmap->bounds
};
// Verify that the compositing mode is correctly applied when the source is opaque and
// the tint color is not clear.
memset(dest_data, GColorClear.argb, sizeof(dest_data));
memset(expect_bmp_data, GColorBlue.argb, sizeof(expect_bmp_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpTint, GColorBlue);
cl_assert(gbitmap_eq(&dest_bitmap, &expect_bmp, "test_bitblt__8bit_comptint-expect.8bit.pbi"));
// Rewrite the destination bitmap to be all black.
memset(dest_data, GColorBlack.argb, sizeof(dest_data));
// Verify that if the tint color is clear, than the source bitmap should not affect the destination bitmap
memset(expect_bmp_data, GColorBlack.argb, sizeof(expect_bmp_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpTint, GColorClear);
cl_assert(gbitmap_eq(&dest_bitmap, &expect_bmp, "test_bitblt__8bit_comptint_clear-expect.8bit.pbi"));
// Verify that if the source is transparent, it should not affect the destination bitmap
prv_set_opacity(src_bitmap, 0);
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpTint, GColorRed);
cl_assert(gbitmap_eq(&dest_bitmap, &expect_bmp, "test_bitblt__8bit_comptint_clear-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Tests comptint, multiple tint colors and varying opacity of the source image.
// Setup:
// - Destination is two 128 by 64 blocks expressing the 64 colors; each row expresses
// one of the colors stack vertically.
// - Source is a set of 4 by 4 blocks each with a 1 pixel wide vertical strip of a color
// with an opacity in [0,3] inclusive.
// Result:
// - Destination should be blended properly
void test_bitblt__8bit_comptint_blend(void) {
const uint8_t NUM_COLORS = 64;
const uint8_t WIDTH = NUM_COLORS * 2;
const uint8_t LEGEND_WIDTH = 4;
const uint8_t LEGEND_HEIGHT = 4;
const uint8_t OFFSET = 1;
const uint8_t HEIGHT = WIDTH;
const uint8_t NUM_OPACITIES = 4;
const uint8_t TOTAL_WIDTH = LEGEND_WIDTH * 2 + WIDTH;
const uint8_t TOTAL_HEIGHT = HEIGHT + OFFSET + LEGEND_HEIGHT * 2;
uint8_t dest_data[TOTAL_WIDTH * TOTAL_HEIGHT];
memset(dest_data, GColorWhite.argb, sizeof(dest_data));
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = TOTAL_WIDTH,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = (GRect){GPointZero, (GSize){TOTAL_WIDTH, TOTAL_HEIGHT}}
};
GColor color = (GColor){ .a = 3, .r = 0, .g = 0, .b = 0 };
uint8_t src_data[] = { color.argb };
GBitmap src_bmp = (GBitmap){
.addr = src_data,
.row_size_bytes = 1,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = GRect(0, 0, 1, 1)
};
for (uint8_t offset_y = 0; offset_y < NUM_COLORS; offset_y++) {
const int16_t offset_x = OFFSET + LEGEND_WIDTH + WIDTH;
const int16_t x = LEGEND_WIDTH;
memset(src_data, color.argb, sizeof(src_data));
color = prv_next_color(color);
const int16_t y_upper = LEGEND_HEIGHT + offset_y;
const GRect upper_rect = GRect(x, y_upper, WIDTH, 1);
const GRect upper_left_legend_rect = GRect(0, y_upper, 3, 1);
const GRect upper_right_legend_rect = GRect(offset_x, y_upper, 3, 1);
bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &src_bmp, upper_rect,
GPointZero, GCompOpAssign, GColorWhite);
bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &src_bmp, upper_left_legend_rect,
GPointZero, GCompOpAssign, GColorWhite);
bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &src_bmp, upper_right_legend_rect,
GPointZero, GCompOpAssign, GColorWhite);
const int16_t y_lower = y_upper + LEGEND_HEIGHT + NUM_COLORS + OFFSET;
const GRect lower_rect = GRect(x, y_lower, WIDTH, 1);
const GRect lower_left_legend_rect = GRect(0, y_lower, 3, 1);
const GRect lower_right_legend_rect = GRect(offset_x, y_lower, 3, 1);
bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &src_bmp, lower_rect,
GPointZero, GCompOpAssign, GColorWhite);
bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &src_bmp, lower_left_legend_rect,
GPointZero, GCompOpAssign, GColorWhite);
bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &src_bmp, lower_right_legend_rect,
GPointZero, GCompOpAssign, GColorWhite);
}
// RGB value should be discarded later on adding them here might relveal bugs.
// .a is the important part
GColor8 test_blend_colors[] = {
(GColor8){.a = 0, .r = 3, .g = 2, .b = 1},
(GColor8){.a = 1, .r = 0, .g = 3, .b = 2},
(GColor8){.a = 2, .r = 1, .g = 0, .b = 3},
(GColor8){.a = 3, .r = 2, .g = 1, .b = 0}
};
// Test image with four pixels of all our suported alpha values
GBitmap test_bmp = (GBitmap){
.addr = test_blend_colors,
.row_size_bytes = 4,
.bounds = GRect(0, 0, 4, 1),
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT
};
for (uint8_t rgb_half = 0; rgb_half < NUM_COLORS / 2; rgb_half++) {
const int16_t x = rgb_half * NUM_OPACITIES + LEGEND_WIDTH;
const int8_t legend_height = 3;
// Upper row with destination colors from 0..31
const GColor upper_tint_color = (GColor){.argb = 0b11000000 | rgb_half};
const int16_t y_upper = LEGEND_HEIGHT;
const GRect upper_rect = GRect(x, y_upper, test_bmp.bounds.size.w, NUM_COLORS);
const GRect upper_legend_rect = GRect(x, y_upper - LEGEND_HEIGHT, test_bmp.bounds.size.w, legend_height);
GColor8 upper_legend[4] = { [0 ... 3] = upper_tint_color };
test_bmp.addr = upper_legend;
bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &test_bmp, upper_legend_rect, GPointZero, GCompOpAssign, GColorWhite);
test_bmp.addr = test_blend_colors;
bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &test_bmp, upper_rect, GPointZero, GCompOpTint, upper_tint_color);
// Lower row with destination colors from 31..63
const int16_t y_lower = y_upper + NUM_COLORS + OFFSET + LEGEND_HEIGHT;
const GRect lower_rect = GRect(x, y_lower, test_bmp.bounds.size.w, NUM_COLORS);
const GColor lower_tint_color = (GColor){.argb = 0b11000000 | (rgb_half + (NUM_COLORS / 2))};
const GRect lower_legend_rect = GRect(x, y_lower - LEGEND_HEIGHT, test_bmp.bounds.size.w, legend_height);
GColor8 lower_legend[4] = { [0 ... 3] = lower_tint_color };
test_bmp.addr = lower_legend;
bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &test_bmp, lower_legend_rect, GPointZero, GCompOpAssign, GColorWhite);
test_bmp.addr = test_blend_colors;
bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, &test_bmp, lower_rect, GPointZero, GCompOpTint, lower_tint_color);
}
cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_comptint_blend-expect.8bit.pbi"));
}
// Tests assign, clipping, makes sure in bottom right corner.
// Setup:
// - Source is 10x15, black box ((0, 0), (5, 10)), rest is red.
// - Dest is White, 50x50.
// - Dest offset is set to 5x10 pixels in bottom right corner.
// Result:
// - White, with 5x10 black box in bottom right corner.
void test_bitblt__8bit_clipping(void) {
GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__8bit_clipping.8bit.pbi");
uint8_t dest_data[50*50];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = 50,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { { 0, 0 }, { 50, 50 } }
};
memset(dest_data, GColorWhite.argb, sizeof(dest_data));
GPoint dest_offset = { dest_bitmap.bounds.size.w-5, dest_bitmap.bounds.size.h-10 };
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, dest_offset, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_clipping-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Test horizontal wrapping when dest_rect wider than src_bitmap.
// Setup:
// - Source 15 x 10, each row has the folling pattern:
// - 2px Red
// - 13px Black
// - Dest Green 50x50
// - Dest rect (17, 10) at (0, 0)
// Result:
// - 2px Red
// - 13px Black
// - 2px Red
// - Rest Blue
void test_bitblt__8bit_wrap_x(void) {
GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__8bit_wrap_x.8bit.pbi");
uint8_t dest_data[50*50];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = 50,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { { 0, 0 }, { 50, 50 } }
};
memset(dest_data, GColorBlue.argb, sizeof(dest_data));
// 2 wider than src_bitmap, so 2 columns of red will repeat again.
GRect dest_rect = GRect(0, 0, src_bitmap->bounds.size.w + 2, src_bitmap->bounds.size.h);
bitblt_bitmap_into_bitmap_tiled_8bit_to_8bit(
&dest_bitmap, src_bitmap, dest_rect, GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_wrap_x-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Test vertical wrapping when dest_rect taller than src_bitmap.
// Setup:
// - Source is 25 x 10
// - 4 rows red, 2 rows blue, 4 rows black.
// - Dest is Green, 50 x 50
// - Dest Rect is 10 x 24 at (0, 0)
// Result:
// - Pattern repeated vertically x2, plus 4 rows of red.
void test_bitblt__8bit_wrap_y(void) {
GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__8bit_wrap_y.8bit.pbi");
uint8_t dest_data[50*50];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = 50,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { { 0, 0 }, { 50, 50 } }
};
memset(dest_data, GColorGreen.argb, sizeof(dest_data));
GRect dest_rect = GRect(0, 0, src_bitmap->bounds.size.w, src_bitmap->bounds.size.h*2 + 4);
bitblt_bitmap_into_bitmap_tiled_8bit_to_8bit(
&dest_bitmap, src_bitmap, dest_rect, GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__8bit_wrap_y-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Test src_origin_offset, shouldn't see any red in dest_bitmap.
// This covers src_origin_offset, y-axis and x-axis wraparound.
// Setup:
// - Source 25x25, 2 columns, 2 rows red, rest is black.
// - Source offset starts at (2, 2)
// - Dest is blue, 100x100.
// - Dest rect is 50x50 at (0,0).
// Result:
// - No red in dest_bitmap.
// - 50x50 black square at (0,0), rest is blue.
void test_bitblt__8bit_src_origin_offset_wrap(void) {
GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__8bit_src_origin_offset_wrap.8bit.pbi");
uint8_t dest_data[100*100];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = 100,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { { 0, 0 }, { 100, 100 } }
};
memset(dest_data, GColorBlue.argb, sizeof(dest_data));
GRect dest_rect = GRect(0, 0, src_bitmap->bounds.size.w*2, src_bitmap->bounds.size.h*2);
GPoint src_origin_offset = { 2, 2 }; // Offset past the 2 red rows
bitblt_bitmap_into_bitmap_tiled_8bit_to_8bit(
&dest_bitmap, src_bitmap, dest_rect, src_origin_offset, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap,
"test_bitblt__8bit_src_origin_offset_wrap-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
//
// Test 1-bit to 8-bit blitting
///////////////////////////////
// Setup:
// - Source is 25x25.
// - Source has alternating white / black lines.
// - Dest is Blue, 100x100.
// - Dest offset is (0,0) to blit to top left corner.
// Result:
// - 25x25 alternating black / white lines in top left corner.
void test_bitblt__1bit_to_8bit_compop(void) {
GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_assign.1bit.pbi");
uint8_t dest_data[100*100];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = 100,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { { 0, 0 }, { 100, 100 } }
};
memset(dest_data, GColorBlue.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_assign-expect.8bit.pbi"));
memset(dest_data, GColorBlue.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpAssignInverted, GColorWhite);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_assigninverted-expect.8bit.pbi"));
memset(dest_data, GColorBlue.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpOr, GColorWhite);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_or-expect.8bit.pbi"));
memset(dest_data, GColorBlue.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpAnd, GColorWhite);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_and-expect.8bit.pbi"));
memset(dest_data, GColorBlue.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpClear, GColorWhite);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_clear-expect.8bit.pbi"));
memset(dest_data, GColorBlue.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, GPointZero, GCompOpSet, GColorWhite);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_set-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Setup:
// - Source is an image of a white cross
// - Dest is blue, same size as source
// Result:
// - Destination should be written with a White cross
// Description:
// - This test verifies that when the bitmap is 1-bit, we treat white as a
// non-transparent color
void test_bitblt__1bit_to_8bit_compor(void) {
GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_compor.1bit.pbi");
GRect bounds = gbitmap_get_bounds(src_bitmap);
cl_assert_equal_i(src_bitmap->info.format, GBitmapFormat1Bit);
uint8_t dest_data[bounds.size.w * bounds.size.h];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = bounds.size.w,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = src_bitmap->bounds
};
memset(dest_data, GColorBlue.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpOr, GColorLightGray);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_compor-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Setup:
// - Source is a 1bit image of a white cross with a black background
// Result:
// - The image names describe the expected result of each destination color / tint color
// combination
void test_bitblt__1bit_to_8bit_comptint(void) {
GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_comptint.1bit.pbi");
const GRect bounds = gbitmap_get_bounds(src_bitmap);
cl_assert_equal_i(src_bitmap->info.format, GBitmapFormat1Bit);
uint8_t dest_data[bounds.size.w * bounds.size.h];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = bounds.size.w,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = src_bitmap->bounds
};
// Image name descriptions
// comptint_<cross_color>_on_<background_color>.8bit
// Destination White
memset(dest_data, GColorWhite.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorWhite);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_comptint_white_cross_white_corners-expect.8bit.pbi"));
memset(dest_data, GColorWhite.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorBlack);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_comptint_white_cross_black_corners-expect.8bit.pbi"));
memset(dest_data, GColorWhite.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorLightGray);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_comptint_white_cross_lightgray_corners-expect.8bit.pbi"));
// Destination Black
memset(dest_data, GColorBlack.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorWhite);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_comptint_black_cross_white_corners-expect.8bit.pbi"));
memset(dest_data, GColorBlack.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorBlack);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_comptint_black_cross_black_corners-expect.8bit.pbi"));
memset(dest_data, GColorBlack.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorLightGray);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_comptint_black_cross_lightgray_corners-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Setup:
// - Source is 25x25.
// - Source has alternating white / black lines.
// - Dest is Blue, 100x100.
// - Dest offset is set to 8x10 clipped in the bottom right corner.
// Result:
// - There should be an 8x10 alternating black & white lines in the bottom right corner.
void test_bitblt__1bit_to_8bit_clipping(void) {
GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_clipping.1bit.pbi");
uint8_t dest_data[100*100];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = 100,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { { 0, 0 }, { 100, 100 } }
};
memset(dest_data, GColorBlue.argb, sizeof(dest_data));
GPoint dest_offset = { dest_bitmap.bounds.size.w-8, dest_bitmap.bounds.size.h-10 };
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, dest_offset, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_clipping-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Setup:
// - Source is 25 x 25.
// - Source rows alternating: 1px black, 1px white.
// - Dest is Blue, 100x100
// - Dest rect is 50 x 25, at (0, 0)
// Result:
// - 50x20 of alternating stripes, rest is blue.
void test_bitblt__1bit_to_8bit_wrap_x(void) {
GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_wrap_x.1bit.pbi");
uint8_t dest_data[100*100];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = 100,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { { 0, 0 }, { 100, 100 } }
};
memset(dest_data, GColorBlue.argb, sizeof(dest_data));
GRect dest_rect = GRect(0, 0, src_bitmap->bounds.size.w*2, src_bitmap->bounds.size.h);
bitblt_bitmap_into_bitmap_tiled_1bit_to_8bit(
&dest_bitmap, src_bitmap, dest_rect, GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_8bit_wrap_x-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Setup:
// - Source is 40 x 30.
// - Source has 2 columns, 4 rows of black, rest is white.
// - Dest is all blue.
// - Source offset (2, 4) past black.
// - Destination is 100 x 100
// - Destination rect is size of white portion of source.
// Result:
// - Blue bitmap with white square at 0,0 of dest_rect size.
void test_bitblt__1bit_to_8bit_src_origin_offset(void) {
GBitmap *src_bitmap =
get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_src_origin_offset.1bit.pbi");
uint8_t dest_data[100*100];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = 100,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { { 0, 0 }, { 100, 100 } }
};
memset(dest_data, GColorBlue.argb, sizeof(dest_data));
GRect dest_rect = GRect(0, 0, src_bitmap->bounds.size.w - 2, src_bitmap->bounds.size.h - 4);
GPoint src_origin_offset = { 2, 4 }; // Offset past the black
bitblt_bitmap_into_bitmap_tiled_1bit_to_8bit(
&dest_bitmap, src_bitmap, dest_rect, src_origin_offset, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap,
"test_bitblt__1bit_to_8bit_src_origin_offset-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Setup:
// - Source is a 10x32 white square.
// - Dest is all black.
// - Dest origin offset set to 15, 18.
// - Dest clipped to 10x10
// Result:
// - Black bitmap with 10x10 white square starting at (15, 10)
void test_bitblt__1bit_to_8bit_dest_origin_offset_clip(void) {
GBitmap *src_bitmap =
get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_dest_origin_offset_clip.1bit.pbi");
uint8_t dest_data[100*100];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = 100,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { { 0, 0 }, { 100, 100 } }
};
memset(dest_data, GColorBlack.argb, sizeof(dest_data));
GRect dest_rect = GRect(15, 10, 10, 10);
GPoint src_origin_offset = { 0, 0 };
bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, src_bitmap, dest_rect, src_origin_offset,
GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap,
"test_bitblt__1bit_to_8bit_dest_origin_offset_clip-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Setup:
// - Source width is 32 pixels (ie. a word in source)
// - Source starts with 2 rows and 4 columns of black pixels.
// - Dest is all blue.
// - Src origin is set to (4, 2)
// - dest origin is set to 10, 25
// - dest size is twice the height / width of the source.
void test_bitblt__1bit_to_8bit_src_origin_offset_wrap(void) {
GBitmap *src_bitmap =
get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_src_origin_offset_wrap.1bit.pbi");
uint8_t dest_data[100*100];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = 100,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { { 0, 0 }, { 100, 100 } }
};
memset(dest_data, GColorBlue.argb, sizeof(dest_data));
GRect dest_rect = GRect(10, 25, src_bitmap->bounds.size.w*2, src_bitmap->bounds.size.h*2);
GPoint src_origin_offset = { .x = 4, .y = 2 }; // Offset past the black
bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, src_bitmap, dest_rect, src_origin_offset,
GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap,
"test_bitblt__1bit_to_8bit_src_origin_offset_wrap-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Setup:
// - Source width is not a multiple of 8 (ie. not byte aligned in source)
// - Source starts with 2 rows and 4 columns of black pixels.
// - Dest is all blue.
// - Src origin is set to (4, 2)
// - dest origin is set to 22, 6
// - dest size is twice the height / width of the source.
void test_bitblt__1bit_to_8bit_src_origin_offset_wrap2(void) {
GBitmap *src_bitmap =
get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_src_origin_offset_wrap2.1bit.pbi");
uint8_t dest_data[100*100];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = 100,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { { 0, 0 }, { 100, 100 } }
};
memset(dest_data, GColorBlue.argb, sizeof(dest_data));
GRect dest_rect = GRect(21, 6, src_bitmap->bounds.size.w*2, src_bitmap->bounds.size.h*2);
GPoint src_origin_offset = { .x = 4, .y = 2 }; // Offset past the black
bitblt_bitmap_into_bitmap_tiled(&dest_bitmap, src_bitmap, dest_rect, src_origin_offset,
GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap,
"test_bitblt__1bit_to_8bit_src_origin_offset_wrap2-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Setup:
// - Source has 2 lines of white and the rest black.
// - Destination all blue.
// - Use gbitmap_init_as_sub_bitmap to get a sub-bitmap that starts at y = 2
// Result:
// - A 48 x 50 black box starting at y=2 in dest, rest is blue.
void test_bitblt__bitmap_into_bitmap_sub_bitmap(void) {
GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__bitmap_into_bitmap_sub_bitmap.8bit.pbi");
GBitmap cropped_src_bitmap;
gbitmap_init_as_sub_bitmap(&cropped_src_bitmap, src_bitmap,
(GRect) { { 0, 2 }, src_bitmap->bounds.size });
uint8_t dest_data[50*50];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = 50,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { { 0, 0 }, { 50, 50 } }
};
memset(dest_data, GColorBlue.argb, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, &cropped_src_bitmap, (GPoint) { 0, 2 },
GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap,
"test_bitblt__bitmap_into_bitmap_sub_bitmap-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Test:
// - source origin offset
// - source bounds origin and size
// - wrapping into larger destination
// - dest rect not at { 0, 0 }
// Setup:
// - Source has non-zero bounds origin, { 5, 5 }, outside of this is red.
// - Source has non-full bounds size, { 10, 10 }, outside of this is white.
// - Source has an origin offset, { 3, 6 }, outside of this is blue.
// - Rest is black
//
// - Destination has non-zero dest_rect origin, { 4, 4 }
// - Destination has non-full dest_rect size larger than source, { 10, 10 }
// - Destination has full size of { 20, 20 }
// Result:
// - Expect a 10 x 10 black box at { 4, 4 } that is of size { 10, 10 }
// - Remainder should be green.
void test_bitblt__8bit_bounds_and_origin_offset(void) {
GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__bounds_and_origin_offset.8bit.pbi");
src_bitmap->bounds = (GRect) { { 5, 5}, { 10, 10 } };
uint8_t dest_data[50*50];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = 50,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { { 0, 0 }, { 50, 50 } }
};
memset(dest_data, GColorGreen.argb, sizeof(dest_data));
const GRect dest_rect = GRect(4, 4, 10, 10);
const GPoint src_origin_offset = GPoint(3, 6);
bitblt_bitmap_into_bitmap_tiled_8bit_to_8bit(&dest_bitmap, src_bitmap, dest_rect,
src_origin_offset, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__bounds_and_origin_offset-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Setup:
// - Source is a 1bit image of a white cross with a black background
// Result:
// - The image names describe the expected result of each destination color / tint color
// combination
void test_bitblt__1bit_to_1bit_comptint(void) {
GBitmap *src_bitmap = get_gbitmap_from_pbi("test_bitblt__1bit_to_1bit_comptint.1bit.pbi");
const GRect bounds = gbitmap_get_bounds(src_bitmap);
cl_assert_equal_i(src_bitmap->info.format, GBitmapFormat1Bit);
uint8_t dest_data[bounds.size.w * bounds.size.h];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = src_bitmap->row_size_bytes,
.info.format = GBitmapFormat1Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = src_bitmap->bounds
};
// Image name descriptions
// comptint_<cross_color>_on_<background_color>.1bit
// Destination White
memset(dest_data, 0b11111111, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorClear);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_1bit_comptint_white_on_white-expect.1bit.pbi"));
memset(dest_data, 0b11111111, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorWhite);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_1bit_comptint_white_on_white-expect.1bit.pbi"));
memset(dest_data, 0b11111111, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorBlack);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_1bit_comptint_black_on_white-expect.1bit.pbi"));
// Destination Black
memset(dest_data, 0b00000000, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorClear);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_1bit_comptint_black_on_black-expect.1bit.pbi"));
memset(dest_data, 0b00000000, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorWhite);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_1bit_comptint_white_on_black-expect.1bit.pbi"));
memset(dest_data, 0b00000000, sizeof(dest_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, src_bitmap, (GPoint){0}, GCompOpTint, GColorBlack);
cl_check(gbitmap_pbi_eq(&dest_bitmap, "test_bitblt__1bit_to_1bit_comptint_black_on_black-expect.1bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// Test:
// - source origin offset
// - source bounds origin and size
// - Source bounds origin is beyond (32, y) to pass word boundary.
// - Source origin offset is beyond (32, y) to pass another word boundary.
// - wrapping into larger destination
// - dest rect not at { 0, 0 }
// Setup:
// - Dest rect at {4, 4}, repeat twice and a bit: {140, 55}
// - Source bounds origin at {37, 3), size {63, 23}
// - Source origin offset (39, 11)
void DISABLED_test_bitblt__1bit_to_8bit_bounds_and_origin_offset(void) {
GBitmap *src_bitmap =
get_gbitmap_from_pbi("test_bitblt__1bit_to_8bit_bounds_and_origin_offset.1bit.pbi");
uint8_t dest_data[144*168];
GBitmap dest_bitmap = {
.addr = dest_data,
.row_size_bytes = 144,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { { 0, 0 }, { 144, 168 } }
};
memset(dest_data, GColorGreen.argb, sizeof(dest_data));
const GRect dest_rect = GRect(4, 4, 140, 55);
const GPoint src_origin_offset = GPoint(39, 11);
src_bitmap->bounds = (GRect) { { 37, 3}, { 63, 23 } };
bitblt_bitmap_into_bitmap_tiled_1bit_to_8bit(&dest_bitmap, src_bitmap, dest_rect,
src_origin_offset, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap,
"test_bitblt__1bit_to_8bit_bounds_and_origin_offset-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
// This test is the same as test_bitblt__1bit_to_1bit_bounds_and_origin_offset, except it is for
// 1-bit to 1-bit.
// FIXME: This is a known legacy-broken case in 1-bit bitblt. See PBL-14671 for more information
void DISABLED__test_bitblt__1bit_to_1bit_bounds_and_origin_offset(void) {
GBitmap *src_bitmap =
get_gbitmap_from_pbi("test_bitblt__1bit_to_1bit_bounds_and_origin_offset.1bit.pbi");
uint8_t dest_data[src_bitmap->row_size_bytes * src_bitmap->bounds.size.h];
GBitmap dest_bitmap = *src_bitmap;
dest_bitmap.addr = dest_data;
dest_bitmap.bounds = src_bitmap->bounds;
memset(dest_data, 0xff, sizeof(dest_data));
const GRect dest_rect = GRect(4, 4, 140, 55);
const GPoint src_origin_offset = GPoint(39, 11);
src_bitmap->bounds = (GRect) { { 37, 3}, { 63, 23 } };
bitblt_bitmap_into_bitmap_tiled_1bit_to_1bit(&dest_bitmap, src_bitmap, dest_rect,
src_origin_offset, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&dest_bitmap,
"test_bitblt__1bit_to_1bit_bounds_and_origin_offset-expect.8bit.pbi"));
gbitmap_destroy(src_bitmap);
}
+200
View File
@@ -0,0 +1,200 @@
/*
* 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/gtypes.h"
#include "applib/graphics/bitblt.h"
#include "clar.h"
#include "util.h"
#include <string.h>
#include <stdio.h>
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
// Helper Functions
////////////////////////////////////
#include "test_graphics.h"
#if SCREEN_COLOR_DEPTH_BITS == 8
#include "8bit/test_framebuffer.h"
#else
Static_assert(false, "These tests are only for color displays");
#endif
static GContext *ctx;
static FrameBuffer *fb = NULL;
// Setup
void test_bitblt_circular__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
ctx = malloc(sizeof(GContext));
test_graphics_context_init(ctx, fb);
framebuffer_clear(fb);
}
// Teardown
void test_bitblt_circular__cleanup(void) {
free(fb);
free(ctx);
}
// Tests
////////////////////////////////////
// Reference PNGs reside in "tests/test_images/"
// and are created at build time, with the test PBI file generated
// by bitmapgen.py from the reference PNG copied to TEST_IMAGES_PATH
// covers 1-bit b&w image
// covers 1,2,4,8 bit palettized
// Tests 1-bit black&white PBI loading into gbitmap
// Result:
// - gbitmap matches platform loaded PBI
void test_bitblt_circular__color_1_bit_bw(void) {
const char test_file[] = "test_bitblt_circular__color_1_bit_bw.1bit.pbi";
GBitmap *bitmap = setup_pbi_test(test_file);
GBitmap *native_framebuffer = graphics_context_get_bitmap(ctx);
native_framebuffer->info.format = GBitmapFormat8BitCircular;
cl_assert(DISPLAY_FRAMEBUFFER_BYTES == 25944);
bitblt_bitmap_into_bitmap(native_framebuffer, bitmap, GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(native_framebuffer, test_file));
}
// Tests 1-bit red&white palettized PBI loading into gbitmap
// Result:
// - gbitmap matches platform loaded PBI
void test_bitblt_circular__color_1_bit(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE);
GBitmap *native_framebuffer = graphics_context_get_bitmap(ctx);
cl_assert(native_framebuffer->info.format == GBitmapFormat8BitCircular);
cl_assert(DISPLAY_FRAMEBUFFER_BYTES == 25944);
bitblt_bitmap_into_bitmap(native_framebuffer, bitmap, GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(native_framebuffer, TEST_PBI_FILE));
}
// Tests 2-bit palettized PBI loading into gbitmap
// Result:
// - gbitmap matches platform loaded PBI
void test_bitblt_circular__color_2_bit(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE);
GBitmap *native_framebuffer = graphics_context_get_bitmap(ctx);
cl_assert(native_framebuffer->info.format == GBitmapFormat8BitCircular);
cl_assert(DISPLAY_FRAMEBUFFER_BYTES == 25944);
bitblt_bitmap_into_bitmap(native_framebuffer, bitmap, GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(native_framebuffer, TEST_PBI_FILE));
}
// Tests 4-bit bitblt palettized to circular display buffer
// Result:
// - gbitmap matches platform loaded PBI
void test_bitblt_circular__color_4_bit(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE);
GBitmap *native_framebuffer = graphics_context_get_bitmap(ctx);
cl_assert(native_framebuffer->info.format == GBitmapFormat8BitCircular);
cl_assert(DISPLAY_FRAMEBUFFER_BYTES == 25944);
bitblt_bitmap_into_bitmap(native_framebuffer, bitmap, GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(native_framebuffer, TEST_PBI_FILE));
}
// Tests 8-bit bitblt to circular display buffer
// Result:
// - gbitmap matches platform loaded PBI
void test_bitblt_circular__color_8_bit(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE);
GBitmap *native_framebuffer = graphics_context_get_bitmap(ctx);
cl_assert(native_framebuffer->info.format == GBitmapFormat8BitCircular);
cl_assert(DISPLAY_FRAMEBUFFER_BYTES == 25944);
bitblt_bitmap_into_bitmap(native_framebuffer, bitmap, GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(native_framebuffer, TEST_PBI_FILE));
}
// Tests 8-bit bitblt tiling support to circular display buffer
// Result:
// - gbitmap matches platform loaded PBI
void test_bitblt_circular__color_8_bit_tiling(void) {
GBitmap *bitmap = setup_png_test(TEST_NAMED_PNG_FILE("test_bitblt_circular__tile"));
cl_assert(bitmap->info.format == GBitmapFormat8Bit);
GBitmap *native_framebuffer = graphics_context_get_bitmap(ctx);
cl_assert(native_framebuffer->info.format == GBitmapFormat8BitCircular);
cl_assert(DISPLAY_FRAMEBUFFER_BYTES == 25944);
bitblt_bitmap_into_bitmap_tiled(
native_framebuffer, bitmap, native_framebuffer->bounds,
GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(native_framebuffer, TEST_PBI_FILE));
}
// Tests palettized bitblt non-power-of-two tiling support to circular display buffer
// Result:
// - gbitmap matches platform loaded PBI
void test_bitblt_circular__color_8_bit_tiling_palettized(void) {
GBitmap *bitmap = setup_png_test(TEST_NAMED_PNG_FILE("test_bitblt_circular__tile_palettized"));
cl_assert(bitmap->info.format == GBitmapFormat2BitPalette);
GBitmap *native_framebuffer = graphics_context_get_bitmap(ctx);
cl_assert(native_framebuffer->info.format == GBitmapFormat8BitCircular);
cl_assert(DISPLAY_FRAMEBUFFER_BYTES == 25944);
bitblt_bitmap_into_bitmap_tiled(
native_framebuffer, bitmap, native_framebuffer->bounds,
GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(native_framebuffer, TEST_PBI_FILE));
}
void test_bitblt_circular__8_bit_converted_circular(void) {
GBitmap *bitmap = setup_png_test(TEST_NAMED_PNG_FILE("test_bitblt_circular__spiral"));
cl_assert(bitmap->info.format == GBitmapFormat8Bit);
// Convert input PNG from rectangular to circular
gbitmap_8bit_to_8bit_circular(bitmap);
cl_assert(bitmap->info.format == GBitmapFormat8BitCircular);
GBitmap *native_framebuffer = graphics_context_get_bitmap(ctx);
GRect framebuffer_bounds = gbitmap_get_bounds(native_framebuffer);
cl_assert(native_framebuffer->info.format == GBitmapFormat8BitCircular);
cl_assert(DISPLAY_FRAMEBUFFER_BYTES == 25944);
// Set screen to black to match empty region with test image
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_fill_rect(ctx, &framebuffer_bounds);
bitblt_bitmap_into_bitmap(native_framebuffer, bitmap, GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(native_framebuffer, TEST_PBI_FILE));
}
void test_bitblt_circular__8_bit_converted_circular_offset(void) {
GBitmap *bitmap = setup_png_test(TEST_NAMED_PNG_FILE("test_bitblt_circular__spiral"));
cl_assert(bitmap->info.format == GBitmapFormat8Bit);
// Convert input PNG from rectangular to circular
gbitmap_8bit_to_8bit_circular(bitmap);
cl_assert(bitmap->info.format == GBitmapFormat8BitCircular);
GBitmap *native_framebuffer = graphics_context_get_bitmap(ctx);
GRect framebuffer_bounds = gbitmap_get_bounds(native_framebuffer);
cl_assert(native_framebuffer->info.format == GBitmapFormat8BitCircular);
cl_assert(DISPLAY_FRAMEBUFFER_BYTES == 25944);
// Set screen to black to match empty region with test image
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_fill_rect(ctx, &framebuffer_bounds);
// Shift the output by non-power-of-2 values for testing
bitblt_bitmap_into_bitmap(native_framebuffer, bitmap, GPoint(33, 10), GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(native_framebuffer, TEST_PBI_FILE));
}
+361
View File
@@ -0,0 +1,361 @@
/*
* 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/bitblt.h"
#include "applib/graphics/bitblt_private.h"
#include "applib/graphics/8_bit/framebuffer.h"
#include "clar.h"
#include <string.h>
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
#include "stubs_compiled_with_legacy2_sdk.h"
#include "test_graphics.h"
// Setup
////////////////////////////////////
static GContext ctx;
static FrameBuffer framebuffer;
static uint8_t dest_bitmap_data[FRAMEBUFFER_SIZE_BYTES];
static GBitmap dest_bitmap = {
.addr = dest_bitmap_data,
.row_size_bytes = FRAMEBUFFER_BYTES_PER_ROW,
.info.is_bitmap_heap_allocated = false,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = {
.size = {
.w = DISP_COLS,
.h = DISP_ROWS
},
.origin = { 0, 0 },
},
};
// Utilities
////////////////////////////////////
#if SCREEN_COLOR_DEPTH_BITS == 8
extern GColor get_bitmap_color(GBitmap *bmp, int x, int y);
#endif
// @param color_index the color index (the value) to put into the bitmap at (x, y).
// @param bpp Bits per pixel.
// @para line_stride how many bytes per line in the bitmap data.
void packed_pixel_set(uint8_t *buf, uint8_t color_index, int16_t x, int16_t y,
uint8_t bpp, int16_t line_stride) {
const uint8_t ppb = 8 / bpp;
uint8_t idx = y*line_stride + (x/(ppb));
const uint8_t shift = (8 - bpp) - bpp * (x % ppb);
const uint8_t mask = ~(((1 << bpp) - 1) << shift);
buf[idx] = buf[idx] & mask;
buf[idx] |= ((color_index & ((1 << bpp) - 1)) << shift);
}
static bool prv_check_source_stripe_blit(const uint8_t *data,
const GBitmap *src_bmp, GColor surround_color) {
for (uint8_t y = 0; y < (FRAMEBUFFER_SIZE_BYTES / FRAMEBUFFER_BYTES_PER_ROW); ++y) {
for (uint8_t x = 0; x < FRAMEBUFFER_BYTES_PER_ROW; ++x) {
uint8_t color = data[y*FRAMEBUFFER_BYTES_PER_ROW + x];
if (y < src_bmp->bounds.size.h && x < src_bmp->bounds.size.w) {
if (color != src_bmp->palette[x].argb) {
return false;
}
} else {
if (color != surround_color.argb) {
return false;
}
}
}
}
return true;
}
// Tests
////////////////////////////////////
// setup and teardown
void test_bitblt_palette__initialize(void) {
framebuffer_init(&framebuffer, &(GSize) { DISP_COLS, DISP_ROWS });
test_graphics_context_init(&ctx, &framebuffer);
}
void test_bitblt_palette__cleanup(void) {
}
void test_bitblt_palette__1Bit_color(void) {
const int BITS_PER_PIXEL = 1;
const int PIXELS_PER_BYTE = (8 / BITS_PER_PIXEL);
const int WIDTH = 2;
const int HEIGHT = 2;
const int ROW_STRIDE = (WIDTH + (PIXELS_PER_BYTE - 1)) / PIXELS_PER_BYTE;
uint8_t s_data[ROW_STRIDE * HEIGHT];
GColor s_palette[1 << 1] = {
GColorMelon, GColorIcterine
};
cl_assert(sizeof(s_palette) == (1 << BITS_PER_PIXEL));
GBitmap s_bmp = (GBitmap) {
.addr = s_data,
.row_size_bytes = ROW_STRIDE,
.info.format = GBitmapFormat1BitPalette,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { .size = { WIDTH, HEIGHT } },
.palette = s_palette,
};
memset(s_data, 0, sizeof(s_data));
for (int y = 0; y < HEIGHT; ++y) {
for (int x = 0; x < WIDTH; ++x) {
packed_pixel_set(s_data, x /* color_index */, x, y, BITS_PER_PIXEL, s_bmp.row_size_bytes);
}
}
memset(dest_bitmap_data, GColorWhite.argb, sizeof(dest_bitmap_data));
#if SCREEN_COLOR_DEPTH_BITS == 8
char print_buf[20];
for (int y = 0; y < HEIGHT; ++y) {
for (int x = 0; x < WIDTH; ++x) {
snprintf(print_buf, sizeof(print_buf), "Failed index = %d, %d", x, y);
cl_check_(gcolor_equal(get_bitmap_color(&s_bmp, x, y), s_palette[x]), print_buf);
cl_check_(gcolor_equal(get_bitmap_color(&s_bmp, x, y), s_palette[x]), print_buf);
}
}
#endif
}
void test_bitblt_palette__4Bit_assign(void) {
const int BITS_PER_PIXEL = 4;
const int PIXELS_PER_BYTE = (8 / BITS_PER_PIXEL);
const int WIDTH = 16;
const int HEIGHT = 16;
const int ROW_STRIDE = (WIDTH + (PIXELS_PER_BYTE - 1)) / PIXELS_PER_BYTE;
uint8_t s_data[ROW_STRIDE * HEIGHT];
GColor s_palette[1 << 4] = {
GColorMelon, GColorIcterine, GColorYellow, GColorSunsetOrange,
GColorScreaminGreen, GColorMagenta, GColorOrange, GColorFolly,
GColorLimerick, GColorPictonBlue, GColorPurple, GColorCadetBlue,
GColorMalachite, GColorGreen, GColorIndigo, GColorVividCerulean
};
cl_assert(sizeof(s_palette) == (1 << BITS_PER_PIXEL));
GBitmap s_bmp = (GBitmap) {
.addr = s_data,
.row_size_bytes = ROW_STRIDE,
.info.format = GBitmapFormat4BitPalette,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { .size = { WIDTH, HEIGHT } },
.palette = s_palette,
};
memset(s_data, 0, sizeof(s_data));
for (int y = 0; y < HEIGHT; ++y) {
for (int x = 0; x < WIDTH; ++x) {
packed_pixel_set(s_data, x /* color_index */, x, y, BITS_PER_PIXEL, s_bmp.row_size_bytes);
}
}
memset(dest_bitmap_data, GColorWhite.argb, sizeof(dest_bitmap_data));
#if SCREEN_COLOR_DEPTH_BITS == 8
char print_buf[20];
for (int y = 0; y < HEIGHT; ++y) {
for (int x = 0; x < WIDTH; ++x) {
snprintf(print_buf, sizeof(print_buf), "Failed index = %d, %d", x, y);
cl_check_(gcolor_equal(get_bitmap_color(&s_bmp, x, y), s_palette[x]), print_buf);
cl_check_(gcolor_equal(get_bitmap_color(&s_bmp, x, y), s_palette[x]), print_buf);
}
}
#endif
bitblt_bitmap_into_bitmap(&dest_bitmap, &s_bmp, GPointZero, GCompOpAssign, GColorWhite);
cl_assert(prv_check_source_stripe_blit(dest_bitmap_data, &s_bmp, GColorWhite));
}
static void prv_opaque_2bit_simple(GCompOp compositing_mode) {
const int BITS_PER_PIXEL = 2;
const int PIXELS_PER_BYTE = (8 / BITS_PER_PIXEL);
const int WIDTH = 4;
const int HEIGHT = 4;
const int ROW_STRIDE = (WIDTH + (PIXELS_PER_BYTE - 1)) / PIXELS_PER_BYTE;
uint8_t s_data[ROW_STRIDE * HEIGHT];
GColor s_palette[] = {
GColorRed, GColorWhite, GColorBlack, GColorBlue
};
cl_assert(sizeof(s_palette) == (1 << BITS_PER_PIXEL));
GBitmap s_bmp = (GBitmap) {
.addr = s_data,
.row_size_bytes = ROW_STRIDE,
.info.format = GBitmapFormat2BitPalette,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { .size = { WIDTH, HEIGHT } },
.palette = s_palette,
};
memset(s_data, 0, sizeof(s_data));
for (int y = 0; y < HEIGHT; ++y) {
for (int x = 0; x < WIDTH; ++x) {
packed_pixel_set(s_data, x /* color_index */, x, y, BITS_PER_PIXEL, s_bmp.row_size_bytes);
}
}
memset(dest_bitmap_data, GColorWhite.argb, sizeof(dest_bitmap_data));
#if SCREEN_COLOR_DEPTH_BITS == 8
char print_buf[20];
for (int y = 0; y < HEIGHT; ++y) {
for (int x = 0; x < WIDTH; ++x) {
snprintf(print_buf, sizeof(print_buf), "Failed index = %d, %d", x, y);
cl_check_(gcolor_equal(get_bitmap_color(&s_bmp, x, y), s_palette[x]), print_buf);
cl_check_(gcolor_equal(get_bitmap_color(&s_bmp, x, y), s_palette[x]), print_buf);
}
}
#endif
bitblt_bitmap_into_bitmap(&dest_bitmap, &s_bmp, GPointZero, compositing_mode, GColorWhite);
switch (compositing_mode) {
case GCompOpTint: {
// Since this is opaque, the tint_color will be used for the RGB value of the destination
// image, so we want to use that as our palette.
for (uint8_t idx = 0; idx < WIDTH; idx++) {
if (s_palette[idx].a == 3) {
s_palette[idx] = GColorWhite;
}
}
break;
}
default:
break;
}
cl_assert(prv_check_source_stripe_blit(dest_bitmap_data, &s_bmp, GColorWhite));
}
static void prv_4bit_simple(GCompOp compositing_mode, GColor color, bool transparent) {
const int BITS_PER_PIXEL = 4;
const int PIXELS_PER_BYTE = (8 / BITS_PER_PIXEL);
const int WIDTH = 16;
const int HEIGHT = 16;
const int ROW_STRIDE = (WIDTH + (PIXELS_PER_BYTE - 1)) / PIXELS_PER_BYTE;
uint8_t s_data[ROW_STRIDE * HEIGHT];
GColor s_palette[] = {
GColorMelon, GColorIcterine, GColorYellow, GColorSunsetOrange,
GColorScreaminGreen, GColorMagenta, GColorOrange, GColorFolly,
GColorLimerick, GColorPictonBlue, GColorPurple, GColorCadetBlue,
GColorMalachite, GColorGreen, GColorIndigo, GColorVividCerulean
};
cl_assert(sizeof(s_palette) == (1 << BITS_PER_PIXEL));
if (transparent) {
// Make all of those colors fully transparent
for (int i = 0; i < sizeof(s_palette); ++i) {
s_palette[i].a = 0;
}
}
GBitmap s_bmp = (GBitmap) {
.addr = s_data,
.row_size_bytes = ROW_STRIDE,
.info.format = GBitmapFormat4BitPalette,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = { .size = { WIDTH, HEIGHT } },
.palette = s_palette,
};
memset(s_data, 0, sizeof(s_data));
for (int y = 0; y < HEIGHT; ++y) {
for (int x = 0; x < WIDTH; ++x) {
packed_pixel_set(s_data, x /* color_index */, x, y, BITS_PER_PIXEL, s_bmp.row_size_bytes);
}
}
memset(dest_bitmap_data, GColorWhite.argb, sizeof(dest_bitmap_data));
bitblt_bitmap_into_bitmap(&dest_bitmap, &s_bmp, GPointZero, compositing_mode, color);
switch (compositing_mode) {
case GCompOpSet: {
if (transparent) {
memset(s_palette, color.argb, sizeof(s_palette));
}
break;
}
case GCompOpAssign:
break;
case GCompOpTint: {
for (uint8_t idx = 0; idx < WIDTH; idx++) {
if (s_palette[idx].a == 3 || transparent) {
s_palette[idx] = color;
}
}
break;
}
default:
break;
}
cl_assert(prv_check_source_stripe_blit(dest_bitmap_data, &s_bmp, GColorWhite));
}
void test_bitblt_palette__2Bit_assign_opaque(void) {
prv_opaque_2bit_simple(GCompOpAssign);
}
void test_bitblt_palette__2Bit_set_opaque(void) {
prv_opaque_2bit_simple(GCompOpSet);
}
void test_bitblt_palette__2Bit_comptint_opaque(void) {
prv_opaque_2bit_simple(GCompOpTint);
}
void test_bitblt_palette__4Bit_assign_opaque(void) {
prv_4bit_simple(GCompOpAssign, GColorWhite, false /* opaque */);
}
void test_bitblt_palette__4Bit_assign_transparent(void) {
prv_4bit_simple(GCompOpAssign, GColorWhite, true /* transparent */);
}
void test_bitblt_palette__4Bit_set_opaque(void) {
prv_4bit_simple(GCompOpSet, GColorWhite, false /* opaque */);
}
void test_bitblt_palette__4Bit_set_transparent(void) {
prv_4bit_simple(GCompOpSet, GColorWhite, true /* transparent */);
}
void test_bitblt_palette__4Bit_comptint_opaque(void) {
prv_4bit_simple(GCompOpTint, GColorBlack, false /* opaque */);
prv_4bit_simple(GCompOpTint, GColorBlue, false /* opaque */);
}
void test_bitblt_palette__4Bit_comptint_transparent(void) {
prv_4bit_simple(GCompOpTint, GColorWhite, true /* transparent */);
}
@@ -0,0 +1,212 @@
/*
* 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/bitblt.h"
#include "applib/graphics/bitblt_private.h"
#include "applib/graphics/1_bit/framebuffer.h"
#include "applib/graphics/gtypes.h"
#include "clar.h"
#include "util.h"
#include <string.h>
#include <stdio.h>
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
#include "test_graphics.h"
extern void prv_apply_tint_color(GColor *color, GColor tint_color);
static uint8_t s_dest_data[100 * 100];
static GBitmap s_dest_bitmap = {
.addr = s_dest_data,
.row_size_bytes = 16,
.info.format = GBitmapFormat1Bit,
.info.version = GBITMAP_VERSION_CURRENT,
.bounds = GRect(0, 0, 100, 100)
};
// Tests
////////////////////////////////////
// setup and teardown
void test_bitblt_palette_1bit__initialize(void) {
// Set dest bitmap to white
memset(s_dest_data, 0b11111111, sizeof(s_dest_data));
}
void test_bitblt_palette_1bit__cleanup(void) {
}
// Test images reside in "tests/fw/graphics/test_images/".
// The wscript will convert them from PNGs in that directory to PBIs in the build directory.
// Tests assign, from same size to same size.
// Setup:
// - Source is 50x50, the left half is semi transparent orange and the right half is orange.
// - Dest is 100x100, white.
// Result:
// - All dithered gray.
void test_bitblt_palette_1bit__1bit_palette_to_1bit_assign(void) {
GBitmap *src_bitmap =
get_gbitmap_from_pbi("test_bitblt_palette_1bit__1bit_palette_to_1bit.pbi");
bitblt_bitmap_into_bitmap(&s_dest_bitmap, src_bitmap, GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&s_dest_bitmap,
"test_bitblt_palette_1bit__1bit_palette_to_1bit_assign-expect.pbi"));
gbitmap_destroy(src_bitmap);
}
// Test images reside in "tests/fw/graphics/test_images/".
// The wscript will convert them from PNGs in that directory to PBIs in the build directory.
// Tests assign, from same size to same size.
// Setup:
// - Source is 50x50, the left half is semi transparent orange and the right half is orange.
// - Dest is 100x100, white.
// Result:
// - The left half will be white and the right half will be dithered gray.
void test_bitblt_palette_1bit__1bit_palette_to_1bit_set(void) {
GBitmap *src_bitmap =
get_gbitmap_from_pbi("test_bitblt_palette_1bit__1bit_palette_to_1bit.pbi");
bitblt_bitmap_into_bitmap(&s_dest_bitmap, src_bitmap, GPointZero, GCompOpSet, GColorWhite);
cl_assert(gbitmap_pbi_eq(&s_dest_bitmap,
"test_bitblt_palette_1bit__1bit_palette_to_1bit_set-expect.pbi"));
gbitmap_destroy(src_bitmap);
}
// Tests assign, from same size to same size.
// Setup:
// - Source is 50x50, alternating lines between orange and blue for the top half.
// The bottom half is a diagonal orange line over blue.
// The left half is semi-transparent and the right half is completely opaque
// - Dest is 100x100, white.
// Result:
// - The top half will be alternating between dithered gray and black lines
// The bottom half consists of a diagonal white line on a black background
void test_bitblt_palette_1bit__2bit_palette_to_1bit_assign(void) {
GBitmap *src_bitmap =
get_gbitmap_from_pbi("test_bitblt_palette_1bit__2bit_palette_to_1bit.pbi");
bitblt_bitmap_into_bitmap(&s_dest_bitmap, src_bitmap, GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&s_dest_bitmap,
"test_bitblt_palette_1bit__2bit_palette_to_1bit-expect.pbi"));
gbitmap_destroy(src_bitmap);
}
// Tests assign, from same size to same size.
// Setup:
// - Source is 50x50, alternating lines between orange and blue for the top half.
// The bottom half is a diagonal orange line over blue.
// The left half is semi-transparent and the right half is completely opaque
// - Dest is 100x100, white.
// Result:
// - The top right half will be alternating between dithered gray and black lines
// The bottom right half consists of a diagonal white line on a black background
// The left half will be completely white
void test_bitblt_palette_1bit__2bit_palette_to_1bit_set(void) {
GBitmap *src_bitmap =
get_gbitmap_from_pbi("test_bitblt_palette_1bit__2bit_palette_to_1bit.pbi");
bitblt_bitmap_into_bitmap(&s_dest_bitmap, src_bitmap, GPointZero, GCompOpSet, GColorWhite);
cl_assert(gbitmap_pbi_eq(&s_dest_bitmap,
"test_bitblt_palette_1bit__2bit_palette_to_1bit_set-expect.pbi"));
gbitmap_destroy(src_bitmap);
}
// Tests assign, from same size to same size.
// Setup:
// - Source is 50x50, alternating lines between orange and blue for the top half.
// The bottom half is a diagonal orange line over blue.
// The left half is semi-transparent and the right half is completely opaque
// - Dest is 50x50, white.
// Result:
// - The image desribed will be tiled in each of the four corners
// The top right half will be alternating between dithered gray and black lines
// The bottom right half consists of a diagonal white line on a black background
// The left half will be completely white
void test_bitblt_palette_1bit__2bit_palette_to_1bit_wrap(void) {
GBitmap *src_bitmap =
get_gbitmap_from_pbi("test_bitblt_palette_1bit__2bit_palette_to_1bit.pbi");
bitblt_bitmap_into_bitmap_tiled(&s_dest_bitmap, src_bitmap, s_dest_bitmap.bounds, GPointZero, GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&s_dest_bitmap,
"test_bitblt_palette_1bit__2bit_palette_to_1bit_wrap-expect.pbi"));
gbitmap_destroy(src_bitmap);
}
// Tests assign, from same size to same size.
// Setup:
// - Source is 50x50, alternating lines between orange and blue for the top half.
// The bottom half is a diagonal orange line over blue.
// The left half is semi-transparent and the right half is completely opaque
// - Dest is 100x100, white.
// Result:
// - The image described below will be at an offset of 20, 20
// The top right half will be alternating between dithered gray and black lines
// The bottom right half consists of a diagonal white line on a black background
// The left half will be completely white
void test_bitblt_palette_1bit__2bit_palette_to_1bit_offest(void) {
GBitmap *src_bitmap =
get_gbitmap_from_pbi("test_bitblt_palette_1bit__2bit_palette_to_1bit.pbi");
bitblt_bitmap_into_bitmap(&s_dest_bitmap, src_bitmap, GPoint(20,20), GCompOpAssign, GColorWhite);
cl_assert(gbitmap_pbi_eq(&s_dest_bitmap,
"test_bitblt_palette_1bit__2bit_palette_to_1bit_offest-expect.pbi"));
gbitmap_destroy(src_bitmap);
}
void test_bitblt_palette_1bit__get_1bit_graphics_grayscale_pattern(void) {
cl_assert_equal_i(graphics_private_get_1bit_grayscale_pattern(GColorWhite, 0), 0xFFFFFFFF);
cl_assert_equal_i(graphics_private_get_1bit_grayscale_pattern(GColorWhite, 1), 0xFFFFFFFF);
cl_assert_equal_i(graphics_private_get_1bit_grayscale_pattern(GColorLightGray, 0), 0x55555555);
cl_assert_equal_i(graphics_private_get_1bit_grayscale_pattern(GColorLightGray, 1), 0xAAAAAAAA);
cl_assert_equal_i(graphics_private_get_1bit_grayscale_pattern(GColorDarkGray, 0), 0x55555555);
cl_assert_equal_i(graphics_private_get_1bit_grayscale_pattern(GColorDarkGray, 1), 0xAAAAAAAA);
cl_assert_equal_i(graphics_private_get_1bit_grayscale_pattern(GColorBlack, 0), 0x00000000);
cl_assert_equal_i(graphics_private_get_1bit_grayscale_pattern(GColorBlack, 1), 0x00000000);
}
void test_bitblt_palette_1bit__prv_apply_tint_color(void) {
GColor color = GColorBlack;
GColor tinted_color = GColorBlack;
prv_apply_tint_color(&color, GColorClear);
cl_assert_equal_i(color.argb, tinted_color.argb);
tinted_color = GColorRed;
prv_apply_tint_color(&color, GColorRed);
cl_assert_equal_i(color.argb, tinted_color.argb);
tinted_color.a = 2;
color.a = 2;
prv_apply_tint_color(&color, GColorRed);
cl_assert_equal_i(color.argb, tinted_color.argb);
tinted_color.a = 1;
color.a = 1;
prv_apply_tint_color(&color, GColorRed);
cl_assert_equal_i(color.argb, tinted_color.argb);
}
+150
View File
@@ -0,0 +1,150 @@
/*
* 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/graphics/bitblt_private.h"
#include "applib/ui/window_private.h"
#include "applib/ui/layer.h"
#include "util/math.h"
#include "util.h"
#include "clar.h"
#include <stdio.h>
// Helper Functions
////////////////////////////////////
#include "test_graphics.h"
#include "${BIT_DEPTH_NAME}/test_framebuffer.h"
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
// Tests
////////////////////////////////////
#define ORIGIN_RECT_NO_CLIP GRect(0, 0, 144, 168)
#define OFFSET_X 9
#define OFFSET_Y 20
#define STRIPE_WIDTH 64
#define STRIPE_HEIGHT 1
void prv_test_plot_horizontal_line(GBitmap *framebuffer, GRect area, GColor color) {
int y = area.origin.y;
int x0 = area.origin.x;
int x1 = x0 + area.size.w;
if (y >= framebuffer->bounds.size.h || y < 0) {
return;
}
int16_t x_min = MAX(MIN(x0, x1), framebuffer->bounds.origin.x);
int16_t x_max = MIN(MAX(x0, x1), framebuffer->bounds.origin.x + framebuffer->bounds.size.w);
for (int i=x_min; i < x_max; i++) {
#if SCREEN_COLOR_DEPTH_BITS == 8
GColor *output = (GColor*)framebuffer->addr + y * framebuffer->row_size_bytes + i;
// src_col, dest_col
output->argb = gcolor_alpha_blend(color, (*output)).argb;
#endif // SCREEN_COLOR_DEPTH_BITS == 8
}
}
void test_blending_${BIT_DEPTH_NAME}__photoshop(void) {
GContext ctx;
FrameBuffer *fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) { DISP_COLS, DISP_ROWS });
test_graphics_context_init(&ctx, fb);
GBitmap *background_0_100 = get_gbitmap_from_pbi("blendtest_0_100_backdrop.pbi");
GBitmap *background_33_66 = get_gbitmap_from_pbi("blendtest_33_66_backdrop.pbi");
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_bitmap_in_rect(&ctx, background_0_100, &ORIGIN_RECT_NO_CLIP);
#if SCREEN_COLOR_DEPTH_BITS == 8
// Sanity check
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "blendtest_0_100_backdrop.pbi"));
// 0% alpha channel blend test
uint8_t color = 0x0;
uint8_t alpha = 0x0;
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_bitmap_in_rect(&ctx, background_0_100, &ORIGIN_RECT_NO_CLIP);
for (int i=0; i < 64; i++) {
prv_test_plot_horizontal_line(&ctx.dest_bitmap,
GRect(OFFSET_X, OFFSET_Y + i, STRIPE_WIDTH, STRIPE_HEIGHT),
(GColor){.argb = color | alpha});
color++;
}
// 100% alpha channel blend test
color = 0x0;
alpha = 0xC0;
for (int i=64; i < 128; i++) {
prv_test_plot_horizontal_line(&ctx.dest_bitmap,
GRect(OFFSET_X, OFFSET_Y + i, STRIPE_WIDTH, STRIPE_HEIGHT),
(GColor){.argb = color | alpha});
color++;
}
// Compare the results
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "blendtest_0_100.pbi"));
// Reset canvas
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_bitmap_in_rect(&ctx, background_33_66, &ORIGIN_RECT_NO_CLIP);
// 33% alpha channel blend test
color = 0x0;
alpha = 0x40;
for (int i=0; i < 64; i++) {
prv_test_plot_horizontal_line(&ctx.dest_bitmap,
GRect(OFFSET_X, OFFSET_Y + i, STRIPE_WIDTH, STRIPE_HEIGHT),
(GColor){.argb = color | alpha});
color++;
}
// 66% alpha channel blend test
color = 0x0;
alpha = 0x80;
for (int i=64; i < 128; i++) {
prv_test_plot_horizontal_line(&ctx.dest_bitmap,
GRect(OFFSET_X, OFFSET_Y + i, STRIPE_WIDTH, STRIPE_HEIGHT),
(GColor){.argb = color | alpha});
color++;
}
// Compare the results
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "blendtest_33_66.pbi"));
#endif
gbitmap_destroy(background_0_100);
gbitmap_destroy(background_33_66);
free(fb);
}
@@ -0,0 +1,59 @@
/*
* 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 "util/bitset.h"
#include "util/size.h"
#include "${BIT_DEPTH_NAME}/test_framebuffer.h"
#include "clar.h"
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
// Setup
////////////////////////////////////
static FrameBuffer framebuffer;
// Tests
////////////////////////////////////
void test_framebuffer_${BIT_DEPTH_NAME}__framebuffer_clear(void) {
// clear the framebuffer
framebuffer_init(&framebuffer, &(GSize) { DISP_COLS, DISP_ROWS });
framebuffer_clear(&framebuffer);
// check the clearing
#if SCREEN_COLOR_DEPTH_BITS == 1
for (int i = 0; i < ARRAY_LENGTH(framebuffer.buffer); i++) {
cl_assert(framebuffer.buffer[i] == 0xffffffff);
}
#elif SCREEN_COLOR_DEPTH_BITS == 8
for (int i = 0; i < ARRAY_LENGTH(framebuffer.buffer); i++) {
cl_assert(framebuffer.buffer[i] == GColorWhite.argb);
}
GRect expected_dirty = GRect(0, 0, DISP_COLS, DISP_ROWS);
#else
cl_assert(!memcmp(&framebuffer.dirty_rect, &expected_dirty, sizeof(GRect)));
cl_assert(false);
#endif
cl_assert(framebuffer.is_dirty == true);
}
@@ -0,0 +1,103 @@
/*
* 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/framebuffer.h"
#include "util/trig.h"
#include "applib/ui/window_private.h"
#include "applib/ui/layer.h"
#include "clar.h"
#include "util.h"
#include <stdio.h>
// Helper Functions
////////////////////////////////////
#include "test_graphics.h"
#include "8bit/test_framebuffer.h"
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
static FrameBuffer *fb = NULL;
#define CLIP_RECT_DRAW_BOX GRect(0, 0, DISP_COLS, DISP_ROWS)
#define CLIP_RECT_CLIP_BOX GRect(0, 0, DISP_COLS, DISP_ROWS)
// Setup and Teardown
////////////////////////////////////
// Setup
void test_framebuffer_clipping__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) { DISP_COLS, DISP_ROWS });
}
// Teardown
void test_framebuffer_clipping__cleanup(void) {
free(fb);
}
// Tests
////////////////////////////////////
void test_framebuffer_clipping__off_screen_left_aa_clipping(void) {
cl_assert_equal_i(GBITMAP_NATIVE_FORMAT, GBitmapFormat8BitCircular);
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, true, 1);
cl_assert_equal_i(ctx.dest_bitmap.info.format, GBitmapFormat8BitCircular);
memset(ctx.dest_bitmap.addr, GColorRedARGB8, FRAMEBUFFER_SIZE_BYTES);
GRect radial_container_rect = ctx.dest_bitmap.bounds;
radial_container_rect = grect_inset(radial_container_rect, GEdgeInsets(-10));
const uint16_t inset_thickness = radial_container_rect.size.w / 4;
graphics_context_set_fill_color(&ctx, GColorGreen);
graphics_fill_radial(&ctx, radial_container_rect, GOvalScaleModeFillCircle, inset_thickness,
TRIG_MAX_ANGLE / 2, TRIG_MAX_ANGLE);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, TEST_NAMED_PBI_FILE("off_screen_left_aa_clipping")));
};
void test_framebuffer_clipping__off_screen_right_aa_clipping(void) {
cl_assert_equal_i(GBITMAP_NATIVE_FORMAT, GBitmapFormat8BitCircular);
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, true, 1);
cl_assert_equal_i(ctx.dest_bitmap.info.format, GBitmapFormat8BitCircular);
memset(ctx.dest_bitmap.addr, GColorRedARGB8, FRAMEBUFFER_SIZE_BYTES);
GRect radial_container_rect = ctx.dest_bitmap.bounds;
radial_container_rect = grect_inset(radial_container_rect, GEdgeInsets(-10));
const uint16_t inset_thickness = radial_container_rect.size.w / 4;
graphics_context_set_fill_color(&ctx, GColorGreen);
graphics_fill_radial(&ctx, radial_container_rect, GOvalScaleModeFillCircle, inset_thickness, 0,
TRIG_MAX_ANGLE / 2);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, TEST_NAMED_PBI_FILE("off_screen_right_aa_clipping")));
};
+101
View File
@@ -0,0 +1,101 @@
/*
* 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 "util/graphics.h"
#include "clar.h"
#include "util.h"
#include <stdio.h>
// Helper Functions
////////////////////////////////////
#include "test_graphics.h"
#include "8bit/test_framebuffer.h"
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
static FrameBuffer *fb = NULL;
#define COLOR(x) ((GColor){.argb=GColor ## x ## ARGB8})
#define NUM_COLORS 4
GColor color_table[NUM_COLORS] = {
COLOR(Red),
COLOR(Yellow),
COLOR(Cyan),
COLOR(Black),
};
// Setup
void test_framebuffer_duma__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) { DISP_COLS, DISP_ROWS });
}
// Teardown
void test_framebuffer_duma__cleanup(void) {
free(fb);
}
// Intentionally unchecked framebuffer drawing function
static void draw_fb_raw(uint8_t *buffer, int offset, GColor8 color) {
buffer[offset] = color.argb;
}
// Tests
////////////////////////////////////
void test_framebuffer_duma__draw_within_framebuffer(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// This test should only be running on color displays
cl_assert(ctx.dest_bitmap.info.format == GBitmapFormat8Bit ||
ctx.dest_bitmap.info.format == GBitmapFormat8BitCircular);
// This should touch all valid bytes in the framebuffer
for (int y = DISP_ROWS - 1; y >= 0; y--) {
GColor color = color_table[y % NUM_COLORS];
int16_t row_offset = DISP_COLS * y;
GBitmapDataRowInfo row_info = gbitmap_get_data_row_info(&ctx.dest_bitmap, y);
for (int x = row_info.min_x; x < row_info.max_x; x++) {
// Use direct framebuffer access to prove framebuffer-correct positioning
row_info.data[x] = color.argb;
}
}
}
// This test validates that a duma assert is caught when drawing outside of the framebuffer
void test_framebuffer_duma__draw_beyond_framebuffer(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// This test should only be running on color displays
cl_assert(ctx.dest_bitmap.info.format == GBitmapFormat8Bit ||
ctx.dest_bitmap.info.format == GBitmapFormat8BitCircular);
uint8_t *buffer = (uint8_t*)ctx.dest_bitmap.addr;
// Expect this to assert using duma protection, we are writing past framebuffer
cl_assert_passert(draw_fb_raw(buffer, FRAMEBUFFER_SIZE_BYTES + 1, GColorWhite));
}
@@ -0,0 +1,75 @@
/*
* 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 "clar.h"
#include "applib/graphics/gtypes.h"
#include <stdio.h>
// stubs
#include "stubs_applib_resource.h"
#include "stubs_app_state.h"
#include "stubs_graphics_context.h"
#include "stubs_heap.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_process_manager.h"
/////////////////////////////
void test_gbitmap_data_row_info__get_info_rectangular(void) {
uint8_t some_addr;
GBitmap bmp = {
.addr = &some_addr,
.row_size_bytes = 123,
.bounds = GRect(1, 2, 3, 4),
.info.format = GBitmapFormat8Bit,
};
cl_assert_equal_i(123, gbitmap_format_get_row_size_bytes(123, GBitmapFormat8Bit));
cl_assert_equal_i(123, gbitmap_get_bytes_per_row(&bmp));
cl_assert_equal_p(&some_addr + 0 * 123, gbitmap_get_data_row_info(&bmp, 0).data);
cl_assert_equal_p(&some_addr + 1 * 123, gbitmap_get_data_row_info(&bmp, 1).data);
cl_assert_equal_i(0, gbitmap_get_data_row_info(&bmp, 3).min_x);
cl_assert_equal_i(3, gbitmap_get_data_row_info(&bmp, 3).max_x);
}
void test_gbitmap_data_row_info__get_info_circular(void) {
uint8_t some_addr;
GBitmapDataRowInfoInternal infos[] = {
{.offset = 1, .min_x = 2, .max_x = 3}, // 0
{.offset = 4, .min_x = 5, .max_x = 6}, // 1
{.offset = 7, .min_x = 8, .max_x = 9}, // 2 // 0
{.offset = 10, .min_x = 11, .max_x = 12}, // 3 // 1
{.offset = 13, .min_x = 14, .max_x = 15}, // 4 // 2
{.offset = 16, .min_x = 17, .max_x = 18}, // 5 // 3
};
GBitmap bmp = {
.addr = &some_addr,
.row_size_bytes = 123,
.bounds = GRect(1, 2, 3, 4),
.info.format = GBitmapFormat8BitCircular,
.data_row_infos = infos,
};
cl_assert_equal_i(0, gbitmap_format_get_row_size_bytes(123, GBitmapFormat8BitCircular));
cl_assert_equal_i(123, gbitmap_get_bytes_per_row(&bmp));
cl_assert_equal_p(&some_addr + 1, gbitmap_get_data_row_info(&bmp, 0).data);
cl_assert_equal_p(&some_addr + 4, gbitmap_get_data_row_info(&bmp, 1).data);
cl_assert_equal_i(11, gbitmap_get_data_row_info(&bmp, 3).min_x);
cl_assert_equal_i(12, gbitmap_get_data_row_info(&bmp, 3).max_x);
}
+222
View File
@@ -0,0 +1,222 @@
/*
* 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/gtypes.h"
#include "clar.h"
#include <stdio.h>
// stubs
#include "stubs_applib_resource.h"
#include "stubs_app_state.h"
#include "stubs_graphics_context.h"
#include "stubs_heap.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_process_manager.h"
/////////////////////////////
const void * const g_gbitmap_spalding_data_row_infos = &g_gbitmap_spalding_data_row_infos;
void test_gbitmap_formats__create_blank(void) {
const GSize s10 = GSize(10, 10);
const GSize s180 = GSize(180, 180);
GBitmap *bmp = NULL;
cl_assert((void*)&bmp->palette == (void*)&bmp->data_row_infos); // union with .palette
cl_assert(NULL != g_gbitmap_spalding_data_row_infos); // make sure unit-test fixture is ok
#ifdef PLATFORM_TINTIN
cl_assert(!process_manager_compiled_with_legacy2_sdk());
bmp = gbitmap_create_blank(s10, GBitmapFormat1Bit);
cl_assert(NULL != bmp);
cl_assert(NULL == bmp->data_row_infos);
bmp = gbitmap_create_blank(s10, GBitmapFormat8Bit);
cl_assert(NULL == bmp);
bmp = gbitmap_create_blank(s10, GBitmapFormat1BitPalette);
cl_assert(NULL != bmp);
cl_assert(g_gbitmap_spalding_data_row_infos != bmp->data_row_infos); // union with .palette
bmp = gbitmap_create_blank(s10, GBitmapFormat2BitPalette);
cl_assert(NULL != bmp);
cl_assert(g_gbitmap_spalding_data_row_infos != bmp->data_row_infos); // union with .palette
bmp = gbitmap_create_blank(s10, GBitmapFormat4BitPalette);
cl_assert(NULL == bmp);
bmp = gbitmap_create_blank(s10, GBitmapFormat8BitCircular);
cl_assert(NULL == bmp);
bmp = gbitmap_create_blank(s180, GBitmapFormat8BitCircular);
cl_assert(NULL == bmp);
#endif
#ifdef PLATFORM_SNOWY
bmp = gbitmap_create_blank(s10, GBitmapFormat1Bit);
cl_assert(NULL != bmp);
cl_assert(NULL == bmp->data_row_infos);
bmp = gbitmap_create_blank(s10, GBitmapFormat8Bit);
cl_assert(NULL != bmp);
cl_assert(NULL == bmp->data_row_infos);
bmp = gbitmap_create_blank(s10, GBitmapFormat1BitPalette);
cl_assert(NULL != bmp);
cl_assert(g_gbitmap_spalding_data_row_infos != bmp->data_row_infos); // union with .palette
bmp = gbitmap_create_blank(s10, GBitmapFormat2BitPalette);
cl_assert(NULL != bmp);
cl_assert(g_gbitmap_spalding_data_row_infos != bmp->data_row_infos); // union with .palette
bmp = gbitmap_create_blank(s10, GBitmapFormat4BitPalette);
cl_assert(NULL != bmp);
cl_assert(g_gbitmap_spalding_data_row_infos != bmp->data_row_infos); // union with .palette
bmp = gbitmap_create_blank(s10, GBitmapFormat8BitCircular);
cl_assert(NULL == bmp);
bmp = gbitmap_create_blank(s180, GBitmapFormat8BitCircular);
cl_assert(NULL == bmp);
#endif
#ifdef PLATFORM_SPALDING
bmp = gbitmap_create_blank(s10, GBitmapFormat1Bit);
cl_assert(NULL != bmp);
cl_assert(NULL == bmp->data_row_infos);
bmp = gbitmap_create_blank(s10, GBitmapFormat8Bit);
cl_assert(NULL != bmp);
cl_assert(NULL == bmp->data_row_infos);
bmp = gbitmap_create_blank(s10, GBitmapFormat1BitPalette);
cl_assert(NULL != bmp);
cl_assert(g_gbitmap_spalding_data_row_infos != bmp->data_row_infos); // union with .palette
bmp = gbitmap_create_blank(s10, GBitmapFormat2BitPalette);
cl_assert(NULL != bmp);
cl_assert(g_gbitmap_spalding_data_row_infos != bmp->data_row_infos); // union with .palette
bmp = gbitmap_create_blank(s10, GBitmapFormat4BitPalette);
cl_assert(NULL != bmp);
cl_assert(g_gbitmap_spalding_data_row_infos != bmp->data_row_infos); // union with .palette
bmp = gbitmap_create_blank(s10, GBitmapFormat8BitCircular);
cl_assert(NULL == bmp);
bmp = gbitmap_create_blank(s180, GBitmapFormat8BitCircular);
cl_assert(NULL != bmp);
cl_assert(g_gbitmap_spalding_data_row_infos == bmp->data_row_infos);
#endif
}
void test_gbitmap_formats__create_blank_with_palette(void) {
const GSize s10 = GSize(10, 10);
const GSize s180 = GSize(180, 180);
GBitmap *bmp;
GColor8 *p = (GColor8 *)&p; // some value to test against
#ifdef PLATFORM_TINTIN
cl_assert(!process_manager_compiled_with_legacy2_sdk());
cl_assert(NULL == gbitmap_create_blank_with_palette(s10, GBitmapFormat1Bit, p, true));
cl_assert(NULL == gbitmap_create_blank_with_palette(s10, GBitmapFormat8Bit, p, true));
bmp = gbitmap_create_blank_with_palette(s10, GBitmapFormat1BitPalette, p, true);
cl_assert(NULL != bmp);
cl_assert(p == gbitmap_get_palette(bmp));
bmp = gbitmap_create_blank_with_palette(s10, GBitmapFormat2BitPalette, p, true);
cl_assert(NULL != bmp);
cl_assert(p == gbitmap_get_palette(bmp));
bmp = gbitmap_create_blank_with_palette(s10, GBitmapFormat4BitPalette, p, true);
cl_assert(NULL == bmp);
cl_assert(NULL == gbitmap_create_blank_with_palette(s10, GBitmapFormat8BitCircular, p, true));
cl_assert(NULL == gbitmap_create_blank_with_palette(s180, GBitmapFormat8BitCircular, p, true));
#endif
#ifdef PLATFORM_SNOWY
cl_assert(NULL == gbitmap_create_blank_with_palette(s10, GBitmapFormat1Bit, p, true));
cl_assert(NULL == gbitmap_create_blank_with_palette(s10, GBitmapFormat8Bit, p, true));
bmp = gbitmap_create_blank_with_palette(s10, GBitmapFormat1BitPalette, p, true);
cl_assert(NULL != bmp);
cl_assert(p == gbitmap_get_palette(bmp));
bmp = gbitmap_create_blank_with_palette(s10, GBitmapFormat2BitPalette, p, true);
cl_assert(NULL != bmp);
cl_assert(p == gbitmap_get_palette(bmp));
bmp = gbitmap_create_blank_with_palette(s10, GBitmapFormat4BitPalette, p, true);
cl_assert(NULL != bmp);
cl_assert(p == gbitmap_get_palette(bmp));
cl_assert(NULL == gbitmap_create_blank_with_palette(s10, GBitmapFormat8BitCircular, p, true));
cl_assert(NULL == gbitmap_create_blank_with_palette(s180, GBitmapFormat8BitCircular, p, true));
#endif
#ifdef PLATFORM_SPALDING
cl_assert(NULL == gbitmap_create_blank_with_palette(s10, GBitmapFormat1Bit, p, true));
cl_assert(NULL == gbitmap_create_blank_with_palette(s10, GBitmapFormat8Bit, p, true));
bmp = gbitmap_create_blank_with_palette(s10, GBitmapFormat1BitPalette, p, true);
cl_assert(NULL != bmp);
cl_assert(p == gbitmap_get_palette(bmp));
bmp = gbitmap_create_blank_with_palette(s10, GBitmapFormat2BitPalette, p, true);
cl_assert(NULL != bmp);
cl_assert(p == gbitmap_get_palette(bmp));
bmp = gbitmap_create_blank_with_palette(s10, GBitmapFormat4BitPalette, p, true);
cl_assert(NULL != bmp);
cl_assert(p == gbitmap_get_palette(bmp));
cl_assert(NULL == gbitmap_create_blank_with_palette(s10, GBitmapFormat8BitCircular, p, true));
cl_assert(NULL == gbitmap_create_blank_with_palette(s180, GBitmapFormat8BitCircular, p, true));
#endif
}
void test_gbitmap_formats__display_framebuffer_bytes(void) {
#ifdef PLATFORM_TINTIN
const size_t expected = 20 * 168; // 20 * 8 == 144px + 2 bytes padding per scanline
#endif
#ifdef PLATFORM_SNOWY
const size_t expected = 144 * 168;
#endif
#ifdef PLATFORM_SPALDING
// all pixels + 2*76
const size_t expected = 25944;
#endif
cl_assert_equal_i(expected, DISPLAY_FRAMEBUFFER_BYTES);
}
size_t prv_gbitmap_size_for_data(GSize size, GBitmapFormat format);
void test_gbitmap_formats__size_for_data(void) {
cl_assert_equal_i( 40, prv_gbitmap_size_for_data(GSize(13, 10), GBitmapFormat1Bit));
cl_assert_equal_i(130, prv_gbitmap_size_for_data(GSize(13, 10), GBitmapFormat8Bit));
cl_assert_equal_i( 20, prv_gbitmap_size_for_data(GSize(13, 10), GBitmapFormat1BitPalette));
cl_assert_equal_i( 40, prv_gbitmap_size_for_data(GSize(13, 10), GBitmapFormat2BitPalette));
cl_assert_equal_i( 70, prv_gbitmap_size_for_data(GSize(13, 10), GBitmapFormat4BitPalette));
cl_assert_equal_i( 0, prv_gbitmap_size_for_data(GSize(13, 10), GBitmapFormat8BitCircular));
const size_t expected = PBL_IF_RECT_ELSE(0, DISPLAY_FRAMEBUFFER_BYTES);
cl_assert_equal_i(expected,
prv_gbitmap_size_for_data(GSize(180, 180), GBitmapFormat8BitCircular));
cl_assert_equal_i(expected,
prv_gbitmap_size_for_data(GSize(DISP_COLS, DISP_ROWS), GBitmapFormat8BitCircular));
}
+302
View File
@@ -0,0 +1,302 @@
/*
* 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 "clar.h"
#include "applib/graphics/framebuffer.h"
// Stubs
///////////////////////
#include "stubs_app_state.h"
#include "stubs_graphics_circle.h"
#include "stubs_graphics_line.h"
#include "stubs_graphics_private.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_process_manager.h"
const GDrawRawImplementation g_default_draw_implementation;
// Statics
///////////////////////
static GContext s_ctx;
static FrameBuffer s_fb;
typedef struct MockBitbltBitmapIntoBitmapTiledCallRecording {
GBitmap *dest_bitmap;
const GBitmap *src_bitmap;
GRect dest_rect;
GPoint src_origin_offset;
GCompOp compositing_mode;
GColor8 tint_color;
} MockBitbltBitmapIntoBitmapTiledCallRecording;
typedef struct MockBitbltBitmapIntoBitmapTiledCallRecordings {
unsigned int call_count;
MockBitbltBitmapIntoBitmapTiledCallRecording last_call;
} MockBitbltBitmapIntoBitmapTiledCallRecordings;
static MockBitbltBitmapIntoBitmapTiledCallRecordings s_bitblt_bitmap_into_bitmap_tiled_calls;
// Fakes
///////////////////////
GContext *graphics_context_get_current_context(void) {
return &s_ctx;
}
void bitblt_bitmap_into_bitmap_tiled(GBitmap* dest_bitmap, const GBitmap *src_bitmap,
GRect dest_rect, GPoint src_origin_offset,
GCompOp compositing_mode, GColor8 tint_color) {
s_bitblt_bitmap_into_bitmap_tiled_calls.call_count++;
s_bitblt_bitmap_into_bitmap_tiled_calls.last_call =
(MockBitbltBitmapIntoBitmapTiledCallRecording) {
.dest_bitmap = dest_bitmap,
.src_bitmap = src_bitmap,
.dest_rect = dest_rect,
.src_origin_offset = src_origin_offset,
.compositing_mode = compositing_mode,
.tint_color = tint_color,
};
}
// Helpers
///////////////////////
static void cl_assert_equal_rect(const GRect a, const GRect b) {
cl_assert_equal_i(a.origin.x, b.origin.x);
cl_assert_equal_i(a.origin.y, b.origin.y);
cl_assert_equal_i(a.size.w, b.size.w);
cl_assert_equal_i(a.size.h, b.size.h);
}
// Setup
///////////////////////
void test_gbitmap_processor__initialize(void) {
s_fb = (FrameBuffer) {};
framebuffer_init(&s_fb, &(GSize) {DISP_COLS, DISP_ROWS});
graphics_context_init(&s_ctx, &s_fb, GContextInitializationMode_App);
s_bitblt_bitmap_into_bitmap_tiled_calls = (MockBitbltBitmapIntoBitmapTiledCallRecordings) {};
}
void test_gbitmap_processor__cleanup(void) {
}
// Tests
///////////////////////
#define EXPECTED_RECT_IN_PRE_FUNCTION (GRect(4, 3, 2, 1))
void test_gbitmap_processor__null_arguments(void) {
GBitmap bitmap = {0};
const GRect rect = EXPECTED_RECT_IN_PRE_FUNCTION;
// Passing NULL for the processor shouldn't cause any problems
graphics_draw_bitmap_in_rect_processed(&s_ctx, &bitmap, &rect, NULL);
// And it should try to draw the bitmap
cl_assert_equal_i(s_bitblt_bitmap_into_bitmap_tiled_calls.call_count, 1);
// Passing a processor with NULL functions shouldn't cause any problems
GBitmapProcessor processor = {0};
graphics_draw_bitmap_in_rect_processed(&s_ctx, &bitmap, &rect, &processor);
// And it should once again try to draw the bitmap
cl_assert_equal_i(s_bitblt_bitmap_into_bitmap_tiled_calls.call_count, 2);
}
#define EXPECTED_COMPOSITING_MODE_BEFORE_AND_AFTER_PRE_FUNCTION (GCompOpSet)
#define EXPECTED_TINT_COLOR_BEFORE_AND_AFTER_PRE_FUNCTION (GColorShockingPink)
#define COMPOSITING_MODE_TO_SPECIFY_IN_PRE_FUNCTION (GCompOpTint)
#define TINT_COLOR_TO_SPECIFY_IN_PRE_FUNCTION (GColorTiffanyBlue)
#define RECT_TO_SPECIFY_IN_PRE_FUNCTION (GRect(-50, -50, 100, 100))
#define BITMAP_TO_SPECIFY_IN_PRE_FUNCTION ((GBitmap *)1234)
#define EXPECTED_CLIPPED_RECT_AFTER_DRAWING_BITMAP (GRect(0, 0, 50, 50))
typedef struct PreAndPostFunctionsTestProcessor {
GBitmapProcessor processor;
GCompOp previous_compositing_mode;
GColor previous_tint_color;
} PreAndPostFunctionsTestProcessor;
static void prv_pre_and_post_functions__pre(GBitmapProcessor *processor, GContext *ctx,
const GBitmap **bitmap_to_use,
GRect *global_grect_to_use) {
PreAndPostFunctionsTestProcessor *processor_with_data =
(PreAndPostFunctionsTestProcessor *)processor;
// Record the existing compositing mode and tint color and check that they are what we expect
cl_assert(ctx->draw_state.compositing_mode ==
EXPECTED_COMPOSITING_MODE_BEFORE_AND_AFTER_PRE_FUNCTION);
processor_with_data->previous_compositing_mode = ctx->draw_state.compositing_mode;
cl_assert(gcolor_equal(ctx->draw_state.tint_color,
EXPECTED_TINT_COLOR_BEFORE_AND_AFTER_PRE_FUNCTION));
processor_with_data->previous_tint_color = ctx->draw_state.tint_color;
// Set the compositing mode and tint color to different values
ctx->draw_state.compositing_mode = COMPOSITING_MODE_TO_SPECIFY_IN_PRE_FUNCTION;
ctx->draw_state.tint_color = TINT_COLOR_TO_SPECIFY_IN_PRE_FUNCTION;
// Check that the rect here is what we gave to graphics_draw_bitmap_in_rect_processed()
cl_assert_equal_rect(*global_grect_to_use, EXPECTED_RECT_IN_PRE_FUNCTION);
// Change the rect
*global_grect_to_use = RECT_TO_SPECIFY_IN_PRE_FUNCTION;
// Change the bitmap
*bitmap_to_use = BITMAP_TO_SPECIFY_IN_PRE_FUNCTION;
}
static void prv_pre_and_post_functions__post(GBitmapProcessor *processor, GContext *ctx,
const GBitmap *bitmap_used,
const GRect *global_clipped_grect_used) {
PreAndPostFunctionsTestProcessor *processor_with_data =
(PreAndPostFunctionsTestProcessor *)processor;
// Check that the changes made to the GContext in .pre are still present
cl_assert(ctx->draw_state.compositing_mode == COMPOSITING_MODE_TO_SPECIFY_IN_PRE_FUNCTION);
cl_assert(gcolor_equal(ctx->draw_state.tint_color, TINT_COLOR_TO_SPECIFY_IN_PRE_FUNCTION));
// Reverse the changes to the GContext that were made in .pre
ctx->draw_state.compositing_mode = processor_with_data->previous_compositing_mode;
ctx->draw_state.tint_color = processor_with_data->previous_tint_color;
// Check that the bitmap here is the bitmap we specified in the .pre function
cl_assert_equal_p(bitmap_used, BITMAP_TO_SPECIFY_IN_PRE_FUNCTION);
// Check that the rect here is the clipped version of the rect we specified in the .pre function
cl_assert_equal_rect(*global_clipped_grect_used, EXPECTED_CLIPPED_RECT_AFTER_DRAWING_BITMAP);
}
void test_gbitmap_processor__pre_and_post_functions(void) {
GBitmap bitmap = {0};
const GRect rect = EXPECTED_RECT_IN_PRE_FUNCTION;
// Set the compositing mode and tint color to known values
s_ctx.draw_state.compositing_mode = EXPECTED_COMPOSITING_MODE_BEFORE_AND_AFTER_PRE_FUNCTION;
s_ctx.draw_state.tint_color = EXPECTED_TINT_COLOR_BEFORE_AND_AFTER_PRE_FUNCTION;
PreAndPostFunctionsTestProcessor processor = (PreAndPostFunctionsTestProcessor) {
.processor.pre = prv_pre_and_post_functions__pre,
.processor.post = prv_pre_and_post_functions__post,
};
graphics_draw_bitmap_in_rect_processed(&s_ctx, &bitmap, &rect, &processor.processor);
// Check that the bitmap was drawn
cl_assert_equal_i(s_bitblt_bitmap_into_bitmap_tiled_calls.call_count, 1);
// Check that the modifications made in the .pre function propagated to the bitmap drawing
cl_assert(s_bitblt_bitmap_into_bitmap_tiled_calls.last_call.compositing_mode ==
COMPOSITING_MODE_TO_SPECIFY_IN_PRE_FUNCTION);
cl_assert(gcolor_equal(s_bitblt_bitmap_into_bitmap_tiled_calls.last_call.tint_color,
TINT_COLOR_TO_SPECIFY_IN_PRE_FUNCTION));
cl_assert_equal_rect(s_bitblt_bitmap_into_bitmap_tiled_calls.last_call.dest_rect,
EXPECTED_CLIPPED_RECT_AFTER_DRAWING_BITMAP);
cl_assert_equal_p(s_bitblt_bitmap_into_bitmap_tiled_calls.last_call.src_bitmap,
BITMAP_TO_SPECIFY_IN_PRE_FUNCTION);
// Check that the modifications made to the GContext in the .pre function were reversed in .post
cl_assert(s_ctx.draw_state.compositing_mode ==
EXPECTED_COMPOSITING_MODE_BEFORE_AND_AFTER_PRE_FUNCTION);
cl_assert(gcolor_equal(s_ctx.draw_state.tint_color,
EXPECTED_TINT_COLOR_BEFORE_AND_AFTER_PRE_FUNCTION));
// Note that additional checks are performed in the .pre and .post functions
}
typedef struct PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor {
GBitmapProcessor processor;
const GBitmap *expected_bitmap_in_post;
bool post_func_called;
} PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor;
static void post_function_called_even_if_pre_function_causes_nothing_to_be_drawn__post(
GBitmapProcessor *processor, GContext *ctx, const GBitmap *bitmap_used,
const GRect *global_clipped_grect_used) {
PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor *processor_with_data =
(PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor *)processor;
// Check that the rectangle here is empty to verify that nothing was drawn
cl_assert_equal_rect(*global_clipped_grect_used, GRectZero);
// Check that bitmap_used is what we expect it to be (the expected value is set in .pre)
cl_assert_equal_p(bitmap_used, processor_with_data->expected_bitmap_in_post);
// Record that the .post function was called
processor_with_data->post_func_called = true;
}
//! Helper function for testing that the .post function is called even if the .pre function
//! causes no bitmap to be drawn
static void prv_post_function_called_even_if_pre_function_causes_nothing_to_be_drawn_test(
GBitmapProcessorPreFunc pre_func) {
GBitmap bitmap = {0};
const GRect rect = EXPECTED_RECT_IN_PRE_FUNCTION;
PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor processor =
(PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor) {
.processor.pre = pre_func,
.processor.post = post_function_called_even_if_pre_function_causes_nothing_to_be_drawn__post,
};
graphics_draw_bitmap_in_rect_processed(&s_ctx, &bitmap, &rect, &processor.processor);
// Check that the bitmap was not drawn
cl_assert_equal_i(s_bitblt_bitmap_into_bitmap_tiled_calls.call_count, 0);
// Check that the .post function was called even though the .pre function makes some change
// that causes no bitmap to be drawn
cl_assert(processor.post_func_called);
}
static void post_function_called_even_if_pre_function_specifies_null_bitmap__pre(
GBitmapProcessor *processor, GContext *ctx, const GBitmap **bitmap_to_use,
GRect *global_grect_to_use) {
PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor *processor_with_data =
(PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor *)processor;
// Change the bitmap to use to NULL to cause nothing to be drawn
*bitmap_to_use = NULL;
// We'll expect the bitmap we set there to be the bitmap passed into .post
processor_with_data->expected_bitmap_in_post = *bitmap_to_use;
}
void test_gbitmap_processor__post_function_called_even_if_pre_function_specifies_null_bitmap(void) {
prv_post_function_called_even_if_pre_function_causes_nothing_to_be_drawn_test(
post_function_called_even_if_pre_function_specifies_null_bitmap__pre);
};
static void post_function_called_even_if_pre_function_specifies_empty_rect__pre(
GBitmapProcessor *processor, GContext *ctx, const GBitmap **bitmap_to_use,
GRect *global_grect_to_use) {
PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor *processor_with_data =
(PostFunctionCalledEvenIfPreFunctionCausesNothingToBeDrawnTestProcessor *)processor;
// Change the rectangle to be empty to cause nothing to be drawn
*global_grect_to_use = GRectZero;
// We'll expect the bitmap we passed into graphics_draw_bitmap_in_rect_processed() in .post
// even though nothing will be drawn
processor_with_data->expected_bitmap_in_post = *bitmap_to_use;
}
void test_gbitmap_processor__post_function_called_even_if_pre_function_specifies_empty_rect(void) {
prv_post_function_called_even_if_pre_function_causes_nothing_to_be_drawn_test(
post_function_called_even_if_pre_function_specifies_empty_rect__pre);
};
@@ -0,0 +1,170 @@
/*
* 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 "clar.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/gbitmap_pbi.h"
#include "stubs_app_state.h"
#include "stubs_graphics_context.h"
#include "stubs_logging.h"
#include "stubs_process_manager.h"
#include "stubs_passert.h"
// Stubs
///////////////////////
bool gbitmap_init_with_png_data(GBitmap *bitmap, const uint8_t *data, size_t data_size) {
return false;
}
bool gbitmap_png_data_is_png(const uint8_t *data, size_t data_size) {
return false;
}
ResAppNum sys_get_current_resource_num(void) {
return 0;
}
const uint8_t *sys_resource_read_only_bytes(ResAppNum app_num, uint32_t resource_id,
size_t *num_bytes_out) {
return NULL;
}
// Fakes
///////////////////////
size_t s_resource_size;
size_t sys_resource_size(ResAppNum app_num, uint32_t resource_id) {
return s_resource_size;
}
typedef struct {
uint16_t row_size_bytes;
union {
uint16_t info_flags;
BitmapInfo info;
};
uint16_t width;
uint16_t height;
} FakeBitmapData;
FakeBitmapData s_fake_bitmap_data;
size_t sys_resource_load_range(
ResAppNum app_num, uint32_t id, uint32_t start_offset, uint8_t *data, size_t num_bytes) {
BitmapData *bitmap = (BitmapData*) data;
*bitmap = (BitmapData) {
.row_size_bytes = s_fake_bitmap_data.row_size_bytes,
.info_flags = s_fake_bitmap_data.info_flags,
.width = s_fake_bitmap_data.width,
.height = s_fake_bitmap_data.height
};
return num_bytes;
}
// Tests
///////////////////////
void test_gbitmap_resource_validation__initialize(void) {
s_resource_size = 0;
s_fake_bitmap_data = (FakeBitmapData) { 0 };
}
static uint32_t prv_calculate_size(FakeBitmapData *bitmap) {
const uint32_t required_size_bytes =
offsetof(BitmapData, data) + // header size
(bitmap->row_size_bytes * bitmap->height) + // pixel data
gbitmap_get_palette_size(bitmap->info.format); // palette data
return required_size_bytes;
}
void test_gbitmap_resource_validation__total_size(void) {
s_fake_bitmap_data = (FakeBitmapData) {
.row_size_bytes = 8,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_1,
.width = 8,
.height = 1
};
// Set the resource size to be valid.
s_resource_size = prv_calculate_size(&s_fake_bitmap_data);
// We should load it successfully
GBitmap bitmap;
cl_assert(gbitmap_init_with_resource_system(&bitmap, 0, 0));
// However, if we corrupt the row_size_bytes field we should fail.
s_fake_bitmap_data.row_size_bytes = 12;
cl_assert(!gbitmap_init_with_resource_system(&bitmap, 0, 0));
// Corrupt it the other way, so that there's not enough data
s_fake_bitmap_data.row_size_bytes = 4;
cl_assert(!gbitmap_init_with_resource_system(&bitmap, 0, 0));
// Fix it up again
s_fake_bitmap_data.row_size_bytes = 8;
cl_assert(gbitmap_init_with_resource_system(&bitmap, 0, 0));
// But now change the palette format to something that requires more space and watch it fail
s_fake_bitmap_data.info.format = GBitmapFormat4BitPalette;
cl_assert(!gbitmap_init_with_resource_system(&bitmap, 0, 0));
// But if we have space for the palette, it should pass
s_resource_size = prv_calculate_size(&s_fake_bitmap_data);
cl_assert(gbitmap_init_with_resource_system(&bitmap, 0, 0));
}
void test_gbitmap_resource_validation__row_size(void) {
s_fake_bitmap_data = (FakeBitmapData) {
.row_size_bytes = 8,
.info.format = GBitmapFormat8Bit,
.info.version = GBITMAP_VERSION_1,
.width = 8,
.height = 1
};
// Set the resource size to be valid.
s_resource_size = prv_calculate_size(&s_fake_bitmap_data);
// We should load it successfully
GBitmap bitmap;
cl_assert(gbitmap_init_with_resource_system(&bitmap, 0, 0));
// Vary the width without changing the height to be too large
s_fake_bitmap_data.width = 10,
cl_assert(!gbitmap_init_with_resource_system(&bitmap, 0, 0));
// Too small is fine though
s_fake_bitmap_data.width = 6,
cl_assert(gbitmap_init_with_resource_system(&bitmap, 0, 0));
// Test with an uneven number of bits and make sure we're rounding correctly
s_fake_bitmap_data.info.format = GBitmapFormat1Bit;
s_fake_bitmap_data.width = 64;
cl_assert(gbitmap_init_with_resource_system(&bitmap, 0, 0));
s_fake_bitmap_data.info.format = GBitmapFormat1Bit;
s_fake_bitmap_data.width = 65;
cl_assert(!gbitmap_init_with_resource_system(&bitmap, 0, 0));
s_fake_bitmap_data.info.format = GBitmapFormat1Bit;
s_fake_bitmap_data.width = 63;
cl_assert(gbitmap_init_with_resource_system(&bitmap, 0, 0));
}
+345
View File
@@ -0,0 +1,345 @@
/*
* 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/gbitmap_sequence.h"
#include "applib/graphics/graphics.h"
#include "clar.h"
#include "util.h"
#include <util/size.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
// Test files are Creative Commons 0 (ie. Public Domain) from
// http://opengameart.org/content/game-character-blue-flappy-bird-sprite-sheets
// http://opengameart.org/content/pixel-puncher-sprites
// http://opengameart.org/content/spike-man-monster
// http://opengameart.org/content/platformer-baddies
// http://opengameart.org/content/greyscale-special-effects-%E2%80%94-various-dimensions-and-flavours
// http://opengameart.org/content/bouncing-ball-guy
// http://opengameart.org/content/medals-3
// http://opengameart.org/content/open-pixel-platformer-tiles-sprites
#if SCREEN_COLOR_DEPTH_BITS == 8
#include "applib/graphics/8_bit/framebuffer.c"
#elif SCREEN_COLOR_DEPTH_BITS == 1
#include "applib/graphics/1_bit/framebuffer.c"
#endif
// Fakes
////////////////////////////////////
#include "fake_resource_syscalls.h"
#include "fake_app_timer.h"
// Stubs
////////////////////////////////////
#include "stubs_applib_resource.h"
#include "stubs_app_state.h"
#include "stubs_logging.h"
#include "stubs_heap.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_print.h"
#include "stubs_queue.h"
#include "stubs_resources.h"
#include "stubs_serial.h"
#include "stubs_ui_window.h"
#define GET_PBI_NAME(x) prv_get_image_name(__func__, x, "pbi")
#define GET_APNG_NAME prv_get_image_name(__func__, 0, "apng")
// Used to work around __func__ not being a string literal (necessary for macro concatenation)
static const char *prv_get_image_name(const char* func_name, int index, const char *extension) {
char *filename = malloc(PATH_STRING_LENGTH);
if (index) {
snprintf(filename, PATH_STRING_LENGTH, "%s_%u.%s", func_name, index, extension);
} else {
snprintf(filename, PATH_STRING_LENGTH, "%s.%s", func_name, extension);
}
return filename;
}
// Tests
////////////////////////////////////
// Reference PNGs reside in "tests/test_images/"
// and are created at build time, with the files copied to TEST_IMAGES_PATH
// and a separate PBI generated by bitmapgen.py from the PNG copied to TEST_IMAGES_PATH
// Tests APNG file with 2-bit color, 6 frames and loop = infinite
// Tests start, stop, reset and loop
// Result:
// - gbitmaps matches platform decoded APNG
void test_gbitmap_sequence__color_2bit_bouncing_ball(void) {
#if PLATFROM_SPALDING
uint32_t resource_id = sys_resource_load_file_as_resource(TEST_IMAGES_PATH, GET_APNG_NAME);
cl_assert(resource_id != UINT32_MAX);
GBitmapSequence *bitmap_sequence = gbitmap_sequence_create_with_resource(resource_id);
cl_assert(bitmap_sequence);
GBitmap *bitmap = gbitmap_create_blank(gbitmap_sequence_get_bitmap_size(bitmap_sequence),
GBitmapFormat8Bit);
cl_assert(bitmap);
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(1)));
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(2)));
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(3)));
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(4)));
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(5)));
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(6)));
// Test loop around
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(1)));
#endif
}
// Tests APNG file with 8-bit color, 88 frames of delay 100ms each and loop = 2
// Tests gbitmap_sequence_update_bitmap_by_elapsed
// Result:
// - gbitmaps matches platform decoded APNG
void test_gbitmap_sequence__color_8bit_fight(void) {
#if PLATFORM_SPALDING
bool status = false;
uint32_t resource_id = sys_resource_load_file_as_resource(TEST_IMAGES_PATH, GET_APNG_NAME);
cl_assert(resource_id != UINT32_MAX);
GBitmapSequence *bitmap_sequence = gbitmap_sequence_create_with_resource(resource_id);
cl_assert(bitmap_sequence);
GBitmap *bitmap = gbitmap_create_blank(gbitmap_sequence_get_bitmap_size(bitmap_sequence),
GBitmapFormat8Bit);
cl_assert(bitmap);
// first frame always shows at the beginning (0 ms)
status = gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 0);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(1)));
// each frame has delay 100ms, so (33 - 1) * 100 ms shows 33rd frame
status = gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, (33 - 1) * 100);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(33)));
status = gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, (60 - 1) * 100);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(60)));
// Since we have loop count 2 and 88 frames, see if we can go to elapsed at 2nd loop correctly
status = gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap,
88 * 100 + (49 - 1) * 100);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(49)));
// Since loop only equals 2, make sure that huge elapsed time leaves us at final frame
status = gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 12345 * 100);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(88)));
#endif
}
// Tests APNG file with 8-bit color, 5 frames and loop = 1
// Tests gbitmap_sequence_update_bitmap_by_elapsed with 0 delay frames for 1, 4 and 5
// and 2 & 3 have delay 1000ms
// Result:
// - gbitmaps matches platform decoded APNG
void test_gbitmap_sequence__color_8bit_coin(void) {
#if PLATFORM_SPALDING
uint32_t resource_id = sys_resource_load_file_as_resource(TEST_IMAGES_PATH, GET_APNG_NAME);
cl_assert(resource_id != UINT32_MAX);
GBitmapSequence *bitmap_sequence = gbitmap_sequence_create_with_resource(resource_id);
cl_assert(bitmap_sequence);
GBitmap *bitmap = gbitmap_create_blank(gbitmap_sequence_get_bitmap_size(bitmap_sequence),
GBitmapFormat8Bit);
cl_assert(bitmap);
// Since frame 1 has 0 delay, it renders then immediately we render frame 2
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 0);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(2)));
// Frame 2 has delay 1000 ms, so at time 1000 ms, we expect frame 3
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 1 * 1000);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(3)));
// Frame 3 has delay 1000 ms, frame 4 and 5 have zero delay, so at time 2000 ms == frame 5
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 2 * 1000);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(5)));
// Since loop only equals 1, make sure that huge elapsed time leaves us at final frame
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 12345 * 1000);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(5)));
#endif
}
// Tests APNG file with 8-bit color, 5 frames and loop = 1
// Tests gbitmap_sequence_update_bitmap_by_elapsed with 0 delay frames for 1, 4 and 5
// and 2 & 3 have delay 1000ms
// Result:
// - gbitmaps matches platform decoded APNG
void test_gbitmap_sequence__color_8bit_coin_round(void) {
#if PLATFORM_SPALDING
uint32_t resource_id = sys_resource_load_file_as_resource(
TEST_IMAGES_PATH, "test_gbitmap_sequence__color_8bit_coin.apng");
cl_assert(resource_id != UINT32_MAX);
GBitmapSequence *bitmap_sequence = gbitmap_sequence_create_with_resource(resource_id);
cl_assert(bitmap_sequence);
GBitmap *bitmap = gbitmap_create_blank(GSize(180, 180), GBitmapFormat8BitCircular);
cl_assert(bitmap);
// Since frame 1 has 0 delay, it renders then immediately we render frame 2
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 0);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(2)));
// Frame 2 has delay 1000 ms, so at time 1000 ms, we expect frame 3
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 1 * 1000);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(3)));
// Frame 3 has delay 1000 ms, frame 4 and 5 have zero delay, so at time 2000 ms == frame 5
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 2 * 1000);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(5)));
// Since loop only equals 1, make sure that huge elapsed time leaves us at final frame
gbitmap_sequence_update_bitmap_by_elapsed(bitmap_sequence, bitmap, 12345 * 1000);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(5)));
#endif
}
// Tests APNG file with 8-bit color, 5 frames and loop infinite, size 64x64
// Tests gbitmap_sequence for bitmap bounds offset and DISPOSE_OP_BACKGROUND
// Result:
// - gbitmaps matches platform decoded APNG
void test_gbitmap_sequence__color_8bit_bounds(void) {
#if PLATFORM_SPALDING
uint32_t resource_id = sys_resource_load_file_as_resource(TEST_IMAGES_PATH, GET_APNG_NAME);
cl_assert(resource_id != UINT32_MAX);
GRect orig_bounds = GRect(0, 0, 180, 180);
int xshift = 71;
int yshift = 39;
GRect shift_bounds = GRect(xshift, yshift,
orig_bounds.size.w - xshift,
orig_bounds.size.h - yshift);
GBitmapSequence *bitmap_sequence = gbitmap_sequence_create_with_resource(resource_id);
cl_assert(bitmap_sequence);
GBitmap *bitmap = gbitmap_create_blank(orig_bounds.size, GBitmapFormat8BitCircular);
cl_assert(bitmap);
// Shift the bounds when updating
gbitmap_set_bounds(bitmap, shift_bounds);
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
// set the original bounds to do whole-image comparison
gbitmap_set_bounds(bitmap, orig_bounds);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(1)));
gbitmap_set_bounds(bitmap, shift_bounds);
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
gbitmap_set_bounds(bitmap, orig_bounds);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(2)));
gbitmap_set_bounds(bitmap, shift_bounds);
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
gbitmap_set_bounds(bitmap, orig_bounds);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(3)));
gbitmap_set_bounds(bitmap, shift_bounds);
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
gbitmap_set_bounds(bitmap, orig_bounds);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(4)));
gbitmap_set_bounds(bitmap, shift_bounds);
gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
gbitmap_set_bounds(bitmap, orig_bounds);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(5)));
#endif
}
// Tests APNG file with 8-bit color, originally broke the updated upng+tinflate code
// Result:
// - gbitmaps matches platform decoded APNG
void test_gbitmap_sequence__color_8bit_yoshi(void) {
#if PLATFORM_SPALDING
bool status = false;
uint32_t resource_id = sys_resource_load_file_as_resource(TEST_IMAGES_PATH, GET_APNG_NAME);
cl_assert(resource_id != UINT32_MAX);
GBitmapSequence *bitmap_sequence = gbitmap_sequence_create_with_resource(resource_id);
GBitmap *bitmap = gbitmap_create_blank(gbitmap_sequence_get_bitmap_size(bitmap_sequence),
GBitmapFormat8Bit);
status = gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(1)));
status = gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(2)));
status = gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_assert_equal_b(status, true);
cl_check(gbitmap_pbi_eq(bitmap, GET_PBI_NAME(3)));
#endif
}
void test_gbitmap_sequence__1bit_to_1bit_notification(void) {
uint32_t resource_id = sys_resource_load_file_as_resource(TEST_IMAGES_PATH, GET_APNG_NAME);
cl_assert(resource_id != UINT32_MAX);
GBitmapSequence *bitmap_sequence = gbitmap_sequence_create_with_resource(resource_id);
GBitmap *bitmap = gbitmap_create_blank(gbitmap_sequence_get_bitmap_size(bitmap_sequence),
GBitmapFormat1Bit);
// We're interested in checking the following frames
const int check_frames[] = { 1, 4, 23, 24, 25, 31, 75 };
int current_frame = 0;
for (int i = 0; i < ARRAY_LENGTH(check_frames); ++i) {
// Advance to the next frame we're interested in.
for (; current_frame < check_frames[i]; ++current_frame) {
bool status = gbitmap_sequence_update_bitmap_next_frame(bitmap_sequence, bitmap, NULL);
cl_assert_equal_b(status, true);
}
printf("Checking %u\n", current_frame);
// Don't use GET_PBI_NAME, it doesn't like not using anything other than an integer literal
char filename_buffer[128];
snprintf(filename_buffer, sizeof(filename_buffer), "%s_%u.pbi", __func__, current_frame);
cl_check(gbitmap_pbi_eq(bitmap, filename_buffer));
}
}
+815
View File
@@ -0,0 +1,815 @@
/*
* 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 "clar.h"
#include "applib/graphics/gdraw_command.h"
#include "applib/graphics/gdraw_command_list.h"
#include "applib/graphics/gdraw_command_image.h"
#include "applib/graphics/gdraw_command_private.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/graphics_line.h"
#include "applib/graphics/gpath.h"
#include <string.h>
#include "stubs_applib_resource.h"
#include "stubs_app_state.h"
#include "stubs_compiled_with_legacy2_sdk.h"
#include "stubs_heap.h"
#include "stubs_memory_layout.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_resources.h"
#include "stubs_syscalls.h"
extern size_t prv_get_list_max_command_size(GDrawCommandList *command_list);
static GColor s_fill_color;
static GColor s_stroke_color;
static uint8_t s_stroke_width;
static uint16_t s_path_num_points;
static GPoint *s_stroke_points = NULL;
static GPoint *s_fill_points = NULL;
static bool s_path_open;
static uint16_t s_radius;
struct PreciseLine {
GPointPrecise p0;
GPointPrecise p1;
} *s_precise_lines = NULL;
static int s_num_precise_lines;
static int s_path_stroke_count;
static int s_path_stroke_precise_count;
static int s_path_fill_count;
static int s_path_fill_precise_count;
static int s_circle_stroke_count;
static int s_circle_fill_count;
static GPoint s_offset;
static GPoint *prv_copy_points(GPoint *points, uint16_t num_points, GPoint offset) {
s_path_num_points = num_points;
GPoint *copied_points = malloc(num_points * sizeof(GPoint));
cl_assert(copied_points != NULL);
for (int i = 0; i < num_points; i++) {
copied_points[i] = gpoint_add(points[i], offset);
}
return copied_points;
}
static bool prv_compare_points(GPoint *a, GPoint *b, uint16_t num_points) {
return (memcmp(a, b, sizeof(GPoint) * num_points) == 0);
}
// Stubs
void graphics_context_set_stroke_color(GContext* ctx, GColor color) {
s_stroke_color = color;
}
void graphics_context_set_fill_color(GContext* ctx, GColor color) {
s_fill_color = color;
}
void graphics_context_set_antialiased(GContext *ctx, bool enable) {
}
void graphics_context_set_stroke_width(GContext* ctx, uint8_t stroke_width) {
s_stroke_width = stroke_width;
}
void gpath_draw_stroke(GContext* ctx, GPath* path, bool open) {
s_stroke_points = prv_copy_points(path->points, path->num_points, s_offset);
s_path_open = open;
s_path_stroke_count++;
}
void gpath_fill_precise_internal(GContext *ctx, GPointPrecise *points, size_t num_points) {
s_fill_points = prv_copy_points((GPoint*)points, num_points, s_offset);
s_path_fill_precise_count++;
}
void gpath_draw_filled(GContext* ctx, GPath *path) {
s_fill_points = prv_copy_points(path->points, path->num_points, s_offset);
s_path_fill_count++;
}
void gpath_draw_outline_precise_internal(GContext *ctx, GPointPrecise *points, size_t num_points,
bool open) {
s_stroke_points = prv_copy_points((GPoint*)points, num_points, s_offset);
s_path_open = open;
s_path_stroke_precise_count++;
}
void graphics_draw_circle(GContext* ctx, GPoint p, uint16_t radius) {
s_stroke_points = prv_copy_points(&p, 1, s_offset);
s_radius = radius;
s_circle_stroke_count++;
}
void graphics_fill_circle(GContext* ctx, GPoint p, uint16_t radius) {
s_fill_points = prv_copy_points(&p, 1, s_offset);
s_radius = radius;
s_circle_fill_count++;
}
void graphics_context_move_draw_box(GContext* ctx, GPoint offset) {
s_offset = offset;
}
void graphics_line_draw_precise_stroked(GContext* ctx, GPointPrecise p0, GPointPrecise p1) {
s_precise_lines = realloc(s_precise_lines,
(s_num_precise_lines + 1) * sizeof(*s_precise_lines));
s_precise_lines[s_num_precise_lines].p0 = p0;
s_precise_lines[s_num_precise_lines].p1 = p1;
s_num_precise_lines++;
}
void prv_reset(void) {
s_fill_color = GColorClear;
s_stroke_color = GColorClear;
s_stroke_width = 0;
s_path_num_points = 0;
if (s_stroke_points) {
free(s_stroke_points);
s_stroke_points = NULL;
}
if (s_fill_points) {
free(s_fill_points);
s_fill_points = NULL;
}
s_path_open = false;
s_radius = 0;
s_path_stroke_count = 0;
s_path_stroke_precise_count = 0;
s_path_fill_count = 0;
s_path_fill_precise_count = 0;
s_circle_stroke_count = 0;
s_circle_fill_count = 0;
s_offset = (GPoint){ 0 };
if (s_precise_lines) {
free(s_precise_lines);
s_precise_lines = NULL;
}
s_num_precise_lines = 0;
}
// setup and teardown
void test_gdraw_command__initialize(void) {
prv_reset();
}
void test_gdraw_command__cleanup(void) {
if (s_stroke_points) {
free(s_stroke_points);
s_stroke_points = NULL;
}
if (s_fill_points) {
free(s_fill_points);
s_fill_points = NULL;
}
if (s_precise_lines) {
free(s_precise_lines);
s_precise_lines = NULL;
}
}
// tests
void test_gdraw_command__draw_command_stroke(void) {
GDrawCommand *command = malloc(sizeof(GDrawCommand) + (sizeof(GPoint) * 2));
*command = (GDrawCommand){
.type = GDrawCommandTypePath,
.hidden = false,
.stroke_color = GColorRed,
.stroke_width = 1,
.fill_color = GColorBlue,
.path_open = false,
.num_points = 2,
};
GPoint points[] = {{ 3, 97 }, {5, 5} };
memcpy(command->points, points, sizeof(points));
gdraw_command_draw(NULL, command);
cl_assert_equal_i(s_stroke_color.argb, GColorRedARGB8);
cl_assert_equal_i(s_fill_color.argb, GColorBlueARGB8);
cl_assert_equal_i(s_stroke_width, 1);
cl_assert_equal_i(s_path_num_points, 2);
cl_assert_equal_b(s_path_open, false);
cl_assert_equal_i(s_path_fill_count, 1);
cl_assert_equal_i(s_path_stroke_count, 1);
cl_assert(prv_compare_points(points, s_stroke_points, s_path_num_points));
cl_assert(prv_compare_points(points, s_fill_points, s_path_num_points));
prv_reset();
// set stroke width to zero - fill should be drawn, but not outline
gdraw_command_set_stroke_width(command, 0);
gdraw_command_draw(NULL, command);
cl_assert_equal_i(s_stroke_color.argb, GColorClearARGB8);
cl_assert_equal_i(s_fill_color.argb, GColorBlueARGB8);
cl_assert_equal_i(s_stroke_width, 0);
cl_assert_equal_i(s_path_num_points, 2);
cl_assert_equal_b(s_path_open, false);
cl_assert_equal_i(s_path_fill_count, 1);
cl_assert_equal_i(s_path_stroke_count, 0);
cl_assert(prv_compare_points(points, s_fill_points, s_path_num_points));
cl_assert_equal_p(s_stroke_points, NULL);
prv_reset();
// make fill color transparent (nothing should be drawn because the stroke width is zero and the
// fill is transparent
GColor color = gdraw_command_get_fill_color(command);
color.a = 0;
gdraw_command_set_fill_color(command, color);
gdraw_command_draw(NULL, command);
cl_assert_equal_i(s_stroke_color.argb, GColorClearARGB8);
cl_assert_equal_i(s_fill_color.argb, GColorClearARGB8);
cl_assert_equal_i(s_stroke_width, 0);
cl_assert_equal_i(s_path_num_points, 0);
cl_assert_equal_b(s_path_open, false);
cl_assert_equal_i(s_path_fill_count, 0);
cl_assert_equal_i(s_path_stroke_count, 0);
cl_assert_equal_p(s_stroke_points, NULL);
cl_assert_equal_p(s_fill_points, NULL);
prv_reset();
// set stroke width to non-zero value. stroke should be drawn, but no fill
gdraw_command_set_stroke_width(command, 2);
gdraw_command_draw(NULL, command);
cl_assert_equal_i(s_stroke_color.argb, GColorRedARGB8);
cl_assert_equal_i(s_fill_color.argb, GColorClearARGB8);
cl_assert_equal_i(s_stroke_width, 2);
cl_assert_equal_i(s_path_num_points, 2);
cl_assert_equal_b(s_path_open, false);
cl_assert_equal_i(s_path_fill_count, 0);
cl_assert_equal_i(s_path_stroke_count, 1);
cl_assert(prv_compare_points(points, s_stroke_points, s_path_num_points));
cl_assert_equal_p(s_fill_points, NULL);
prv_reset();
// set stroke color to be transparent and restore fill - fill should be drawn, but no outline
// should be drawn
gdraw_command_set_fill_color(command, GColorGreen);
color = gdraw_command_get_stroke_color(command);
color.a = 0;
gdraw_command_set_stroke_color(command, color);
gdraw_command_draw(NULL, command);
cl_assert_equal_i(s_stroke_color.argb, GColorClearARGB8);
cl_assert_equal_i(s_fill_color.argb, GColorGreenARGB8);
cl_assert_equal_i(s_stroke_width, 0);
cl_assert_equal_i(s_path_num_points, 2);
cl_assert_equal_b(s_path_open, false);
cl_assert_equal_i(s_path_fill_count, 1);
cl_assert_equal_i(s_path_stroke_count, 0);
cl_assert_equal_p(s_stroke_points, NULL);
cl_assert(prv_compare_points(points, s_fill_points, s_path_num_points));
prv_reset();
// restore stroke color and change both points
gdraw_command_set_stroke_color(command, GColorPurple);
GPoint points2[] = { { 23, 45 }, { 67, 13} };
gdraw_command_set_point(command, 0, points2[0]);
gdraw_command_set_point(command, 1, points2[1]);
gdraw_command_draw(NULL, command);
cl_assert_equal_i(s_stroke_color.argb, GColorPurpleARGB8);
cl_assert_equal_i(s_fill_color.argb, GColorGreenARGB8);
cl_assert_equal_i(s_stroke_width, 2);
cl_assert_equal_i(s_path_num_points, 2);
cl_assert_equal_b(s_path_open, false);
cl_assert_equal_i(s_path_fill_count, 1);
cl_assert_equal_i(s_path_stroke_count, 1);
cl_assert(prv_compare_points(points2, s_stroke_points, s_path_num_points));
cl_assert(prv_compare_points(points2, s_fill_points, s_path_num_points));
prv_reset();
// set path to be open
gdraw_command_set_path_open(command, true);
gdraw_command_draw(NULL, command);
cl_assert_equal_i(s_stroke_color.argb, GColorPurpleARGB8);
cl_assert_equal_i(s_fill_color.argb, GColorGreenARGB8);
cl_assert_equal_i(s_stroke_width, 2);
cl_assert_equal_i(s_path_num_points, 2);
cl_assert_equal_b(s_path_open, true);
cl_assert_equal_i(s_path_fill_count, 1);
cl_assert_equal_i(s_path_stroke_count, 1);
cl_assert(prv_compare_points(points2, s_stroke_points, s_path_num_points));
cl_assert(prv_compare_points(points2, s_fill_points, s_path_num_points));
prv_reset();
// set command to be hidden - nothing should be drawn
gdraw_command_set_hidden(command, true);
cl_assert_equal_i(s_stroke_color.argb, GColorClearARGB8);
cl_assert_equal_i(s_fill_color.argb, GColorClearARGB8);
cl_assert_equal_i(s_stroke_width, 0);
cl_assert_equal_i(s_path_num_points, 0);
cl_assert_equal_b(s_path_open, false);
cl_assert_equal_i(s_path_fill_count, 0);
cl_assert_equal_i(s_path_stroke_count, 0);
cl_assert_equal_p(s_stroke_points, NULL);
cl_assert_equal_p(s_fill_points, NULL);
free(command);
}
void test_gdraw_command__draw_precise_path(void) {
GDrawCommand *command = malloc(sizeof(GDrawCommand) + (sizeof(GPoint) * 3 ));
*command = (GDrawCommand){
.type = GDrawCommandTypePrecisePath,
.hidden = false,
.stroke_color = GColorRed,
.stroke_width = 1,
.fill_color = GColorBlue,
.path_open = false,
.num_points = 3,
};
GPointPrecise points[] = {
{ .x.raw_value = 8, .y.raw_value = 17 },
{ .x.raw_value = 4, .y.raw_value = 16 },
{ .x.raw_value = 2,.y.raw_value = 7 }
};
memcpy(command->precise_points, points, sizeof(points));
gdraw_command_draw(NULL, command);
cl_assert_equal_i(1, s_path_fill_precise_count);
cl_assert(prv_compare_points((GPoint*)points, s_fill_points, s_path_num_points));
cl_assert_equal_i(1, s_path_stroke_precise_count);
cl_assert_equal_b(false, s_path_open);
cl_assert(prv_compare_points((GPoint*)points, s_stroke_points, s_path_num_points));
prv_reset();
// change to open path and ensure that only draws 2 lines
command->path_open = true;
gdraw_command_draw(NULL, command);
cl_assert_equal_i(1, s_path_stroke_precise_count);
cl_assert_equal_b(true, s_path_open);
cl_assert(prv_compare_points((GPoint*)points, s_stroke_points, s_path_num_points));
free(command);
}
void test_gdraw_command__draw_circle(void) {
GDrawCommand *command = malloc(sizeof(GDrawCommand) + sizeof(GPoint));
*command = (GDrawCommand){
.type = GDrawCommandTypeCircle,
.hidden = false,
.stroke_color = GColorGreen,
.stroke_width = 1,
.fill_color = GColorOrange,
.radius = 300,
.num_points = 1,
};
GPoint center = { 15, 17 };
command->points[0] = center;
gdraw_command_draw(NULL, command);
cl_assert_equal_i(s_stroke_color.argb, GColorGreenARGB8);
cl_assert_equal_i(s_fill_color.argb, GColorOrangeARGB8);
cl_assert_equal_i(s_stroke_width, 1);
cl_assert_equal_i(s_path_num_points, 1);
cl_assert_equal_b(s_radius, 300);
cl_assert_equal_i(s_circle_fill_count, 1);
cl_assert_equal_i(s_circle_stroke_count, 1);
cl_assert(prv_compare_points(&center, s_stroke_points, s_path_num_points));
cl_assert(prv_compare_points(&center, s_fill_points, s_path_num_points));
prv_reset();
// set stroke width to zero - fill should be drawn, but not outline
gdraw_command_set_stroke_width(command, 0);
gdraw_command_draw(NULL, command);
cl_assert_equal_i(s_stroke_color.argb, GColorClearARGB8);
cl_assert_equal_i(s_fill_color.argb, GColorOrangeARGB8);
cl_assert_equal_i(s_stroke_width, 0);
cl_assert_equal_i(s_path_num_points, 1);
cl_assert_equal_b(s_radius, 300);
cl_assert_equal_i(s_circle_fill_count, 1);
cl_assert_equal_i(s_circle_stroke_count, 0);
cl_assert_equal_p(s_stroke_points, NULL);
cl_assert(prv_compare_points(&center, s_fill_points, s_path_num_points));
prv_reset();
// make fill color transparent (nothing should be drawn because the stroke width is zero and the
// fill is transparent
GColor color = gdraw_command_get_fill_color(command);
color.a = 0;
gdraw_command_set_fill_color(command, color);
gdraw_command_draw(NULL, command);
cl_assert_equal_i(s_stroke_color.argb, GColorClearARGB8);
cl_assert_equal_i(s_fill_color.argb, GColorClearARGB8);
cl_assert_equal_i(s_stroke_width, 0);
cl_assert_equal_i(s_path_num_points, 0);
cl_assert_equal_b(s_radius, 0);
cl_assert_equal_i(s_circle_fill_count, 0);
cl_assert_equal_i(s_circle_stroke_count, 0);
cl_assert_equal_p(s_stroke_points, NULL);
cl_assert_equal_p(s_fill_points, NULL);
prv_reset();
// set stroke width to non-zero value. stroke should be drawn, but no fill
gdraw_command_set_stroke_width(command, 2);
gdraw_command_draw(NULL, command);
cl_assert_equal_i(s_stroke_color.argb, GColorGreenARGB8);
cl_assert_equal_i(s_fill_color.argb, GColorClearARGB8);
cl_assert_equal_i(s_stroke_width, 2);
cl_assert_equal_i(s_path_num_points, 1);
cl_assert_equal_b(s_radius, 300);
cl_assert_equal_i(s_circle_fill_count, 0);
cl_assert_equal_i(s_circle_stroke_count, 1);
cl_assert(prv_compare_points(&center, s_stroke_points, s_path_num_points));
cl_assert_equal_p(s_fill_points, NULL);
prv_reset();
// set stroke color to be transparent and restore fill - fill should be drawn, but no outline
// should be drawn
gdraw_command_set_fill_color(command, GColorRed);
color = gdraw_command_get_stroke_color(command);
color.a = 0;
gdraw_command_set_stroke_color(command, color);
gdraw_command_draw(NULL, command);
cl_assert_equal_i(s_stroke_color.argb, GColorClearARGB8);
cl_assert_equal_i(s_fill_color.argb, GColorRedARGB8);
cl_assert_equal_i(s_stroke_width, 0);
cl_assert_equal_i(s_path_num_points, 1);
cl_assert_equal_b(s_path_open, false);
cl_assert_equal_i(s_circle_fill_count, 1);
cl_assert_equal_i(s_circle_stroke_count, 0);
cl_assert_equal_p(s_stroke_points, NULL);
cl_assert(prv_compare_points(&center, s_fill_points, s_path_num_points));
prv_reset();
// restore stroke color and set radius to zero - only a stroke should be drawn
gdraw_command_set_stroke_color(command, GColorPurple);
gdraw_command_set_radius(command, 0);
gdraw_command_draw(NULL, command);
cl_assert_equal_i(s_stroke_color.argb, GColorPurpleARGB8);
cl_assert_equal_i(s_fill_color.argb, GColorClearARGB8);
cl_assert_equal_i(s_stroke_width, 2);
cl_assert_equal_i(s_path_num_points, 1);
cl_assert_equal_b(s_radius, 0);
cl_assert_equal_i(s_circle_fill_count, 0);
cl_assert_equal_i(s_circle_stroke_count, 1);
cl_assert(prv_compare_points(&center, s_stroke_points, s_path_num_points));
cl_assert_equal_p(s_fill_points, NULL);
prv_reset();
// restore radius and set hidden - nothing should be drawn
gdraw_command_set_radius(command, 300);
gdraw_command_set_hidden(command, true);
gdraw_command_draw(NULL, command);
cl_assert_equal_i(s_stroke_color.argb, GColorClearARGB8);
cl_assert_equal_i(s_fill_color.argb, GColorClearARGB8);
cl_assert_equal_i(s_stroke_width, 0);
cl_assert_equal_i(s_path_num_points, 0);
cl_assert_equal_b(s_radius, 0);
cl_assert_equal_i(s_circle_fill_count, 0);
cl_assert_equal_i(s_circle_stroke_count, 0);
cl_assert_equal_p(s_stroke_points, NULL);
cl_assert_equal_p(s_fill_points, NULL);
free(command);
}
static GDrawCommandList *prv_create_command_list_3(void) {
GDrawCommandList *command_list = malloc(sizeof(GDrawCommandList) + (3 * sizeof(GDrawCommand)) +
(sizeof(GPoint) * 6));
*command_list = (GDrawCommandList) {
.num_commands = 3
};
GDrawCommand *command;
command = gdraw_command_list_get_command(command_list, 0);
*command = (GDrawCommand) {
.type = GDrawCommandTypePath,
.hidden = false,
.stroke_color = GColorRed,
.stroke_width = 1,
.fill_color = GColorBlue,
.path_open = false,
.num_points = 2,
};
GPoint points1[] = { { 3, 97 }, {5, 5} };
memcpy(command_list->commands[0].points, points1, sizeof(points1));
command = gdraw_command_list_get_command(command_list, 1);
*command = (GDrawCommand) {
.type = GDrawCommandTypeCircle,
.hidden = false,
.stroke_color = GColorGreen,
.stroke_width = 1,
.fill_color = GColorOrange,
.radius = 300,
.num_points = 1,
};
command->points[0] = (GPoint) { 1, 2 };
command = gdraw_command_list_get_command(command_list, 2);
*command = (GDrawCommand) {
.type = GDrawCommandTypePath,
.hidden = false,
.stroke_color = GColorGreen,
.stroke_width = 1,
.fill_color = GColorPurple,
.path_open = false,
.num_points = 3,
};
GPoint points2[] = { { 6, 7 }, {5, 5}, { 0, 0 } };
memcpy(command->points, points2, sizeof(points2));
return command_list;
}
void test_gdraw_command__draw_command_list(void) {
GDrawCommandList *command_list = malloc(sizeof(GDrawCommandList) + sizeof(GDrawCommand) +
(sizeof(GPoint) * 2));
*command_list = (GDrawCommandList) {
.num_commands = 1
};
command_list->commands[0] = (GDrawCommand) {
.type = GDrawCommandTypePath,
.hidden = false,
.stroke_color = GColorRed,
.stroke_width = 1,
.fill_color = GColorBlue,
.path_open = false,
.num_points = 2,
};
GPoint points1[] = { { 3, 97 }, {5, 5} };
memcpy(command_list->commands[0].points, points1, sizeof(points1));
GContext *ctx = (GContext *)123; // just a fake internal guard != NULL
gdraw_command_list_draw(ctx, command_list);
cl_assert_equal_i(s_path_stroke_count, 1);
cl_assert_equal_i(s_path_fill_count, 1);
cl_assert_equal_i(s_circle_stroke_count, 0);
cl_assert_equal_i(s_circle_fill_count, 0);
prv_reset();
free(command_list);
command_list = prv_create_command_list_3();
gdraw_command_list_draw(ctx, command_list);
cl_assert_equal_i(s_path_stroke_count, 2);
cl_assert_equal_i(s_path_fill_count, 2);
cl_assert_equal_i(s_circle_stroke_count, 1);
cl_assert_equal_i(s_circle_fill_count, 1);
prv_reset();
gdraw_command_list_get_command(command_list, 2)->hidden = true;
gdraw_command_list_draw(ctx, command_list);
cl_assert_equal_i(s_path_stroke_count, 1);
cl_assert_equal_i(s_path_fill_count, 1);
cl_assert_equal_i(s_circle_stroke_count, 1);
cl_assert_equal_i(s_circle_fill_count, 1);
prv_reset();
gdraw_command_list_get_command(command_list, 0)->hidden = true;
gdraw_command_list_draw(ctx, command_list);
cl_assert_equal_i(s_path_stroke_count, 0);
cl_assert_equal_i(s_path_fill_count, 0);
cl_assert_equal_i(s_circle_stroke_count, 1);
cl_assert_equal_i(s_circle_fill_count, 1);
prv_reset();
gdraw_command_list_get_command(command_list, 1)->hidden = true;
gdraw_command_list_draw(ctx, command_list);
cl_assert_equal_i(s_path_stroke_count, 0);
cl_assert_equal_i(s_path_fill_count, 0);
cl_assert_equal_i(s_circle_stroke_count, 0);
cl_assert_equal_i(s_circle_fill_count, 0);
free(command_list);
}
void test_gdraw_command__validate_list(void) {
GDrawCommandList *command_list = prv_create_command_list_3();
size_t size = sizeof(GDrawCommandList) + (3 * sizeof(GDrawCommand)) +
(sizeof(GPoint) * 6);
cl_assert(gdraw_command_list_validate(command_list, size));
command_list->num_commands = 4;
cl_assert(!gdraw_command_list_validate(command_list, size));
command_list->num_commands = 3;
GDrawCommand *command = gdraw_command_list_get_command(command_list, 0);
command->num_points = 0;
cl_assert(!gdraw_command_list_validate(command_list, size));
command->num_points = 2;
command = gdraw_command_list_get_command(command_list, 2);
command->num_points = 4;
cl_assert(!gdraw_command_list_validate(command_list, size));
command->num_points = 3;
command->type = GDrawCommandTypeCircle;
cl_assert(!gdraw_command_list_validate(command_list, size));
command->type = GDrawCommandTypePrecisePath;
cl_assert(gdraw_command_list_validate(command_list, size));
free(command_list);
}
void test_gdraw_command__validate_image(void) {
size_t size = sizeof(GDrawCommandImage) + (3 * sizeof(GDrawCommand)) +
(sizeof(GPoint) * 6);
GDrawCommandImage *image = malloc(size);
GDrawCommandList *command_list = prv_create_command_list_3();
memcpy(&image->command_list, command_list, sizeof(GDrawCommandList) + (3 * sizeof(GDrawCommand)) +
(sizeof(GPoint) * 6));
free(command_list);
image->version = 1;
image->size = GSize(20, 20);
cl_assert(gdraw_command_image_validate(image, size));
cl_assert(!gdraw_command_image_validate(image, size - 1));
cl_assert(!gdraw_command_image_validate(image, size + 1));
cl_assert_equal_i(size, gdraw_command_image_get_data_size(image));
image->version = 2;
cl_assert(!gdraw_command_image_validate(image, size));
free(image);
}
void test_gdraw_command__clone_image(void) {
cl_assert_equal_p(gdraw_command_image_clone(NULL), NULL);
size_t size = sizeof(GDrawCommandImage) + (3 * sizeof(GDrawCommand)) +
(sizeof(GPoint) * 6);
GDrawCommandImage *image = malloc(size);
memset(image, 0, size);
GDrawCommandList *command_list = prv_create_command_list_3();
memcpy(&image->command_list, command_list, sizeof(GDrawCommandList) + (3 * sizeof(GDrawCommand)) +
(sizeof(GPoint) * 6));
free(command_list);
image->version = 1;
image->size = GSize(20, 20);
GDrawCommandImage *clone = gdraw_command_image_clone(image);
cl_assert(clone != image);
cl_assert_equal_i(gdraw_command_image_get_data_size(clone), size);
cl_assert_equal_i(0, memcmp(clone, image, size));
free(clone);
free(image);
}
void test_gdraw_command__draw_image(void) {
GDrawCommandImage *image = malloc(sizeof(GDrawCommandImage) + sizeof(GDrawCommand) +
(sizeof(GPoint) * 2));
GDrawCommandList *command_list = &image->command_list;
*command_list = (GDrawCommandList) {
.num_commands = 1
};
GDrawCommand *command;
command = gdraw_command_list_get_command(command_list, 0);
*command = (GDrawCommand) {
.type = GDrawCommandTypePath,
.hidden = false,
.stroke_color = GColorRed,
.stroke_width = 1,
.fill_color = GColorBlue,
.path_open = true,
.num_points = 2,
};
GPoint points[] = {{ 6, 1 }, {5, -5} };
memcpy(command_list->commands[0].points, points, sizeof(points));
GContext *ctx = (GContext *)123; // just a fake internal guard != NULL
gdraw_command_image_draw(ctx, image, GPoint(0, 0));
cl_assert_equal_i(s_path_num_points, 2);
cl_assert(prv_compare_points(points, s_stroke_points, s_path_num_points));
cl_assert(prv_compare_points(points, s_fill_points, s_path_num_points));
prv_reset();
gdraw_command_image_draw(ctx, image, GPoint(-1, 1));
cl_assert_equal_i(s_path_num_points, 2);
points[0] = GPoint(5, 2);
points[1] = GPoint(4, -4);
cl_assert(prv_compare_points(points, s_stroke_points, s_path_num_points));
cl_assert(prv_compare_points(points, s_fill_points, s_path_num_points));
free(image);
}
void test_gdraw_command__draw_frame(void) {
GDrawCommandFrame *frame = malloc(sizeof(GDrawCommandFrame) + sizeof(GDrawCommand) +
(sizeof(GPoint) * 2));
GDrawCommandList *command_list = &frame->command_list;
*command_list = (GDrawCommandList) {
.num_commands = 1
};
GDrawCommand *command = gdraw_command_list_get_command(command_list, 0);
*command = (GDrawCommand) {
.type = GDrawCommandTypePath,
.hidden = false,
.stroke_color = GColorRed,
.stroke_width = 1,
.fill_color = GColorBlue,
.path_open = true,
.num_points = 2,
};
GPoint points[] = {{ 1, 1 }, {2, -2} };
memcpy(command_list->commands[0].points, points, sizeof(points));
GContext *ctx = (GContext *)123; // just a fake internal guard != NULL
gdraw_command_frame_draw(ctx, NULL, frame, GPoint(0, 0));
cl_assert_equal_i(s_path_num_points, 2);
cl_assert(prv_compare_points(points, s_stroke_points, s_path_num_points));
cl_assert(prv_compare_points(points, s_fill_points, s_path_num_points));
prv_reset();
gdraw_command_frame_draw(ctx, NULL, frame, GPoint(-1, 1));
cl_assert_equal_i(s_path_num_points, 2);
points[0] = GPoint(0, 2);
points[1] = GPoint(1, -1);
cl_assert(prv_compare_points(points, s_stroke_points, s_path_num_points));
cl_assert(prv_compare_points(points, s_fill_points, s_path_num_points));
free(frame);
}
static int s_iterations = 0;
static bool prv_iterate(GDrawCommand *command, uint32_t index, void *context) {
s_iterations++;
return true;
}
void test_gdraw_command__iterate(void) {
GDrawCommandList *command_list = prv_create_command_list_3();
void *end = gdraw_command_list_iterate_private(command_list, prv_iterate, NULL);
cl_assert_equal_i(s_iterations, 3);
GDrawCommand *command = gdraw_command_list_get_command(command_list, 2);
void *expected_end = command->points + command->num_points;
cl_assert_equal_p(end, expected_end);
free(command_list);
}
typedef struct {
GDrawCommandProcessor processor;
GColor stroke_color;
} SetStrokeColorProcessor;
static void prv_set_stroke_color(GDrawCommandProcessor *processor,
GDrawCommand *processed_command,
const size_t processed_command_max_size,
const GDrawCommandList* list,
const GDrawCommand *command) {
SetStrokeColorProcessor *stroke_processor = (SetStrokeColorProcessor *)processor;
gdraw_command_set_stroke_color(processed_command, stroke_processor->stroke_color);
}
void test_gdraw_command__draw_command_list_processed(void) {
GDrawCommandList *command_list = prv_create_command_list_3();
GColor stroke_color = GColorTiffanyBlue;
SetStrokeColorProcessor stroke_processor = {
.processor = {
.command = prv_set_stroke_color,
},
.stroke_color = stroke_color,
};
// ctx can't be null pass in random pointer to satisfy function
GContext ctx;
gdraw_command_list_draw_processed(&ctx, command_list, (GDrawCommandProcessor *)&stroke_processor);
cl_assert_equal_i(s_stroke_color.argb, GColorTiffanyBlueARGB8);
free(command_list);
}
void test_gdraw_command__get_max_command_size_in_list(void) {
GDrawCommandList *command_list = prv_create_command_list_3();
// The third command in the list is the largest so it's size should be returned by
// prv_get_list_max_command_size
cl_assert_equal_i(gdraw_command_get_data_size(gdraw_command_list_get_command(command_list, 2)),
prv_get_list_max_command_size(command_list));
free(command_list);
}
@@ -0,0 +1,81 @@
/*
* 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 "clar.h"
#include "applib/graphics/gdraw_command.h"
#include "applib/graphics/gdraw_command_list.h"
#include "applib/graphics/gdraw_command_image.h"
#include "applib/graphics/gdraw_command_private.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/graphics_line.h"
#include "applib/graphics/gpath.h"
#include <string.h>
#include "util.h" // graphics tests utils
#include "fake_pbl_malloc.h"
#include "fake_resource_syscalls.h"
#include "stubs_app_state.h"
#include "stubs_compiled_with_legacy2_sdk.h"
#include "stubs_graphics_context.h"
#include "stubs_gpath.h"
#include "stubs_heap.h"
#include "stubs_logging.h"
#include "stubs_memory_layout.h"
#include "stubs_passert.h"
// Stubs
void graphics_draw_circle(GContext* ctx, GPoint p, uint16_t radius) {}
void graphics_fill_circle(GContext* ctx, GPoint p, uint16_t radius) {}
void framebuffer_clear(FrameBuffer* f){}
void graphics_context_move_draw_box(GContext* ctx, GPoint offset) {}
void graphics_line_draw_precise_stroked(GContext* ctx, GPointPrecise p0, GPointPrecise p1) {}
const uint8_t *resource_get_builtin_bytes(ResAppNum app_num, uint32_t resource_id,
uint32_t *num_bytes_out) { return NULL; }
void test_gdraw_command_resources__load_pdci(void) {
uint32_t resource_id = sys_resource_load_file_as_resource(TEST_IMAGES_PATH, TEST_PDC_FILE);
cl_assert(resource_id != UINT32_MAX);
GDrawCommandImage *image = gdraw_command_image_create_with_resource(resource_id);
cl_assert(image != NULL);
}
void test_gdraw_command_resources__load_pdcs(void) {
uint32_t resource_id = sys_resource_load_file_as_resource(TEST_IMAGES_PATH, TEST_PDC_FILE);
cl_assert(resource_id != UINT32_MAX);
GDrawCommandSequence *sequence = gdraw_command_sequence_create_with_resource(resource_id);
cl_assert(sequence != NULL);
}
// Test that loading an invalid PDC file fails
void test_gdraw_command_resources__load_invalid(void) {
uint32_t resource_id = sys_resource_load_file_as_resource(TEST_IMAGES_PATH, TEST_PDC_FILE);
cl_assert(resource_id != UINT32_MAX);
// Test Command_Image
GDrawCommandImage *image = gdraw_command_image_create_with_resource(resource_id);
cl_assert(image == NULL);
// Test Command Sequence
GDrawCommandSequence *sequence = gdraw_command_sequence_create_with_resource(resource_id);
cl_assert(sequence == NULL);
}
@@ -0,0 +1,260 @@
/*
* 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 "clar.h"
#include "applib/graphics/gdraw_command.h"
#include "applib/graphics/gdraw_command_list.h"
#include "applib/graphics/gdraw_command_frame.h"
#include "applib/graphics/gdraw_command_private.h"
#include "applib/graphics/gdraw_command_sequence.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/gpath.h"
#include "util/size.h"
#include "stubs_applib_resource.h"
#include "stubs_memory_layout.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_resources.h"
#include "stubs_syscalls.h"
// Stubs
void graphics_context_set_stroke_color(GContext* ctx, GColor color) {}
void graphics_context_set_fill_color(GContext* ctx, GColor color) {}
void graphics_context_set_stroke_width(GContext* ctx, uint8_t stroke_width) {}
void gpath_draw_stroke(GContext* ctx, GPath* path, bool open) {}
void gpath_draw_filled(GContext* ctx, GPath *path) {}
void graphics_draw_circle(GContext* ctx, GPoint p, uint16_t radius) {}
void graphics_fill_circle(GContext* ctx, GPoint p, uint16_t radius) {}
void graphics_context_move_draw_box(GContext* ctx, GPoint offset) {}
void graphics_line_draw_precise_stroked(GContext* ctx, GPointPrecise p0, GPointPrecise p1) {}
void gpath_fill_precise_internal(GContext *ctx, GPointPrecise *points, size_t num_points) {}
void gpath_draw_outline_precise_internal(GContext *ctx, GPointPrecise *points, size_t num_points,
bool open) {}
typedef uint16_t ResourceId;
const uint8_t *resource_get_builtin_bytes(ResAppNum app_num, uint32_t resource_id,
uint32_t *num_bytes_out) { return NULL; }
// setup and teardown
void test_gdraw_command_sequence__initialize(void) {
}
void test_gdraw_command_sequence__cleanup(void) {
}
size_t prv_create_test_sequence(GDrawCommandSequence **sequence_ptr) {
size_t size = sizeof(GDrawCommandSequence) + (sizeof(GDrawCommandFrame) * 2) +
(sizeof(GDrawCommand) * 4) + (sizeof(GPoint) * 9);
GDrawCommandSequence *sequence = calloc(1, size);
*sequence_ptr = sequence;
*sequence = (GDrawCommandSequence){
.version = GDRAW_COMMAND_VERSION,
.num_frames = 2,
.play_count = 1,
};
GDrawCommandFrame *frame;
frame = &sequence->frames[0];
*frame = (GDrawCommandFrame) {
.duration = 15,
};
frame->command_list = (GDrawCommandList) {
.num_commands = 3
};
GDrawCommand *command;
command = gdraw_command_list_get_command(&frame->command_list, 0);
GPoint points1[] = { { 3, 97 }, {5, 5} };
*command = (GDrawCommand) {
.type = GDrawCommandTypePath,
.hidden = false,
.stroke_color = GColorRed,
.stroke_width = 1,
.fill_color = GColorBlue,
.path_open = false,
.num_points = ARRAY_LENGTH(points1),
};
memcpy(command->points, points1, sizeof(points1));
command = gdraw_command_list_get_command(&frame->command_list, 1);
*command = (GDrawCommand) {
.type = GDrawCommandTypeCircle,
.hidden = false,
.stroke_color = GColorGreen,
.stroke_width = 1,
.fill_color = GColorOrange,
.radius = 300,
.num_points = 1,
};
command->points[0] = (GPoint) { 1, 2 };
command = gdraw_command_list_get_command(&frame->command_list, 2);
GPoint points2[] = { { 6, 7 }, {5, 5}, { 0, 0 } };
*command = (GDrawCommand) {
.type = GDrawCommandTypePath,
.hidden = false,
.stroke_color = GColorGreen,
.stroke_width = 1,
.fill_color = GColorPurple,
.path_open = false,
.num_points = ARRAY_LENGTH(points2),
};
memcpy(command->points, points2, sizeof(points2));
frame = (GDrawCommandFrame *)(command->points + command->num_points);
*frame = (GDrawCommandFrame) {
.duration = 30,
};
frame->command_list = (GDrawCommandList) {
.num_commands = 1
};
command = gdraw_command_list_get_command(&frame->command_list, 0);
points2[0].x++; // increment x value to distinguish draw command from command in previous frame
*command = (GDrawCommand) {
.type = GDrawCommandTypePath,
.hidden = false,
.stroke_color = GColorRed,
.stroke_width = 5,
.fill_color = GColorBlack,
.path_open = false,
.num_points = ARRAY_LENGTH(points2),
};
memcpy(command->points, points2, sizeof(points2));
return size;
}
// tests
void test_gdraw_command_sequence__validate(void) {
GDrawCommandSequence *sequence;
size_t size = prv_create_test_sequence(&sequence);
cl_assert_equal_i(size, gdraw_command_sequence_get_data_size(sequence));
cl_assert(gdraw_command_sequence_validate(sequence, size));
cl_assert(!gdraw_command_sequence_validate(sequence, size - 1));
cl_assert(!gdraw_command_sequence_validate(sequence, size + 1));
cl_assert(!gdraw_command_sequence_validate(sequence, 0));
sequence->num_frames = 0;
cl_assert(!gdraw_command_sequence_validate(sequence, size));
sequence->num_frames = 1;
cl_assert(!gdraw_command_sequence_validate(sequence, size));
sequence->num_frames = 3;
cl_assert(!gdraw_command_sequence_validate(sequence, size));
sequence->num_frames = 2;
sequence->version = 0xFF;
cl_assert(!gdraw_command_sequence_validate(sequence, size));
free(sequence);
}
void test_gdraw_command_sequence__get_frame_by_elapsed(void) {
GDrawCommandSequence *sequence;
prv_create_test_sequence(&sequence);
GDrawCommandFrame *frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 0);
cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 0));
cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_elapsed(sequence, 14));
frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 15);
cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 1));;
cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_elapsed(sequence, 44));
cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_elapsed(sequence, 45));
cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_elapsed(sequence, 46));
// test that frame is skipped when the duration is zero (first frame shown will be the first one
// with non-zero duration
frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 0);
frame->duration = 0;
frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 0);
cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 1));
frame = gdraw_command_sequence_get_frame_by_index(sequence, 0);
frame->duration = 15;
// test that the sequence loops when the play count is greater than 1
sequence->play_count = 2;
frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 45);
cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 0));
frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 45 + 15);
cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 1));
// test that the sequence loops infinitely when the play count is infinite
sequence->play_count = (uint16_t)PLAY_COUNT_INFINITE;
frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 45 * 5);
cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 0));
frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, (45 + 15) * 5);
cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 1));
// test that the sequence returns the last frame if the play count is zero
sequence->play_count = 0;
frame = gdraw_command_sequence_get_frame_by_elapsed(sequence, 1);
cl_assert_equal_p(frame, gdraw_command_sequence_get_frame_by_index(sequence, 1));
free(sequence);
}
void test_gdraw_command_sequence__get_frame_by_index(void) {
GDrawCommandSequence *sequence;
prv_create_test_sequence(&sequence);
GDrawCommandFrame *frame = gdraw_command_sequence_get_frame_by_index(sequence, 0);
cl_assert_equal_i(frame->duration, 15);
cl_assert_equal_i(frame->command_list.num_commands, 3);
GDrawCommand *command = gdraw_command_list_get_command(&frame->command_list, 2);
cl_assert_equal_i(command->type, GDrawCommandTypePath);
cl_assert_equal_i(command->num_points, 3);
cl_assert_equal_i(command->stroke_color.argb, GColorGreenARGB8);
cl_assert_equal_i(command->fill_color.argb, GColorPurpleARGB8);
cl_assert_equal_i(command->points[0].x, 6);
frame = gdraw_command_sequence_get_frame_by_index(sequence, 1);
cl_assert_equal_i(frame->duration, 30);
cl_assert_equal_i(frame->command_list.num_commands, 1);
command = gdraw_command_list_get_command(&frame->command_list, 0);
cl_assert_equal_i(command->type, GDrawCommandTypePath);
cl_assert_equal_i(command->num_points, 3);
cl_assert_equal_i(command->stroke_color.argb, GColorRedARGB8);
cl_assert_equal_i(command->fill_color.argb, GColorBlackARGB8);
cl_assert_equal_i(command->points[0].x, 7);
cl_assert_equal_p(gdraw_command_sequence_get_frame_by_index(sequence, 2), NULL);
free(sequence);
}
void test_gdraw_command_sequence__clone(void) {
cl_assert_equal_p(gdraw_command_sequence_clone(NULL), NULL);
GDrawCommandSequence *sequence;
prv_create_test_sequence(&sequence);
GDrawCommandSequence *clone = gdraw_command_sequence_clone(sequence);
cl_assert(clone != sequence);
size_t expected_size = gdraw_command_sequence_get_data_size(sequence);
cl_assert_equal_i(gdraw_command_sequence_get_data_size(clone), expected_size);
cl_assert_equal_i(0, memcmp(clone, sequence, expected_size));
free(sequence);
}
@@ -0,0 +1,244 @@
/*
* 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 "clar.h"
#include "applib/graphics/gdraw_command_private.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/graphics_line.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/framebuffer.h"
#include "applib/ui/animation.h"
#include "applib/ui/animation_timing.h"
#include "applib/ui/animation_interpolate.h"
#include "applib/graphics/gdraw_command_transforms.h"
#include "util.h"
#include "test_graphics.h"
#include "8bit/test_framebuffer.h"
#include "weather_app_resources.h"
// Stubs
////////////////////////////////////
#include "stubs_applib_resource.h"
#include "stubs_app_state.h"
#include "stubs_heap.h"
#include "stubs_logging.h"
#include "stubs_memory_layout.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_resources.h"
#include "stubs_syscalls.h"
#include <stdio.h>
// stubs
InterpolateInt64Function animation_private_current_interpolate_override(void) {
return NULL;
}
static FrameBuffer *fb = NULL;
// Setup
void test_gdraw_command_transforms__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
}
// Teardown
void test_gdraw_command_transforms__cleanup(void) {
free(fb);
}
void test_gdraw_command_transforms__to_square(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
graphics_context_set_fill_color(&ctx, GColorFromHEX(0x55aaff));
graphics_context_set_antialiased(&ctx, true);
graphics_fill_rect(&ctx, &ctx.dest_bitmap.bounds);
GDrawCommandImage *l = weather_app_resource_create_sun();
gdraw_command_image_draw(&ctx, l, GPoint(0, 0));
int32_t dt = ANIMATION_NORMALIZED_MAX / 5;
int32_t t = 0;
l = weather_app_resource_create_sun();
gdraw_command_image_attract_to_square(l, t += dt);
gdraw_command_image_draw(&ctx, l, GPoint(48, 0));
l = weather_app_resource_create_sun();
gdraw_command_image_attract_to_square(l, t += dt);
gdraw_command_image_draw(&ctx, l, GPoint(0, 48));
l = weather_app_resource_create_sun();
gdraw_command_image_attract_to_square(l, t += dt);
gdraw_command_image_draw(&ctx, l, GPoint(48, 48));
l = weather_app_resource_create_sun();
gdraw_command_image_attract_to_square(l, t += dt);
gdraw_command_image_draw(&ctx, l, GPoint(0, 96));
l = weather_app_resource_create_sun();
gdraw_command_image_attract_to_square(l, t += dt);
gdraw_command_image_draw(&ctx, l, GPoint(48, 96));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "test_gdraw_command_transforms__to_square.8bit.pbi"));
}
// re-enable this "test" to debug per-frame transitions
void _test_gdraw_command_transforms__to_square_sequence(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
graphics_context_set_antialiased(&ctx, true);
int32_t dt = ANIMATION_NORMALIZED_MAX / 16;
int32_t t = 0;
while (t <= ANIMATION_NORMALIZED_MAX) {
graphics_context_set_fill_color(&ctx, GColorFromHEX(0x55aaff));
graphics_fill_rect(&ctx, &ctx.dest_bitmap.bounds);
GDrawCommandImage *img = weather_app_resource_create_sun();
gdraw_command_image_attract_to_square(img, t);
gdraw_command_image_draw(&ctx, img, GPoint(48, 96));
char fn[20];
snprintf(fn, sizeof(fn), "tos_%06d.png", t);
tests_write_gbitmap_to_pbi(&ctx.dest_bitmap, fn);
free(img);
t += dt;
}
}
int16_t prv_int_scale_and_translate_to(
int16_t value, int16_t size, int16_t from_range, int16_t to_range,
int16_t from_min, int16_t to_min, int32_t normalized, InterpolateInt64Function interpolate);
int64_t prv_default_interpolate(int32_t normalized, int64_t from, int64_t to);
void test_gdraw_command_transforms__int_scale_to_translate_overflow(void) {
InterpolateInt64Function interp = prv_default_interpolate;
int y = prv_int_scale_and_translate_to(255, 10, 10, 10, 0, 255, ANIMATION_NORMALIZED_MAX,
interp);
cl_assert_equal_i(y, 255 + 255);
}
void test_gdraw_command_transforms__int_scale_to_translate_overflow_neg(void) {
InterpolateInt64Function interp = prv_default_interpolate;
int y = prv_int_scale_and_translate_to(-255, 10, 10, 10, 0, -255, ANIMATION_NORMALIZED_MAX,
interp);
cl_assert_equal_i(y, -255 + -255);
}
void test_gdraw_command_transforms__int_scale_to_scale_overflow(void) {
InterpolateInt64Function interp = prv_default_interpolate;
int y = prv_int_scale_and_translate_to(181, 1, 1, 181, 0, 0, ANIMATION_NORMALIZED_MAX,
interp);
cl_assert_equal_i(y, 181 * 181);
}
void test_gdraw_command_transforms__int_scale_to_scale_overflow_neg(void) {
InterpolateInt64Function interp = prv_default_interpolate;
int y = prv_int_scale_and_translate_to(-181, 1, 1, 181, 0, 0, ANIMATION_NORMALIZED_MAX,
interp);
cl_assert_equal_i(y, -181 * 181);
}
void test_gdraw_command_transforms__segmented_scale(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
graphics_context_set_fill_color(&ctx, GColorFromHEX(0x55aaff));
graphics_context_set_antialiased(&ctx, true);
graphics_fill_rect(&ctx, &ctx.dest_bitmap.bounds);
GDrawCommandImage *l = weather_app_resource_create_sun();
gdraw_command_image_draw(&ctx, l, GPointZero);
int32_t dt = ANIMATION_NORMALIZED_MAX / 5;
int32_t t = 0;
InterpolateInt64Function interp = NULL;
int16_t s = 48;
GRect from = GRect(0, 0, s, s);
GRect to = GRect(90, 0, s, s);
GPointIndexLookup *index_lookup = gdraw_command_list_create_index_lookup_by_distance(
gdraw_command_image_get_command_list(l), GPoint(s, s / 2));
Fixed_S32_16 f = Fixed_S32_16(FIXED_S32_16_ONE.raw_value / 2);
l = weather_app_resource_create_sun();
gdraw_command_image_scale_segmented_to(l, from, to, t += dt, interp, index_lookup, f, false);
gdraw_command_image_draw(&ctx, l, GPoint(0, 0));
l = weather_app_resource_create_sun();
gdraw_command_image_scale_segmented_to(l, from, to, t += dt, interp, index_lookup, f, false);
gdraw_command_image_draw(&ctx, l, GPoint(0, 48));
l = weather_app_resource_create_sun();
gdraw_command_image_scale_segmented_to(l, from, to, t += dt, interp, index_lookup, f, false);
gdraw_command_image_draw(&ctx, l, GPoint(0, 96));
l = weather_app_resource_create_sun();
gdraw_command_image_scale_segmented_to(l, from, to, t += dt, interp, index_lookup, f, false);
gdraw_command_image_draw(&ctx, l, GPoint(0, 144));
l = weather_app_resource_create_sun();
gdraw_command_image_scale_segmented_to(l, from, to, t += dt, interp, index_lookup, f, false);
gdraw_command_image_draw(&ctx, l, GPoint(0, 192));
free(index_lookup);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "test_gdraw_command_transforms__segmented_scale.8bit.pbi"));
}
// re-enable this "test" to debug per-frame transitions
void _test_gdraw_command_transforms__scale_segmented_sequence(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
graphics_context_set_antialiased(&ctx, true);
int32_t dt = ANIMATION_NORMALIZED_MAX / 16;
int32_t t = 0;
InterpolateInt64Function interp = NULL;
int16_t s = 48;
GRect from = GRect(144 - s * 3 / 4, s / 4, s / 2, s / 2);
GRect to = GRect(s / 2, s, 2 * s, 2 * s);
GDrawCommandImage *img = weather_app_resource_create_cloud();
GPointIndexLookup *index_lookup = gdraw_command_list_create_index_lookup_by_distance(
gdraw_command_image_get_command_list(img), GPoint(s / 2, s));
free(img);
Fixed_S32_16 f = Fixed_S32_16(FIXED_S32_16_ONE.raw_value / 8);
while (t <= ANIMATION_NORMALIZED_MAX) {
graphics_context_set_fill_color(&ctx, GColorFromHEX(0x55aaff));
graphics_fill_rect(&ctx, &ctx.dest_bitmap.bounds);
img = weather_app_resource_create_cloud();
gdraw_command_image_scale_segmented_to(img, from, to, t, interp, index_lookup, f, false);
gdraw_command_image_draw(&ctx, img, GPointZero);
char fn[20];
snprintf(fn, sizeof(fn), "elo_%06d.png", t);
tests_write_gbitmap_to_pbi(&ctx.dest_bitmap, fn);
t += dt;
}
}
+40
View File
@@ -0,0 +1,40 @@
/*
* 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.
*/
#pragma once
#include "applib/graphics/gtypes.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/framebuffer.h"
#include "stubs_compiled_with_legacy2_sdk.h"
#include "stubs_app_state.h"
#define GRAPHICS_FIXTURE_PATH "graphics"
#define GRAPHICS_FIXTURE_OUT_PATH ".."
extern GContext *s_app_state_get_graphics_context;
static void test_graphics_context_reset(GContext* ctx, FrameBuffer* framebuffer) {
graphics_context_init(ctx, framebuffer, GContextInitializationMode_App);
framebuffer_clear(framebuffer);
}
static void test_graphics_context_init(GContext* ctx, FrameBuffer* framebuffer) {
test_graphics_context_reset(ctx, framebuffer);
s_app_state_get_graphics_context = ctx; // needed by app_state_get_graphics_context
graphics_context_set_antialiased(ctx, false);
}
+114
View File
@@ -0,0 +1,114 @@
/*
* 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 "clar.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/graphics.h"
#include "stubs_applib_resource.h"
#include "stubs_app_state.h"
#include "stubs_compiled_with_legacy2_sdk.h"
#include "stubs_graphics_line.h"
#include "stubs_heap.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_logging.h"
const int FrameBuffer_MaxX = 144;
const int FrameBuffer_MaxY = 85;
// stubs
bool gbitmap_init_with_png_data(GBitmap *bitmap, const uint8_t *data, size_t data_size){
return false;
}
bool gbitmap_png_data_is_png(const uint8_t *data, size_t data_size){
return false;
}
void cos_lookup(){}
void sin_lookup(){}
void framebuffer_dirty_all(){}
void framebuffer_mark_dirty_rect(){}
void graphics_circle_draw_quadrant(GContext* ctx, int x0, int y0, uint16_t radius,
GCornerMask quadrant){}
void graphics_circle_quadrant_draw_1px_non_aa(GContext* ctx, GPoint p,
uint16_t radius, GCornerMask quadrant) {}
void graphics_internal_circle_quadrant_fill_aa(GContext* ctx, GPoint p,
uint16_t radius, GCornerMask quadrant) {}
void graphics_circle_quadrant_draw(GContext* ctx, GPoint p, uint16_t radius,
GCornerMask quadrant) {}
void graphics_circle_quadrant_fill_non_aa(GContext* ctx, GPoint p,
uint16_t radius, GCornerMask quadrant) {}
void sys_get_current_resource_num(){}
void sys_resource_read_only_bytes(){}
void sys_resource_load_range(){}
void sys_resource_size(){}
int32_t integer_sqrt(int64_t x){ return 0;}
GBitmap framebuffer_get_as_bitmap(FrameBuffer *fb, const GSize *size) {
return (GBitmap) {
.addr = fb,
.row_size_bytes = FrameBuffer_MaxX,
.info = (BitmapInfo) {.format = GBitmapFormat8Bit, .version = GBITMAP_VERSION_CURRENT},
.bounds = (GRect) { { 0, 0 }, { FrameBuffer_MaxX, FrameBuffer_MaxY } },
};
}
// tests
void test_graphics_blending__closest_opaque(void) {
cl_assert_equal_i(GColorRedARGB8, gcolor_closest_opaque((GColor8){.a= 3, .r = 3}).argb);
cl_assert_equal_i(GColorRedARGB8, gcolor_closest_opaque((GColor8){.a= 2, .r = 3}).argb);
cl_assert_equal_i(GColorClearARGB8, gcolor_closest_opaque((GColor8){.a= 1, .r = 3}).argb);
cl_assert_equal_i(GColorClearARGB8, gcolor_closest_opaque((GColor8){.a= 0, .r = 3}).argb);
}
void test_graphics_blending__ctx_text_color_discards_alpha(void) {
GContext ctx = {};
graphics_context_set_text_color(&ctx, (GColor8){.a= 3, .r = 3});
cl_assert_equal_i(GColorRedARGB8, ctx.draw_state.text_color.argb);
graphics_context_set_text_color(&ctx, (GColor8){.a= 2, .r = 3});
cl_assert_equal_i(GColorRedARGB8, ctx.draw_state.text_color.argb);
graphics_context_set_text_color(&ctx, (GColor8){.a= 1, .r = 3});
cl_assert_equal_i(GColorClearARGB8, ctx.draw_state.text_color.argb);
graphics_context_set_text_color(&ctx, (GColor8){.a= 0, .r = 3});
cl_assert_equal_i(GColorClearARGB8, ctx.draw_state.text_color.argb);
}
void test_graphics_blending__ctx_stroke_color_discards_alpha(void) {
GContext ctx = {};
graphics_context_set_stroke_color(&ctx, (GColor8){.a= 3, .r = 3});
cl_assert_equal_i(GColorRedARGB8, ctx.draw_state.stroke_color.argb);
graphics_context_set_stroke_color(&ctx, (GColor8){.a= 2, .r = 3});
cl_assert_equal_i(GColorRedARGB8, ctx.draw_state.stroke_color.argb);
graphics_context_set_stroke_color(&ctx, (GColor8){.a= 1, .r = 3});
cl_assert_equal_i(GColorClearARGB8, ctx.draw_state.stroke_color.argb);
graphics_context_set_stroke_color(&ctx, (GColor8){.a= 0, .r = 3});
cl_assert_equal_i(GColorClearARGB8, ctx.draw_state.stroke_color.argb);
}
void test_graphics_blending__ctx_fill_color_discards_alpha(void) {
GContext ctx = {};
graphics_context_set_fill_color(&ctx, (GColor8){.a= 3, .r = 3});
cl_assert_equal_i(GColorRedARGB8, ctx.draw_state.fill_color.argb);
graphics_context_set_fill_color(&ctx, (GColor8){.a= 2, .r = 3});
cl_assert_equal_i(GColorRedARGB8, ctx.draw_state.fill_color.argb);
graphics_context_set_fill_color(&ctx, (GColor8){.a= 1, .r = 3});
cl_assert_equal_i(GColorClearARGB8, ctx.draw_state.fill_color.argb);
graphics_context_set_fill_color(&ctx, (GColor8){.a= 0, .r = 3});
cl_assert_equal_i(GColorClearARGB8, ctx.draw_state.fill_color.argb);
}
+417
View File
@@ -0,0 +1,417 @@
/*
* 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_circle.h"
#include "util/trig.h"
#include "clar.h"
#include "pebble_asserts.h"
#include <stdio.h>
// stubs
#include "stubs_process_manager.h"
#include "stubs_passert.h"
#include "stubs_logging.h"
#include "stubs_app_state.h"
#include "stubs_heap.h"
GBitmap* graphics_capture_frame_buffer(GContext* ctx) {
return NULL;
}
bool graphics_release_frame_buffer(GContext* ctx, GBitmap* buffer) {
return true;
}
void graphics_draw_pixel(){}
void graphics_fill_rect(GContext* ctx, const GRect *rect) {}
void graphics_private_draw_horizontal_line(){}
void graphics_private_draw_vertical_line(){}
void graphics_private_plot_pixel(){}
void graphics_private_set_pixel(){}
/////////////////////////////
static GPointPrecise s_center;
static Fixed_S16_3 s_radius;
static Fixed_S16_3 s_radius_inner;
static Fixed_S16_3 s_radius_outer;
static int32_t s_angle_start;
static int32_t s_angle_end;
////////////////////////////
void test_graphics_circle__initialize(void) {
s_center = (GPointPrecise){};
s_radius = (Fixed_S16_3){};
s_radius_inner = (Fixed_S16_3){};
s_radius_outer = (Fixed_S16_3){};
}
void test_graphics_circle__gpoint_from_polar_returns_zero_for_null(void) {
const uint16_t radius = 5;
const GPoint result = gpoint_from_polar_internal(NULL, radius, 0);
cl_assert_equal_gpoint(result, GPointZero);
}
void test_graphics_circle__gpoint_from_polar_returns_correct_points(void) {
const uint16_t radius = 5;
GPoint result;
const GPoint origin_center = GPointZero;
// 90 degrees should be (5, 0)
result = gpoint_from_polar_internal(&origin_center, radius, (TRIG_MAX_ANGLE / 4));
cl_assert_equal_gpoint(result, GPoint(5, 0));
// 270 (90 * 3) degrees should be (-5, 0)
result = gpoint_from_polar_internal(&origin_center, radius, (TRIG_MAX_ANGLE * 3 / 4));
cl_assert_equal_gpoint(result, GPoint(-5, 0));
const GPoint offset_center = GPoint(1, 1);
// 90 degrees should be (6, 1)
result = gpoint_from_polar_internal(&offset_center, radius, (TRIG_MAX_ANGLE / 4));
cl_assert_equal_gpoint(result, GPoint(6, 1));
// 270 (90 * 3) degrees should be (-4, 1)
result = gpoint_from_polar_internal(&offset_center, radius, (TRIG_MAX_ANGLE * 3 / 4));
cl_assert_equal_gpoint(result, GPoint(-4, 1));
}
void test_graphics_circle__gpoint_from_polar_normalizes_input_angles(void) {
const uint16_t radius = 5;
const GPoint center = GPointZero;
GPoint result;
// -180 degrees should be (0, 5)
result = gpoint_from_polar_internal(&center, radius, -(TRIG_MAX_ANGLE / 2));
cl_assert_equal_gpoint(result, GPoint(0, 5));
// -90 degrees should be (-5, 0)
result = gpoint_from_polar_internal(&center, radius, -(TRIG_MAX_ANGLE / 4));
cl_assert_equal_gpoint(result, GPoint(-5, 0));
// -450 degrees (-90 * 5 -> -90) should be (-5, 0)
result = gpoint_from_polar_internal(&center, radius, -(TRIG_MAX_ANGLE * 5 / 4));
cl_assert_equal_gpoint(result, GPoint(-5, 0));
// 450 degrees (90 * 5 -> 90) should be (0, 5)
result = gpoint_from_polar_internal(&center, radius, (TRIG_MAX_ANGLE * 5 / 4));
cl_assert_equal_gpoint(result, GPoint(5, 0));
// 360 degrees (-> 0 degrees) should be (0, -5)
result = gpoint_from_polar_internal(&center, radius, TRIG_MAX_ANGLE);
cl_assert_equal_gpoint(result, GPoint(0, -5));
// -360 degrees (-> 0 degrees) should be (0, -5)
result = gpoint_from_polar_internal(&center, radius, -TRIG_MAX_ANGLE);
cl_assert_equal_gpoint(result, GPoint(0, -5));
}
void test_graphics_circle__gpoint_from_polar_correct_scale(void) {
// edge cases are covered above, this test only verifies that
// the internal implementation correctly scales
GPoint result = gpoint_from_polar(GRect(0, 0, 10, 10), GOvalScaleModeFillCircle, 0);
cl_assert_equal_gpoint(result, GPoint(4, 0));
}
void test_graphics_circle__grect_centered_from_polar(void) {
GOvalScaleMode mode = GOvalScaleModeFillCircle;
// Basic container rect with GPointZero origin
const GRect container_rect1 = GRect(0, 0, 10, 10);
const GRect resulting_rect1 = grect_centered_from_polar(container_rect1, mode, 0, GSize(3, 5));
cl_assert_equal_grect(resulting_rect1, GRect(3, -2, 3, 5));
// Container rect is offset from GPointZero
const GRect container_rect2 = GRect(2, 2, 4, 4);
const GRect resulting_rect2 = grect_centered_from_polar(container_rect2, mode, 0, GSize(2, 4));
cl_assert_equal_grect(resulting_rect2, GRect(3, 0, 2, 4));
// Odd-length width and height for container rect, 180 degree angle
const GRect container_rect3 = GRect(2, 2, 5, 5);
const GRect resulting_rect3 = grect_centered_from_polar(container_rect3, mode,
DEG_TO_TRIGANGLE(180), GSize(2, 4));
cl_assert_equal_grect(resulting_rect3, GRect(3, 4, 2, 4));
}
void test_graphics_circle__grect_centered_internal(void) {
GPointPrecise p1 = GPointPrecise(0, 0);
// GRectZero + standardize
cl_assert_equal_grect(GRect(0, 0, 0, 0), grect_centered_internal(&p1, GSize(0, 0)));
cl_assert_equal_grect(GRect(0, -1, 1, 2), grect_centered_internal(&p1, GSize(-1, -2)));
// handles fixed point
cl_assert_equal_grect(GRect(-1, -1, 2, 2), grect_centered_internal(&p1, GSize(2, 2)));
p1.x.raw_value += FIXED_S16_3_HALF.raw_value;
cl_assert_equal_grect(GRect(0, -1, 2, 2), grect_centered_internal(&p1, GSize(2, 2)));
// Repeat for an offset center point
GPointPrecise p2 = GPointPreciseFromGPoint(GPoint(5, 5));
// GRectZero + standardize
cl_assert_equal_grect(GRect(5, 5, 0, 0), grect_centered_internal(&p2, GSize(0, 0)));
cl_assert_equal_grect(GRect(5, 4, 1, 2), grect_centered_internal(&p2, GSize(-1, -2)));
// handles fixed point
cl_assert_equal_grect(GRect(4, 4, 2, 2), grect_centered_internal(&p2, GSize(2, 2)));
p2.x.raw_value += FIXED_S16_3_HALF.raw_value;
cl_assert_equal_grect(GRect(5, 4, 2, 2), grect_centered_internal(&p2, GSize(2, 2)));
// Repeat for a positive offset center point with 0.5 fractions
GPointPrecise p3 = GPointPreciseFromGPoint(GPoint(5, 5));
p3.x.raw_value += FIXED_S16_3_HALF.raw_value;
p3.y.raw_value += FIXED_S16_3_HALF.raw_value;
// GRectZero + standardize
cl_assert_equal_grect(GRect(6, 6, 0, 0), grect_centered_internal(&p3, GSize(0, 0)));
cl_assert_equal_grect(GRect(5, 5, 1, 2), grect_centered_internal(&p3, GSize(-1, -2)));
// handles fixed point
cl_assert_equal_grect(GRect(5, 5, 2, 2), grect_centered_internal(&p3, GSize(2, 2)));
p3.x.raw_value += FIXED_S16_3_HALF.raw_value;
cl_assert_equal_grect(GRect(5, 5, 2, 2), grect_centered_internal(&p3, GSize(2, 2)));
// Repeat for a negative offset center point with 0.5 fractions
GPointPrecise p4 = GPointPreciseFromGPoint(GPoint(-5, -5));
p4.x.raw_value -= FIXED_S16_3_HALF.raw_value;
p4.y.raw_value -= FIXED_S16_3_HALF.raw_value;
// GRectZero + standardize
cl_assert_equal_grect(GRect(-5, -5, 0, 0), grect_centered_internal(&p4, GSize(0, 0)));
cl_assert_equal_grect(GRect(-6, -6, 1, 2), grect_centered_internal(&p4, GSize(-1, -2)));
// handles fixed point
cl_assert_equal_grect(GRect(-6, -6, 2, 2), grect_centered_internal(&p4, GSize(2, 2)));
p4.x.raw_value += FIXED_S16_3_HALF.raw_value;
cl_assert_equal_grect(GRect(-6, -6, 2, 2), grect_centered_internal(&p4, GSize(2, 2)));
}
#define cl_assert_fixedS16_3(v, f) \
cl_assert_equal_i((v).raw_value, (int)((f) * FIXED_S16_3_ONE.raw_value))
#define cl_assert_gpoint_precise(p, px, py) \
do { \
cl_assert_fixedS16_3(p.x, px); \
cl_assert_fixedS16_3(p.y, py); \
} while(0)
void test_graphics_circle__grect_polar_calc_values_handles_null(void) {
GPointPrecise center = {};
Fixed_S16_3 radius = {};
const GRect r = GRect(0, 0, 3, 5);
const GOvalScaleMode mode = GOvalScaleModeFitCircle;
grect_polar_calc_values(&r, mode, NULL, NULL);
grect_polar_calc_values(&r, mode, &center, NULL);
grect_polar_calc_values(&r, mode, NULL, &radius);
cl_assert_gpoint_precise(center, 1, 2);
cl_assert_fixedS16_3(radius, 1);
grect_polar_calc_values(NULL, mode, &center, &radius);
cl_assert_gpoint_precise(center, 1, 2);
cl_assert_fixedS16_3(radius, 1);
}
void test_graphics_circle__grect_polar_calc_values_edge_cases(void) {
GPointPrecise center = {};
Fixed_S16_3 radius = {};
const GOvalScaleMode mode = GOvalScaleModeFillCircle;
GRect r = GRect(0, 5, 0, 0);
grect_polar_calc_values(&r, mode, &center, &radius);
cl_assert_gpoint_precise(center, 0, 5);
cl_assert_fixedS16_3(radius, 0);
// 1 pixel width means radius of 0
r = GRect(-1, -5, 1, 1);
grect_polar_calc_values(&r, mode, &center, &radius);
cl_assert_gpoint_precise(center, -1, -5);
cl_assert_fixedS16_3(radius, 0);
// 2 pixel width means: center is 1px from side, 0.5 pixels to center of outer pixels
r = GRect(-1, -5, 2, 2);
grect_polar_calc_values(&r, mode, &center, &radius);
cl_assert_gpoint_precise(center, -0.5, -4.5);
cl_assert_fixedS16_3(radius, 0.5);
}
void test_graphics_circle__grect_polar_calc_values_standardizes(void) {
GPointPrecise center = {};
Fixed_S16_3 radius = {};
const GOvalScaleMode mode = GOvalScaleModeFitCircle;
GRect r = GRect(0, 0, 10, 20);
grect_polar_calc_values(&r, mode, &center, &radius);
cl_assert_gpoint_precise(center, 4.5, 9.5);
cl_assert_fixedS16_3(radius, 4.5);
r = GRect(0, 0, -10, -20);
grect_polar_calc_values(&r, mode, &center, &radius);
cl_assert_gpoint_precise(center, -5.5, -10.5);
cl_assert_fixedS16_3(radius, 4.5);
}
void test_graphics_circle__grect_polar_calc_values_square(void) {
GPointPrecise center = {};
Fixed_S16_3 radius = {};
GRect r = GRect(0, 0, 5, 5);
GOvalScaleMode mode = GOvalScaleModeFitCircle; // irrelevant as we deal with squares
grect_polar_calc_values(&r, mode, &center, &radius);
cl_assert_gpoint_precise(center, 2, 2);
cl_assert_fixedS16_3(radius, 2);
r = GRect(0, 0, 6, 6);
grect_polar_calc_values(&r, mode, &center, &radius);
cl_assert_gpoint_precise(center, 2.5, 2.5);
cl_assert_fixedS16_3(radius, 2.5);
r = GRect(0, 0, 10, 10);
grect_polar_calc_values(&r, mode, &center, &radius);
cl_assert_gpoint_precise(center, 4.5, 4.5);
cl_assert_fixedS16_3(radius, 4.5);
r = GRect(1, 1, 9, 9);
grect_polar_calc_values(&r, mode, &center, &radius);
cl_assert_gpoint_precise(center, 5, 5);
cl_assert_fixedS16_3(radius, 4);
r = GRect(2, 2, 8, 8);
grect_polar_calc_values(&r, mode, &center, &radius);
cl_assert_gpoint_precise(center, 5.5, 5.5);
cl_assert_fixedS16_3(radius, 3.5);
}
void test_graphics_circle__grect_polar_calc_values_mode(void) {
GPointPrecise center = {};
Fixed_S16_3 radius = {};
GRect r = GRect(0, 0, 144, 168);
grect_polar_calc_values(&r, GOvalScaleModeFitCircle, &center, &radius);
cl_assert_gpoint_precise(center, 144 / 2 - 0.5, 168 / 2 - 0.5);
cl_assert_fixedS16_3(radius, 144 / 2 - 0.5);
grect_polar_calc_values(&r, GOvalScaleModeFillCircle, &center, &radius);
cl_assert_gpoint_precise(center, 144 / 2 - 0.5, 168 / 2 - 0.5);
cl_assert_fixedS16_3(radius, 168 / 2 - 0.5);
}
void graphics_draw_arc_precise_internal(GContext *ctx, GPointPrecise center,
Fixed_S16_3 radius,
int32_t angle_start, int32_t angle_end) {
s_center = center;
s_radius = radius;
}
void test_graphics_circle__draw_arc(void) {
cl_assert_gpoint_precise(s_center, 0, 0);
cl_assert_fixedS16_3(s_radius, 0);
graphics_draw_arc(NULL, GRect(0, 0, 10, 12), GOvalScaleModeFitCircle, 0, 0);
cl_assert_gpoint_precise(s_center, 4.5, 5.5);
cl_assert_fixedS16_3(s_radius, 4.5);
}
void test_graphics_circle__fill_oval(void) {
cl_assert_gpoint_precise(s_center, 0, 0);
cl_assert_fixedS16_3(s_radius_inner, 0);
cl_assert_fixedS16_3(s_radius_outer, 0);
cl_assert_equal_i(s_angle_start, 0);
cl_assert_equal_i(s_angle_end, 0);
graphics_fill_oval(NULL, GRect(0, 0, 10, 12), GOvalScaleModeFitCircle);
cl_assert_gpoint_precise(s_center, 4.5, 5.5);
cl_assert_fixedS16_3(s_radius_outer, 4.5);
cl_assert(s_radius_inner.integer <= 0);
cl_assert_equal_i(s_angle_start, 0);
cl_assert_equal_i(s_angle_end, TRIG_MAX_ANGLE);
graphics_fill_oval(NULL, GRect(10, 12, -10, -12), GOvalScaleModeFitCircle);
cl_assert_gpoint_precise(s_center, 4.5, 5.5);
cl_assert_fixedS16_3(s_radius_outer, 4.5);
cl_assert(s_radius_inner.integer <= 0);
cl_assert_equal_i(s_angle_start, 0);
cl_assert_equal_i(s_angle_end, TRIG_MAX_ANGLE);
graphics_fill_oval(NULL, GRect(0, 0, 0, 0), GOvalScaleModeFillCircle);
cl_assert_gpoint_precise(s_center, 0.0, 0.0);
cl_assert_fixedS16_3(s_radius_outer, 0.0);
cl_assert(s_radius_inner.integer <= 0);
cl_assert_equal_i(s_angle_start, 0);
cl_assert_equal_i(s_angle_end, TRIG_MAX_ANGLE);
}
void graphics_fill_radial_precise_internal(GContext *ctx, GPointPrecise center,
Fixed_S16_3 radius_inner, Fixed_S16_3 radius_outer,
int32_t angle_start, int32_t angle_end) {
s_center = center;
s_radius_inner = radius_inner;
s_radius_outer = radius_outer;
s_angle_start = angle_start;
s_angle_end = angle_end;
}
void test_graphics_circle__fill_radial(void) {
GContext *ctx = (GContext *)&ctx;
cl_assert_gpoint_precise(s_center, 0, 0);
cl_assert_fixedS16_3(s_radius_inner, 0);
cl_assert_fixedS16_3(s_radius_outer, 0);
graphics_fill_radial(NULL, GRect(0, 0, 10, 12), GOvalScaleModeFitCircle, 3, 0, 0);
cl_assert_gpoint_precise(s_center, 4.5, 5.5);
cl_assert_fixedS16_3(s_radius_outer, 4.5);
cl_assert_fixedS16_3(s_radius_inner, 1.5);
}
void test_graphics_circle__DEG_TO_TRIGANGLE(void) {
cl_assert_equal_i(DEG_TO_TRIGANGLE(720), TRIG_MAX_ANGLE * 2);
cl_assert_equal_i(DEG_TO_TRIGANGLE(-720), -TRIG_MAX_ANGLE * 2);
cl_assert_equal_i(DEG_TO_TRIGANGLE(360), TRIG_MAX_ANGLE);
cl_assert_equal_i(DEG_TO_TRIGANGLE(-360), -TRIG_MAX_ANGLE);
cl_assert_equal_i(DEG_TO_TRIGANGLE(180), TRIG_PI);
cl_assert_equal_i(DEG_TO_TRIGANGLE(-180), -TRIG_PI);
cl_assert_equal_i(DEG_TO_TRIGANGLE(90), TRIG_PI / 2);
cl_assert_equal_i(DEG_TO_TRIGANGLE(-90), -TRIG_PI / 2);
cl_assert_equal_i(DEG_TO_TRIGANGLE(0), 0);
}
void test_graphics_circle__TRIGANGLE_TO_DEG(void) {
cl_assert_equal_i(TRIGANGLE_TO_DEG(TRIG_MAX_ANGLE * 2), 720);
cl_assert_equal_i(TRIGANGLE_TO_DEG(-TRIG_MAX_ANGLE * 2), -720);
cl_assert_equal_i(TRIGANGLE_TO_DEG(TRIG_MAX_ANGLE), 360);
cl_assert_equal_i(TRIGANGLE_TO_DEG(-TRIG_MAX_ANGLE), -360);
cl_assert_equal_i(TRIGANGLE_TO_DEG(TRIG_PI / 2), 90);
cl_assert_equal_i(TRIGANGLE_TO_DEG(-TRIG_PI / 2), -90);
cl_assert_equal_i(TRIGANGLE_TO_DEG(TRIG_MAX_ANGLE / 2), 180);
cl_assert_equal_i(TRIGANGLE_TO_DEG(-TRIG_MAX_ANGLE / 2), -180);
cl_assert_equal_i(TRIGANGLE_TO_DEG(TRIG_PI), 180);
cl_assert_equal_i(TRIGANGLE_TO_DEG(-TRIG_PI), -180);
cl_assert_equal_i(TRIGANGLE_TO_DEG(0), 0);
}
+263
View File
@@ -0,0 +1,263 @@
/*
* 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/gtypes.h"
#include "clar.h"
#include "stubs_app_state.h"
#include "stubs_heap.h"
#include "stubs_passert.h"
#include "stubs_process_manager.h"
void test_graphics_colors__black(void) {
cl_assert(GColorBlack.r == 0b00);
cl_assert(GColorBlack.g == 0b00);
cl_assert(GColorBlack.b == 0b00);
cl_assert(GColorBlack.a == 0b11);
}
void test_graphics_colors__white(void) {
cl_assert(GColorWhite.r == 0b11);
cl_assert(GColorWhite.g == 0b11);
cl_assert(GColorWhite.b == 0b11);
cl_assert(GColorWhite.a == 0b11);
}
void test_graphics_colors__red(void) {
cl_assert(GColorRed.r == 0b11);
cl_assert(GColorRed.g == 0b00);
cl_assert(GColorRed.b == 0b00);
cl_assert(GColorRed.a == 0b11);
}
void test_graphics_colors__green(void) {
cl_assert(GColorGreen.r == 0b00);
cl_assert(GColorGreen.g == 0b11);
cl_assert(GColorGreen.b == 0b00);
cl_assert(GColorGreen.a == 0b11);
}
void test_graphics_colors__blue(void) {
cl_assert(GColorBlue.r == 0b00);
cl_assert(GColorBlue.g == 0b00);
cl_assert(GColorBlue.b == 0b11);
cl_assert(GColorBlue.a == 0b11);
}
void test_graphics_colors__equal(void) {
cl_assert(gcolor_equal(GColorBlue, GColorBlue));
cl_assert(gcolor_equal(GColorRed, GColorRed));
cl_assert(!gcolor_equal(GColorRed, GColorBlue));
// Two colors with zero alpha values should be equal regardless of what their RGB channels are
GColor8 color1 = GColorBlue;
color1.a = 0;
GColor8 color2 = GColorRed;
color2.a = 0;
cl_assert(gcolor_equal(color1, color2));
// But two colors with semi-transparent alpha values should not be equal if their RGB channels
// don't match
color1.a = 1;
color2.a = 1;
cl_assert(!gcolor_equal(color1, color2));
}
void test_graphics_colors__equal__deprecated(void) {
cl_assert(gcolor_equal__deprecated(GColorBlue, GColorBlue));
cl_assert(gcolor_equal__deprecated(GColorRed, GColorRed));
cl_assert(!gcolor_equal__deprecated(GColorRed, GColorBlue));
// Check the behavior of the deprecated function (which is incorrect) that two colors with
// zero alpha values but different RGB values are not considered equal
GColor8 color1 = GColorBlue;
color1.a = 0;
GColor8 color2 = GColorRed;
color2.a = 0;
cl_assert(!gcolor_equal__deprecated(color1, color2));
}
void test_graphics_colors__inverted_readable_color(void) {
GColor8 (*fun)(GColor8) = gcolor_legible_over;
// transparent colors result in transparen - who has a better idea?
cl_assert_equal_i(GColorClearARGB8, fun(GColorClear).argb);
// // obvious cases
cl_assert_equal_i(GColorWhiteARGB8, fun(GColorBlack).argb);
cl_assert_equal_i(GColorBlackARGB8, fun(GColorWhite).argb);
// // expectation as derived from Appfaces_all.psd
cl_assert_equal_i(GColorWhiteARGB8, fun(GColorFromHEX(0xff0000)).argb);
cl_assert_equal_i(GColorBlackARGB8, fun(GColorFromHEX(0x00aaff)).argb);
cl_assert_equal_i(GColorWhiteARGB8, fun(GColorFromHEX(0xaa0055)).argb);
cl_assert_equal_i(GColorBlackARGB8, fun(GColorFromHEX(0x55aa55)).argb);
cl_assert_equal_i(GColorBlackARGB8, fun(GColorFromHEX(0xff5555)).argb);
cl_assert_equal_i(GColorWhiteARGB8, fun(GColorFromHEX(0x0055aa)).argb);
cl_assert_equal_i(GColorBlackARGB8, fun(GColorFromHEX(0xff5500)).argb);
cl_assert_equal_i(GColorBlackARGB8, fun(GColorFromHEX(0xaaaaaa)).argb);
// // expectation as derived from Appfaces_generic_colors.psd
cl_assert_equal_i(GColorWhiteARGB8, fun(GColorFromHEX(0x0000aa)).argb);
cl_assert_equal_i(GColorWhiteARGB8, fun(GColorFromHEX(0x005500)).argb);
cl_assert_equal_i(GColorWhiteARGB8, fun(GColorFromHEX(0x550055)).argb);
cl_assert_equal_i(GColorWhiteARGB8, fun(GColorFromHEX(0xaa0000)).argb);
// contradiction with previous case - oh dear...
// cl_assert_equal_i(GColorWhiteARGB8, fun(GColorFromHEX(0xff5500)).argb);
cl_assert_equal_i(GColorWhiteARGB8, fun(GColorFromHEX(0xaa5500)).argb);
}
void test_graphics_colors__grayscale_colors(void) {
cl_assert_equal_i(GColorClearARGB8, gcolor_get_grayscale(GColorClear).argb);
cl_assert_equal_i(GColorWhiteARGB8, gcolor_get_grayscale(GColorWhite).argb);
cl_assert_equal_i(GColorBlackARGB8, gcolor_get_grayscale(GColorBlack).argb);
cl_assert_equal_i(GColorLightGrayARGB8, gcolor_get_grayscale(GColorLightGray).argb);
cl_assert_equal_i(GColorDarkGrayARGB8, gcolor_get_grayscale(GColorDarkGray).argb);
cl_assert_equal_i(GColorWhiteARGB8, gcolor_get_grayscale(GColorYellow).argb);
cl_assert_equal_i(GColorBlackARGB8, gcolor_get_grayscale(GColorBlue).argb);
cl_assert_equal_i(GColorLightGrayARGB8, gcolor_get_grayscale(GColorTiffanyBlue).argb);
cl_assert_equal_i(GColorDarkGrayARGB8, gcolor_get_grayscale(GColorOrange).argb);
}
void test_graphics_colors__bw_colors(void) {
cl_assert_equal_i(GColorClearARGB8, gcolor_get_bw(GColorClear).argb);
cl_assert_equal_i(GColorWhiteARGB8, gcolor_get_bw(GColorWhite).argb);
cl_assert_equal_i(GColorBlackARGB8, gcolor_get_bw(GColorBlack).argb);
cl_assert_equal_i(GColorWhiteARGB8, gcolor_get_bw(GColorLightGray).argb);
cl_assert_equal_i(GColorBlackARGB8, gcolor_get_bw(GColorDarkGray).argb);
cl_assert_equal_i(GColorWhiteARGB8, gcolor_get_bw(GColorYellow).argb);
cl_assert_equal_i(GColorBlackARGB8, gcolor_get_bw(GColorBlue).argb);
cl_assert_equal_i(GColorWhiteARGB8, gcolor_get_bw(GColorTiffanyBlue).argb);
cl_assert_equal_i(GColorBlackARGB8, gcolor_get_bw(GColorOrange).argb);
}
static void prv_test_get_luminance(GColor8 color_to_test, GColor8Component expected_luminance) {
cl_assert_equal_i(gcolor_get_luminance(color_to_test), expected_luminance);
// Test that alpha doesn't affect luminance
GColor8 semitransparent_color = color_to_test;
semitransparent_color.a = 1;
cl_assert_equal_i(gcolor_get_luminance(semitransparent_color), expected_luminance);
GColor8 transparent_color = color_to_test;
transparent_color.a = 0;
cl_assert_equal_i(gcolor_get_luminance(transparent_color), expected_luminance);
}
void test_graphics_colors__get_luminance(void) {
prv_test_get_luminance(GColorBlack, 0);
prv_test_get_luminance(GColorWhite, 3);
prv_test_get_luminance(GColorYellow, 3);
prv_test_get_luminance(GColorRed, 2);
prv_test_get_luminance(GColorBlue, 1);
}
void test_graphics_colors__tint_luminance_lookup_table_init(void) {
GColor8 tint_color = GColorClear;
GColor8 lookup_table_out[GCOLOR8_COMPONENT_NUM_VALUES];
// Passing in NULL for lookup_table_out should assert
cl_assert_passert(gcolor_tint_luminance_lookup_table_init(tint_color, NULL));
// Setting the tint color to black should result in a gradient from black to white
tint_color = GColorBlack;
gcolor_tint_luminance_lookup_table_init(tint_color, lookup_table_out);
cl_assert(gcolor_equal(lookup_table_out[0], GColorBlack));
cl_assert(gcolor_equal(lookup_table_out[1], GColorDarkGray));
cl_assert(gcolor_equal(lookup_table_out[2], GColorLightGray));
cl_assert(gcolor_equal(lookup_table_out[3], GColorWhite));
// Setting the tint color to blue should result in a gradient from blue to yellow
tint_color = GColorBlue;
gcolor_tint_luminance_lookup_table_init(tint_color, lookup_table_out);
cl_assert(gcolor_equal(lookup_table_out[0], GColorBlue));
cl_assert(gcolor_equal(lookup_table_out[1], GColorLiberty));
cl_assert(gcolor_equal(lookup_table_out[2], GColorBrass));
cl_assert(gcolor_equal(lookup_table_out[3], GColorYellow));
// lookup_table_out's entries should all preserve the alpha of tint_color
tint_color = GColorBlack;
tint_color.a = 1;
gcolor_tint_luminance_lookup_table_init(tint_color, lookup_table_out);
for (GColor8Component i = 0; i < GCOLOR8_COMPONENT_NUM_VALUES; i++) {
cl_assert_equal_i(lookup_table_out[i].a, tint_color.a);
}
}
static void prv_test_tint_using_luminance_and_perform_lookup_using_color_luminance(
GColor8 src_color, GColor8 tint_color, GColor8 expected_result_color) {
GColor8 lookup_table[GCOLOR8_COMPONENT_NUM_VALUES];
gcolor_tint_luminance_lookup_table_init(tint_color, lookup_table);
cl_assert(gcolor_equal(
gcolor_perform_lookup_using_color_luminance_and_multiply_alpha(src_color, lookup_table),
expected_result_color));
cl_assert(gcolor_equal(gcolor_tint_using_luminance_and_multiply_alpha(src_color, tint_color),
expected_result_color));
}
void test_graphics_colors__tint_using_luminance_and_perform_lookup_using_color_luminance(void) {
// Passing in NULL for lookup_table should assert
cl_assert_passert(gcolor_perform_lookup_using_color_luminance_and_multiply_alpha(GColorRed,
NULL));
// A src_color of yellow should have a luminance that picks white from lookup_table
// initialized with a tint_color of black
prv_test_tint_using_luminance_and_perform_lookup_using_color_luminance(GColorYellow,
GColorBlack,
GColorWhite);
// A src_color of red should have a luminance that picks light gray from lookup_table initialized
// with a tint_color of black
prv_test_tint_using_luminance_and_perform_lookup_using_color_luminance(GColorRed,
GColorBlack,
GColorLightGray);
// A src_color of blue should have a luminance that picks dark gray from lookup_table
// initialized with a tint_color of black
prv_test_tint_using_luminance_and_perform_lookup_using_color_luminance(GColorBlue,
GColorBlack,
GColorDarkGray);
// A src_color of black should have a luminance that picks black from lookup_table initialized
// with a tint_color of black
prv_test_tint_using_luminance_and_perform_lookup_using_color_luminance(GColorBlack,
GColorBlack,
GColorBlack);
}
void test_graphics_colors__component_multiply(void) {
const GColor8Component max_component_value = GCOLOR8_COMPONENT_NUM_VALUES - 1;
// 0% multiplied by anything is 0%
for (GColor8Component i = 0; i <= max_component_value; i++) {
cl_assert_equal_i(gcolor_component_multiply(i, 0), 0);
cl_assert_equal_i(gcolor_component_multiply(0, i), 0);
}
// Associative property (excluding 0% since we already test it above)
for (GColor8Component i = 1; i <= max_component_value; i++) {
for (GColor8Component j = 1; j <= max_component_value; j++) {
cl_assert_equal_i(gcolor_component_multiply(i, j), gcolor_component_multiply(j, i));
}
}
// A few specific combinations
cl_assert_equal_i(gcolor_component_multiply(3, 3), 3);
cl_assert_equal_i(gcolor_component_multiply(3, 2), 2);
cl_assert_equal_i(gcolor_component_multiply(2, 2), 1);
cl_assert_equal_i(gcolor_component_multiply(1, 3), 1);
cl_assert_equal_i(gcolor_component_multiply(2, 1), 1);
}
@@ -0,0 +1,847 @@
/*
* 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/gtypes.h"
#include "applib/graphics/graphics.h"
#include "util/attributes.h"
#include "clar.h"
#include "util.h"
// Helper Functions
////////////////////////////////////
#include "test_graphics.h"
#include "${BIT_DEPTH_NAME}/test_framebuffer.h"
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
// Definitions
#define SW_EVEN 4
#define SW_ODD 5
// Includes all possible fields for all shapes
// The fields that are not needed for a particular shape will be zeroed out.
typedef struct PACKED {
char func[30];
GContext ctx;
GPoint p0;
GPoint p1;
GRect r0;
uint16_t radius;
uint8_t quadrant;
GCornerMask corner_mask;
int16_t major_axis_offset;
Fixed_S16_3 offset_start;
Fixed_S16_3 offset_stop;
bool anti_aliased;
uint16_t brightness;
GColor color;
GBitmap fb;
} ArgsForMock;
static ArgsForMock s_last_args_for_mock;
static GContext context;
static FrameBuffer *fb = NULL;
/////////////////////////////////////
/// FUNCTION OVERRIDES
/////////////////////////////////////
void graphics_line_draw_1px_non_aa(GContext* ctx, GPoint p0, GPoint p1) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.p0 = p0,
.p1 = p1
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void graphics_line_draw_1px_aa(GContext* ctx, GPoint p0, GPoint p1) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.p0 = p0,
.p1 = p1
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void graphics_line_draw_stroked_aa(GContext* ctx, GPoint p0, GPoint p1, uint8_t stroke_width) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.p0 = p0,
.p1 = p1
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void graphics_line_draw_stroked_non_aa(GContext* ctx, GPoint p0, GPoint p1, uint8_t stroke_width) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.p0 = p0,
.p1 = p1
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void prv_fill_rect_non_aa(GContext* ctx, const GRect *rect, uint16_t radius,
GCornerMask corner_mask, GColor fill_color) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.r0 = *rect,
.radius = radius,
.corner_mask = corner_mask
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void prv_fill_rect_aa(GContext* ctx, const GRect *rect, uint16_t radius,
GCornerMask corner_mask, GColor fill_color) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.r0 = *rect,
.radius = radius,
.corner_mask = corner_mask
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void prv_draw_rect(GContext *ctx, const GRect *rect) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.r0 = *rect,
.radius = 0,
.corner_mask = GCornerNone
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void prv_draw_rect_aa_stroked(GContext *ctx, const GRect *rect, uint8_t stroke_width) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.r0 = *rect,
.radius = 0,
.corner_mask = GCornerNone
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void prv_draw_rect_stroked(GContext *ctx, const GRect *rect, uint8_t stroke_width) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.r0 = *rect,
.radius = 0,
.corner_mask = GCornerNone
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void graphics_circle_draw_1px_non_aa(GContext* ctx, GPoint p, uint16_t radius) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.p0 = p,
.radius = radius
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void graphics_circle_draw_1px_aa(GContext* ctx, GPoint p, uint16_t radius) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.p0 = p,
.radius = radius
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void graphics_circle_draw_stroked_aa(GContext* ctx, GPoint p, uint16_t radius, uint8_t stroke_width) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.p0 = p,
.radius = radius
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void graphics_circle_draw_stroked_non_aa(GContext* ctx, GPoint p, uint16_t radius, uint8_t stroke_width) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.p0 = p,
.radius = radius
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void graphics_internal_circle_quadrant_fill_aa(GContext* ctx, GPoint p,
uint16_t radius, GCornerMask quadrant) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.p0 = p,
.radius = radius,
.corner_mask = quadrant
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void graphics_circle_fill_non_aa(GContext* ctx, GPoint p, uint16_t radius) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.p0 = p,
.radius = radius
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void prv_draw_round_rect(GContext* ctx, const GRect *rect, uint16_t radius) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.r0 = *rect,
.radius = radius,
.corner_mask = GCornersAll
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void prv_draw_round_rect_aa(GContext* ctx, const GRect *rect, uint16_t radius) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.r0 = *rect,
.radius = radius,
.corner_mask = GCornersAll
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void prv_draw_round_rect_aa_stroked(GContext* ctx, const GRect *rect, uint16_t radius,
uint8_t stroke_width) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.r0 = *rect,
.radius = radius,
.corner_mask = GCornersAll
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
void prv_draw_round_rect_stroked(GContext* ctx, const GRect *rect, uint16_t radius, uint8_t stroke_width) {
s_last_args_for_mock = (ArgsForMock){
.ctx = *ctx,
.r0 = *rect,
.radius = radius,
.corner_mask = GCornersAll
};
strncpy(s_last_args_for_mock.func, __func__, sizeof(s_last_args_for_mock.func));
}
/////////////////////////////////////
/// HELPER FUNCTIONS
/////////////////////////////////////
// Copy over the last arguments - take the largest possible shape
static void copy_last_args_for_mock(ArgsForMock *actual_args, ArgsForMock *last_args) {
memcpy(actual_args, last_args, sizeof(ArgsForMock));
}
// Zero out the arguments from the last run
static void reset_last_args_for_mock() {
memset(&s_last_args_for_mock, 0x00, sizeof(s_last_args_for_mock));
}
// Validate the arguments based on the shape that is drawn
static bool validate_args(ArgsForMock *actual_args, ArgsForMock *valid_args) {
if (memcmp(actual_args, valid_args, sizeof(ArgsForMock)) == 0) {
return true;
}
return false;
}
// This macro will call the expected code_block and then validate the arguments set in code_block
// match what was expected from the last mock call (i.e. if graphics_draw_line was called, then
// internally it would call prv_draw_line... and the actual_args would be set to the arguments
// that are set in prv_draw_line... so when compared to directly calling prv_draw_line..., the two
// should match.
#define ASSERT_CALLED(code_block) \
do {\
ArgsForMock actual_args; \
copy_last_args_for_mock(&actual_args, &s_last_args_for_mock); \
reset_last_args_for_mock(); \
code_block; \
bool cmp_result = validate_args(&actual_args, &s_last_args_for_mock); \
printf("1 %d %d %d %d\n", actual_args.r0.origin.x, actual_args.r0.origin.y, \
actual_args.r0.size.w, actual_args.r0.size.h); \
actual_args = s_last_args_for_mock; \
printf("2 %d %d %d %d\n", actual_args.r0.origin.x, actual_args.r0.origin.y, \
actual_args.r0.size.w, actual_args.r0.size.h); \
cl_assert(cmp_result); \
} while(0)
// This is used to make sure nothing has changed when calling code_block
// i.e. no prv_... functions have been called
// If any prv_... functions are called then s_last_args_for_mock will be updated
#define ASSERT_NO_CHANGE(code_block) \
do {\
ArgsForMock actual_args; \
reset_last_args_for_mock(); \
copy_last_args_for_mock(&actual_args, &s_last_args_for_mock); \
code_block; \
bool cmp_result = validate_args(&actual_args, &s_last_args_for_mock); \
cl_assert(cmp_result); \
} while(0)
static void setup_test(GContext* ctx, bool antialiased, uint8_t stroke_width, GColor stroke_color,
GColor fill_color, bool lock) {
graphics_context_set_antialiased(ctx, antialiased);
graphics_context_set_stroke_width(ctx, stroke_width);
graphics_context_set_stroke_color(ctx, stroke_color);
graphics_context_set_fill_color(ctx, fill_color);
ctx->lock = lock;
reset_last_args_for_mock();
}
// Setup
void test_graphics_context_${BIT_DEPTH_NAME}__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
test_graphics_context_init(&context, fb);
}
// Teardown
void test_graphics_context_${BIT_DEPTH_NAME}__cleanup(void) {
free(fb);
}
/////////////////////////////////////
/// TESTS
/////////////////////////////////////
void test_graphics_context_${BIT_DEPTH_NAME}__set(void) {
GDrawState draw_state;
GColor color;
// Stroke Color
graphics_context_set_stroke_color(&context, GColorClear);
cl_assert_equal_i(context.draw_state.stroke_color.argb, GColorClear.argb);
color = GColorBlue;
graphics_context_set_stroke_color(&context, color);
#if PBL_COLOR
cl_assert_equal_i(context.draw_state.stroke_color.argb, GColorBlue.argb);
#else
cl_assert_equal_i(context.draw_state.stroke_color.argb, GColorBlack.argb);
#endif
color.a = 2;
graphics_context_set_stroke_color(&context, color);
#if PBL_COLOR
cl_assert_equal_i(context.draw_state.stroke_color.argb, GColorBlue.argb);
#else
cl_assert_equal_i(context.draw_state.stroke_color.argb, GColorBlack.argb);
#endif
color.a = 1;
graphics_context_set_stroke_color(&context, color);
cl_assert_equal_i(context.draw_state.stroke_color.argb, GColorClear.argb);
color.a = 0;
graphics_context_set_stroke_color(&context, color);
cl_assert_equal_i(context.draw_state.stroke_color.argb, GColorClear.argb);
// Stroke Color - 2-bit
graphics_context_set_stroke_color_2bit(&context, GColor2Black);
cl_assert(gcolor_equal(context.draw_state.stroke_color, GColorBlack));
// Fill Color
graphics_context_set_fill_color(&context, GColorClear);
cl_assert_equal_i(context.draw_state.fill_color.argb, GColorClear.argb);
color = GColorOrange;
graphics_context_set_fill_color(&context, color);
#if PBL_COLOR
cl_assert_equal_i(context.draw_state.fill_color.argb, GColorOrange.argb);
#else
cl_assert_equal_i(context.draw_state.fill_color.argb, GColorDarkGray.argb);
#endif
color.a = 2;
graphics_context_set_fill_color(&context, color);
#if PBL_COLOR
cl_assert_equal_i(context.draw_state.fill_color.argb, GColorOrange.argb);
#else
cl_assert_equal_i(context.draw_state.fill_color.argb, GColorDarkGray.argb);
#endif
color.a = 1;
graphics_context_set_fill_color(&context, color);
cl_assert_equal_i(context.draw_state.fill_color.argb, GColorClear.argb);
color.a = 0;
graphics_context_set_fill_color(&context, color);
cl_assert_equal_i(context.draw_state.fill_color.argb, GColorClear.argb);
// Fill Color - 2-bit
graphics_context_set_fill_color_2bit(&context, GColor2White);
cl_assert(gcolor_equal(context.draw_state.fill_color, GColorWhite));
// Compositing Mode
graphics_context_set_compositing_mode(&context, GCompOpOr);
cl_assert(context.draw_state.compositing_mode == GCompOpOr);
// Text Color
graphics_context_set_text_color(&context, GColorClear);
cl_assert_equal_i(context.draw_state.text_color.argb, GColorClear.argb);
color = GColorYellow;
graphics_context_set_text_color(&context, color);
#if PBL_COLOR
cl_assert_equal_i(context.draw_state.text_color.argb, GColorYellow.argb);
#else
cl_assert_equal_i(context.draw_state.text_color.argb, GColorWhite.argb);
#endif
color.a = 2;
graphics_context_set_text_color(&context, color);
#if PBL_COLOR
cl_assert_equal_i(context.draw_state.text_color.argb, GColorYellow.argb);
#else
cl_assert_equal_i(context.draw_state.text_color.argb, GColorWhite.argb);
#endif
color.a = 1;
graphics_context_set_text_color(&context, color);
cl_assert_equal_i(context.draw_state.text_color.argb, GColorClear.argb);
color.a = 0;
graphics_context_set_text_color(&context, color);
cl_assert_equal_i(context.draw_state.text_color.argb, GColorClear.argb);
// Text Color - 2-bit
graphics_context_set_text_color_2bit(&context, GColor2White);
cl_assert(gcolor_equal(context.draw_state.text_color, GColorWhite));
#if PBL_COLOR
// Antialiased
graphics_context_set_antialiased(&context, true);
cl_assert(context.draw_state.antialiased == true);
#endif
// Stroke Width
graphics_context_set_stroke_width(&context, 11);
cl_assert(context.draw_state.stroke_width == 11);
// Make sure setting to zero is ignored
draw_state = graphics_context_get_drawing_state(&context);
graphics_context_set_stroke_width(&context, 0);
cl_assert(memcmp(&draw_state, &context.draw_state, sizeof(GDrawState)) == 0);
// Check draw state
memset(&draw_state, 0x5A, sizeof(GDrawState));
graphics_context_set_drawing_state(&context, draw_state);
cl_assert(memcmp(&draw_state, &context.draw_state, sizeof(GDrawState)) == 0);
}
void test_graphics_context_${BIT_DEPTH_NAME}__draw_antialiased(void) {
// Stroke width = 1, antialiased
setup_test(&context, true, 1, GColorBlack, GColorBlack, false);
graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10));
#if PBL_COLOR
ASSERT_CALLED(graphics_line_draw_1px_aa(&context, GPoint(5, 5), GPoint(45, 10)));
#else
ASSERT_CALLED(graphics_line_draw_1px_non_aa(&context, GPoint(5, 5), GPoint(45, 10)));
#endif
setup_test(&context, true, 1, GColorBlack, GColorBlack, false);
graphics_draw_rect(&context, &GRect(10, 20, 40, 10));
ASSERT_CALLED(prv_draw_rect(&context, &GRect(10, 20, 40, 10)));
setup_test(&context, true, 1, GColorBlack, GColorBlack, false);
graphics_draw_circle(&context, GPoint(50, 50), 10);
#if PBL_COLOR
ASSERT_CALLED(graphics_circle_draw_1px_aa(&context, GPoint(50, 50), 10));
#else
ASSERT_CALLED(graphics_circle_draw_1px_non_aa(&context, GPoint(50, 50), 10));
#endif
setup_test(&context, true, 1, GColorBlack, GColorBlack, false);
graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4);
#if PBL_COLOR
ASSERT_CALLED(prv_draw_round_rect_aa(&context, &GRect(20, 80, 40, 10), 4));
#else
ASSERT_CALLED(prv_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4));
#endif
}
void test_graphics_context_${BIT_DEPTH_NAME}__draw_stroke_width_1(void) {
// Stroke width 1, non-antialiased
setup_test(&context, false, 1, GColorBlack, GColorBlack, false);
graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10));
ASSERT_CALLED(graphics_line_draw_1px_non_aa(&context, GPoint(5, 5), GPoint(45, 10)));
setup_test(&context, false, 1, GColorBlack, GColorBlack, false);
graphics_draw_rect(&context, &GRect(10, 20, 40, 10));
ASSERT_CALLED(prv_draw_rect(&context, &GRect(10, 20, 40, 10)));
setup_test(&context, false, 1, GColorBlack, GColorBlack, false);
graphics_draw_circle(&context, GPoint(50, 50), 10);
ASSERT_CALLED(graphics_circle_draw_1px_non_aa(&context, GPoint(50, 50), 10));
setup_test(&context, false, 1, GColorBlack, GColorBlack, false);
graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4);
ASSERT_CALLED(prv_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4));
}
void test_graphics_context_${BIT_DEPTH_NAME}__draw_stroke_width_2(void) {
// Stroke width 2, non-antialiased
setup_test(&context, false, 2, GColorBlack, GColorBlack, false);
graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10));
ASSERT_CALLED(graphics_line_draw_stroked_non_aa(&context, GPoint(5, 5), GPoint(45, 10), 2));
setup_test(&context, false, 2, GColorBlack, GColorBlack, false);
graphics_draw_rect(&context, &GRect(10, 20, 40, 10));
ASSERT_CALLED(prv_draw_rect(&context, &GRect(10, 20, 40, 10)));
setup_test(&context, false, 2, GColorBlack, GColorBlack, false);
graphics_draw_circle(&context, GPoint(50, 50), 10);
ASSERT_CALLED(graphics_circle_draw_stroked_non_aa(&context, GPoint(50, 50), 10, 2));
setup_test(&context, false, 2, GColorBlack, GColorBlack, false);
graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4);
ASSERT_CALLED(prv_draw_round_rect_stroked(&context, &GRect(20, 80, 40, 10), 4, 2));
}
void test_graphics_context_${BIT_DEPTH_NAME}__draw_stroke_width_even(void) {
// Stroke width even > 2, non-antialiased
setup_test(&context, false, SW_EVEN, GColorBlack, GColorBlack, false);
graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10));
ASSERT_CALLED(graphics_line_draw_stroked_non_aa(&context, GPoint(5, 5), GPoint(45, 10), SW_EVEN));
setup_test(&context, false, SW_EVEN, GColorBlack, GColorBlack, false);
graphics_draw_rect(&context, &GRect(10, 20, 40, 10));
ASSERT_CALLED(prv_draw_rect_stroked(&context, &GRect(10, 20, 40, 10), SW_EVEN));
setup_test(&context, false, SW_EVEN, GColorBlack, GColorBlack, false);
graphics_draw_circle(&context, GPoint(50, 50), 10);
ASSERT_CALLED(graphics_circle_draw_stroked_non_aa(&context, GPoint(50, 50), 10, SW_EVEN));
setup_test(&context, false, SW_EVEN, GColorBlack, GColorBlack, false);
graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4);
ASSERT_CALLED(prv_draw_round_rect_stroked(&context, &GRect(20, 80, 40, 10), 4, SW_EVEN));
}
void test_graphics_context_${BIT_DEPTH_NAME}__draw_stroke_width_odd(void) {
// Stroke width odd > 1, non-antialiased
setup_test(&context, false, SW_ODD, GColorBlack, GColorBlack, false);
graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10));
ASSERT_CALLED(graphics_line_draw_stroked_non_aa(&context, GPoint(5, 5), GPoint(45, 10), SW_ODD));
setup_test(&context, false, SW_ODD, GColorBlack, GColorBlack, false);
graphics_draw_rect(&context, &GRect(10, 20, 40, 10));
ASSERT_CALLED(prv_draw_rect_stroked(&context, &GRect(10, 20, 40, 10), SW_ODD));
setup_test(&context, false, SW_ODD, GColorBlack, GColorBlack, false);
graphics_draw_circle(&context, GPoint(50, 50), 10);
ASSERT_CALLED(graphics_circle_draw_stroked_non_aa(&context, GPoint(50, 50), 10, SW_ODD));
setup_test(&context, false, SW_ODD, GColorBlack, GColorBlack, false);
graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4);
ASSERT_CALLED(prv_draw_round_rect_stroked(&context, &GRect(20, 80, 40, 10), 4, SW_ODD));
}
void test_graphics_context_${BIT_DEPTH_NAME}__draw_antialiased_stroke_width_2(void) {
// Stroke width = 2, antialiased
setup_test(&context, true, 2, GColorBlack, GColorBlack, false);
graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10));
#if PBL_COLOR
ASSERT_CALLED(graphics_line_draw_stroked_aa(&context, GPoint(5, 5), GPoint(45, 10), 2));
#else
ASSERT_CALLED(graphics_line_draw_stroked_non_aa(&context, GPoint(5, 5), GPoint(45, 10), 2));
#endif
setup_test(&context, true, 2, GColorBlack, GColorBlack, false);
graphics_draw_rect(&context, &GRect(10, 20, 40, 10));
ASSERT_CALLED(prv_draw_rect(&context, &GRect(10, 20, 40, 10)));
setup_test(&context, true, 2, GColorBlack, GColorBlack, false);
graphics_draw_circle(&context, GPoint(50, 50), 10);
#if PBL_COLOR
ASSERT_CALLED(graphics_circle_draw_stroked_aa(&context, GPoint(50, 50), 10, 2));
#else
ASSERT_CALLED(graphics_circle_draw_stroked_non_aa(&context, GPoint(50, 50), 10, 2));
#endif
setup_test(&context, true, 2, GColorBlack, GColorBlack, false);
graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4);
#if PBL_COLOR
ASSERT_CALLED(prv_draw_round_rect_aa_stroked(&context, &GRect(20, 80, 40, 10), 4, 2));
#else
ASSERT_CALLED(prv_draw_round_rect_stroked(&context, &GRect(20, 80, 40, 10), 4, 2));
#endif
}
void test_graphics_context_${BIT_DEPTH_NAME}__draw_antialiased_stroke_width_even(void) {
// Stroke width even > 2, antialiased
setup_test(&context, true, SW_EVEN, GColorBlack, GColorBlack, false);
graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10));
#if PBL_COLOR
ASSERT_CALLED(graphics_line_draw_stroked_aa(&context, GPoint(5, 5), GPoint(45, 10), SW_EVEN));
#else
ASSERT_CALLED(graphics_line_draw_stroked_non_aa(&context, GPoint(5, 5), GPoint(45, 10), SW_EVEN));
#endif
setup_test(&context, true, SW_EVEN, GColorBlack, GColorBlack, false);
graphics_draw_rect(&context, &GRect(10, 20, 40, 10));
#if PBL_COLOR
ASSERT_CALLED(prv_draw_rect_aa_stroked(&context, &GRect(10, 20, 40, 10), SW_EVEN));
#else
ASSERT_CALLED(prv_draw_rect_stroked(&context, &GRect(10, 20, 40, 10), SW_EVEN));
#endif
setup_test(&context, true, SW_EVEN, GColorBlack, GColorBlack, false);
graphics_draw_circle(&context, GPoint(50, 50), 10);
#if PBL_COLOR
ASSERT_CALLED(graphics_circle_draw_stroked_aa(&context, GPoint(50, 50), 10, SW_EVEN));
#else
ASSERT_CALLED(graphics_circle_draw_stroked_non_aa(&context, GPoint(50, 50), 10, SW_EVEN));
#endif
setup_test(&context, true, SW_EVEN, GColorBlack, GColorBlack, false);
graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4);
#if PBL_COLOR
ASSERT_CALLED(prv_draw_round_rect_aa_stroked(&context, &GRect(20, 80, 40, 10), 4, SW_EVEN));
#else
ASSERT_CALLED(prv_draw_round_rect_stroked(&context, &GRect(20, 80, 40, 10), 4, SW_EVEN));
#endif
}
void test_graphics_context_${BIT_DEPTH_NAME}__draw_antialiased_stroke_width_odd(void) {
// Stroke width odd > 1, antialiased
setup_test(&context, true, SW_ODD, GColorBlack, GColorBlack, false);
graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10));
#if PBL_COLOR
ASSERT_CALLED(graphics_line_draw_stroked_aa(&context, GPoint(5, 5), GPoint(45, 10), SW_ODD));
#else
ASSERT_CALLED(graphics_line_draw_stroked_non_aa(&context, GPoint(5, 5), GPoint(45, 10), SW_ODD));
#endif
setup_test(&context, true, SW_ODD, GColorBlack, GColorBlack, false);
graphics_draw_rect(&context, &GRect(10, 20, 40, 10));
#if PBL_COLOR
ASSERT_CALLED(prv_draw_rect_aa_stroked(&context, &GRect(10, 20, 40, 10), SW_ODD));
#else
ASSERT_CALLED(prv_draw_rect_stroked(&context, &GRect(10, 20, 40, 10), SW_ODD));
#endif
setup_test(&context, true, SW_ODD, GColorBlack, GColorBlack, false);
graphics_draw_circle(&context, GPoint(50, 50), 10);
#if PBL_COLOR
ASSERT_CALLED(graphics_circle_draw_stroked_aa(&context, GPoint(50, 50), 10, SW_ODD));
#else
ASSERT_CALLED(graphics_circle_draw_stroked_non_aa(&context, GPoint(50, 50), 10, SW_ODD));
#endif
setup_test(&context, true, SW_ODD, GColorBlack, GColorBlack, false);
graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4);
#if PBL_COLOR
ASSERT_CALLED(prv_draw_round_rect_aa_stroked(&context, &GRect(20, 80, 40, 10), 4, SW_ODD));
#else
ASSERT_CALLED(prv_draw_round_rect_stroked(&context, &GRect(20, 80, 40, 10), 4, SW_ODD));
#endif
}
void test_graphics_context_${BIT_DEPTH_NAME}__fill(void) {
// Fill shape, non-antialiased (Stroke width/color N/A)
setup_test(&context, false, 5, GColorBlack, GColorBlack, false);
graphics_fill_rect(&context, &GRect(10, 20, 40, 10));
ASSERT_CALLED(prv_fill_rect_non_aa(&context, &GRect(10, 20, 40, 10), 0, GCornerNone, GColorBlack));
setup_test(&context, false, 5, GColorBlack, GColorBlack, false);
graphics_fill_circle(&context, GPoint(50, 50), 10);
ASSERT_CALLED(graphics_circle_fill_non_aa(&context, GPoint(50, 50), 10));
setup_test(&context, false, 5, GColorBlack, GColorBlack, false);
graphics_fill_round_rect(&context, &GRect(20, 80, 40, 10), 4, GCornersAll);
ASSERT_CALLED(prv_fill_rect_non_aa(&context, &GRect(20, 80, 40, 10), 4, GCornersAll, GColorBlack));
}
void test_graphics_context_${BIT_DEPTH_NAME}__fill_antialiased(void) {
// Fill shape, antialiased (Stroke width/color N/A)
setup_test(&context, true, 5, GColorBlack, GColorBlack, false);
graphics_fill_rect(&context, &GRect(10, 20, 40, 10));
#if SCREEN_COLOR_DEPTH_BITS == 1
ASSERT_CALLED(prv_fill_rect_non_aa(&context, &GRect(10, 20, 40, 10), 0, GCornerNone, GColorBlack));
#else
ASSERT_CALLED(prv_fill_rect_aa(&context, &GRect(10, 20, 40, 10), 0, GCornerNone, GColorBlack));
#endif
setup_test(&context, true, 5, GColorBlack, GColorBlack, false);
graphics_fill_circle(&context, GPoint(50, 50), 10);
#if PBL_COLOR
ASSERT_CALLED(graphics_internal_circle_quadrant_fill_aa(&context, GPoint(50, 50), 10, GCornersAll));
#else
ASSERT_CALLED(graphics_circle_fill_non_aa(&context, GPoint(50, 50), 10));
#endif
setup_test(&context, true, 5, GColorBlack, GColorBlack, false);
graphics_fill_round_rect(&context, &GRect(20, 80, 40, 10), 4, GCornersAll);
#if SCREEN_COLOR_DEPTH_BITS == 1
ASSERT_CALLED(prv_fill_rect_non_aa(&context, &GRect(20, 80, 40, 10), 4, GCornersAll, GColorBlack));
#else
ASSERT_CALLED(prv_fill_rect_aa(&context, &GRect(20, 80, 40, 10), 4, GCornersAll, GColorBlack));
#endif
}
void test_graphics_context_${BIT_DEPTH_NAME}__lock(void) {
// Test all the setup test combinations as above
setup_test(&context, false, 1, GColorBlack, GColorBlack, true);
ASSERT_NO_CHANGE(graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10)));
ASSERT_NO_CHANGE(graphics_draw_rect(&context, &GRect(10, 20, 40, 10)));
ASSERT_NO_CHANGE(graphics_draw_circle(&context, GPoint(50, 50), 10));
ASSERT_NO_CHANGE(graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4));
setup_test(&context, false, 2, GColorBlack, GColorBlack, true);
ASSERT_NO_CHANGE(graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10)));
ASSERT_NO_CHANGE(graphics_draw_rect(&context, &GRect(10, 20, 40, 10)));
ASSERT_NO_CHANGE(graphics_draw_circle(&context, GPoint(50, 50), 10));
ASSERT_NO_CHANGE(graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4));
setup_test(&context, false, SW_EVEN, GColorBlack, GColorBlack, true);
ASSERT_NO_CHANGE(graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10)));
ASSERT_NO_CHANGE(graphics_draw_rect(&context, &GRect(10, 20, 40, 10)));
ASSERT_NO_CHANGE(graphics_draw_circle(&context, GPoint(50, 50), 10));
ASSERT_NO_CHANGE(graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4));
setup_test(&context, false, SW_ODD, GColorBlack, GColorBlack, true);
ASSERT_NO_CHANGE(graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10)));
ASSERT_NO_CHANGE(graphics_draw_rect(&context, &GRect(10, 20, 40, 10)));
ASSERT_NO_CHANGE(graphics_draw_circle(&context, GPoint(50, 50), 10));
ASSERT_NO_CHANGE(graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4));
ASSERT_NO_CHANGE(graphics_fill_rect(&context, &GRect(10, 20, 40, 10)));
ASSERT_NO_CHANGE(graphics_fill_circle(&context, GPoint(50, 50), 10));
ASSERT_NO_CHANGE(graphics_fill_round_rect(&context, &GRect(20, 80, 40, 10), 4, GCornersAll));
setup_test(&context, true, 1, GColorBlack, GColorBlack, true);
ASSERT_NO_CHANGE(graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10)));
ASSERT_NO_CHANGE(graphics_draw_rect(&context, &GRect(10, 20, 40, 10)));
ASSERT_NO_CHANGE(graphics_draw_circle(&context, GPoint(50, 50), 10));
ASSERT_NO_CHANGE(graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4));
setup_test(&context, true, 2, GColorBlack, GColorBlack, true);
ASSERT_NO_CHANGE(graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10)));
ASSERT_NO_CHANGE(graphics_draw_rect(&context, &GRect(10, 20, 40, 10)));
ASSERT_NO_CHANGE(graphics_draw_circle(&context, GPoint(50, 50), 10));
ASSERT_NO_CHANGE(graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4));
setup_test(&context, true, SW_EVEN, GColorBlack, GColorBlack, true);
ASSERT_NO_CHANGE(graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10)));
ASSERT_NO_CHANGE(graphics_draw_rect(&context, &GRect(10, 20, 40, 10)));
ASSERT_NO_CHANGE(graphics_draw_circle(&context, GPoint(50, 50), 10));
ASSERT_NO_CHANGE(graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4));
setup_test(&context, true, SW_ODD, GColorBlack, GColorBlack, true);
ASSERT_NO_CHANGE(graphics_draw_line(&context, GPoint(5, 5), GPoint(45, 10)));
ASSERT_NO_CHANGE(graphics_draw_rect(&context, &GRect(10, 20, 40, 10)));
ASSERT_NO_CHANGE(graphics_draw_circle(&context, GPoint(50, 50), 10));
ASSERT_NO_CHANGE(graphics_draw_round_rect(&context, &GRect(20, 80, 40, 10), 4));
ASSERT_NO_CHANGE(graphics_fill_rect(&context, &GRect(10, 20, 40, 10)));
ASSERT_NO_CHANGE(graphics_fill_circle(&context, GPoint(50, 50), 10));
ASSERT_NO_CHANGE(graphics_fill_round_rect(&context, &GRect(20, 80, 40, 10), 4, GCornersAll));
}
void test_graphics_context_${BIT_DEPTH_NAME}__lock_context(void) {
GDrawState draw_state;
context.lock = true;
// Stroke Color
draw_state = graphics_context_get_drawing_state(&context);
graphics_context_set_stroke_color(&context, GColorBlue);
cl_assert(memcmp(&draw_state, &context.draw_state, sizeof(GDrawState)) == 0);
// Fill Color
draw_state = graphics_context_get_drawing_state(&context);
graphics_context_set_fill_color(&context, GColorGreen);
cl_assert(memcmp(&draw_state, &context.draw_state, sizeof(GDrawState)) == 0);
// Compositing Mode
draw_state = graphics_context_get_drawing_state(&context);
graphics_context_set_compositing_mode(&context, GCompOpOr);
cl_assert(memcmp(&draw_state, &context.draw_state, sizeof(GDrawState)) == 0);
// Text Color
draw_state = graphics_context_get_drawing_state(&context);
graphics_context_set_text_color(&context, GColorRed);
cl_assert(memcmp(&draw_state, &context.draw_state, sizeof(GDrawState)) == 0);
// Antialiased
draw_state = graphics_context_get_drawing_state(&context);
graphics_context_set_antialiased(&context, true);
cl_assert(memcmp(&draw_state, &context.draw_state, sizeof(GDrawState)) == 0);
// Stroke Width
draw_state = graphics_context_get_drawing_state(&context);
graphics_context_set_stroke_width(&context, 11);
cl_assert(memcmp(&draw_state, &context.draw_state, sizeof(GDrawState)) == 0);
}
void test_graphics_context_${BIT_DEPTH_NAME}__lock_framebuffer(void) {
FrameBuffer fb = {};
framebuffer_init(&fb, &(GSize) {DISP_COLS, DISP_ROWS});
GContext ctx = {.dest_bitmap.info = {
.format = GBITMAP_NATIVE_FORMAT,
.version = GBITMAP_VERSION_CURRENT,
}, .parent_framebuffer=&fb};
GBitmap *framebuffer = graphics_capture_frame_buffer(&ctx);
cl_assert(ctx.lock == true);
cl_assert_equal_p(framebuffer, &ctx.dest_bitmap);
// Test releasing on any platform
cl_assert(fb.is_dirty == false);
graphics_release_frame_buffer(&ctx, framebuffer);
cl_assert(ctx.lock == false);
cl_assert(fb.is_dirty == true);
};
void test_graphics_context_${BIT_DEPTH_NAME}__lock_framebuffer_8BitCircular(void) {
// Test for locking of requested framebuffer format
GContext ctx = {.dest_bitmap.info.format = GBitmapFormat8BitCircular};
GBitmap *bmp = graphics_capture_frame_buffer_format(&ctx, GBitmapFormat8BitCircular);
cl_assert_equal_p(bmp, &ctx.dest_bitmap);
cl_assert(ctx.lock == true);
};
void test_graphics_context_${BIT_DEPTH_NAME}__lock_framebuffer_fails_from_8BitCircular(void) {
// Test for locking of 8Bit Circular framebuffer when framebuffer is regular 8Bit
GContext ctx = {.dest_bitmap.info.format = GBitmapFormat8BitCircular};
GBitmap *bmp = graphics_capture_frame_buffer_format(&ctx, GBitmapFormat8Bit);
cl_assert(ctx.lock == false);
cl_assert_equal_p(bmp, NULL);
};
void test_graphics_context_${BIT_DEPTH_NAME}__lock_framebuffer_1Bit_on_8BitCircular_must_fail(void) {
// Test for locking of 1Bit framebuffer when frambuffer is 8Bit Circular
GContext ctx = {.dest_bitmap.info.format = GBitmapFormat8BitCircular};
GBitmap *bmp = graphics_capture_frame_buffer_format(&ctx, GBitmapFormat1Bit);
cl_assert(ctx.lock == false);
cl_assert_equal_p(bmp, NULL);
};
void test_graphics_context_${BIT_DEPTH_NAME}__lock_framebuffer_2BitPalette_must_fail(void) {
// Ensure that the code path for unsupported capture formats leaves the GContext unlocked.
GContext ctx = {.dest_bitmap.info.format = GBitmapFormat8Bit};
GBitmap *bmp = graphics_capture_frame_buffer_format(&ctx, GBitmapFormat2BitPalette);
cl_assert(ctx.lock == false);
cl_assert_equal_p(bmp, NULL);
}
@@ -0,0 +1,699 @@
/*
* 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);
}
@@ -0,0 +1,450 @@
/*
* 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 "clar.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/framebuffer.h"
#include "applib/ui/bitmap_layer.h"
#include "applib/ui/window_private.h"
#include "util/size.h"
#include <stdio.h>
// Helper Functions
////////////////////////////////////
#include "test_graphics.h"
#include "../graphics/util.h"
#if (SCREEN_COLOR_DEPTH_BITS == 1)
#include "1bit/test_framebuffer.h"
#elif (SCREEN_COLOR_DEPTH_BITS == 8)
#include "8bit/test_framebuffer.h"
#else
#error "Unrecognized SCREEN_COLOR_DEPTH_BITS"
#endif
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
// Setup
////////////////////////////////////
static GBitmap test_image;
static GContext s_ctx;
static FrameBuffer *s_fb = NULL;
void test_graphics_draw_bitmap__initialize(void) {
s_fb = malloc(sizeof(FrameBuffer));
framebuffer_init(s_fb, &(GSize) {DISP_COLS, DISP_ROWS});
test_graphics_context_init(&s_ctx, s_fb);
read_pbi("no_litter_crop.png.pbi", &test_image);
}
void test_graphics_draw_bitmap__cleanup(void) {
free(s_fb);
free(test_image.addr);
}
// Layout Test Support
////////////////////////////////////
#define TEST_LAYER_SIZE (GSize(64, 110))
#define TEST_LAYER_OFFSET_ORIGIN (GPoint(80, 55))
static GPoint s_layer_test_image_bounds_offset;
static void prv_layer_test_update_proc(Layer *layer, GContext *ctx) {
GRect destination = test_image.bounds;
destination.origin = s_layer_test_image_bounds_offset;
graphics_context_set_compositing_mode(ctx, GCompOpAssign);
graphics_draw_bitmap_in_rect(ctx, &test_image, &destination);
}
static void prv_layer_test(GPoint layer_origin, GPoint image_bounds_offset) {
Layer layer;
const GRect layer_frame = (GRect) {
.origin = layer_origin,
.size = TEST_LAYER_SIZE,
};
layer_init(&layer, &layer_frame);
layer_set_update_proc(&layer, prv_layer_test_update_proc);
s_layer_test_image_bounds_offset = image_bounds_offset;
layer_render_tree(&layer, &s_ctx);
}
static void prv_origin_layer_test(GPoint image_bounds_offset) {
prv_layer_test(GPointZero, image_bounds_offset);
}
static void prv_offset_layer_test(GPoint image_bounds_offset) {
prv_layer_test(TEST_LAYER_OFFSET_ORIGIN, image_bounds_offset);
}
static void prv_bitmap_layer_test(GPoint frame_origin, GPoint bounds_origin) {
BitmapLayer bitmap_layer;
const GRect bitmap_layer_frame = (GRect) {
.origin = frame_origin,
.size = TEST_LAYER_SIZE,
};
bitmap_layer_init(&bitmap_layer, &bitmap_layer_frame);
Layer *layer = bitmap_layer_get_layer(&bitmap_layer);
GRect bitmap_layer_bounds = bitmap_layer_frame;
bitmap_layer_bounds.origin = bounds_origin;
layer_set_bounds(layer, &bitmap_layer_bounds);
bitmap_layer_set_bitmap(&bitmap_layer, &test_image);
bitmap_layer_set_compositing_mode(&bitmap_layer, GCompOpAssign);
layer_render_tree(bitmap_layer_get_layer(&bitmap_layer), &s_ctx);
}
static void prv_origin_bitmap_layer_test(GPoint bounds_origin) {
prv_bitmap_layer_test(GPointZero, bounds_origin);
}
static void prv_offset_bitmap_layer_test(GPoint bounds_origin) {
prv_bitmap_layer_test(TEST_LAYER_OFFSET_ORIGIN, bounds_origin);
}
// Composite Test Support
////////////////////////////////////
static GBitmap *prv_create_bitmap_from_png_file(const char *png_filename_without_extension) {
GBitmap *result = NULL;
char png_file_path[strlen(CLAR_FIXTURE_PATH) + 1 + strlen(GRAPHICS_FIXTURE_PATH) + 1 +
strlen(png_filename_without_extension) + 1];
sprintf(png_file_path, "%s/%s/%s.png", CLAR_FIXTURE_PATH, GRAPHICS_FIXTURE_PATH,
png_filename_without_extension);
FILE *fp = fopen(png_file_path, "r");
if (fp) {
// Go to the end of the file to discover the size of the file
if (fseek(fp, 0, SEEK_END) != 0) {
goto cleanup;
}
const long end_of_file = ftell(fp);
const size_t file_size = (size_t)end_of_file;
if (end_of_file == -1) {
goto cleanup;
}
uint8_t *png_data = malloc((size_t)file_size);
if (!png_data) {
goto cleanup;
}
// Go back to the start of the file and read it into the buffer
if (fseek(fp, 0L, SEEK_SET) != 0) {
goto cleanup;
}
cl_assert_equal_i(fread(png_data, 1, file_size, fp), file_size);
// Create a GBitmap using the PNG data
result = gbitmap_create_from_png_data(png_data, file_size);
if (result) {
free(png_data);
}
}
cleanup:
fclose(fp);
return result;
}
static GBitmap *prv_create_bitmap_from_pbi_file(const char *pbi_filename_without_extension) {
// Create a static GBitmap to avoid modifying the read_pbi() function to return a GBitmap
static GBitmap result;
char pbi_filename[PATH_STRING_LENGTH] = {0};
snprintf(pbi_filename, PATH_STRING_LENGTH, "%s.pbi", pbi_filename_without_extension);
if (!read_pbi(pbi_filename, &result)) {
return NULL;
} else {
return &result;
}
}
typedef GBitmap *(*CompositeTestGBitmapCreateFunc)(const char *filename);
typedef struct CompositeTest {
const char *test_name;
GBitmapFormat expected_test_image_bitmap_format;
CompositeTestGBitmapCreateFunc bitmap_create_func;
bool need_to_destroy_bitmap;
} CompositeTest;
static const CompositeTest s_composite_tests[] = {
{
.test_name = "1bitBW",
.expected_test_image_bitmap_format = GBitmapFormat1Bit,
.bitmap_create_func = prv_create_bitmap_from_pbi_file,
.need_to_destroy_bitmap = false,
},
{
.test_name = "2bitTrns",
.expected_test_image_bitmap_format = GBitmapFormat2BitPalette,
.bitmap_create_func = prv_create_bitmap_from_png_file,
.need_to_destroy_bitmap = true,
},
#if PBL_COLOR
{
.test_name = "4bitTrns",
.expected_test_image_bitmap_format = GBitmapFormat4BitPalette,
.bitmap_create_func = prv_create_bitmap_from_png_file,
.need_to_destroy_bitmap = true,
},
{
.test_name = "8bitTrns",
.expected_test_image_bitmap_format = GBitmapFormat8Bit,
.bitmap_create_func = prv_create_bitmap_from_png_file,
.need_to_destroy_bitmap = true,
},
#endif
};
#define COMPOSITE_TEST_IMAGE_SIZE_WIDTH (100)
#define COMPOSITE_TEST_OFFSET_X (COMPOSITE_TEST_IMAGE_SIZE_WIDTH / 2)
#define COMPOSITE_TEST_OFFSET_Y (0)
static bool prv_gbitmap_format_and_compositing_mode_combo_is_valid(GBitmapFormat bitmap_format,
GCompOp compositing_mode) {
return !((bitmap_format != GBitmapFormat1Bit) &&
((compositing_mode == GCompOpAssignInverted) ||
(compositing_mode == GCompOpOr) ||
(compositing_mode == GCompOpAnd) ||
(compositing_mode == GCompOpClear)));
}
static void prv_composite_test_draw_bitmap(GContext *ctx, GBitmap *bitmap, GPoint offset,
GCompOp compositing_mode) {
GRect destination = bitmap->bounds;
destination.origin = gpoint_add(destination.origin, offset);
graphics_context_set_compositing_mode(ctx, GCompOpAssign);
graphics_draw_bitmap_in_rect(ctx, bitmap, &destination);
destination.origin = gpoint_add(destination.origin, GPoint(COMPOSITE_TEST_OFFSET_X,
COMPOSITE_TEST_OFFSET_Y));
graphics_context_set_compositing_mode(ctx, compositing_mode);
graphics_draw_bitmap_in_rect(ctx, bitmap, &destination);
}
static void prv_composite_test(const char *unit_test_name, GCompOp compositing_mode) {
for (size_t i = 0; i < ARRAY_LENGTH(s_composite_tests); i++) {
const CompositeTest *test_data = &s_composite_tests[i];
// Skip invalid GBitmapFormat and GCompOp combinations
if (!prv_gbitmap_format_and_compositing_mode_combo_is_valid(
test_data->expected_test_image_bitmap_format, compositing_mode)) {
break;
}
framebuffer_clear(s_fb);
char test_image_filename[PATH_STRING_LENGTH] = {0};
snprintf(test_image_filename, PATH_STRING_LENGTH, "test_graphics_draw_bitmap_%s_test_image",
test_data->test_name);
GBitmap *bitmap = test_data->bitmap_create_func(test_image_filename);
cl_assert(bitmap);
cl_assert(gbitmap_get_format(bitmap) == test_data->expected_test_image_bitmap_format);
// Draw the two variations of the test image at GPointZero
prv_composite_test_draw_bitmap(&s_ctx, bitmap, GPointZero, compositing_mode);
// Then redraw the two variations offset so the bottom right edge of the right variation
// is aligned with the bottom right edge of the framebuffer
const GPoint framebuffer_bottom_right_point =
GPoint(grect_get_max_x(&s_ctx.dest_bitmap.bounds),
grect_get_max_y(&s_ctx.dest_bitmap.bounds));
GPoint offset_point = gpoint_sub(framebuffer_bottom_right_point,
GPoint(bitmap->bounds.size.w, bitmap->bounds.size.h));
offset_point = gpoint_sub(offset_point, GPoint(COMPOSITE_TEST_OFFSET_X,
COMPOSITE_TEST_OFFSET_Y));
prv_composite_test_draw_bitmap(&s_ctx, bitmap, offset_point, compositing_mode);
// Check the result
char unit_test_result_image_file_base_name[PATH_STRING_LENGTH] = {0};
snprintf(unit_test_result_image_file_base_name, PATH_STRING_LENGTH,
"%s_%s", unit_test_name, test_data->test_name);
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap,
namecat(unit_test_result_image_file_base_name, ".pbi")));
if (test_data->need_to_destroy_bitmap) {
gbitmap_destroy(bitmap);
}
}
}
// Tests
////////////////////////////////////
void test_graphics_draw_bitmap__origin_layer_inside(void) {
prv_origin_layer_test(GPointZero);
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__origin_layer_across_x(void) {
prv_origin_layer_test(GPoint(25, 0));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__origin_layer_across_nx(void) {
prv_origin_layer_test(GPoint(-25, 0));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__origin_layer_across_y(void) {
prv_origin_layer_test(GPoint(0, 40));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__origin_layer_across_ny(void) {
prv_origin_layer_test(GPoint(0, -40));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__origin_bitmap_layer_inside(void) {
prv_origin_bitmap_layer_test(GPointZero);
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__origin_bitmap_layer_across_x(void) {
prv_origin_bitmap_layer_test(GPoint(25, 0));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__origin_bitmap_layer_across_nx(void) {
prv_origin_bitmap_layer_test(GPoint(-25, 0));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__origin_bitmap_layer_across_y(void) {
prv_origin_bitmap_layer_test(GPoint(0, 75));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__origin_bitmap_layer_across_ny(void) {
prv_origin_bitmap_layer_test(GPoint(0, -25));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__offset_layer_inside(void) {
prv_offset_layer_test(GPointZero);
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__offset_layer_across_x(void) {
prv_offset_layer_test(GPoint(25, 0));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__offset_layer_across_nx(void) {
prv_offset_layer_test(GPoint(-25, 0));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__offset_layer_across_y(void) {
prv_offset_layer_test(GPoint(0, 40));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__offset_layer_across_ny(void) {
prv_offset_layer_test(GPoint(0, -40));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__offset_bitmap_layer_inside(void) {
prv_offset_bitmap_layer_test(GPointZero);
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__offset_bitmap_layer_across_x(void) {
prv_offset_bitmap_layer_test(GPoint(25, 0));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__offset_bitmap_layer_across_nx(void) {
prv_offset_bitmap_layer_test(GPoint(-25, 0));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__offset_bitmap_layer_across_y(void) {
prv_offset_bitmap_layer_test(GPoint(0, 75));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__offset_bitmap_layer_across_ny(void) {
prv_offset_bitmap_layer_test(GPoint(0, -25));
cl_check(gbitmap_pbi_eq(&s_ctx.dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_bitmap__composite_assign(void) {
prv_composite_test(__func__, GCompOpAssign);
}
void test_graphics_draw_bitmap__composite_assign_inverted(void) {
prv_composite_test(__func__, GCompOpAssignInverted);
}
void test_graphics_draw_bitmap__composite_or(void) {
prv_composite_test(__func__, GCompOpOr);
}
void test_graphics_draw_bitmap__composite_and(void) {
prv_composite_test(__func__, GCompOpAnd);
}
void test_graphics_draw_bitmap__composite_clear(void) {
prv_composite_test(__func__, GCompOpClear);
}
void test_graphics_draw_bitmap__composite_set(void) {
prv_composite_test(__func__, GCompOpSet);
}
void test_graphics_draw_bitmap__composite_tint(void) {
graphics_context_set_tint_color(&s_ctx, GColorOrange);
prv_composite_test(__func__, GCompOpTint);
}
void test_graphics_draw_bitmap__composite_tint_luminance_black_opaque(void) {
graphics_context_set_tint_color(&s_ctx, GColorBlack);
prv_composite_test(__func__, GCompOpTintLuminance);
}
void test_graphics_draw_bitmap__composite_tint_luminance_black_semitransparent(void) {
GColor tint_color = GColorBlack;
tint_color.a = 2;
// We have to set the tint color directly in the GContext because
// graphics_context_set_tint_color() calls gcolor_closest_opaque() on the color you give it
s_ctx.draw_state.tint_color = tint_color;
prv_composite_test(__func__, GCompOpTintLuminance);
}
void test_graphics_draw_bitmap__composite_tint_luminance_blue_opaque(void) {
graphics_context_set_tint_color(&s_ctx, GColorBlue);
prv_composite_test(__func__, GCompOpTintLuminance);
}
@@ -0,0 +1,926 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "applib/graphics/graphics.h"
#include "applib/graphics/graphics_circle_private.h"
#include "applib/graphics/framebuffer.h"
#include "util/trig.h"
#include "applib/ui/window_private.h"
#include "applib/ui/layer.h"
#include "util/size.h"
#include "clar.h"
#include "util.h"
#include "pebble_asserts.h"
#include <stdio.h>
// 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_circle_${BIT_DEPTH_NAME}__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
}
// Teardown
void test_graphics_draw_circle_${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_context_set_antialiased(ctx, false);
graphics_draw_circle(ctx, GPoint(14, 14), 12);
}
void white_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorWhite);
graphics_context_set_antialiased(ctx, false);
graphics_draw_circle(ctx, GPoint(14, 14), 12);
}
void clear_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorClear);
graphics_context_set_antialiased(ctx, false);
graphics_draw_circle(ctx, GPoint(14, 14), 12);
}
void across_x_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_draw_circle(ctx, GPoint(28, 14), 12);
}
void across_nx_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_draw_circle(ctx, GPoint(-14, 14), 12);
}
void across_y_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_draw_circle(ctx, GPoint(14, 28), 12);
}
void across_ny_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_draw_circle(ctx, GPoint(14, -14), 12);
}
void test_graphics_draw_circle_${BIT_DEPTH_NAME}__origin_layer(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(0, 0, 28, 28));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_inside_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_x_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_across_x_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_nx_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_across_nx_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_y_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_across_y_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_ny_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_across_ny_origin_layer.${BIT_DEPTH_NAME}.pbi"));
}
#define RADIUS_BIG 15
#define RADIUS_MEDIUM 8
#define RADIUS_MIN_CALCULATED 3
#define RADIUS_MAX_PRECOMPUTED 2
#define RADIUS_SMALL 1
#define RADIUS_NONE 0
#define ORIGIN_RECT_NO_CLIP GRect(0, 0, 144, 168)
#define ORIGIN_RECT_CLIP_XY GRect(0, 0, 30, 40)
#define ORIGIN_RECT_CLIP_NXNY GRect(0, 0, 30, 40)
#define CENTER_OF_ORIGIN_RECT GPoint(20, 25)
#define CENTER_OF_ORIGIN_RECT_NXNY GPoint(10, 15)
void test_graphics_draw_circle_${BIT_DEPTH_NAME}__origin_layer_aa(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Big circles
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_origin_aa_r16_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_origin_aa_r16_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_origin_aa_r16_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Medium circles
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_origin_aa_r8_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_origin_aa_r8_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_origin_aa_r8_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Small circles
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_origin_aa_r1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_origin_aa_r1_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_origin_aa_r1_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Testing of the special cases for radius:
// Radius of 3 - starting point for calculated edges
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_MIN_CALCULATED);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_origin_aa_r3_no_clip.${BIT_DEPTH_NAME}.pbi"));
// Radius of 2 - ending point for precomputed edges
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_MAX_PRECOMPUTED);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_origin_aa_r2_no_clip.${BIT_DEPTH_NAME}.pbi"));
// Radius of 0 - draw a single pixel
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_NONE);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_origin_aa_r0_no_clip.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_draw_circle_${BIT_DEPTH_NAME}__offset_layer(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(10, 15, 28, 28));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_inside_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_x_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_across_x_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_nx_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_across_nx_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_y_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_across_y_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_ny_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_across_ny_offset_layer.${BIT_DEPTH_NAME}.pbi"));
}
#define OFFSET_RECT_NO_CLIP GRect(10, 10, 40, 50)
#define OFFSET_RECT_CLIP_XY GRect(10, 10, 30, 40)
#define OFFSET_RECT_CLIP_NXNY GRect(0, 0, 30, 40)
#define CENTER_OF_OFFSET_RECT GPoint(10, 15)
#define CENTER_OF_OFFSET_RECT_NXNY GPoint(0, 5)
void test_graphics_draw_circle_${BIT_DEPTH_NAME}__offset_layer_aa(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Big circles
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_aa_r16_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_aa_r16_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT_NXNY, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_aa_r16_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Medium circles
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_aa_r8_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_aa_r8_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT_NXNY, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_aa_r8_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Small circles
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_aa_r1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_aa_r1_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 1);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT_NXNY, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_aa_r1_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
}
extern void graphics_circle_quadrant_draw_1px_non_aa(GContext* ctx, GPoint p,
uint16_t radius,
GCornerMask quadrant);
void test_graphics_draw_circle_${BIT_DEPTH_NAME}__quadrants(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_circle_quadrant_draw_1px_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, GCornerTopLeft);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_r8_quad_top_left.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_circle_quadrant_draw_1px_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, GCornerTopRight);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_r8_quad_top_right.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_circle_quadrant_draw_1px_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, GCornerBottomLeft);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_r8_quad_bottom_left.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_circle_quadrant_draw_1px_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, GCornerBottomRight);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_r8_quad_bottom_right.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_circle_quadrant_draw_1px_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, GCornersTop);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_r8_quads_top.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_circle_quadrant_draw_1px_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, GCornersBottom);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_r8_quads_bottom.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_circle_quadrant_draw_1px_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, GCornersRight);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_r8_quads_right.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_circle_quadrant_draw_1px_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, GCornersLeft);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_offset_r8_quads_left.${BIT_DEPTH_NAME}.pbi"));
}
extern void graphics_circle_quadrant_draw_1px_aa(GContext* ctx, GPoint p,
uint16_t radius,
GCornerMask quadrant);
void test_graphics_draw_circle_${BIT_DEPTH_NAME}__quadrants_aa(void) {
#if PBL_COLOR
GContext ctx;
test_graphics_context_init(&ctx, fb);
typedef struct {
char *filename_part;
GCornerMask mask;
} TestConfig;
TestConfig test_config[] = {
{
.filename_part = "quad_top_left",
.mask = GCornerTopLeft,
},
{
.filename_part = "quad_top_right",
.mask = GCornerTopRight,
},
{
.filename_part = "quad_bottom_right",
.mask = GCornerBottomRight,
},
{
.filename_part = "quad_bottom_left",
.mask = GCornerBottomLeft,
},
{
.filename_part = "quads_top",
.mask = GCornersTop,
},
{
.filename_part = "quads_bottom",
.mask = GCornersBottom,
},
{
.filename_part = "quads_right",
.mask = GCornersRight,
},
{
.filename_part = "quads_left",
.mask = GCornersLeft,
},
};
// note: not the prettiest, fast a quick way to render all the scenarios Nitin was interested in
for (int i = 0; i < ARRAY_LENGTH(test_config); i++) {
TestConfig c = test_config[i];
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
GPoint pt = CENTER_OF_ORIGIN_RECT;
// draw multiple quads with different radiuses
for (int r = 0; r <= 15; r++) {
graphics_circle_quadrant_draw_1px_aa(&ctx, pt, r, c.mask);
// center point follows a grid
pt.x += 30;
if (pt.x > 120) {
pt.x = CENTER_OF_ORIGIN_RECT.x;
pt.y += 30;
}
}
// construct file name and create meaningful assert description
char filename[100];
snprintf(filename, sizeof(filename),
"draw_circle_offset_aa_r8_%s.${BIT_DEPTH_NAME}.pbi", c.filename_part);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, filename));
}
#endif
}
void test_graphics_draw_circle_${BIT_DEPTH_NAME}__clear(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(0, 0, 60, 60));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_inside_origin_layer.${BIT_DEPTH_NAME}.pbi"));
layer_set_update_proc(&layer, &white_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(framebuffer_is_empty("white_over_black", ctx.parent_framebuffer, GColorWhite));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_inside_origin_layer.${BIT_DEPTH_NAME}.pbi"));
layer_set_update_proc(&layer, &clear_layer_update_callback);
layer_render_tree(&layer, &ctx);
#if PBL_COLOR
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_clear.8bit.pbi"));
#else
cl_check(framebuffer_is_empty("clear_over_black", ctx.parent_framebuffer, GColorWhite));
#endif
}
// Draws circles in a grid pattern while increasing the stroke width for each circle
static void prv_draw_circles(GContext *ctx) {
GPoint pt = CENTER_OF_ORIGIN_RECT;
for (int sw = 0; sw <= 10; sw++) {
ctx->draw_state.stroke_width = sw;
graphics_draw_circle(ctx, pt, 0);
// center point follows a grid
pt.x += 30;
if (pt.x > 120) {
pt.x = CENTER_OF_ORIGIN_RECT.x;
pt.y += 30;
}
}
}
#define TO_TRIG(deg) (((deg) * TRIG_MAX_ANGLE) / 360)
void test_graphics_draw_circle_${BIT_DEPTH_NAME}__zero_swX_black(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Draw black circles with different alpha values in the stroke color with antialiasing disabled
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 0);
graphics_context_set_stroke_color(&ctx, GColorBlack);
prv_draw_circles(&ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_r0_swX_black.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_draw_circle_${BIT_DEPTH_NAME}__zero_aa_swX_black(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Draw black circles with different alpha values in the stroke color with antialiasing enabled
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 0);
graphics_context_set_stroke_color(&ctx, GColorBlack);
prv_draw_circles(&ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_r0_aa_swX_black.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_draw_circle_8bit__zero_swX_color(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Draw red circles with different alpha values in the stroke color with antialiasing disabled
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 0);
graphics_context_set_stroke_color(&ctx, GColorRed);
prv_draw_circles(&ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_r0_swX_color.8bit.pbi"));
}
void test_graphics_draw_circle_8bit__zero_aa_swX_color(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Draw red circles with different alpha values in the stroke color with antialiasing enabled
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 0);
graphics_context_set_stroke_color(&ctx, GColorRed);
prv_draw_circles(&ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_r0_aa_swX_color.8bit.pbi"));
}
void test_graphics_draw_circle_${BIT_DEPTH_NAME}__zero_swX_clear(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Draw clear circles with antialiasing disabled
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 0);
graphics_context_set_stroke_color(&ctx, GColorClear);
prv_draw_circles(&ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_r0_swX_clear.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_draw_circle_${BIT_DEPTH_NAME}__zero_aa_swX_clear(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Draw clear circles with antialiasing enabled
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 0);
graphics_context_set_stroke_color(&ctx, GColorClear);
prv_draw_circles(&ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_circle_r0_aa_swX_clear.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_draw_circle_${BIT_DEPTH_NAME}__arc_stroked(void){
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Letter C
uint32_t angle_end = TRIG_MAX_ANGLE + (TRIG_MAX_ANGLE / 8);
uint32_t angle_start = (TRIG_MAX_ANGLE / 8) * 3;
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 4);
graphics_draw_arc_internal(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_BIG, angle_start, angle_end);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_arc_origin_aa_letter_c.${BIT_DEPTH_NAME}.pbi"));
// Stroke width bigger than radius
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, RADIUS_BIG * 2);
graphics_draw_arc_internal(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_BIG, angle_start, angle_end);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_arc_origin_aa_stroke_bigger_than_radius.${BIT_DEPTH_NAME}.pbi"));
// Negative angle letter C
angle_end -= TRIG_MAX_ANGLE;
angle_start -= TRIG_MAX_ANGLE;
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 4);
graphics_draw_arc_internal(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_BIG, angle_start, angle_end);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_arc_origin_aa_letter_c.${BIT_DEPTH_NAME}.pbi"));
// More negative angle letter C (both angle are actually negative)
angle_end -= TRIG_MAX_ANGLE;
angle_start -= TRIG_MAX_ANGLE;
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 4);
graphics_draw_arc_internal(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_BIG, angle_start, angle_end);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_arc_origin_aa_letter_c.${BIT_DEPTH_NAME}.pbi"));
// Incorrect angles (angle_start > angle_end) - should result in empty image
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, RADIUS_BIG * 2);
graphics_draw_arc_internal(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_BIG, angle_end, angle_start);
cl_check(framebuffer_is_empty(NULL, ctx.parent_framebuffer, GColorWhite));
// table with most popular angles to test
typedef struct {
char *filename_part;
uint32_t angle;
} TestAngles;
TestAngles test_angles[] = {
{
.filename_part = "__1_degrees",
.angle = TRIG_MAX_ANGLE / 360,
},
{
.filename_part = "__6_degrees",
.angle = TRIG_MAX_ANGLE / 60,
},
{
.filename_part = "_30_degrees",
.angle = TRIG_MAX_ANGLE / 12,
},
{
.filename_part = "_45_degrees",
.angle = TRIG_MAX_ANGLE / 8,
},
{
.filename_part = "_90_degrees",
.angle = TRIG_MAX_ANGLE / 4,
},
{
.filename_part = "181_degrees",
.angle = TRIG_MAX_ANGLE / 2 + TRIG_MAX_ANGLE / 360,
}
};
for (int i = 0; i < ARRAY_LENGTH(test_angles); i++) {
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
GPoint pt = CENTER_OF_ORIGIN_RECT;
uint16_t radius = 10;
uint16_t stroke_width = 1;
for (int r = 1; r <= 7; r++) {
graphics_context_set_stroke_width(&ctx, stroke_width);
graphics_draw_arc_internal(&ctx, pt, radius, 0, test_angles[i].angle);
stroke_width += 1;
radius += 3;
pt.x += (radius + stroke_width) * 2;
if (pt.x > 120) {
pt.x = CENTER_OF_ORIGIN_RECT.x;
pt.y += (radius + stroke_width) * 2;
}
}
char filename[100];
snprintf(filename, sizeof(filename),
"draw_arc_offset_aa_end_angle_%s.${BIT_DEPTH_NAME}.pbi", test_angles[i].filename_part);
cl_check_(gbitmap_pbi_eq(&ctx.dest_bitmap, filename), filename);
}
for (int i = 0; i < ARRAY_LENGTH(test_angles); i++) {
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
GPoint pt = CENTER_OF_ORIGIN_RECT;
uint16_t radius = 10;
uint16_t stroke_width = 1;
for (int r = 1; r <= 7; r++) {
graphics_context_set_stroke_width(&ctx, stroke_width);
graphics_draw_arc_internal(&ctx, pt, radius, test_angles[i].angle, TRIG_MAX_ANGLE);
stroke_width += 1;
radius += 3;
pt.x += (radius + stroke_width) * 2;
if (pt.x > 120) {
pt.x = CENTER_OF_ORIGIN_RECT.x;
pt.y += (radius + stroke_width) * 2;
}
}
char filename[100];
snprintf(filename, sizeof(filename),
"draw_arc_offset_aa_start_angle_%s.${BIT_DEPTH_NAME}.pbi", test_angles[i].filename_part);
cl_check_(gbitmap_pbi_eq(&ctx.dest_bitmap, filename), filename);
}
// Additional 90° end_angle unit tests:
angle_start = TO_TRIG(45);
angle_end = TO_TRIG(90);
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 5);
GPoint pt = CENTER_OF_ORIGIN_RECT;
// This test is sponsored by number 4
for (int r = 0; r < 4; r++) {
graphics_draw_arc_internal(&ctx, pt, RADIUS_BIG, angle_start, angle_end);
angle_start += TRIG_MAX_ANGLE/4;
angle_end += TRIG_MAX_ANGLE/4;
pt.x += RADIUS_BIG * 4;
if (pt.x > 120) {
pt.x = CENTER_OF_ORIGIN_RECT.x;
pt.y += RADIUS_BIG * 4;
}
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_arc_aa_end_angle_on_divider.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_draw_circle_8bit__arc_colors(void){
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Letter C
uint32_t angle_end = TRIG_MAX_ANGLE + (TRIG_MAX_ANGLE / 8);
uint32_t angle_start = (TRIG_MAX_ANGLE / 8) * 3;
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_context_set_fill_color(&ctx, GColorRed);
graphics_context_set_stroke_color(&ctx, GColorGreen);
graphics_draw_arc_internal(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_BIG, angle_start, angle_end);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_arc_origin_aa_letter_c_color_1px.8bit.pbi"));
// Letter C SW 4
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 4);
graphics_context_set_fill_color(&ctx, GColorRed);
graphics_context_set_stroke_color(&ctx, GColorGreen);
graphics_draw_arc_internal(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_BIG, angle_start, angle_end);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_arc_origin_aa_letter_c_color_4px.8bit.pbi"));
}
#define DRAWING_SPACING 40
char *mode_names[] = {
"_90_degrees",
"180_degrees",
"270_degrees",
};
char *precision_modes[] = {
"without_faction",
"with_fraction",
};
void test_graphics_draw_circle_${BIT_DEPTH_NAME}__radial_precise(void){
GContext ctx;
test_graphics_context_init(&ctx, fb);
uint32_t angle_start = TRIG_MAX_ANGLE / 8;
uint32_t angle_end = angle_start + (TRIG_MAX_ANGLE / 4);
GPointPrecise center = GPointPrecise(CENTER_OF_ORIGIN_RECT.x * 8, CENTER_OF_ORIGIN_RECT.y * 8);
Fixed_S16_3 radius = (Fixed_S16_3){.integer = RADIUS_BIG};
for (int precision_mode = 0; precision_mode < 2; precision_mode++) {
for (int mode = 0; mode < 3; mode++) {
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 4);
for (int angle_offset=0; angle_offset < 4; angle_offset++) {
graphics_draw_arc_precise_internal(&ctx, center, radius, angle_start, angle_end);
center.x.integer += (angle_offset % 2 == 0) ? DRAWING_SPACING : -DRAWING_SPACING;
center.y.integer += (angle_offset == 1) ? DRAWING_SPACING : 0;
angle_start += TRIG_MAX_ANGLE / 4;
angle_end += TRIG_MAX_ANGLE / 4;
}
char filename[100];
snprintf(filename, sizeof(filename),
"draw_arc_origin_aa_precise_%s_%s_.${BIT_DEPTH_NAME}.pbi", precision_modes[precision_mode], mode_names[mode]);
cl_check_(gbitmap_pbi_eq(&ctx.dest_bitmap, filename), filename);
center.y.integer -= DRAWING_SPACING;
angle_end += TRIG_MAX_ANGLE / 4;
}
center.x.fraction = 4;
center.y.fraction = 4;
radius.fraction = 4;
angle_start = TRIG_MAX_ANGLE / 8;
angle_end = angle_start + (TRIG_MAX_ANGLE / 4);
}
}
void test_graphics_draw_circle_${BIT_DEPTH_NAME}__calc_draw_config_caps(void) {
// ## Section with special cases
// Test for no angles
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(0), TO_TRIG(0)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(0),GCornerNone},
.full_quadrants = GCornerNone,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(0),GCornerNone}
}));
// Tests for 360° angle
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(0), TO_TRIG(360)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(0),GCornerNone},
.full_quadrants = GCornersAll,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(0),GCornerNone}
}));
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(20), TO_TRIG(380)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(0),GCornerNone},
.full_quadrants = GCornersAll,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(0),GCornerNone}
}));
// Tests for overflowing angles
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(20), TO_TRIG(1000)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(0),GCornerNone},
.full_quadrants = GCornersAll,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(0),GCornerNone}
}));
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(360), TO_TRIG(370)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(0),GCornerTopRight},
.full_quadrants = GCornerNone,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(10),GCornerTopRight}
}));
// # Section with quadrant filling
// Tests for full quadrants
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(0), TO_TRIG(90)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(0),GCornerTopRight},
.full_quadrants = GCornerTopRight,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(90),GCornerBottomRight}
}));
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(90), TO_TRIG(180)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(90),GCornerBottomRight},
.full_quadrants = GCornerBottomRight,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(180),GCornerBottomLeft}
}));
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(180), TO_TRIG(270)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(180),GCornerBottomLeft},
.full_quadrants = GCornerBottomLeft,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(270),GCornerTopLeft}
}));
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(270), TO_TRIG(360)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(270),GCornerTopLeft},
.full_quadrants = GCornerTopLeft,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(0),GCornerTopRight}
}));
// Tests for pairs of quadrants
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(0), TO_TRIG(180)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(0),GCornerTopRight},
.full_quadrants = GCornersRight,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(180),GCornerBottomLeft}
}));
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(180), TO_TRIG(360)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(180),GCornerBottomLeft},
.full_quadrants = GCornersLeft,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(0),GCornerTopRight}
}));
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(270), TO_TRIG(360 + 90)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(270),GCornerTopLeft},
.full_quadrants = GCornersTop,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(90),GCornerBottomRight}
}));
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(90), TO_TRIG(270)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(90),GCornerBottomRight},
.full_quadrants = GCornersBottom,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(270),GCornerTopLeft}
}));
// Tests for triples of quadrants
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(0), TO_TRIG(270)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(0),GCornerTopRight},
.full_quadrants = (GCornersBottom | GCornerTopRight),
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(270),GCornerTopLeft}
}));
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(90), TO_TRIG(360)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(90),GCornerBottomRight},
.full_quadrants = (GCornersBottom | GCornerTopLeft),
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(0),GCornerTopRight}
}));
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(180), TO_TRIG(360 + 90)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(180),GCornerBottomLeft},
.full_quadrants = (GCornersTop | GCornerBottomLeft),
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(90),GCornerBottomRight}
}));
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(270), TO_TRIG(360 + 180)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(270),GCornerTopLeft},
.full_quadrants = (GCornersTop | GCornerBottomRight),
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(180),GCornerBottomLeft}
}));
// ## Section with regular angles
// Within same quadrant
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(30), TO_TRIG(60)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(30),GCornerTopRight},
.full_quadrants = GCornerNone,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(60),GCornerTopRight}
}));
// Starting quadrant filling up and ending quadrant finishing
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(45), TO_TRIG(270 + 45)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(45),GCornerTopRight},
.full_quadrants = GCornersBottom,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(270 + 45),GCornerTopLeft}
}));
// Ending quadrant filling up and starting quadrant finishing
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(90 + 45), TO_TRIG(360 + 45)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(90 + 45),GCornerBottomRight},
.full_quadrants = GCornersLeft,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(45),GCornerTopRight}
}));
// ## Section with regular angles but over 360°
// Within same quadrant
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(360 + 30), TO_TRIG(360 + 60)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(30),GCornerTopRight},
.full_quadrants = GCornerNone,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(60),GCornerTopRight}
}));
// Starting quadrant filling up and ending quadrant finishing
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(360 + 45), TO_TRIG(360 + 270 + 45)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(45),GCornerTopRight},
.full_quadrants = GCornersBottom,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(270 + 45),GCornerTopLeft}
}));
// Ending quadrant filling up and starting quadrant finishing
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(360 + 90 + 45), TO_TRIG(360 + 360 + 45)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(90 + 45),GCornerBottomRight},
.full_quadrants = GCornersLeft,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(45),GCornerTopRight}
}));
// ## Section with negative angles
// Within same quadrant
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(30 - 360), TO_TRIG(60 - 360)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(30)+1,GCornerTopRight},
.full_quadrants = GCornerNone,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(60)+1,GCornerTopRight}
}));
// Starting quadrant filling up and ending quadrant finishing
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(45 - 360), TO_TRIG(270 + 45 - 360)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(45),GCornerTopRight},
.full_quadrants = GCornersBottom,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(270 + 45),GCornerTopLeft}
}));
// Ending quadrant filling up and starting quadrant finishing
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(90 + 45 - 360), TO_TRIG(360 + 45 - 360)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(90 + 45),GCornerBottomRight},
.full_quadrants = GCornersLeft,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(45),GCornerTopRight}
}));
// Pair of quadrants
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(0 - 360), TO_TRIG(180 - 360)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(360),GCornerTopRight},
.full_quadrants = GCornersRight,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(180),GCornerBottomLeft}
}));
// Negative to positive
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(-45), TO_TRIG(45)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(270 + 45),GCornerTopLeft},
.full_quadrants = GCornerNone,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(45),GCornerTopRight}
}));
// Flipping quadrant issue
cl_assert_equal_edc(prv_calc_draw_config_ellipsis(TO_TRIG(70), TO_TRIG(90)), ((EllipsisDrawConfig){
.start_quadrant = (EllipsisPartDrawConfig){TO_TRIG(70),GCornerTopRight},
.full_quadrants = GCornerNone,
.end_quadrant = (EllipsisPartDrawConfig){TO_TRIG(90),GCornerBottomRight}
}));
}
+353
View File
@@ -0,0 +1,353 @@
/*
* 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/framebuffer.h"
#include "applib/ui/window_private.h"
#include "applib/ui/layer.h"
#include "clar.h"
#include "util.h"
#include <stdio.h>
// Helper Functions
////////////////////////////////////
#include "test_graphics.h"
#include "8bit/test_framebuffer.h"
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
// Fakes
////////////////////////////////////
#include "fake_gbitmap_get_data_row.h"
static FrameBuffer *fb = NULL;
#define CLIP_RECT_DRAW_BOX GRect(0, 0, DISP_COLS, DISP_ROWS)
#define CLIP_RECT_CLIP_BOX GRect(0, 0, DISP_COLS, DISP_ROWS)
// Setup and Teardown
////////////////////////////////////
// Setup
void test_graphics_draw_core__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) { DISP_COLS, DISP_ROWS });
// Enable fake data row handling which will override the gbitmap_get_data_row_xxx() functions
// with their fake counterparts in fake_gbitmap_get_data_row.c
s_fake_data_row_handling = true;
}
// Teardown
void test_graphics_draw_core__cleanup(void) {
free(fb);
}
// Helpers
////////////////////////////////////
// HORIZONTAL LINE HELPERS
typedef void (*HLinePatternDrawFunction)(GContext *ctx, int16_t y, int16_t x1, int16_t x2,
GColor color);
static void prv_draw_hlines_in_rect(GContext *ctx, HLinePatternDrawFunction draw_func,
const GRect *rect, GColor color) {
for (int16_t y = 0; y < rect->size.h; y++) {
draw_func(ctx, rect->origin.y + y, rect->origin.x , grect_get_max_x(rect) - 1, color);
}
}
static void prv_draw_hline_test_pattern(GContext *ctx, HLinePatternDrawFunction draw_func) {
const GRect *bitmap_bounds = &ctx->dest_bitmap.bounds;
// Fill the screen with red
graphics_context_set_fill_color(ctx, GColorRed);
graphics_fill_rect(ctx, bitmap_bounds);
const GSize *bitmap_bounds_size = &bitmap_bounds->size;
// Draw the bottom rectangle blue with 66% opacity
// (will appear at the top because data rows are vertically flipped)
const GSize bottom_rect_size = GSize(bitmap_bounds_size->w * 4 / 5, bitmap_bounds_size->h / 2);
const GRect bottom_rect = (GRect) {
.origin = GPoint((bitmap_bounds_size->w - bottom_rect_size.w) / 2, bitmap_bounds_size->h / 2),
.size = bottom_rect_size
};
GColor bottom_rect_color = GColorBlue;
bottom_rect_color.a = 2;
prv_draw_hlines_in_rect(ctx, draw_func, &bottom_rect, bottom_rect_color);
// Draw the top rectangles green with 66% opacity
// (will appear at the bottom because data rows are vertically flipped)
const GSize top_rects_size = GSize(bitmap_bounds_size->w / 4, bitmap_bounds_size->h * 2 / 5);
const int16_t top_rects_x_offset = ((bitmap_bounds_size->w / 2) - top_rects_size.w) / 2;
GColor top_rects_color = GColorGreen;
top_rects_color.a = 2;
const GRect top_left_rect = (GRect) {
.origin = GPoint(top_rects_x_offset, 0),
.size = top_rects_size
};
prv_draw_hlines_in_rect(ctx, draw_func, &top_left_rect, top_rects_color);
const GRect top_right_rect = (GRect) {
.origin = GPoint((bitmap_bounds_size->w / 2) + top_rects_x_offset, 0),
.size = top_rects_size
};
prv_draw_hlines_in_rect(ctx, draw_func, &top_right_rect, top_rects_color);
}
// VERTICAL LINE HELPERS
typedef void (*VLinePatternDrawFunction)(GContext *ctx, int16_t x, int16_t y1, int16_t y2,
GColor color);
static void prv_draw_vlines_in_rect(GContext *ctx, HLinePatternDrawFunction draw_func,
const GRect *rect, GColor color) {
for (int16_t x = 0; x < rect->size.w; x++) {
draw_func(ctx, rect->origin.x + x, rect->origin.y, grect_get_max_y(rect) - 1, color);
}
}
static void prv_draw_vline_test_pattern(GContext *ctx, HLinePatternDrawFunction draw_func) {
const GRect *bitmap_bounds = &ctx->dest_bitmap.bounds;
// Fill the screen with red
graphics_context_set_fill_color(ctx, GColorRed);
graphics_fill_rect(ctx, bitmap_bounds);
const GSize *bitmap_bounds_size = &bitmap_bounds->size;
// Draw the left rectangle blue with 66% opacity
// (will appear at the top because data rows are vertically flipped)
const GRect left_rect = GRect(0, 0, bitmap_bounds_size->w * 2 / 5, bitmap_bounds_size->h);
GColor left_rect_color = GColorBlue;
left_rect_color.a = 2;
prv_draw_vlines_in_rect(ctx, draw_func, &left_rect, left_rect_color);
// Draw the right rectangles green with 66% opacity (top) and yellow with 66% opacity (bottom)
// (green will appear on top and yellow on bottom because data rows are vertically flipped)
const GSize right_rects_size = GSize(bitmap_bounds_size->w * 2 / 5, bitmap_bounds_size->h / 4);
const int16_t right_rects_x = bitmap_bounds->size.w * 3 / 5;
const int16_t right_rects_y_offset = ((bitmap_bounds_size->h / 2) - right_rects_size.h) / 2;
GColor top_right_rect_color = GColorGreen;
top_right_rect_color.a = 2;
const GRect top_left_rect = (GRect) {
.origin = GPoint(right_rects_x, right_rects_y_offset),
.size = right_rects_size
};
prv_draw_vlines_in_rect(ctx, draw_func, &top_left_rect, top_right_rect_color);
GColor bottom_right_rect_color = GColorYellow;
bottom_right_rect_color.a = 2;
const GRect top_right_rect = (GRect) {
.origin = GPoint(right_rects_x, (bitmap_bounds_size->h / 2) + right_rects_y_offset),
.size = right_rects_size
};
prv_draw_vlines_in_rect(ctx, draw_func, &top_right_rect, bottom_right_rect_color);
}
// Tests
////////////////////////////////////
// HORIZONTAL LINE TESTS
// These tests use a pattern of two skinny 66% opacity green rectangles drawn at the top of the
// screen and one wide 66% opacity blue rectangle drawn at the bottom of the screen when drawing
// horizontal lines. Due to the fake GBitmap data row handling, the result you should see is that
// the pattern is clipped to a diamond mask and flipped vertically (i.e. blue rect on top, green
// rects on bottom)
void prv_assign_horizontal_line_raw(GBitmap *framebuffer, int16_t y, Fixed_S16_3 x1,
Fixed_S16_3 x2, GColor color);
static void prv_hline_pattern_assign_horizontal_line_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 };
const Fixed_S16_3 x2_fixed = (Fixed_S16_3) { .integer = x2 };
prv_assign_horizontal_line_raw(&ctx->dest_bitmap, y, x1_fixed, x2_fixed, color);
}
void test_graphics_draw_core__assign_horizontal_line_raw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, false, 1);
prv_draw_hline_test_pattern(&ctx, prv_hline_pattern_assign_horizontal_line_raw);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_core_assign_horizontal_line_raw")));
};
void prv_blend_horizontal_line_raw(GBitmap *framebuffer, int16_t y, int16_t x1, int16_t x2,
GColor color);
static void prv_hline_pattern_blend_horizontal_line_raw(GContext *ctx, int16_t y, int16_t x1,
int16_t x2, GColor color) {
prv_blend_horizontal_line_raw(&ctx->dest_bitmap, y, x1, x2, color);
}
void test_graphics_draw_core__blend_horizontal_line_raw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, false, 1);
prv_draw_hline_test_pattern(&ctx, prv_hline_pattern_blend_horizontal_line_raw);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_core_blend_horizontal_line_raw")));
};
void prv_assign_horizontal_line_delta_raw(GBitmap *framebuffer, 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_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 = (x2 - x1) / 6;
x2_fixed.integer -= gradient_width;
prv_assign_horizontal_line_delta_raw(&ctx->dest_bitmap, y, x1_fixed, x2_fixed, gradient_width,
gradient_width, ctx->draw_state.clip_box.origin.x,
grect_get_max_x(&ctx->draw_state.clip_box) - 1, color);
}
void test_graphics_draw_core__assign_horizontal_line_delta_raw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, false, 1);
prv_draw_hline_test_pattern(&ctx, prv_hline_pattern_assign_horizontal_line_delta_raw);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_core_assign_horizontal_line_delta_raw")));
};
// VERTICAL LINE TESTS
// These tests use a pattern of two skinny 66% opacity rectangles drawn at the right of the
// screen (green on top and yellow on bottom) and one tall 66% opacity blue rectangle drawn at the
// left of the screen when drawing vertical lines. Due to the fake GBitmap data row handling, the
// result you should see is that the pattern is clipped to a diamond mask and flipped vertically
// (i.e. green rect on bottom and yellow rect on top) EXCEPT for the prv_assign_vertical_line_raw()
// unit test which disables the vertical flipping
void prv_assign_vertical_line_raw(GBitmap *framebuffer, int16_t x, Fixed_S16_3 y1, Fixed_S16_3 y2,
GColor color);
static void prv_vline_pattern_assign_vertical_line_raw(GContext *ctx, int16_t x, int16_t y1,
int16_t y2, GColor color) {
const Fixed_S16_3 y1_fixed = (Fixed_S16_3) { .integer = y1 };
const Fixed_S16_3 y2_fixed = (Fixed_S16_3) { .integer = y2 };
prv_assign_vertical_line_raw(&ctx->dest_bitmap, x, y1_fixed, y2_fixed, color);
}
void test_graphics_draw_core__assign_vertical_line_raw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, false, 1);
prv_draw_vline_test_pattern(&ctx, prv_vline_pattern_assign_vertical_line_raw);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_core_assign_vertical_line_raw")));
};
void prv_blend_vertical_line_raw(GBitmap *framebuffer, int16_t x, int16_t y1, int16_t y2,
GColor color);
static void prv_vline_pattern_blend_vertical_line_raw(GContext *ctx, int16_t x, int16_t y1,
int16_t y2, GColor color) {
prv_blend_vertical_line_raw(&ctx->dest_bitmap, x, y1, y2, color);
}
void test_graphics_draw_core__blend_vertical_line_raw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, false, 1);
prv_draw_vline_test_pattern(&ctx, prv_vline_pattern_blend_vertical_line_raw);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_core_blend_vertical_line_raw")));
};
// PIXEL DRAWING AND COLUMN REPLICATION TESTS
void prv_replicate_column_row_raw(GBitmap *framebuffer, int16_t src_x, int16_t dst_x1,
int16_t dst_x2);
void set_pixel_raw_8bit(GContext* ctx, GPoint point);
void test_graphics_draw_core__set_pixel_raw_8bit_replicate_column_row_raw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, false, 1);
// Draw a colored gradient of horizontal lines down the left half of the screen
const GRect bitmap_bounds = ctx.dest_bitmap.bounds;
const uint8_t max_rgb_value = 0b00111111;
for (int y = 0; y < bitmap_bounds.size.h; y++) {
GColor color = (GColor) {
.argb = y * max_rgb_value / (bitmap_bounds.size.h - 1)
};
color.a = 3; // 100% opacity
ctx.draw_state.stroke_color = color;
for (int x = 0; x < bitmap_bounds.size.w / 2; x++) {
const GPoint point = GPoint(x, y);
set_pixel_raw_8bit(&ctx, point);
}
}
// Replicate the last column of the colored gradient for the remaining columns of the bitmap
prv_replicate_column_row_raw(&ctx.dest_bitmap,
(bitmap_bounds.size.w / 2) - 1,
(bitmap_bounds.size.w / 2),
bitmap_bounds.size.w - 1);
const bool result =
gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_core_set_pixel_raw_8bit_replicate_column_row_raw"));
cl_check(result);
};
void test_graphics_draw_core__plot_pixel(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, false, 1);
const GRect *bitmap_bounds = &ctx.dest_bitmap.bounds;
// Fill the screen with red
graphics_context_set_fill_color(&ctx, GColorRed);
graphics_fill_rect(&ctx, bitmap_bounds);
// Draw 66% opacity blue pixels using graphics_private_plot_pixel() over the entire screen
// The expected result is that the entire screen will be purple due to the blending
for (int y = 0; y < bitmap_bounds->size.h; y++) {
for (int x = 0; x < bitmap_bounds->size.w; x++) {
graphics_private_plot_pixel(&ctx.dest_bitmap, &ctx.draw_state.clip_box, x, y, 1, GColorBlue);
}
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, TEST_NAMED_PBI_FILE("draw_core_plot_pixel")));
};
@@ -0,0 +1,138 @@
/*
* 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/framebuffer.h"
#include "applib/ui/window_private.h"
#include "applib/ui/layer.h"
#include "clar.h"
#include "util.h"
#include <stdio.h>
// Helper Functions
////////////////////////////////////
#include "test_graphics.h"
#include "8bit/test_framebuffer.h"
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
// State
////////////////////////////////////
typedef enum {
GDrawRawFunctionTypeAssignHorizontalLine,
GDrawRawFunctionTypeAssignVerticalLine,
GDrawRawFunctionTypeBlendHorizontalLine,
GDrawRawFunctionTypeBlendVerticalLine,
GDrawRawFunctionTypeAssignHorizontalLineDelta,
NumGDrawRawFunctionTypes
} GDrawRawFunctionType;
static unsigned int s_raw_drawing_function_counters[NumGDrawRawFunctionTypes];
// Setup and Teardown
////////////////////////////////////
static FrameBuffer *fb = NULL;
// Setup
void test_graphics_draw_implementation__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) { DISP_COLS, DISP_ROWS });
memset(s_raw_drawing_function_counters, 0, sizeof(s_raw_drawing_function_counters));
}
// Teardown
void test_graphics_draw_implementation__cleanup(void) {
free(fb);
}
// Fake raw drawing functions
////////////////////////////////////
static void prv_fake_raw_assign_horizontal_line(GContext *ctx, int16_t y, Fixed_S16_3 x1,
Fixed_S16_3 x2, GColor color) {
s_raw_drawing_function_counters[GDrawRawFunctionTypeAssignHorizontalLine]++;
}
static void prv_fake_raw_assign_vertical_line(GContext *ctx, int16_t x, Fixed_S16_3 y1,
Fixed_S16_3 y2, GColor color) {
s_raw_drawing_function_counters[GDrawRawFunctionTypeAssignVerticalLine]++;
}
static void prv_fake_raw_blend_horizontal_line(GContext *ctx, int16_t y, int16_t x1,
int16_t x2, GColor color) {
s_raw_drawing_function_counters[GDrawRawFunctionTypeBlendHorizontalLine]++;
}
static void prv_fake_raw_blend_vertical_line(GContext *ctx, int16_t x, int16_t y1,
int16_t y2, GColor color) {
s_raw_drawing_function_counters[GDrawRawFunctionTypeBlendVerticalLine]++;
}
static void prv_fake_raw_assign_horizontal_line_delta(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) {
s_raw_drawing_function_counters[GDrawRawFunctionTypeAssignHorizontalLineDelta]++;
}
// Tests
////////////////////////////////////
#define CLIP_RECT_DRAW_BOX GRect(0, 0, DISP_COLS, DISP_ROWS)
#define CLIP_RECT_CLIP_BOX GRect(0, 0, DISP_COLS, DISP_ROWS)
void test_graphics_draw_implementation__fill_circle_aa(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, true, 1);
// Set the draw implementation functions to the fake ones in this file that just increment some
// counters, then call graphics_fill_circle() and check the values of the counters
ctx.draw_state.draw_implementation = &(GDrawRawImplementation) {
.assign_horizontal_line = prv_fake_raw_assign_horizontal_line,
.assign_vertical_line = prv_fake_raw_assign_vertical_line,
.blend_horizontal_line = prv_fake_raw_blend_horizontal_line,
.blend_vertical_line = prv_fake_raw_blend_vertical_line,
.assign_horizontal_line_delta = prv_fake_raw_assign_horizontal_line_delta,
};
const GRect bounds = ctx.dest_bitmap.bounds;
graphics_context_set_fill_color(&ctx, GColorBlack);
graphics_fill_circle(&ctx, grect_center_point(&bounds), 5);
cl_assert_equal_i(11, s_raw_drawing_function_counters[GDrawRawFunctionTypeAssignHorizontalLine]);
cl_assert_equal_i(0, s_raw_drawing_function_counters[GDrawRawFunctionTypeAssignVerticalLine]);
cl_assert_equal_i(0, s_raw_drawing_function_counters[GDrawRawFunctionTypeBlendHorizontalLine]);
cl_assert_equal_i(0, s_raw_drawing_function_counters[GDrawRawFunctionTypeBlendVerticalLine]);
cl_assert_equal_i(0,
s_raw_drawing_function_counters[GDrawRawFunctionTypeAssignHorizontalLineDelta]);
};
+935
View File
@@ -0,0 +1,935 @@
/*
* 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 <stdio.h>
// Helper Functions
////////////////////////////////////
#include "test_graphics.h"
#if SCREEN_COLOR_DEPTH_BITS == 8
#include "8bit/test_framebuffer.h"
#else
#include "1bit/test_framebuffer.h"
#endif
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
static FrameBuffer *fb = NULL;
// Setup
void test_graphics_draw_line__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
}
// Teardown
void test_graphics_draw_line__cleanup(void) {
free(fb);
}
// Tests
////////////////////////////////////
void inside_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(5, 35), GPoint(45, 40));
}
void white_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorWhite);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(5, 35), GPoint(45, 40));
}
void clear_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorClear);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(5, 35), GPoint(45, 40));
}
void across_x_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(50, 40), GPoint(70, 35));
}
void across_nx_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(-25, 35), GPoint(15, 40));
}
void across_y_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(40, 50), GPoint(35, 70));
}
void across_ny_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(5, -30), GPoint(45, 30));
}
void test_graphics_draw_line__origin_layer(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(0, 0, 60, 60));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_line_inside_origin_layer")));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_x_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_line_across_x_origin_layer")));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_nx_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_line_across_nx_origin_layer")));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_y_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_line_across_y_origin_layer")));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_ny_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_line_across_ny_origin_layer")));
}
void test_graphics_draw_line__offset_layer(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(10, 10, 60, 60));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_line_inside_offset_layer")));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_x_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_line_across_x_offset_layer")));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_nx_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_line_across_nx_offset_layer")));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_y_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_line_across_y_offset_layer")));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_ny_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_line_across_ny_offset_layer")));
}
void test_graphics_draw_line__clear(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(0, 0, 60, 60));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_line_inside_origin_layer")));
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,
TEST_NAMED_PBI_FILE("draw_line_inside_origin_layer")));
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, TEST_NAMED_PBI_FILE("draw_line_clear")));
#else
cl_check(framebuffer_is_empty("clear_over_black", ctx.parent_framebuffer, GColorWhite));
#endif
}
#define MAX_NUM_ROWS 168
#define MAX_NUM_COLS 144
#define ORIGIN_RECT_NO_CLIP GRect(0, 0, MAX_NUM_COLS, MAX_NUM_ROWS)
#define ORIGIN_RECT_CLIP_EVEN GRect(10, 10, 60, 60)
#define ORIGIN_RECT_CLIP_ODD GRect(11, 11, 61, 61)
void test_graphics_draw_line___origin_horizontal_dotted(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Test odd and even rows draw appropriately
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(6, 12), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(6, 23), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(7, 13), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(7, 24), 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_origin_no_clip")));
// Even rows of different lengths
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 0), MAX_NUM_COLS);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 2), 148);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 4), 0);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 6), 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 8), 2);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 10), 3);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 12), 4);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 14), 20);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 16), 21);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 18), 22);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 20), 143);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 22), 145);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_origin_even_rows_no_clip")));
// Odd rows of different lengths
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 1), MAX_NUM_COLS);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 3), 148);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 5), 0);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 7), 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 9), 2);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 11), 3);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 13), 4);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 15), 20);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 17), 21);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 19), 22);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 21), 143);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 23), 145);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_origin_odd_rows_no_clip")));
// Test to make sure drawing on all rows creates checkerboard pattern
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
for (int16_t row = 0; row < MAX_NUM_ROWS; row++) {
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, row), MAX_NUM_COLS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_origin_checkerboard_no_clip")));
// Clipping on even boundaries - no offset
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_EVEN, ORIGIN_RECT_NO_CLIP, false, 1);
for (int16_t row = 0; row < MAX_NUM_ROWS; row++) {
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, row), MAX_NUM_COLS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_origin_even_clip")));
// Clipping on odd boundaries - no offset
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_ODD, ORIGIN_RECT_NO_CLIP, false, 1);
for (int16_t row = 0; row < MAX_NUM_ROWS; row++) {
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, row), MAX_NUM_COLS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_origin_odd_clip")));
}
#define OFFSET_RECT_EVEN GRect(14, 14, MAX_NUM_COLS, MAX_NUM_ROWS)
#define OFFSET_RECT_ODD GRect(15, 15, MAX_NUM_COLS, MAX_NUM_ROWS)
#define OFFSET_RECT_CLIP_EVEN GRect(10, 10, 60, 60)
#define OFFSET_RECT_CLIP_ODD GRect(11, 11, 61, 61)
void test_graphics_draw_line___even_offset_horizontal_dotted(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Test odd and even rows draw appropriately
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_EVEN, OFFSET_RECT_EVEN, false, 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(6, 12), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(6, 23), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(7, 13), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(7, 24), 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_even_offset_no_clip")));
// Even rows of different lengths
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_EVEN, OFFSET_RECT_EVEN, false, 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 0), MAX_NUM_COLS);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 2), MAX_NUM_COLS + 4);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 4), 0);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 6), 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 8), 2);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 10), 3);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 12), 4);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 14), 20);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 16), 21);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 18), 22);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 20), MAX_NUM_COLS - 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 22), MAX_NUM_COLS + 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_even_offset_even_rows_no_clip")));
// Odd rows of different lengths
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_EVEN, OFFSET_RECT_EVEN, false, 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 1), MAX_NUM_COLS);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 3), MAX_NUM_COLS + 4);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 5), 0);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 7), 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 9), 2);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 11), 3);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 13), 4);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 15), 20);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 17), 21);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 19), 22);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 21), MAX_NUM_COLS - 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 23), MAX_NUM_COLS + 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_even_offset_odd_rows_no_clip")));
// Test to make sure drawing on all rows creates checkerboard pattern
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_EVEN, OFFSET_RECT_EVEN, false, 1);
for (int16_t row = 0; row < MAX_NUM_ROWS; row++) {
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, row), MAX_NUM_COLS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_even_offset_checkerboard_no_clip")));
// Clipping on even boundaries - no offset
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_EVEN, OFFSET_RECT_EVEN, false, 1);
for (int16_t row = 0; row < MAX_NUM_ROWS; row++) {
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, row), MAX_NUM_COLS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_even_offset_even_clip")));
// Clipping on odd boundaries - no offset
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_ODD, OFFSET_RECT_EVEN, false, 1);
for (int16_t row = 0; row < MAX_NUM_ROWS; row++) {
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, row), MAX_NUM_COLS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_even_offset_odd_clip")));
}
void test_graphics_draw_line___odd_offset_horizontal_dotted(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Test odd and even rows draw appropriately
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_ODD, OFFSET_RECT_ODD, false, 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(6, 12), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(6, 23), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(7, 13), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(7, 24), 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_odd_offset_no_clip")));
// Even rows of different lengths
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_ODD, OFFSET_RECT_ODD, false, 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 0), MAX_NUM_COLS);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 2), MAX_NUM_COLS + 4);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 4), 0);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 6), 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 8), 2);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 10), 3);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 12), 4);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 14), 20);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 16), 21);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 18), 22);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 20), MAX_NUM_COLS - 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 22), MAX_NUM_COLS + 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_odd_offset_even_rows_no_clip")));
// Odd rows of different lengths
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_ODD, OFFSET_RECT_ODD, false, 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 1), MAX_NUM_COLS);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 3), MAX_NUM_COLS + 4);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 5), 0);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 7), 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 9), 2);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 11), 3);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 13), 4);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 15), 20);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 17), 21);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 19), 22);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 21), MAX_NUM_COLS - 1);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, 23), MAX_NUM_COLS + 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_odd_offset_odd_rows_no_clip")));
// Test to make sure drawing on all rows creates checkerboard pattern
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_ODD, OFFSET_RECT_ODD, false, 1);
for (int16_t row = 0; row < MAX_NUM_ROWS; row++) {
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, row), MAX_NUM_COLS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_odd_offset_checkerboard_no_clip")));
// Clipping on even boundaries - no offset
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_EVEN, OFFSET_RECT_ODD, false, 1);
for (int16_t row = 0; row < MAX_NUM_ROWS; row++) {
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, row), MAX_NUM_COLS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_odd_offset_even_clip")));
// Clipping on odd boundaries - no offset
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_ODD, OFFSET_RECT_ODD, false, 1);
for (int16_t row = 0; row < MAX_NUM_ROWS; row++) {
graphics_draw_horizontal_line_dotted(&ctx, GPoint(0, row), MAX_NUM_COLS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_horiz_dotted_line_odd_offset_odd_clip")));
}
void test_graphics_draw_line___origin_vertical_dotted(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Test odd and even cols draw appropriately
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(12, 6), 10);
graphics_draw_vertical_line_dotted(&ctx, GPoint(23, 6), 10);
graphics_draw_vertical_line_dotted(&ctx, GPoint(13, 7), 10);
graphics_draw_vertical_line_dotted(&ctx, GPoint(24, 7), 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_origin_no_clip")));
// Even cols of different lengths
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(0, 0), MAX_NUM_ROWS);
graphics_draw_vertical_line_dotted(&ctx, GPoint(2, 0), MAX_NUM_ROWS + 4);
graphics_draw_vertical_line_dotted(&ctx, GPoint(4, 0), 0);
graphics_draw_vertical_line_dotted(&ctx, GPoint(6, 0), 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(8, 0), 2);
graphics_draw_vertical_line_dotted(&ctx, GPoint(10, 0), 3);
graphics_draw_vertical_line_dotted(&ctx, GPoint(12, 0), 4);
graphics_draw_vertical_line_dotted(&ctx, GPoint(14, 0), 20);
graphics_draw_vertical_line_dotted(&ctx, GPoint(16, 0), 21);
graphics_draw_vertical_line_dotted(&ctx, GPoint(18, 0), 22);
graphics_draw_vertical_line_dotted(&ctx, GPoint(20, 0), MAX_NUM_ROWS - 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(22, 0), MAX_NUM_ROWS + 1 );
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_origin_even_cols_no_clip")));
// Odd cols of different lengths
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(1, 0), MAX_NUM_ROWS);
graphics_draw_vertical_line_dotted(&ctx, GPoint(3, 0), MAX_NUM_ROWS + 4);
graphics_draw_vertical_line_dotted(&ctx, GPoint(5, 0), 0);
graphics_draw_vertical_line_dotted(&ctx, GPoint(7, 0), 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(9, 0), 2);
graphics_draw_vertical_line_dotted(&ctx, GPoint(11, 0), 3);
graphics_draw_vertical_line_dotted(&ctx, GPoint(13, 0), 4);
graphics_draw_vertical_line_dotted(&ctx, GPoint(15, 0), 20);
graphics_draw_vertical_line_dotted(&ctx, GPoint(17, 0), 21);
graphics_draw_vertical_line_dotted(&ctx, GPoint(19, 0), 22);
graphics_draw_vertical_line_dotted(&ctx, GPoint(21, 0), MAX_NUM_ROWS - 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(23, 0), MAX_NUM_ROWS + 1 );
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_origin_odd_cols_no_clip")));
// Test to make sure drawing on all cols creates checkerboard pattern
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
for (int16_t col = 0; col < MAX_NUM_COLS; col++) {
graphics_draw_vertical_line_dotted(&ctx, GPoint(col, 0), MAX_NUM_ROWS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_origin_checkerboard_no_clip")));
// Clipping on even boundaries - no offset
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_EVEN, ORIGIN_RECT_NO_CLIP, false, 1);
for (int16_t col = 0; col < MAX_NUM_COLS; col++) {
graphics_draw_vertical_line_dotted(&ctx, GPoint(col, 0), MAX_NUM_ROWS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_origin_even_clip")));
// Clipping on odd boundaries - no offset
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_ODD, ORIGIN_RECT_NO_CLIP, false, 1);
for (int16_t col = 0; col < MAX_NUM_COLS; col++) {
graphics_draw_vertical_line_dotted(&ctx, GPoint(col, 0), MAX_NUM_ROWS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_origin_odd_clip")));
}
void test_graphics_draw_line___even_offset_vertical_dotted(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Test odd and even cols draw appropriately
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_EVEN, OFFSET_RECT_EVEN, false, 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(12, 6), 10);
graphics_draw_vertical_line_dotted(&ctx, GPoint(23, 6), 10);
graphics_draw_vertical_line_dotted(&ctx, GPoint(13, 7), 10);
graphics_draw_vertical_line_dotted(&ctx, GPoint(24, 7), 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_even_offset_no_clip")));
// Even cols of different lengths
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_EVEN, OFFSET_RECT_EVEN, false, 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(0, 0), MAX_NUM_ROWS);
graphics_draw_vertical_line_dotted(&ctx, GPoint(2, 0), MAX_NUM_ROWS + 4);
graphics_draw_vertical_line_dotted(&ctx, GPoint(4, 0), 0);
graphics_draw_vertical_line_dotted(&ctx, GPoint(6, 0), 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(8, 0), 2);
graphics_draw_vertical_line_dotted(&ctx, GPoint(10, 0), 3);
graphics_draw_vertical_line_dotted(&ctx, GPoint(12, 0), 4);
graphics_draw_vertical_line_dotted(&ctx, GPoint(14, 0), 20);
graphics_draw_vertical_line_dotted(&ctx, GPoint(16, 0), 21);
graphics_draw_vertical_line_dotted(&ctx, GPoint(18, 0), 22);
graphics_draw_vertical_line_dotted(&ctx, GPoint(20, 0), MAX_NUM_ROWS - 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(22, 0), MAX_NUM_ROWS + 1 );
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_even_offset_even_cols_no_clip")));
// Odd cols of different lengths
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_EVEN, OFFSET_RECT_EVEN, false, 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(1, 0), MAX_NUM_ROWS);
graphics_draw_vertical_line_dotted(&ctx, GPoint(3, 0), MAX_NUM_ROWS + 4);
graphics_draw_vertical_line_dotted(&ctx, GPoint(5, 0), 0);
graphics_draw_vertical_line_dotted(&ctx, GPoint(7, 0), 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(9, 0), 2);
graphics_draw_vertical_line_dotted(&ctx, GPoint(11, 0), 3);
graphics_draw_vertical_line_dotted(&ctx, GPoint(13, 0), 4);
graphics_draw_vertical_line_dotted(&ctx, GPoint(15, 0), 20);
graphics_draw_vertical_line_dotted(&ctx, GPoint(17, 0), 21);
graphics_draw_vertical_line_dotted(&ctx, GPoint(19, 0), 22);
graphics_draw_vertical_line_dotted(&ctx, GPoint(21, 0), MAX_NUM_ROWS - 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(23, 0), MAX_NUM_ROWS + 1 );
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_even_offset_odd_cols_no_clip")));
// Test to make sure drawing on all cols creates checkerboard pattern
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_EVEN, OFFSET_RECT_EVEN, false, 1);
for (int16_t col = 0; col < MAX_NUM_COLS; col++) {
graphics_draw_vertical_line_dotted(&ctx, GPoint(col, 0), MAX_NUM_ROWS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_even_offset_checkerboard_no_clip")));
// Clipping on even boundaries - no offset
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_EVEN, OFFSET_RECT_EVEN, false, 1);
for (int16_t col = 0; col < MAX_NUM_COLS; col++) {
graphics_draw_vertical_line_dotted(&ctx, GPoint(col, 0), MAX_NUM_ROWS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_even_offset_even_clip")));
// Clipping on odd boundaries - no offset
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_ODD, OFFSET_RECT_EVEN, false, 1);
for (int16_t col = 0; col < MAX_NUM_COLS; col++) {
graphics_draw_vertical_line_dotted(&ctx, GPoint(col, 0), MAX_NUM_ROWS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_even_offset_odd_clip")));
}
void test_graphics_draw_line___odd_offset_vertical_dotted(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Test odd and even cols draw appropriately
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_ODD, OFFSET_RECT_ODD, false, 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(12, 6), 10);
graphics_draw_vertical_line_dotted(&ctx, GPoint(23, 6), 10);
graphics_draw_vertical_line_dotted(&ctx, GPoint(13, 7), 10);
graphics_draw_vertical_line_dotted(&ctx, GPoint(24, 7), 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_odd_offset_no_clip")));
// Even cols of different lengths
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_ODD, OFFSET_RECT_ODD, false, 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(0, 0), MAX_NUM_ROWS);
graphics_draw_vertical_line_dotted(&ctx, GPoint(2, 0), MAX_NUM_ROWS + 4);
graphics_draw_vertical_line_dotted(&ctx, GPoint(4, 0), 0);
graphics_draw_vertical_line_dotted(&ctx, GPoint(6, 0), 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(8, 0), 2);
graphics_draw_vertical_line_dotted(&ctx, GPoint(10, 0), 3);
graphics_draw_vertical_line_dotted(&ctx, GPoint(12, 0), 4);
graphics_draw_vertical_line_dotted(&ctx, GPoint(14, 0), 20);
graphics_draw_vertical_line_dotted(&ctx, GPoint(16, 0), 21);
graphics_draw_vertical_line_dotted(&ctx, GPoint(18, 0), 22);
graphics_draw_vertical_line_dotted(&ctx, GPoint(20, 0), MAX_NUM_ROWS - 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(22, 0), MAX_NUM_ROWS + 1 );
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_odd_offset_even_cols_no_clip")));
// Odd cols of different lengths
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_ODD, OFFSET_RECT_ODD, false, 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(1, 0), MAX_NUM_ROWS);
graphics_draw_vertical_line_dotted(&ctx, GPoint(3, 0), MAX_NUM_ROWS + 4);
graphics_draw_vertical_line_dotted(&ctx, GPoint(5, 0), 0);
graphics_draw_vertical_line_dotted(&ctx, GPoint(7, 0), 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(9, 0), 2);
graphics_draw_vertical_line_dotted(&ctx, GPoint(11, 0), 3);
graphics_draw_vertical_line_dotted(&ctx, GPoint(13, 0), 4);
graphics_draw_vertical_line_dotted(&ctx, GPoint(15, 0), 20);
graphics_draw_vertical_line_dotted(&ctx, GPoint(17, 0), 21);
graphics_draw_vertical_line_dotted(&ctx, GPoint(19, 0), 22);
graphics_draw_vertical_line_dotted(&ctx, GPoint(21, 0), MAX_NUM_ROWS - 1);
graphics_draw_vertical_line_dotted(&ctx, GPoint(23, 0), MAX_NUM_ROWS + 1 );
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_odd_offset_odd_cols_no_clip")));
// Test to make sure drawing on all cols creates checkerboard pattern
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_ODD, OFFSET_RECT_ODD, false, 1);
for (int16_t col = 0; col < MAX_NUM_COLS; col++) {
graphics_draw_vertical_line_dotted(&ctx, GPoint(col, 0), MAX_NUM_ROWS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_odd_offset_checkerboard_no_clip")));
// Clipping on even boundaries - no offset
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_EVEN, OFFSET_RECT_ODD, false, 1);
for (int16_t col = 0; col < MAX_NUM_COLS; col++) {
graphics_draw_vertical_line_dotted(&ctx, GPoint(col, 0), MAX_NUM_ROWS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_odd_offset_even_clip")));
// Clipping on odd boundaries - no offset
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_ODD, OFFSET_RECT_ODD, false, 1);
for (int16_t col = 0; col < MAX_NUM_COLS; col++) {
graphics_draw_vertical_line_dotted(&ctx, GPoint(col, 0), MAX_NUM_ROWS);
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
TEST_NAMED_PBI_FILE("draw_vert_dotted_line_odd_offset_odd_clip")));
}
void test_graphics_draw_line__dotted_cross(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Test horizontal and vertical lines cross appropriately
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
// cross - even vert, odd horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(10, 10), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(5, 15), 10);
// cross - odd vert, even horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(41, 11), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(36, 16), 10);
// T facing up
graphics_draw_vertical_line_dotted(&ctx, GPoint(70, 15), 5);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(65, 15), 10);
// T facing down
graphics_draw_vertical_line_dotted(&ctx, GPoint(101, 11), 5);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(96, 16), 10);
// cross - even vert, even horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(10, 32), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(4, 36), 10);
// cross - odd vert, odd horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(41, 33), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(37, 37), 10);
// T facing up - lined up
graphics_draw_vertical_line_dotted(&ctx, GPoint(70, 36), 5);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(66, 36), 10);
// T facing down - lined up
graphics_draw_vertical_line_dotted(&ctx, GPoint(101, 33), 5);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(97, 37), 10);
// T facing left - lined up
graphics_draw_vertical_line_dotted(&ctx, GPoint(10, 70), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(5, 76), 5);
// T facing right - even vert, odd horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(50, 70), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(45, 75), 5);
// T facing right - odd vert, even horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(71, 71), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(66, 76), 5);
// T facing left - even vert, odd horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(100, 70), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(101, 75), 5);
// T facing left - odd vert, even horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(131, 71), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(132, 76), 5);
// T facing right - lined up
graphics_draw_vertical_line_dotted(&ctx, GPoint(10, 90), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(10, 96), 5);
// T facing right - even vert, even horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(50, 90), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(46, 95), 5);
// T facing right - odd vert, odd horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(71, 91), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(67, 96), 5);
// T facing left - even vert, even horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(100, 90), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(100, 95), 5);
// T facing left - odd vert, odd horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(131, 91), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(131, 96), 5);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, TEST_NAMED_PBI_FILE("draw_dotted_line_cross")));
}
void test_graphics_draw_line_8bit__dotted_cross_color(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Test horizontal and vertical lines cross appropriately
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_context_set_stroke_color(&ctx, GColorRed);
// cross - even vert, odd horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(10, 10), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(5, 15), 10);
// cross - odd vert, even horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(41, 11), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(36, 16), 10);
// T facing up
graphics_draw_vertical_line_dotted(&ctx, GPoint(70, 15), 5);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(65, 15), 10);
// T facing down
graphics_draw_vertical_line_dotted(&ctx, GPoint(101, 11), 5);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(96, 16), 10);
// cross - even vert, even horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(10, 32), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(4, 36), 10);
// cross - odd vert, odd horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(41, 33), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(37, 37), 10);
// T facing up - lined up
graphics_draw_vertical_line_dotted(&ctx, GPoint(70, 36), 5);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(66, 36), 10);
// T facing down - lined up
graphics_draw_vertical_line_dotted(&ctx, GPoint(101, 33), 5);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(97, 37), 10);
// T facing left - lined up
graphics_draw_vertical_line_dotted(&ctx, GPoint(10, 70), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(5, 76), 5);
// T facing right - even vert, odd horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(50, 70), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(45, 75), 5);
// T facing right - odd vert, even horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(71, 71), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(66, 76), 5);
// T facing left - even vert, odd horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(100, 70), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(101, 75), 5);
// T facing left - odd vert, even horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(131, 71), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(132, 76), 5);
// T facing right - lined up
graphics_draw_vertical_line_dotted(&ctx, GPoint(10, 90), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(10, 96), 5);
// T facing right - even vert, even horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(50, 90), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(46, 95), 5);
// T facing right - odd vert, odd horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(71, 91), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(67, 96), 5);
// T facing left - even vert, even horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(100, 90), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(100, 95), 5);
// T facing left - odd vert, odd horiz
graphics_draw_vertical_line_dotted(&ctx, GPoint(131, 91), 10);
graphics_draw_horizontal_line_dotted(&ctx, GPoint(131, 96), 5);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, TEST_NAMED_PBI_FILE("draw_dotted_line_cross_color")));
}
static void draw_lines_same_point(GContext *ctx) {
graphics_context_set_antialiased(ctx, false);
graphics_context_set_stroke_width(ctx, 1);
graphics_draw_line(ctx, GPoint(10,10), GPoint(10,10));
graphics_context_set_antialiased(ctx, true);
graphics_context_set_stroke_width(ctx, 1);
graphics_draw_line(ctx, GPoint(20,20), GPoint(20,20));
graphics_context_set_antialiased(ctx, false);
graphics_context_set_stroke_width(ctx, 2);
graphics_draw_line(ctx, GPoint(30,30), GPoint(30,30));
graphics_context_set_antialiased(ctx, true);
graphics_context_set_stroke_width(ctx, 2);
graphics_draw_line(ctx, GPoint(40,40), GPoint(40,40));
graphics_context_set_antialiased(ctx, false);
graphics_context_set_stroke_width(ctx, 3);
graphics_draw_line(ctx, GPoint(50,50), GPoint(50,50));
graphics_context_set_antialiased(ctx, true);
graphics_context_set_stroke_width(ctx, 3);
graphics_draw_line(ctx, GPoint(60,60), GPoint(60,60));
graphics_context_set_antialiased(ctx, false);
graphics_context_set_stroke_width(ctx, 4);
graphics_draw_line(ctx, GPoint(70,70), GPoint(70,70));
graphics_context_set_antialiased(ctx, true);
graphics_context_set_stroke_width(ctx, 4);
graphics_draw_line(ctx, GPoint(80,80), GPoint(80,80));
graphics_context_set_antialiased(ctx, false);
graphics_context_set_stroke_width(ctx, 5);
graphics_draw_line(ctx, GPoint(90,90), GPoint(90,90));
graphics_context_set_antialiased(ctx, true);
graphics_context_set_stroke_width(ctx, 5);
graphics_draw_line(ctx, GPoint(100,100), GPoint(100,100));
}
void test_graphics_draw_line__same_point(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);
draw_lines_same_point(&ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, TEST_NAMED_PBI_FILE("draw_line_same_point")));
#if SCREEN_COLOR_DEPTH_BITS == 8
graphics_context_set_stroke_color(&ctx, GColorRed);
draw_lines_same_point(&ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, TEST_NAMED_PBI_FILE("draw_line_same_point_color")));
#endif
}
#define CLIP_RECT_DRAW_BOX GRect(10, 10, 40, 40)
#define CLIP_RECT_CLIP_BOX GRect(10, 10, 20, 20)
#define CLIP_OFFSET 100
static void prv_draw_lines(GContext *ctx, uint8_t sw, uint16_t xoffset, uint16_t yoffset) {
// Adjust drawing box and clipping box
ctx->draw_state.drawing_box = CLIP_RECT_DRAW_BOX;
ctx->draw_state.drawing_box.origin.x += xoffset;
ctx->draw_state.drawing_box.origin.y += yoffset;
ctx->draw_state.clip_box = CLIP_RECT_CLIP_BOX;
ctx->draw_state.clip_box.origin.x += xoffset;
ctx->draw_state.clip_box.origin.y += yoffset;
graphics_context_set_stroke_width(ctx, sw);
graphics_draw_line(ctx, GPoint(-2, 10), GPoint(2, 10)); // left
graphics_draw_line(ctx, GPoint(-2, 5), GPoint(5, -2)); // top left corner
graphics_draw_line(ctx, GPoint(10, -2), GPoint(10, 2)); // top
graphics_draw_line(ctx, GPoint(15, -2), GPoint(22, 5)); // top right corner
graphics_draw_line(ctx, GPoint(18, 10), GPoint(22, 10)); // right
graphics_draw_line(ctx, GPoint(22, 15), GPoint(15, 22)); // bottom right corner
graphics_draw_line(ctx, GPoint(10, 18), GPoint(10, 22)); // bottom
graphics_draw_line(ctx, GPoint(5, 22), GPoint(-2, 15)); // bottom left corner
}
void test_graphics_draw_line__clipping_rect(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, false, 1);
graphics_context_set_stroke_color(&ctx, GColorBlack);
// Draw lines around boundaries of clipping box - AA false, SW 1
prv_draw_lines(&ctx, 1, 0, 0);
// Draw lines around boundaries of clipping box - AA false, SW 2
prv_draw_lines(&ctx, 2, CLIP_OFFSET, 0);
// Draw lines around boundaries of clipping box - AA false, SW 3
prv_draw_lines(&ctx, 3, 0, CLIP_OFFSET);
// Draw lines around boundaries of clipping box - AA false, SW 4
prv_draw_lines(&ctx, 4, CLIP_OFFSET, CLIP_OFFSET);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, TEST_NAMED_PBI_FILE("draw_line_clip_rect")));
}
void test_graphics_draw_line__clipping_rect_aa(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, true, 1);
graphics_context_set_stroke_color(&ctx, GColorBlack);
// Draw lines around boundaries of clipping box - AA true, SW 1
prv_draw_lines(&ctx, 1, 0, 0);
// Draw lines around boundaries of clipping box - AA true, SW 2
prv_draw_lines(&ctx, 2, CLIP_OFFSET, 0);
// Draw lines around boundaries of clipping box - AA true, SW 3
prv_draw_lines(&ctx, 3, 0, CLIP_OFFSET);
// Draw lines around boundaries of clipping box - AA true, SW 4
prv_draw_lines(&ctx, 4, CLIP_OFFSET, CLIP_OFFSET);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, TEST_NAMED_PBI_FILE("draw_line_clip_rect_aa")));
}
@@ -0,0 +1,324 @@
/*
* 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 <stdio.h>
// 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"));
}
@@ -0,0 +1,670 @@
/*
* 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 <stdio.h>
// 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_rect_${BIT_DEPTH_NAME}__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
}
// Teardown
void test_graphics_draw_rect_${BIT_DEPTH_NAME}__cleanup(void) {
free(fb);
}
////////////////////////////////////
static void inside_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_draw_rect(ctx, &GRect(4, 2, 16, 8));
}
static void white_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorWhite);
graphics_draw_rect(ctx, &GRect(4, 2, 16, 8));
}
static void clear_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorClear);
graphics_draw_rect(ctx, &GRect(4, 2, 16, 8));
}
static void across_x_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_draw_rect(ctx, &GRect(10, 2, 18, 4));
}
static void across_nx_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_draw_rect(ctx, &GRect(-10, 2, 18, 4));
}
static void across_y_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_draw_rect(ctx, &GRect(4, 5, 18, 10));
}
static void across_ny_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_draw_rect(ctx, &GRect(4, -5, 18, 10));
}
void test_graphics_draw_rect_${BIT_DEPTH_NAME}__origin_layer(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(0, 0, 20, 10));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_inside_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_x_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_across_x_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_nx_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_across_nx_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_y_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_across_y_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_ny_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_across_ny_origin_layer.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_draw_rect_${BIT_DEPTH_NAME}__offset_layer(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(10, 15, 20, 10));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_inside_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_x_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_across_x_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_nx_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_across_nx_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_y_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_across_y_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_ny_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_across_ny_offset_layer.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_draw_rect_${BIT_DEPTH_NAME}__color(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(0, 0, 20, 10));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_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_rect_inside_origin_layer.${BIT_DEPTH_NAME}.pbi"));
layer_set_update_proc(&layer, &clear_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(framebuffer_is_empty("clear_over_black", ctx.parent_framebuffer, GColorWhite));
}
#define ORIGIN_RECT_NO_CLIP GRect(0, 0, 144, 168)
#define ORIGIN_RECT_CLIP_XY GRect(0, 0, 20, 20)
#define ORIGIN_RECT_CLIP_NXNY GRect(0, 0, 144, 168)
#define ORIGIN_DRAW_RECT_NO_CLIP GRect(6, 6, 30, 40)
#define ORIGIN_DRAW_RECT_CLIP_XY GRect(6, 6, 30, 40)
#define ORIGIN_DRAW_RECT_CLIP_NXNY GRect(-16, -16, 30, 40)
void test_graphics_draw_rect_${BIT_DEPTH_NAME}__origin_aa_sw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// AA = true, SW = 1
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw1_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 1);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw1_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// AA = true, SW = 2
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 2);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw2_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 2);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw2_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 2);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw2_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// AA = true, SW = 3
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 3);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw3_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 3);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw3_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 3);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw3_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// AA = true, SW = 4
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 4);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw4_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 4);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw4_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 4);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw4_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
// AA = true, SW = 5
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 5);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw5_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 5);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw5_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 5);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw5_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// AA = true, SW = 11
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 11);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 11);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw11_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 11);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_aa_sw11_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
}
void test_graphics_draw_rect_${BIT_DEPTH_NAME}__origin_sw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// SW = 1
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, false, 1);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw1_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, false, 1);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw1_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// SW = 2
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 2);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw2_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, false, 2);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw2_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, false, 2);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw2_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// SW = 3
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 3);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw3_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, false, 3);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw3_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, false, 3);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw3_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// SW = 4
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 4);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw4_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, false, 4);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw4_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, false, 4);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw4_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
// SW = 5
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 5);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw5_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, false, 5);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw5_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, false, 5);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw5_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// SW = 11
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 11);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, false, 11);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw11_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, false, 11);
graphics_draw_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_origin_sw11_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
}
#define OFFSET_RECT_NO_CLIP GRect(20, 10, 144, 168)
#define OFFSET_RECT_CLIP_XY GRect(20, 10, 20, 20)
#define OFFSET_RECT_CLIP_NXNY GRect(20, 10, 144, 168)
#define OFFSET_DRAW_RECT_NO_CLIP GRect(6, 6, 30, 40)
#define OFFSET_DRAW_RECT_CLIP_XY GRect(6, 6, 30, 40)
#define OFFSET_DRAW_RECT_CLIP_NXNY GRect(-16, -16, 30, 40)
void test_graphics_draw_rect_${BIT_DEPTH_NAME}__offset_aa_sw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// AA = true, SW = 1
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 1);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 1);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw1_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 1);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw1_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// AA = true, SW = 2
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 2);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw2_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 2);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw2_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 2);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw2_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// AA = true, SW = 3
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 3);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw3_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 3);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw3_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 3);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw3_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// AA = true, SW = 4
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 4);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw4_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 4);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw4_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 4);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw4_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
// AA = true, SW = 5
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 5);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw5_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 5);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw5_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 5);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw5_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// AA = true, SW = 11
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 11);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 11);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw11_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 11);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_aa_sw11_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
}
void test_graphics_draw_rect_${BIT_DEPTH_NAME}__offset_sw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// SW = 1
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, false, 1);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, false, 1);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw1_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, false, 1);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw1_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// SW = 2
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, false, 2);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw2_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, false, 2);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw2_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, false, 2);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw2_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// SW = 3
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, false, 3);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw3_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, false, 3);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw3_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, false, 3);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw3_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// SW = 4
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, false, 4);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw4_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, false, 4);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw4_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, false, 4);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw4_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
// SW = 5
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, false, 5);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw5_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, false, 5);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw5_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, false, 5);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw5_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// SW = 11
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, false, 11);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, false, 11);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw11_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, false, 11);
graphics_draw_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_offset_sw11_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
}
#define BOX_SIZE 8
#define CLIP_RECT_DRAW_BOX GRect(10, 10, 140, 30)
#define CLIP_RECT_CLIP_BOX GRect(10, 10, 120, 2*BOX_SIZE + 4)
#define CLIP_RECT_RECT_BOX GRect(0, 0, BOX_SIZE, BOX_SIZE)
#define CLIP_OFFSET 40
// yoffset if used to allow for multiple test cases to be drawn and tested on the same test image
// nudge is used to move the drawn rectangle just outside each corner the clipping
static void prv_draw_rects(GContext *ctx, uint8_t sw, uint16_t yoffset, int16_t nudge) {
// Adjust drawing box and clipping box
ctx->draw_state.drawing_box = CLIP_RECT_DRAW_BOX;
ctx->draw_state.drawing_box.origin.y += yoffset;
ctx->draw_state.clip_box = CLIP_RECT_CLIP_BOX;
ctx->draw_state.clip_box.origin.y += yoffset;
graphics_context_set_stroke_width(ctx, sw);
// Top left corner of clipping box
GRect box = CLIP_RECT_RECT_BOX;
box.origin.x -= nudge;
box.origin.y -= nudge;
graphics_draw_rect(ctx, &box);
// Top right corner of clipping box
box.origin.x = CLIP_RECT_CLIP_BOX.size.w - BOX_SIZE;
box.origin.x += nudge;
graphics_draw_rect(ctx, &box);
// Bottom right corner of clipping box
box.origin.y = CLIP_RECT_CLIP_BOX.size.h - BOX_SIZE;
box.origin.y += nudge;
graphics_draw_rect(ctx, &box);
// Bottom left corner of clipping box
box.origin.x = 0;
box.origin.x -= nudge;
graphics_draw_rect(ctx, &box);
}
void test_graphics_draw_rect_${BIT_DEPTH_NAME}__clipping_rect(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, false, 1);
graphics_context_set_stroke_color(&ctx, GColorBlack);
// Draw rectangles around boundaries of clipping box - AA false
prv_draw_rects(&ctx, 1, 0, 0);
prv_draw_rects(&ctx, 2, CLIP_OFFSET, 0);
prv_draw_rects(&ctx, 3, 2 * CLIP_OFFSET, 0);
prv_draw_rects(&ctx, 4, 3 * CLIP_OFFSET, 0);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_clip_rect.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, false, 1);
prv_draw_rects(&ctx, 1, 0, 1);
prv_draw_rects(&ctx, 2, CLIP_OFFSET, 1);
prv_draw_rects(&ctx, 3, 2 * CLIP_OFFSET, 1);
prv_draw_rects(&ctx, 4, 3 * CLIP_OFFSET, 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_clip_rect_nudge.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_draw_rect_${BIT_DEPTH_NAME}__clipping_rect_aa(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, true, 1);
graphics_context_set_stroke_color(&ctx, GColorBlack);
// Draw rectangles around boundaries of clipping box - AA true
prv_draw_rects(&ctx, 1, 0, 0);
prv_draw_rects(&ctx, 2, CLIP_OFFSET, 0);
prv_draw_rects(&ctx, 3, 2 * CLIP_OFFSET, 0);
prv_draw_rects(&ctx, 4, 3 * CLIP_OFFSET, 0);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_clip_rect_aa.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, true, 1);
prv_draw_rects(&ctx, 1, 0, 1);
prv_draw_rects(&ctx, 2, CLIP_OFFSET, 1);
prv_draw_rects(&ctx, 3, 2 * CLIP_OFFSET, 1);
prv_draw_rects(&ctx, 4, 3 * CLIP_OFFSET, 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rect_clip_rect_aa_nudge.${BIT_DEPTH_NAME}.pbi"));
}
#define ORIGIN_RECT GRect(0, 0, 144, 4)
#define LEFT_EDGE_RECT GRect(5, 4, 59, 4)
#define RIGHT_EDGE_RECT GRect(0, 8, 40, 4)
#define BOTH_EDGE_RECT GRect(5, 12, 20, 4)
#define CLIPPED_RECT GRect(-10,16,20,4)
#define OVERLAP_RECT GRect(0,0,20,20)
#define CORNER_RADIUS_RECT GRect(5,24,20,20)
static void prv_draw_dither_rects(GContext* ctx, GColor8 fill_color) {
test_graphics_context_init(ctx, fb);
graphics_context_set_fill_color(ctx, fill_color);
graphics_fill_rect(ctx, &ORIGIN_RECT);
graphics_fill_rect(ctx, &LEFT_EDGE_RECT);
graphics_fill_rect(ctx, &RIGHT_EDGE_RECT);
graphics_fill_rect(ctx, &BOTH_EDGE_RECT);
graphics_fill_rect(ctx, &CLIPPED_RECT);
graphics_fill_round_rect(ctx, &CORNER_RADIUS_RECT, 4, GCornersAll);
ctx->draw_state.drawing_box = GRect(100,2, 40, 40);
graphics_fill_rect(ctx, &OVERLAP_RECT);
cl_check(gbitmap_pbi_eq(&ctx->dest_bitmap, "draw_multiple_rect_dithered.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_draw_rect_${BIT_DEPTH_NAME}__dithering_gray(void) {
#if SCREEN_COLOR_DEPTH_BITS == 1
GContext ctx;
prv_draw_dither_rects(&ctx, GColorLightGray);
prv_draw_dither_rects(&ctx, GColorDarkGray);
#endif
}
@@ -0,0 +1,355 @@
/*
* 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/framebuffer.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/gtypes.h"
#include "util/trig.h"
#include "applib/ui/layer.h"
#include "applib/ui/window_private.h"
#include "clar.h"
#include "util.h"
#include <stdio.h>
// Helper Functions
////////////////////////////////////
#include "test_graphics.h"
#if SCREEN_COLOR_DEPTH_BITS == 1
#include "1bit/test_framebuffer.h"
#else
#include "8bit/test_framebuffer.h"
#endif
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
///////////////////////////////////////////////////////////
// Fakes
#include "fake_gbitmap_get_data_row.h"
// Setup
////////////////////////////////////
static GBitmap *test_image_bw;
static GBitmap *test_image_color;
static FrameBuffer *fb = NULL;
#if SCREEN_COLOR_DEPTH_BITS == 1
extern bool get_bitmap_bit(GBitmap *bmp, int x, int y);
#elif SCREEN_COLOR_DEPTH_BITS == 8
extern GColor get_bitmap_color(GBitmap *bmp, int x, int y);
#endif
void test_graphics_draw_rotated_bitmap__initialize(void) {
s_fake_data_row_handling = false;
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
test_image_bw = get_gbitmap_from_pbi("test_rotated_bitmap_no_litter.Xbit.pbi");
cl_assert(test_image_bw != NULL);
test_image_color = get_gbitmap_from_pbi("test_rotated_bitmap_redstar.Xbit.pbi");
cl_assert(test_image_color != NULL);
}
void test_graphics_draw_rotated_bitmap__cleanup(void) {
free(fb);
if (test_image_bw) {
if (test_image_bw->addr) {
free(test_image_bw->addr);
}
free(test_image_bw);
}
if (test_image_color) {
if (test_image_color->addr) {
free(test_image_color->addr);
}
free(test_image_color);
}
}
static void setup_test_rotate_bitmap(GContext *ctx, FrameBuffer *fb,
GRect clip_box, GRect drawing_box,
GCompOp compositing_mode) {
test_graphics_context_reset(ctx, fb);
GDrawState draw_state = {
.clip_box = clip_box,
.drawing_box = drawing_box,
.compositing_mode = compositing_mode,
};
setup_test_context(ctx,
(CTX_FLAG_DS_CLIP_BOX | CTX_FLAG_DS_DRAWING_BOX |
CTX_FLAG_DS_COMPOSITING_MODE),
&draw_state, NULL);
}
#define ORIGIN_RECT_NO_CLIP GRect(0, 0, DISP_COLS, DISP_ROWS)
// Tests
////////////////////////////////////
void test_graphics_draw_rotated_bitmap__get_color(void) {
#if SCREEN_COLOR_DEPTH_BITS == 1
cl_check(get_bitmap_bit(test_image_bw, 8, 16) == 1);
cl_check(get_bitmap_bit(test_image_bw, 8, 24) == 0);
cl_check(get_bitmap_bit(test_image_color, 30, 2) == 0);
cl_check(get_bitmap_bit(test_image_color, 30, 10) == 0);
cl_check(get_bitmap_bit(test_image_color, 30, 30) == 1);
#elif SCREEN_COLOR_DEPTH_BITS == 8
cl_check(gcolor_equal(get_bitmap_color(test_image_bw, 8, 16), GColorWhite));
cl_check(gcolor_equal(get_bitmap_color(test_image_bw, 8, 24), GColorBlack));
cl_check(gcolor_equal(get_bitmap_color(test_image_color, 30, 2), GColorClear));
cl_check(gcolor_equal(get_bitmap_color(test_image_color, 30, 10), GColorRed));
cl_check(gcolor_equal(get_bitmap_color(test_image_color, 30, 30), GColorScreaminGreen));
#endif
}
void test_graphics_draw_rotated_bitmap__origin_bw_assign(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// No Clip, Angle 0
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_bw,
GPointZero, DEG_TO_TRIGANGLE(0), GPointZero);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rotated_bitmap_origin_bw_assign_0.Xbit.pbi"));
// Top-left corner rotation point, Angle 45
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_bw,
GPointZero, DEG_TO_TRIGANGLE(45), GPointZero);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"draw_rotated_bitmap_origin_bw_assign_corner_45.Xbit.pbi"));
// Top-left center rotation point, Angle 45
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_bw,
GPoint(27, 40), DEG_TO_TRIGANGLE(45), GPointZero);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"draw_rotated_bitmap_origin_bw_assign_center_45.Xbit.pbi"));
}
void test_graphics_draw_rotated_bitmap__origin_bw_set(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// No Clip, Angle 0
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpSet);
graphics_draw_rotated_bitmap(&ctx, test_image_bw,
GPointZero, DEG_TO_TRIGANGLE(0), GPointZero);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rotated_bitmap_origin_bw_set_0.Xbit.pbi"));
// Top-left corner rotation point, Angle 45
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpSet);
graphics_draw_rotated_bitmap(&ctx, test_image_bw,
GPointZero, DEG_TO_TRIGANGLE(45), GPointZero);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"draw_rotated_bitmap_origin_bw_set_corner_45.Xbit.pbi"));
// Top-left center rotation point, Angle 45
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpSet);
graphics_draw_rotated_bitmap(&ctx, test_image_bw,
GPoint(27, 40), DEG_TO_TRIGANGLE(45), GPointZero);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"draw_rotated_bitmap_origin_bw_set_center_45.Xbit.pbi"));
}
void test_graphics_draw_rotated_bitmap__offset_bw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// No Clip, Angle 0, Offset
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_bw,
GPointZero, DEG_TO_TRIGANGLE(0), GPoint(20, 20));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rotated_bitmap_offset_bw_0.Xbit.pbi"));
// Top-left corner rotation point, Angle 45
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_bw,
GPointZero, DEG_TO_TRIGANGLE(45), GPoint(20, 20));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rotated_bitmap_offset_bw_corner_45.Xbit.pbi"));
// Top-left center rotation point, Angle 45
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_bw,
GPoint(27, 40), DEG_TO_TRIGANGLE(45), GPoint(20, 20));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rotated_bitmap_offset_bw_center_45.Xbit.pbi"));
}
void test_graphics_draw_rotated_bitmap__origin_color_assign(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// No Clip, Angle 0
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_color,
GPointZero, DEG_TO_TRIGANGLE(0), GPointZero);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rotated_bitmap_origin_color_assign_0.Xbit.pbi"));
// Top-left corner rotation point, Angle 45
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_color,
GPointZero, DEG_TO_TRIGANGLE(45), GPointZero);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"draw_rotated_bitmap_origin_color_assign_corner_45.Xbit.pbi"));
// Top-left center rotation point, Angle 45
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_color,
GPoint(30, 30), DEG_TO_TRIGANGLE(45), GPointZero);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"draw_rotated_bitmap_origin_color_assign_center_45.Xbit.pbi"));
// Test transparency
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_context_set_fill_color(&ctx, GColorBlue);
graphics_fill_rect(&ctx, &GRect(0, 0, 20, 10));
graphics_draw_rotated_bitmap(&ctx, test_image_color,
GPoint(30, 30), DEG_TO_TRIGANGLE(45), GPointZero);
cl_check(gbitmap_pbi_eq(
&ctx.dest_bitmap,
"draw_rotated_bitmap_origin_color_assign_center_45_transparent.Xbit.pbi"));
}
void test_graphics_draw_rotated_bitmap__origin_color_set(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// No Clip, Angle 0
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpSet);
graphics_draw_rotated_bitmap(&ctx, test_image_color,
GPointZero, DEG_TO_TRIGANGLE(0), GPointZero);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rotated_bitmap_origin_color_set_0.Xbit.pbi"));
// Top-left corner rotation point, Angle 45
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpSet);
graphics_draw_rotated_bitmap(&ctx, test_image_color,
GPointZero, DEG_TO_TRIGANGLE(45), GPointZero);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"draw_rotated_bitmap_origin_color_set_corner_45.Xbit.pbi"));
// Top-left center rotation point, Angle 45
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpSet);
graphics_draw_rotated_bitmap(&ctx, test_image_color,
GPoint(30, 30), DEG_TO_TRIGANGLE(45), GPointZero);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"draw_rotated_bitmap_origin_color_set_center_45.Xbit.pbi"));
// Test transparency
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpSet);
graphics_context_set_fill_color(&ctx, GColorBlue);
graphics_fill_rect(&ctx, &GRect(0, 0, 20, 10));
graphics_draw_rotated_bitmap(&ctx, test_image_color,
GPoint(30, 30), DEG_TO_TRIGANGLE(45), GPointZero);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"draw_rotated_bitmap_origin_color_set_center_45_transparent.Xbit.pbi"));
}
void test_graphics_draw_rotated_bitmap__offset_color(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// No Clip, Angle 0
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_color,
GPointZero, DEG_TO_TRIGANGLE(0), GPoint(20, 20));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rotated_bitmap_offset_color_0.Xbit.pbi"));
// Top-left corner rotation point, Angle 45
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_color,
GPointZero, DEG_TO_TRIGANGLE(45), GPoint(20, 20));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rotated_bitmap_offset_color_corner_45.Xbit.pbi"));
// Top-left center rotation point, Angle 45
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_color,
GPoint(30, 30), DEG_TO_TRIGANGLE(45), GPoint(20, 20));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rotated_bitmap_offset_color_center_45.Xbit.pbi"));
}
void test_graphics_draw_rotated_bitmap__offset_edge(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// bottom edge center rotation point, Angle 2
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_color,
GPoint(30, 59), DEG_TO_TRIGANGLE(2), GPoint(72, 84));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rotated_bitmap_offset_bottomedge_2.Xbit.pbi"));
// top edge center rotation point, Angle 2
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_color,
GPoint(30, 1), DEG_TO_TRIGANGLE(2), GPoint(72, 84));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rotated_bitmap_offset_topedge_2.Xbit.pbi"));
// left edge center rotation point, Angle 2
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_color,
GPoint(1, 30), DEG_TO_TRIGANGLE(2), GPoint(72, 84));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rotated_bitmap_offset_leftedge_2.Xbit.pbi"));
// right edge center rotation point, Angle 2
setup_test_rotate_bitmap(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(&ctx, test_image_color,
GPoint(59, 30), DEG_TO_TRIGANGLE(2), GPoint(72, 84));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_rotated_bitmap_offset_rightedge_2.Xbit.pbi"));
}
void test_graphics_draw_rotated_bitmap__data_row_handling(void) {
// Enable fake data row handling which will override the gbitmap_get_data_row_xxx() functions
// with their fake counterparts in fake_gbitmap_get_data_row.c
s_fake_data_row_handling = true;
s_fake_data_row_handling_disable_vertical_flip = true;
GContext *ctx = malloc(sizeof(GContext));
test_graphics_context_init(ctx, fb);
framebuffer_clear(fb);
GBitmap *test_image = get_gbitmap_from_pbi("stamp.Xbit.pbi");
cl_assert(test_image != NULL);
// PBL-24705 grect_center_point is off by 1
GPoint center = GPoint(DISP_COLS / 2 - 1, DISP_ROWS / 2 - 1);
// No Clip, Angle 0
setup_test_rotate_bitmap(ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(ctx, test_image, GPoint(
test_image->bounds.size.w / 2 - 1,
test_image->bounds.size.h / 2 - 1),
DEG_TO_TRIGANGLE(0), center);
cl_check(gbitmap_pbi_eq(&ctx->dest_bitmap, "draw_rotated_bitmap_stamp_0deg.Xbit.pbi"));
// Top-left corner rotation point, Angle 45
setup_test_rotate_bitmap(ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(ctx, test_image,
GPoint(71, 71), DEG_TO_TRIGANGLE(45), center);
cl_check(gbitmap_pbi_eq(&ctx->dest_bitmap, "draw_rotated_bitmap_stamp_45deg.Xbit.pbi"));
// Top-left corner rotation point, Angle 180
setup_test_rotate_bitmap(ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, GCompOpAssign);
graphics_draw_rotated_bitmap(ctx, test_image,
GPoint(71, 71), DEG_TO_TRIGANGLE(180), center);
cl_check(gbitmap_pbi_eq(&ctx->dest_bitmap, "draw_rotated_bitmap_stamp_180deg.Xbit.pbi"));
}
@@ -0,0 +1,729 @@
/*
* 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 "util/math.h"
#include "util.h"
#include "clar.h"
#include <stdio.h>
// 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_round_rect_${BIT_DEPTH_NAME}__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
}
// Teardown
void test_graphics_draw_round_rect_${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_context_set_antialiased(ctx, false);
graphics_draw_round_rect(ctx, &GRect(2, 2, 28, 20), 4);
}
void white_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorWhite);
graphics_context_set_antialiased(ctx, false);
graphics_draw_round_rect(ctx, &GRect(2, 2, 28, 20), 4);
}
void clear_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorClear);
graphics_context_set_antialiased(ctx, false);
graphics_draw_round_rect(ctx, &GRect(2, 2, 28, 20), 4);
}
void across_x_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_draw_round_rect(ctx, &GRect(16, 2, 28, 20), 4);
}
void across_nx_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_draw_round_rect(ctx, &GRect(-14, 2, 28, 20), 4);
}
void across_y_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_draw_round_rect(ctx, &GRect(2, 12, 28, 20), 4);
}
void across_ny_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_draw_round_rect(ctx, &GRect(2, -10, 28, 20), 4);
}
void test_graphics_draw_round_rect_${BIT_DEPTH_NAME}__origin_layer(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(0, 0, 32, 24));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_inside_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_x_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_across_x_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_x_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_across_x_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_y_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_across_y_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_y_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_across_y_origin_layer.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_draw_round_rect_${BIT_DEPTH_NAME}__offset_layer(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(10, 15, 32, 24));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_inside_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_x_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_across_x_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_nx_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_across_nx_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_y_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_across_y_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_ny_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_across_ny_offset_layer.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_draw_round_rect_${BIT_DEPTH_NAME}__clear(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(0, 0, 32, 24));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_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_round_rect_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_round_rect_clear.8bit.pbi"));
#else
cl_check(framebuffer_is_empty("clear_over_black", ctx.parent_framebuffer, GColorWhite));
#endif
}
#define RADIUS_DEFAULT 4
#define RECT_WIDTH 30
#define RECT_HEIGHT 40
#define ORIGIN_RECT_NO_CLIP GRect(0, 0, 144, 168)
#define ORIGIN_RECT_CLIP_XY GRect(0, 0, 20, 20)
#define ORIGIN_RECT_CLIP_NXNY GRect(0, 0, 144, 168)
#define ORIGIN_DRAW_RECT_NO_CLIP GRect(6, 6, RECT_WIDTH, RECT_HEIGHT)
#define ORIGIN_DRAW_RECT_CLIP_XY GRect(6, 6, RECT_WIDTH, RECT_HEIGHT)
#define ORIGIN_DRAW_RECT_CLIP_NXNY GRect(-16, -16, RECT_WIDTH, RECT_HEIGHT)
void test_graphics_draw_round_rect_${BIT_DEPTH_NAME}__origin_aa_sw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// AA = true, SW = 1
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw1_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw1_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// AA = true, SW = 2
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 2);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw2_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 2);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw2_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 2);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw2_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
// AA = true, SW = 3
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 3);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw3_no_clip.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 3);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw3_clip_xy.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 3);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw3_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// AA = true, SW = 4
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 4);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw4_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 4);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw4_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 4);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw4_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
// AA = true, SW = 5
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 5);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw5_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 5);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw5_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 5);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw5_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// AA = true, SW = 11
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw11_clip_xy.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_aa_sw11_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_draw_round_rect_${BIT_DEPTH_NAME}__origin_sw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// SW = 1
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, false, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw1_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, false, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw1_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// SW = 2
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 2);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw2_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, false, 2);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw2_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, false, 2);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw2_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// SW = 3
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 3);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw3_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, false, 3);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw3_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, false, 3);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw3_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// SW = 4
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 4);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw4_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, false, 4);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw4_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, false, 4);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw4_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
// SW = 5
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 5);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw5_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, false, 5);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw5_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, false, 5);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw5_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// SW = 11
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, false, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw11_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, false, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r4_sw11_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_draw_round_rect_${BIT_DEPTH_NAME}__origin_radius_aa_sw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// AA = true, SW = 1
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 0);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r0_aa_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r1_aa_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 2);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r2_aa_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 3);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r3_aa_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_rmax_aa_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
// Should not draw anything
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_rmax1_aa_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
// Should not draw anything
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) + 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_rmax2_aa_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
// AA = true, SW = 11
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 0);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r0_aa_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 1);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r1_aa_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 2);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r2_aa_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 3);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r3_aa_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_rmax_aa_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
#endif
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_rmax1_aa_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) + 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_rmax2_aa_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
#endif
}
void test_graphics_draw_round_rect_${BIT_DEPTH_NAME}__origin_radius_sw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// AA = false, SW = 1
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 0);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r0_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r1_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 2);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r2_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 3);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r3_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_rmax_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
// Should not draw anything
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_rmax1_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
// Should not draw anything
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) + 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_rmax2_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
// AA = false, SW = 11
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 0);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r0_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r1_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 2);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r2_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 3);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_r3_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_rmax_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
// Should not draw anything
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_rmax1_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
// Should not draw anything
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 11);
graphics_draw_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) + 1);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_origin_rmax2_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
}
#define OFFSET_RECT_NO_CLIP GRect(20, 10, 144, 168)
#define OFFSET_RECT_CLIP_XY GRect(20, 10, 20, 20)
#define OFFSET_RECT_CLIP_NXNY GRect(20, 10, 144, 168)
#define OFFSET_DRAW_RECT_NO_CLIP GRect(6, 6, RECT_WIDTH, RECT_HEIGHT)
#define OFFSET_DRAW_RECT_CLIP_XY GRect(6, 6, RECT_WIDTH, RECT_HEIGHT)
#define OFFSET_DRAW_RECT_CLIP_NXNY GRect(-16, -16, RECT_WIDTH, RECT_HEIGHT)
void test_graphics_draw_round_rect_${BIT_DEPTH_NAME}__offset_aa_sw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// AA = true, SW = 1
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 1);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 1);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw1_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 1);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw1_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// AA = true, SW = 2
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 2);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw2_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 2);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw2_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 2);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw2_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
// AA = true, SW = 3
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 3);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw3_no_clip.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 3);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw3_clip_xy.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 3);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw3_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// AA = true, SW = 4
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 4);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw4_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 4);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw4_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 4);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw4_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
// AA = true, SW = 5
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 5);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw5_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 5);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw5_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 5);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw5_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// AA = true, SW = 11
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 11);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 11);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw11_clip_xy.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 11);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_aa_sw11_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_draw_round_rect_${BIT_DEPTH_NAME}__offset_sw(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// SW = 1
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, false, 1);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, false, 1);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw1_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, false, 1);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw1_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// SW = 2
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, false, 2);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw2_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, false, 2);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw2_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, false, 2);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw2_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
// SW = 3
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, false, 3);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw3_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, false, 3);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw3_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, false, 3);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw3_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
// SW = 4
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, false, 4);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw4_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, false, 4);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw4_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, false, 4);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw4_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
// SW = 5
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, false, 5);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw5_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, false, 5);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw5_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, false, 5);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw5_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// SW = 11
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, false, 11);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_NO_CLIP, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw11_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, false, 11);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_XY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw11_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, false, 11);
graphics_draw_round_rect(&ctx, &OFFSET_DRAW_RECT_CLIP_NXNY, RADIUS_DEFAULT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_round_rect_offset_r4_sw11_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
}
@@ -0,0 +1,201 @@
/*
* 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 "applib/graphics/bitblt_private.h"
#include "clar.h"
#include "util.h"
#include <stdio.h>
// 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_stroke_${BIT_DEPTH_NAME}__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
}
// Teardown
void test_graphics_draw_stroke_${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_context_set_stroke_width(ctx, 10);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(5, 35), GPoint(45, 40));
}
void white_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorWhite);
graphics_context_set_stroke_width(ctx, 10);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(5, 35), GPoint(45, 40));
}
void clear_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorClear);
graphics_context_set_stroke_width(ctx, 10);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(5, 35), GPoint(45, 40));
}
void across_x_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_stroke_width(ctx, 10);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(50, 40), GPoint(70, 35));
}
void across_nx_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_stroke_width(ctx, 10);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(-25, 35), GPoint(15, 40));
}
void across_y_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_stroke_width(ctx, 10);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(40, 50), GPoint(35, 70));
}
void across_ny_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_stroke_width(ctx, 10);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(5, -30), GPoint(45, 30));
}
void across_screen_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_stroke_width(ctx, 10);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(25, 25), GPoint(119, 143));
}
void same_start_stop_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_context_set_stroke_width(ctx, 10);
graphics_context_set_antialiased(ctx, false);
graphics_draw_line(ctx, GPoint(20, 20), GPoint(20, 20));
}
#define ORIGIN_RECT_NO_CLIP GRect(0, 0, 144, 168)
#define ORIGIN_RECT_CLIP_XY GRect(0, 0, 30, 40)
#define ORIGIN_RECT_CLIP_NXNY GRect(0, 0, 30, 40)
#define START_ON_ORIGIN_RECT GPoint(5, 5)
#define END_ON_ORIGIN_RECT GPoint(25, 25)
#define START_ON_ORIGIN_RECT_XY GPoint(15, 15)
#define END_ON_ORIGIN_RECT_XY GPoint(35, 35)
#define START_ON_ORIGIN_RECT_NXNY GPoint(-5, -5)
#define END_ON_ORIGIN_RECT_NXNY GPoint(15, 15)
#define STROKE_WIDTH 10
void test_graphics_draw_stroke_${BIT_DEPTH_NAME}__origin_layer(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_WIDTH);
graphics_draw_line(&ctx, START_ON_ORIGIN_RECT, END_ON_ORIGIN_RECT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_inside_origin_layer.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, STROKE_WIDTH);
graphics_draw_line(&ctx, START_ON_ORIGIN_RECT_XY, END_ON_ORIGIN_RECT_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_across_x_origin_layer.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, STROKE_WIDTH);
graphics_draw_line(&ctx, START_ON_ORIGIN_RECT_NXNY, END_ON_ORIGIN_RECT_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_across_nxny_origin_layer.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_WIDTH);
graphics_draw_line(&ctx, END_ON_ORIGIN_RECT, END_ON_ORIGIN_RECT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_same_point_origin_layer.${BIT_DEPTH_NAME}.pbi"));
}
#define OFFSET_RECT_NO_CLIP GRect(10, 10, 144, 168)
#define OFFSET_RECT_CLIP_XY GRect(10, 10, 30, 40)
#define OFFSET_RECT_CLIP_NXNY GRect(10, 10, 30, 40)
void test_graphics_draw_stroke_${BIT_DEPTH_NAME}__offset_layer(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if SCREEN_COLOR_DEPTH_BITS == 8
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, STROKE_WIDTH);
graphics_draw_line(&ctx, START_ON_ORIGIN_RECT, END_ON_ORIGIN_RECT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_inside_offset_layer.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, STROKE_WIDTH);
graphics_draw_line(&ctx, START_ON_ORIGIN_RECT_XY, END_ON_ORIGIN_RECT_XY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_across_x_offset_layer.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, STROKE_WIDTH);
graphics_draw_line(&ctx, START_ON_ORIGIN_RECT_NXNY, END_ON_ORIGIN_RECT_NXNY);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_across_nxny_offset_layer.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, STROKE_WIDTH);
graphics_draw_line(&ctx, END_ON_ORIGIN_RECT, END_ON_ORIGIN_RECT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_same_point_offset_layer.${BIT_DEPTH_NAME}.pbi"));
}
#define COLOR_START_POINT GPoint(5, 35)
#define COLOR_END_POINT GPoint(45, 40)
void test_graphics_draw_stroke_${BIT_DEPTH_NAME}__color(void) {
// TODO: Fix blending and reenable this - PBL-16509
/*
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 10);
graphics_context_set_stroke_color(&ctx, GColorBlack);
graphics_draw_line(&ctx, COLOR_START_POINT, COLOR_END_POINT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_inside_origin_layer.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 10);
graphics_context_set_stroke_color(&ctx, GColorClear);
graphics_draw_line(&ctx, COLOR_START_POINT, COLOR_END_POINT);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_inside_origin_layer.${BIT_DEPTH_NAME}.pbi"));
*/
}
@@ -0,0 +1,271 @@
/*
* 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 "applib/graphics/bitblt_private.h"
#include "clar.h"
#include "util.h"
#include <stdio.h>
// 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_stroke_precise_${BIT_DEPTH_NAME}__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
}
// Teardown
void test_graphics_draw_stroke_precise_${BIT_DEPTH_NAME}__cleanup(void) {
free(fb);
}
// Tests
////////////////////////////////////
#define ORIGIN_RECT_NO_CLIP GRect(0, 0, 144, 168)
#define ORIGIN_RECT_CLIP_XY GRect(0, 0, 30, 40)
#define ORIGIN_RECT_CLIP_NXNY GRect(0, 0, 30, 40)
#define START_ON_ORIGIN_RECT GPointPrecise(5, 5)
#define END_ON_ORIGIN_RECT GPointPrecise(25, 25)
#define START_ON_ORIGIN_RECT_XY GPointPrecise(15, 15)
#define END_ON_ORIGIN_RECT_XY GPointPrecise(35, 35)
#define START_ON_ORIGIN_RECT_NXNY GPointPrecise(-5, -5)
#define END_ON_ORIGIN_RECT_NXNY GPointPrecise(15, 15)
void test_graphics_draw_stroke_precise_${BIT_DEPTH_NAME}__origin_layer_aa(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if PBL_COLOR
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 10);
graphics_line_draw_precise_stroked_aa(&ctx, START_ON_ORIGIN_RECT, END_ON_ORIGIN_RECT, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_inside_origin_layer_aa.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 10);
graphics_line_draw_precise_stroked_aa(&ctx, START_ON_ORIGIN_RECT_XY, END_ON_ORIGIN_RECT_XY, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_across_x_origin_layer_aa.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 10);
graphics_line_draw_precise_stroked_aa(&ctx, START_ON_ORIGIN_RECT_NXNY, END_ON_ORIGIN_RECT_NXNY, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_across_nxny_origin_layer_aa.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix offset calculation and reenable this: - PBL-16509
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 10);
graphics_line_draw_precise_stroked_aa(&ctx, END_ON_ORIGIN_RECT, END_ON_ORIGIN_RECT, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_same_point_origin_layer_aa.${BIT_DEPTH_NAME}.pbi"));
#endif
}
void test_graphics_draw_stroke_precise_${BIT_DEPTH_NAME}__origin_layer_non_aa(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 10);
graphics_line_draw_precise_stroked_non_aa(&ctx, START_ON_ORIGIN_RECT, END_ON_ORIGIN_RECT, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_inside_origin_layer_non_aa.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, false, 10);
graphics_line_draw_precise_stroked_non_aa(&ctx, START_ON_ORIGIN_RECT_XY, END_ON_ORIGIN_RECT_XY, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_across_x_origin_layer_non_aa.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, false, 10);
graphics_line_draw_precise_stroked_non_aa(&ctx, START_ON_ORIGIN_RECT_NXNY, END_ON_ORIGIN_RECT_NXNY, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_across_nxny_origin_layer_non_aa.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 10);
graphics_line_draw_precise_stroked_non_aa(&ctx, END_ON_ORIGIN_RECT, END_ON_ORIGIN_RECT, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_same_point_origin_layer_non_aa.${BIT_DEPTH_NAME}.pbi"));
}
#define OFFSET_RECT_NO_CLIP GRect(10, 10, 144, 168)
#define OFFSET_RECT_CLIP_XY GRect(10, 10, 30, 40)
#define OFFSET_RECT_CLIP_NXNY GRect(10, 10, 30, 40)
void test_graphics_draw_stroke_precise_${BIT_DEPTH_NAME}__offset_layer_aa(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// TODO: Fix offset calculation and reenable this: - PBL-16509
#if PBL_COLOR
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 10);
graphics_line_draw_precise_stroked_aa(&ctx, START_ON_ORIGIN_RECT, END_ON_ORIGIN_RECT, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_inside_offset_layer_aa.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 10);
graphics_line_draw_precise_stroked_aa(&ctx, START_ON_ORIGIN_RECT_XY, END_ON_ORIGIN_RECT_XY, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_across_x_offset_layer_aa.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 10);
graphics_line_draw_precise_stroked_aa(&ctx, START_ON_ORIGIN_RECT_NXNY, END_ON_ORIGIN_RECT_NXNY, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_across_nxny_offset_layer_aa.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix offset calculation and reenable this: - PBL-16509
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 10);
graphics_line_draw_precise_stroked_aa(&ctx, END_ON_ORIGIN_RECT, END_ON_ORIGIN_RECT, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_same_point_offset_layer_aa.${BIT_DEPTH_NAME}.pbi"));
#endif
}
void test_graphics_draw_stroke_precise_${BIT_DEPTH_NAME}__offset_layer_non_aa(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, false, 10);
graphics_line_draw_precise_stroked_non_aa(&ctx, START_ON_ORIGIN_RECT, END_ON_ORIGIN_RECT, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_inside_offset_layer_non_aa.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, false, 10);
graphics_line_draw_precise_stroked_non_aa(&ctx, START_ON_ORIGIN_RECT_XY, END_ON_ORIGIN_RECT_XY, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_across_x_offset_layer_non_aa.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, false, 10);
graphics_line_draw_precise_stroked_non_aa(&ctx, START_ON_ORIGIN_RECT_NXNY, END_ON_ORIGIN_RECT_NXNY, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_across_nxny_offset_layer_non_aa.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, false, 10);
graphics_line_draw_precise_stroked_non_aa(&ctx, END_ON_ORIGIN_RECT, END_ON_ORIGIN_RECT, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_same_point_offset_layer_non_aa.${BIT_DEPTH_NAME}.pbi"));
}
#define COLOR_START_POINT GPointPrecise(5, 35)
#define COLOR_END_POINT GPointPrecise(45, 40)
void test_graphics_draw_stroke_${BIT_DEPTH_NAME}__color(void) {
// TODO: Fix blending and reenable this - PBL-16509
/*
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 10);
graphics_context_set_stroke_color(&ctx, GColorBlack);
graphics_line_draw_precise_stroked_aa(&ctx, COLOR_START_POINT, COLOR_END_POINT, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_inside_origin_layer.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 10);
graphics_context_set_stroke_color(&ctx, GColorClear);
graphics_line_draw_precise_stroked_non_aa(&ctx, COLOR_START_POINT, COLOR_END_POINT, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_inside_origin_layer.${BIT_DEPTH_NAME}.pbi"));
*/
}
/*
* Following points come from bug reports, causing "plasma effect" where multiple
* lines in close vicinity of one spot (~1 pixel) caused artifact instead of
* elegant AA circle.
*/
// First pair, distance less than 1px
#define CLOSE_POINTS_LESS_THAN_1PX_START (GPointPrecise){{.integer = 71, .fraction = 4}, {.integer = 73, .fraction = 5}}
#define CLOSE_POINTS_LESS_THAN_1PX_END (GPointPrecise){{.integer = 71, .fraction = 5}, {.integer = 73, .fraction = 6}}
//Second pair, distance around 1px
#define CLOSE_POINTS_AROUND_1PX_START (GPointPrecise){{.integer = 71, .fraction = 4}, {.integer = 74, .fraction = 1}}
#define CLOSE_POINTS_AROUND_1PX_END (GPointPrecise){{.integer = 71, .fraction = 1}, {.integer = 73, .fraction = 3}}
void test_graphics_draw_stroke_precise_${BIT_DEPTH_NAME}__close_points_aa(void) {
#if PBL_COLOR
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 10);
graphics_line_draw_precise_stroked_aa(&ctx, CLOSE_POINTS_LESS_THAN_1PX_START, CLOSE_POINTS_LESS_THAN_1PX_END, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_close_points_less_than_1px_aa.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 10);
graphics_line_draw_precise_stroked_aa(&ctx, CLOSE_POINTS_AROUND_1PX_START, CLOSE_POINTS_AROUND_1PX_END, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_close_points_around_1px_aa.${BIT_DEPTH_NAME}.pbi"));
#endif
}
void test_graphics_draw_stroke_precise_${BIT_DEPTH_NAME}__close_points_non_aa(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 10);
graphics_line_draw_precise_stroked_non_aa(&ctx, CLOSE_POINTS_LESS_THAN_1PX_START, CLOSE_POINTS_LESS_THAN_1PX_END, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_close_points_less_than_1px_non_aa.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 10);
graphics_line_draw_precise_stroked_non_aa(&ctx, CLOSE_POINTS_AROUND_1PX_START, CLOSE_POINTS_AROUND_1PX_END, 10);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_close_points_around_1px_non_aa.${BIT_DEPTH_NAME}.pbi"));
}
/*
* Following functions will test issue of same starting/ending point for stroke width, where point lies
* between pixels due to precise points. This should be fixed by PBL-20783.
*/
void test_graphics_draw_stroke_precise_${BIT_DEPTH_NAME}__same_point_aa(void) {
#if PBL_COLOR
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 10);
int radius = 5;
int x_offset = 10 * FIXED_S16_3_ONE.raw_value;
for (int i = 0; i < 8; i++) {
radius += 1;
x_offset += radius * FIXED_S16_3_ONE.raw_value + 4 * FIXED_S16_3_ONE.raw_value;
GPointPrecise p = GPointPrecise(x_offset, 15 * FIXED_S16_3_ONE.raw_value);
for (int j=0; j<9; j++) {
graphics_line_draw_precise_stroked_aa(&ctx, p, p, radius);
p.x.raw_value += 1;
p.y.integer += 16;
}
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_same_points_pattern_aa.${BIT_DEPTH_NAME}.pbi"));
#endif
}
void test_graphics_draw_stroke_precise_${BIT_DEPTH_NAME}__same_point_non_aa(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 10);
int radius = 5;
int x_offset = 10 * FIXED_S16_3_ONE.raw_value;
for (int i = 0; i < 8; i++) {
radius += 1;
x_offset += radius * FIXED_S16_3_ONE.raw_value + 4 * FIXED_S16_3_ONE.raw_value;
GPointPrecise p = GPointPrecise(x_offset, 15 * FIXED_S16_3_ONE.raw_value);
for (int j=0; j<9; j++) {
graphics_line_draw_precise_stroked_non_aa(&ctx, p, p, radius);
p.x.raw_value += 1;
p.y.integer += 16;
}
}
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_stroke_precise_same_points_pattern_non_aa.${BIT_DEPTH_NAME}.pbi"));
}
@@ -0,0 +1,567 @@
/*
* 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 "clar.h"
#include "fixtures/load_test_resources.h"
#include "applib/fonts/fonts_private.h"
#include "applib/graphics/framebuffer.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/text.h"
#include "applib/graphics/text_resources.h"
#include "applib/ui/layer.h"
#include "applib/ui/window_private.h"
#include "resource/resource_ids.auto.h"
#include <stdio.h>
// Helper Functions
////////////////////////////////////
#include "test_graphics.h"
#include "${BIT_DEPTH_NAME}/test_framebuffer.h"
#include "util.h"
///////////////////////////////////////////////////////////
// Stubs
#include "stubs_analytics.h"
#include "stubs_app_state.h"
#include "stubs_applib_resource.h"
#include "stubs_bootbits.h"
#include "stubs_heap.h"
#include "stubs_logging.h"
#include "stubs_memory_layout.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_print.h"
#include "stubs_prompt.h"
#include "stubs_serial.h"
#include "stubs_sleep.h"
#include "stubs_syscall_internal.h"
#include "stubs_syscalls.h"
#include "stubs_system_reset.h"
#include "stubs_task_watchdog.h"
#include "stubs_ui_window.h"
#include "stubs_unobstructed_area.h"
///////////////////////////////////////////////////////////
// Fakes
#include "fake_gbitmap_get_data_row.h"
static FrameBuffer *fb = NULL;
// Setup
void test_graphics_draw_text_${BIT_DEPTH_NAME}__initialize(void) {
s_fake_data_row_handling = false;
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
}
// Teardown
void test_graphics_draw_text_${BIT_DEPTH_NAME}__cleanup(void) {
free(fb);
}
///////////////////////////////////////////////////////////
// Tests
static FontInfo s_font_info;
static const char *s_text_buffer = "Text Clipping";
static void prv_setup_resources(void) {
// Setup resources
fake_spi_flash_init(0, 0x1000000);
pfs_init(false);
pfs_format(true /* write erase headers */);
load_resource_fixture_in_flash(RESOURCES_FIXTURE_PATH, SYSTEM_RESOURCES_FIXTURE_NAME, false /* is_next */);
resource_init();
}
// Corner Tests
void draw_text_single_line_ellipsis_clip_across_nx_zero_y_offset(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(-44, 0, 72, 32),
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_ellipsis_clip_across_ny_descender(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(0, -25, 100, 32),
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter, NULL);
}
// Ellipsis Tests
void draw_text_single_line_ellipsis_clip_across_ny(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(4, -18, 72, 32),
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_ellipsis_clip_across_y(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(4, 20, 72, 32),
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_ellipsis_clip_across_nx(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(-44, 4, 72, 32),
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_ellipsis_clip_across_x(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(34, 4, 72, 32),
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_ellipsis_clip_outside_ny(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(4, -40, 72, 32),
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_ellipsis_clip_outside_y(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(4, 40, 72, 32),
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_ellipsis_clip_outside_nx(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(-80, 4, 72, 32),
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_ellipsis_clip_outside_x(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(80, 4, 72, 32),
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter, NULL);
}
// Word Wrap Tests
void draw_text_single_line_wordwrap_clip_across_ny(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(4, -18, 72, 32),
GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_wordwrap_clip_across_ny_second_line(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(4, -46, 72, 50),
GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_wordwrap_clip_across_y(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(4, 20, 72, 32),
GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_wordwrap_clip_across_y_second_line(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(4, -10, 72, 50),
GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_wordwrap_clip_across_nx(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(-44, 4, 72, 32),
GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_wordwrap_clip_across_x(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(34, 4, 72, 32),
GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_wordwrap_clip_outside_ny(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(4, -40, 72, 32),
GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_wordwrap_clip_outside_y(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(4, 40, 72, 32),
GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_wordwrap_clip_outside_nx(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(-80, 4, 72, 32),
GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
}
void draw_text_single_line_wordwrap_clip_outside_x(Layer* me, GContext* ctx) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, s_text_buffer, &s_font_info, GRect(80, 4, 72, 32),
GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
}
void canvas_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_draw_rect(ctx, &GRect(39, 39, 82, 42));
}
void test_graphics_draw_text_${BIT_DEPTH_NAME}__clipping(void) {
GContext ctx;
Layer canvas;
Layer layer;
prv_setup_resources();
uint32_t gothic_24_handle = RESOURCE_ID_GOTHIC_24_BOLD;
cl_assert(text_resources_init_font(0, gothic_24_handle, 0, &s_font_info));
test_graphics_context_init(&ctx, fb);
layer_init(&canvas, &GRect(0, 0, 144, 168));
layer_set_update_proc(&canvas, &canvas_layer_update_callback);
layer_init(&layer, &GRect(40, 40, 80, 40));
layer_add_child(&canvas, &layer);
// Corner cases
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_ellipsis_clip_across_nx_zero_y_offset);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_ellipsis_clip_across_nx_zero_y_offset.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_ellipsis_clip_across_ny_descender);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_ellipsis_clip_across_ny_descender.${BIT_DEPTH_NAME}.pbi"));
// Ellipsis tests
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_ellipsis_clip_across_ny);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_ellipsis_clip_across_ny.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_ellipsis_clip_across_y);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_ellipsis_clip_across_y.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_ellipsis_clip_across_nx);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_ellipsis_clip_across_nx.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_ellipsis_clip_across_x);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_ellipsis_clip_across_x.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_ellipsis_clip_outside_ny);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_ellipsis_clip_outside_ny.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_ellipsis_clip_outside_y);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_ellipsis_clip_outside_y.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_ellipsis_clip_outside_nx);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_ellipsis_clip_outside_nx.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_ellipsis_clip_outside_x);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_ellipsis_clip_outside_x.${BIT_DEPTH_NAME}.pbi"));
// Word Wrap tests
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_wordwrap_clip_across_ny);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_wordwrap_clip_across_ny.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_wordwrap_clip_across_ny_second_line);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_wordwrap_clip_across_ny_second_line.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_wordwrap_clip_across_y);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_wordwrap_clip_across_y.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_wordwrap_clip_across_y_second_line);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_wordwrap_clip_across_y_second_line.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_wordwrap_clip_across_nx);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_wordwrap_clip_across_nx.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_wordwrap_clip_across_x);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_wordwrap_clip_across_x.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_wordwrap_clip_outside_ny);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_wordwrap_clip_outside_ny.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_wordwrap_clip_outside_y);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_wordwrap_clip_outside_y.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_wordwrap_clip_outside_nx);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_wordwrap_clip_outside_nx.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &draw_text_single_line_wordwrap_clip_outside_x);
layer_render_tree(&canvas, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_single_line_wordwrap_clip_outside_x.${BIT_DEPTH_NAME}.pbi"));
}
#define RECT_TEXT_0_0 GRect(0, 0, 140, 1000)
#define RECT_TEXT_2_0 GRect(2, 0, 140, 1000)
void test_graphics_draw_text_${BIT_DEPTH_NAME}__clipping_letters(void) {
GContext ctx;
prv_setup_resources();
uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18_BOLD;
cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));
test_graphics_context_init(&ctx, fb);
// Test when clipping/drawing and text bounds are all at (0, 0)
setup_test_aa_sw(&ctx, fb, RECT_TEXT_0_0, RECT_TEXT_0_0, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "jja", &s_font_info, RECT_TEXT_0_0,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_jja00.${BIT_DEPTH_NAME}.pbi"));
// Test when clipping/drawing are at (2, 0) and text bounds is at (0, 0)
setup_test_aa_sw(&ctx, fb, RECT_TEXT_2_0, RECT_TEXT_2_0, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "jja", &s_font_info, RECT_TEXT_0_0,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_jja20.${BIT_DEPTH_NAME}.pbi"));
// Test when clipping/drawing and text bounds are all at (2, 0)
setup_test_aa_sw(&ctx, fb, RECT_TEXT_2_0, RECT_TEXT_2_0, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "jja", &s_font_info, RECT_TEXT_2_0,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_jja22.${BIT_DEPTH_NAME}.pbi"));
// Test when clipping/drawing and text bounds are all at (2, 0)
setup_test_aa_sw(&ctx, fb, RECT_TEXT_2_0, RECT_TEXT_2_0, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "ajj", &s_font_info, RECT_TEXT_2_0,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_ajj22.${BIT_DEPTH_NAME}.pbi"));
// Test when clipping/drawing and text bounds are all at (2, 0) - no negative offset
setup_test_aa_sw(&ctx, fb, RECT_TEXT_2_0, RECT_TEXT_2_0, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "aaa", &s_font_info, RECT_TEXT_2_0,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_aaa22.${BIT_DEPTH_NAME}.pbi"));
}
#define RECT_NULL GRect(0, 0, 0, 0)
#define RECT_NULL_W GRect(0, 0, 0, 20)
#define RECT_NULL_H GRect(0, 0, 20, 0)
#define RECT_NEG GRect(0, 0, -20, -20)
#define RECT_NEG_W GRect(0, 0, -20, 0)
#define RECT_NEG_H GRect(0, 0, 0, -20)
void test_graphics_draw_text_${BIT_DEPTH_NAME}__zero(void) {
GContext ctx;
prv_setup_resources();
uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18_BOLD;
cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));
test_graphics_context_init(&ctx, fb);
// Test zero text bounds size - ensure nothing is rendered
setup_test_aa_sw(&ctx, fb, RECT_TEXT_0_0, RECT_TEXT_0_0, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "jja", &s_font_info, RECT_NULL,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(framebuffer_is_empty("draw_text_null", fb, GColorWhite));
setup_test_aa_sw(&ctx, fb, RECT_TEXT_0_0, RECT_TEXT_0_0, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "jja", &s_font_info, RECT_NULL_W,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(framebuffer_is_empty("draw_text_null_w", fb, GColorWhite));
setup_test_aa_sw(&ctx, fb, RECT_TEXT_0_0, RECT_TEXT_0_0, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "jja", &s_font_info, RECT_NULL_H,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(framebuffer_is_empty("draw_text_null_h", fb, GColorWhite));
// Test negative text bounds size - ensure nothing is rendered
setup_test_aa_sw(&ctx, fb, RECT_TEXT_0_0, RECT_TEXT_0_0, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "jja", &s_font_info, RECT_NEG,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(framebuffer_is_empty("draw_text_neg", fb, GColorWhite));
setup_test_aa_sw(&ctx, fb, RECT_TEXT_0_0, RECT_TEXT_0_0, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "jja", &s_font_info, RECT_NEG_W,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(framebuffer_is_empty("draw_text_neg_w", fb, GColorWhite));
setup_test_aa_sw(&ctx, fb, RECT_TEXT_0_0, RECT_TEXT_0_0, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "jja", &s_font_info, RECT_NEG_H,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(framebuffer_is_empty("draw_text_neg_h", fb, GColorWhite));
// Test null context boxes - ensure nothing is rendered
setup_test_aa_sw(&ctx, fb, RECT_NULL, RECT_NULL, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "jja", &s_font_info, RECT_TEXT_0_0,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(framebuffer_is_empty("draw_text_null_null", fb, GColorWhite));
setup_test_aa_sw(&ctx, fb, RECT_NULL_W, RECT_NULL_W, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "jja", &s_font_info, RECT_TEXT_0_0,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(framebuffer_is_empty("draw_text_null_null", fb, GColorWhite));
setup_test_aa_sw(&ctx, fb, RECT_NULL_H, RECT_NULL_H, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "jja", &s_font_info, RECT_TEXT_0_0,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(framebuffer_is_empty("draw_text_null_null", fb, GColorWhite));
// Test negative context boxes - ensure nothing is rendered
setup_test_aa_sw(&ctx, fb, RECT_NEG, RECT_NEG, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "jja", &s_font_info, RECT_TEXT_0_0,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(framebuffer_is_empty("draw_text_null_null", fb, GColorWhite));
setup_test_aa_sw(&ctx, fb, RECT_NEG, RECT_NEG, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "jja", &s_font_info, RECT_NEG,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(framebuffer_is_empty("draw_text_null_null", fb, GColorWhite));
setup_test_aa_sw(&ctx, fb, RECT_NEG, RECT_NEG, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "jja", &s_font_info, RECT_NULL,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(framebuffer_is_empty("draw_text_null_null", fb, GColorWhite));
}
void test_graphics_draw_text_8bit__color(void) {
GContext ctx;
prv_setup_resources();
uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18_BOLD;
cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, GRect(0, 0, 144, 168), GRect(0, 0, 144, 168), false, 1);
graphics_context_set_fill_color(&ctx, GColorRed);
graphics_fill_rect(&ctx, &GRect(0, 0, 144, 168));
graphics_context_set_text_color(&ctx, GColorBlue);
graphics_draw_text(&ctx, "blue100", &s_font_info, GRect(10, 10, 100, 30),
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
ctx.draw_state.text_color.a = 2;
graphics_draw_text(&ctx, "blue66", &s_font_info, GRect(10, 40, 100, 30),
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
ctx.draw_state.text_color.a = 1;
graphics_draw_text(&ctx, "blue33", &s_font_info, GRect(10, 70, 100, 30),
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
ctx.draw_state.text_color.a = 0;
graphics_draw_text(&ctx, "blue0", &s_font_info, GRect(10, 100, 100, 30),
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_color_assign.8bit.pbi"));
setup_test_aa_sw(&ctx, fb, GRect(0, 0, 144, 168), GRect(0, 0, 144, 168), false, 1);
graphics_context_set_fill_color(&ctx, GColorRed);
graphics_fill_rect(&ctx, &GRect(0, 0, 144, 168));
graphics_context_set_compositing_mode(&ctx, GCompOpSet);
graphics_context_set_text_color(&ctx, GColorBlue);
graphics_draw_text(&ctx, "blue100", &s_font_info, GRect(10, 10, 100, 30),
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
ctx.draw_state.text_color.a = 2;
graphics_draw_text(&ctx, "blue66", &s_font_info, GRect(10, 40, 100, 30),
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
ctx.draw_state.text_color.a = 1;
graphics_draw_text(&ctx, "blue33", &s_font_info, GRect(10, 70, 100, 30),
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
ctx.draw_state.text_color.a = 0;
graphics_draw_text(&ctx, "blue0", &s_font_info, GRect(10, 100, 100, 30),
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_color_set.8bit.pbi"));
}
void test_graphics_draw_text_8bit__data_row_offsets(void) {
GContext ctx;
// Setup resources
fake_spi_flash_init(0, 0x1000000);
pfs_init(false);
pfs_format(true /* write erase headers */);
load_resource_fixture_in_flash(RESOURCES_FIXTURE_PATH, SYSTEM_RESOURCES_FIXTURE_NAME, false /* is_next */);
resource_init();
memset(&s_font_info, 0, sizeof(s_font_info));
uint32_t gothic_18_handle = RESOURCE_ID_GOTHIC_18_BOLD;
cl_assert(text_resources_init_font(0, gothic_18_handle, 0, &s_font_info));
test_graphics_context_init(&ctx, fb);
// Enable fake data row handling which will override the gbitmap_get_data_row_xxx() functions
// with their fake counterparts in fake_gbitmap_get_data_row.c
s_fake_data_row_handling = true;
// The following test uses fake bitmap data row handling to clip the text rendering of a repeated
// string of alphabet characters to a diamond mask which is flipped vertically
setup_test_aa_sw(&ctx, fb, RECT_TEXT_0_0, RECT_TEXT_0_0, false, 1);
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_draw_text(&ctx, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR"
"STUVWXYZabcdefghijklmnopqrstuvwxyz", &s_font_info, RECT_TEXT_0_0,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "draw_text_data_row_offsets.8bit.pbi"));
}
@@ -0,0 +1,484 @@
/*
* 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 "clar.h"
#include "fixtures/load_test_resources.h"
#include "applib/fonts/fonts_private.h"
#include "applib/graphics/framebuffer.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/text.h"
#include "applib/graphics/text_resources.h"
#include "applib/ui/layer.h"
#include "applib/ui/window_private.h"
#include "resource/resource_ids.auto.h"
#include "util/size.h"
// Helper Functions
////////////////////////////////////
#include "test_graphics.h"
#include "8bit/test_framebuffer.h"
#include "util.h"
///////////////////////////////////////////////////////////
// Stubs
#include "stubs_analytics.h"
#include "stubs_app_state.h"
#include "stubs_applib_resource.h"
#include "stubs_bootbits.h"
#include "stubs_heap.h"
#include "stubs_logging.h"
#include "stubs_memory_layout.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_print.h"
#include "stubs_prompt.h"
#include "stubs_serial.h"
#include "stubs_sleep.h"
#include "stubs_syscall_internal.h"
#include "stubs_syscalls.h"
#include "stubs_system_reset.h"
#include "stubs_task_watchdog.h"
#include "stubs_ui_window.h"
#include "stubs_unobstructed_area.h"
///////////////////////////////////////////////////////////
// Tests
static FrameBuffer *fb = NULL;
static GContext ctx;
#define NUM_STEPS (5)
#define DELTA 20
static FontInfo s_font_info;
static GBitmap *s_dest_bitmap;
static char *s_text = "A B C D E F G "
"H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o "
"p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R"
"S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z";
void prv_prepare_fb_steps_xy(GSize size, int16_t steps_x, int16_t steps_y) {
gbitmap_destroy(s_dest_bitmap);
s_dest_bitmap = gbitmap_create_blank(GSize(size.w * steps_x, size.h * steps_y),
GBITMAP_NATIVE_FORMAT);
ctx.dest_bitmap = *s_dest_bitmap;
ctx.draw_state.clip_box = (GRect){.size = size};
ctx.draw_state.drawing_box = ctx.draw_state.clip_box;
graphics_context_set_text_color(&ctx, GColorBlack);
graphics_context_set_fill_color(&ctx, GColorLightGray);
memset(s_dest_bitmap->addr, 0xff, s_dest_bitmap->row_size_bytes * s_dest_bitmap->bounds.size.h);
}
void prv_prepare_fb_steps(GSize size) {
prv_prepare_fb_steps_xy(size, NUM_STEPS, NUM_STEPS);
}
void test_graphics_draw_text_flow__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
ctx = (GContext){};
// Setup resources
fake_spi_flash_init(0, 0x1000000);
pfs_init(false);
pfs_format(true /* write erase headers */);
load_resource_fixture_in_flash(RESOURCES_FIXTURE_PATH, SYSTEM_RESOURCES_FIXTURE_NAME, false /* is_next */);
resource_init();
memset(&s_font_info, 0, sizeof(s_font_info));
cl_assert(text_resources_init_font(0, RESOURCE_ID_GOTHIC_18_BOLD, 0, &s_font_info));
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, ctx.dest_bitmap.bounds, ctx.dest_bitmap.bounds, false, 1);
prv_prepare_fb_steps(GSize(DISP_COLS, DISP_ROWS));
}
void test_graphics_draw_text_flow__cleanup(void) {
free(fb);
gbitmap_destroy(s_dest_bitmap);
s_dest_bitmap = NULL;
}
#define RECT_TEXT_0_0 GRect(0, 0, DISP_COLS, DISP_ROWS)
GRangeHorizontal perimeter_for_display_round(const GPerimeter *perimeter,
const GSize *ctx_size,
GRangeVertical vertical_range,
uint16_t inset);
static uint8_t *prv_bitmap_offset_for_steps(GBitmap *bmp, int sx, int sy,
int steps_x, int steps_y) {
sx += (steps_x - 1) / 2;
sy += (steps_y - 1) / 2;
int16_t step_w = bmp->bounds.size.w / steps_x;
int16_t step_h = bmp->bounds.size.h / steps_y;
return ((uint8_t *)bmp->addr) + (sy * step_h * bmp->row_size_bytes) + (sx * step_w);
}
typedef enum {
RenderMoveTextBox,
RenderMoveDrawBox,
} RenderMoveMode;
void render_steps(TextLayoutExtended *layout, RenderMoveMode mode, int delta, int16_t height,
char **texts) {
const GTextLayoutCacheRef layout_cache = (GTextLayoutCacheRef const)layout;
int steps_x = ctx.dest_bitmap.bounds.size.w / ctx.draw_state.clip_box.size.w;
int steps_y = ctx.dest_bitmap.bounds.size.h / ctx.draw_state.clip_box.size.h;
int text_idx = 0;
for (int sx = -((steps_x - 1) / 2) ; sx <= steps_x / 2; sx++) {
for (int sy = -((steps_y - 1) / 2); sy <= steps_y / 2; sy++) {
// as draw_text internally uses absolute coordinates to derive its state we cannot
// simply adjust the draw_box to accomplish a side-by-side comparison
ctx.dest_bitmap.addr = prv_bitmap_offset_for_steps(s_dest_bitmap, sx, sy, steps_x, steps_y);
GRect box = {.size = GSize(DISP_COLS, height)};
const GPoint origin = GPoint(delta * sx, delta * sy);
if (mode == RenderMoveTextBox) {
box.origin = origin;
ctx.draw_state.drawing_box.origin = GPointZero;
} else {
ctx.draw_state.drawing_box.origin = origin;
}
graphics_fill_rect(&ctx, &box);
graphics_draw_rect(&ctx, &(GRect){.origin = GPoint(-ctx.draw_state.drawing_box.origin.x,
-ctx.draw_state.drawing_box.origin.y),
.size = ctx.draw_state.clip_box.size});
char *text = texts ? texts[text_idx++] : s_text;
graphics_draw_text(&ctx, text, &s_font_info, box,
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, layout_cache);
}
}
}
void test_graphics_draw_text_flow__flow_no_paging(void) {
TextLayoutExtended layout = {
.flow_data = {
.perimeter.impl = &(GPerimeter){.callback=perimeter_for_display_round},
.perimeter.inset = 8,
},
};
render_steps(&layout, RenderMoveTextBox, DELTA, DISP_ROWS, NULL);
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_text_flow__flow_no_paging_draw_box(void) {
TextLayoutExtended layout = {
.flow_data = {
.perimeter.impl = &(GPerimeter){.callback=perimeter_for_display_round},
.perimeter.inset = 8,
},
};
render_steps(&layout, RenderMoveDrawBox, DELTA, DISP_ROWS, NULL);
// should result in the very same output as if you did a placement via text box
cl_check(gbitmap_pbi_eq(s_dest_bitmap, "test_graphics_draw_text_flow__flow_no_paging.pbi"));
}
void test_graphics_draw_text_flow__with_origin_zero(void) {
TextLayoutExtended layout = {
.flow_data = {
.perimeter.impl = &(GPerimeter){.callback=perimeter_for_display_round},
.perimeter.inset = 8,
.paging.page_on_screen.size_h = DISP_ROWS, // setting a page height != enables positioning
.paging.origin_on_screen = {0, 0},
},
};
render_steps(&layout, RenderMoveTextBox, DELTA, DISP_ROWS, NULL);
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_text_flow__with_origin_non_zero(void) {
TextLayoutExtended layout = {
.flow_data = {
.perimeter.impl = &(GPerimeter){.callback=perimeter_for_display_round},
.perimeter.inset = 8,
.paging.page_on_screen.size_h = DISP_ROWS, // setting a page height != enables positioning
.paging.origin_on_screen = {DELTA, 2 * DELTA},
},
};
render_steps(&layout, RenderMoveTextBox, DELTA, DISP_ROWS, NULL);
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_text_flow__with_paging(void) {
prv_prepare_fb_steps(GSize(DISP_COLS, 2 * DISP_ROWS));
TextLayoutExtended layout = {
.flow_data = {
.perimeter.impl = &(GPerimeter){.callback=perimeter_for_display_round},
.perimeter.inset = 8,
.paging.page_on_screen = {
.origin_y = 25,
.size_h = 100
}, // setting a page height != enables positioning
},
};
render_steps(&layout, RenderMoveTextBox, DELTA, 1000, NULL);
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_text_flow__avoid_repeat_text_to_avoid_orphans(void) {
TextLayoutExtended layout = {
.flow_data = {
.perimeter.impl = &(GPerimeter){.callback=perimeter_for_display_round},
.perimeter.inset = 8,
.paging.page_on_screen = {
.origin_y = 25,
.size_h = 100
}, // setting a page height != enables positioning
},
};
char first_page_one_line[] = "A B C D E F G H I";
char second_page_one_line[] = "A B C D E F G H I J K L M N";
char second_page_two_lines[] = "A B C D E F G H I J K L M N O P Q R S T U V";
char second_page_full[] = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
"a b c d e f g h j k l m n o p q r s t u v w x y z "
"A";
char third_page_one_line[] = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
"a b c d e f g h j k l m n o p q r s t u v w x y z "
"A B C D E F G";
char third_page_two_lines[] = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
"a b c d e f g h j k l m n o p q r s t u v w x y z "
"A B C D E F G I J K L M N O P";
char third_page_full[] = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
"a b c d e f g h j k l m n o p q r s t u v w x y z "
"A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
"a b c d e f g h j k l m n o p q r s t u";
char fourth_page_one_line[] = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
"a b c d e f g h j k l m n o p q r s t u v w x y z "
"A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
"a b c d e f g h j k l m n o p q r s t u v w x y z";
char *texts[] = {
first_page_one_line,
second_page_one_line, second_page_two_lines, second_page_full,
third_page_one_line, third_page_two_lines, third_page_full, fourth_page_one_line,
};
const int16_t num_steps = ARRAY_LENGTH(texts);
const GSize size = GSize(144, 300);
prv_prepare_fb_steps_xy(size, num_steps, 1);
ctx.draw_state.avoid_text_orphans = true;
render_steps(&layout, RenderMoveDrawBox, 0, size.h, texts);
// draw markers to visualize page breaks
ctx.draw_state.clip_box = s_dest_bitmap->bounds;
ctx.draw_state.drawing_box = s_dest_bitmap->bounds;
ctx.dest_bitmap.addr = s_dest_bitmap->addr;
graphics_context_set_stroke_color(&ctx, GColorDarkGray);
int16_t y = layout.flow_data.paging.page_on_screen.origin_y;
while (y < size.h) {
graphics_draw_line(&ctx, GPoint(0, y), GPoint(s_dest_bitmap->bounds.size.w, y));
y += layout.flow_data.paging.page_on_screen.size_h;
}
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
}
GRangeHorizontal perimeter_for_circle(GRangeVertical vertical_range, GPoint center, int32_t radius);
GRangeHorizontal perimeter_for_display_rect(const GPerimeter *perimeter,
const GSize *ctx_size,
GRangeVertical vertical_range,
uint16_t inset);
// easiest way to make these dimensions identical to spalding although the tests take
// defaults from basalt's screen resolution. The original `perimeter_for_display_round`
// uses the platform-specific `DISP_FRAME`
static GRangeHorizontal prv_perimeter_for_display_round(const GPerimeter *perimeter,
const GSize *ctx_size,
GRangeVertical vertical_range,
uint16_t inset) {
const GRect disp_180_frame = GRect(0, 0, 180, 180);
const GPoint center = grect_center_point(&disp_180_frame);
const int32_t radius = grect_shortest_side(disp_180_frame) / 2 - inset;
return perimeter_for_circle(vertical_range, center, radius);
}
void test_graphics_draw_text_flow__draw_text_doom(void) {
// text and configuration we see in text_flow demo app
cl_assert(text_resources_init_font(0, RESOURCE_ID_GOTHIC_24_BOLD, 0, &s_font_info));
TextLayoutExtended layout = {
.flow_data = {
.perimeter.impl = &(GPerimeter){.callback = prv_perimeter_for_display_round},
.perimeter.inset = 8,
.paging.page_on_screen = {
.origin_y = 48,
.size_h = 85
},
.paging.origin_on_screen.y = 412,
},
};
char text[] = "Dib: You're just jealous...\nZim: This has nothing to do with jelly!\n"
"Zim: You dare agree with me? Prepare to meet your horrible doom!";
prv_prepare_fb_steps_xy(GSize(180, 300), 1, 1);
ctx.draw_state.avoid_text_orphans = true;
graphics_draw_text(&ctx, text, &s_font_info, GRect(0, 0, 180, 1000),
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter,
(GTextAttributes *const) &layout);
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
prv_prepare_fb_steps_xy(GSize(180, 300), 1, 1);
ctx.draw_state.avoid_text_orphans = true;
ctx.draw_state.clip_box.origin.y = 48;
ctx.draw_state.clip_box.size.h = 85;
ctx.draw_state.drawing_box.origin.y = -183;
graphics_draw_text(&ctx, text, &s_font_info, GRect(0, 0, 180, 1000),
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter,
(GTextAttributes *const) &layout);
cl_check(gbitmap_pbi_eq(s_dest_bitmap, namecat(namecat(__func__, "__clipped"), ".pbi")));
prv_prepare_fb_steps_xy(GSize(180, 300), 1, 1);
ctx.draw_state.avoid_text_orphans = false;
graphics_draw_text(&ctx, text, &s_font_info, GRect(0, 0, 180, 1000),
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter,
(GTextAttributes *const) &layout);
cl_check(gbitmap_pbi_eq(s_dest_bitmap, namecat(namecat(__func__, "__with_orphan"), ".pbi")));
}
void test_graphics_draw_text_flow__max_used_size_draw_text_doom(void) {
cl_assert(text_resources_init_font(0, RESOURCE_ID_GOTHIC_24_BOLD, 0, &s_font_info));
TextLayoutExtended layout = {
.flow_data = {
.perimeter.impl = &(GPerimeter){.callback = prv_perimeter_for_display_round},
.perimeter.inset = 8,
.paging.page_on_screen = {
.origin_y = 48,
.size_h = 85
},
.paging.origin_on_screen.y = 412,
},
};
void *layout_ref = (void *)&layout;
char text[] = "Dib: You're just jealous...\nZim: This has nothing to do with jelly!\n"
"Zim: You dare agree with me? Prepare to meet your horrible doom!";
const GFont font = &s_font_info;
const GRect box = GRect(0, 0, 180, 1000);
const GTextOverflowMode overflow_mode = GTextOverflowModeTrailingEllipsis;
const GTextAlignment text_alignment = GTextAlignmentCenter;
const GSize fb_size = GSize(180, 300);
const int16_t steps_x = 1;
const int16_t steps_y = 1;
prv_prepare_fb_steps_xy(fb_size, steps_x, steps_y);
ctx.draw_state.avoid_text_orphans = true;
const GSize size_with_orphan_avoidance = graphics_text_layout_get_max_used_size(&ctx,
text, font, box,
overflow_mode,
text_alignment,
layout_ref);
// TODO: PBL-34191 move .avoid_text_orphans from GContext to TextLayout so layout is invalidated
// Invalidate the layout so it will be recalculated for the next step
layout.hash = 0;
prv_prepare_fb_steps_xy(fb_size, steps_x, steps_y);
ctx.draw_state.avoid_text_orphans = false;
const GSize size_without_orphan_avoidance = graphics_text_layout_get_max_used_size(&ctx,
text, font,
box,
overflow_mode,
text_alignment,
layout_ref);
// We should get different heights because the orphan avoidance algorithm adds an extra line
cl_assert_equal_i(size_with_orphan_avoidance.h, 279);
cl_assert_equal_i(size_without_orphan_avoidance.h, 255);
}
void test_graphics_draw_text_flow__no_infinite_loop(void) {
TextLayoutExtended layout = {
.flow_data = {
.perimeter.impl = &(GPerimeter){.callback = perimeter_for_display_rect},
},
};
char text[] = "Prevent orhpans for tall-enough pages.";
const int16_t line_height = 22;
// some more pixels to show that orphan prevention really only applies if there's enough space
// for enough *full* lines
const int16_t some = 5;
prv_prepare_fb_steps_xy(GSize(180, 300), 3, 1);
ctx.draw_state.avoid_text_orphans = true;
for (int i = 0; i < 3; i++) {
const int number_of_lines_per_page = i + 1;
layout.flow_data.paging.page_on_screen.size_h = number_of_lines_per_page * line_height + some;
layout.flow_data.paging.origin_on_screen.y =
layout.flow_data.paging.page_on_screen.size_h - line_height;
graphics_draw_text(&ctx, text, &s_font_info, GRect(0, 0, 180, 1000),
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter,
(GTextAttributes *const) &layout);
const int16_t second_page_start_y =
(layout.flow_data.paging.page_on_screen.size_h - layout.flow_data.paging.origin_on_screen.y);
const int16_t second_page_end_y =
(second_page_start_y + layout.flow_data.paging.page_on_screen.size_h);
graphics_draw_line(&ctx, GPoint(0, second_page_start_y), GPoint(180, second_page_start_y));
graphics_draw_line(&ctx, GPoint(0, second_page_end_y), GPoint(180, second_page_end_y));
ctx.draw_state.drawing_box.origin.x += 180;
ctx.draw_state.clip_box.origin.x += 180;
}
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
}
void test_graphics_draw_text_flow__no_infinite_loop2(void) {
// replicates the bug described in PBL-29267 noticed in the notification app
// the following values are those we measured in GDB when it entered the infinite loop
cl_assert(text_resources_init_font(0, RESOURCE_ID_GOTHIC_24_BOLD, 0, &s_font_info));
TextLayoutExtended layout = {
.flow_data = {
.perimeter.impl = &(GPerimeter){.callback = prv_perimeter_for_display_round,},
.perimeter.inset = 8,
.paging = {
.origin_on_screen = GPoint(12, 83),
.page_on_screen.origin_y = 24,
.page_on_screen.size_h = 140,
}
},
};
char text[] = "Late again? Can you be on time ever? Seriosly? Dude!!!";
prv_prepare_fb_steps_xy(GSize(180, 360), 1, 1);
ctx.draw_state.avoid_text_orphans = true;
GRect box = (GRect){.origin = {.x = 12, .y = 59}, .size = {.w = 156, .h = 2480}};
graphics_draw_text(&ctx, text, &s_font_info, box,
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter,
(GTextAttributes *const) &layout);
cl_check(gbitmap_pbi_eq(s_dest_bitmap, TEST_PBI_FILE));
}
@@ -0,0 +1,846 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "applib/graphics/graphics.h"
#include "applib/graphics/graphics_circle_private.h"
#include "applib/graphics/framebuffer.h"
#include "util/trig.h"
#include "applib/ui/window_private.h"
#include "applib/ui/layer.h"
#include "util/size.h"
#include "clar.h"
#include "util.h"
#include "pebble_asserts.h"
#include <stdio.h>
// Helper Functions
////////////////////////////////////
#include "test_graphics.h"
#include "${BIT_DEPTH_NAME}/test_framebuffer.h"
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
static FrameBuffer *fb = NULL;
// Setup
void test_graphics_fill_circle_${BIT_DEPTH_NAME}__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
}
// Teardown
void test_graphics_fill_circle_${BIT_DEPTH_NAME}__cleanup(void) {
free(fb);
}
// Tests
////////////////////////////////////
void inside_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_fill_circle(ctx, GPoint(14, 14), 12);
}
void white_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_fill_color(ctx, GColorWhite);
graphics_context_set_antialiased(ctx, false);
graphics_fill_circle(ctx, GPoint(14, 14), 12);
}
void clear_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_fill_color(ctx, GColorClear);
graphics_context_set_antialiased(ctx, false);
graphics_fill_circle(ctx, GPoint(14, 14), 12);
}
void across_x_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_fill_circle(ctx, GPoint(28, 14), 12);
}
void across_nx_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_fill_circle(ctx, GPoint(-14, 14), 12);
}
void across_y_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_fill_circle(ctx, GPoint(14, 28), 12);
}
void across_ny_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_context_set_antialiased(ctx, false);
graphics_fill_circle(ctx, GPoint(14, -14), 12);
}
void test_graphics_fill_circle_${BIT_DEPTH_NAME}__origin_layer(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(0, 0, 28, 28));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_inside_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_x_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_x_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_nx_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_nx_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_y_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_y_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_ny_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_ny_origin_layer.${BIT_DEPTH_NAME}.pbi"));
}
#define RADIUS_BIG 15
#define RADIUS_MEDIUM 8
#define RADIUS_MIN_CALCULATED 3
#define RADIUS_MAX_PRECOMPUTED 2
#define RADIUS_SMALL 1
#define RADIUS_NONE 0
#define ORIGIN_RECT_NO_CLIP GRect(0, 0, 144, 168)
#define ORIGIN_RECT_CLIP_XY GRect(0, 0, 30, 40)
#define ORIGIN_RECT_CLIP_NXNY GRect(0, 0, 30, 40)
#define CENTER_OF_ORIGIN_RECT GPoint(20, 25)
#define CENTER_OF_ORIGIN_RECT_NXNY GPoint(10, 15)
void test_graphics_fill_circle_${BIT_DEPTH_NAME}__origin_layer_aa(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Big circles
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r16_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r16_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r16_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Medium circles
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r8_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r8_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r8_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Small circles
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r1_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r1_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Testing of the special cases for radius:
// Radius of 3 - starting point for calculated edges
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_MIN_CALCULATED);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r3_no_clip.${BIT_DEPTH_NAME}.pbi"));
// Radius of 2 - ending point for precomputed edges
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_MAX_PRECOMPUTED);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r2_no_clip.${BIT_DEPTH_NAME}.pbi"));
// No circle
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_NONE);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_origin_aa_r0_no_clip.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_fill_circle_${BIT_DEPTH_NAME}__offset_layer(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(10, 15, 28, 28));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_inside_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_x_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_x_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_nx_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_nx_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_y_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_y_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_ny_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_across_ny_offset_layer.${BIT_DEPTH_NAME}.pbi"));
}
#define OFFSET_RECT_NO_CLIP GRect(10, 10, 40, 50)
#define OFFSET_RECT_CLIP_XY GRect(10, 10, 30, 40)
#define OFFSET_RECT_CLIP_NXNY GRect(0, 0, 30, 40)
#define CENTER_OF_OFFSET_RECT GPoint(10, 15)
#define CENTER_OF_OFFSET_RECT_NXNY GPoint(0, 5)
void test_graphics_fill_circle_${BIT_DEPTH_NAME}__offset_layer_aa(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Big circles
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r16_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r16_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT_NXNY, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r16_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Medium circles
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r8_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r8_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT_NXNY, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r8_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Small circles
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r1_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, 1);
graphics_fill_circle(&ctx, CENTER_OF_OFFSET_RECT_NXNY, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_offset_aa_r1_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_fill_circle_${BIT_DEPTH_NAME}__quadrants_aa(void) {
#if PBL_COLOR
GContext ctx;
test_graphics_context_init(&ctx, fb);
typedef struct {
char *filename_part;
GCornerMask mask;
} TestConfig;
TestConfig test_config[] = {
{
.filename_part = "quad_top_left",
.mask = GCornerTopLeft,
},
{
.filename_part = "quad_top_right",
.mask = GCornerTopRight,
},
{
.filename_part = "quad_bottom_right",
.mask = GCornerBottomRight,
},
{
.filename_part = "quad_bottom_left",
.mask = GCornerBottomLeft,
},
{
.filename_part = "quads_top",
.mask = GCornersTop,
},
{
.filename_part = "quads_bottom",
.mask = GCornersBottom,
},
{
.filename_part = "quads_right",
.mask = GCornersRight,
},
{
.filename_part = "quads_left",
.mask = GCornersLeft,
},
};
// note: not the prettiest, fast a quick way to render all the scenarios Nitin was interested in
for (int i = 0; i < ARRAY_LENGTH(test_config); i++) {
TestConfig c = test_config[i];
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
GPoint pt = CENTER_OF_ORIGIN_RECT;
// draw multiple quads with different radiuses
for (int r = 1; r <= 15; r++) {
graphics_internal_circle_quadrant_fill_aa(&ctx, pt, r, c.mask);
// center point follows a grid
pt.x += 30;
if (pt.x > 120) {
pt.x = CENTER_OF_ORIGIN_RECT.x;
pt.y += 30;
}
}
// construct file name and create meaningful assert description
char filename[100];
snprintf(filename, sizeof(filename),
"fill_circle_offset_aa_%s.${BIT_DEPTH_NAME}.pbi", c.filename_part);
cl_check_(gbitmap_pbi_eq(&ctx.dest_bitmap, filename), filename);
}
#endif
}
void test_graphics_fill_circle_${BIT_DEPTH_NAME}__color(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(0, 0, 28, 28));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_inside_origin_layer.${BIT_DEPTH_NAME}.pbi"));
layer_set_update_proc(&layer, &white_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(framebuffer_is_empty("white_over_black", ctx.parent_framebuffer, GColorWhite));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_circle_inside_origin_layer.${BIT_DEPTH_NAME}.pbi"));
layer_set_update_proc(&layer, &clear_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(framebuffer_is_empty("clear_over_black", ctx.parent_framebuffer, GColorWhite));
}
#define TO_TRIG(deg) (((deg) * TRIG_MAX_ANGLE) / 360)
void test_graphics_fill_circle_${BIT_DEPTH_NAME}__radial(void){
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Pacman
uint32_t angle_end = TRIG_MAX_ANGLE + (TRIG_MAX_ANGLE / 8);
uint32_t angle_start = (TRIG_MAX_ANGLE / 8) * 3;
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_radial_internal(&ctx, CENTER_OF_ORIGIN_RECT, 0, RADIUS_BIG, angle_start, angle_end);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_origin_aa_pacman.${BIT_DEPTH_NAME}.pbi"));
// Letter C
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_radial_internal(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, RADIUS_BIG, angle_start,
angle_end);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_origin_aa_letter_c.${BIT_DEPTH_NAME}.pbi"));
// Negative angles - uses same resource image as result should be identical
angle_start -= TRIG_MAX_ANGLE;
angle_end -= TRIG_MAX_ANGLE;
// Pacman
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_radial_internal(&ctx, CENTER_OF_ORIGIN_RECT, 0, RADIUS_BIG, angle_start, angle_end);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_origin_aa_pacman.${BIT_DEPTH_NAME}.pbi"));
// Letter C
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_radial_internal(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, RADIUS_BIG, angle_start,
angle_end);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_origin_aa_letter_c.${BIT_DEPTH_NAME}.pbi"));
// table with most popular angles to test
typedef struct {
char *filename_part;
uint32_t angle;
} TestAngles;
TestAngles test_angles[] = {
{
.filename_part = "__1_degrees",
.angle = TRIG_MAX_ANGLE / 360,
},
{
.filename_part = "__6_degrees",
.angle = TRIG_MAX_ANGLE / 60,
},
{
.filename_part = "_30_degrees",
.angle = TRIG_MAX_ANGLE / 12,
},
{
.filename_part = "_45_degrees",
.angle = TRIG_MAX_ANGLE / 8,
},
{
.filename_part = "_90_degrees",
.angle = TRIG_MAX_ANGLE / 4,
},
{
.filename_part = "181_degrees",
.angle = TRIG_MAX_ANGLE / 2 + TRIG_MAX_ANGLE / 360,
}
};
for (int i = 0; i < ARRAY_LENGTH(test_angles); i++) {
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
GPoint pt = CENTER_OF_ORIGIN_RECT;
uint16_t inner_radius = 0;
uint16_t outer_radius = 10;
for (int r = 1; r <= 8; r++) {
graphics_fill_radial_internal(&ctx, pt, inner_radius, outer_radius, 0, test_angles[i].angle);
inner_radius += 1;
outer_radius += 3;
pt.x += outer_radius * 2;
if (pt.x > 120) {
pt.x = CENTER_OF_ORIGIN_RECT.x;
pt.y += outer_radius * 2;
}
}
char filename[100];
snprintf(filename, sizeof(filename),
"fill_radial_offset_aa_end_angle_%s.${BIT_DEPTH_NAME}.pbi", test_angles[i].filename_part);
cl_check_(gbitmap_pbi_eq(&ctx.dest_bitmap, filename), filename);
}
for (int i = 0; i < ARRAY_LENGTH(test_angles); i++) {
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
GPoint pt = CENTER_OF_ORIGIN_RECT;
uint16_t inner_radius = 0;
uint16_t outer_radius = 10;
for (int r = 1; r <= 8; r++) {
graphics_fill_radial_internal(&ctx, pt, inner_radius, outer_radius, test_angles[i].angle,
TRIG_MAX_ANGLE);
inner_radius += 1;
outer_radius += 3;
pt.x += outer_radius * 2;
if (pt.x > 120) {
pt.x = CENTER_OF_ORIGIN_RECT.x;
pt.y += outer_radius * 2;
}
}
char filename[100];
snprintf(filename, sizeof(filename),
"fill_radial_offset_aa_start_angle_%s.${BIT_DEPTH_NAME}.pbi", test_angles[i].filename_part);
cl_check_(gbitmap_pbi_eq(&ctx.dest_bitmap, filename), filename);
}
// table with radiuses
typedef struct {
char *filename_part;
int radius;
} TestRadiuses;
TestRadiuses test_radiuses[] = {
{
.filename_part = "_inner_0",
.radius = 0,
},
{
.filename_part = "_inner_20",
.radius = 15,
}
};
// table with quadrants
typedef struct {
char *filename_part;
int angle_start;
int angle_end;
} TestQuadrants;
TestQuadrants test_quadrants[] = {
{
.filename_part = "_part",
.angle_start = (TO_TRIG(-45)),
.angle_end = (TO_TRIG(-45)),
},
{
.filename_part = "_two_parts",
.angle_start = 0,
.angle_end = 0,
},
{
.filename_part = "_quadrant_and_two_parts",
.angle_start = 0,
.angle_end = (TO_TRIG(90)),
}
};
#if PBL_COLOR
//Colors table
GColor colors[4] = {
GColorBlack,
GColorRed,
GColorBlue,
GColorGreen,
};
#endif
uint16_t outer_radius = 30;
int32_t twelveth_of_angle = TRIG_MAX_ANGLE / 12;
int32_t quarter_of_angle = TRIG_MAX_ANGLE / 4;
GPoint center = GPoint(72, 84);
// Cases for quadrant joints
for (int i = 0; i < ARRAY_LENGTH(test_radiuses); i++) {
for (int j = 0; j < ARRAY_LENGTH(test_quadrants); j++) {
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
int offset_angle = 0;
for (int r = 0; r < 4; r++) {
int offset = (((r + 1) % 4 < 2) ? -20 : 20);
GPoint pt = GPoint(center.x + ((r % 2 == 0) ? 0 : offset),
center.y + ((r % 2 == 0) ? offset * 2 : 0));
#if PBL_COLOR
graphics_context_set_fill_color(&ctx, colors[r]);
#endif
graphics_fill_radial_internal(&ctx, pt, test_radiuses[i].radius, outer_radius,
test_quadrants[j].angle_start + offset_angle -
twelveth_of_angle,
test_quadrants[j].angle_end + offset_angle +
twelveth_of_angle);
offset_angle += quarter_of_angle;
}
char filename[100];
snprintf(filename, sizeof(filename),
"fill_radial_aa_joints_%s%s.${BIT_DEPTH_NAME}.pbi",
test_radiuses[i].filename_part, test_quadrants[j].filename_part);
cl_check_(gbitmap_pbi_eq(&ctx.dest_bitmap, filename), filename);
}
}
}
void test_graphics_fill_circle_${BIT_DEPTH_NAME}__radial_precise(void){
GContext ctx;
test_graphics_context_init(&ctx, fb);
// letter C
uint32_t angle_end = TRIG_MAX_ANGLE + (TRIG_MAX_ANGLE / 8);
uint32_t angle_start = (TRIG_MAX_ANGLE / 8) * 3;
GPointPrecise center = GPointPrecise(CENTER_OF_ORIGIN_RECT.x * 8, CENTER_OF_ORIGIN_RECT.y * 8);
Fixed_S16_3 radius_inner = (Fixed_S16_3){.integer = RADIUS_MEDIUM};
Fixed_S16_3 radius_outer = (Fixed_S16_3){.integer = RADIUS_BIG};
// Drawing
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_radial_precise_internal(&ctx, center, radius_inner, radius_outer, angle_start, angle_end);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_origin_aa_precise_letter_c.${BIT_DEPTH_NAME}.pbi"));
//Make the points utilise precision powers
center.x.raw_value += 4;
center.y.raw_value += 4;
radius_inner.raw_value += 4;
radius_outer.raw_value += 4;
// Drawing
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_radial_precise_internal(&ctx, center, radius_inner, radius_outer, angle_start, angle_end);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_origin_aa_precise_halfs_letter_c.${BIT_DEPTH_NAME}.pbi"));
}
typedef struct {
char *filename_part;
int angle_start;
int angle_end;
} TestRadialAnglesConfigs;
TestRadialAnglesConfigs test_radial_angles[] = {
{
.filename_part = "_part",
.angle_start = (TO_TRIG(-45)),
.angle_end = (TO_TRIG(-45)),
},
{
.filename_part = "_two_parts",
.angle_start = 0,
.angle_end = 0,
},
{
.filename_part = "_quadrant_and_two_parts",
.angle_start = 0,
.angle_end = (TO_TRIG(90)),
},
{
.filename_part = "_two_quadrants_and_two_parts",
.angle_start = 0,
.angle_end = (TO_TRIG(180)),
},
{
.filename_part = "_three_quadrants_and_two_parts",
.angle_start = 0,
.angle_end = (TO_TRIG(270)),
},
{
.filename_part = "_full",
.angle_start = 0,
.angle_end = TRIG_MAX_ANGLE,
}
};
typedef struct {
char *filename_part;
int16_t width;
int16_t height;
GOvalScaleMode scale_mode;
int16_t inset;
} TestRadialGRectConfigs;
TestRadialGRectConfigs test_radial_rects[] = {
{
.filename_part = "_even_rect_fill",
.width = 40,
.height = 40,
.scale_mode = GOvalScaleModeFillCircle,
.inset = 10,
},
{
.filename_part = "_even_rect_fit",
.width = 40,
.height = 40,
.scale_mode = GOvalScaleModeFitCircle,
.inset = 10,
},
{
.filename_part = "_odd_rect_fill",
.width = 41,
.height = 41,
.scale_mode = GOvalScaleModeFillCircle,
.inset = 10,
},
{
.filename_part = "_odd_rect_fit",
.width = 41,
.height = 41,
.scale_mode = GOvalScaleModeFitCircle,
.inset = 10,
},
{
.filename_part = "_even_rect_fill_no_middle",
.width = 40,
.height = 40,
.scale_mode = GOvalScaleModeFillCircle,
.inset = 20,
},
{
.filename_part = "_even_rect_fit_no_middle",
.width = 40,
.height = 40,
.scale_mode = GOvalScaleModeFitCircle,
.inset = 20,
},
{
.filename_part = "_odd_rect_fill_no_middle",
.width = 41,
.height = 41,
.scale_mode = GOvalScaleModeFillCircle,
.inset = 21,
},
{
.filename_part = "_odd_rect_fit_no_middle",
.width = 41,
.height = 41,
.scale_mode = GOvalScaleModeFitCircle,
.inset = 21,
},
};
void prv_draw_radial_in_rect_debugged(GContext *ctx, int16_t width, int16_t height,
GOvalScaleMode scale_mode, int16_t inset,
int32_t angle_start, int32_t angle_end){
int offset_angle = 0;
int32_t twelveth_of_angle = TRIG_MAX_ANGLE / 12;
GPoint center = GPoint(72, 84);
for (int i=0; i<4; i++) {
GRect rect = GRect(center.x - (width/2), center.y - (height / 2), width, height);
int offset_x = (((i + 1) % 4 < 2) ? -(width * 2 / 3) : (width * 2 / 3));
int offset_y = ((i % 4 < 2) ? -(height * 2 / 3) : (height * 2 / 3));
rect.origin.x += offset_x;
rect.origin.y += offset_y;
const GRect bigger_rect = GRect(rect.origin.x - 1, rect.origin.y - 1,
rect.size.w + 2, rect.size.h + 2);
graphics_context_set_stroke_color(ctx, PBL_IF_COLOR_ELSE(GColorGreen, GColorWhite));
graphics_draw_rect(ctx, &rect);
graphics_context_set_stroke_color(ctx, PBL_IF_COLOR_ELSE(GColorRed, GColorBlack));
graphics_draw_rect(ctx, &bigger_rect);
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_fill_radial(ctx, rect, scale_mode, inset,
angle_start + offset_angle - twelveth_of_angle,
angle_end + offset_angle + twelveth_of_angle);
offset_angle += TRIG_MAX_ANGLE / 4;
}
}
void test_graphics_fill_circle_${BIT_DEPTH_NAME}__radial_grect(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
for (int rect_id=0; rect_id < ARRAY_LENGTH(test_radial_rects); rect_id++) {
for (int angle_id=0; angle_id < ARRAY_LENGTH(test_radial_angles); angle_id++) {
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
prv_draw_radial_in_rect_debugged(&ctx,
test_radial_rects[rect_id].width,
test_radial_rects[rect_id].height,
test_radial_rects[rect_id].scale_mode,
test_radial_rects[rect_id].inset,
test_radial_angles[angle_id].angle_start,
test_radial_angles[angle_id].angle_end);
char filename[100];
snprintf(filename, sizeof(filename),
"fill_radial%s%s.${BIT_DEPTH_NAME}.pbi",
test_radial_rects[rect_id].filename_part, test_radial_angles[angle_id].filename_part);
cl_check_(gbitmap_pbi_eq(&ctx.dest_bitmap, filename), filename);
}
}
}
void prv_test_dithering_color(GContext *ctx, GColor color) {
const uint32_t angle_end = DEG_TO_TRIGANGLE(405);
const uint32_t angle_start = DEG_TO_TRIGANGLE(135);
GRect rect = GRect(10, 10, 40, 40);
setup_test_aa_sw(ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_context_set_fill_color(ctx, color);
// Circle
graphics_fill_radial(ctx, rect, GOvalScaleModeFitCircle, 50, 0, TRIG_MAX_ANGLE);
// Pacman
rect.origin.y += 50;
graphics_fill_radial(ctx, rect, GOvalScaleModeFitCircle, 50, angle_start, angle_end);
// Letter C
rect.origin.y += 50;
graphics_fill_radial(ctx, rect, GOvalScaleModeFitCircle, 10, angle_start, angle_end);;
// Following SHOULD NOT be dithered into grayscale:
graphics_context_set_stroke_color(ctx, color);
// Circle:
GPoint point = GPoint(95, 56);
graphics_draw_circle(ctx, point, 20);
// Line:
GPoint p1 = GPoint(75, 140);
GPoint p2 = GPoint(115, 140);
graphics_draw_line(ctx, p1, p2);
// Stroked Circle:
graphics_context_set_stroke_width(ctx, 12);
point.y += 52;
graphics_draw_circle(ctx, point, 20);
// Stroked Line:
p1.y += 10;
p2.y += 10;
graphics_draw_line(ctx, p1, p2);
// Stroked line turning into circle:
point.y = 20;
graphics_draw_circle(ctx, point, 5);
}
void test_graphics_fill_circle_${BIT_DEPTH_NAME}__dithering_grayscale(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
prv_test_dithering_color(&ctx, GColorWhite);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_dither_GColorWhite.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_init(&ctx, fb);
prv_test_dithering_color(&ctx, GColorLightGray);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_dither_GColorLightGray.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_init(&ctx, fb);
prv_test_dithering_color(&ctx, GColorDarkGray);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_dither_GColorDarkGray.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_init(&ctx, fb);
prv_test_dithering_color(&ctx, GColorBlack);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_dither_GColorBlack.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_init(&ctx, fb);
prv_test_dithering_color(&ctx, GColorJaegerGreen);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_dither_GColorJaegerGreen.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_init(&ctx, fb);
prv_test_dithering_color(&ctx, GColorOrange);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_radial_dither_GColorOrange.${BIT_DEPTH_NAME}.pbi"));
}
@@ -0,0 +1,585 @@
/*
* 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 "util/math.h"
#include "util.h"
#include "clar.h"
#include <stdio.h>
// Helper Functions
////////////////////////////////////
#include "test_graphics.h"
#include "${BIT_DEPTH_NAME}/test_framebuffer.h"
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
static FrameBuffer *fb = NULL;
// Setup
void test_graphics_fill_rect_${BIT_DEPTH_NAME}__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
}
// Teardown
void test_graphics_fill_rect_${BIT_DEPTH_NAME}__cleanup(void) {
free(fb);
}
// Tests
////////////////////////////////////
void inside_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_antialiased(ctx, false);
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_fill_rect(ctx, &GRect(4, 2, 16, 8));
}
void across_x_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_antialiased(ctx, false);
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_fill_round_rect(ctx, &GRect(10, 2, 18, 4), 4, GCornersAll);
}
void across_nx_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_antialiased(ctx, false);
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_fill_round_rect(ctx, &GRect(-10, 2, 18, 4), 4, GCornersAll);
}
void across_y_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_antialiased(ctx, false);
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_fill_round_rect(ctx, &GRect(4, 5, 18, 10), 4, GCornersAll);
}
void across_ny_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_antialiased(ctx, false);
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_fill_round_rect(ctx, &GRect(4, -5, 18, 10), 4, GCornersAll);
}
void corners_all_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_antialiased(ctx, false);
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_fill_round_rect(ctx, &GRect(2, 2, 28, 20), 4, GCornersAll);
}
void white_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_antialiased(ctx, false);
graphics_context_set_fill_color(ctx, GColorWhite);
graphics_fill_round_rect(ctx, &GRect(2, 2, 28, 20), 4, GCornersAll);
}
void clear_layer_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_antialiased(ctx, false);
graphics_context_set_fill_color(ctx, GColorClear);
graphics_fill_round_rect(ctx, &GRect(2, 2, 28, 20), 4, GCornersAll);
}
void corners_clipped_update_callback(Layer* me, GContext* ctx) {
graphics_context_set_antialiased(ctx, false);
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_fill_round_rect(ctx, &GRect(-19, 0, 20, 20), 4, GCornersAll);
}
void test_graphics_fill_rect_${BIT_DEPTH_NAME}__origin_layer(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(0, 0, 20, 10));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_inside_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_x_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_across_x_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_nx_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_across_nx_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_y_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_across_y_origin_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_ny_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_across_ny_origin_layer.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_fill_rect_${BIT_DEPTH_NAME}__offset_layer(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(10, 15, 20, 10));
layer_set_update_proc(&layer, &inside_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_inside_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_x_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_across_x_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_nx_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_across_nx_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_y_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_across_y_offset_layer.${BIT_DEPTH_NAME}.pbi"));
test_graphics_context_reset(&ctx, fb);
layer_set_update_proc(&layer, &across_ny_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_across_ny_offset_layer.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_fill_rect_${BIT_DEPTH_NAME}__clipped(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(0, 0, 100, 100));
layer_set_update_proc(&layer, &corners_clipped_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_clipped.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_fill_rect_${BIT_DEPTH_NAME}__color(void) {
GContext ctx;
Layer layer;
test_graphics_context_init(&ctx, fb);
layer_init(&layer, &GRect(0, 0, 32, 24));
layer_set_update_proc(&layer, &corners_all_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_corners_all.${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, &corners_all_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_corners_all.${BIT_DEPTH_NAME}.pbi"));
layer_set_update_proc(&layer, &clear_layer_update_callback);
layer_render_tree(&layer, &ctx);
cl_check(framebuffer_is_empty("clear_over_black", ctx.parent_framebuffer, GColorWhite));
}
#define RECT_WIDTH 30
#define RECT_HEIGHT 40
#define ORIGIN_RECT_NO_CLIP GRect(0, 0, 144, 168)
#define ORIGIN_RECT_CLIP_XY GRect(0, 0, 20, 20)
#define ORIGIN_RECT_CLIP_NXNY GRect(0, 0, 144, 168)
#define ORIGIN_DRAW_RECT_NO_CLIP GRect(6, 6, RECT_WIDTH, RECT_HEIGHT)
#define ORIGIN_DRAW_RECT_CLIP_XY GRect(6, 6, RECT_WIDTH, RECT_HEIGHT)
#define ORIGIN_DRAW_RECT_CLIP_NXNY GRect(-16, -16, RECT_WIDTH, RECT_HEIGHT)
void test_graphics_fill_rect_8bit__transparency(void) {
GContext ctx;
FrameBuffer fb;
framebuffer_init(&fb, &(GSize) {DISP_COLS, DISP_ROWS});
test_graphics_context_init(&ctx, &fb);
// AA = true, SW has no effect, all corners
setup_test_aa_sw(&ctx, &fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_context_set_fill_color(&ctx, GColorBlue);
graphics_fill_rect(&ctx, &GRect(10, 10, 100, 140));
graphics_context_set_fill_color(&ctx, GColorRed);
ctx.draw_state.fill_color.a = 0;
graphics_fill_rect(&ctx, &GRect(20, 20, 70, 20));
ctx.draw_state.fill_color.a = 1;
graphics_fill_rect(&ctx, &GRect(20, 50, 70, 20));
ctx.draw_state.fill_color.a = 2;
graphics_fill_rect(&ctx, &GRect(20, 80, 70, 20));
ctx.draw_state.fill_color.a = 3;
graphics_fill_rect(&ctx, &GRect(20, 110, 70, 20));
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_transparent.8bit.pbi"));
}
void test_graphics_fill_rect_${BIT_DEPTH_NAME}__origin_radius(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// AA = true, SW has no effect, all corners
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 0, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r0_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 1, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 2, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r2_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 3, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r3_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 4, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r4_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 5, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r5_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 6, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r6_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 7, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r7_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 8, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r8_no_clip.${BIT_DEPTH_NAME}.pbi"));
// Should draw same as radius 8
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 9, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r9_no_clip.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_fill_rect_${BIT_DEPTH_NAME}__origin_radius_aa(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// AA = true, SW has no effect, all corners
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 0, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r0_aa_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 1, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r1_aa_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 2, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r2_aa_no_clip.${BIT_DEPTH_NAME}.pbi"));
// TODO: Remove these #ifdefs in PBL-15916 when support for non-antialiased rounded rect
#if SCREEN_COLOR_DEPTH_BITS == 8
// TODO: Fix left corners PBL-15915
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 3, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r3_aa_no_clip.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix left corners PBL-15915
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 4, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r4_aa_no_clip.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix left corners PBL-15915
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 5, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r5_aa_no_clip.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix left corners PBL-15915
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 6, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r6_aa_no_clip.${BIT_DEPTH_NAME}.pbi"));
// TODO: Fix left corners PBL-15915
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 7, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r7_aa_no_clip.${BIT_DEPTH_NAME}.pbi"));
#endif
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 8, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r8_aa_no_clip.${BIT_DEPTH_NAME}.pbi"));
// TODO: Remove these #ifdefs in PBL-15916 when support for non-antialiased rounded rect
#if SCREEN_COLOR_DEPTH_BITS == 8
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, 9, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r9_aa_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_aa_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2), GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax1_aa_no_clip.${BIT_DEPTH_NAME}.pbi"));
// Should draw same as rmax1
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) + 1, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax2_aa_no_clip.${BIT_DEPTH_NAME}.pbi"));
#endif
// AA = true, SW has no effect, Clip XY, all corners
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, 0, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r0_aa_clip_xy.${BIT_DEPTH_NAME}.pbi"));
// TODO: Remove these #ifdefs in PBL-15916 when support for non-antialiased rounded rect
#if SCREEN_COLOR_DEPTH_BITS == 8
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, 1, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r1_aa_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, 2, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r2_aa_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, 3, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r3_aa_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_aa_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2), GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax1_aa_clip_xy.${BIT_DEPTH_NAME}.pbi"));
// Should draw same as rmax1
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_XY, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) + 1, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax2_aa_clip_xy.${BIT_DEPTH_NAME}.pbi"));
#endif
// AA = true, SW has no effect, Clip NXNY, all corners
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY , ORIGIN_RECT_CLIP_NXNY , true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY , 0, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r0_aa_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// TODO: Remove these #ifdefs in PBL-15916 when support for non-antialiased rounded rect
#if SCREEN_COLOR_DEPTH_BITS == 8
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY , ORIGIN_RECT_CLIP_NXNY , true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY , 1, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r1_aa_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY , ORIGIN_RECT_CLIP_NXNY , true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY , 2, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r2_aa_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY , ORIGIN_RECT_CLIP_NXNY , true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY , 3, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_r3_aa_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY , ORIGIN_RECT_CLIP_NXNY , true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY , ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_aa_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY , ORIGIN_RECT_CLIP_NXNY , true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY , ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2), GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax1_aa_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Should draw same as rmax1
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY , ORIGIN_RECT_CLIP_NXNY , true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_CLIP_NXNY , ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) + 1, GCornersAll);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax2_aa_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif
}
void test_graphics_fill_rect_${BIT_DEPTH_NAME}__origin_corners(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// TODO: Currently prv_fill_rect only supports up to radius 8, fix in PBL-15916 to support
// arbitrary radius values - this affects all tests here
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornersBottom);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_bottom.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornersTop);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_top.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornersLeft);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_left.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornersRight);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_right.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornerTopLeft);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_topleft.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornerTopRight);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_topright.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornerBottomLeft);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_bottomleft.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornerBottomRight);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_bottomright.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_fill_rect_${BIT_DEPTH_NAME}__origin_aa_corners(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
// TODO: Remove these #ifdefs in PBL-15916 when support for non-antialiased rounded rect
#if SCREEN_COLOR_DEPTH_BITS == 8
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornersBottom);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_aa_bottom.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornersTop);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_aa_top.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornersLeft);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_aa_left.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornersRight);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_aa_right.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornerTopLeft);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_aa_topleft.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornerTopRight);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_aa_topright.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornerBottomLeft);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_aa_bottomleft.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, 1);
graphics_fill_round_rect(&ctx, &ORIGIN_DRAW_RECT_NO_CLIP, ((MIN(RECT_WIDTH, RECT_HEIGHT)) / 2) - 1, GCornerBottomRight);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_origin_rmax_aa_bottomright.${BIT_DEPTH_NAME}.pbi"));
#endif
}
extern uint16_t prv_clamp_corner_radius(GSize size, GCornerMask corner_mask,
uint16_t radius);
void test_graphics_fill_rect__corner_radius(void) {
// Test 0 radius cases
cl_assert_equal_i(prv_clamp_corner_radius(GSize(0, 0), GCornerNone, 0), 0);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(0, 0), GCornerNone, 2), 0);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(0, 0), GCornersAll, 0), 0);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(0, 0), GCornersAll, 8), 0);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(0, 5), GCornersAll, 8), 0);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(4, 0), GCornersAll, 8), 0);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(4, 8), GCornerNone, 8), 0);
// Test minimum of width and height is taken as radius
cl_assert_equal_i(prv_clamp_corner_radius(GSize(4, 8), GCornersAll, 4), 2);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(1, 10), GCornersAll, 4), 0);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(2, 10), GCornersAll, 4), 1);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(3, 10), GCornersAll, 4), 1);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(4, 10), GCornersAll, 4), 2);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(5, 10), GCornersAll, 4), 2);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(6, 10), GCornersAll, 4), 3);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(7, 10), GCornersAll, 4), 3);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(8, 10), GCornersAll, 4), 4);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(9, 10), GCornersAll, 4), 4);
cl_assert_equal_i(prv_clamp_corner_radius(GSize(10, 10), GCornersAll, 4), 4);
}
#define BOX_SIZE 10
#define CLIP_RECT_DRAW_BOX GRect(10, 10, 140, 30)
#define CLIP_RECT_CLIP_BOX GRect(10, 10, 120, 2*BOX_SIZE + 4)
#define CLIP_RECT_RECT_BOX GRect(0, 0, BOX_SIZE, BOX_SIZE)
#define CLIP_OFFSET 40
// yoffset if used to allow for multiple test cases to be drawn and tested on the same test image
// nudge is used to move the rectangle just outside the clipping box on each corner
static void prv_fill_rects(GContext *ctx, uint16_t yoffset, int16_t nudge, int16_t radius) {
// Adjust drawing box and clipping box
ctx->draw_state.drawing_box = CLIP_RECT_DRAW_BOX;
ctx->draw_state.drawing_box.origin.y += yoffset;
ctx->draw_state.clip_box = CLIP_RECT_CLIP_BOX;
ctx->draw_state.clip_box.origin.y += yoffset;
// Top left corner of clipping box
GRect box = CLIP_RECT_RECT_BOX;
box.origin.x -= nudge;
box.origin.y -= nudge;
graphics_fill_round_rect(ctx, &box, radius, GCornersAll);
// Top right corner of clipping box
box.origin.x = CLIP_RECT_CLIP_BOX.size.w - BOX_SIZE;
box.origin.x += nudge;
graphics_fill_round_rect(ctx, &box, radius, GCornersAll);
// Bottom right corner of clipping box
box.origin.y = CLIP_RECT_CLIP_BOX.size.h - BOX_SIZE;
box.origin.y += nudge;
graphics_fill_round_rect(ctx, &box, radius, GCornersAll);
// Bottom left corner of clipping box
box.origin.x = 0;
box.origin.x -= nudge;
graphics_fill_round_rect(ctx, &box, radius, GCornersAll);
}
void test_graphics_fill_rect_${BIT_DEPTH_NAME}__clipping_rect(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, false, 1);
graphics_context_set_stroke_color(&ctx, GColorBlack);
// Fills rectangles around boundaries of clipping box - AA false
prv_fill_rects(&ctx, 0, 0, 0);
prv_fill_rects(&ctx, CLIP_OFFSET, 1, 0);
prv_fill_rects(&ctx, 2 * CLIP_OFFSET, 0, 4);
prv_fill_rects(&ctx, 3 * CLIP_OFFSET, 1, 4);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_clip_rect.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_fill_rect_${BIT_DEPTH_NAME}__clipping_rect_aa(void) {
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, CLIP_RECT_CLIP_BOX, CLIP_RECT_DRAW_BOX, true, 1);
graphics_context_set_stroke_color(&ctx, GColorBlack);
// Fills rectangles around boundaries of clipping box - AA true
prv_fill_rects(&ctx, 0, 0, 0);
prv_fill_rects(&ctx, CLIP_OFFSET, 1, 0);
prv_fill_rects(&ctx, 2 * CLIP_OFFSET, 0, 4);
prv_fill_rects(&ctx, 3 * CLIP_OFFSET, 1, 4);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "fill_rect_clip_rect_aa.${BIT_DEPTH_NAME}.pbi"));
}
@@ -0,0 +1,459 @@
/*
* 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 "clar.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/gpath.h"
#include "util/trig.h"
#include "applib/ui/ui.h"
#include <string.h>
// Helper Functions
////////////////////////////////////
#include "util.h"
#include "test_graphics.h"
#include "${BIT_DEPTH_NAME}/test_framebuffer.h"
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
static const int16_t SCREEN_WIDTH = 144;
static const int16_t SCREEN_HEIGHT = 168;
static FrameBuffer *fb = NULL;
static const GPathInfo s_house_path_info = {
.num_points = 11,
.points = (GPoint []) {
{-40, 0}, {0, -40}, {40, 0}, {28, 0}, {28, 40}, {10, 40},
{10, 16}, {-10, 16}, {-10, 40}, {-28, 40}, {-28, 0},
},
};
static const GPathInfo s_bolt_path_info = {
.num_points = 6,
.points = (GPoint []) {{21, 0}, {14, 26}, {28, 26}, {7, 60}, {14, 34}, {0, 34}}
};
static const GPathInfo s_duplicates_path_info = {
6,
(GPoint[]) {
{40, 0},
{40, 0},
{0, 40},
{0, 40},
{80, 40},
{80, 40}
}
};
static const GPathInfo s_single_duplicate_path_info = {
2,
(GPoint[]) {
{40, 0},
{40, 0}
}
};
static const GPathInfo s_crossing_path_info = {
6,
(GPoint[]) {
{0, 40},
{20, 20},
{60, 60},
{80, 40},
{60, 20},
{20, 60}
}
};
static const GPathInfo s_infinite_path_info = {
16,
(GPoint[]) {
{-50, 0},
{-50, -60},
{10, -60},
{10, -20},
{-10, -20},
{-10, -40},
{-30, -40},
{-30, -20},
{50, -20},
{50, 40},
{-10, 40},
{-10, 0},
{10, 0},
{10, 20},
{30, 20},
{30, 0}
}
};
static const GPathInfo s_aa_clipping_path_info = {
.num_points = 4,
.points = (GPoint []) {{0,0}, {200, 0}, {200, 30}, {0, 30}}
};
static bool s_outline_mode = false;
static int s_path_angle = 0;
static GPath *s_house_path = NULL;
static GPath *s_bolt_path = NULL;
static GPath *s_duplicates_path = NULL;
static GPath *s_single_duplicate_path = NULL;
static GPath *s_crossing_path = NULL;
static GPath *s_infinite_path = NULL;
static GPath *s_current_path = NULL;
static GPath *s_aa_clipping_path = NULL;
static void prv_filled_update_proc(Layer *layer, GContext *ctx) {
graphics_context_set_stroke_color(ctx, GColorBlack);
#if 0 // Guidelines
int num_segments = 4;
int segment_width = SCREEN_WIDTH / num_segments;
int segment_height = SCREEN_HEIGHT / num_segments;
for (int i = 1; i < num_segments; ++i) {
graphics_draw_line(ctx,
GPoint(segment_width * i, 0),
GPoint(segment_width * i, SCREEN_HEIGHT));
graphics_draw_line(ctx,
GPoint(0, segment_height * i),
GPoint(SCREEN_WIDTH, segment_height * i));
}
#endif
gpath_rotate_to(s_current_path, s_path_angle * TRIG_MAX_ANGLE / 360);
if (s_outline_mode) {
graphics_context_set_stroke_color(ctx, GColorBlack);
gpath_draw_outline(ctx, s_current_path);
} else {
graphics_context_set_fill_color(ctx, GColorBlack);
gpath_draw_filled(ctx, s_current_path);
}
}
static void prv_reset(void) {
gpath_move_to(s_house_path, GPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
s_outline_mode = false;
s_path_angle = 0;
}
// setup and teardown
void test_graphics_gpath_${BIT_DEPTH_NAME}__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
s_house_path = gpath_create(&s_house_path_info);
s_bolt_path = gpath_create(&s_bolt_path_info);
s_duplicates_path = gpath_create(&s_duplicates_path_info);
s_crossing_path = gpath_create(&s_crossing_path_info);
s_infinite_path = gpath_create(&s_infinite_path_info);
s_aa_clipping_path = gpath_create(&s_aa_clipping_path_info);
prv_reset();
}
void test_graphics_gpath_${BIT_DEPTH_NAME}__cleanup(void) {
free(fb);
gpath_destroy(s_house_path);
gpath_destroy(s_bolt_path);
gpath_destroy(s_duplicates_path);
gpath_destroy(s_infinite_path);
gpath_destroy(s_crossing_path);
gpath_destroy(s_aa_clipping_path);
}
// tests
void test_graphics_gpath_${BIT_DEPTH_NAME}__filled(void) {
GContext ctx;
Layer layer;
prv_reset();
s_current_path = s_house_path;
test_graphics_context_init(&ctx, fb);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_gpath_${BIT_DEPTH_NAME}__filled_clipped(void) {
GContext ctx;
Layer layer;
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_house_path;
ctx.draw_state.clip_box = GRect(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"gpath_filled_top_clipped.${BIT_DEPTH_NAME}.pbi"));
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_house_path;
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"gpath_filled_bottom_clipped.${BIT_DEPTH_NAME}.pbi"));
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_house_path;
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"gpath_filled_left_clipped.${BIT_DEPTH_NAME}.pbi"));
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_house_path;
ctx.draw_state.clip_box = GRect(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"gpath_filled_right_clipped.${BIT_DEPTH_NAME}.pbi"));
}
// outside with no clipping -- results should be identical to the regular filled test
void test_graphics_gpath_${BIT_DEPTH_NAME}__filled_outside(void) {
GContext ctx;
Layer layer;
printf("-- top\n");
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_house_path;
gpath_move_to(s_house_path, GPoint(SCREEN_WIDTH / 2, 0));
ctx.draw_state.drawing_box = GRect(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT);
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled.${BIT_DEPTH_NAME}.pbi"));
printf("-- bottom\n");
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_house_path;
gpath_move_to(s_house_path, GPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT));
ctx.draw_state.drawing_box = GRect(0, -SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT);
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled.${BIT_DEPTH_NAME}.pbi"));
printf("-- left\n");
prv_reset();
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_house_path;
gpath_move_to(s_house_path, GPoint(0, SCREEN_HEIGHT / 2));
ctx.draw_state.drawing_box = GRect(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled.${BIT_DEPTH_NAME}.pbi"));
printf("-- right\n");
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_house_path;
gpath_move_to(s_house_path, GPoint(SCREEN_WIDTH, SCREEN_HEIGHT / 2));
ctx.draw_state.drawing_box = GRect(-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled.${BIT_DEPTH_NAME}.pbi"));
}
// AA section
void test_graphics_gpath_${BIT_DEPTH_NAME}__filled_aa(void) {
GContext ctx;
Layer layer;
// House path - tests horizontal line edge case
prv_reset();
s_current_path = s_house_path;
test_graphics_context_init(&ctx, fb);
graphics_context_set_antialiased(&ctx, true);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled_aa.${BIT_DEPTH_NAME}.pbi"));
// Special case for two points that are duplicates...
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_single_duplicate_path;
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
graphics_context_set_antialiased(&ctx, true);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"gpath_filled_single_duplicate_aa.${BIT_DEPTH_NAME}.pbi"));
}
void test_graphics_gpath_${BIT_DEPTH_NAME}__filled_clipped_aa(void) {
GContext ctx;
Layer layer;
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_house_path;
ctx.draw_state.clip_box = GRect(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
graphics_context_set_antialiased(&ctx, true);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"gpath_filled_top_clipped_aa.${BIT_DEPTH_NAME}.pbi"));
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_house_path;
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
graphics_context_set_antialiased(&ctx, true);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"gpath_filled_bottom_clipped_aa.${BIT_DEPTH_NAME}.pbi"));
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_house_path;
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
graphics_context_set_antialiased(&ctx, true);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"gpath_filled_left_clipped_aa.${BIT_DEPTH_NAME}.pbi"));
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_house_path;
ctx.draw_state.clip_box = GRect(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT);
graphics_context_set_antialiased(&ctx, true);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"gpath_filled_right_clipped_aa.${BIT_DEPTH_NAME}.pbi"));
}
// Additional test to check AA on edges - works only on 8bit
void test_graphics_gpath_8bit__filled_bolt_aa(void) {
// NOTE: Those tests are being performed only in 8bits due to differences caused by AA,
// performing them in both 1bit and 8bit would create differences on the edges
// and fail unit tests as a result
GContext ctx;
Layer layer;
// Bolt path - test antialiased edges
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_bolt_path;
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
graphics_context_set_antialiased(&ctx, true);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"gpath_filled_bolt_aa.8bit.pbi"));
// Duplicate points in GPath test - makes sure theres no division by zero ;)
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_duplicates_path;
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
graphics_context_set_antialiased(&ctx, true);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"gpath_filled_duplicates_aa.8bit.pbi"));
// Crossing path - makes sure algorithm works for path that crosses itself
prv_reset();
s_current_path = s_crossing_path;
test_graphics_context_init(&ctx, fb);
graphics_context_set_antialiased(&ctx, true);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled_crossing_aa.8bit.pbi"));
// Infinite path - shows an example where path seems to be crossing itself but
// in fact it does not
prv_reset();
s_current_path = s_infinite_path;
gpath_move_to(s_infinite_path, GPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
test_graphics_context_init(&ctx, fb);
graphics_context_set_antialiased(&ctx, true);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled_infinite_aa.8bit.pbi"));
// An angle of infinite path - here we see the spacing between the parts
prv_reset();
s_current_path = s_infinite_path;
gpath_move_to(s_infinite_path, GPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
s_path_angle = 45;
test_graphics_context_init(&ctx, fb);
graphics_context_set_antialiased(&ctx, true);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled_infinite_45_aa.8bit.pbi"));
// Another angle of infinite path
prv_reset();
s_current_path = s_infinite_path;
gpath_move_to(s_infinite_path, GPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
s_path_angle = 70;
test_graphics_context_init(&ctx, fb);
graphics_context_set_antialiased(&ctx, true);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled_infinite_70_aa.8bit.pbi"));
// House path - two edge cases for tipping points of the path
prv_reset();
s_current_path = s_house_path;
gpath_move_to(s_house_path, GPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
s_path_angle = 20;
test_graphics_context_init(&ctx, fb);
graphics_context_set_antialiased(&ctx, true);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled_house_20_aa.8bit.pbi"));
// This case demonstrates tipping point that is also the starting point of the path
prv_reset();
s_current_path = s_house_path;
gpath_move_to(s_house_path, GPoint(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2));
s_path_angle = 105;
test_graphics_context_init(&ctx, fb);
graphics_context_set_antialiased(&ctx, true);
prv_filled_update_proc(NULL, &ctx);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "gpath_filled_house_105_aa.8bit.pbi"));
// Safety
s_path_angle = 0;
}
void test_graphics_gpath_8bit__clipping_aa(void) {
// NOTE: This test verifies correct clipping of anti-aliased edges on gpaths, therefore
// it works only on 8bit
GContext ctx;
Layer layer;
prv_reset();
test_graphics_context_init(&ctx, fb);
s_current_path = s_aa_clipping_path;
s_path_angle = 17;
ctx.draw_state.clip_box = GRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
graphics_context_set_antialiased(&ctx, true);
prv_filled_update_proc(NULL, &ctx);
// NOTE: Expected result of this test is to have an antialiased stripe go through the screen,
// where antialiased edges are being nicely cut off on top and bottom of the stripe
// (antialiased gradient would dive into the stripe near screen edges), also top
// left corner is intentinally ending just before screen cuts it out to verify that
// fractional anti-aliasing is not bleeding into row before (would occur as pixels on
// right side of the screen)
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap,
"gpath_clipping_aa.8bit.pbi"));
// Safety
s_path_angle = 0;
}
@@ -0,0 +1,306 @@
/*
* 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/graphics/gtransform.h"
#include "util/trig.h"
#include "util/math_fixed.h"
#include "clar.h"
#include <stdio.h>
#include <string.h>
// Helper Functions
////////////////////////////////////
// Stubs
////////////////////////////////////
#include "stubs_app_state.h"
#include "stubs_applib_resource.h"
#include "stubs_compiled_with_legacy2_sdk.h"
#include "stubs_heap.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_pebble_tasks.h"
#include "stubs_print.h"
#include "stubs_resources.h"
#include "stubs_serial.h"
#include "stubs_syscalls.h"
#include "stubs_ui_window.h"
#include "stubs_unobstructed_area.h"
// Tests
////////////////////////////////////
/////////////////////////////////
/// Generic matrix tests
/////////////////////////////////
void test_graphics_gtransform_${BIT_DEPTH_NAME}__types_gtransformnumber(void) {
GTransform t_c; // matrix to compare against
GTransformNumber tn;
int32_t test_num;
tn = GTransformNumberFromNumber(1);
test_num = (int32_t)((float)1 * (1 << FIXED_S32_16_PRECISION));
cl_assert((memcmp(&tn, &test_num, sizeof(GTransformNumber)) == 0));
tn = GTransformNumberFromNumber(3.5);
test_num = (int32_t)((float)3.5 * (1 << FIXED_S32_16_PRECISION));
cl_assert((memcmp(&tn, &test_num, sizeof(GTransformNumber)) == 0));
tn = GTransformNumberFromNumber(-2);
test_num = (int32_t)((float)-2 * (1 << FIXED_S32_16_PRECISION));
cl_assert((memcmp(&tn, &test_num, sizeof(GTransformNumber)) == 0));
tn = GTransformNumberFromNumber(-3.5);
test_num = (int32_t)((float)-3.5 * (1 << FIXED_S32_16_PRECISION));
cl_assert((memcmp(&tn, &test_num, sizeof(GTransformNumber)) == 0));
t_c = GTransform(GTransformNumberFromNumber(1), GTransformNumberFromNumber(2),
GTransformNumberFromNumber(3), GTransformNumberFromNumber(4),
GTransformNumberFromNumber(5), GTransformNumberFromNumber(6));
int32_t test_array[6] = {1 * (1 << FIXED_S32_16_PRECISION),
2 * (1 << FIXED_S32_16_PRECISION),
3 * (1 << FIXED_S32_16_PRECISION),
4 * (1 << FIXED_S32_16_PRECISION),
5 * (1 << FIXED_S32_16_PRECISION),
6 * (1 << FIXED_S32_16_PRECISION)};
cl_assert((memcmp(&t_c, &test_array, sizeof(GTransform)) == 0));
cl_assert(gtransform_is_equal(&t_c, (const GTransform*)&test_array));
t_c = GTransformFromNumbers(1, 2, 3, 4, 5, 6);
cl_assert(gtransform_is_equal(&t_c, (const GTransform*)&test_array));
// Test to make sure implemented rotation calculation is correct
int32_t angle = DEG_TO_TRIGANGLE(45);
int32_t cosine = cos_lookup(angle);
GTransformNumber num = GTransformNumberFromNumber((float)cosine / TRIG_MAX_RATIO);
GTransformNumber num2 =
(GTransformNumber) { .raw_value = (int32_t)(((int64_t)cosine *
GTransformNumberOne.raw_value) / TRIG_MAX_RATIO) };
cl_assert(num.raw_value == num2.raw_value);
}
void test_graphics_gtransform_${BIT_DEPTH_NAME}__types_precise(void) {
GPointPrecise pointP = GPointPreciseFromGPoint(GPoint(2, 5));
GPointPrecise pointP_c = GPointPrecise((2 % GPOINT_PRECISE_MAX) << GPOINT_PRECISE_PRECISION,
(5 % GPOINT_PRECISE_MAX) << GPOINT_PRECISE_PRECISION);
cl_assert(gpointprecise_equal(&pointP, &pointP_c));
GVectorPrecise vectorP = GVectorPreciseFromGVector(GVector(2, 5));
GVectorPrecise vectorP_c = GVectorPrecise((2 % GVECTOR_PRECISE_MAX) << GVECTOR_PRECISE_PRECISION,
(5 % GVECTOR_PRECISE_MAX) << GVECTOR_PRECISE_PRECISION);
cl_assert(gvectorprecise_equal(&vectorP, &vectorP_c));
}
void test_graphics_gtransform_${BIT_DEPTH_NAME}__init(void) {
GTransform t;
GTransform t_c; // matrix to compare against
// Test Identity Matrix
t = GTransformIdentity();
t_c = GTransformFromNumbers(1, 0, 0, 1, 0, 0);
cl_assert(gtransform_is_equal(&t, &t_c));
cl_assert(gtransform_is_identity(&t));
cl_assert(gtransform_is_identity(&t_c));
// Test Scale Matrix
t = GTransformScale(GTransformNumberFromNumber(2), GTransformNumberFromNumber(5));
t_c = GTransformFromNumbers(2, 0, 0, 5, 0, 0);
cl_assert(gtransform_is_equal(&t, &t_c));
cl_assert(gtransform_is_only_scale(&t));
cl_assert(gtransform_is_only_scale(&t_c));
t_c = GTransformScaleFromNumber(2, 5);
cl_assert(gtransform_is_equal(&t, &t_c));
// Test Translation Matrix
t = GTransformTranslation(GTransformNumberFromNumber(2), GTransformNumberFromNumber(5));
t_c = GTransformFromNumbers(1, 0, 0, 1, 2, 5);
cl_assert(gtransform_is_equal(&t, &t_c));
cl_assert(gtransform_is_only_translation(&t));
cl_assert(gtransform_is_only_translation(&t_c));
t_c = GTransformTranslationFromNumber(2, 5);
cl_assert(gtransform_is_equal(&t, &t_c));
// Test Rotation Matrix
int32_t angle = DEG_TO_TRIGANGLE(45);
t = GTransformRotation(angle);
int32_t cosine = cos_lookup(angle);
int32_t sine = sin_lookup(angle);
t_c = GTransform(GTransformNumberFromNumber((float)cosine / TRIG_MAX_RATIO),
GTransformNumberFromNumber(-(float)sine / TRIG_MAX_RATIO),
GTransformNumberFromNumber((float)sine / TRIG_MAX_RATIO),
GTransformNumberFromNumber((float)cosine / TRIG_MAX_RATIO),
GTransformNumberZero,
GTransformNumberZero);
cl_assert(gtransform_is_equal(&t, &t_c));
angle = DEG_TO_TRIGANGLE(46);
cosine = cos_lookup(angle);
sine = sin_lookup(angle);
t_c = GTransform(GTransformNumberFromNumber((float)cosine / TRIG_MAX_RATIO),
GTransformNumberFromNumber(-(float)sine / TRIG_MAX_RATIO),
GTransformNumberFromNumber((float)sine / TRIG_MAX_RATIO),
GTransformNumberFromNumber((float)cosine / TRIG_MAX_RATIO),
GTransformNumberZero,
GTransformNumberZero);
cl_assert(!gtransform_is_equal(&t, &t_c));
t = GTransformRotation(0); // Should return identity if angle == 0
cl_assert(gtransform_is_identity(&t));
}
void test_graphics_gtransform_${BIT_DEPTH_NAME}__concat(void) {
GTransform t_new;
GTransform t1;
GTransform t2;
GTransform t_c; // matrix to compare against
// Test identity concatenation
t1 = GTransformIdentity();
t2 = GTransformIdentity();
t_c = GTransformIdentity();
gtransform_concat(&t_new, &t1, &t2);
cl_assert(gtransform_is_equal(&t_new, &t_c));
// Test identity concatenation with non-identity
t1 = GTransformFromNumbers(1, 2, 3, 4, 5, 6);
t2 = GTransformIdentity();
t_c = GTransformFromNumbers(1, 2, 3, 4, 5, 6);
gtransform_concat(&t_new, &t1, &t2);
cl_assert(gtransform_is_equal(&t_new, &t_c));
// Test pointer re-use
gtransform_concat(&t2, &t1, &t2);
cl_assert(gtransform_is_equal(&t2, &t_c));
// Test non-identity concatenation with identity
t1 = GTransformIdentity();
t2 = GTransformFromNumbers(1, 2, 3, 4, 5, 6);
t_c = GTransformFromNumbers(1, 2, 3, 4, 5, 6);
gtransform_concat(&t_new, &t1, &t2);
cl_assert(gtransform_is_equal(&t_new, &t_c));
// Test pointer re-use
gtransform_concat(&t1, &t1, &t2);
cl_assert(gtransform_is_equal(&t1, &t_c));
// Test concatenation of two non-identity matrices
t1 = GTransformFromNumbers(3, 5, 7, 11, 13, 17);
t2 = GTransformFromNumbers(1, 2, 3, 4, 5, 6);
t_c = GTransformFromNumbers(18, 26, 40, 58, 69, 100);
gtransform_concat(&t_new, &t1, &t2);
cl_assert(gtransform_is_equal(&t_new, &t_c));
}
void test_graphics_gtransform_${BIT_DEPTH_NAME}__scale(void) {
GTransform t_new;
GTransform t1;
GTransform t2;
GTransform t_c; // matrix to compare against
// Test scaling
t1 = GTransformFromNumbers(1, 2, 3, 4, 5, 6);
t2 = GTransformFromNumbers(1, 2, 3, 4, 5, 6);
t_c = GTransformFromNumbers(10, 20, 600, 800, 5, 6);
gtransform_scale(&t_new, &t1, GTransformNumberFromNumber(10), GTransformNumberFromNumber(200));
cl_assert(gtransform_is_equal(&t_new, &t_c));
cl_assert(gtransform_is_equal(&t1, &t2)); // ensure t1 has not changed
gtransform_scale_number(&t_new, &t1, 10, 200);
cl_assert(gtransform_is_equal(&t_new, &t_c));
cl_assert(gtransform_is_equal(&t1, &t2)); // ensure t1 has not changed
// Test pointer re-use
gtransform_scale(&t1, &t1, GTransformNumberFromNumber(10), GTransformNumberFromNumber(200));
cl_assert(gtransform_is_equal(&t1, &t_c));
}
void test_graphics_gtransform_${BIT_DEPTH_NAME}__translation(void) {
GTransform t_new;
GTransform t1;
GTransform t2;
GTransform t_c; // matrix to compare against
// Test translation
t1 = GTransformFromNumbers(1, 2, 3, 4, 5, 6);
t2 = GTransformFromNumbers(1, 2, 3, 4, 5, 6);
t_c = GTransformFromNumbers(1, 2, 3, 4, 615, 826);
gtransform_translate(&t_new, &t1,
GTransformNumberFromNumber(10), GTransformNumberFromNumber(200));
cl_assert(gtransform_is_equal(&t_new, &t_c));
cl_assert(gtransform_is_equal(&t1, &t2)); // ensure t1 has not changed
gtransform_translate_number(&t_new, &t1, 10, 200);
cl_assert(gtransform_is_equal(&t_new, &t_c));
cl_assert(gtransform_is_equal(&t1, &t2)); // ensure t1 has not changed
// Test pointer re-use
gtransform_translate(&t1, &t1, GTransformNumberFromNumber(10), GTransformNumberFromNumber(200));
cl_assert(gtransform_is_equal(&t1, &t_c));
}
void test_graphics_gtransform_${BIT_DEPTH_NAME}__rotation(void) {
GTransform t_new;
GTransform t1;
GTransform t2;
GTransform t_c; // matrix to compare against
// Test rotation
t1 = GTransformFromNumbers(10, 10, 10, 10, 10, 10);
t2 = GTransformFromNumbers(10, 10, 10, 10, 10, 10);
// Initialize a, b, c, and d based on the expected result
// a = b = 10*cos(45) - 10*sin(45)
// c = d = 10*sin(45) + 10*cos(45)
t_c = GTransform(GTransformNumberFromNumber(0),
GTransformNumberFromNumber(0),
(Fixed_S32_16){ .raw_value = (int32_t)(923960) },
(Fixed_S32_16){ .raw_value = (int32_t)(923960) },
GTransformNumberFromNumber(10),
GTransformNumberFromNumber(10));
gtransform_rotate(&t_new, &t1, DEG_TO_TRIGANGLE(45));
cl_assert(gtransform_is_equal(&t_new, &t_c));
cl_assert(gtransform_is_equal(&t1, &t2)); // ensure t1 has not changed
// Test pointer re-use
gtransform_rotate(&t1, &t1, DEG_TO_TRIGANGLE(45));
cl_assert(gtransform_is_equal(&t1, &t_c));
}
+64
View File
@@ -0,0 +1,64 @@
/*
* 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.
*/
#pragma once
#include "applib/graphics/gtypes.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/graphics_private_raw_mask.h"
#include "applib/graphics/framebuffer.h"
#include "util/graphics.h"
static uint8_t test_graphics_context_mask_get_value_for_coordinate(const GContext *ctx,
const GDrawMask *mask,
GPoint p) {
const uint8_t mask_pixel_value = graphics_private_raw_mask_get_value(ctx, mask, p);
cl_assert(WITHIN(mask_pixel_value, 0, (1 << (GDRAWMASK_BITS_PER_PIXEL)) - 1));
return mask_pixel_value;
}
static void test_graphics_context_mask_set_value_for_coordinate(GContext *ctx, GDrawMask *mask,
uint8_t value, GPoint p) {
cl_assert(WITHIN(value, 0, (1 << (GDRAWMASK_BITS_PER_PIXEL)) - 1));
graphics_private_raw_mask_set_value(ctx, mask, p, value);
}
static void test_graphics_context_mask_debug(GContext *ctx, GDrawMask *mask) {
GBitmap *framebuffer_bitmap = &ctx->dest_bitmap;
// Naive approach
for (int y = 0; y < framebuffer_bitmap->bounds.size.h; y++) {
const GBitmapDataRowInfo current_data_row_info = gbitmap_get_data_row_info(framebuffer_bitmap,
y);
// Iterate over the relevant mask row pixel values and use each to draw our debug image
for (int x = current_data_row_info.min_x; x <= current_data_row_info.max_x; x++) {
const uint8_t mask_pixel_value =
test_graphics_context_mask_get_value_for_coordinate(ctx, mask, GPoint(x, y));
const GColor color_lookup[1 << GDRAWMASK_BITS_PER_PIXEL] = {
GColorBlack,
GColorDarkGray,
GColorLightGray,
GColorWhite,
};
const GColor pixel_color = color_lookup[mask_pixel_value];
current_data_row_info.data[x] = pixel_color.argb;
}
}
}
@@ -0,0 +1,286 @@
/*
* 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 <stdio.h>
// 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_stroke_circle_${BIT_DEPTH_NAME}__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) {DISP_COLS, DISP_ROWS});
}
// Teardown
void test_graphics_stroke_circle_${BIT_DEPTH_NAME}__cleanup(void) {
free(fb);
}
// Tests
////////////////////////////////////
#define RADIUS_BIG 15
#define RADIUS_MEDIUM 8
#define RADIUS_MIN_CALCULATED 3
#define RADIUS_MAX_PRECOMPUTED 2
#define RADIUS_SMALL 1
#define RADIUS_NONE 0
#define STROKE_BIG 10
#define STROKE_SMALL 5
#define STROKE_THREE 3 // In case of fluctuation
#define ORIGIN_RECT_NO_CLIP GRect(0, 0, 40, 50)
#define ORIGIN_RECT_CLIP_XY GRect(0, 0, 30, 40)
#define ORIGIN_RECT_CLIP_NXNY GRect(0, 0, 30, 40)
#define CENTER_OF_ORIGIN_RECT GPoint(20, 25)
#define CENTER_OF_ORIGIN_RECT_NXNY GPoint(10, 15)
void test_graphics_stroke_circle_${BIT_DEPTH_NAME}__origin_layer(void) {
#if SCREEN_COLOR_DEPTH_BITS == 8
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Big circles
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_BIG);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_origin_aa_r16_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, STROKE_BIG);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_origin_aa_r16_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, STROKE_BIG);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_origin_aa_r16_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Medium circles
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_SMALL);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_origin_aa_r8_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, STROKE_SMALL);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_origin_aa_r8_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, STROKE_SMALL);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_origin_aa_r8_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Small circles
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_SMALL);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_origin_aa_r1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_XY, ORIGIN_RECT_CLIP_XY, true, STROKE_SMALL);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_origin_aa_r1_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_CLIP_NXNY, ORIGIN_RECT_CLIP_NXNY, true, STROKE_SMALL);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_origin_aa_r1_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Testing of the special cases for radius:
// Radius of 3 - starting point for calculated edges
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_THREE);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_MIN_CALCULATED);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_origin_aa_r3_no_clip.${BIT_DEPTH_NAME}.pbi"));
// Radius of 2 - ending point for precomputed edges
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_THREE);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_MAX_PRECOMPUTED);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_origin_aa_r2_no_clip.${BIT_DEPTH_NAME}.pbi"));
// No circle
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_THREE);
graphics_draw_circle(&ctx, CENTER_OF_ORIGIN_RECT_NXNY, RADIUS_NONE);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_origin_aa_r0_no_clip.${BIT_DEPTH_NAME}.pbi"));
#endif // SCREEN_COLOR_DEPTH_BITS == 8
}
#define OFFSET_RECT_NO_CLIP GRect(10, 10, 40, 50)
#define OFFSET_RECT_CLIP_XY GRect(10, 10, 30, 40)
#define OFFSET_RECT_CLIP_NXNY GRect(0, 0, 30, 40)
#define CENTER_OF_OFFSET_RECT GPoint(10, 15)
#define CENTER_OF_OFFSET_RECT_NXNY GPoint(0, 5)
void test_graphics_stroke_circle_${BIT_DEPTH_NAME}__offset_layer_aa(void) {
#if SCREEN_COLOR_DEPTH_BITS == 8
GContext ctx;
test_graphics_context_init(&ctx, fb);
// Big circles
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, STROKE_BIG);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r16_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, STROKE_BIG);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r16_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, STROKE_BIG);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT_NXNY, RADIUS_BIG);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r16_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Medium circles
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, STROKE_SMALL);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r8_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, STROKE_SMALL);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r8_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, STROKE_SMALL);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT_NXNY, RADIUS_MEDIUM);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r8_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
// Small circles
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_NO_CLIP, OFFSET_RECT_NO_CLIP, true, STROKE_SMALL);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r1_no_clip.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_XY, OFFSET_RECT_CLIP_XY, true, STROKE_SMALL);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r1_clip_xy.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, OFFSET_RECT_CLIP_NXNY, OFFSET_RECT_CLIP_NXNY, true, STROKE_SMALL);
graphics_draw_circle(&ctx, CENTER_OF_OFFSET_RECT_NXNY, RADIUS_SMALL);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r1_clip_nxny.${BIT_DEPTH_NAME}.pbi"));
#endif // SCREEN_COLOR_DEPTH_BITS == 8
}
extern void graphics_circle_quadrant_draw_stroked_non_aa(
GContext* ctx, GPoint p, uint16_t radius, uint8_t stroke_width,
GCornerMask quadrant);
void test_graphics_stroke_circle_${BIT_DEPTH_NAME}__quadrants(void) {
#if SCREEN_COLOR_DEPTH_BITS == 8
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornerTopLeft);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_r8_quad_top_left.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornerTopRight);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_r8_quad_top_right.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornerBottomLeft);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_r8_quad_bottom_left.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornerBottomRight);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_r8_quad_bottom_right.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornersTop);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_r8_quads_top.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornersBottom);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_r8_quads_bottom.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornersRight);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_r8_quads_right.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, false, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_non_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornersLeft);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_r8_quads_left.${BIT_DEPTH_NAME}.pbi"));
#endif // SCREEN_COLOR_DEPTH_BITS == 8
}
extern void graphics_circle_quadrant_draw_stroked_aa(
GContext* ctx, GPoint p, uint16_t radius, uint8_t stroke_width,
GCornerMask quadrant);
void test_graphics_draw_circle_${BIT_DEPTH_NAME}__quadrants_aa(void) {
#if SCREEN_COLOR_DEPTH_BITS == 8
GContext ctx;
test_graphics_context_init(&ctx, fb);
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornerTopLeft);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r8_quad_top_left.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornerTopRight);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r8_quad_top_right.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornerBottomLeft);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r8_quad_bottom_left.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornerBottomRight);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r8_quad_bottom_right.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornersTop);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r8_quads_top.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornersBottom);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r8_quads_bottom.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornersRight);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r8_quads_right.${BIT_DEPTH_NAME}.pbi"));
setup_test_aa_sw(&ctx, fb, ORIGIN_RECT_NO_CLIP, ORIGIN_RECT_NO_CLIP, true, STROKE_SMALL);
graphics_circle_quadrant_draw_stroked_aa(&ctx, CENTER_OF_ORIGIN_RECT, RADIUS_MEDIUM, STROKE_SMALL,GCornersLeft);
cl_check(gbitmap_pbi_eq(&ctx.dest_bitmap, "stroke_circle_offset_aa_r8_quads_left.${BIT_DEPTH_NAME}.pbi"));
#endif // SCREEN_COLOR_DEPTH_BITS == 8
}
+258
View File
@@ -0,0 +1,258 @@
/*
* 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 "apps/system_apps/timeline/text_node.h"
#include "clar.h"
#include "pebble_asserts.h"
// Stubs
/////////////////////
#include "stubs_app_state.h"
#include "stubs_heap.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_process_manager.h"
// TODO: PBL-22271 Complete timeline text node unit tests
// Fakes
/////////////////////
typedef struct GTextNodeTestData {
GContext gcontext;
GRect clip_box;
GTextNode *text_node;
GSize max_used_size;
} GTextNodeTestData;
GTextNodeTestData s_data;
#define TEST_TEXT "DUMMY TEXT"
#define TEST_FONT ((void *)0xf0a7f0a7)
#define TEST_TEXT_SIZE GSize(50, 18)
#define TEST_TEXT_BOX GRect(10, 10, 140, 200)
void graphics_draw_text(GContext *ctx, const char *text, GFont const font, const GRect box,
const GTextOverflowMode overflow_mode, const GTextAlignment alignment,
const GTextLayoutCacheRef layout_ref) {
GTextNodeText *text_node = (GTextNodeText *)s_data.text_node;
cl_assert(text_node);
cl_assert_equal_s(text_node->text, text);
cl_assert_equal_p(text_node->font, font);
cl_assert_equal_p(text_node->overflow, overflow_mode);
cl_assert_equal_p(text_node->alignment, alignment);
TextLayoutExtended *layout = (TextLayoutExtended *)layout_ref;
cl_assert_equal_p(text_node->line_spacing_delta, layout->line_spacing_delta);
layout->max_used_size = s_data.max_used_size;
}
GSize graphics_text_layout_get_max_used_size(GContext *ctx, const char *text,
GFont const font, const GRect box,
const GTextOverflowMode overflow_mode,
const GTextAlignment alignment,
GTextLayoutCacheRef layout_ref) {
GTextNodeText *text_node = (GTextNodeText *)s_data.text_node;
cl_assert(text_node);
cl_assert_equal_s(text_node->text, text);
cl_assert_equal_p(text_node->font, font);
cl_assert_equal_p(text_node->overflow, overflow_mode);
cl_assert_equal_p(text_node->alignment, alignment);
TextLayoutExtended *layout = (TextLayoutExtended *)layout_ref;
cl_assert_equal_p(text_node->line_spacing_delta, layout->line_spacing_delta);
layout->max_used_size = s_data.max_used_size;
return layout->max_used_size;
}
// Setup and Teardown
////////////////////////////////////
void test_graphics_text_node__initialize(void) {
s_data = (GTextNodeTestData) {};
}
void test_graphics_text_node__cleanup(void) {
}
// Tests
////////////////////////////////////
void test_graphics_text_node__text_draw(void) {
GContext *ctx = &s_data.gcontext;
GTextNodeText text_node = {};
text_node.text = (char *)TEST_TEXT;
text_node.font = TEST_FONT;
text_node.overflow = GTextOverflowModeTrailingEllipsis;
text_node.alignment = GTextAlignmentCenter;
s_data.text_node = &text_node.node;
s_data.max_used_size = TEST_TEXT_SIZE;
GSize size;
graphics_text_node_draw(&text_node.node, ctx, &TEST_TEXT_BOX, NULL, &size);
cl_assert(size.w > 0);
cl_assert(size.h > 0);
cl_assert_equal_i(size.w, TEST_TEXT_SIZE.w);
cl_assert_equal_i(size.h, TEST_TEXT_SIZE.h);
}
void test_graphics_text_node__text_size(void) {
GContext *ctx = &s_data.gcontext;
GTextNodeText text_node = {};
text_node.text = (char *)TEST_TEXT;
text_node.font = TEST_FONT;
text_node.overflow = GTextOverflowModeTrailingEllipsis;
text_node.alignment = GTextAlignmentCenter;
s_data.text_node = &text_node.node;
s_data.max_used_size = TEST_TEXT_SIZE;
GSize size;
graphics_text_node_get_size(&text_node.node, ctx, &TEST_TEXT_BOX, NULL, &size);
cl_assert(size.w > 0);
cl_assert(size.h > 0);
cl_assert_equal_i(size.w, TEST_TEXT_SIZE.w);
cl_assert_equal_i(size.h, TEST_TEXT_SIZE.h);
}
static int s_num_draw_custom_calls = 0;
static void prv_draw_custom(GContext *ctx, const GRect *box, const GTextNodeDrawConfig *config,
bool render, GSize *size_out, void *user_data) {
s_num_draw_custom_calls++;
*size_out = TEST_TEXT_SIZE;
}
void test_graphics_text_node__custom_cached_size(void) {
GContext *ctx = &s_data.gcontext;
GTextNodeCustom custom_node = {};
custom_node.node.type = GTextNodeType_Custom;
custom_node.callback = prv_draw_custom;
GSize size;
graphics_text_node_get_size(&custom_node.node, ctx, &TEST_TEXT_BOX, NULL, &size);
cl_assert(size.w > 0);
cl_assert(size.h > 0);
cl_assert_equal_i(size.w, TEST_TEXT_SIZE.w);
cl_assert_equal_i(size.h, TEST_TEXT_SIZE.h);
cl_assert_equal_i(size.w, custom_node.node.cached_size.w);
cl_assert_equal_i(size.h, custom_node.node.cached_size.h);
cl_assert_equal_i(s_num_draw_custom_calls, 1);
graphics_text_node_get_size(&custom_node.node, ctx, &TEST_TEXT_BOX, NULL, &size);
cl_assert(size.w > 0);
cl_assert(size.h > 0);
cl_assert_equal_i(size.w, TEST_TEXT_SIZE.w);
cl_assert_equal_i(size.h, TEST_TEXT_SIZE.h);
cl_assert_equal_i(size.w, custom_node.node.cached_size.w);
cl_assert_equal_i(size.h, custom_node.node.cached_size.h);
cl_assert_equal_i(s_num_draw_custom_calls, 1);
}
void test_graphics_text_node__create_container_nodes_buffer(void) {
GTextNodeHorizontal *h_empty = graphics_text_node_create_horizontal(0);
cl_assert_equal_i(h_empty->container.max_nodes, 0);
cl_assert_equal_p(NULL, h_empty->container.nodes);
GTextNodeHorizontal *h_nodes = graphics_text_node_create_horizontal(3);
cl_assert_equal_i(h_nodes->container.max_nodes, 3);
cl_assert_equal_p(h_nodes + 1, (void *)h_nodes->container.nodes);
GTextNodeVertical *v_empty = graphics_text_node_create_vertical(0);
cl_assert_equal_i(v_empty->container.max_nodes, 0);
cl_assert_equal_p(NULL, v_empty->container.nodes);
GTextNodeVertical *v_nodes = graphics_text_node_create_vertical(3);
cl_assert_equal_i(v_nodes->container.max_nodes, 3);
cl_assert_equal_p(v_nodes + 1, (void *)v_nodes->container.nodes);
}
void test_graphics_text_node__destroy(void) {
const char *str_a = "A";
GTextNodeText *text_a = graphics_text_node_create_text(strlen(str_a) + 1);
cl_assert(text_a->node.free_on_destroy);
strcpy((char *)text_a->text, str_a);
const char *str_b = task_strdup("B");
GTextNodeText *text_b = graphics_text_node_create_text(0);
cl_assert(text_b->node.free_on_destroy);
text_b->text = (char *)str_b;
const char *str_c = "C";
GTextNodeText text_c = {
.text = (char *)str_c,
};
cl_assert(!text_c.node.free_on_destroy);
GTextNodeCustom *custom_a = graphics_text_node_create_custom(NULL, NULL);
cl_assert(custom_a->node.free_on_destroy);
GTextNodeHorizontal *horizontal_a = graphics_text_node_create_horizontal(3);
cl_assert(horizontal_a->container.node.free_on_destroy);
cl_assert_equal_i(horizontal_a->container.max_nodes, 3);
cl_assert_equal_i(horizontal_a->container.num_nodes, 0);
cl_assert(graphics_text_node_container_add_child(&horizontal_a->container, &text_a->node));
cl_assert(graphics_text_node_container_add_child(&horizontal_a->container, &text_b->node));
cl_assert(graphics_text_node_container_add_child(&horizontal_a->container, &text_c.node));
cl_assert(!graphics_text_node_container_add_child(&horizontal_a->container, &custom_a->node));
cl_assert_equal_i(horizontal_a->container.num_nodes, 3);
GTextNodeVertical *vertical_a = graphics_text_node_create_vertical(2);
cl_assert(vertical_a->container.node.free_on_destroy);
cl_assert_equal_i(vertical_a->container.max_nodes, 2);
cl_assert_equal_i(vertical_a->container.num_nodes, 0);
cl_assert(graphics_text_node_container_add_child(&vertical_a->container, &horizontal_a->container.node));
cl_assert(graphics_text_node_container_add_child(&vertical_a->container, &custom_a->node));
cl_assert(!graphics_text_node_container_add_child(&vertical_a->container, &text_c.node));
cl_assert_equal_i(vertical_a->container.num_nodes, 2);
graphics_text_node_destroy(&vertical_a->container.node);
task_free((char *)str_b);
}
static void prv_draw_custom_clip(GContext *ctx, const GRect *box,
const GTextNodeDrawConfig *config, bool render, GSize *size_out,
void *user_data) {
cl_assert_equal_grect(ctx->draw_state.clip_box, s_data.clip_box);
}
#define TEST_CLIP_BOX GRect(10, 20, 30, 40)
void test_graphics_text_node__clip(void) {
GContext *ctx = &s_data.gcontext;
GTextNodeCustom custom_node = {};
custom_node.node.type = GTextNodeType_Custom;
custom_node.callback = prv_draw_custom_clip;
// Clipping off
s_data.gcontext.draw_state.clip_box = DISP_FRAME;
s_data.clip_box = DISP_FRAME;
graphics_text_node_draw(&custom_node.node, ctx, &TEST_CLIP_BOX, NULL, NULL);
cl_assert_equal_grect(ctx->draw_state.clip_box, DISP_FRAME);
// Clipping on
custom_node.node.clip = true;
s_data.gcontext.draw_state.clip_box = DISP_FRAME;
s_data.clip_box = TEST_CLIP_BOX;
graphics_text_node_draw(&custom_node.node, ctx, &TEST_CLIP_BOX, NULL, NULL);
cl_assert_equal_grect(ctx->draw_state.clip_box, DISP_FRAME);
}
@@ -0,0 +1,312 @@
/*
* 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/gtypes.h"
#include "applib/graphics/framebuffer.h"
#include "applib/ui/window_private.h"
#include "applib/ui/window_stack_animation_round.h"
#include "applib/ui/layer.h"
#include "board/displays/display_spalding.h"
#include "services/common/compositor/compositor_transitions.h"
#include "util/trig.h"
#include "clar.h"
#include "util.h"
#include <stdio.h>
// Helper Functions
////////////////////////////////////
#include "test_graphics.h"
#include "8bit/test_framebuffer.h"
// Stubs
////////////////////////////////////
#include "graphics_common_stubs.h"
#include "stubs_applib_resource.h"
void window_transition_context_appearance_call_all(WindowTransitioningContext *ctx) {}
void window_render(Window *window, GContext *ctx) {}
bool compositor_transition_app_to_app_should_be_skipped(void) {
return false;
}
AnimationPrivate *animation_private_animation_find(Animation *handle) {
return NULL;
}
AnimationProgress animation_private_get_animation_progress(const AnimationPrivate *animation) {
return 0;
}
const AnimationImplementation* animation_get_implementation(Animation *animation_h) {
return NULL;
}
const GDrawRawImplementation g_compositor_transitions_app_fb_draw_implementation = {0};
void compositor_port_hole_transition_draw_outer_ring(GContext *ctx, int16_t pixels,
GColor ring_color) {
}
// Setup and Teardown
////////////////////////////////////
static FrameBuffer *fb = NULL;
// Setup
void test_graphics_window_stack_animation__initialize(void) {
fb = malloc(sizeof(FrameBuffer));
framebuffer_init(fb, &(GSize) { DISP_COLS, DISP_ROWS });
}
void test_graphics_window_stack_animation__cleanup(void) {
free(fb);
}
// Helpers
////////////////////////////////////
typedef void (*ClippingMaskDrawFunc)(GContext *ctx);
static void prv_test_clipping_mask(ClippingMaskDrawFunc draw_func, const char *expected_image) {
GContext *ctx = malloc(sizeof(GContext));
test_graphics_context_init(ctx, fb);
framebuffer_clear(fb);
graphics_context_set_antialiased(ctx, true);
// Start by filling the framebuffer with green pixels to make things easier to see
memset(ctx->dest_bitmap.addr, GColorGreenARGB8, FRAMEBUFFER_SIZE_BYTES);
const bool transparent = true;
GDrawMask *mask = graphics_context_mask_create(ctx, transparent);
cl_assert(mask);
cl_assert(graphics_context_mask_record(ctx, mask));
draw_func(ctx);
cl_assert(graphics_context_mask_record(ctx, NULL));
cl_assert_equal_p(ctx->draw_state.draw_implementation, &g_default_draw_implementation);
cl_assert(graphics_context_mask_use(ctx, mask));
cl_assert_equal_p(ctx->draw_state.draw_mask, mask);
graphics_context_set_fill_color(ctx, GColorRed);
graphics_fill_rect(ctx, &ctx->dest_bitmap.bounds);
cl_assert(graphics_context_mask_use(ctx, NULL));
cl_assert_equal_p(ctx->draw_state.draw_mask, NULL);
cl_check(gbitmap_pbi_eq(&ctx->dest_bitmap, TEST_NAMED_PBI_FILE(expected_image)));
graphics_context_mask_destroy(ctx, mask);
free(ctx);
}
// Tests
////////////////////////////////////
// NOTE: All of the following tests first fill the framebuffer with green to make it easier to see
// incorrect pixels
// This test records a clipping mask of the first frame of the left "round flip" compositor
// transition animation and then clips a full-screen red rectangle to the resulting mask
static void prv_left_flip_first_frame_clipping(GContext *ctx) {
compositor_round_flip_transitions_flip_animation_update(ctx, 0,
CompositorTransitionDirectionLeft,
GColorWhite);
}
void test_graphics_window_stack_animation__left_flip_first_frame_clipping(void) {
prv_test_clipping_mask(prv_left_flip_first_frame_clipping, "left_flip_first_frame_clipping");
};
// This test records a clipping mask of the 1/4 progress frame of the left "round flip" compositor
// transition animation and then clips a full-screen red rectangle to the resulting mask
static void prv_left_flip_first_quarter_frame_clipping(GContext *ctx) {
compositor_round_flip_transitions_flip_animation_update(ctx, ANIMATION_NORMALIZED_MAX / 4,
CompositorTransitionDirectionLeft,
GColorWhite);
}
void test_graphics_window_stack_animation__left_flip_first_quarter_frame_clipping(void) {
prv_test_clipping_mask(prv_left_flip_first_quarter_frame_clipping,
"left_flip_first_quarter_frame_clipping");
};
// This test records a clipping mask of the half progress frame of the left "round flip" compositor
// transition animation and then clips a full-screen red rectangle to the resulting mask
static void prv_left_flip_half_frame_clipping(GContext *ctx) {
compositor_round_flip_transitions_flip_animation_update(ctx, ANIMATION_NORMALIZED_MAX / 2,
CompositorTransitionDirectionLeft,
GColorWhite);
}
void test_graphics_window_stack_animation__left_flip_half_frame_clipping(void) {
prv_test_clipping_mask(prv_left_flip_half_frame_clipping, "left_flip_half_frame_clipping");
};
// This test records a clipping mask of the 3/4 progress frame of the left "round flip" compositor
// transition animation and then clips a full-screen red rectangle to the resulting mask
static void prv_left_flip_third_quarter_frame_clipping(GContext *ctx) {
compositor_round_flip_transitions_flip_animation_update(ctx, ANIMATION_NORMALIZED_MAX * 3 / 4,
CompositorTransitionDirectionLeft,
GColorWhite);
}
void test_graphics_window_stack_animation__left_flip_third_quarter_frame_clipping(void) {
prv_test_clipping_mask(prv_left_flip_third_quarter_frame_clipping,
"left_flip_third_quarter_frame_clipping");
};
// This test records a clipping mask of the last frame of the left "round flip" compositor
// transition animation and then clips a full-screen red rectangle to the resulting mask
static void prv_left_flip_last_frame_clipping(GContext *ctx) {
compositor_round_flip_transitions_flip_animation_update(ctx, ANIMATION_NORMALIZED_MAX,
CompositorTransitionDirectionLeft,
GColorWhite);
}
void test_graphics_window_stack_animation__left_flip_last_frame_clipping(void) {
prv_test_clipping_mask(prv_left_flip_last_frame_clipping, "left_flip_last_frame_clipping");
};
// This test records a clipping mask of the first frame of the right "round flip" compositor
// transition animation and then clips a full-screen red rectangle to the resulting mask
static void prv_right_flip_first_frame_clipping(GContext *ctx) {
// The first frame is ANIMATION_NORMALIZED_MAX because the right flip animation is actually
// played backwards
compositor_round_flip_transitions_flip_animation_update(ctx, ANIMATION_NORMALIZED_MAX,
CompositorTransitionDirectionRight,
GColorWhite);
}
void test_graphics_window_stack_animation__right_flip_first_frame_clipping(void) {
prv_test_clipping_mask(prv_right_flip_first_frame_clipping, "right_flip_first_frame_clipping");
};
// This test records a clipping mask of the 1/4 progress frame of the right "round flip" compositor
// transition animation and then clips a full-screen red rectangle to the resulting mask
static void prv_right_flip_first_quarter_frame_clipping(GContext *ctx) {
// The 1/4 progress frame is ANIMATION_NORMALIZED_MAX * 3/4 because the right flip animation is
// actually played backwards
compositor_round_flip_transitions_flip_animation_update(ctx, ANIMATION_NORMALIZED_MAX * 3 / 4,
CompositorTransitionDirectionRight,
GColorWhite);
}
void test_graphics_window_stack_animation__right_flip_first_quarter_frame_clipping(void) {
prv_test_clipping_mask(prv_right_flip_first_quarter_frame_clipping,
"right_flip_first_quarter_frame_clipping");
};
// This test records a clipping mask of the half progress frame of the right "round flip" compositor
// transition animation and then clips a full-screen red rectangle to the resulting mask
static void prv_right_flip_half_frame_clipping(GContext *ctx) {
compositor_round_flip_transitions_flip_animation_update(ctx, ANIMATION_NORMALIZED_MAX / 2,
CompositorTransitionDirectionRight,
GColorWhite);
}
void test_graphics_window_stack_animation__right_flip_half_frame_clipping(void) {
prv_test_clipping_mask(prv_right_flip_half_frame_clipping, "right_flip_half_frame_clipping");
};
// This test records a clipping mask of the 3/4 progress frame of the right "round flip" compositor
// transition animation and then clips a full-screen red rectangle to the resulting mask
static void prv_right_flip_third_quarter_frame_clipping(GContext *ctx) {
// The 3/4 progress frame is ANIMATION_NORMALIZED_MAX * 1/4 because the right flip animation is
// actually played backwards
compositor_round_flip_transitions_flip_animation_update(ctx, ANIMATION_NORMALIZED_MAX / 4,
CompositorTransitionDirectionRight,
GColorWhite);
}
void test_graphics_window_stack_animation__right_flip_third_quarter_frame_clipping(void) {
prv_test_clipping_mask(prv_right_flip_third_quarter_frame_clipping,
"right_flip_third_quarter_frame_clipping");
};
// This test records a clipping mask of the last frame of the right "round flip" compositor
// transition animation and then clips a full-screen red rectangle to the resulting mask
static void prv_right_flip_last_frame_clipping(GContext *ctx) {
// The last frame is 0 because the right flip animation is actually played backwards
compositor_round_flip_transitions_flip_animation_update(ctx, 0,
CompositorTransitionDirectionRight,
GColorWhite);
}
void test_graphics_window_stack_animation__right_flip_last_frame_clipping(void) {
prv_test_clipping_mask(prv_right_flip_last_frame_clipping, "right_flip_last_frame_clipping");
};
void test_graphics_window_stack_animation__move_pixels_horizontally(void) {
GContext *ctx = malloc(sizeof(GContext));
test_graphics_context_init(ctx, fb);
framebuffer_clear(fb);
GBitmap *const bitmap = &ctx->dest_bitmap;
// vertical test pattern
for (int16_t y = 0; y < DISP_ROWS; y++) {
GBitmapDataRowInfo row_info = prv_gbitmap_get_data_row_info(bitmap, y);
for (int x = 0; x < DISP_COLS; x++) {
GColor8 color = GColorFromRGB(1 * x * UINT8_MAX / DISP_COLS,
2 * x * UINT8_MAX / DISP_COLS,
4 * x * UINT8_MAX / DISP_COLS);
if (row_info.min_x <= x && x <= row_info.max_x) {
row_info.data[x] = color.argb;
}
}
}
// nop
graphics_private_move_pixels_horizontally(NULL, 50, false);
graphics_private_move_pixels_horizontally(bitmap, 0, false);
graphics_private_move_pixels_horizontally(bitmap, 50, false);
cl_check(gbitmap_pbi_eq(bitmap, TEST_NAMED_PBI_FILE("move_horizontal_right")));
graphics_private_move_pixels_horizontally(bitmap, -100, false);
cl_check(gbitmap_pbi_eq(bitmap, TEST_NAMED_PBI_FILE("move_horizontal_left")));
graphics_private_move_pixels_horizontally(bitmap, 400, false);
cl_check(gbitmap_pbi_eq(bitmap, TEST_NAMED_PBI_FILE("move_horizontal_right_too_far")));
graphics_private_move_pixels_horizontally(bitmap, -400, true);
cl_check(gbitmap_pbi_eq(bitmap, TEST_NAMED_PBI_FILE("move_horizontal_left_filled")));
free(ctx);
}
+190
View File
@@ -0,0 +1,190 @@
/*
* 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/gtypes.h"
#include "clar.h"
#include "pebble_asserts.h"
#include <stdio.h>
// stubs
#include "stubs_process_manager.h"
#include "stubs_passert.h"
#include "stubs_app_state.h"
#include "stubs_heap.h"
/////////////////////////////
void test_gtypes__gpoint_scale_by_gsize(void) {
GSize from = GSize(10, 20);
GSize to = GSize(20, 40);
GPoint point = GPoint(10, 10);
GPoint result = GPointZero;
result = gpoint_scale_by_gsize(point, from, to);
cl_assert_equal_i(result.x, 20);
cl_assert_equal_i(result.y, 20);
}
void test_gtypes__gpoint_scale_by_gsize_from_zero(void) {
GSize from = GSizeZero;
GSize to = GSize(20, 40);
GPoint point = GPoint(10, 10);
GPoint result = GPointZero;
result = gpoint_scale_by_gsize(point, from, to);
cl_assert_equal_i(result.x, 0);
cl_assert_equal_i(result.y, 0);
}
void test_gtypes__gpoint_scale_by_gsize_to_zero(void) {
GSize from = GSize(10, 20);
GSize to = GSizeZero;
GPoint point = GPoint(10, 10);
GPoint result = GPointZero;
result = gpoint_scale_by_gsize(point, from, to);
cl_assert_equal_i(result.x, 0);
cl_assert_equal_i(result.y, 0);
}
#define cl_assert_equal_insets(i1, i2) \
do { \
cl_assert_equal_i(i1.top, i2.top); \
cl_assert_equal_i(i1.right, i2.right); \
cl_assert_equal_i(i1.bottom, i2.bottom); \
cl_assert_equal_i(i1.left, i2.left); \
} while(0)
void test_gtypes__gedge_insets(void) {
GEdgeInsets g4 = (GEdgeInsets){.top = 1, .right = 2, .bottom = 3, .left = 4};
cl_assert_equal_insets(GEdgeInsets(1, 2, 3, 4), g4);
GEdgeInsets g3 = (GEdgeInsets){.top = 1, .right = 2, .bottom = 3, .left = 2};
cl_assert_equal_insets(GEdgeInsets(1, 2, 3), g3);
GEdgeInsets g2 = (GEdgeInsets){.top = 1, .right = 2, .bottom = 1, .left = 2};
cl_assert_equal_insets(GEdgeInsets(1, 2), g2);
GEdgeInsets g1 = (GEdgeInsets){.top = 1, .right = 1, .bottom = 1, .left = 1};
cl_assert_equal_insets(GEdgeInsets(1), g1);
}
void test_gtypes__grect_longest_side(void) {
cl_assert_equal_i(0, grect_longest_side(GRectZero));
cl_assert_equal_i(20, grect_longest_side(GRect(0, 0, 10, 20)));
cl_assert_equal_i(20, grect_longest_side(GRect(0, 0, 20, 10)));
cl_assert_equal_i(20, grect_longest_side(GRect(0, 0, 10, -20)));
cl_assert_equal_i(20, grect_longest_side(GRect(0, 0, -20, 10)));
}
void test_gtypes__grect_shortest_side(void) {
cl_assert_equal_i(0, grect_shortest_side(GRectZero));
cl_assert_equal_i(10, grect_shortest_side(GRect(0, 0, 10, 20)));
cl_assert_equal_i(10, grect_shortest_side(GRect(0, 0, 20, 10)));
cl_assert_equal_i(10, grect_shortest_side(GRect(0, 0, 10, -20)));
cl_assert_equal_i(10, grect_shortest_side(GRect(0, 0, -20, 10)));
}
void test_gtypes__grect_inset(void) {
GRect rect = GRect(10, 20, 30, 40);
cl_assert_equal_grect(GRect(12, 23, 26, 34), grect_inset_internal(rect, 2, 3));
cl_assert_equal_grect(GRect(7, 18, 36, 44), grect_inset_internal(rect, -3, -2));
}
void test_gtypes__grect_inset_standardizes(void) {
GRect rect = GRect(100, 100, -30, -40);
cl_assert_equal_grect(GRect(70, 60, 30, 40), grect_inset_internal(rect, 0, 0));
cl_assert_equal_grect(GRect(72, 63, 26, 34), grect_inset_internal(rect, 2, 3));
}
void test_gtypes__grect_inset_returns_zero_rect_for_large_insets(void) {
GRect rect = GRect(10, 20, 30, 40);
cl_assert_equal_grect(GRect(25, 20, 0, 40), grect_inset_internal(rect, 15, 0));
cl_assert_equal_grect(GRectZero, grect_inset_internal(rect, 16, 0));
cl_assert_equal_grect(GRect(10, 40, 30, 0), grect_inset_internal(rect, 0, 20));
cl_assert_equal_grect(GRectZero, grect_inset_internal(rect, 0, 21));
cl_assert_equal_grect(GRectZero, grect_inset_internal(rect, 16, 21));
}
void test_gtypes__grect_crop_asserts_for_large_insets(void) {
GRect rect = GRect(10, 20, 30, 40);
cl_assert_equal_grect(GRect(25, 35, 0, 10), grect_crop(rect, 15));
cl_assert_passert(grect_crop(rect, 16));
}
void test_gtypes__pbl_if_rect_else(void) {
#if defined(PLATFORM_TINTIN)
cl_assert_equal_i(1, PBL_IF_RECT_ELSE(1,2));
#elif defined(PLATFORM_SNOWY)
cl_assert_equal_i(1, PBL_IF_RECT_ELSE(1,2));
#elif defined(PLATFORM_SPALDING)
cl_assert_equal_i(2, PBL_IF_RECT_ELSE(1,2));
#else
#error "unknown platform"
#endif
}
void test_gtypes__pbl_if_round_else(void) {
#if defined(PLATFORM_TINTIN)
cl_assert_equal_i(2, PBL_IF_ROUND_ELSE(1,2));
#elif defined(PLATFORM_SNOWY)
cl_assert_equal_i(2, PBL_IF_ROUND_ELSE(1,2));
#elif defined(PLATFORM_SPALDING)
cl_assert_equal_i(1, PBL_IF_ROUND_ELSE(1,2));
#else
#error "unknown platform"
#endif
}
void test_gtypes__pbl_if_bw_else(void) {
#if defined(PLATFORM_TINTIN)
cl_assert_equal_i(1, PBL_IF_BW_ELSE(1,2));
#elif defined(PLATFORM_SNOWY)
cl_assert_equal_i(2, PBL_IF_BW_ELSE(1,2));
#elif defined(PLATFORM_SPALDING)
cl_assert_equal_i(2, PBL_IF_BW_ELSE(1,2));
#else
#error "unknown platform"
#endif
}
void test_gtypes__pbl_if_color_else(void) {
#if defined(PLATFORM_TINTIN)
cl_assert_equal_i(2, PBL_IF_COLOR_ELSE(1,2));
#elif defined(PLATFORM_SNOWY)
cl_assert_equal_i(1, PBL_IF_COLOR_ELSE(1,2));
#elif defined(PLATFORM_SPALDING)
cl_assert_equal_i(1, PBL_IF_COLOR_ELSE(1,2));
#else
#error "unknown platform"
#endif
}
void test_gtypes__color_fallback(void) {
#if defined(PLATFORM_TINTIN)
cl_assert_equal_i(2, COLOR_FALLBACK(1,2));
#elif defined(PLATFORM_SNOWY)
cl_assert_equal_i(1, COLOR_FALLBACK(1,2));
#elif defined(PLATFORM_SPALDING)
cl_assert_equal_i(1, COLOR_FALLBACK(1,2));
#else
#error "unknown platform"
#endif
}
@@ -0,0 +1,77 @@
/*
* 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/gbitmap_png.h"
#include "clar.h"
#include "util.h"
#include <string.h>
// Stubs
////////////////////////////////////
#include "stubs_applib_resource.h"
#include "stubs_app_state.h"
#include "stubs_heap.h"
#include "stubs_logging.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_print.h"
#include "stubs_resources.h"
#include "stubs_syscalls.h"
#include "stubs_serial.h"
#include "stubs_ui_window.h"
#include "stubs_compiled_with_legacy2_sdk.h"
extern uint8_t prv_byte_reverse(uint8_t b);
void test_palettized_conversion__test_byte_reverse(void) {
cl_assert(prv_byte_reverse(0b11110000) == 0b00001111);
cl_assert(prv_byte_reverse(0b10100101) == 0b10100101);
}
// Each of the following tests depend on a PNG being converted into 1-bit PBI,
// as well as loading in a b&w PNG, which internally will be converted to a
// 1-bit palettized image.
// The palettized PNG loaded in is to be used as the expected value
#define TEST_1BIT_FILE TEST_PBI_FILE_FMT(1bit)
#define TEST_PALETTIZED_FILE TEST_PBI_FILE
void test_palettized_conversion__create_palettized_from_1bit(void) {
GBitmap *img_1bit = get_gbitmap_from_pbi(TEST_1BIT_FILE);
cl_assert(img_1bit);
GBitmap *img_palettized = gbitmap_create_palettized_from_1bit(img_1bit);
cl_assert(img_palettized);
gbitmap_pbi_eq(img_palettized, TEST_PALETTIZED_FILE);
gbitmap_destroy(img_palettized);
GRect test_bounds = GRect(3, 3, 46, 46);
img_1bit->bounds = test_bounds;
img_palettized = gbitmap_create_palettized_from_1bit(img_1bit);
cl_assert(gcolor_equal(img_palettized->palette[0], GColorBlack));
cl_assert(gcolor_equal(img_palettized->palette[1], GColorWhite));
gbitmap_pbi_eq_with_bounds(img_palettized, TEST_PALETTIZED_FILE, &test_bounds);
gbitmap_destroy(img_palettized);
gbitmap_destroy(img_1bit);
}
+108
View File
@@ -0,0 +1,108 @@
/*
* 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/gtypes.h"
#include "clar.h"
#include "util.h"
#include <string.h>
#include <stdio.h>
// Stubs
////////////////////////////////////
#include "stubs_applib_resource.h"
#include "stubs_app_state.h"
#include "stubs_heap.h"
#include "stubs_resources.h"
#include "stubs_syscalls.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_logging.h"
// Tests
////////////////////////////////////
// Reference PNGs reside in "tests/fw/graphics/test_images/"
// and are created at build time, with the test PBI file generated
// by bitmapgen.py from the reference PNG copied to TEST_IMAGES_PATH
// covers 1,2,4,8 bit palettized
// covers 1,2,4,8 bit palettized with transparency
// Tests 1-bit red&white palettized PBI loading into gbitmap
// Result:
// - gbitmap matches platform loaded PBI
void test_pbi__color_1_bit(void) {
GBitmap *bitmap = setup_pbi_test(TEST_PBI_FILE);
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE));
}
// Tests 2-bit palettized PBI loading into gbitmap
// Result:
// - gbitmap matches platform loaded PBI
void test_pbi__color_2_bit(void) {
GBitmap *bitmap = setup_pbi_test(TEST_PBI_FILE);
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE));
}
// Tests 4-bit palettized PBI loading into gbitmap
// Result:
// - gbitmap matches platform loaded PBI
void test_pbi__color_4_bit(void) {
GBitmap *bitmap = setup_pbi_test(TEST_PBI_FILE);
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE));
}
// Tests 8-bit color PBI loading into gbitmap
// Result:
// - gbitmap matches platform loaded PBI
void test_pbi__color_8_bit(void) {
GBitmap *bitmap = setup_pbi_test(TEST_PBI_FILE);
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE));
}
// Tests 1-bit transparent palettized PBI loading into gbitmap
// Result:
// - gbitmap matches platform loaded PBI
void test_pbi__color_1_bit_transparent(void) {
GBitmap *bitmap = setup_pbi_test(TEST_PBI_FILE);
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE));
}
// Tests 2-bit transparent palettized PBI loading into gbitmap
// Result:
// - gbitmap matches platform loaded PBI
void test_pbi__color_2_bit_transparent(void) {
GBitmap *bitmap = setup_pbi_test(TEST_PBI_FILE);
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE));
}
// Tests 4-bit transparent palettized PBI loading into gbitmap
// Result:
// - gbitmap matches platform loaded PBI
void test_pbi__color_4_bit_transparent(void) {
GBitmap *bitmap = setup_pbi_test(TEST_PBI_FILE);
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE));
}
// Tests 8-bit transparent PBI loading into gbitmap
// Result:
// - gbitmap matches platform loaded PBI
void test_pbi__color_8_bit_transparent(void) {
GBitmap *bitmap = setup_pbi_test(TEST_PBI_FILE);
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE));
}
+37
View File
@@ -0,0 +1,37 @@
/*
* 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 "clar.h"
#include "util.h"
#include "applib/graphics/gtypes.h"
//// Stubs
#include "stubs_applib_resource.h"
#include "stubs_app_state.h"
#include "stubs_heap.h"
#include "stubs_resources.h"
#include "stubs_syscalls.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_logging.h"
// tests
void test_pdc__draw_pdc_image(void) {
GBitmap *bitmap = setup_pbi_test(TEST_PDC_PBI_FILE);
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE));
}
+118
View File
@@ -0,0 +1,118 @@
/*
* 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/perimeter.h"
#include "clar.h"
#include "util/trig.h"
#include <string.h>
#include <stdio.h>
#include <math.h>
#ifndef M_PI
// M_PI doesn't exist in Linux
#define M_PI 3.14159265358979323846 /* pi */
#endif
#define DEG2RAD(a) (M_PI/180*(a))
// Stubs
////////////////////////////////////
#include "stubs_heap.h"
#include "stubs_passert.h"
#include "stubs_logging.h"
#include "stubs_app_state.h"
#include "stubs_compiled_with_legacy2_sdk.h"
#define BETWEEN(val, low, high) \
(val >= low && val <= high) ? true : false
// Tests
////////////////////////////////////
GRangeHorizontal perimeter_for_circle(GRangeVertical vertical_range, GPoint center, int32_t radius);
GRangeHorizontal perimeter_for_display_round(const GPerimeter *perimeter,
const GSize *ctx_size,
GRangeVertical vertical_range,
uint16_t inset);
GRangeHorizontal perimeter_for_display_rect(const GPerimeter *perimeter,
const GSize *ctx_size,
GRangeVertical vertical_range,
uint16_t inset);
void test_perimeter__perimeter_for_circle(void) {
GRect bounds = GRect(0,0,180,180);
GPoint center = grect_center_point(&bounds);
int16_t radius = bounds.size.w / 2;
// test robustness of perimeter_horizontal_range_for_circle
for (int y = 0; y < bounds.size.h; y++) {
GRangeHorizontal h_range = perimeter_for_circle(
(GRangeVertical) {.origin_y = y, .size_h = 0}, center, radius);
// internally we use integer_sqrt, which causes precision loss
// so we need to mirror some of the fixed point truncation here
int16_t height = 90 - y;
int16_t width = sqrt(radius * radius - height * height);
int16_t test_origin = 90 - width;
// Due to integer math and truncation, we need to test +-1
cl_assert(BETWEEN(h_range.origin_x, test_origin - 1, test_origin + 1));
cl_assert(BETWEEN(h_range.size_w, (width - 1) * 2, (width + 1) * 2));
}
}
#define cl_assert_equal_rangehorizontal(r1, r2) \
do { \
cl_assert_equal_i((r1).origin_x, (r2).origin_x); \
cl_assert_equal_i((r1).size_w, (r2).size_w); \
} while(0)
void test_perimeter__perimeter_for_display_rect(void) {
GPerimeter p = {
.callback = perimeter_for_display_rect,
};
const GSize ctx_size = GSize(DISP_COLS, DISP_ROWS);
GRangeVertical r = {.origin_y = 10, .size_h = 10};
GRangeHorizontal expected = (GRangeHorizontal){.origin_x = 0, .size_w = DISP_COLS};
cl_assert_equal_rangehorizontal(expected, perimeter_for_display_rect(&p, &ctx_size, r, 0));
expected = (GRangeHorizontal){.origin_x = 5, .size_w = DISP_COLS - 10};
cl_assert_equal_rangehorizontal(expected, perimeter_for_display_rect(&p, &ctx_size, r, 5));
cl_assert_equal_i(0, perimeter_for_display_rect(&p, &ctx_size, r, 500).size_w);
}
void test_perimeter__perimeter_for_display_round(void) {
GPerimeter p = {
.callback = perimeter_for_display_round,
};
GRangeVertical r = {.origin_y = 10, .size_h = 10};
const GSize ctx_size = GSize(DISP_COLS, DISP_ROWS);
const GRect disp = GRect(0, 0, DISP_COLS, DISP_ROWS);
GRangeHorizontal expected =
perimeter_for_circle(r, grect_center_point(&disp), grect_shortest_side(disp) / 2);
cl_assert_equal_rangehorizontal(expected, perimeter_for_display_round(&p, &ctx_size, r, 0));
expected = perimeter_for_circle(r, grect_center_point(&disp), grect_shortest_side(disp) / 2 - 5);
cl_assert_equal_rangehorizontal(expected, perimeter_for_display_round(&p, &ctx_size, r, 5));
cl_assert_equal_i(0, perimeter_for_display_round(&p, &ctx_size, r, 500).size_w);
}
void test_perimeter__g_perimeter_for_display(void) {
GPerimeterCallback expected = PBL_IF_RECT_ELSE(perimeter_for_display_rect,
perimeter_for_display_round);
cl_assert_equal_p(expected, g_perimeter_for_display->callback);
}
+209
View File
@@ -0,0 +1,209 @@
/*
* 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/gbitmap_png.h"
#include "clar.h"
#include "util.h"
#include <string.h>
#include <stdio.h>
// Stubs
////////////////////////////////////
#include "stubs_applib_resource.h"
#include "stubs_app_state.h"
#include "stubs_heap.h"
#include "stubs_resources.h"
#include "stubs_syscalls.h"
#include "stubs_passert.h"
#include "stubs_pbl_malloc.h"
#include "stubs_logging.h"
// Tests
////////////////////////////////////
// Reference PNGs reside in "tests/fw/graphics/test_images/"
// and are created at build time, with the PNG file copied to TEST_IMAGES_PATH
// and a separate PBI generated by bitmapgen.py from the PNG copied to TEST_IMAGES_PATH
// covers 1,2,4,8 bit palettized
// covers 1,2,4,8 bit palettized with transparency
// covers 1, 2 bit greyscale
// covers 1, 2 bit greyscale with transparency
// covers 4 bit greyscale with transparency (black, white, dark_grey, light_grey, transparent)
// Tests 1-bit jazzberry_jam colored palettized PNG loading into gbitmap
// tests covers an old bug in image tooling where 1 color was 2 power 0
// so bitdepth was detected as 0 (incorrectly)
// Result:
// - gbitmap matches platform loaded PNG
void test_png__color_1_bit_jazzberry_jam(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(1bitpalette));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(1bitpalette)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat1BitPalette);
}
// Tests 1-bit red&white palettized PNG loading into gbitmap
// Result:
// - gbitmap matches platform loaded PNG
void test_png__color_1_bit(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(1bitpalette));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(1bitpalette)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat1BitPalette);
}
// Tests 2-bit palettized PNG loading into gbitmap
// Result:
// - gbitmap matches platform loaded PNG
void test_png__color_2_bit(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(2bitpalette));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(2bitpalette)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat2BitPalette);
}
// Tests 4-bit palettized PNG loading into gbitmap
// Result:
// - gbitmap matches platform loaded PNG
void test_png__color_4_bit(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(4bitpalette));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(4bitpalette)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat4BitPalette);
}
// Tests 8-bit palettized 64-color PNG loading into gbitmap
// Result:
// - gbitmap matches platform loaded PNG
void test_png__color_8_bit(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(8bit));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(8bit)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat8Bit);
}
// Tests 1-bit transparent palettized PNG loading into gbitmap
// Result:
// - gbitmap matches platform loaded PNG
void test_png__color_1_bit_transparent(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(1bitpalette));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(1bitpalette)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat1BitPalette);
}
// Tests 2-bit transparent palettized PNG loading into gbitmap
// Result:
// - gbitmap matches platform loaded PNG
void test_png__color_2_bit_transparent(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(2bitpalette));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(2bitpalette)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat2BitPalette);
}
// Tests 4-bit transparent palettized PNG loading into gbitmap
// Result:
// - gbitmap matches platform loaded PNG
void test_png__color_4_bit_transparent(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(4bitpalette));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(4bitpalette)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat4BitPalette);
}
// Tests 8-bit transparent palettized PNG loading into gbitmap
// Result:
// - gbitmap matches platform loaded PNG
void test_png__color_8_bit_transparent(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(8bit));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(8bit)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat8Bit);
}
// Tests 1-bit all black greyscale PNG loading into gbitmap
// Result:
// - gbitmap matches platform loaded PNG
void test_png__greyscale_1_bit_black(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(1bitpalette));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(1bitpalette)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat1BitPalette);
}
// Tests 1-bit b&w greyscale PNG loading into gbitmap
// Result:
// - gbitmap matches platform loaded PNG
void test_png__greyscale_1_bit(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(1bitpalette));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(1bitpalette)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat1BitPalette);
}
// Tests 2-bit greyscale PNG loading into gbitmap
// Result:
// - gbitmap matches platform loaded PNG
void test_png__greyscale_2_bit(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(2bitpalette));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(2bitpalette)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat2BitPalette);
}
// Tests 1-bit transparent greyscale PNG loading into gbitmap
// Result:
// - gbitmap matches platform loaded PNG
void test_png__greyscale_1_bit_transparent(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(1bitpalette));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(1bitpalette)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat1BitPalette);
}
// Tests 2-bit transparent greyscale PNG loading into gbitmap
// Result:
// - gbitmap matches platform loaded PNG
void test_png__greyscale_2_bit_transparent(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(2bitpalette));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(2bitpalette)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat2BitPalette);
}
// Tests 4-bit transparent greyscale PNG loading into gbitmap
// Result:
// - gbitmap matches platform loaded PNG
void test_png__greyscale_4_bit_transparent(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(4bitpalette));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(4bitpalette)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat4BitPalette);
}
// Tests 8-bit transparent color PNG with 4-bit number of colors (<= 16 colors)
// loading into gbitmap
// Checks for bug fixed for PBL-15809, where GBitmap depth was based off
// number of colors and not index value bits-per-pixel (bpp).
// Result:
// - gbitmap matches platform loaded PNG
void test_png__color_8_bit_transparent_bpp_check(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(8bit));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(8bit)));
// Make sure bpp detection doesn't fail in the future
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat8Bit);
}
// Tests 8-bit transparent color PNG with 256 Colors
// loading into gbitmap
// Checks for bug fixed for PBL-16549, where uint8_t limit was 255
// causing failure to load palette
// Result:
// - gbitmap matches platform loaded PNG
void test_png__color_256_colors_check(void) {
GBitmap *bitmap = setup_png_test(TEST_PNG_FILE_FMT(raw));
cl_assert(gbitmap_pbi_eq(bitmap, TEST_PBI_FILE_FMT(raw)));
cl_assert_equal_i(gbitmap_get_format(bitmap), GBitmapFormat8Bit);
}
+510
View File
@@ -0,0 +1,510 @@
/*
* 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.
*/
#pragma once
#include "test_graphics.h"
#include "applib/graphics/gbitmap_png.h"
#include "util/graphics.h"
#include "util/math.h"
#include "clar.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#define PATH_STRING_LENGTH 512
extern GBitmapDataRowInfo prv_gbitmap_get_data_row_info(const GBitmap *bitmap, uint16_t y);
#include "util_pbi.h"
// The following macros append the ~platform (unless default) and the filetype extension
// unless file_name contains Xbit, then appends .(native_bitdepth)bit and filetype extension
#define TEST_NAMED_PBI_FILE(file_name) namecat(file_name, ".pbi")
#define TEST_NAMED_PNG_FILE(file_name) namecat(file_name, ".png")
// The following macros create a file name based on the function name plus extension
#define TEST_PBI_FILE namecat(__func__, ".pbi")
#define TEST_PBI_FILE_FMT(fmt) namecat(__func__, "." #fmt ".pbi")
#define TEST_PNG_FILE namecat(__func__, ".png")
#define TEST_PNG_FILE_FMT(fmt) namecat(__func__, "." #fmt ".png")
#define TEST_PDC_FILE namecat(__func__, ".pdc")
#define TEST_PDC_PBI_FILE namecat(__func__, ".pdc.pbi")
#define TEST_APNG_FILE namecat(__func__, ".apng")
#define TEST_PBI_FILE_X(x) namecat(__func__, "_" #x ".pbi")
bool tests_write_gbitmap_to_pbi(GBitmap *bmp, const char *filename) {
char full_path[PATH_STRING_LENGTH];
snprintf(full_path, sizeof(full_path), "%s/%s", TEST_OUTPUT_PATH, filename);
return write_gbitmap_to_pbi(bmp, full_path, PBI2PNG_EXE);
}
// Used to work around __func__ not being a string literal (necessary for macro concatenation)
static const char *namecat(const char* str1, const char* str2){
char *filename = malloc(PATH_STRING_LENGTH);
filename[0] = '\0';
strcat(filename, str1);
char *filename_xbit = strstr(filename, ".Xbit");
if (filename_xbit) {
// Support using ".Xbit" for the native bitdepth
filename_xbit[1] = (SCREEN_COLOR_DEPTH_BITS == 8) ? '8' : '1';
printf("filename and filename_xbit %s : %s\n", filename, filename_xbit);
} else {
#if !PLATFORM_DEFAULT
// Add ~platform to files with unit-tests built for a specific platform
strcat(filename, "~");
strcat(filename, PLATFORM_NAME);
#endif
}
strcat(filename, str2);
return filename;
}
static char get_terminal_color(uint8_t c) {
switch (c) {
case GColorBlackARGB8: // black
return 'B';
case GColorWhiteARGB8: // white
return 'W';
case GColorRedARGB8: // red
return 'R';
case GColorGreenARGB8: // green
return 'G';
case GColorBlueARGB8: // blue
return 'b';
default:
return ' ';
}
}
// A simple functon for printing 8-bit gbitmaps to the console.
// Makes it easy to quickly review failing test cases.
void print_bitmap(const GBitmap *bmp) {
printf("Row Size Bytes: %d\n", bmp->row_size_bytes);
printf("Bounds: ");
printf(GRECT_PRINTF_FORMAT, GRECT_PRINTF_FORMAT_EXPLODE(bmp->bounds));
printf("\n");
GSize size = bmp->bounds.size;
uint8_t *data = (uint8_t *)bmp->addr;
// Build a coordinate system, 3 rows up top for the col number
for (uint8_t y = 0; y < 3; ++y) {
printf("\t"); // leave space for row #
for (uint8_t x = 0; x < size.w; ++x) {
int num = -1;
switch (y) {
case 0: // hundreds
if (x < 100) break;
num = (x / 100) % 10;
break;
case 1: // tens
if (x < 10) break;
num = (x / 10) % 10;
break;
case 2: // ones
num = x % 10;
break;
}
if (num < 0) {
printf(" ");
} else {
printf("%d", num);
}
}
printf("\n");
}
const uint8_t start_x = bmp->bounds.origin.x;
const uint8_t end_x = start_x + bmp->bounds.size.w;
const uint8_t start_y = bmp->bounds.origin.y;
const uint8_t end_y = start_y + bmp->bounds.size.h;
for (uint8_t y = start_y; y < end_y; ++y) {
printf("\n%d\t", y);
for (uint8_t x = start_x; x < end_x; ++x) {
uint8_t color = data[y * bmp->row_size_bytes + x];
printf("%c", get_terminal_color(color));
}
}
printf("\n\n\n\n");
}
// Will load the PBI at location $TEST_IMAGES_PATH/filename into a gbitmap.
GBitmap *get_gbitmap_from_pbi(const char *filename) {
char full_path[PATH_STRING_LENGTH];
snprintf(full_path, PATH_STRING_LENGTH, "%s/%s", TEST_IMAGES_PATH, filename);
// Support using ".Xbit" for the native bitdepth
char *filename_xbit = strstr(full_path, ".Xbit");
if (filename_xbit) {
filename_xbit[1] = (SCREEN_COLOR_DEPTH_BITS == 8) ? '8' : '1';
}
FILE *file = fopen(full_path, "r");
if (!file) {
printf("Unable to open file: %s\n", full_path);
return NULL;
}
GBitmap *bmp = malloc(sizeof(*bmp));
*bmp = (GBitmap){0};
// Read bitmap header
fread(&bmp->row_size_bytes, sizeof(bmp->row_size_bytes), 1, file);
fread(&bmp->info_flags, sizeof(bmp->info_flags), 1, file);
fread(&bmp->bounds, sizeof(bmp->bounds), 1, file);
size_t data_size = bmp->row_size_bytes * bmp->bounds.size.h;
bmp->addr = malloc(data_size);
bmp->info.is_bitmap_heap_allocated = true;
fread(bmp->addr, 1, data_size, file);
uint8_t palette_size = gbitmap_get_palette_size(gbitmap_get_format(bmp));
if (palette_size > 0) {
// Allocate palette of GColor8 entries in ARGB8 format
bmp->palette = malloc(palette_size * sizeof(GColor8));
fread(bmp->palette, 1, palette_size * sizeof(GColor8), file);
}
fclose(file);
return bmp;
}
#define ACTUAL_PBI_FILE_EXTENSION "-actual.pbi"
#define EXPECTED_PBI_FILE_EXTENSION "-expected.pbi"
#define DIFF_PBI_FILE_EXTENSION "-diff.pbi"
#define DIFF_COLOR GColorMagenta
static GColor8 prv_convert_to_gcolor8(GBitmapFormat format, uint8_t raw_value, GColor *palette) {
uint8_t color8 = raw_value;
switch (format) {
case GBitmapFormat1Bit:
color8 = (raw_value) ? GColorWhite.argb : GColorBlack.argb;
break;
case GBitmapFormat1BitPalette:
case GBitmapFormat2BitPalette:
case GBitmapFormat4BitPalette:
color8 = palette[raw_value].argb;
break;
case GBitmapFormat8Bit:
default:
break;
}
return (GColor8)color8;
}
static uint8_t prv_raw_image_get_value_for_format(const uint8_t *raw_image_buffer,
uint32_t x, uint32_t y, uint16_t row_stride_bytes, uint8_t bitdepth, GBitmapFormat format) {
if (format == GBitmapFormat1Bit){
// Retrieve the byte from the image buffer containing the requested pixel
uint32_t pixel_in_byte = raw_image_buffer[y * row_stride_bytes + (x / 8)];
// Find the index of the pixel in terms of coordinates and aligned_width
uint32_t pixel_index = y * (row_stride_bytes * 8) + x;
// Shift and mask the requested pixel data from the byte containing it and return
return (uint8_t)(pixel_in_byte >> ((pixel_index % 8)) & 1);
} else {
return raw_image_get_value_for_bitdepth(raw_image_buffer, x, y, row_stride_bytes, bitdepth);
}
}
static void prv_write_diff_to_file(const char *filename, GBitmap *expected_bmp,
GBitmap *actual_bmp, GBitmap *diff_bmp) {
// Write the expected output to filename-expected.png
char bmp_filename[PATH_STRING_LENGTH];
if (expected_bmp) {
strncpy(bmp_filename, filename, PATH_STRING_LENGTH - strlen(EXPECTED_PBI_FILE_EXTENSION) - 1);
char *ext = strrchr(bmp_filename, '.');
strncpy(ext, EXPECTED_PBI_FILE_EXTENSION, strlen(EXPECTED_PBI_FILE_EXTENSION) + 1);
cl_assert(tests_write_gbitmap_to_pbi(expected_bmp, bmp_filename));
// TODO: PBL-20932 Add 1-bit and palletized support
if (actual_bmp->info.format == GBitmapFormat8Bit && diff_bmp) {
// Only write the diff file if there is an expected image
strncpy(bmp_filename, filename, PATH_STRING_LENGTH - strlen(DIFF_PBI_FILE_EXTENSION) - 1);
ext = strrchr(bmp_filename, '.');
strncpy(ext, DIFF_PBI_FILE_EXTENSION, strlen(DIFF_PBI_FILE_EXTENSION) + 1);
cl_assert(tests_write_gbitmap_to_pbi(diff_bmp, bmp_filename));
}
}
// Write the actual output to the filename-actual.png
strncpy(bmp_filename, filename, PATH_STRING_LENGTH - strlen(ACTUAL_PBI_FILE_EXTENSION) - 1);
char *ext = strrchr(bmp_filename, '.');
strncpy(ext, ACTUAL_PBI_FILE_EXTENSION, strlen(ACTUAL_PBI_FILE_EXTENSION) + 1);
cl_assert(tests_write_gbitmap_to_pbi(actual_bmp, bmp_filename));
}
// declared in gbitmap.c
GBitmap *prv_gbitmap_create_blank_internal_no_platform_checks(GSize size, GBitmapFormat format);
// Compare two bitmap and return whether or not they are the same
// Note that if both passed bitmaps are NULL, this test will succeed!
bool gbitmap_eq(GBitmap *actual_bmp, GBitmap *expected_bmp, const char *filename) {
bool rc = false;
GBitmap *diff_bmp = NULL;
if (!actual_bmp && !expected_bmp) {
return true;
} else if (!actual_bmp || !expected_bmp) {
goto done;
}
if (!grect_equal(&actual_bmp->bounds, &expected_bmp->bounds)) {
printf("Unmatched bounds\n");
printf("\tExpected: ");
printf(GRECT_PRINTF_FORMAT, GRECT_PRINTF_FORMAT_EXPLODE(expected_bmp->bounds));
printf("\n\tGot: ");
printf(GRECT_PRINTF_FORMAT, GRECT_PRINTF_FORMAT_EXPLODE(actual_bmp->bounds));
goto done;
}
uint8_t actual_bmp_palette_size = gbitmap_get_palette_size(gbitmap_get_format(actual_bmp));
uint8_t expected_bmp_palette_size = gbitmap_get_palette_size(gbitmap_get_format(expected_bmp));
uint8_t *expected_bmp_data = (uint8_t *)expected_bmp->addr;
uint8_t actual_bmp_bpp = gbitmap_get_bits_per_pixel(gbitmap_get_format(actual_bmp));
uint8_t expected_bmp_bpp = gbitmap_get_bits_per_pixel(gbitmap_get_format(expected_bmp));
const int16_t start_y = actual_bmp->bounds.origin.y;
const int16_t end_y = start_y + actual_bmp->bounds.size.h;
rc = true;
// Create a bitmap for the diff image - force 8-bit
// The diff image contains first the actual image, then the diff image, and then the expected image
// These images are separated by one pixel column (transparent)
GSize diff_bmp_size = actual_bmp->bounds.size;
diff_bmp_size.w = (3 * diff_bmp_size.w) + 2; // 2 pixels to divide the three images
diff_bmp = prv_gbitmap_create_blank_internal_no_platform_checks(diff_bmp_size, GBitmapFormat8Bit);
if (!diff_bmp) {
printf("Unable to create diff bitmap\n");
rc = false;
goto done;
}
for (int y = start_y; y < end_y; ++y) {
uint8_t *line = ((uint8_t*)diff_bmp->addr) + (diff_bmp->row_size_bytes * y);
// TODO: PBL-20932 Add 1-bit and palletized support
if (actual_bmp->info.format == GBitmapFormat8Bit) {
line[(diff_bmp->row_size_bytes / 3) + 1] = GColorClear.argb; // Separator pixel between images
line[(2 * diff_bmp->row_size_bytes / 3) + 1] = GColorClear.argb; // Separator pixel between images
}
// Needs to be prv_gbitmap_get_data_row_info to avoid unit test mocked version
const GBitmapDataRowInfo dest_row_info = prv_gbitmap_get_data_row_info(actual_bmp, y);
const int16_t start_x = MAX(actual_bmp->bounds.origin.x, dest_row_info.min_x);
const int16_t end_x = MIN(grect_get_max_x(&actual_bmp->bounds), dest_row_info.max_x + 1);
const int16_t y_line = 0; // line is constant zero below now that we are retrieving row
if (end_x < start_x) {
continue;
}
for (int x = start_x; x < end_x; ++x) {
uint8_t *actual_bmp_data = dest_row_info.data;
uint8_t actual_bmp_val = prv_raw_image_get_value_for_format(actual_bmp_data, x, y_line,
actual_bmp->row_size_bytes,
actual_bmp_bpp,
actual_bmp->info.format);
uint8_t expected_bmp_val = prv_raw_image_get_value_for_format(expected_bmp_data, x, y,
expected_bmp->row_size_bytes,
expected_bmp_bpp,
expected_bmp->info.format);
GColor8 actual_bmp_color = prv_convert_to_gcolor8(actual_bmp->info.format,
actual_bmp_val, actual_bmp->palette);
GColor8 expected_bmp_color = prv_convert_to_gcolor8(expected_bmp->info.format,
expected_bmp_val, expected_bmp->palette);
if (!gcolor_equal(actual_bmp_color, expected_bmp_color)) {
if (rc) {
// Only print out the first mismatch
printf("Mismatch at x: %d y: %d\n", x, y);
printf("value for end_x was:%d\n", end_x);
printf("format was %d\n", actual_bmp->info.format);
}
rc = false;
}
// TODO: PBL-20932 Add 1-bit and palletized support
if (actual_bmp->info.format == GBitmapFormat8Bit) {
if (actual_bmp_color.argb != expected_bmp_color.argb) {
GColor8 diff_bmp_color = DIFF_COLOR;
line[(diff_bmp->row_size_bytes / 3) + x + 1] = diff_bmp_color.argb;
} else {
line[(diff_bmp->row_size_bytes / 3) + x + 1] = actual_bmp_color.argb;
}
// Fill in the actual and expected pixels on either side of the diff image
line[x] = actual_bmp_color.argb;
line[(2 * diff_bmp->row_size_bytes / 3) + x + 1] = expected_bmp_color.argb;
}
}
}
done:
if (!rc) {
prv_write_diff_to_file(filename, expected_bmp, actual_bmp, diff_bmp);
}
gbitmap_destroy(diff_bmp);
return rc;
}
// Compare the given gbitmap to a gbmitmap loaded from a PBI in the filename given.
bool gbitmap_pbi_eq_with_bounds(GBitmap *bmp, const char *filename, const GRect *bounds) {
GBitmap *pbi_bmp = get_gbitmap_from_pbi(filename);
if (pbi_bmp && bounds) {
pbi_bmp->bounds = *bounds;
}
bool rc = gbitmap_eq(bmp, pbi_bmp, filename);
gbitmap_destroy(pbi_bmp);
return rc;
}
bool gbitmap_pbi_eq(GBitmap *bmp, const char *filename) {
return gbitmap_pbi_eq_with_bounds(bmp, filename, NULL);
}
size_t load_file(const char* filename, uint8_t** data) {
char full_path[PATH_STRING_LENGTH];
snprintf(full_path, sizeof(full_path), "%s/%s", TEST_IMAGES_PATH, filename);
FILE *file = fopen(full_path,"rb");
if(file == NULL){
printf("Error: couldn't open file: %s\n", filename);
cl_assert(false);
}
fseek(file,0,SEEK_END);
int data_size = ftell(file);
fseek(file,0,SEEK_SET);
*data = (unsigned char*)malloc(data_size);
fread(*data,1, data_size, file);
fclose(file);
return data_size;
}
// Mask flags to indicate what to setup in the draw_state of GContext within setup_test_context
#define CTX_FLAG_DS_ALL 0x00000010
#define CTX_FLAG_DS_CLIP_BOX 0x00000020
#define CTX_FLAG_DS_DRAWING_BOX 0x00000040
#define CTX_FLAG_DS_STROKE_COLOR 0x00000080
#define CTX_FLAG_DS_FILL_COLOR 0x00000100
#define CTX_FLAG_DS_TEXT_COLOR 0x00000200
#define CTX_FLAG_DS_COMPOSITING_MODE 0x00000400
#define CTX_FLAG_DS_ANTIALIASED 0x00000800
#define CTX_FLAG_DS_STROKE_WIDTH 0x00001000
void setup_test_context(GContext* ctx, uint32_t flags, GDrawState *draw_state, bool *lock) {
if (draw_state) {
if (flags & CTX_FLAG_DS_CLIP_BOX) {
ctx->draw_state.clip_box = draw_state->clip_box;
}
if (flags & CTX_FLAG_DS_DRAWING_BOX) {
ctx->draw_state.drawing_box = draw_state->drawing_box;
}
if (flags & CTX_FLAG_DS_STROKE_COLOR) {
graphics_context_set_stroke_color(ctx, draw_state->stroke_color);
}
if (flags & CTX_FLAG_DS_FILL_COLOR) {
graphics_context_set_fill_color(ctx, draw_state->fill_color);
}
if (flags & CTX_FLAG_DS_TEXT_COLOR) {
graphics_context_set_text_color(ctx, draw_state->text_color);
}
if (flags & CTX_FLAG_DS_COMPOSITING_MODE) {
graphics_context_set_compositing_mode(ctx, draw_state->compositing_mode);
}
if (flags & CTX_FLAG_DS_ANTIALIASED) {
#if PBL_COLOR
graphics_context_set_antialiased(ctx, draw_state->antialiased);
#endif
}
if (flags & CTX_FLAG_DS_STROKE_WIDTH) {
graphics_context_set_stroke_width(ctx, draw_state->stroke_width);
}
}
if (lock) {
ctx->lock = lock;
}
}
GBitmap* setup_pbi_test(const char *filename) {
uint8_t *pbi_data = NULL;
size_t pbi_size = 0;
pbi_size = load_file(filename, &pbi_data);
cl_assert(pbi_size > 0);
cl_assert(pbi_data);
return gbitmap_create_with_data(pbi_data);
}
static GBitmap* setup_png_test(const char *filename) {
uint8_t *png_data = NULL;
size_t png_size = 0;
png_size = load_file(filename, &png_data);
cl_assert(png_size > 0);
cl_assert(png_data);
return gbitmap_create_from_png_data(png_data, png_size);
}
void setup_test_aa_sw(GContext *ctx, FrameBuffer *fb, GRect clip_box, GRect drawing_box,
bool antialiased, uint8_t stroke_width) {
test_graphics_context_reset(ctx, fb);
GDrawState draw_state = {
.clip_box = clip_box,
.drawing_box = drawing_box,
#if PBL_COLOR
.antialiased = antialiased,
#endif
.stroke_width = stroke_width
};
setup_test_context(ctx,
(CTX_FLAG_DS_CLIP_BOX | CTX_FLAG_DS_DRAWING_BOX |
CTX_FLAG_DS_ANTIALIASED | CTX_FLAG_DS_STROKE_WIDTH),
&draw_state, NULL);
}
#if PLATFORM_SPALDING
bool gbitmap_8bit_to_8bit_circular(GBitmap *bitmap) {
// Only allow conversion of 8Bit 180x180 rectangular bitmaps to circular
if (!bitmap || (gbitmap_get_format(bitmap) != GBitmapFormat8Bit) ||
(bitmap->bounds.size.w != DISP_COLS) || (bitmap->bounds.size.h != DISP_COLS)) {
return false;
}
// Using realloc or copying to a new buffer has high memory overhead
// attempt to shuffle bytes in place to allow 3rd party watchapps use
uint8_t *data = gbitmap_get_data(bitmap);
// Convert format and link to data_row_infos table
bitmap->info.format = GBitmapFormat8BitCircular;
bitmap->data_row_infos = g_gbitmap_spalding_data_row_infos;
for (uint32_t y = 0; y < DISP_ROWS; y++) {
GBitmapDataRowInfo row_info = gbitmap_get_data_row_info(bitmap, y);
// copy (using memmove for overlapping region) valid bytes between min_x and max_x
memmove(&row_info.data[row_info.min_x], &data[y * DISP_COLS + row_info.min_x],
row_info.max_x - row_info.min_x + 1);
}
return true;
}
#endif
+132
View File
@@ -0,0 +1,132 @@
/*
* 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.
*/
#pragma once
#include "applib/graphics/gtypes.h"
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#define PATH_STRING_LENGTH 512
extern GBitmapDataRowInfo prv_gbitmap_get_data_row_info(const GBitmap *bitmap, uint16_t y);
bool write_gbitmap_to_pbi(GBitmap *bmp, const char *filepath, const char *pbi2png_path) {
char pbi_path[PATH_STRING_LENGTH];
char png_path[PATH_STRING_LENGTH];
char *ext = NULL;
strncpy(pbi_path, filepath, sizeof(pbi_path));
ext = strrchr(pbi_path, '.');
strncpy(ext + 1, "pbi", 3);
strncpy(png_path, filepath, sizeof(png_path));
ext = strrchr(png_path, '.');
strncpy(ext + 1, "png", 3);
FILE *file = fopen(pbi_path, "w");
if (!file) {
printf("Unable to open file: %s\n", pbi_path);
return false;
}
// Just in case this output bitmap was created by hand.
bmp->info.version = GBITMAP_VERSION_CURRENT;
// PBL-24228 Support Circular PBIs
uint16_t info_flags = bmp->info_flags;
#ifdef PLATFORM_SPALDING
if(bmp->info.format == GBitmapFormat8BitCircular) {
// Have to force output format to 8Bit;
((BitmapInfo*)&info_flags)->format = GBitmapFormat8Bit;
}
#endif
// use entire bounds to include entire image
GRect entire_bounds = GRect(0, 0,
bmp->bounds.origin.x + bmp->bounds.size.w,
bmp->bounds.origin.y + bmp->bounds.size.h);
fwrite(&bmp->row_size_bytes, sizeof(bmp->row_size_bytes), 1, file);
fwrite(&info_flags, 2, 1, file);
fwrite(&entire_bounds, sizeof(GRect), 1, file);
#ifdef PLATFORM_SPALDING
if(bmp->info.format == GBitmapFormat8BitCircular) {
for (int y = 0; y < entire_bounds.size.h; ++y) {
// 8-Bit circular buffer is centered in padded rows, so just grab row and write DISP_COLS
const GBitmapDataRowInfo dest_row_info = prv_gbitmap_get_data_row_info(bmp, y);
uint8_t *bmp_row = dest_row_info.data;
int x = 0;
const uint8_t blank = 0;
// PBL-24229 missing mask: data outside of circle is garbage from previous and next rows
// Pad with blanks before min_x
while (x < dest_row_info.min_x) {
fwrite(&blank, 1, 1, file);
x++;
}
if (x <= dest_row_info.max_x) {
int length = dest_row_info.max_x - x + 1;
fwrite(&bmp_row[dest_row_info.min_x], 1, length, file);
x += length;
}
// Pad with blanks after max_x
while (x < entire_bounds.size.w) {
fwrite(&blank, 1, 1, file);
x++;
}
}
} else {
#endif
size_t data_size = bmp->row_size_bytes * (entire_bounds.size.h);
fwrite(bmp->addr, 1, data_size, file);
#ifdef PLATFORM_SPALDING
}
#endif
uint8_t palette_size = gbitmap_get_palette_size(gbitmap_get_format(bmp));
if (palette_size > 0) {
fwrite(bmp->palette, 1, palette_size * sizeof(*bmp->palette), file);
}
fclose(file);
printf("PBI file written to: %s\n", pbi_path);
int pid = fork();
if (pid == 0) {
char *args[] = {
"python",
(char *)pbi2png_path,
(char *)pbi_path,
(char *)png_path,
NULL,
};
execvp("python", args);
perror("execv"); // Error internal to execve
exit(EXIT_FAILURE);
}
int status;
if (wait(&status) >= 0 && !WIFEXITED(status)) {
printf("FAILURE: pbi2png.py process exited with %d status. PNG file not written\n",
WEXITSTATUS(status));
} else {
printf("PNG file written to: %s\n", png_path);
}
return true;
}
+199
View File
@@ -0,0 +1,199 @@
/*
* 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 "weather_app_resources.h"
#include "kernel/pbl_malloc.h"
#include "applib/graphics/gdraw_command_private.h"
#include "applib/ui/animation.h"
#include "util/size.h"
#include <string.h>
#if !TINTIN_FORCE_FIT
#if defined(UNITTEST)
#include <stdio.h>
#endif
GDrawCommandImage *weather_app_resource_create_cloud() {
GPoint c0_points[] =
{{22, 6}, {28, 1}, {39, 1}, {46, 10}, {46, 23}, {4, 23}, {4, 14}, {12, 6}};
GPoint c1_points[] =
{{17, 12}, {28, 1}, {39, 1}, {46, 10}, {46, 23}, {4, 23}, {4, 14}, {12, 6}, {22, 6}};
GPoint c2_points[] = {{ 5, 35}, {17, 23}};
GPoint c3_points[] = {{ 9, 43}, {29, 23}};
GPoint c4_points[] = {{23, 41}, {41, 23}};
const uint16_t num_commands = 5;
GDrawCommandImage *image = task_malloc(
sizeof(GDrawCommandImage) +
sizeof(GDrawCommand) * num_commands +
sizeof(c0_points) + sizeof(c1_points) + sizeof(c2_points)+ sizeof(c3_points) +
sizeof(c4_points));
*image = (GDrawCommandImage){
.version = 1,
.size = GSize(48, 48),
.command_list = (GDrawCommandList) {
.num_commands = num_commands,
},
};
GDrawCommandList *list = &image->command_list;
GDrawCommand *c0 = gdraw_command_list_get_command(list, 0);
*c0 = (GDrawCommand){
.type = GDrawCommandTypePath,
.fill_color = GColorWhite,
.num_points = ARRAY_LENGTH(c0_points),
};
memcpy(c0->points, c0_points, sizeof(c0_points));
GDrawCommand *c1 = gdraw_command_list_get_command(list, 1);
*c1 = (GDrawCommand){
.type = GDrawCommandTypePath,
.path_open = true,
.stroke_color = GColorBlack,
.stroke_width = 3,
.num_points = ARRAY_LENGTH(c1_points),
};
memcpy(c1->points, c1_points, sizeof(c1_points));
GDrawCommand *c2 = gdraw_command_list_get_command(list, 2);
*c2 = (GDrawCommand){
.type = GDrawCommandTypePath,
.hidden = false,
.stroke_color = GColorBlack,
.stroke_width = 3,
.path_open = true,
.num_points = ARRAY_LENGTH(c2_points),
};
memcpy(c2->points, c2_points, sizeof(c2_points));
GDrawCommand *c3 = gdraw_command_list_get_command(list, 3);
*c3 = *c2;
memcpy(c3->points, c3_points, sizeof(c3_points));
GDrawCommand *c4 = gdraw_command_list_get_command(list, 4);
*c4 = *c2;
memcpy(c4->points, c4_points, sizeof(c4_points));
return image;
}
#define COPY_POINTS(target, source) \
for (size_t i=0; i < ARRAY_LENGTH(source); i++) { \
(target)[i] = (source)[i]; \
}
GDrawCommandImage *weather_app_resource_create_cloud_25px(void) {
GDrawCommandImage *result = weather_app_resource_create_cloud();
result->size = GSize(25, 25);
GPoint c0_points[] =
{ {10, 4}, {13, 1}, {19, 1}, {23, 6}, {23, 13}, {1, 13}, {1, 8}, {4, 4}};
GPoint c1_points[] =
{{7, 7}, {13, 1}, {19, 1}, {23, 6}, {23, 13}, {1, 13}, {1, 8}, {4, 4}, {10, 4}};
GPoint c2_points[] = {{1, 19}, {7, 13}};
GPoint c3_points[] = {{3, 24}, {14, 13}};
GPoint c4_points[] = {{11, 23}, {21, 13}};
COPY_POINTS(gdraw_command_list_get_command(&result->command_list, 0)->points, c0_points)
COPY_POINTS(gdraw_command_list_get_command(&result->command_list, 1)->points, c1_points)
COPY_POINTS(gdraw_command_list_get_command(&result->command_list, 2)->points, c2_points)
COPY_POINTS(gdraw_command_list_get_command(&result->command_list, 3)->points, c3_points)
COPY_POINTS(gdraw_command_list_get_command(&result->command_list, 4)->points, c4_points)
return result;
}
GDrawCommandImage *weather_app_resource_create_sun(void) {
GPoint c0_points[] = {{47, 23}, {5, 23}};
GPoint c1_points[] = {{13, 10}, {39, 36}};
GPoint c2_points[] = {{26, 2}, {26, 44}};
GPoint c3_points[] = {{39, 10}, {13, 36}};
GPoint c4_points[] = {{21, 11}, {31, 11}, {39, 18}, {39, 28}, {31, 36}, {21, 36}, {13, 28},
{13, 18}};
const uint16_t num_commands = 5;
GDrawCommandImage *image = task_malloc(
sizeof(GDrawCommandImage) +
sizeof(GDrawCommand) * num_commands +
sizeof(c0_points) + sizeof(c1_points) + sizeof(c2_points) + sizeof(c3_points) +
sizeof(c4_points));
*image = (GDrawCommandImage){
.version = 1,
.size = GSize(48, 48),
.command_list = (GDrawCommandList) {
.num_commands = num_commands,
},
};
GDrawCommandList *list = &image->command_list;
GDrawCommand *c0 = gdraw_command_list_get_command(list, 0);
*c0 = (GDrawCommand){
.type = GDrawCommandTypePath,
.stroke_color = GColorBlack,
.stroke_width = 3,
.path_open = true,
.num_points = ARRAY_LENGTH(c0_points),
};
memcpy(c0->points, c0_points, sizeof(c0_points));
GDrawCommand *c1 = gdraw_command_list_get_command(list, 1);
*c1 = *c0;
memcpy(c1->points, c1_points, sizeof(c1_points));
GDrawCommand *c2 = gdraw_command_list_get_command(list, 2);
*c2 = *c0;
memcpy(c2->points, c2_points, sizeof(c2_points));
GDrawCommand *c3 = gdraw_command_list_get_command(list, 3);
*c3 = *c0;
memcpy(c3->points, c3_points, sizeof(c3_points));
GDrawCommand *c4 = gdraw_command_list_get_command(list, 4);
*c4 = (GDrawCommand){
.type = GDrawCommandTypePath,
.stroke_color = GColorBlack,
.stroke_width = 3,
.fill_color = GColorWhite,
.num_points = ARRAY_LENGTH(c4_points),
};
memcpy(c4->points, c4_points, sizeof(c4_points));
return image;
}
GDrawCommandImage *weather_app_resource_create_sun_25px(void) {
GDrawCommandImage *result = weather_app_resource_create_sun();
result->size = GSize(25, 25);
GPoint c0_points[] = {{0, 12}, {24, 12}};
GPoint c1_points[] = {{12, 0}, {12, 24}};
GPoint c2_points[] = {{3, 3}, {21, 21}};
GPoint c3_points[] = {{3, 21}, {21, 3}};
GPoint c4_points[] = {{9, 4}, {15, 4}, {20, 9}, {20, 15}, {15, 20}, {9, 20}, {4, 15}, {4, 9}};
COPY_POINTS(gdraw_command_list_get_command(&result->command_list, 0)->points, c0_points)
COPY_POINTS(gdraw_command_list_get_command(&result->command_list, 1)->points, c1_points)
COPY_POINTS(gdraw_command_list_get_command(&result->command_list, 2)->points, c2_points)
COPY_POINTS(gdraw_command_list_get_command(&result->command_list, 3)->points, c3_points)
COPY_POINTS(gdraw_command_list_get_command(&result->command_list, 4)->points, c4_points)
return result;
}
#endif
+25
View File
@@ -0,0 +1,25 @@
/*
* 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.
*/
#pragma once
#include "applib/graphics/gdraw_command_image.h"
GDrawCommandImage *weather_app_resource_create_cloud(void);
GDrawCommandImage *weather_app_resource_create_sun(void);
GDrawCommandImage *weather_app_resource_create_cloud_25px(void);
GDrawCommandImage *weather_app_resource_create_sun_25px(void);
+703
View File
@@ -0,0 +1,703 @@
from waftools.pebble_test import clar, get_bitdepth_for_platform
import waflib.Utils
def _process_template(task):
from string import Template
template = Template(open(task.inputs[0].srcpath(), 'r').read())
processed = template.safe_substitute(BIT_DEPTH_NAME=task.generator.bit_depth_name)
waflib.Utils.writef(task.outputs[0].srcpath(), processed)
def graphics_process_test_template(ctx, source, bit_depth):
path = ctx.path.find_node(source).srcpath()
bit_depth_name = "{}bit".format(bit_depth)
# Format the output filename.
# - ".template.c" => ".auto.c"
# - Add bit depth to filename
# eg. "test_graphics.template.c" => "test_graphics_8bit.c"
split = source.replace("template.", "").split('.')
target_name = split[0] + '_' + bit_depth_name + '.' + split[1]
out_path = ctx.path.get_bld().make_node(target_name)
ctx(name='generate_test_from_template', rule=_process_template, source=ctx.path.find_node(source),
target=out_path, bit_depth_name=bit_depth_name)
return out_path
def build(ctx):
graphics_sources_ant_glob = \
" src/fw/applib/graphics/${BITDEPTH}_bit/framebuffer.c" \
" src/fw/applib/graphics/framebuffer.c" \
" src/fw/applib/graphics/gbitmap.c" \
" src/fw/applib/graphics/gcolor_definitions.c" \
" src/fw/applib/graphics/gtypes.c" \
" src/fw/applib/graphics/graphics.c" \
" src/fw/applib/graphics/graphics_bitmap.c" \
" src/fw/applib/graphics/gtransform.c" \
" src/fw/applib/graphics/bitblt.c" \
" src/fw/applib/graphics/${BITDEPTH}_bit/bitblt_private.c" \
" src/fw/applib/ui/layer.c" \
" src/fw/applib/ui/bitmap_layer.c" \
" tests/fakes/fake_display.c" \
" tests/fakes/fake_gbitmap_png.c" \
" src/fw/applib/graphics/graphics_private.c" \
" src/fw/applib/graphics/graphics_private_raw.c" \
" src/fw/applib/graphics/graphics_circle.c" \
" src/fw/applib/graphics/graphics_line.c" \
" src/fw/applib/graphics/gpath.c"
graphics_test_sources = [
"test_framebuffer.template.c",
"test_graphics_draw_pixel.template.c",
"test_graphics_draw_stroke.template.c",
"test_graphics_draw_rect.template.c",
"test_graphics_fill_rect.template.c",
"test_graphics_draw_circle.template.c",
"test_graphics_stroke_circle.template.c",
"test_graphics_fill_circle.template.c",
"test_graphics_draw_round_rect.template.c",
"test_graphics_gpath.template.c",
"test_graphics_gtransform.template.c",
"test_graphics_context.template.c",
"test_blending.template.c",
"test_graphics_draw_stroke_precise.template.c"
]
for platform in ['tintin', 'snowy']:
bit_depth = get_bitdepth_for_platform(ctx, platform)
defines = ctx.env.test_image_defines
for test in graphics_test_sources:
generated_source = graphics_process_test_template(ctx, test, bit_depth)
clar(ctx,
sources_ant_glob=graphics_sources_ant_glob,
test_sources=[generated_source],
add_includes=[ctx.path.abspath()],
defines=defines,
platforms=[platform])
clar(ctx,
sources_ant_glob=" src/fw/applib/graphics/${BITDEPTH}_bit/bitblt_private.c"
" src/fw/applib/graphics/${BITDEPTH}_bit/framebuffer.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/graphics/gbitmap.c"
" src/fw/applib/graphics/gbitmap_png.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/graphics.c"
" src/fw/applib/graphics/graphics_bitmap.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/gtypes.c"
" src/fw/applib/ui/bitmap_layer.c"
" src/fw/applib/ui/layer.c"
" src/fw/applib/vendor/tinflate/tinflate.c"
" src/fw/applib/vendor/uPNG/upng.c",
test_sources_ant_glob='test_graphics_draw_bitmap.c',
defines=ctx.env.test_image_defines,
platforms=['snowy', 'silk'])
templated_graphics_draw_text_sources_ant_glob = \
" src/fw/applib/graphics/{depth_dir}/framebuffer.c" \
" src/fw/applib/graphics/framebuffer.c" \
" src/fw/applib/graphics/gcolor_definitions.c" \
" src/fw/applib/graphics/gtypes.c" \
" src/fw/applib/graphics/gbitmap.c" \
" src/fw/applib/graphics/graphics.c" \
" src/fw/applib/graphics/gtransform.c" \
" src/fw/applib/graphics/bitblt.c" \
" src/fw/applib/graphics/{depth_dir}/bitblt_private.c" \
" src/fw/applib/ui/layer.c" \
" tests/fakes/fake_gbitmap_get_data_row.c" \
" tests/fakes/fake_gbitmap_png.c" \
" tests/fakes/fake_display.c" \
" src/fw/applib/graphics/text_render.c" \
" src/fw/applib/graphics/utf8.c" \
" src/fw/applib/graphics/text_layout.c" \
" src/fw/applib/graphics/perimeter.c" \
" src/fw/applib/graphics/text_resources.c" \
" src/fw/flash_region/flash_region.c" \
" src/fw/flash_region/filesystem_regions.c" \
" src/fw/system/hexdump.c" \
" src/fw/applib/fonts/codepoint.c" \
" src/fw/resource/resource.c" \
" src/fw/resource/resource_storage.c" \
" src/fw/resource/resource_storage_builtin.c" \
" src/fw/resource/resource_storage_file.c" \
" src/fw/resource/resource_storage_flash.c" \
" src/fw/services/normal/filesystem/flash_translation.c" \
" src/fw/services/normal/filesystem/pfs.c" \
" src/fw/services/normal/filesystem/app_file.c" \
" src/fw/util/crc8.c" \
" src/fw/util/legacy_checksum.c" \
" src/fw/drivers/flash/flash_crc.c" \
" tests/fakes/fake_fonts.c" \
" tests/fakes/fake_rtc.c" \
" tests/fakes/fake_spi_flash.c" \
" tests/fixtures/resources/builtin_resources.auto.c" \
" tests/fixtures/resources/pfs_resource_table.c" \
" src/fw/applib/graphics/graphics_private.c" \
" src/fw/applib/graphics/graphics_private_raw.c" \
" src/fw/applib/graphics/graphics_circle.c" \
" src/fw/applib/graphics/graphics_line.c"
graphics_draw_text_test_sources = [
"test_graphics_draw_text.template.c"
]
for platform in ['tintin', 'snowy']:
bit_depth = 1 if platform == 'tintin' else 8
depth_dir = '{}_bit'.format(bit_depth)
graphics_sources_ant_glob = templated_graphics_draw_text_sources_ant_glob.format(depth_dir=depth_dir)
defines = ctx.env.test_image_defines
for test in graphics_draw_text_test_sources:
generated_source = graphics_process_test_template(ctx, test, bit_depth)
clar(ctx,
sources_ant_glob=graphics_sources_ant_glob,
test_sources=[generated_source],
add_includes=[ctx.path.abspath()],
defines=defines,
override_includes=['dummy_board'],
platforms=[platform])
clar(ctx,
sources_ant_glob=templated_graphics_draw_text_sources_ant_glob.format(depth_dir="8_bit"),
test_sources_ant_glob='test_graphics_draw_text_flow.c',
defines = ctx.env.test_image_defines,
override_includes=['dummy_board'])
# All tests that are bit-depth agnostic should follow.
clar(ctx,
sources_ant_glob = \
" src/fw/applib/graphics/gcolor_definitions.c" \
" src/fw/applib/graphics/gtypes.c", \
test_sources_ant_glob = "test_graphics_colors.c")
clar(ctx,
sources_ant_glob = \
" src/fw/applib/graphics/gtypes.c",
test_sources_ant_glob = "test_gtypes.c",
platforms=["tintin", "snowy", "spalding"])
clar(ctx,
sources_ant_glob = \
" src/fw/applib/graphics/gtypes.c" \
" src/fw/applib/graphics/graphics_circle.c",
test_sources_ant_glob = "test_graphics_circle.c")
clar(ctx,
sources_ant_glob = \
" src/fw/applib/graphics/gtypes.c" \
" tests/fakes/fake_gbitmap_png.c" \
" tests/fakes/fake_resource_syscalls.c" \
" src/fw/applib/graphics/gbitmap.c",
test_sources_ant_glob = "test_gbitmap_data_row_info.c")
clar(ctx,
sources_ant_glob = \
" src/fw/applib/graphics/gbitmap.c" \
" src/fw/applib/graphics/gtypes.c" \
" tests/fakes/fake_applib_resource.c",
test_sources_ant_glob = "test_gbitmap_resource_validation.c")
clar(ctx,
sources_ant_glob = \
" src/fw/applib/graphics/gtypes.c" \
" tests/fakes/fake_gbitmap_png.c" \
" tests/fakes/fake_resource_syscalls.c" \
" src/fw/applib/graphics/gbitmap.c",
test_sources_ant_glob = "test_gbitmap_formats.c",
platforms=["tintin", "snowy", "spalding"])
clar(ctx,
sources_ant_glob = \
" src/fw/applib/graphics/framebuffer.c" \
" src/fw/applib/graphics/gbitmap.c" \
" src/fw/applib/graphics/8_bit/framebuffer.c" \
" src/fw/applib/graphics/gcolor_definitions.c" \
" src/fw/applib/graphics/graphics.c" \
" src/fw/applib/graphics/graphics_bitmap.c" \
" src/fw/applib/graphics/gtypes.c" \
" tests/fakes/fake_gbitmap_png.c" \
" tests/fakes/fake_resource_syscalls.c" \
" tests/fakes/fake_applib_resource.c" \
" tests/stubs/stubs_bitblt.c",
test_sources_ant_glob = "test_gbitmap_processor.c")
clar(ctx,
sources_ant_glob = \
" src/fw/applib/graphics/gtypes.c" \
" src/fw/applib/graphics/gcolor_definitions.c" \
" src/fw/applib/graphics/gbitmap.c" \
" src/fw/applib/graphics/graphics.c" \
" src/fw/applib/graphics/graphics_private.c" \
" src/fw/applib/graphics/graphics_private_raw.c" \
" src/fw/applib/graphics/bitblt.c" \
" src/fw/applib/graphics/8_bit/bitblt_private.c", \
test_sources_ant_glob = "test_graphics_blending.c",
platforms=['snowy'])
clar(ctx,
sources_ant_glob = \
" src/fw/applib/graphics/gcolor_definitions.c" \
" src/fw/applib/graphics/gdraw_command.c" \
" src/fw/applib/graphics/gdraw_command_list.c" \
" src/fw/applib/graphics/gdraw_command_image.c" \
" src/fw/applib/graphics/gdraw_command_frame.c",
test_sources_ant_glob = "test_gdraw_command.c")
clar(ctx,
sources_ant_glob = \
" src/fw/applib/graphics/gcolor_definitions.c" \
" src/fw/applib/graphics/gdraw_command.c" \
" src/fw/applib/graphics/gdraw_command_list.c" \
" src/fw/applib/graphics/gdraw_command_frame.c" \
" src/fw/applib/graphics/gdraw_command_sequence.c",
test_sources_ant_glob = "test_gdraw_command_sequence.c")
clar(ctx,
sources_ant_glob = \
" src/fw/applib/graphics/gcolor_definitions.c" \
" src/fw/applib/graphics/gbitmap.c" \
" src/fw/applib/graphics/gtypes.c" \
" src/fw/applib/graphics/gdraw_command.c" \
" src/fw/applib/graphics/gdraw_command_list.c" \
" src/fw/applib/graphics/gdraw_command_frame.c" \
" src/fw/applib/graphics/gdraw_command_image.c" \
" src/fw/applib/graphics/gdraw_command_sequence.c" \
" tests/fakes/fake_gbitmap_png.c" \
" tests/fakes/fake_resource_syscalls.c" \
" tests/fakes/fake_applib_resource.c",
runtime_deps=ctx.env.test_pdcs,
defines=ctx.env.test_image_defines,
test_sources_ant_glob = "test_gdraw_command_resources.c")
clar(ctx,
sources_ant_glob = \
" src/fw/applib/graphics/8_bit/framebuffer.c" \
" src/fw/applib/graphics/framebuffer.c" \
" src/fw/applib/graphics/gcolor_definitions.c" \
" src/fw/applib/graphics/gtypes.c" \
" src/fw/applib/graphics/gbitmap.c" \
" src/fw/applib/graphics/graphics.c" \
" src/fw/applib/graphics/graphics_private.c" \
" src/fw/applib/graphics/graphics_private_raw.c" \
" src/fw/applib/graphics/graphics_line.c" \
" src/fw/applib/graphics/graphics_circle.c" \
" src/fw/applib/graphics/gpath.c" \
" src/fw/applib/graphics/bitblt.c" \
" src/fw/applib/graphics/8_bit/bitblt_private.c" \
" src/fw/applib/ui/animation_interpolate.c" \
" src/fw/applib/ui/animation_timing.c" \
" tests/fakes/fake_gbitmap_png.c" \
" tests/fw/graphics/weather_app_resources.c" \
" src/fw/applib/graphics/gdraw_command.c" \
" src/fw/applib/graphics/gdraw_command_frame.c" \
" src/fw/applib/graphics/gdraw_command_list.c" \
" src/fw/applib/graphics/gdraw_command_image.c" \
" src/fw/applib/graphics/gdraw_command_transforms.c", \
test_sources_ant_glob = "test_gdraw_command_transforms.c",
defines=ctx.env.test_image_defines,
override_includes=['dummy_board'])
clar(ctx,
sources_ant_glob =
" src/fw/applib/vendor/uPNG/upng.c"
" src/fw/applib/vendor/tinflate/tinflate.c"
" src/fw/applib/graphics/gbitmap.c" \
" src/fw/applib/graphics/gbitmap_png.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/8_bit/framebuffer.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/graphics/graphics.c"
" src/fw/applib/graphics/8_bit/bitblt_private.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/gtypes.c",
runtime_deps=ctx.env.pdcs2png_test_files,
test_sources_ant_glob="test_pdc.c",
defines=ctx.env.test_image_defines)
clar(ctx,
sources_ant_glob =
" src/fw/applib/vendor/uPNG/upng.c"
" src/fw/applib/vendor/tinflate/tinflate.c"
" src/fw/applib/graphics/gbitmap.c" \
" src/fw/applib/graphics/gbitmap_png.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/8_bit/framebuffer.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/graphics/graphics.c"
" src/fw/applib/graphics/8_bit/bitblt_private.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/gtypes.c",
test_sources_ant_glob="test_png.c",
defines=ctx.env.test_image_defines,
runtime_deps=filter(lambda x: 'test_png__' in str(x), ctx.env.test_pngs))
clar(ctx,
sources_ant_glob =
" src/fw/applib/vendor/uPNG/upng.c"
" src/fw/applib/vendor/tinflate/tinflate.c"
" src/fw/applib/graphics/gbitmap.c" \
" src/fw/applib/graphics/gbitmap_png.c"
" src/fw/applib/graphics/gbitmap_sequence.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/8_bit/bitblt_private.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/graphics/graphics.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/gtypes.c"
" src/fw/board/displays/display_spalding.c"
" tests/fakes/fake_resource_syscalls.c",
test_sources_ant_glob="test_gbitmap_sequence.c",
defines=ctx.env.test_image_defines,
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis,
platforms=['tintin', 'spalding'])
clar(ctx,
sources_ant_glob =
" src/fw/applib/vendor/uPNG/upng.c"
" src/fw/applib/vendor/tinflate/tinflate.c"
" src/fw/applib/graphics/gtypes.c"
" src/fw/applib/graphics/gbitmap.c" \
" tests/fakes/fake_gbitmap_png.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/8_bit/framebuffer.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/graphics/8_bit/bitblt_private.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/graphics.c",
test_sources_ant_glob="test_pbi.c",
defines=ctx.env.test_image_defines,
runtime_deps=filter(lambda x: 'test_pbi__' in str(x), ctx.env.test_pbis))
clar(ctx,
sources_ant_glob =
" src/fw/applib/vendor/uPNG/upng.c"
" src/fw/applib/vendor/tinflate/tinflate.c"
" src/fw/applib/graphics/gtypes.c"
" src/fw/applib/graphics/gbitmap.c" \
" src/fw/applib/graphics/gbitmap_png.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/8_bit/framebuffer.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/graphics/8_bit/bitblt_private.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/graphics.c",
test_sources_ant_glob="test_palettized_conversion.c",
defines=ctx.env.test_image_defines,
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis)
clar(ctx,
sources_ant_glob=(
"src/fw/applib/graphics/gtypes.c "
"src/fw/apps/system_apps/timeline/text_node.c "
"tests/stubs/stubs_text_layout.c "
),
test_sources_ant_glob="test_graphics_text_node.c")
clar(ctx,
sources_ant_glob =
" src/fw/applib/graphics/perimeter.c"
" src/fw/applib/graphics/gtypes.c",
test_sources_ant_glob="test_perimeter.c",
test_libs=['m'],
defines=defines,
platforms=['snowy', 'spalding'])
clar(ctx,
sources_ant_glob =
" src/fw/applib/graphics/gtypes.c"
" src/fw/applib/graphics/gbitmap.c"
" tests/fakes/fake_gbitmap_png.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/${BITDEPTH}_bit/framebuffer.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/ui/layer.c"
" src/fw/applib/graphics/${BITDEPTH}_bit/bitblt_private.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/graphics.c",
test_sources_ant_glob="test_graphics_draw_line.c",
defines=ctx.env.test_image_defines,
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis,
platforms=['tintin', 'snowy'])
clar(ctx,
sources_ant_glob =
" src/fw/applib/vendor/uPNG/upng.c"
" src/fw/applib/vendor/tinflate/tinflate.c"
" src/fw/applib/graphics/gtypes.c"
" src/fw/applib/graphics/gbitmap.c" \
" src/fw/applib/graphics/gbitmap_png.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/${BITDEPTH}_bit/framebuffer.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/graphics/${BITDEPTH}_bit/bitblt_private.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/graphics.c",
test_sources_ant_glob="test_bitblt_palette_1bit.c",
defines=ctx.env.test_image_defines,
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis,
platforms=['tintin'])
clar(ctx,
sources_ant_glob =
" src/fw/applib/vendor/uPNG/upng.c"
" src/fw/applib/vendor/tinflate/tinflate.c"
" src/fw/applib/graphics/gbitmap.c"
" src/fw/applib/graphics/gbitmap_png.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/8_bit/framebuffer.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/graphics/graphics.c"
" src/fw/applib/graphics/8_bit/bitblt_private.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/gtypes.c"
" src/fw/board/displays/display_spalding.c",
test_sources_ant_glob="test_framebuffer_duma.c",
defines=ctx.env.test_image_defines,
platforms=['snowy', 'spalding'])
clar(ctx,
sources_ant_glob =
" src/fw/applib/graphics/gtypes.c"
" src/fw/applib/graphics/gbitmap.c"
" tests/fakes/fake_gbitmap_png.c"
" tests/fakes/fake_gbitmap_get_data_row.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/8_bit/framebuffer.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/ui/layer.c"
" src/fw/applib/graphics/8_bit/bitblt_private.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/graphics.c",
test_sources_ant_glob="test_graphics_draw_core.c",
defines=ctx.env.test_image_defines,
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis,
platforms=['snowy'])
clar(ctx,
sources_ant_glob =
" src/fw/applib/graphics/gtypes.c"
" src/fw/applib/graphics/gbitmap.c"
" tests/fakes/fake_gbitmap_png.c"
" tests/fakes/fake_gbitmap_get_data_row.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/8_bit/framebuffer.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/ui/layer.c"
" src/fw/applib/graphics/8_bit/bitblt_private.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/graphics.c"
" src/fw/board/displays/display_spalding.c",
test_sources_ant_glob="test_framebuffer_clipping.c",
defines=ctx.env.test_image_defines,
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis,
platforms=['spalding'])
clar(ctx,
sources_ant_glob =
" src/fw/applib/graphics/gtypes.c"
" src/fw/applib/graphics/gbitmap.c"
" tests/fakes/fake_gbitmap_png.c"
" tests/fakes/fake_gbitmap_get_data_row.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/${BITDEPTH}_bit/framebuffer.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/ui/layer.c"
" src/fw/applib/graphics/8_bit/bitblt_private.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/graphics_private_raw_mask.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/graphics_mask.c"
" src/fw/applib/graphics/graphics.c"
" src/fw/applib/ui/window_stack_animation_round.c"
" tests/stubs/stubs_animation.c"
" src/fw/services/common/compositor/default/compositor_round_flip_transitions.c"
" src/fw/applib/ui/animation_interpolate.c"
" src/fw/board/displays/display_spalding.c",
test_sources_ant_glob="test_graphics_window_stack_animation.c",
defines=ctx.env.test_image_defines + ['CAPABILITY_HAS_MASKING=1'],
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis,
platforms=['spalding'])
clar(ctx,
sources_ant_glob =
" src/fw/applib/graphics/gtypes.c"
" src/fw/applib/graphics/gbitmap.c"
" tests/fakes/fake_gbitmap_png.c"
" tests/fakes/fake_applib_resource.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/${BITDEPTH}_bit/framebuffer.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/ui/layer.c"
" src/fw/applib/graphics/8_bit/bitblt_private.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/graphics_private_raw_mask.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/graphics_mask.c"
" src/fw/applib/graphics/graphics.c"
" src/fw/board/displays/display_spalding.c",
test_sources_ant_glob="test_graphics_context_mask.c",
defines=ctx.env.test_image_defines + ['CAPABILITY_HAS_MASKING=1'],
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis,
platforms=['snowy'])
clar(ctx,
sources_ant_glob =
" src/fw/applib/graphics/gtypes.c"
" src/fw/applib/graphics/gbitmap.c"
" tests/fakes/fake_gbitmap_png.c"
" tests/fakes/fake_gbitmap_get_data_row.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/8_bit/framebuffer.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/ui/layer.c"
" src/fw/applib/graphics/8_bit/bitblt_private.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/graphics.c",
test_sources_ant_glob="test_graphics_draw_implementation.c",
defines=ctx.env.test_image_defines,
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis)
clar(ctx,
sources_ant_glob =
" src/fw/applib/vendor/uPNG/upng.c"
" src/fw/applib/vendor/tinflate/tinflate.c"
" src/fw/applib/graphics/gtypes.c"
" src/fw/applib/graphics/gbitmap.c"
" src/fw/applib/graphics/gbitmap_png.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/8_bit/framebuffer.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/graphics/8_bit/bitblt_private.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/graphics.c"
" src/fw/board/displays/display_$PLATFORM.c",
test_sources_ant_glob="test_bitblt_circular.c",
defines=ctx.env.test_image_defines,
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis,
platforms=["spalding"])
clar(ctx,
sources_ant_glob =
" src/fw/applib/graphics/gtypes.c"
" src/fw/applib/graphics/gbitmap.c"
" tests/fakes/fake_gbitmap_png.c"
" tests/fakes/fake_gbitmap_get_data_row.c"
" src/fw/applib/graphics/gcolor_definitions.c"
" src/fw/applib/graphics/${BITDEPTH}_bit/framebuffer.c"
" src/fw/applib/graphics/framebuffer.c"
" src/fw/applib/ui/layer.c"
" src/fw/applib/graphics/${BITDEPTH}_bit/bitblt_private.c"
" src/fw/applib/graphics/bitblt.c"
" src/fw/applib/graphics/graphics_private.c"
" src/fw/applib/graphics/graphics_private_raw.c"
" src/fw/applib/graphics/graphics_bitmap.c"
" src/fw/applib/graphics/graphics_circle.c"
" src/fw/applib/graphics/graphics_line.c"
" src/fw/applib/graphics/graphics.c",
test_sources_ant_glob="test_graphics_draw_rotated_bitmap.c",
defines=ctx.env.test_image_defines,
runtime_deps=ctx.env.test_pngs + ctx.env.test_pbis,
platforms=["tintin", "snowy"])
###############################
# 8-bit specific bitblt tests #
###############################
graphics_sources_ant_glob = \
" src/fw/applib/graphics/gcolor_definitions.c" \
" src/fw/applib/graphics/8_bit/framebuffer.c" \
" src/fw/applib/graphics/framebuffer.c" \
" src/fw/applib/graphics/8_bit/bitblt_private.c" \
" src/fw/applib/graphics/gbitmap.c" \
" src/fw/applib/graphics/gtypes.c" \
" src/fw/applib/graphics/graphics.c" \
" src/fw/applib/graphics/graphics_bitmap.c" \
" src/fw/applib/graphics/gtransform.c" \
" src/fw/applib/graphics/bitblt.c" \
" src/fw/applib/ui/layer.c" \
" tests/fakes/fake_gbitmap_png.c" \
" tests/fakes/fake_display.c" \
" src/fw/applib/graphics/graphics_private.c" \
" src/fw/applib/graphics/graphics_private_raw.c" \
" src/fw/applib/graphics/graphics_circle.c" \
" src/fw/applib/graphics/graphics_line.c"
graphics_test_sources_8bit = [
"test_bitblt.c",
"test_bitblt_palette.c"
]
for test in graphics_test_sources_8bit:
defines = ctx.env.test_image_defines
clar(ctx,
sources_ant_glob = graphics_sources_ant_glob,
test_sources_ant_glob = test,
defines=defines)
# vim:filetype=python