pebble/src/fw/applib/graphics/graphics_private_raw.c
2025-01-27 11:38:16 -08:00

339 lines
14 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 "bitblt_private.h"
#include "graphics.h"
#include "graphics_private.h"
#include "graphics_private_raw.h"
#include "graphics_private_raw_mask.h"
#include "gtypes.h"
#include "system/passert.h"
#include "util/bitset.h"
#include "util/graphics.h"
#include "util/math.h"
ALWAYS_INLINE void graphics_private_raw_blend_color_factor(const GContext *ctx, GColor *dst_color,
unsigned int data_offset,
GColor src_color, int x,
uint8_t factor) {
#if SCREEN_COLOR_DEPTH_BITS == 8
src_color.a = (uint8_t)(factor * 3 / (FIXED_S16_3_ONE.raw_value - 1));
const GColor blended_color = gcolor_alpha_blend(src_color, *dst_color);
#if CAPABILITY_HAS_MASKING
const GDrawMask *mask = ctx->draw_state.draw_mask;
graphics_private_raw_mask_apply(dst_color, mask, data_offset, x, 1, blended_color);
#else
*dst_color = blended_color;
#endif // CAPABILITY_HAS_MASKING
#endif // (SCREEN_COLOR_DEPTH_BITS == 8)
}
static ALWAYS_INLINE void prv_set_color(const GContext *ctx, GColor *dst_color,
unsigned int data_row_offset, int x, int width,
GColor src_color) {
#if CAPABILITY_HAS_MASKING
const GDrawMask *mask = ctx->draw_state.draw_mask;
graphics_private_raw_mask_apply(dst_color, mask, data_row_offset, x, width, src_color);
#else
memset(dst_color, src_color.argb, (size_t)width);
#endif // CAPABILITY_HAS_MASKING
}
// Plots row at given starting position and width, dithers grayscale colors
static void prv_assign_row_with_pattern_1bit(GBitmap *framebuffer, int16_t y, int16_t x,
int32_t width, GColor color) {
const uint32_t pattern = graphics_private_get_1bit_grayscale_pattern(color, (uint8_t) y);
uint32_t left_edge_block, right_edge_block, mask;
const uint32_t left_edge_bits_count = x % 32;
const uint32_t right_edge_bits_count = (x + width) % 32;
uint32_t *block = ((uint32_t*)framebuffer->addr) + (y * (framebuffer->row_size_bytes / 4))
+ (x / 32);
bool both_edges_in_same_block = (left_edge_bits_count + width) < 32;
if (both_edges_in_same_block) {
left_edge_block = (0xffffffff << left_edge_bits_count);
right_edge_block = right_edge_bits_count ? (0xffffffff >> (32 - right_edge_bits_count)) : 0;
mask = (left_edge_block & right_edge_block);
*(block) = (*(block) & ~mask) | (pattern & mask);
} else {
if (left_edge_bits_count) {
mask = 0xffffffff << left_edge_bits_count;
*(block) = (*(block) & ~mask) | (pattern & mask);
block++;
width -= (32 - left_edge_bits_count);
}
if (right_edge_bits_count) {
mask = 0xffffffff >> (32 - right_edge_bits_count);
*(block + (width / 32)) = (*(block + (width / 32)) & ~mask) | (pattern & mask);
width -= right_edge_bits_count;
}
if (width > 0) {
memset(block, pattern, (width / 8));
}
}
}
// ## Line blending functions:
// This function draws horizontal line with AA edges, given values have to be adjusted for
// screen coordinates and clipped according to the clip box, does not respect transparency
// on the drawn line (beside edges)
T_STATIC void prv_assign_horizontal_line_raw(GContext *ctx, int16_t y, Fixed_S16_3 x1,
Fixed_S16_3 x2, GColor color) {
PBL_ASSERTN(ctx);
GBitmap *framebuffer = &ctx->dest_bitmap;
PBL_ASSERTN(framebuffer->bounds.origin.x == 0 && framebuffer->bounds.origin.y == 0);
// Clip the line to the bitmap data row's range
const GBitmapDataRowInfo data_row_info = gbitmap_get_data_row_info(framebuffer, y);
x1.raw_value = MAX(x1.raw_value, data_row_info.min_x << FIXED_S16_3_PRECISION);
x2.raw_value = MIN(x2.raw_value, data_row_info.max_x << FIXED_S16_3_PRECISION);
if (x1.integer > x2.integer) {
return;
}
#if PBL_COLOR
GColor8 *output = (GColor8 *)(data_row_info.data + x1.integer);
// first pixel with blending if fraction is different than 0
const unsigned int data_row_offset = data_row_info.data - (uint8_t *)framebuffer->addr;
if (x1.fraction != 0) {
graphics_private_raw_blend_color_factor(ctx, output, data_row_offset, color, x1.integer,
(uint8_t)(FIXED_S16_3_ONE.raw_value - x1.fraction));
output++;
x1.integer++;
}
// middle pixels
const int16_t width = x2.integer - x1.integer + 1;
if (width > 0) {
prv_set_color(ctx, output, data_row_offset, x1.integer, width, color);
output += width;
// x1 doesn't need to be increased as it's not used anymore in this function
}
// last pixel with blending (don't render first *and* last pixel if line length is 1)
if (x2.fraction != 0) {
graphics_private_raw_blend_color_factor(ctx, output, data_row_offset, color, x2.integer,
(uint8_t)x2.fraction);
}
#else
// TODO: as part of PBL-30849 make this a first-class function
// also see prv_blend_horizontal_line_raw
const int16_t x1_rounded = (x1.raw_value + FIXED_S16_3_HALF.raw_value) / FIXED_S16_3_FACTOR;
const int16_t x2_rounded = (x2.raw_value + FIXED_S16_3_HALF.raw_value) / FIXED_S16_3_FACTOR;
prv_assign_row_with_pattern_1bit(framebuffer, y, x1_rounded, x2_rounded - x1_rounded + 1, color);
#endif
}
// This function draws vertical line with AA edges, given values have to be adjusted for
// screen coordinates and clipped according to the clip box, does not respect transparency
// on the drawn line (beside edges)
T_STATIC void prv_assign_vertical_line_raw(GContext *ctx, int16_t x, Fixed_S16_3 y1,
Fixed_S16_3 y2, GColor color) {
PBL_ASSERTN(ctx);
GBitmap *framebuffer = &ctx->dest_bitmap;
PBL_ASSERTN(framebuffer->bounds.origin.x == 0 && framebuffer->bounds.origin.y == 0);
GBitmapDataRowInfo data_row_info = gbitmap_get_data_row_info(framebuffer, y1.integer);
GColor8 *output = (GColor8 *)(data_row_info.data + x);
// first pixel with blending
const unsigned int data_row_offset = data_row_info.data - (uint8_t *)framebuffer->addr;
if (y1.fraction != 0) {
// Only draw the pixel if its within the bitmap data row range
if (WITHIN(x, data_row_info.min_x, data_row_info.max_x)) {
graphics_private_raw_blend_color_factor(ctx, output, data_row_offset, color, x,
(uint8_t)(FIXED_S16_3_ONE.raw_value - y1.fraction));
}
y1.integer++;
data_row_info = gbitmap_get_data_row_info(framebuffer, y1.integer);
output = (GColor8 *)(data_row_info.data + x);
}
// middle pixels
while (y1.integer <= y2.integer) {
// Only draw the pixel if its within the bitmap data row range
if (WITHIN(x, data_row_info.min_x, data_row_info.max_x)) {
prv_set_color(ctx, output, data_row_offset, x, 1, color);
}
y1.integer++;
data_row_info = gbitmap_get_data_row_info(framebuffer, y1.integer);
output = (GColor8 *)(data_row_info.data + x);
}
// last pixel with blending (don't render first *and* last pixel if line length is 1)
if (y2.fraction != 0) {
// Only draw the pixel if its within the bitmap data row range
if (WITHIN(x, data_row_info.min_x, data_row_info.max_x)) {
graphics_private_raw_blend_color_factor(ctx, output, data_row_offset, color, x,
(uint8_t)y2.fraction);
}
}
}
// This function draws horizontal line with blending, given values have to be clipped and adjusted
// clip_box and draw_box respecively.
T_STATIC void prv_blend_horizontal_line_raw(GContext *ctx, int16_t y, int16_t x1, int16_t x2,
GColor color) {
PBL_ASSERTN(ctx);
GBitmap *framebuffer = &ctx->dest_bitmap;
// Clip the line to the bitmap data row's range
const GBitmapDataRowInfo data_row_info = gbitmap_get_data_row_info(framebuffer, y);
x1 = MAX(x1, data_row_info.min_x);
x2 = MIN(x2, data_row_info.max_x);
#if PBL_COLOR
for (int i = x1; i <= x2; i++) {
GColor *output = (GColor *)(data_row_info.data + i);
const unsigned int data_row_offset = data_row_info.data - (uint8_t *)framebuffer->addr;
prv_set_color(ctx, output, data_row_offset, i, 1, gcolor_alpha_blend(color, *output));
}
#else
// TODO: as part of PBL-30849 make this a first-class function
// also see, prv_assign_horizontal_line_raw
prv_assign_row_with_pattern_1bit(framebuffer, y, x1, x2 - x1 + 1, color);
#endif // SCREEN_COLOR_DEPTH_BITS == 8
}
// This function draws vertical line with blending, given values have to be clipped and adjusted
// clip_box and draw_box respecively.
T_STATIC void prv_blend_vertical_line_raw(GContext *ctx, int16_t x, int16_t y1, int16_t y2,
GColor color) {
PBL_ASSERTN(ctx);
GBitmap *framebuffer = &ctx->dest_bitmap;
#if SCREEN_COLOR_DEPTH_BITS == 8
for (int i = y1; i < y2; i++) {
// Skip over pixels outside the bitmap data row's range
const GBitmapDataRowInfo data_row_info = gbitmap_get_data_row_info(framebuffer, i);
if (!WITHIN(x, data_row_info.min_x, data_row_info.max_x)) {
continue;
}
GColor *output = (GColor *)(data_row_info.data + x);
const unsigned int data_row_offset = data_row_info.data - (uint8_t *)framebuffer->addr;
prv_set_color(ctx, output, data_row_offset, x, 1, gcolor_alpha_blend(color, *output));
}
#else
bool black = (gcolor_equal(color, GColorBlack));
for (int i = y1; i < y2; i++) {
uint8_t *line = ((uint8_t *)framebuffer->addr) + (framebuffer->row_size_bytes * i);
bitset8_update(line, x, !black);
}
#endif // SCREEN_COLOR_DEPTH_BITS == 8
}
// This function will draw a horizontal line with two gradients on side representing AA edges
T_STATIC 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) {
PBL_ASSERTN(ctx);
GBitmap *framebuffer = &ctx->dest_bitmap;
PBL_ASSERTN(framebuffer->bounds.origin.x == 0 && framebuffer->bounds.origin.y == 0);
// Clip the clip box to the bitmap data row's range
const GBitmapDataRowInfo data_row_info = gbitmap_get_data_row_info(framebuffer, y);
clip_box_min_x = MAX(clip_box_min_x, data_row_info.min_x);
clip_box_max_x = MIN(clip_box_max_x, data_row_info.max_x);
// If x1 is further outside the clip box than the left gradient width, we need to move x1 up
// to clip_box_min_x and proceed such that we don't draw the left gradient
int16_t x1_distance_outside_clip_box = clip_box_min_x - x1.integer;
if (x1_distance_outside_clip_box > left_aa_offset) {
left_aa_offset = 0;
x1.integer += x1_distance_outside_clip_box;
}
// Clip x2 to clip_box_max_x
x2.integer = MIN(clip_box_max_x, x2.integer);
// Return early if there's nothing to draw
if (x1.integer > x2.integer) {
return;
}
GColor8 *output = (GColor8 *)(data_row_info.data + x1.integer);
// first pixel with blending
const unsigned int data_row_offset = data_row_info.data - (uint8_t *)framebuffer->addr;
if (left_aa_offset == 1) {
// To prevent bleeding of left-hand AA below clip_box
if (x1.integer >= clip_box_min_x) {
graphics_private_raw_blend_color_factor(ctx, output, data_row_offset, color, x1.integer,
(uint8_t)(FIXED_S16_3_ONE.raw_value - x1.fraction));
}
output++;
x1.integer++;
// or first AA gradient with blending
} else {
for (int i = 0; i < left_aa_offset; i++) {
// To preserve gradient with clipping:
if (x1.integer < clip_box_min_x) {
output++;
x1.integer++;
continue;
}
if (x1.integer > clip_box_max_x) {
break;
}
graphics_private_raw_blend_color_factor(ctx, output, data_row_offset, color, x1.integer,
(uint8_t)(FIXED_S16_3_ONE.raw_value * i /
left_aa_offset));
output++;
x1.integer++;
}
}
// middle pixels
const int16_t width = x2.integer - x1.integer + 1;
if (width > 0) {
prv_set_color(ctx, output, data_row_offset, x1.integer, width, color);
output += width;
x1.integer += width;
}
// last pixel with blending (don't render first *and* last pixel if line length is 1)
if (right_aa_offset <= 1) {
if (x1.integer <= clip_box_max_x) {
graphics_private_raw_blend_color_factor(ctx, output, data_row_offset, color, x1.integer,
(uint8_t)x2.fraction);
}
// or last AA gradient with blending
} else {
for (int i = 0; i < right_aa_offset; i++) {
if (x1.integer > clip_box_max_x) {
break;
}
graphics_private_raw_blend_color_factor(ctx, output, data_row_offset, color, x1.integer,
(uint8_t)(FIXED_S16_3_ONE.raw_value *
(right_aa_offset - i) / right_aa_offset));
output++;
x1.integer++;
}
}
}
// TODO: Platform switches could happen here, too
const GDrawRawImplementation g_default_draw_implementation = {
.assign_horizontal_line = prv_assign_horizontal_line_raw,
.assign_vertical_line = prv_assign_vertical_line_raw,
.blend_horizontal_line = prv_blend_horizontal_line_raw,
.blend_vertical_line = prv_blend_vertical_line_raw,
.assign_horizontal_line_delta = prv_assign_horizontal_line_delta_raw,
};