-
Notifications
You must be signed in to change notification settings - Fork 2
/
image.py
219 lines (184 loc) · 8.54 KB
/
image.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# The class file for the image which will get converted
import os
import re
# Import Pillow
from PIL import Image, ImageOps
import numpy as np
from dither import *
# The class of the image being converted with all it's attributes
class ImageForConversion:
def __init__(self, num, path="", format=""):
self.number = num
self.file_path = path
self.file_format = format
self.processed_image_name = "image_"+str(num)
self.conversion_mode = 0
self.width = 0
self.height = 0
self.original_ratio = 0
self.bw_tresh = 50
self.invert = False
self.resize = False
self.constrain = False
self.ditherKernel = 0 # See dither function for what each of the values mean
self.resultString = "" # The resulting string
def getPath(self):
return self.file_path
def initial_process(self):
self.process_image_name()
self.set_conversion_mode(0)
self.set_default_width_height()
self.process_image() # Process it right away
def process_image_name(self):
# Extract the file name without the extension
base_name = os.path.basename(self.file_path)
file_name, _ = os.path.splitext(base_name)
# Cut the file name at 30 characters and remove spaces or special characters
file_name = re.sub(r'[^a-zA-Z0-9]', '_', file_name[:30])
# Update processed_image_name with the cleaned file name
self.processed_image_name = file_name
def set_conversion_mode(self, conversion_mode):
self.conversion_mode = conversion_mode
def set_default_width_height(self):
try:
with Image.open(self.file_path) as img:
self.width = img.width
self.height = img.height
self.original_ratio = float(img.width / img.height)
except Exception as e:
print(f"Error: {e}")
def convert_to_4bit_grayscale(self, img):
# Convert the image to a numpy array
img_array = np.array(img)
# Normalize the pixel values to 0-15 range
img_array = (img_array // 16) * 16
# Convert back to an image
img_4bit = Image.fromarray(img_array)
return img_4bit
# This function processes the image
def process_image(self):
try:
with Image.open(self.file_path) as img:
# Resize the image if needed
if self.resize:
img = img.resize((self.width, self.height))
# 1 bit processing
if self.conversion_mode == 0:
# Convert to grayscale
img = img.convert("L")
# Dithering
if self.ditherKernel == 0:
# No dithering
# Apply threshold to convert to black and white
threshold = int(self.bw_tresh * 255 / 100) # Map threshold from 0 to 100 to 0 to 255
img = img.point(lambda p: 255 if p > threshold else 0, '1') # Apply threshold
else:
# Dithering
img.save("preview.png") # Save to file
img_dither_input = cv2.imread("preview.png", 0)
dither_module = ditherModule()
img_dither_output = dither_module.dither(img_dither_input, method=self.ditherKernel,
resize=False)
cv2.imwrite("preview.png", img_dither_output)
img = Image.open("preview.png") # Re-open the image
# TODO why does treshold 0 give the desired result here?
threshold = 0 # Map threshold from 0 to 100 to 0 to 255
img = img.point(lambda p: 255 if p > threshold else 0, '1') # Apply threshold
# 4 bit processing
else:
img = img.convert("L")
if self.ditherKernel == 0:
# No dithering
img = self.convert_to_4bit_grayscale(img)
else:
# dithering
img.save("preview.png") # Save to file
img_dither_input = cv2.imread("preview.png", 0)
dither_module = ditherModule4bit()
img_dither_output = dither_module.dither(img_dither_input, 3,
resize=False)
cv2.imwrite("preview.png", img_dither_output)
img = Image.open("preview.png") # Re-open the image
img = img.convert("L")
# If invert is on, invert the image
if self.invert:
img = ImageOps.invert(img.convert("RGB")).convert(img.mode)
# Save or process the image further as needed
img.save("preview.png")
# Convert it to code also!
if self.conversion_mode == 0:
self.bw_image_to_c_array(img)
else:
self.grayscale_to_c_array(img)
except Exception as e:
print(f"An error occurred while processing the image: {e}")
def change_name(self, new_name):
# Define the regex pattern to find the parts to replace
pattern = r'const uint8_t\s+\w+\[\] PROGMEM ='
width_pattern = r'const uint16_t\s+\w+_w ='
height_pattern = r'const uint16_t\s+\w+_h ='
# Replace the names in the string
self.resultString = re.sub(pattern, f'const uint8_t {new_name}[] PROGMEM =', self.resultString)
self.resultString = re.sub(width_pattern, f'const uint16_t {new_name}_w =', self.resultString)
self.resultString = re.sub(height_pattern, f'const uint16_t {new_name}_h =', self.resultString)
self.processed_image_name = new_name
def bw_image_to_c_array(self, image):
# Ensure image is in 'L' mode (grayscale) and convert to 1-bit
image = image.convert('1')
# Get image dimensions
width, height = image.size
# Get pixel data
pixel_data = image.getdata()
# Create byte array
byte_array = []
for y in range(height):
for x in range(0, width, 8):
byte = 0
for bit in range(8):
if x + bit < width:
byte = (byte << 1) | (1 if pixel_data[y * width + x + bit] == 0 else 0)
else:
byte = (byte << 1)
byte_array.append(byte)
# Convert byte array to hex string
hex_array = ','.join(f'0x{byte:02X}' for byte in byte_array)
# Generate the C-style array string
result = (
f"const uint8_t {self.processed_image_name}[] PROGMEM = {{ {hex_array} }};\n"
f"const uint16_t {self.processed_image_name}_w = {str(self.width)};\n"
f"const uint16_t {self.processed_image_name}_h = {str(self.height)};"
)
# Lastly, save it
self.resultString = result
def grayscale_to_c_array(self, image):
# Ensure image is in 'L' mode (grayscale)
image = image.convert('L')
# Get image dimensions
width, height = image.size
# Get pixel data
pixel_data = image.getdata()
# Create byte array
byte_array = []
for y in range(height):
for x in range(0, width, 2):
byte = 0
# Pack two 4-bit grayscale values into one byte
for nibble in range(2):
if x + nibble < width:
grayscale_value = pixel_data[y * width + x + nibble]
# Convert 8-bit grayscale to 4-bit by dividing by 16
four_bit_value = grayscale_value // 16
byte = (byte << 4) | four_bit_value
else:
byte = (byte << 4)
byte_array.append(byte)
# Convert byte array to hex string
hex_array = ','.join(f'0x{byte:02X}' for byte in byte_array)
# Generate the C-style array string
result = (
f"const uint8_t {self.processed_image_name}[] PROGMEM = {{ {hex_array} }};\n"
f"const uint16_t {self.processed_image_name}_w = {str(self.width)};\n"
f"const uint16_t {self.processed_image_name}_h = {str(self.height)};"
)
# Lastly, save it
self.resultString = result