-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathmain.py
370 lines (336 loc) · 15.1 KB
/
main.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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
from PyQt5.QtWidgets import QApplication, QFileDialog, QStyle, QMessageBox
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtGui import QPixmap, QIcon, QIntValidator
from PIL import Image, ImageOps
from PIL.ImageQt import ImageQt
import numpy as np
import sys
import os
# Constants
EIGHT_BIT = 8
main_gui_file_name = "main_gui.ui"
# Path to the icon
script_dir_path = os.path.dirname(os.path.realpath(__file__))
icon_path = os.sep.join([script_dir_path, "images", "icon.png"])
# Generate the path to the gui file
qtCreatorFile = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), main_gui_file_name)
# Load the main GUI
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)
#------------------------------------------------
# Main Class
#------------------------------------------------
class MyApp(QtWidgets.QMainWindow, Ui_MainWindow):
__image_path = None
__image_width = 0
__image_height = 0
original_width =0
original_height =0
image_ratio = 0
__is_inverted = False # Flag to keep track if the image was inverted
image_array = None
image = None
original_image = None
#-------------------------------
# Constructor
#-------------------------------
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
self.setWindowTitle("Open LCD Assisstant")
self.init_gui()
self.load_button.clicked.connect(self.load_image)
self.save_button.clicked.connect(self.save_bmp_txt)
self.convert_button.clicked.connect(self.convert_image)
self.invert_button.clicked.connect(self.invert_image)
self.resize_button.clicked.connect(self.resize_image)
self.reset_button.clicked.connect(self.reset_image)
self.width_lineEdit.textEdited.connect(self.set_new_height)
self.height_lineEdit.textEdited.connect(self.set_new_width)
#-------------------------------
# Initialize some GUI's element
#-------------------------------
def init_gui(self):
# Set the window's size automatically to the UI made in QtDesigner
self.setFixedSize(self.geometry().width(),self.geometry().height())
self.radio_horizontal.setChecked(True)
self.convert_button.setEnabled(False)
self.save_button.setEnabled(False)
self.radio_vertical.setEnabled(False)
self.radio_horizontal.setEnabled(False)
self.invert_button.setEnabled(False)
# Add somne styl to :
# convert button
pixmapi = QStyle.SP_CommandLink
icon = self.style().standardIcon(pixmapi)
self.convert_button.setIcon(icon)
# invert button
pixmapi = QStyle.SP_BrowserReload
icon = self.style().standardIcon(pixmapi)
self.invert_button.setIcon(icon)
# save button
pixmapi = QStyle.SP_DialogSaveButton
icon = self.style().standardIcon(pixmapi)
self.save_button.setIcon(icon)
# GUI elements Related to resize
# Restricts input to integer
self.width_lineEdit.setValidator(QIntValidator())
self.height_lineEdit.setValidator(QIntValidator())
# Desable before loading the image
self.width_lineEdit.setEnabled(False)
self.height_lineEdit.setEnabled(False)
self.keep_ratio.setEnabled(False)
self.keep_ratio.setChecked(True)
self.resize_button.setEnabled(False)
self.reset_button.setEnabled(False)
#---------------------------------------------------
# Callback function for the load image button
#---------------------------------------------------
def load_image(self):
'''
Callback function for the load image button
Loads the image to be converted
'''
# Get the image path
self.__image_path, _ = QtWidgets.QFileDialog.getOpenFileName(self, 'Single File','', 'Image Files(*.png *.jpg *.bmp)')
self.path_line_edit.setText(self.__image_path)
if os.path.isfile(self.__image_path):
# Load the image
self.image = Image.open(self.__image_path)
self.original_width, self.original_height = self.image.size
self.width_lineEdit.setText(str(self.original_width))
self.height_lineEdit.setText(str(self.original_height))
self.image_ratio = self.original_width/self.original_height
# Convert it to monochrom bitmap and creat np array
self.image_to_array()
# Keep the original image after monochrom conversion
self.original_image = self.image
# Display the image
self.display_image(self.image)
# Set is inverted? flag to default
self.__is_inverted = False
# Enable buttons and co.
self.convert_button.setEnabled(True)
self.radio_vertical.setEnabled(True)
self.radio_horizontal.setEnabled(True)
self.invert_button.setEnabled(True)
self.width_lineEdit.setEnabled(True)
self.height_lineEdit.setEnabled(True)
self.keep_ratio.setEnabled(True)
self.resize_button.setEnabled(True)
self.reset_button.setEnabled(True)
#------------------------------------------
# Display the image in the GUI
# Parameters:
# image : The image to be displayed
#------------------------------------------
def display_image(self, image):
'''
Show the image in the GUI
Parameters:
image : The image to be displayed
'''
qim = ImageQt(image)
pixmap = QtGui.QPixmap.fromImage(qim)
scene = QtWidgets.QGraphicsScene(self)
item = QtWidgets.QGraphicsPixmapItem(pixmap)
scene.addItem(item)
self.image_view.setScene(scene)
#------------------------------------------
# Callback function for the conver button
#------------------------------------------
def convert_image(self):
'''
Callback function for the conver button.
Select the conversion method
'''
vertical = self.radio_vertical.isChecked()
horizontal = self.radio_horizontal.isChecked()
if vertical:
self.convert_vertical()
self.save_button.setEnabled(True)
if horizontal:
self.convert_horizontal()
self.save_button.setEnabled(True)
#----------------------------------------------------------
# Convert the image to monochrom bitmap and creat Numpy array
#----------------------------------------------------------
def image_to_array(self):
'''
Convert the image to monochrom bitmap and creat Numpy array
'''
self.image = self.image.convert('1')
self.image_array = np.asarray(self.image,dtype=int)
self.__image_height, self.__image_width = self.image_array.shape
#---------------------------------------------------
# Save the result of the bitmap array in a text file
#---------------------------------------------------
def save_bmp_txt(self):
'''
Save the result of the bitmap array in a text file
'''
save_file_name = f'bitmap_{os.path.basename(self.__image_path)[:-4]}'
save_path, filter = QFileDialog.getSaveFileName(self, "Save file as (*.txt)", save_file_name, "Text files (*.txt)")
if not save_path:
return
text = self.plainTextEdit.toPlainText()
with open(save_path, 'w') as f:
f.write(text)
#------------------------------
# Format the image
#------------------------------
def format_image(self):
'''
Adjust the width and height so that they can be divided by 8.
1 Byte = 8 Bits
'''
if (self.__image_width % EIGHT_BIT) == 0:
pad_width = 0
elif (self.__image_width % EIGHT_BIT) != 0:
pad_width = EIGHT_BIT - (self.__image_width % EIGHT_BIT)
if (self.__image_height % EIGHT_BIT) == 0:
pad_height = 0
elif (self.__image_height % EIGHT_BIT) != 0:
pad_height = EIGHT_BIT - (self.__image_height % EIGHT_BIT)
formated_image_array = np.pad(self.image_array, [(0,pad_height), (0, pad_width)], mode='constant')
return formated_image_array
#------------------------------
# Invert the image pixel color
#------------------------------
def invert_image(self):
'''
Invert the image pixel color
'''
# Convert the image to a supported inversion format, before inverting
self.image = ImageOps.invert( self.image.convert('RGB') )
#Display the new image
self.display_image(self.image)
#Set the flag to inverted state
self.__is_inverted = not (self.__is_inverted)
# Display a message to the status bar only if the image is inverted
message = "Image inverted" if self.__is_inverted else ""
self.statusBar().showMessage(message)
#---------------------------------------------------------------------
# Calculate & Set the new height according to the entred value of the width
#---------------------------------------------------------------------
def set_new_height(self):
# Get the new Width and Height from the GUI
new_width = self.width_lineEdit.text()
if self.keep_ratio.isChecked():
if new_width:
new_height = round(int(new_width) / self.image_ratio)
self.height_lineEdit.setText(str(new_height))
#---------------------------------------------------------------------
# Calculate & Set the new width according to the entred value of the Height
#---------------------------------------------------------------------
def set_new_width(self):
# Get the new Width and Height from the GUI
new_height = self.height_lineEdit.text()
if self.keep_ratio.isChecked():
if new_height:
new_width = round(int(new_height) * self.image_ratio)
self.width_lineEdit.setText(str(new_width))
#----------------------------------------------------------
# Resize the original image to the new size (Width, Height)
#----------------------------------------------------------
def resize_image(self):
'''
Resize the original image to the new size (Width, Height)
Parameters:
Width, Height : inputs from the G
'''
# Get the new Width and Height from the GUI
new_width = self.width_lineEdit.text()
new_height = self.height_lineEdit.text()
# The inputs are not empty
if not(new_width and new_height):
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setText("Widht or Height cannot be empty!")
msg.setInformativeText("Please enter a valide value")
_ = msg.exec_()
elif int(new_width) == self.original_width and int(new_height) == self.original_height and (self.image == self.original_image):
pass # Just do nothing if everything is the same
else:
new_width = int(new_width)
new_height = int(new_height)
self.resized_image = self.image.resize((new_width, new_height))
self.image = self.resized_image
self.display_image(self.image)
self.plainTextEdit.clear()
#-------------------------------------------
# Reset the edited image to the original one
#-------------------------------------------
def reset_image(self):
'''
Reset the edited image to the original one
'''
# Get the new Width and Height from the GUI
new_width = self.width_lineEdit.text()
new_height = self.height_lineEdit.text()
if int(new_width) == self.original_width and int(new_height) == self.original_height and (self.image == self.original_image):
pass # If everything is as the original, just pass...
else:
self.image = self.original_image
self.width_lineEdit.setText(str(self.original_width))
self.height_lineEdit.setText(str(self.original_height))
self.__is_inverted = False
self.statusBar().clearMessage()
self.display_image(self.original_image)
self.plainTextEdit.clear()
#----------------------------------------------------------------------
# Convert the image into bitmap array using the vertical (byte orientation) method
#----------------------------------------------------------------------
def convert_vertical(self):
'''
Convert the image into bitmap array using the vertical (byte orientation) method
'''
byte_array = ''
index = 0
pad_array = self.format_image()
for y in range(0,self.__image_width,EIGHT_BIT):
for x in range(self.__image_height):
one_byte = pad_array[y:y+8,x]
a = (''.join(str(y) for y in np.flip(one_byte)))
byte_array += '0x'+format((int(a, 2)), '02x')+','
index += 1
if index%16 == 0:
byte_array += '\n'
byte_array = f'const unsigned char bitmap_{os.path.basename(self.__image_path)[:-4]} [] PROGMEM ={{\n' + byte_array.strip()[:-1] + '};' # remove the last comma
self.plainTextEdit.setPlainText(byte_array)
return byte_array
#----------------------------------------------------------------------
# Convert the image into bitmap array using the horizontal (byte orientation) method
#----------------------------------------------------------------------
def convert_horizontal(self):
'''
Convert the image into bitmap array using the horizontal (byte orientation) method
'''
self.image_to_array()
image_size = f'#define {os.path.basename(self.__image_path)[:-4]}_width \t {self.__image_width}\n'
image_size += f'#define {os.path.basename(self.__image_path)[:-4]}_height \t {self.__image_height}\n'
byte_array = ''
index = 0
pad_array = self.format_image()
height, width = pad_array.shape
for x in range(height):
for y in range(0,width,EIGHT_BIT):
one_byte = pad_array[x, y:y+EIGHT_BIT]
a = (''.join(str(i) for i in np.flip(one_byte))) # flip LSB and MSB
byte_array += '0x'+format((int(a, 2)), '02x')+','
index += 1
if index%16 == 0:
byte_array += '\n'
byte_array = f'const unsigned char {os.path.basename(self.__image_path)[:-4]}_bmp [] PROGMEM ={{\n' + byte_array.strip()[:-1] + '};' # remove the last comma
self.plainTextEdit.setPlainText(image_size+byte_array)
return byte_array
#------------------------------------------------
# Start the App
#------------------------------------------------
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
app.setWindowIcon(QIcon(icon_path))
app.setApplicationName("Open LCD")
window = MyApp()
window.show()
sys.exit(app.exec_())