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

165 lines
6.6 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.h"
#include "bitblt_private.h"
#include "system/logging.h"
#include "util/math.h"
#include "util/bitset.h"
void bitblt_into_1bit_setup_compositing_mode(GCompOp *compositing_mode,
GColor tint_color) {
if ((*compositing_mode == GCompOpTint) || (*compositing_mode == GCompOpTintLuminance)) {
// Force our interpretation of the tint color to be black, white, or clear
tint_color = gcolor_get_bw(tint_color);
if (gcolor_equal(tint_color, GColorBlack)) {
*compositing_mode = GCompOpAnd;
} else if (gcolor_equal(tint_color, GColorWhite)) {
*compositing_mode = GCompOpSet;
}
}
}
void bitblt_bitmap_into_bitmap_tiled_1bit_to_1bit(GBitmap* dest_bitmap,
const GBitmap* src_bitmap, GRect dest_rect,
GPoint src_origin_offset,
GCompOp compositing_mode,
GColor tint_color) {
bitblt_into_1bit_setup_compositing_mode(&compositing_mode, tint_color);
const int8_t dest_begin_x = (dest_rect.origin.x / 32);
const uint32_t * const dest_block_x_begin = ((uint32_t*)dest_bitmap->addr) + dest_begin_x;
const int dest_row_length_words = (dest_bitmap->row_size_bytes / 4);
// The number of bits between the beginning of dest_block and the beginning of the nearest 32-bit block:
const uint8_t dest_shift_at_line_begin = (dest_rect.origin.x % 32);
const uint32_t * const src_block_x_begin =
((uint32_t*)src_bitmap->addr) +
((src_bitmap->bounds.origin.x + (src_origin_offset.x % src_bitmap->bounds.size.w)) / 32);
const int src_row_length_words = (src_bitmap->row_size_bytes / 4);
const uint8_t src_shift_at_line_begin = ((src_bitmap->bounds.origin.x + src_origin_offset.x) % 32);
const uint8_t src_bits_left_at_line_begin = MIN(32 - src_shift_at_line_begin,
MIN(dest_rect.size.w + src_origin_offset.x,
src_bitmap->bounds.size.w));
// how many 32-bit blocks do we need to bitblt on this row:
const int16_t dest_end_x = (dest_rect.origin.x + dest_rect.size.w);
const uint8_t num_dest_blocks_per_row = (dest_end_x / 32) + ((dest_end_x % 32) ? 1 : 0) - dest_begin_x;
// The bitblt loops:
const int16_t dest_y_end = dest_rect.origin.y + dest_rect.size.h;
int16_t src_y = src_bitmap->bounds.origin.y + src_origin_offset.y;
for (int16_t dest_y = dest_rect.origin.y; dest_y != dest_y_end; ++dest_y, ++src_y) {
// Wrap-around source bitmap vertically:
if (src_y >= src_bitmap->bounds.origin.y + src_bitmap->bounds.size.h) {
src_y = src_bitmap->bounds.origin.y;
}
int8_t src_dest_shift = 32 + dest_shift_at_line_begin - src_shift_at_line_begin;
uint8_t dest_shift = dest_shift_at_line_begin;
uint8_t row_bits_left = dest_rect.size.w;
uint32_t *dest_block = (uint32_t *)dest_block_x_begin + (dest_y * dest_row_length_words);
uint32_t * const src_block_begin = (uint32_t *)src_block_x_begin + (src_y * src_row_length_words);
uint32_t *src_block = src_block_begin;
uint32_t src = *src_block;
rotl32(src, src_dest_shift);
uint8_t src_bits_left = src_bits_left_at_line_begin;
const uint32_t *dest_block_end = dest_block + num_dest_blocks_per_row;
const uint32_t *src_block_end = src_block + src_row_length_words;
while (dest_block != dest_block_end) {
const uint8_t number_of_bits = MIN(32 - dest_shift, MIN(row_bits_left, src_bits_left));
const uint32_t mask_outer_bit = ((number_of_bits < 31) ? (1 << number_of_bits) : 0);
const uint32_t mask = ((mask_outer_bit - 1) << dest_shift);
switch (compositing_mode) {
case GCompOpClear:
*(dest_block) &= ~(mask & src);
break;
case GCompOpSet:
*(dest_block) |= mask & ~src;
break;
case GCompOpOr:
*(dest_block) |= mask & src;
break;
case GCompOpAnd:
*(dest_block) &= ~mask | src;
break;
case GCompOpAssignInverted:
*(dest_block) ^= mask & (~src ^ *(dest_block));
break;
default:
case GCompOpAssign:
// this basically does: masked(dest_bits) = masked(src_bits)
*(dest_block) ^= mask & (src ^ *(dest_block));
break;
}
dest_shift = (dest_shift + number_of_bits) % 32;
row_bits_left -= number_of_bits;
src_bits_left -= number_of_bits;
if (src_bits_left == 0 && row_bits_left != 0) {
++src_block;
if (src_block == src_block_end) {
// Wrap-around source bitmap horizontally:
src_block = src_block_begin;
src_bits_left = src_bits_left_at_line_begin;
src_dest_shift = (src_dest_shift + src_bitmap->bounds.size.w) % 32;
} else {
src_bits_left = 32; // excessive right edge bits will be masked off eventually
}
src = *src_block;
rotl32(src, src_dest_shift);
if (dest_shift) {
continue;
}
}
// Proceed to next dest_block:
++dest_block;
}
}
}
void bitblt_bitmap_into_bitmap(GBitmap* dest_bitmap, const GBitmap* src_bitmap,
GPoint dest_offset, GCompOp compositing_mode, GColor8 tint_color) {
GRect dest_rect = { dest_offset, src_bitmap->bounds.size };
grect_clip(&dest_rect, &dest_bitmap->bounds);
GBitmap src_clipped_bitmap = *src_bitmap;
src_clipped_bitmap.bounds.origin = (GPoint) {
src_bitmap->bounds.origin.x + (dest_rect.origin.x - dest_offset.x),
src_bitmap->bounds.origin.y + (dest_rect.origin.y - dest_offset.y)
};
bitblt_bitmap_into_bitmap_tiled(dest_bitmap, &src_clipped_bitmap, dest_rect,
GPointZero, compositing_mode, tint_color);
}
bool bitblt_compositing_mode_is_noop(GCompOp compositing_mode, GColor tint_color) {
return (((compositing_mode == GCompOpTint) || (compositing_mode == GCompOpTintLuminance)) &&
gcolor_is_invisible(tint_color));
}