aboutsummaryrefslogtreecommitdiff
path: root/circuitpython/shared-module/bitmaptools/__init__.c
diff options
context:
space:
mode:
authorRaghuram Subramani <raghus2247@gmail.com>2022-06-19 19:47:51 +0530
committerRaghuram Subramani <raghus2247@gmail.com>2022-06-19 19:47:51 +0530
commit4fd287655a72b9aea14cdac715ad5b90ed082ed2 (patch)
tree65d393bc0e699dd12d05b29ba568e04cea666207 /circuitpython/shared-module/bitmaptools/__init__.c
parent0150f70ce9c39e9e6dd878766c0620c85e47bed0 (diff)
add circuitpython code
Diffstat (limited to 'circuitpython/shared-module/bitmaptools/__init__.c')
-rw-r--r--circuitpython/shared-module/bitmaptools/__init__.c859
1 files changed, 859 insertions, 0 deletions
diff --git a/circuitpython/shared-module/bitmaptools/__init__.c b/circuitpython/shared-module/bitmaptools/__init__.c
new file mode 100644
index 0000000..4c73d8f
--- /dev/null
+++ b/circuitpython/shared-module/bitmaptools/__init__.c
@@ -0,0 +1,859 @@
+/*
+ * This file is part of the Micro Python project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Kevin Matocha
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "shared-bindings/bitmaptools/__init__.h"
+#include "shared-bindings/displayio/Bitmap.h"
+#include "shared-bindings/displayio/Palette.h"
+#include "shared-bindings/displayio/ColorConverter.h"
+#include "shared-module/displayio/Bitmap.h"
+
+#include "py/mperrno.h"
+#include "py/runtime.h"
+#include "py/stream.h"
+
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+void common_hal_bitmaptools_rotozoom(displayio_bitmap_t *self, int16_t ox, int16_t oy,
+ int16_t dest_clip0_x, int16_t dest_clip0_y,
+ int16_t dest_clip1_x, int16_t dest_clip1_y,
+ displayio_bitmap_t *source, int16_t px, int16_t py,
+ int16_t source_clip0_x, int16_t source_clip0_y,
+ int16_t source_clip1_x, int16_t source_clip1_y,
+ mp_float_t angle,
+ mp_float_t scale,
+ uint32_t skip_index, bool skip_index_none) {
+
+ // Copies region from source to the destination bitmap, including rotation,
+ // scaling and clipping of either the source or destination regions
+ //
+ // *self: destination bitmap
+ // ox: the (ox, oy) destination point where the source (px,py) point is placed
+ // oy:
+ // dest_clip0: (x,y) is the corner of the clip window on the destination bitmap
+ // dest_clip1: (x,y) is the other corner of the clip window of the destination bitmap
+ // *source: the source bitmap
+ // px: the (px, py) point of rotation of the source bitmap
+ // py:
+ // source_clip0: (x,y) is the corner of the clip window on the source bitmap
+ // source_clip1: (x,y) is the other of the clip window on the source bitmap
+ // angle: angle of rotation in radians, positive is clockwise
+ // scale: scale factor
+ // skip_index: color index that should be ignored (and not copied over)
+ // skip_index_none: if skip_index_none is True, then all color indexes should be copied
+ // (that is, no color indexes should be skipped)
+
+
+ // Copy complete "source" bitmap into "self" bitmap at location x,y in the "self"
+ // Add a boolean to determine if all values are copied, or only if non-zero
+ // If skip_value is encountered in the source bitmap, it will not be copied.
+ // If skip_value is `None`, then all pixels are copied.
+
+
+ // # Credit from https://github.com/wernsey/bitmap
+ // # MIT License from
+ // # * Copyright (c) 2017 Werner Stoop <wstoop@gmail.com>
+ // #
+ // # *
+ // # * #### `void bm_rotate_blit(Bitmap *dst, int ox, int oy, Bitmap *src, int px, int py, double angle, double scale);`
+ // # *
+ // # * Rotates a source bitmap `src` around a pivot point `px,py` and blits it onto a destination bitmap `dst`.
+ // # *
+ // # * The bitmap is positioned such that the point `px,py` on the source is at the offset `ox,oy` on the destination.
+ // # *
+ // # * The `angle` is clockwise, in radians. The bitmap is also scaled by the factor `scale`.
+ // #
+ // # void bm_rotate_blit(Bitmap *dst, int ox, int oy, Bitmap *src, int px, int py, double angle, double scale);
+
+
+ // # /*
+ // # Reference:
+ // # "Fast Bitmap Rotation and Scaling" By Steven Mortimer, Dr Dobbs' Journal, July 01, 2001
+ // # http://www.drdobbs.com/architecture-and-design/fast-bitmap-rotation-and-scaling/184416337
+ // # See also http://www.efg2.com/Lab/ImageProcessing/RotateScanline.htm
+ // # */
+
+
+ int16_t x,y;
+
+ int16_t minx = dest_clip1_x;
+ int16_t miny = dest_clip1_y;
+ int16_t maxx = dest_clip0_x;
+ int16_t maxy = dest_clip0_y;
+
+ mp_float_t sinAngle = MICROPY_FLOAT_C_FUN(sin)(angle);
+ mp_float_t cosAngle = MICROPY_FLOAT_C_FUN(cos)(angle);
+
+ mp_float_t dx, dy;
+
+ /* Compute the position of where each corner on the source bitmap
+ will be on the destination to get a bounding box for scanning */
+ dx = -cosAngle * px * scale + sinAngle * py * scale + ox;
+ dy = -sinAngle * px * scale - cosAngle * py * scale + oy;
+ if (dx < minx) {
+ minx = (int16_t)dx;
+ }
+ if (dx > maxx) {
+ maxx = (int16_t)dx;
+ }
+ if (dy < miny) {
+ miny = (int16_t)dy;
+ }
+ if (dy > maxy) {
+ maxy = (int16_t)dy;
+ }
+
+ dx = cosAngle * (source->width - px) * scale + sinAngle * py * scale + ox;
+ dy = sinAngle * (source->width - px) * scale - cosAngle * py * scale + oy;
+ if (dx < minx) {
+ minx = (int16_t)dx;
+ }
+ if (dx > maxx) {
+ maxx = (int16_t)dx;
+ }
+ if (dy < miny) {
+ miny = (int16_t)dy;
+ }
+ if (dy > maxy) {
+ maxy = (int16_t)dy;
+ }
+
+ dx = cosAngle * (source->width - px) * scale - sinAngle * (source->height - py) * scale + ox;
+ dy = sinAngle * (source->width - px) * scale + cosAngle * (source->height - py) * scale + oy;
+ if (dx < minx) {
+ minx = (int16_t)dx;
+ }
+ if (dx > maxx) {
+ maxx = (int16_t)dx;
+ }
+ if (dy < miny) {
+ miny = (int16_t)dy;
+ }
+ if (dy > maxy) {
+ maxy = (int16_t)dy;
+ }
+
+ dx = -cosAngle * px * scale - sinAngle * (source->height - py) * scale + ox;
+ dy = -sinAngle * px * scale + cosAngle * (source->height - py) * scale + oy;
+ if (dx < minx) {
+ minx = (int16_t)dx;
+ }
+ if (dx > maxx) {
+ maxx = (int16_t)dx;
+ }
+ if (dy < miny) {
+ miny = (int16_t)dy;
+ }
+ if (dy > maxy) {
+ maxy = (int16_t)dy;
+ }
+
+ /* Clipping */
+ if (minx < dest_clip0_x) {
+ minx = dest_clip0_x;
+ }
+ if (maxx > dest_clip1_x - 1) {
+ maxx = dest_clip1_x - 1;
+ }
+ if (miny < dest_clip0_y) {
+ miny = dest_clip0_y;
+ }
+ if (maxy > dest_clip1_y - 1) {
+ maxy = dest_clip1_y - 1;
+ }
+
+ mp_float_t dvCol = cosAngle / scale;
+ mp_float_t duCol = sinAngle / scale;
+
+ mp_float_t duRow = dvCol;
+ mp_float_t dvRow = -duCol;
+
+ mp_float_t startu = px - (ox * dvCol + oy * duCol);
+ mp_float_t startv = py - (ox * dvRow + oy * duRow);
+
+ mp_float_t rowu = startu + miny * duCol;
+ mp_float_t rowv = startv + miny * dvCol;
+
+ displayio_area_t dirty_area = {minx, miny, maxx + 1, maxy + 1, NULL};
+ displayio_bitmap_set_dirty_area(self, &dirty_area);
+
+ for (y = miny; y <= maxy; y++) {
+ mp_float_t u = rowu + minx * duRow;
+ mp_float_t v = rowv + minx * dvRow;
+ for (x = minx; x <= maxx; x++) {
+ if (u >= source_clip0_x && u < source_clip1_x && v >= source_clip0_y && v < source_clip1_y) {
+ uint32_t c = common_hal_displayio_bitmap_get_pixel(source, (int)u, (int)v);
+ if ((skip_index_none) || (c != skip_index)) {
+ displayio_bitmap_write_pixel(self, x, y, c);
+ }
+ }
+ u += duRow;
+ v += dvRow;
+ }
+ rowu += duCol;
+ rowv += dvCol;
+ }
+}
+
+void common_hal_bitmaptools_fill_region(displayio_bitmap_t *destination,
+ int16_t x1, int16_t y1,
+ int16_t x2, int16_t y2,
+ uint32_t value) {
+ // writes the value (a bitmap color index) into a bitmap in the specified rectangular region
+ //
+ // input checks should ensure that x1 < x2 and y1 < y2 and are within the bitmap region
+
+ displayio_area_t area = { x1, y1, x2, y2, NULL };
+ displayio_area_canon(&area);
+
+ displayio_area_t bitmap_area = { 0, 0, destination->width, destination->height, NULL };
+ displayio_area_compute_overlap(&area, &bitmap_area, &area);
+
+ // update the dirty rectangle
+ displayio_bitmap_set_dirty_area(destination, &area);
+
+ int16_t x, y;
+ for (x = area.x1; x < area.x2; x++) {
+ for (y = area.y1; y < area.y2; y++) {
+ displayio_bitmap_write_pixel(destination, x, y, value);
+ }
+ }
+}
+
+void common_hal_bitmaptools_boundary_fill(displayio_bitmap_t *destination,
+ int16_t x, int16_t y,
+ uint32_t fill_color_value, uint32_t replaced_color_value) {
+
+ if (fill_color_value == replaced_color_value) {
+ // There is nothing to do
+ return;
+ }
+
+ uint32_t current_point_color_value;
+
+ // the list of points that we'll check
+ mp_obj_t fill_area = mp_obj_new_list(0, NULL);
+
+ // first point is the one user passed in
+ mp_obj_t point[] = { mp_obj_new_int(x), mp_obj_new_int(y) };
+ mp_obj_list_append(
+ fill_area,
+ mp_obj_new_tuple(2, point)
+ );
+
+ int16_t minx = x;
+ int16_t miny = y;
+ int16_t maxx = x;
+ int16_t maxy = y;
+
+ if (replaced_color_value == INT_MAX) {
+ current_point_color_value = common_hal_displayio_bitmap_get_pixel(
+ destination,
+ mp_obj_get_int(point[0]),
+ mp_obj_get_int(point[1]));
+ replaced_color_value = (uint32_t)current_point_color_value;
+ }
+
+ mp_obj_t *fill_points;
+ size_t list_length = 0;
+ mp_obj_list_get(fill_area, &list_length, &fill_points);
+
+ mp_obj_t current_point;
+
+ size_t tuple_len = 0;
+ mp_obj_t *tuple_items;
+
+ int cur_x, cur_y;
+
+ // while there are still points to check
+ while (list_length > 0) {
+ mp_obj_list_get(fill_area, &list_length, &fill_points);
+ current_point = mp_obj_list_pop(fill_area, 0);
+ mp_obj_tuple_get(current_point, &tuple_len, &tuple_items);
+ current_point_color_value = common_hal_displayio_bitmap_get_pixel(
+ destination,
+ mp_obj_get_int(tuple_items[0]),
+ mp_obj_get_int(tuple_items[1]));
+
+ // if the current point is not background color ignore it
+ if (current_point_color_value != replaced_color_value) {
+ mp_obj_list_get(fill_area, &list_length, &fill_points);
+ continue;
+ }
+
+ cur_x = mp_obj_int_get_checked(tuple_items[0]);
+ cur_y = mp_obj_int_get_checked(tuple_items[1]);
+
+ if (cur_x < minx) {
+ minx = (int16_t)cur_x;
+ }
+ if (cur_x > maxx) {
+ maxx = (int16_t)cur_x;
+ }
+ if (cur_y < miny) {
+ miny = (int16_t)cur_y;
+ }
+ if (cur_y > maxy) {
+ maxy = (int16_t)cur_y;
+ }
+
+ // fill the current point with fill color
+ displayio_bitmap_write_pixel(
+ destination,
+ mp_obj_get_int(tuple_items[0]),
+ mp_obj_get_int(tuple_items[1]),
+ fill_color_value);
+
+ // add all 4 surrounding points to the list to check
+
+ // ignore points outside of the bitmap
+ if (mp_obj_int_get_checked(tuple_items[1]) - 1 >= 0) {
+ mp_obj_t above_point[] = {
+ tuple_items[0],
+ MP_OBJ_NEW_SMALL_INT(mp_obj_int_get_checked(tuple_items[1]) - 1)
+ };
+ mp_obj_list_append(
+ fill_area,
+ mp_obj_new_tuple(2, above_point));
+ }
+
+ // ignore points outside of the bitmap
+ if (mp_obj_int_get_checked(tuple_items[0]) - 1 >= 0) {
+ mp_obj_t left_point[] = {
+ MP_OBJ_NEW_SMALL_INT(mp_obj_int_get_checked(tuple_items[0]) - 1),
+ tuple_items[1]
+ };
+ mp_obj_list_append(
+ fill_area,
+ mp_obj_new_tuple(2, left_point));
+ }
+
+ // ignore points outside of the bitmap
+ if (mp_obj_int_get_checked(tuple_items[0]) + 1 < destination->width) {
+ mp_obj_t right_point[] = {
+ MP_OBJ_NEW_SMALL_INT(mp_obj_int_get_checked(tuple_items[0]) + 1),
+ tuple_items[1]
+ };
+ mp_obj_list_append(
+ fill_area,
+ mp_obj_new_tuple(2, right_point));
+ }
+
+ // ignore points outside of the bitmap
+ if (mp_obj_int_get_checked(tuple_items[1]) + 1 < destination->height) {
+ mp_obj_t below_point[] = {
+ tuple_items[0],
+ MP_OBJ_NEW_SMALL_INT(mp_obj_int_get_checked(tuple_items[1]) + 1)
+ };
+ mp_obj_list_append(
+ fill_area,
+ mp_obj_new_tuple(2, below_point));
+ }
+
+ mp_obj_list_get(fill_area, &list_length, &fill_points);
+ }
+
+ // set dirty the area so displayio will draw
+ displayio_area_t area = { minx, miny, maxx + 1, maxy + 1, NULL};
+ displayio_bitmap_set_dirty_area(destination, &area);
+
+}
+
+void common_hal_bitmaptools_draw_line(displayio_bitmap_t *destination,
+ int16_t x0, int16_t y0,
+ int16_t x1, int16_t y1,
+ uint32_t value) {
+
+ //
+ // adapted from Adafruit_CircuitPython_Display_Shapes.Polygon._line
+ //
+
+ // update the dirty rectangle
+ int16_t xbb0, xbb1, ybb0, ybb1;
+ if (x0 < x1) {
+ xbb0 = x0;
+ xbb1 = x1 + 1;
+ } else {
+ xbb0 = x1;
+ xbb1 = x0 + 1;
+ }
+ if (y0 < y1) {
+ ybb0 = y0;
+ ybb1 = y1 + 1;
+ } else {
+ ybb0 = y1;
+ ybb1 = y0 + 1;
+ }
+ displayio_area_t area = { xbb0, ybb0, xbb1, ybb1, NULL };
+ displayio_area_t bitmap_area = { 0, 0, destination->width, destination->height, NULL };
+ displayio_area_compute_overlap(&area, &bitmap_area, &area);
+
+ displayio_bitmap_set_dirty_area(destination, &area);
+
+ int16_t temp, x, y;
+
+ if (x0 == x1) { // vertical line
+ if (y0 > y1) { // ensure y1 > y0
+ temp = y0;
+ y0 = y1;
+ y1 = temp;
+ }
+ for (y = y0; y < (y1 + 1); y++) { // write a horizontal line
+ displayio_bitmap_write_pixel(destination, x0, y, value);
+ }
+ } else if (y0 == y1) { // horizontal line
+ if (x0 > x1) { // ensure y1 > y0
+ temp = x0;
+ x0 = x1;
+ x1 = temp;
+ }
+ for (x = x0; x < (x1 + 1); x++) { // write a horizontal line
+ displayio_bitmap_write_pixel(destination, x, y0, value);
+ }
+ } else {
+ bool steep;
+ steep = (abs(y1 - y0) > abs(x1 - x0));
+
+ if (steep) { // flip x0<->y0 and x1<->y1
+ temp = x0;
+ x0 = y0;
+ y0 = temp;
+ temp = x1;
+ x1 = y1;
+ y1 = temp;
+ }
+
+ if (x0 > x1) { // flip x0<->x1 and y0<->y1
+ temp = x0;
+ x0 = x1;
+ x1 = temp;
+ temp = y0;
+ y0 = y1;
+ y1 = temp;
+ }
+
+ int16_t dx, dy, ystep;
+ dx = x1 - x0;
+ dy = abs(y1 - y0);
+
+ mp_float_t err = dx / 2;
+
+ if (y0 < y1) {
+ ystep = 1;
+ } else {
+ ystep = -1;
+ }
+
+ for (x = x0; x < (x1 + 1); x++) {
+ if (steep) {
+ displayio_bitmap_write_pixel(destination, y0, x, value);
+ } else {
+ displayio_bitmap_write_pixel(destination, x, y0, value);
+ }
+ err -= dy;
+ if (err < 0) {
+ y0 += ystep;
+ err += dx;
+ }
+ }
+ }
+}
+
+void common_hal_bitmaptools_arrayblit(displayio_bitmap_t *self, void *data, int element_size, int x1, int y1, int x2, int y2, bool skip_specified, uint32_t skip_value) {
+ uint32_t mask = (1 << common_hal_displayio_bitmap_get_bits_per_value(self)) - 1;
+
+ for (int y = y1; y < y2; y++) {
+ for (int x = x1; x < x2; x++) {
+ uint32_t value;
+ switch (element_size) {
+ default:
+ case 1:
+ value = *(uint8_t *)data;
+ data = (void *)((uint8_t *)data + 1);
+ break;
+ case 2:
+ value = *(uint16_t *)data;
+ data = (void *)((uint16_t *)data + 1);
+ break;
+ case 4:
+ value = *(uint32_t *)data;
+ data = (void *)((uint32_t *)data + 1);
+ break;
+ }
+ if (!skip_specified || value != skip_value) {
+ displayio_bitmap_write_pixel(self, x, y, value & mask);
+ }
+ }
+ }
+ displayio_area_t area = { x1, y1, x2, y2, NULL };
+ displayio_bitmap_set_dirty_area(self, &area);
+}
+
+void common_hal_bitmaptools_readinto(displayio_bitmap_t *self, mp_obj_t *file, int element_size, int bits_per_pixel, bool reverse_pixels_in_element, bool swap_bytes, bool reverse_rows) {
+ uint32_t mask = (1 << common_hal_displayio_bitmap_get_bits_per_value(self)) - 1;
+
+ const mp_stream_p_t *file_proto = mp_get_stream_raise(file, MP_STREAM_OP_READ);
+
+ displayio_area_t a = {0, 0, self->width, self->height, NULL};
+ displayio_bitmap_set_dirty_area(self, &a);
+
+ size_t elements_per_row = (self->width * bits_per_pixel + element_size * 8 - 1) / (element_size * 8);
+ size_t rowsize = element_size * elements_per_row;
+ size_t rowsize_in_u32 = (rowsize + sizeof(uint32_t) - 1) / sizeof(uint32_t);
+ size_t rowsize_in_u16 = (rowsize + sizeof(uint16_t) - 1) / sizeof(uint16_t);
+
+ for (int y = 0; y < self->height; y++) {
+ uint32_t rowdata32[rowsize_in_u32];
+ uint16_t *rowdata16 = (uint16_t *)rowdata32;
+ uint8_t *rowdata8 = (uint8_t *)rowdata32;
+ const int y_draw = reverse_rows ? (self->height) - 1 - y : y;
+
+
+ int error = 0;
+ mp_uint_t bytes_read = file_proto->read(file, rowdata32, rowsize, &error);
+ if (error) {
+ mp_raise_OSError(error);
+ }
+ if (bytes_read != rowsize) {
+ mp_raise_msg(&mp_type_EOFError, NULL);
+ }
+
+ if (swap_bytes) {
+ switch (element_size) {
+ case 2:
+ for (size_t i = 0; i < rowsize_in_u16; i++) {
+ rowdata16[i] = __builtin_bswap16(rowdata16[i]);
+ }
+ break;
+ case 4:
+ for (size_t i = 0; i < rowsize_in_u32; i++) {
+ rowdata32[i] = __builtin_bswap32(rowdata32[i]);
+ }
+ default:
+ break;
+ }
+ }
+
+ for (int x = 0; x < self->width; x++) {
+ int value = 0;
+ switch (bits_per_pixel) {
+ case 1: {
+ int byte_offset = x / 8;
+ int bit_offset = reverse_pixels_in_element ? (7 - x % 8) : x % 8;
+
+ value = (rowdata8[byte_offset] >> bit_offset) & 1;
+ break;
+ }
+ case 2: {
+ int byte_offset = x / 4;
+ int bit_offset = 2 * (reverse_pixels_in_element ? (3 - x % 4) : x % 4);
+
+ value = (rowdata8[byte_offset] >> bit_offset) & 3;
+ break;
+ }
+ case 4: {
+ int byte_offset = x / 2;
+ int bit_offset = 4 * (reverse_pixels_in_element ? (1 - x % 2) : x % 2);
+
+ value = (rowdata8[byte_offset] >> bit_offset) & 0xf;
+ break;
+ }
+ case 8:
+ value = rowdata8[x];
+ break;
+
+ case 16:
+ value = rowdata16[x];
+ break;
+
+ case 24:
+ value = (rowdata8[x * 3] << 16) | (rowdata8[x * 3 + 1] << 8) | rowdata8[x * 3 + 2];
+ break;
+
+ case 32:
+ value = rowdata32[x];
+ break;
+ }
+ displayio_bitmap_write_pixel(self, x, y_draw, value & mask);
+ }
+ }
+}
+
+typedef struct {
+ uint8_t count; // The number of items in terms[]
+ uint8_t mx; // the maximum of the absolute value of the dx values
+ uint8_t dl; // the scaled dither value applied to the pixel at distance [1,0]
+ struct { // dl is the scaled dither values applied to the pixel at [dx,dy]
+ int8_t dx, dy, dl;
+ } terms[];
+} bitmaptools_dither_algorithm_info_t;
+
+static bitmaptools_dither_algorithm_info_t atkinson = {
+ 4, 2, 256 / 8, {
+ {2, 0, 256 / 8},
+ {-1, 1, 256 / 8},
+ {0, 1, 256 / 8},
+ {0, 2, 256 / 8},
+ }
+};
+
+static bitmaptools_dither_algorithm_info_t floyd_stenberg = {
+ 3, 1, 7 * 256 / 16,
+ {
+ {-1, 1, 3 * 256 / 16},
+ {0, 1, 5 * 256 / 16},
+ {1, 1, 1 * 256 / 16},
+ }
+};
+
+bitmaptools_dither_algorithm_info_t *algorithms[] = {
+ [DITHER_ALGORITHM_ATKINSON] = &atkinson,
+ [DITHER_ALGORITHM_FLOYD_STENBERG] = &floyd_stenberg,
+};
+
+enum {
+ SWAP_BYTES = 1 << 0,
+ SWAP_RB = 1 << 1,
+};
+
+STATIC void fill_row(displayio_bitmap_t *bitmap, int swap, int16_t *luminance_data, int y, int mx) {
+ if (y >= bitmap->height) {
+ return;
+ }
+
+ // zero out padding area
+ for (int i = 0; i < mx; i++) {
+ luminance_data[-mx + i] = 0;
+ luminance_data[bitmap->width + i] = 0;
+ }
+
+ if (bitmap->bits_per_value == 8) {
+ uint8_t *pixel_data = (uint8_t *)(bitmap->data + bitmap->stride * y);
+ for (int x = 0; x < bitmap->width; x++) {
+ *luminance_data++ = *pixel_data++;
+ }
+ } else {
+ uint16_t *pixel_data = (uint16_t *)(bitmap->data + bitmap->stride * y);
+ for (int x = 0; x < bitmap->width; x++) {
+ uint16_t pixel = *pixel_data++;
+ if (swap & SWAP_BYTES) {
+ pixel = __builtin_bswap16(pixel);
+ }
+ int r = (pixel >> 8) & 0xf8;
+ int g = (pixel >> 3) & 0xfc;
+ int b = (pixel << 3) & 0xf8;
+
+ if (swap & SWAP_RB) {
+ uint8_t tmp = r;
+ r = b;
+ b = tmp;
+ }
+
+ // ideal coefficients are around .299, .587, .114 (according to
+ // ppmtopnm), this differs from the 'other' luma-converting
+ // function in circuitpython (why?)
+
+ // we correct for the fact that the input ranges are 0..0xf8 (or
+ // 0xfc) rather than 0x00..0xff
+ // Check: (0xf8 * 78 + 0xfc * 154 + 0xf8 * 29) // 256 == 255
+ *luminance_data++ = (r * 78 + g * 154 + b * 29) / 256;
+ }
+ }
+}
+
+static void write_pixels(displayio_bitmap_t *bitmap, int y, bool *data) {
+ if (bitmap->bits_per_value == 1) {
+ uint32_t *pixel_data = (uint32_t *)(bitmap->data + bitmap->stride * y);
+ for (int i = 0; i < bitmap->stride; i++) {
+ uint32_t p = 0;
+ for (int j = 0; j < 32; j++) {
+ p = (p << 1);
+ if (*data++) {
+ p |= 1;
+ }
+ }
+ *pixel_data++ = p;
+ }
+ } else {
+ uint16_t *pixel_data = (uint16_t *)(bitmap->data + bitmap->stride * y);
+ for (int i = 0; i < bitmap->width; i++) {
+ *pixel_data++ = *data++ ? 65535 : 0;
+ }
+ }
+}
+
+void common_hal_bitmaptools_dither(displayio_bitmap_t *dest_bitmap, displayio_bitmap_t *source_bitmap, displayio_colorspace_t colorspace, bitmaptools_dither_algorithm_t algorithm) {
+ int height = dest_bitmap->height, width = dest_bitmap->width;
+
+ int swap = 0;
+ if (colorspace == DISPLAYIO_COLORSPACE_RGB565_SWAPPED || colorspace == DISPLAYIO_COLORSPACE_BGR565_SWAPPED) {
+ swap |= SWAP_BYTES;
+ }
+ if (colorspace == DISPLAYIO_COLORSPACE_BGR565 || colorspace == DISPLAYIO_COLORSPACE_BGR565_SWAPPED) {
+ swap |= SWAP_RB;
+ }
+
+ bitmaptools_dither_algorithm_info_t *info = algorithms[algorithm];
+ // rowdata holds 3 rows of data. Each one is larger than the input
+ // bitmap's width, beacuse `mx` extra pixels are allocated at the start and
+ // end of the row so that no conditionals are needed when storing the error data.
+ int16_t rowdata[(width + 2 * info->mx) * 3];
+ int16_t *rows[3] = {
+ rowdata + info->mx, rowdata + width + info->mx * 3, rowdata + 2 * width + info->mx * 5
+ };
+ // out holds one output row of pixels, and is padded to be a multiple of 32 so that the 1bpp storage loop can be simplified
+ bool out[(width + 31) / 32 * 32];
+
+ fill_row(source_bitmap, swap, rows[0], 0, info->mx);
+ fill_row(source_bitmap, swap, rows[1], 1, info->mx);
+ fill_row(source_bitmap, swap, rows[2], 2, info->mx);
+
+ int16_t err = 0;
+
+ for (int y = 0; y < height; y++) {
+
+ // Serpentine dither. Going left-to-right...
+ for (int x = 0; x < width; x++) {
+ int32_t pixel_in = rows[0][x] + err;
+ bool pixel_out = pixel_in >= 128;
+ out[x] = pixel_out;
+
+ err = pixel_in - (pixel_out ? 255 : 0);
+
+ for (int i = 0; i < info->count; i++) {
+ int x1 = x + info->terms[i].dx;
+ int dy = info->terms[i].dy;
+
+ rows[dy][x1] = ((info->terms[i].dl * err) / 256) + rows[dy][x1];
+ }
+ err = (err * info->dl) / 256;
+ }
+ write_pixels(dest_bitmap, y, out);
+
+ // Cycle the rows by shuffling pointers, this is faster than copying the data.
+ int16_t *tmp = rows[0];
+ rows[0] = rows[1];
+ rows[1] = rows[2];
+ rows[2] = tmp;
+
+ y++;
+ if (y == height) {
+ break;
+ }
+
+ fill_row(source_bitmap, swap, rows[2], y + 2, info->mx);
+
+ // Serpentine dither. Going right-to-left...
+ for (int x = width; x--;) {
+ int16_t pixel_in = rows[0][x] + err;
+ bool pixel_out = pixel_in >= 128;
+ out[x] = pixel_out;
+ err = pixel_in - (pixel_out ? 255 : 0);
+
+ for (int i = 0; i < info->count; i++) {
+ int x1 = x - info->terms[i].dx;
+ int dy = info->terms[i].dy;
+
+ rows[dy][x1] = ((info->terms[i].dl * err) / 256) + rows[dy][x1];
+ }
+ err = (err * info->dl) / 256;
+ }
+ write_pixels(dest_bitmap, y, out);
+
+ tmp = rows[0];
+ rows[0] = rows[1];
+ rows[1] = rows[2];
+ rows[2] = tmp;
+
+ fill_row(source_bitmap, swap, rows[2], y + 3, info->mx);
+ }
+
+ displayio_area_t a = { 0, 0, width, height, NULL };
+ displayio_bitmap_set_dirty_area(dest_bitmap, &a);
+}
+
+void common_hal_bitmaptools_alphablend(displayio_bitmap_t *dest, displayio_bitmap_t *source1, displayio_bitmap_t *source2, displayio_colorspace_t colorspace, mp_float_t factor1, mp_float_t factor2) {
+ displayio_area_t a = {0, 0, dest->width, dest->height, NULL};
+ displayio_bitmap_set_dirty_area(dest, &a);
+
+ int ifactor1 = (int)(factor1 * 256);
+ int ifactor2 = (int)(factor2 * 256);
+
+ if (colorspace == DISPLAYIO_COLORSPACE_L8) {
+ for (int y = 0; y < dest->height; y++) {
+ uint8_t *dptr = (uint8_t *)(dest->data + y * dest->stride);
+ uint8_t *sptr1 = (uint8_t *)(source1->data + y * source1->stride);
+ uint8_t *sptr2 = (uint8_t *)(source2->data + y * source2->stride);
+ for (int x = 0; x < dest->width; x++) {
+ // This is round(l1*f1 + l2*f2) & clip to range in fixed-point
+ int pixel = (*sptr1++ *ifactor1 + *sptr2++ *ifactor2 + 128) / 256;
+ *dptr++ = MIN(255, MAX(0, pixel));
+ }
+ }
+ } else {
+ bool swap = (colorspace == DISPLAYIO_COLORSPACE_RGB565_SWAPPED) || (colorspace == DISPLAYIO_COLORSPACE_BGR565_SWAPPED);
+ for (int y = 0; y < dest->height; y++) {
+ uint16_t *dptr = (uint16_t *)(dest->data + y * dest->stride);
+ uint16_t *sptr1 = (uint16_t *)(source1->data + y * source1->stride);
+ uint16_t *sptr2 = (uint16_t *)(source2->data + y * source2->stride);
+ for (int x = 0; x < dest->width; x++) {
+ int spix1 = *sptr1++;
+ int spix2 = *sptr2++;
+
+ if (swap) {
+ spix1 = __builtin_bswap16(spix1);
+ spix2 = __builtin_bswap16(spix2);
+ }
+ const int r_mask = 0xf800; // (or b mask, if BGR)
+ const int g_mask = 0x07e0;
+ const int b_mask = 0x001f; // (or r mask, if BGR)
+
+ // This is round(r1*f1 + r2*f2) & clip to range in fixed-point
+ // but avoiding shifting it down to start at bit 0
+ int r = ((spix1 & r_mask) * ifactor1
+ + (spix2 & r_mask) * ifactor2 + r_mask / 2) / 256;
+ r = MIN(r_mask, MAX(0, r)) & r_mask;
+
+ // ditto
+ int g = ((spix1 & g_mask) * ifactor1
+ + (spix2 & g_mask) * ifactor2 + g_mask / 2) / 256;
+ g = MIN(g_mask, MAX(0, g)) & g_mask;
+
+ int b = ((spix1 & b_mask) * ifactor1
+ + (spix2 & b_mask) * ifactor2 + b_mask / 2) / 256;
+ b = MIN(b_mask, MAX(0, b)) & b_mask;
+
+ uint16_t pixel = r | g | b;
+ if (swap) {
+ pixel = __builtin_bswap16(pixel);
+ }
+ *dptr++ = pixel;
+ }
+ }
+ }
+}