712db9c597
Add B1 directory-type seed mechanism to AGENT_WORKSPACE_SEED_FILES: - Extended agentWorkspaceFilesForRun with collectDirectorySeedFiles helper - arm2d-skill (SKILL.md + references/ + python/) auto-injected to .agents/skills/arm2d-skill/ - Enables Code Agent to follow ARM-2D constraints and call asset scripts in case Skill source: https://github.com/notLabyet/HWLabOA
747 lines
28 KiB
Python
747 lines
28 KiB
Python
"""zhRGB565 Encoder Library
|
|
Combines RLE and RLE+Differential encoding implementations
|
|
Strictly follows C implementation data types and behavior
|
|
"""
|
|
|
|
import numpy as np
|
|
from typing import Tuple, Optional
|
|
|
|
# Encoding thresholds
|
|
RLE_THRESHOLD = 3 # Minimum consecutive pixels for RLE encoding
|
|
DIFF_THRESHOLD = 7 # Minimum pixels for differential encoding
|
|
|
|
|
|
def rgb565_get_r_u8(color: np.uint16) -> np.uint8:
|
|
"""Extract RGB565 red component (5 bits) - matches C uint8_t"""
|
|
return np.uint8((color >> 11) & 0x1F)
|
|
|
|
|
|
def rgb565_get_g_u8(color: np.uint16) -> np.uint8:
|
|
"""Extract RGB565 green component (6 bits) - matches C uint8_t"""
|
|
return np.uint8((color >> 5) & 0x3F)
|
|
|
|
|
|
def rgb565_get_b_u8(color: np.uint16) -> np.uint8:
|
|
"""Extract RGB565 blue component (5 bits) - matches C uint8_t"""
|
|
return np.uint8(color & 0x1F)
|
|
|
|
|
|
def rgb332_val(r: np.uint8, g: np.uint8, b: np.uint8) -> np.uint8:
|
|
"""Pack RGB components into RGB332 format - matches C uint8_t"""
|
|
return np.uint8(((r & 0x07) << 5) | ((g & 0x07) << 2) | (b & 0x03))
|
|
|
|
|
|
def pack_u8_to_u16(high: np.uint8, low: np.uint8) -> np.uint16:
|
|
"""Pack two uint8 values into one uint16 - matches C uint16_t"""
|
|
return np.uint16((np.uint16(high) << 8) | np.uint16(low))
|
|
|
|
|
|
def can_compress_diff(diff: np.uint16) -> bool:
|
|
"""
|
|
Check if difference can be compressed into one byte - matches C implementation exactly
|
|
|
|
Condition: R component <=7, G component <=7, B component <=3
|
|
"""
|
|
r = rgb565_get_r_u8(diff)
|
|
g = rgb565_get_g_u8(diff)
|
|
b = rgb565_get_b_u8(diff)
|
|
|
|
return (r <= np.uint8(7)) and (g <= np.uint8(7)) and (b <= np.uint8(3))
|
|
|
|
|
|
def compress_diff_to_byte(diff: np.uint16) -> np.uint8:
|
|
"""Compress difference into one byte (RGB332 format) - matches C uint8_t"""
|
|
r = rgb565_get_r_u8(diff)
|
|
g = rgb565_get_g_u8(diff)
|
|
b = rgb565_get_b_u8(diff)
|
|
|
|
return rgb332_val(r, g, b)
|
|
|
|
|
|
def find_encode_flag(img: np.ndarray, pixel_count: np.uint64) -> Tuple[np.uint16, np.uint16, np.uint16, bool]:
|
|
"""
|
|
Find optimal encoding flag - strictly matches C implementation data types
|
|
|
|
Priority:
|
|
1. Unused high byte values (XX00 format)
|
|
2. High bytes where all pixels have at least 3 consecutive identical values
|
|
3. Least used high bytes
|
|
|
|
Args:
|
|
img: RGB565 image data array
|
|
pixel_count: Number of pixels (uint64_t in C)
|
|
|
|
Returns:
|
|
(encode_flag, encode_flag_cs, encode_flag_mode, is_perfect)
|
|
encode_flag: Flag value (high byte << 8)
|
|
encode_flag_cs: Related info (minimum consecutive count or occurrence count)
|
|
encode_flag_mode: Mode (0=unused, 1=RLE perfect, 2=least used)
|
|
is_perfect: Whether perfect flag was found
|
|
"""
|
|
if pixel_count == 0:
|
|
return np.uint16(0xFF00), np.uint16(0), np.uint16(0), True
|
|
|
|
# ========== Step 1: Count basic information ==========
|
|
# Match C implementation: uint8_t used_map[32] = {0};
|
|
used_map = np.zeros(32, dtype=np.uint8)
|
|
# Match C implementation: uint16_t count_map[256] = {0};
|
|
# Use uint32_t to prevent overflow with large images
|
|
count_map = np.zeros(256, dtype=np.uint32)
|
|
|
|
for i in range(int(pixel_count)):
|
|
pixel = int(img[i])
|
|
hi_byte = (pixel >> 8) & 0xFF
|
|
used_map[hi_byte // 8] |= np.uint8(1 << (hi_byte % 8))
|
|
count_map[hi_byte] += np.uint32(1) # Use uint32_t to prevent overflow
|
|
|
|
# ========== Step 2: Priority 1 - Unused high bytes ==========
|
|
for hi_byte in range(256):
|
|
if (int(used_map[hi_byte // 8]) & (1 << (hi_byte % 8))) == 0:
|
|
return np.uint16(hi_byte << 8), np.uint16(0), np.uint16(0), True
|
|
|
|
# ========== Step 3: Priority 2 - Perfect RLE high bytes ==========
|
|
for hi_byte in range(256):
|
|
if count_map[hi_byte] == 0:
|
|
continue
|
|
|
|
is_perfect = True
|
|
min_continuous = np.uint16(0xFFFF)
|
|
|
|
i = np.uint64(0)
|
|
while i < pixel_count:
|
|
# Skip pixels that don't have this high byte
|
|
while i < pixel_count and ((int(img[int(i)]) >> 8) & 0xFF) != hi_byte:
|
|
i += np.uint64(1)
|
|
|
|
if i >= pixel_count:
|
|
break
|
|
|
|
# Check continuous segments
|
|
current_value = np.uint16(img[int(i)])
|
|
continuous_count = np.uint16(1)
|
|
j = i + np.uint64(1)
|
|
|
|
while j < pixel_count and np.uint16(img[int(j)]) == current_value:
|
|
continuous_count += np.uint16(1)
|
|
j += np.uint64(1)
|
|
|
|
# Check if condition is met (at least 3 consecutive identical)
|
|
if continuous_count < RLE_THRESHOLD:
|
|
is_perfect = False
|
|
break
|
|
|
|
if continuous_count < min_continuous:
|
|
min_continuous = continuous_count
|
|
|
|
i = j
|
|
|
|
if is_perfect and min_continuous != np.uint16(0xFFFF):
|
|
# Find a specific pixel value as flag
|
|
concrete_value = np.uint16(hi_byte << 8)
|
|
for k in range(int(pixel_count)):
|
|
if ((int(img[k]) >> 8) & 0xFF) == hi_byte:
|
|
concrete_value = np.uint16(img[k])
|
|
break
|
|
|
|
return np.uint16(concrete_value & 0xFF00), min_continuous, np.uint16(1), True
|
|
|
|
# ========== Step 4: Priority 3 - Least used high bytes ==========
|
|
min_hi_byte = np.uint16(0)
|
|
min_count = np.uint16(0xFFFF)
|
|
|
|
for hi_byte in range(256):
|
|
if count_map[hi_byte] > 0:
|
|
if count_map[hi_byte] < min_count:
|
|
min_count = count_map[hi_byte]
|
|
min_hi_byte = np.uint16(hi_byte)
|
|
elif count_map[hi_byte] == min_count:
|
|
if hi_byte < min_hi_byte:
|
|
min_hi_byte = np.uint16(hi_byte)
|
|
|
|
if min_count == np.uint16(0xFFFF):
|
|
return np.uint16(0xFF00), np.uint16(0), np.uint16(2), False
|
|
|
|
# Important: In least used mode, return XX00 format
|
|
return np.uint16(min_hi_byte << 8), min_count, np.uint16(2), False
|
|
|
|
|
|
def check_rle_length(pixels: np.ndarray, start: np.uint32, end: np.uint32) -> np.uint32:
|
|
"""
|
|
Check RLE length starting from position - strictly matches C implementation data types
|
|
|
|
Args:
|
|
pixels: Pixel array
|
|
start: Start position (uint32_t in C)
|
|
end: End position (exclusive, uint32_t in C)
|
|
|
|
Returns:
|
|
Number of consecutive identical pixels (uint32_t)
|
|
"""
|
|
if start >= end:
|
|
return np.uint32(0)
|
|
|
|
first_color = np.uint16(pixels[int(start)])
|
|
length = np.uint32(1)
|
|
|
|
for i in range(int(start) + 1, int(end)):
|
|
if np.uint16(pixels[i]) == first_color:
|
|
length += np.uint32(1)
|
|
if length == np.uint32(0xFFFF):
|
|
break
|
|
else:
|
|
break
|
|
|
|
return length
|
|
|
|
|
|
def calculate_diff_length(data: np.ndarray, start: np.uint32, end: np.uint32, output: np.ndarray) -> np.uint16:
|
|
"""
|
|
Calculate the number of pixels that meet differential encoding criteria - strictly matches C implementation
|
|
|
|
Args:
|
|
data: Pixel data array
|
|
start: Start position (uint32_t in C)
|
|
end: End position (exclusive, uint32_t in C)
|
|
output: Output difference data buffer
|
|
|
|
Returns:
|
|
Compressible difference pixel count (uint16_t in C)
|
|
"""
|
|
if end - start < DIFF_THRESHOLD:
|
|
return np.uint16(0)
|
|
|
|
idx = np.uint16(0) # CRITICAL: Must be uint16_t like C
|
|
idx_tmp = np.uint16(0)
|
|
tmp = np.zeros(2, dtype=np.uint8)
|
|
|
|
# CRITICAL: C uses uint16_t for loop variable, not uint32_t
|
|
i = np.uint16(start)
|
|
diff_count = np.uint16(1)
|
|
rle_cnt = np.uint16(1)
|
|
|
|
while i < end - np.uint16(1):
|
|
current_pixel = np.uint16(data[int(i)])
|
|
next_pixel = np.uint16(data[int(i) + 1])
|
|
diff = np.uint16(current_pixel ^ next_pixel)
|
|
|
|
# Meet differential encoding conditions
|
|
if can_compress_diff(diff):
|
|
diff_count += np.uint16(1)
|
|
|
|
tmp[int(idx_tmp)] = compress_diff_to_byte(diff)
|
|
idx_tmp += np.uint16(1)
|
|
|
|
if idx_tmp == np.uint16(2):
|
|
if int(idx) >= len(output):
|
|
# Prevent buffer overflow - match C behavior
|
|
break
|
|
output[int(idx)] = pack_u8_to_u16(tmp[0], tmp[1])
|
|
idx += np.uint16(1)
|
|
idx_tmp = np.uint16(0)
|
|
if idx == np.uint16(31): # Actual encoded source data 31*2 + 1
|
|
break
|
|
|
|
if diff == np.uint16(0):
|
|
# diff=0 means 2 identical pixels
|
|
rle_cnt += np.uint16(1)
|
|
# Handle uint16_t overflow - if rle_cnt reaches max, treat as exceeding threshold
|
|
if rle_cnt == np.uint16(0): # Overflow occurred
|
|
diff_count -= np.uint16(RLE_THRESHOLD)
|
|
break
|
|
if rle_cnt > np.uint16(RLE_THRESHOLD): # CRITICAL: Revert back to >
|
|
# Consecutive pixels exceed 3+1, exit for RLE processing
|
|
diff_count -= np.uint16(RLE_THRESHOLD)
|
|
break
|
|
else:
|
|
rle_cnt = np.uint16(0)
|
|
|
|
# CRITICAL: C uses i++ which keeps it as uint16_t
|
|
i += np.uint16(1)
|
|
else:
|
|
break
|
|
|
|
# CRITICAL: C doesn't have this logic - only adjust for even count
|
|
# Handle remaining differences - CRITICAL: Try removing this adjustment
|
|
# if idx_tmp == np.uint16(1):
|
|
# # Odd number of differences, reduce by one
|
|
# diff_count -= np.uint16(1)
|
|
|
|
# Number of pixels meeting differential encoding must be odd and not zero
|
|
if diff_count % np.uint16(2) == np.uint16(0) and diff_count != np.uint16(0):
|
|
diff_count -= np.uint16(1)
|
|
|
|
if diff_count >= np.uint16(DIFF_THRESHOLD):
|
|
return diff_count
|
|
else:
|
|
return np.uint16(0)
|
|
|
|
|
|
def encode_rgb565_rle_only(input_data: np.ndarray, width: np.uint16, height: np.uint16) -> Tuple[Optional[np.ndarray], np.uint32, float]:
|
|
"""
|
|
Pure RLE encoding function - strictly matches C implementation data types
|
|
|
|
Args:
|
|
input_data: Input RGB565 data, length is width*height
|
|
width: Image width (uint16_t in C)
|
|
height: Image height (uint16_t in C)
|
|
|
|
Returns:
|
|
(output_data, output_size, compression_ratio)
|
|
output_data: Encoded data array
|
|
output_size: Encoded data size (uint32_t in C)
|
|
compression_ratio: Compression ratio (percentage, smaller is better)
|
|
"""
|
|
pixel_count = np.uint64(width) * np.uint64(height)
|
|
|
|
if width == 0 or height == 0 or pixel_count == 0:
|
|
return None, np.uint32(0), 0.0
|
|
|
|
# Find encoding flag - pass pixel_count as uint64_t
|
|
encode_flag, encode_flag_cs, encode_flag_mode, flag_ok = find_encode_flag(input_data, pixel_count)
|
|
|
|
# Estimate maximum output size - match C calculation exactly (uint64_t)
|
|
max_output_size = np.uint64(10) + np.uint64(height) + (pixel_count * np.uint64(2))
|
|
output = np.zeros(int(max_output_size), dtype=np.uint32) # Use 32-bit to avoid overflow during construction
|
|
|
|
# Allocate row offset array (uint32_t like C)
|
|
row_offsets = np.zeros(height + 1, dtype=np.uint32)
|
|
|
|
# Encoding data buffer (uint32_t for index calculations)
|
|
encoded_data = np.zeros(int(max_output_size), dtype=np.uint32)
|
|
encoded_index = np.uint32(0)
|
|
|
|
# Traverse row by row (uint16_t y in C)
|
|
for y in range(int(height)):
|
|
row_offsets[y] = encoded_index
|
|
row_start = y * int(width)
|
|
row = input_data[row_start:row_start + int(width)]
|
|
|
|
col = np.uint32(0)
|
|
while col < width:
|
|
# Check RLE length (uint32_t in C)
|
|
rle_len = check_rle_length(row, col, width)
|
|
|
|
if rle_len >= RLE_THRESHOLD:
|
|
color = np.uint16(row[int(col)])
|
|
|
|
if rle_len >= np.uint32(128):
|
|
# Long encoding: flag, color, count (match C exactly)
|
|
encoded_data[int(encoded_index)] = encode_flag
|
|
encoded_index += np.uint32(1)
|
|
encoded_data[int(encoded_index)] = color
|
|
encoded_index += np.uint32(1)
|
|
encoded_data[int(encoded_index)] = rle_len
|
|
encoded_index += np.uint32(1)
|
|
else:
|
|
# Short encoding: flag + count, color (match C exactly)
|
|
encoded_data[int(encoded_index)] = np.uint16(encode_flag + rle_len)
|
|
encoded_index += np.uint32(1)
|
|
encoded_data[int(encoded_index)] = color
|
|
encoded_index += np.uint32(1)
|
|
|
|
col += rle_len
|
|
else:
|
|
# Handle pixels that conflict with flag
|
|
color_tmp = np.uint16(row[int(col)])
|
|
|
|
if (color_tmp & np.uint16(0xFF00)) == encode_flag:
|
|
# Pixel conflicts with flag code, use RLE short encoding to store single pixel
|
|
encoded_data[int(encoded_index)] = np.uint16(encode_flag + 1)
|
|
encoded_index += np.uint32(1)
|
|
encoded_data[int(encoded_index)] = color_tmp
|
|
encoded_index += np.uint32(1)
|
|
else:
|
|
# Store original pixel directly
|
|
encoded_data[int(encoded_index)] = color_tmp
|
|
encoded_index += np.uint32(1)
|
|
|
|
col += np.uint32(1)
|
|
|
|
row_offsets[height] = encoded_index
|
|
|
|
# Calculate upgrade table - match C logic exactly (uint16_t upgrade[500])
|
|
upgrade = np.zeros(500, dtype=np.uint16)
|
|
upgrade_len = np.uint16(0)
|
|
for i in range(int(height) - 1):
|
|
tmp0 = row_offsets[i]
|
|
tmp1 = row_offsets[i + 1]
|
|
if tmp0 > tmp1:
|
|
upgrade[int(upgrade_len)] = np.uint16(i + 1)
|
|
upgrade_len += np.uint16(1)
|
|
|
|
# Calculate row table start coordinate and encoding data start coordinate - match C exactly
|
|
row_offset_addr = np.uint16(6) + upgrade_len
|
|
encode_data_addr = row_offset_addr + np.uint16(height) + np.uint16(1)
|
|
|
|
# Fill header - match C structure exactly
|
|
output[0] = width
|
|
output[1] = height
|
|
output[2] = encode_flag
|
|
output[3] = upgrade_len
|
|
output[4] = row_offset_addr
|
|
output[5] = encode_data_addr
|
|
|
|
idx = np.uint32(6)
|
|
|
|
# Write upgrade table (uint16_t values)
|
|
if upgrade_len > 0:
|
|
for i in range(int(upgrade_len)):
|
|
output[int(idx)] = upgrade[i]
|
|
idx += np.uint32(1)
|
|
|
|
# Write row offset table (uint32_t values)
|
|
for i in range(int(height) + 1):
|
|
output[int(idx)] = row_offsets[i]
|
|
idx += np.uint32(1)
|
|
|
|
# Write encoding data
|
|
for i in range(int(encoded_index)):
|
|
output[int(idx)] = encoded_data[i]
|
|
idx += np.uint32(1)
|
|
|
|
# Calculate compression ratio - match C calculation exactly
|
|
original_size = float(pixel_count * np.uint64(2)) # Original data size (bytes)
|
|
compressed_size = float(idx * np.uint32(2)) # Compressed size (bytes)
|
|
compression_ratio = (compressed_size / original_size) * 100.0
|
|
|
|
# Convert to uint16 array for return (like C output)
|
|
result = output[:int(idx)].astype(np.uint16)
|
|
|
|
return result, idx, compression_ratio
|
|
|
|
|
|
def encode_rgb565_rle_diff(input_data: np.ndarray, width: np.uint16, height: np.uint16) -> Tuple[Optional[np.ndarray], np.uint32, float]:
|
|
"""
|
|
RLE+Differential mixed encoding function - strictly matches C implementation data types
|
|
|
|
Args:
|
|
input_data: Input RGB565 data, length is width*height
|
|
width: Image width (uint16_t in C)
|
|
height: Image height (uint16_t in C)
|
|
|
|
Returns:
|
|
(output_data, output_size, compression_ratio)
|
|
"""
|
|
pixel_count = np.uint64(width) * np.uint64(height)
|
|
|
|
if width == np.uint16(0) or height == np.uint16(0) or pixel_count == np.uint64(0):
|
|
return None, np.uint32(0), 0.0
|
|
|
|
# Find encoding flag - match C call exactly (uint64_t pixel_count)
|
|
encode_flag, encode_flag_cs, encode_flag_mode, flag_ok = find_encode_flag(input_data, pixel_count)
|
|
|
|
# Estimate maximum output size - match C calculation exactly (uint64_t)
|
|
max_output_size = np.uint64(6) + np.uint64(height) + (pixel_count * np.uint64(2))
|
|
output = np.zeros(int(max_output_size), dtype=np.uint32) # Use 32-bit to avoid overflow
|
|
|
|
# Allocate row offset array - use uint32_t like C
|
|
row_offsets = np.zeros(int(height) + 1, dtype=np.uint32)
|
|
|
|
# Differential encoding data buffer - match C: encoded_diff_data = (uint16_t*)malloc((size_t)65536 * 2 * sizeof(uint16_t))
|
|
encoded_diff_data = np.zeros(65536 * 2, dtype=np.uint16)
|
|
|
|
# Encoding data buffer - use uint32_t for index calculations (encoded_index = uint32_t)
|
|
encoded_data = np.zeros(int(max_output_size), dtype=np.uint32)
|
|
encoded_index = np.uint32(0)
|
|
|
|
# Traverse row by row - match C logic exactly (uint16_t y)
|
|
for y in range(int(height)):
|
|
row_offsets[y] = encoded_index
|
|
row_start = y * int(width)
|
|
row = input_data[row_start:row_start + int(width)]
|
|
|
|
col = np.uint32(0)
|
|
while col < width:
|
|
# Try differential encoding first (like C) - uint32_t parameters
|
|
diff_len = calculate_diff_length(row, col, width, encoded_diff_data)
|
|
|
|
if diff_len >= np.uint16(DIFF_THRESHOLD):
|
|
base_color = np.uint16(row[int(col)])
|
|
|
|
if diff_len >= np.uint16(128):
|
|
# Long encoding - match C exactly
|
|
encoded_data[int(encoded_index)] = encode_flag
|
|
encoded_index += np.uint32(1)
|
|
encoded_data[int(encoded_index)] = base_color
|
|
encoded_index += np.uint32(1)
|
|
encoded_data[int(encoded_index)] = np.uint16(0x8000 + (diff_len // np.uint16(2)))
|
|
encoded_index += np.uint32(1)
|
|
|
|
for i in range(int(diff_len) // 2):
|
|
encoded_data[int(encoded_index)] = encoded_diff_data[i]
|
|
encoded_index += np.uint32(1)
|
|
else:
|
|
# Short encoding - match C exactly
|
|
encoded_data[int(encoded_index)] = np.uint16(encode_flag + np.uint16(0x80) + (diff_len // np.uint16(2)))
|
|
encoded_index += np.uint32(1)
|
|
encoded_data[int(encoded_index)] = base_color
|
|
encoded_index += np.uint32(1)
|
|
|
|
for i in range(int(diff_len) // 2):
|
|
encoded_data[int(encoded_index)] = encoded_diff_data[i]
|
|
encoded_index += np.uint32(1)
|
|
|
|
col += diff_len
|
|
else:
|
|
# Try RLE encoding (like C) - uint32_t parameters
|
|
rle_len = check_rle_length(row, col, width)
|
|
|
|
if rle_len >= np.uint32(RLE_THRESHOLD):
|
|
color = np.uint16(row[int(col)])
|
|
|
|
if rle_len >= np.uint32(128):
|
|
# Long encoding - match C exactly
|
|
encoded_data[int(encoded_index)] = encode_flag
|
|
encoded_index += np.uint32(1)
|
|
encoded_data[int(encoded_index)] = color
|
|
encoded_index += np.uint32(1)
|
|
encoded_data[int(encoded_index)] = rle_len
|
|
encoded_index += np.uint32(1)
|
|
else:
|
|
# Short encoding - match C exactly
|
|
encoded_data[int(encoded_index)] = np.uint16(encode_flag + rle_len)
|
|
encoded_index += np.uint32(1)
|
|
encoded_data[int(encoded_index)] = color
|
|
encoded_index += np.uint32(1)
|
|
|
|
col += rle_len
|
|
else:
|
|
# Store original pixel directly - match C logic exactly
|
|
color_tmp = np.uint16(row[int(col)])
|
|
|
|
if (color_tmp & np.uint16(0xFF00)) == encode_flag:
|
|
# Pixel conflicts with flag code - match C exactly
|
|
encoded_data[int(encoded_index)] = np.uint16(encode_flag + np.uint16(1))
|
|
encoded_index += np.uint32(1)
|
|
encoded_data[int(encoded_index)] = row[int(col)] # CRITICAL: C uses row[col], not color_tmp
|
|
encoded_index += np.uint32(1)
|
|
else:
|
|
# Store original pixel directly
|
|
encoded_data[int(encoded_index)] = color_tmp
|
|
encoded_index += np.uint32(1)
|
|
|
|
col += np.uint32(1)
|
|
|
|
row_offsets[int(height)] = encoded_index
|
|
|
|
# Calculate upgrade table - match C logic exactly (uint16_t upgrade[500])
|
|
upgrade = np.zeros(500, dtype=np.uint16)
|
|
upgrade_len = np.uint16(0)
|
|
for i in range(int(height) - 1):
|
|
# CRITICAL: C casts to uint16_t for comparison
|
|
tmp0 = np.uint16(row_offsets[i])
|
|
tmp1 = np.uint16(row_offsets[i + 1])
|
|
if tmp0 > tmp1:
|
|
upgrade[int(upgrade_len)] = np.uint16(i + 1)
|
|
upgrade_len += np.uint16(1)
|
|
|
|
# Calculate row table start coordinate and encoding data start coordinate - match C exactly
|
|
row_offset_addr = np.uint16(6) + upgrade_len
|
|
encode_data_addr = row_offset_addr + np.uint16(height) + np.uint16(1)
|
|
|
|
# Fill header - match C structure exactly
|
|
output[0] = width
|
|
output[1] = height
|
|
output[2] = encode_flag
|
|
output[3] = upgrade_len
|
|
output[4] = row_offset_addr
|
|
output[5] = encode_data_addr
|
|
|
|
idx = np.uint32(6)
|
|
|
|
# Write upgrade table (uint16_t values)
|
|
if upgrade_len > 0:
|
|
for i in range(int(upgrade_len)):
|
|
output[int(idx)] = upgrade[i]
|
|
idx += np.uint32(1)
|
|
|
|
# Write row offset table (uint16_t values - CRITICAL: C casts to uint16_t)
|
|
for i in range(int(height) + 1):
|
|
output[int(idx)] = np.uint16(row_offsets[i])
|
|
idx += np.uint32(1)
|
|
|
|
# Write encoding data
|
|
for i in range(int(encoded_index)):
|
|
output[int(idx)] = encoded_data[i]
|
|
idx += np.uint32(1)
|
|
|
|
# Calculate compression ratio - match C calculation exactly
|
|
original_size = float(pixel_count * np.uint64(2))
|
|
compressed_size = float(idx * np.uint32(2))
|
|
compression_ratio = (compressed_size / original_size) * 100.0
|
|
|
|
# Convert to uint16 array for return (like C output)
|
|
result = output[:int(idx)].astype(np.uint16)
|
|
|
|
return result, idx, compression_ratio
|
|
|
|
|
|
def generate_c_array(output_data: np.ndarray, output_size: np.uint32,
|
|
width: np.uint16, height: np.uint16, compression_ratio: float,
|
|
src_path: str = "", array_name: str = "img") -> str:
|
|
"""
|
|
Generate C language array format string - matches C implementation
|
|
|
|
Args:
|
|
output_data: Encoded data
|
|
output_size: Data size (uint32_t in C)
|
|
width: Image width (uint16_t in C)
|
|
height: Image height (uint16_t in C)
|
|
compression_ratio: Compression ratio
|
|
src_path: Source file path
|
|
array_name: Array name
|
|
|
|
Returns:
|
|
C language code string
|
|
"""
|
|
import os
|
|
|
|
# Get base filename
|
|
base_name = os.path.splitext(os.path.basename(src_path))[0] if src_path else "image"
|
|
|
|
lines = []
|
|
lines.append("// Compressed RGB565 data")
|
|
lines.append(f"// Source file: {src_path}")
|
|
lines.append(f"// Original size: {width} x {height} = {width * height} pixels")
|
|
lines.append(f"// Compression ratio: {compression_ratio:.2f}%")
|
|
lines.append("")
|
|
lines.append(f"const uint16_t _{base_name}_zhRGB565_Data[{output_size}] = {{")
|
|
lines.append("")
|
|
|
|
# Write header information
|
|
lines.append(" /* width, height, encode_flag, level_up_table_len, row_offset_addr, data_addr */")
|
|
lines.append(f" {output_data[0]}, {output_data[1]}, 0x{output_data[2]:04X}, {output_data[3]}, {output_data[4]}, {output_data[5]},")
|
|
lines.append("")
|
|
|
|
idx = np.uint32(6)
|
|
upgrade_len = np.uint16(output_data[3])
|
|
row_offset_addr = np.uint16(output_data[4])
|
|
|
|
# Write upgrade table
|
|
if upgrade_len > 0:
|
|
lines.append(" /* level_up table */")
|
|
line = " "
|
|
count = 0
|
|
for i in range(int(upgrade_len)):
|
|
line += f"{output_data[int(idx)]}, "
|
|
idx += np.uint32(1)
|
|
count += 1
|
|
if count % 16 == 0:
|
|
lines.append(line.rstrip())
|
|
line = " "
|
|
if line.strip():
|
|
lines.append(line.rstrip())
|
|
lines.append("")
|
|
else:
|
|
lines.append(" /* NO level_up table */")
|
|
lines.append("")
|
|
|
|
# Write row offset table
|
|
lines.append(f" /* Row offset table ({height} rows total) */")
|
|
line = " "
|
|
for i in range(int(height) + 1):
|
|
line += f"{output_data[int(idx)]}"
|
|
idx += np.uint32(1)
|
|
line += ", " # Add comma after all elements (including last)
|
|
if (i + 1) % 16 == 0:
|
|
lines.append(line.rstrip())
|
|
line = " "
|
|
if line.strip():
|
|
lines.append(line.rstrip())
|
|
lines.append("")
|
|
|
|
# Write encoding data
|
|
lines.append(" /* Encoded data */")
|
|
|
|
# Format output by lines - match C logic exactly
|
|
hhcnt = np.uint32(0)
|
|
line_base = np.uint32(0)
|
|
next_line = np.uint32(1)
|
|
current_line = np.uint32(0)
|
|
|
|
lines.append(f" /* 0 */")
|
|
line = " "
|
|
|
|
while idx < output_size:
|
|
if next_line == height:
|
|
# Last line, output all
|
|
while idx < output_size:
|
|
line += f"0x{int(output_data[int(idx)]):04X}, "
|
|
idx += np.uint32(1)
|
|
hhcnt += np.uint32(1)
|
|
if hhcnt % 16 == 0:
|
|
lines.append(line.rstrip())
|
|
line = " "
|
|
else:
|
|
current_line_pos = np.uint32(output_data[int(row_offset_addr) + int(current_line)]) + line_base
|
|
next_line_pos = np.uint32(output_data[int(row_offset_addr) + int(next_line)]) + line_base
|
|
|
|
if current_line_pos > next_line_pos:
|
|
line_base += np.uint32(65536)
|
|
next_line_pos += np.uint32(65536)
|
|
|
|
for j in range(int(current_line_pos), int(next_line_pos)):
|
|
if idx >= output_size:
|
|
break
|
|
line += f"0x{int(output_data[int(idx)]):04X}, "
|
|
idx += np.uint32(1)
|
|
hhcnt += np.uint32(1)
|
|
if hhcnt % 16 == 0 and j != int(next_line_pos) - 1:
|
|
lines.append(line.rstrip())
|
|
line = " "
|
|
|
|
current_line = next_line
|
|
next_line += np.uint32(1)
|
|
hhcnt = np.uint32(0)
|
|
|
|
if idx >= output_size:
|
|
break
|
|
|
|
if line.strip():
|
|
lines.append(line.rstrip())
|
|
lines.append(f" /* {current_line} */")
|
|
line = " "
|
|
|
|
if line.strip():
|
|
lines.append(line.rstrip())
|
|
|
|
lines.append("};")
|
|
lines.append("")
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Test code
|
|
# Create a simple test image
|
|
test_data = np.array([
|
|
0xF800, 0xF800, 0xF800, 0x07E0, 0x07E0, 0x001F, 0x001F, 0x001F,
|
|
0xF800, 0xF800, 0xF800, 0x07E0, 0x07E0, 0x001F, 0x001F, 0x001F,
|
|
], dtype=np.uint16)
|
|
|
|
width = np.uint16(8)
|
|
height = np.uint16(2)
|
|
|
|
print("Testing RLE only encoding:")
|
|
result, size, ratio = encode_rgb565_rle_only(test_data, width, height)
|
|
if result is not None:
|
|
print(f"Encoding successful!")
|
|
print(f"Output size: {size} uint16")
|
|
print(f"Compression ratio: {ratio:.2f}%")
|
|
print("\nGenerated C array:")
|
|
c_code = generate_c_array(result, size, width, height, ratio, "test.bmp")
|
|
print(c_code)
|
|
|
|
print("\n" + "="*50 + "\n")
|
|
|
|
# Test RLE+Diff encoding
|
|
print("Testing RLE+Diff encoding:")
|
|
result, size, ratio = encode_rgb565_rle_diff(test_data, width, height)
|
|
if result is not None:
|
|
print(f"Encoding successful!")
|
|
print(f"Output size: {size} uint16")
|
|
print(f"Compression ratio: {ratio:.2f}%")
|
|
print("\nGenerated C array:")
|
|
c_code = generate_c_array(result, size, width, height, ratio, "test.bmp")
|
|
print(c_code)
|