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

361 lines
11 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "applib/graphics/graphics.h"
#include "applib/graphics/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 */);
}