X-Git-Url: https://git.ayoreis.com/hackapet/Adafruit_Blinka_Displayio.git/blobdiff_plain/96a8f5564dce100d739e43528ecd40a10316be8d..86839449b3dff6550f8bcdb33f669b73990e6abb:/bitmaptools/__init__.py diff --git a/bitmaptools/__init__.py b/bitmaptools/__init__.py index e3490f6..e9ca3bb 100644 --- a/bitmaptools/__init__.py +++ b/bitmaptools/__init__.py @@ -1,7 +1,8 @@ import math -from typing import Optional, Tuple - -from displayio import Bitmap +import struct +from typing import Optional, Tuple, BinaryIO +import numpy as np +from displayio import Bitmap, Colorspace import circuitpython_typing @@ -61,7 +62,7 @@ def draw_circle(dest_bitmap: Bitmap, x: int, y: int, radius: int, value: int): def draw_polygon(dest_bitmap: Bitmap, xs: circuitpython_typing.ReadableBuffer, ys: circuitpython_typing.ReadableBuffer, - value: int, close: bool | None = True): + value: int, close: bool = True): if len(xs) != len(ys): raise ValueError("Length of xs and ys must be equal.") @@ -129,21 +130,47 @@ def blit(dest_bitmap: Bitmap, source_bitmap: Bitmap, def rotozoom( - dest_bitmap, - source_bitmap, + dest_bitmap: Bitmap, + source_bitmap: Bitmap, *, - ox: int, - oy: int, - dest_clip0: Tuple[int, int], - dest_clip1: Tuple[int, int], - px: int, - py: int, - source_clip0: Tuple[int, int], - source_clip1: Tuple[int, int], - angle: float, - scale: float, - skip_index: int, + ox: Optional[int] = None, + oy: Optional[int] = None, + dest_clip0: Optional[Tuple[int, int]] = None, + dest_clip1: Optional[Tuple[int, int]] = None, + px: Optional[int] = None, + py: Optional[int] = None, + source_clip0: Optional[Tuple[int, int]] = None, + source_clip1: Optional[Tuple[int, int]] = None, + angle: Optional[float] = None, + scale: Optional[float] = None, + skip_index: Optional[int] = None, ): + + if ox is None: + ox = dest_bitmap.width // 2 + if oy in None: + oy = dest_bitmap.height // 2 + + if dest_clip0 is None: + dest_clip0 = (0,0) + if dest_clip1 is None: + dest_clip1 = (dest_bitmap.width,dest_bitmap.height) + + if px is None: + px = source_bitmap.width // 2 + if py in None: + py = source_bitmap.height // 2 + + if source_clip0 is None: + source_clip0 = (0,0) + if source_clip1 is None: + source_clip1 = (source_bitmap.width,source_bitmap.height) + + if angle is None: + angle = 0.0 + if scale is None: + scale = 1.0 + dest_clip0_x, dest_clip0_y = dest_clip0 dest_clip1_x, dest_clip1_y = dest_clip1 source_clip0_x, source_clip0_y = source_clip0 @@ -222,9 +249,8 @@ def arrayblit( bitmap: Bitmap, data: circuitpython_typing.ReadableBuffer, x1: int = 0, y1: int = 0, - x2: int | None = None, y2: int | None = None, - skip_index: int | None = None): - + x2: Optional[int] = None, y2: Optional[int] = None, + skip_index: Optional[int] = None): if x2 is None: x2 = bitmap.width if y2 is None: @@ -237,3 +263,368 @@ def arrayblit( value = int(data[i] % _value_count) if skip_index is None or value != skip_index: bitmap[x, y] = value + + +def readinto(bitmap: Bitmap, + file: BinaryIO, + bits_per_pixel: int, + element_size: int = 1, + reverse_pixels_in_element: bool = False, + swap_bytes: bool = False, + reverse_rows: bool = False): + width = bitmap.width + height = bitmap.height + bits_per_value = bitmap._bits_per_value + mask = (1 << bits_per_value) - 1 + + elements_per_row = (width * bits_per_pixel + element_size * 8 - 1) // (element_size * 8) + rowsize = element_size * elements_per_row + + for y in range(height): + row_bytes = file.read(rowsize) + if len(row_bytes) != rowsize: + raise EOFError() + + # Convert the raw bytes into the appropriate type array for processing + rowdata = bytearray(row_bytes) + + if swap_bytes: + if element_size == 2: + rowdata = bytearray( + b''.join( + struct.pack('H', rowdata[i:i + 2])[0]) + for i in range(0, len(rowdata), 2) + ) + ) + elif element_size == 4: + rowdata = bytearray( + b''.join( + struct.pack('I', rowdata[i:i + 4])[0]) + for i in range(0, len(rowdata), 4) + ) + ) + + y_draw = height - 1 - y if reverse_rows else y + + for x in range(width): + value = 0 + if bits_per_pixel == 1: + byte_offset = x // 8 + bit_offset = 7 - (x % 8) if reverse_pixels_in_element else x % 8 + value = (rowdata[byte_offset] >> bit_offset) & 0x1 + elif bits_per_pixel == 2: + byte_offset = x // 4 + bit_index = 3 - (x % 4) if reverse_pixels_in_element else x % 4 + bit_offset = 2 * bit_index + value = (rowdata[byte_offset] >> bit_offset) & 0x3 + elif bits_per_pixel == 4: + byte_offset = x // 2 + bit_index = 1 - (x % 2) if reverse_pixels_in_element else x % 2 + bit_offset = 4 * bit_index + value = (rowdata[byte_offset] >> bit_offset) & 0xF + elif bits_per_pixel == 8: + value = rowdata[x] + elif bits_per_pixel == 16: + value = struct.unpack_from('> 8) & 0xFF) + sp2 = ((sp2 & 0xFF) << 8) | ((sp2 >> 8) & 0xFF) + + blend_source1 = skip_source1_index is None or sp1 != skip_source1_index + blend_source2 = skip_source2_index is None or sp2 != skip_source2_index + + if blend_source1 and blend_source2: + ifactor_blend = ifactor1 + ifactor2 - ifactor1 * ifactor2 // 256 + + red_dca = ((sp1 & r_mask) >> 8) * ifactor1 + grn_dca = ((sp1 & g_mask) >> 3) * ifactor1 + blu_dca = ((sp1 & b_mask) << 3) * ifactor1 + + red_sca = ((sp2 & r_mask) >> 8) * ifactor2 + grn_sca = ((sp2 & g_mask) >> 3) * ifactor2 + blu_sca = ((sp2 & b_mask) << 3) * ifactor2 + + if blendmode == BlendMode.Screen: + red_blend = red_sca + red_dca - (red_sca * red_dca // 65536) + grn_blend = grn_sca + grn_dca - (grn_sca * grn_dca // 65536) + blu_blend = blu_sca + blu_dca - (blu_sca * blu_dca // 65536) + elif blendmode == BlendMode.Normal: + red_blend = red_sca + red_dca * (256 - ifactor2) // 256 + grn_blend = grn_sca + grn_dca * (256 - ifactor2) // 256 + blu_blend = blu_sca + blu_dca * (256 - ifactor2) // 256 + + r = ((red_blend // ifactor_blend) << 8) & r_mask + g = ((grn_blend // ifactor_blend) << 3) & g_mask + b = ((blu_blend // ifactor_blend) >> 3) & b_mask + + pixel = (r & r_mask) | (g & g_mask) | (b & b_mask) + + if swap: + pixel = ((pixel & 0xFF) << 8) | ((pixel >> 8) & 0xFF) + + elif blend_source1: + r = ((sp1 & r_mask) * ifactor1 // 256) & r_mask + g = ((sp1 & g_mask) * ifactor1 // 256) & g_mask + b = ((sp1 & b_mask) * ifactor1 // 256) & b_mask + pixel = r | g | b + elif blend_source2: + r = ((sp2 & r_mask) * ifactor2 // 256) & r_mask + g = ((sp2 & g_mask) * ifactor2 // 256) & g_mask + b = ((sp2 & b_mask) * ifactor2 // 256) & b_mask + pixel = r | g | b + else: + pixel = dest[x, y] + + print(f"pixel hex: {hex(pixel)}") + dest[x, y] = pixel + +class DitherAlgorithm: + Atkinson = "bitmaptools.DitherAlgorithm.Atkinson" + FloydStenberg = "bitmaptools.DitherAlgorithm.FloydStenberg" + + atkinson = { + 'count': 4, + 'mx': 2, + 'dl': 256 // 8, + 'terms': [ + {'dx': 2, 'dy': 0, 'dl': 256 // 8}, + {'dx': -1, 'dy': 1, 'dl': 256 // 8}, + {'dx': 0, 'dy': 1, 'dl': 256 // 8}, + {'dx': 0, 'dy': 2, 'dl': 256 // 8}, + ] + } + + floyd_stenberg = { + 'count': 3, + 'mx': 1, + 'dl': 7 * 256 // 16, + 'terms': [ + {'dx': -1, 'dy': 1, 'dl': 3 * 256 // 16}, + {'dx': 0, 'dy': 1, 'dl': 5 * 256 // 16}, + {'dx': 1, 'dy': 1, 'dl': 1 * 256 // 16}, + ] + } + + algorithm_map = { + Atkinson: atkinson, + FloydStenberg: floyd_stenberg + } + + +def dither(dest_bitmap, source_bitmap, colorspace, algorithm=DitherAlgorithm.Atkinson): + SWAP_BYTES = 1 << 0 + SWAP_RB = 1 << 1 + height, width = dest_bitmap.width, dest_bitmap.height + swap_bytes = colorspace in (Colorspace.RGB565_SWAPPED, Colorspace.BGR565_SWAPPED) + swap_rb = colorspace in (Colorspace.BGR565, Colorspace.BGR565_SWAPPED) + algorithm_info = DitherAlgorithm.algorithm_map[algorithm] + mx = algorithm_info['mx'] + count = algorithm_info['count'] + terms = algorithm_info['terms'] + dl = algorithm_info['dl'] + + swap = 0 + if swap_bytes: + swap |= SWAP_BYTES + + if swap_rb: + swap |= SWAP_RB + + print(f"swap: {swap}") + + # Create row data arrays (3 rows with padding on both sides) + rowdata = [[0] * (width + 2 * mx) for _ in range(3)] + rows = [ + rowdata[0][mx:], + rowdata[1][mx:], + rowdata[2][mx:] + ] + + # Output array for one row at a time (padded to multiple of 32) + out = [False] * (((width + 31) // 32) * 32) + + # Helper function to fill a row with luminance data + def fill_row(bitmap, swap, luminance_data, y, mx): + if y >= bitmap.height: + return + + # Zero out padding area + for i in range(mx): + luminance_data[-mx + i] = 0 + luminance_data[bitmap.width + i] = 0 + + if bitmap._bits_per_value == 8: + for x in range(bitmap.width): + luminance_data[x] = bitmap[x, y] + else: + for x in range(bitmap.width): + pixel = bitmap[x, y] + if swap & SWAP_BYTES: + # Swap bytes (equivalent to __builtin_bswap16) + pixel = ((pixel & 0xFF) << 8) | ((pixel >> 8) & 0xFF) + + r = (pixel >> 8) & 0xF8 + g = (pixel >> 3) & 0xFC + b = (pixel << 3) & 0xF8 + + if swap & SWAP_BYTES: + r, b = b, r + + # Calculate luminance using same formula as C version + luminance_data[x] = (r * 78 + g * 154 + b * 29) // 256 + + # Helper function to write pixels to destination bitmap + def write_pixels(bitmap, y, data): + if bitmap._bits_per_value == 1: + for i in range(0, bitmap.width, 32): + # Pack 32 bits into an integer + p = 0 + for j in range(min(32, bitmap.width - i)): + p = (p << 1) + if data[i + j]: + p |= 1 + + # Write packed value + for j in range(min(32, bitmap.width - i)): + bitmap[i + j, y] = (p >> (31 - j)) & 1 + else: + for i in range(bitmap.width): + bitmap[i, y] = 65535 if data[i] else 0 + + # Fill initial rows + fill_row(source_bitmap, swap, rows[0], 0, mx) + fill_row(source_bitmap, swap, rows[1], 1, mx) + fill_row(source_bitmap, swap, rows[2], 2, mx) + + err = 0 + + for y in range(height): + # Going left to right + for x in range(width): + pixel_in = rows[0][x] + err + pixel_out = pixel_in >= 128 + out[x] = pixel_out + + err = pixel_in - (255 if pixel_out else 0) + + # Distribute error to neighboring pixels + for i in range(count): + x1 = x + terms[i]['dx'] + dy = terms[i]['dy'] + + rows[dy][x1] = ((terms[i]['dl'] * err) // 256) + rows[dy][x1] + + err = (err * dl) // 256 + + write_pixels(dest_bitmap, y, out) + + # Cycle the rows + rows[0], rows[1], rows[2] = rows[1], rows[2], rows[0] + + y += 1 + if y == height: + break + + # Fill the next row for future processing + fill_row(source_bitmap, swap, rows[2], y + 2, mx) + + # Going right to left + for x in range(width - 1, -1, -1): + pixel_in = rows[0][x] + err + pixel_out = pixel_in >= 128 + out[x] = pixel_out + + err = pixel_in - (255 if pixel_out else 0) + + # Distribute error to neighboring pixels (in reverse direction) + for i in range(count): + x1 = x - terms[i]['dx'] + dy = terms[i]['dy'] + + rows[dy][x1] = ((terms[i]['dl'] * err) // 256) + rows[dy][x1] + + err = (err * dl) // 256 + + write_pixels(dest_bitmap, y, out) + + # Cycle the rows again + rows[0], rows[1], rows[2] = rows[1], rows[2], rows[0] + + # Fill the next row for future processing + fill_row(source_bitmap, swap, rows[2], y + 3, mx) + + # Mark the entire bitmap as dirty (this would be implementation-specific) + # In CircuitPython, this might be something like: + dest_bitmap.dirty() \ No newline at end of file