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
66import numpy as np
77from typing import Tuple , Optional
88
9- # Encoding threshold
9+ # Encoding thresholds
1010RLE_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
2861def 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+
164279def 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+
298579def 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 ("\n Generated 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 ("\n Generated C array:" )
745+ c_code = generate_c_array (result , size , width , height , ratio , "test.bmp" )
746+ print (c_code )
0 commit comments