Skip to content

Commit 233dfd6

Browse files
authored
Developing (#68)
* Add files via upload zhRGB565 Compression algorithm in python * Update img2c.py add_argument ''--zhRGB565'' and ''--zhRGB565-diff'' for zhRGB565 Compression algorithm * Update rle_diff_encoder.py comment in English * Update rle_encoder.py comment in English * Update img2c.py fix bug * Update rle_diff_encoder.py Strictly follows C implementation data types * Update rle_encoder.py Strictly follows C implementation data types * Implement zhRGB565 encoder library Add zhRGB565 encoder library for RLE and differential encoding. * Fix import statement for zhRGB565 module Fix import statement for zhRGB565 module * Delete tools/zhRGB565_core directory Delete tools/zhRGB565_core directory
1 parent 8a77a66 commit 233dfd6

File tree

4 files changed

+309
-411
lines changed

4 files changed

+309
-411
lines changed

tools/img2c.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040

4141
# Import zhRGB565 compression modules
4242
try:
43-
from zhRGB565_core import (
43+
from zhRGB565 import (
4444
encode_rgb565_rle_only,
4545
generate_c_array as generate_rle_c_array,
4646
encode_rgb565_rle_diff,
Lines changed: 308 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,61 @@
1-
"""
2-
RLE (Run-Length Encoding) Encoder
3-
Pure RLE encoding implementation - Strictly follows C implementation data types
1+
"""zhRGB565 Encoder Library
2+
Combines RLE and RLE+Differential encoding implementations
3+
Strictly follows C implementation data types and behavior
44
"""
55

66
import numpy as np
77
from typing import Tuple, Optional
88

9-
# Encoding threshold
9+
# Encoding thresholds
1010
RLE_THRESHOLD = 3 # Minimum consecutive pixels for RLE encoding
11+
DIFF_THRESHOLD = 7 # Minimum pixels for differential encoding
12+
13+
14+
def rgb565_get_r_u8(color: np.uint16) -> np.uint8:
15+
"""Extract RGB565 red component (5 bits) - matches C uint8_t"""
16+
return np.uint8((color >> 11) & 0x1F)
17+
18+
19+
def rgb565_get_g_u8(color: np.uint16) -> np.uint8:
20+
"""Extract RGB565 green component (6 bits) - matches C uint8_t"""
21+
return np.uint8((color >> 5) & 0x3F)
22+
23+
24+
def rgb565_get_b_u8(color: np.uint16) -> np.uint8:
25+
"""Extract RGB565 blue component (5 bits) - matches C uint8_t"""
26+
return np.uint8(color & 0x1F)
27+
28+
29+
def rgb332_val(r: np.uint8, g: np.uint8, b: np.uint8) -> np.uint8:
30+
"""Pack RGB components into RGB332 format - matches C uint8_t"""
31+
return np.uint8(((r & 0x07) << 5) | ((g & 0x07) << 2) | (b & 0x03))
1132

1233

13-
def rgb565_get_r(color: int) -> int:
14-
"""Extract RGB565 red component (5 bits)"""
15-
return (color >> 11) & 0x1F
34+
def pack_u8_to_u16(high: np.uint8, low: np.uint8) -> np.uint16:
35+
"""Pack two uint8 values into one uint16 - matches C uint16_t"""
36+
return np.uint16((np.uint16(high) << 8) | np.uint16(low))
1637

1738

18-
def rgb565_get_g(color: int) -> int:
19-
"""Extract RGB565 green component (6 bits)"""
20-
return (color >> 5) & 0x3F
39+
def can_compress_diff(diff: np.uint16) -> bool:
40+
"""
41+
Check if difference can be compressed into one byte - matches C implementation exactly
42+
43+
Condition: R component <=7, G component <=7, B component <=3
44+
"""
45+
r = rgb565_get_r_u8(diff)
46+
g = rgb565_get_g_u8(diff)
47+
b = rgb565_get_b_u8(diff)
48+
49+
return (r <= np.uint8(7)) and (g <= np.uint8(7)) and (b <= np.uint8(3))
2150

2251

23-
def rgb565_get_b(color: int) -> int:
24-
"""Extract RGB565 blue component (5 bits)"""
25-
return color & 0x1F
52+
def compress_diff_to_byte(diff: np.uint16) -> np.uint8:
53+
"""Compress difference into one byte (RGB332 format) - matches C uint8_t"""
54+
r = rgb565_get_r_u8(diff)
55+
g = rgb565_get_g_u8(diff)
56+
b = rgb565_get_b_u8(diff)
57+
58+
return rgb332_val(r, g, b)
2659

2760

2861
def find_encode_flag(img: np.ndarray, pixel_count: np.uint64) -> Tuple[np.uint16, np.uint16, np.uint16, bool]:
@@ -161,6 +194,88 @@ def check_rle_length(pixels: np.ndarray, start: np.uint32, end: np.uint32) -> np
161194
return length
162195

163196

197+
def calculate_diff_length(data: np.ndarray, start: np.uint32, end: np.uint32, output: np.ndarray) -> np.uint16:
198+
"""
199+
Calculate the number of pixels that meet differential encoding criteria - strictly matches C implementation
200+
201+
Args:
202+
data: Pixel data array
203+
start: Start position (uint32_t in C)
204+
end: End position (exclusive, uint32_t in C)
205+
output: Output difference data buffer
206+
207+
Returns:
208+
Compressible difference pixel count (uint16_t in C)
209+
"""
210+
if end - start < DIFF_THRESHOLD:
211+
return np.uint16(0)
212+
213+
idx = np.uint16(0) # CRITICAL: Must be uint16_t like C
214+
idx_tmp = np.uint16(0)
215+
tmp = np.zeros(2, dtype=np.uint8)
216+
217+
# CRITICAL: C uses uint16_t for loop variable, not uint32_t
218+
i = np.uint16(start)
219+
diff_count = np.uint16(1)
220+
rle_cnt = np.uint16(1)
221+
222+
while i < end - np.uint16(1):
223+
current_pixel = np.uint16(data[int(i)])
224+
next_pixel = np.uint16(data[int(i) + 1])
225+
diff = np.uint16(current_pixel ^ next_pixel)
226+
227+
# Meet differential encoding conditions
228+
if can_compress_diff(diff):
229+
diff_count += np.uint16(1)
230+
231+
tmp[int(idx_tmp)] = compress_diff_to_byte(diff)
232+
idx_tmp += np.uint16(1)
233+
234+
if idx_tmp == np.uint16(2):
235+
if int(idx) >= len(output):
236+
# Prevent buffer overflow - match C behavior
237+
break
238+
output[int(idx)] = pack_u8_to_u16(tmp[0], tmp[1])
239+
idx += np.uint16(1)
240+
idx_tmp = np.uint16(0)
241+
if idx == np.uint16(31): # Actual encoded source data 31*2 + 1
242+
break
243+
244+
if diff == np.uint16(0):
245+
# diff=0 means 2 identical pixels
246+
rle_cnt += np.uint16(1)
247+
# Handle uint16_t overflow - if rle_cnt reaches max, treat as exceeding threshold
248+
if rle_cnt == np.uint16(0): # Overflow occurred
249+
diff_count -= np.uint16(RLE_THRESHOLD)
250+
break
251+
if rle_cnt > np.uint16(RLE_THRESHOLD): # CRITICAL: Revert back to >
252+
# Consecutive pixels exceed 3+1, exit for RLE processing
253+
diff_count -= np.uint16(RLE_THRESHOLD)
254+
break
255+
else:
256+
rle_cnt = np.uint16(0)
257+
258+
# CRITICAL: C uses i++ which keeps it as uint16_t
259+
i += np.uint16(1)
260+
else:
261+
break
262+
263+
# CRITICAL: C doesn't have this logic - only adjust for even count
264+
# Handle remaining differences - CRITICAL: Try removing this adjustment
265+
# if idx_tmp == np.uint16(1):
266+
# # Odd number of differences, reduce by one
267+
# diff_count -= np.uint16(1)
268+
269+
# Number of pixels meeting differential encoding must be odd and not zero
270+
if diff_count % np.uint16(2) == np.uint16(0) and diff_count != np.uint16(0):
271+
diff_count -= np.uint16(1)
272+
273+
if diff_count >= np.uint16(DIFF_THRESHOLD):
274+
return diff_count
275+
else:
276+
return np.uint16(0)
277+
278+
164279
def encode_rgb565_rle_only(input_data: np.ndarray, width: np.uint16, height: np.uint16) -> Tuple[Optional[np.ndarray], np.uint32, float]:
165280
"""
166281
Pure RLE encoding function - strictly matches C implementation data types
@@ -295,6 +410,172 @@ def encode_rgb565_rle_only(input_data: np.ndarray, width: np.uint16, height: np.
295410
return result, idx, compression_ratio
296411

297412

413+
def encode_rgb565_rle_diff(input_data: np.ndarray, width: np.uint16, height: np.uint16) -> Tuple[Optional[np.ndarray], np.uint32, float]:
414+
"""
415+
RLE+Differential mixed encoding function - strictly matches C implementation data types
416+
417+
Args:
418+
input_data: Input RGB565 data, length is width*height
419+
width: Image width (uint16_t in C)
420+
height: Image height (uint16_t in C)
421+
422+
Returns:
423+
(output_data, output_size, compression_ratio)
424+
"""
425+
pixel_count = np.uint64(width) * np.uint64(height)
426+
427+
if width == np.uint16(0) or height == np.uint16(0) or pixel_count == np.uint64(0):
428+
return None, np.uint32(0), 0.0
429+
430+
# Find encoding flag - match C call exactly (uint64_t pixel_count)
431+
encode_flag, encode_flag_cs, encode_flag_mode, flag_ok = find_encode_flag(input_data, pixel_count)
432+
433+
# Estimate maximum output size - match C calculation exactly (uint64_t)
434+
max_output_size = np.uint64(6) + np.uint64(height) + (pixel_count * np.uint64(2))
435+
output = np.zeros(int(max_output_size), dtype=np.uint32) # Use 32-bit to avoid overflow
436+
437+
# Allocate row offset array - use uint32_t like C
438+
row_offsets = np.zeros(int(height) + 1, dtype=np.uint32)
439+
440+
# Differential encoding data buffer - match C: encoded_diff_data = (uint16_t*)malloc((size_t)65536 * 2 * sizeof(uint16_t))
441+
encoded_diff_data = np.zeros(65536 * 2, dtype=np.uint16)
442+
443+
# Encoding data buffer - use uint32_t for index calculations (encoded_index = uint32_t)
444+
encoded_data = np.zeros(int(max_output_size), dtype=np.uint32)
445+
encoded_index = np.uint32(0)
446+
447+
# Traverse row by row - match C logic exactly (uint16_t y)
448+
for y in range(int(height)):
449+
row_offsets[y] = encoded_index
450+
row_start = y * int(width)
451+
row = input_data[row_start:row_start + int(width)]
452+
453+
col = np.uint32(0)
454+
while col < width:
455+
# Try differential encoding first (like C) - uint32_t parameters
456+
diff_len = calculate_diff_length(row, col, width, encoded_diff_data)
457+
458+
if diff_len >= np.uint16(DIFF_THRESHOLD):
459+
base_color = np.uint16(row[int(col)])
460+
461+
if diff_len >= np.uint16(128):
462+
# Long encoding - match C exactly
463+
encoded_data[int(encoded_index)] = encode_flag
464+
encoded_index += np.uint32(1)
465+
encoded_data[int(encoded_index)] = base_color
466+
encoded_index += np.uint32(1)
467+
encoded_data[int(encoded_index)] = np.uint16(0x8000 + (diff_len // np.uint16(2)))
468+
encoded_index += np.uint32(1)
469+
470+
for i in range(int(diff_len) // 2):
471+
encoded_data[int(encoded_index)] = encoded_diff_data[i]
472+
encoded_index += np.uint32(1)
473+
else:
474+
# Short encoding - match C exactly
475+
encoded_data[int(encoded_index)] = np.uint16(encode_flag + np.uint16(0x80) + (diff_len // np.uint16(2)))
476+
encoded_index += np.uint32(1)
477+
encoded_data[int(encoded_index)] = base_color
478+
encoded_index += np.uint32(1)
479+
480+
for i in range(int(diff_len) // 2):
481+
encoded_data[int(encoded_index)] = encoded_diff_data[i]
482+
encoded_index += np.uint32(1)
483+
484+
col += diff_len
485+
else:
486+
# Try RLE encoding (like C) - uint32_t parameters
487+
rle_len = check_rle_length(row, col, width)
488+
489+
if rle_len >= np.uint32(RLE_THRESHOLD):
490+
color = np.uint16(row[int(col)])
491+
492+
if rle_len >= np.uint32(128):
493+
# Long encoding - match C exactly
494+
encoded_data[int(encoded_index)] = encode_flag
495+
encoded_index += np.uint32(1)
496+
encoded_data[int(encoded_index)] = color
497+
encoded_index += np.uint32(1)
498+
encoded_data[int(encoded_index)] = rle_len
499+
encoded_index += np.uint32(1)
500+
else:
501+
# Short encoding - match C exactly
502+
encoded_data[int(encoded_index)] = np.uint16(encode_flag + rle_len)
503+
encoded_index += np.uint32(1)
504+
encoded_data[int(encoded_index)] = color
505+
encoded_index += np.uint32(1)
506+
507+
col += rle_len
508+
else:
509+
# Store original pixel directly - match C logic exactly
510+
color_tmp = np.uint16(row[int(col)])
511+
512+
if (color_tmp & np.uint16(0xFF00)) == encode_flag:
513+
# Pixel conflicts with flag code - match C exactly
514+
encoded_data[int(encoded_index)] = np.uint16(encode_flag + np.uint16(1))
515+
encoded_index += np.uint32(1)
516+
encoded_data[int(encoded_index)] = row[int(col)] # CRITICAL: C uses row[col], not color_tmp
517+
encoded_index += np.uint32(1)
518+
else:
519+
# Store original pixel directly
520+
encoded_data[int(encoded_index)] = color_tmp
521+
encoded_index += np.uint32(1)
522+
523+
col += np.uint32(1)
524+
525+
row_offsets[int(height)] = encoded_index
526+
527+
# Calculate upgrade table - match C logic exactly (uint16_t upgrade[500])
528+
upgrade = np.zeros(500, dtype=np.uint16)
529+
upgrade_len = np.uint16(0)
530+
for i in range(int(height) - 1):
531+
# CRITICAL: C casts to uint16_t for comparison
532+
tmp0 = np.uint16(row_offsets[i])
533+
tmp1 = np.uint16(row_offsets[i + 1])
534+
if tmp0 > tmp1:
535+
upgrade[int(upgrade_len)] = np.uint16(i + 1)
536+
upgrade_len += np.uint16(1)
537+
538+
# Calculate row table start coordinate and encoding data start coordinate - match C exactly
539+
row_offset_addr = np.uint16(6) + upgrade_len
540+
encode_data_addr = row_offset_addr + np.uint16(height) + np.uint16(1)
541+
542+
# Fill header - match C structure exactly
543+
output[0] = width
544+
output[1] = height
545+
output[2] = encode_flag
546+
output[3] = upgrade_len
547+
output[4] = row_offset_addr
548+
output[5] = encode_data_addr
549+
550+
idx = np.uint32(6)
551+
552+
# Write upgrade table (uint16_t values)
553+
if upgrade_len > 0:
554+
for i in range(int(upgrade_len)):
555+
output[int(idx)] = upgrade[i]
556+
idx += np.uint32(1)
557+
558+
# Write row offset table (uint16_t values - CRITICAL: C casts to uint16_t)
559+
for i in range(int(height) + 1):
560+
output[int(idx)] = np.uint16(row_offsets[i])
561+
idx += np.uint32(1)
562+
563+
# Write encoding data
564+
for i in range(int(encoded_index)):
565+
output[int(idx)] = encoded_data[i]
566+
idx += np.uint32(1)
567+
568+
# Calculate compression ratio - match C calculation exactly
569+
original_size = float(pixel_count * np.uint64(2))
570+
compressed_size = float(idx * np.uint32(2))
571+
compression_ratio = (compressed_size / original_size) * 100.0
572+
573+
# Convert to uint16 array for return (like C output)
574+
result = output[:int(idx)].astype(np.uint16)
575+
576+
return result, idx, compression_ratio
577+
578+
298579
def generate_c_array(output_data: np.ndarray, output_size: np.uint32,
299580
width: np.uint16, height: np.uint16, compression_ratio: float,
300581
src_path: str = "", array_name: str = "img") -> str:
@@ -441,6 +722,7 @@ def generate_c_array(output_data: np.ndarray, output_size: np.uint32,
441722
width = np.uint16(8)
442723
height = np.uint16(2)
443724

725+
print("Testing RLE only encoding:")
444726
result, size, ratio = encode_rgb565_rle_only(test_data, width, height)
445727
if result is not None:
446728
print(f"Encoding successful!")
@@ -449,3 +731,16 @@ def generate_c_array(output_data: np.ndarray, output_size: np.uint32,
449731
print("\nGenerated C array:")
450732
c_code = generate_c_array(result, size, width, height, ratio, "test.bmp")
451733
print(c_code)
734+
735+
print("\n" + "="*50 + "\n")
736+
737+
# Test RLE+Diff encoding
738+
print("Testing RLE+Diff encoding:")
739+
result, size, ratio = encode_rgb565_rle_diff(test_data, width, height)
740+
if result is not None:
741+
print(f"Encoding successful!")
742+
print(f"Output size: {size} uint16")
743+
print(f"Compression ratio: {ratio:.2f}%")
744+
print("\nGenerated C array:")
745+
c_code = generate_c_array(result, size, width, height, ratio, "test.bmp")
746+
print(c_code)

0 commit comments

Comments
 (0)