mirror of
https://github.com/google/pebble.git
synced 2025-05-06 18:01:41 -04:00
339 lines
14 KiB
C
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,
|
|
};
|