Last Updated on 2020-11-04 by Clay
Introduction
When we used PyInstaller to create a Python program as an executable file, we usually need to display a picture in our program.
But if we used PyInstaller to make an executable file directly, we found we can't show the picture. How does we solve this problem?
We have three methods to solve it, and I will record them in this article.
Method 1 | Method 2 | Method 3 | |
Degree of difficulty | Easy | Easy | Difficult |
Method | Put the picture in the directory of the executable file | Use absolute path to load picture | Convert the picture to byte and load it in the code |
Properties | On your computer, neither executable files nor pictures can be changed into folders | Can only be used on your own computer | It is still available even if you share it with others |
And I will introduce these ways.
Prepare in advance
If you want to know how does PyInstall work, may be you can refer my blog article in past: PyInstaller —— How to make a exe file from Python file
If you want to learn about PyQt5 module, may be you can refer my blog article: PyQt5 Tutorial (1) Install PyQt5 and print “Hello World!”
Here we go!
First, I use Qt Designer (a graphic tool) to create a simple window, there is only a component: QLabel.
My sample code:
# -*- coding: utf-8 -*-
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(796, 554)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(170, 70, 431, 341))
self.label.setText("")
self.label.setObjectName("label")
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
This is just a graphic interface code, I will write another program to inherit this interface class.
# -*- coding: utf-8 -*-
import sys
import cv2
from PyQt5 import QtWidgets
from PyQt5.QtGui import *
from test import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
image = cv2.imread('test.png')
h, w, channel = image.shape
bytesPerLine = 3*w
image = QImage(image, w, h, bytesPerLine, QImage.Format_RGB888).rgbSwapped()
image = QPixmap(image)
self.ui.label.setPixmap(image)
self.ui.label.setScaledContents(True)
if __name__ == '__main__':
app = QtWidgets.QApplication([])
window = MainWindow()
window.show()
sys.exit(app.exec_())
Output:
I use OpenCV module to load the picture (the picture named test.png), and use QPixmap()
to store it, and use the QLabel component to show it.
At this point, the preparatory work has come to an end.
Method-1: Put the picture in the directory of the executable file
First we using the Python package PyInstaller to package our program with picture to the executable file.
pyinstaller -F test_GUI.py --noconsole
(Explain: -F means we package to just only one executable file, --noconsole means we cancel the terminal black window show when we running the executable file)
Then we come to the dist folder.
We have an executable file test_GUI.exe, double click and run it.
We will get an error message: Failed to execute script test_GUI. This is normal, because the executable file cannot find the picture we want to display.
We can use the method 1 to fix it: We copy the explore picture and move it to this folder. Don't forget will must named it be test.png.
And then we try again!
This time we can execute the program normally. But I have to remind: I do not recommend this way. You can not move the picture, and the picture need to stay with the executable file.
Method-2: using absolution path to load picture
This is another easy way to display our picture.
image = cv2.imread('C:/Users/Clay/PycharmProjects/VideoScreenshot/test.png')
We use absolution path to replace the relative path, and we use PyInstaller to package this program again.
pyinstaller -F test_GUI.py --noconsole
This time, we have no picture under our executable file directory. But when we execute the test_GUI.exe:
We can show the picture! But we can not move the picture path (It is stored somewhere in our computer). This way is not good, we is difficult to share this executable program to others.
The best Method:
Convert picture to byte data, and put it in code and load it.
This is the my most recommended method, it is almost guaranteed to be shared with others.
Fist, we need to use base64 module to convert picture to byte data:
# -*- coding: utf-8 -*-
import base64
def pic2str(file, functionName):
pic = open(file, 'rb')
content = '{} = {}\n'.format(functionName, base64.b64encode(pic.read()))
pic.close()
with open('pic2str.py', 'a') as f:
f.write(content)
if __name__ == '__main__':
pic2str('test.png', 'explode')
The function pic2str() only needs to input picture path and variable name and it will be worked. This function will create a new .py file named pic2str.py to assign the picture byte data to the variable in the pic2str.py.
Let's take a look at the results:
The byte data is very long and it is too long make we can not see it all.
And we change some executable file code, import pic2str script (and its variable).
# -*- coding: utf-8 -*-
import sys
import base64
from io import BytesIO
from PyQt5 import QtWidgets
from PyQt5.QtGui import *
from test import Ui_MainWindow
from PIL import Image, ImageQt
from pic2str import explode
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# Load byte data
byte_data = base64.b64decode(explode)
image_data = BytesIO(byte_data)
image = Image.open(image_data)
# PIL to QPixmap
qImage = ImageQt.ImageQt(image)
image = QPixmap.fromImage(qImage)
# QPixmap to QLabel
self.ui.label.setPixmap(image)
self.ui.label.setScaledContents(True)
if __name__ == '__main__':
app = QtWidgets.QApplication([])
window = MainWindow()
window.show()
sys.exit(app.exec_())
And we package it again:
pyinstaller -F test_GUI.py --noconsole
We can delete all the explode picture in your computer and execute the test_GUI.exe. And you can see our program is executable successful.
If it can be displayed normally, it means that this method is very successful and we can share it with friends at will!
This method is worked because PyInstaller does not package Python files as native executable files, but creates an environment that can execute Python files, so usually this exe file is very big and not link to any picture.
External materials such as pictures are one of the objects that PyInstaller will not link together, so this loophole is exploited and the bytes of the picture are directly stored in the .py file.
If the .py files are not successfully packaged, it means that this module really has a big loophole.
However, I currently use PyInstaller. Although I have encountered a lot of pitfalls, it can be solved by patience.
By the way, if you get an error message:
ValueError: unsupported image mode 'LA'
Maybe you can refer this article: [Solved][Python] ValueError: unsupported image mode ‘LA’
謝謝你的分享!
不會,希望有幫助到你。
it is working!! thank you
I am glad to hear it!