How to Build a Simple WebP Conversion Tool with Python, Qt and OpenCV

“WebP is an image format employing both lossy and lossless compression, and supports animation and alpha transparency. Developed by Google, it is designed to create files that are smaller for the same quality, or of higher quality for the same size, than JPEG, PNG, and GIF image formats” — Wikipedia. Speed is one of the key factors of SEO optimization. Developers tend to use WebP to replace JPEG, PNG, and GIF in order to make web pages SEO friendly. This article demonstrates how to build a simple WebP conversion tool with Python, Qt and OpenCV.

Installation

pip install opencv-python pyside2

Developing the WebP Conversion Tool

Step 1: Design the layout and load UI to Python Code

Let’s open Python/Lib/site-packages/PySide2/designer to design the GUI.

  • Label: display the loaded image.

Once the UI design is done, we convert the .ui file to .py file using pyside2-uic, which is located at Python/Scripts/.

The next step is to load the design.py file and add the following code to the main.py file:

import sys
from PySide2.QtGui import QPixmap, QImage, QPainter, QPen, QColor
from PySide2.QtWidgets import QApplication, QMainWindow, QInputDialog
from PySide2.QtCore import QFile, QTimer, QEvent
from PySide2.QtWidgets import *
from design import Ui_MainWindow

import os
import cv2

from PySide2.QtCore import QObject, QThread, Signal

class MainWindow(QMainWindow):

def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.setAcceptDrops(True)

def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())


if __name__ == '__main__':
main()

Now, running the main.py file will show the GUI.

There are two ways to load an image file or a folder:

  • Click a button to open the system file dialog.

To open the system file dialog, we use QFileDialog, which provides getOpenFileName to pick a file and getExistingDirectory to pick a directory.

self.ui.actionOpen_File.triggered.connect(self.openFile)
self.ui.actionOpen_Folder.triggered.connect(self.openFolder)

def openFile(self):
filename = QFileDialog.getOpenFileName(self, 'Open File',
self._path, "Barcode images (*)")

def openFolder(self):
directory = QFileDialog.getExistingDirectory(self, 'Open Folder',
self._path, QFileDialog.ShowDirsOnly)

To enable drag and drop for file and folder, we must set setAcceptDrops(True) for the MainWindow and override the dragEnterEvent and dropEvent methods:

def __init__(self):
# ...
self.setAcceptDrops(True)

def dragEnterEvent(self, event):
event.acceptProposedAction()

def dropEvent(self, event):
urls = event.mimeData().urls()
filename = urls[0].toLocalFile()
if os.path.isdir(filename):
self.appendFolder(filename)
else:
self.appendFile(filename)
event.acceptProposedAction()

As we get the file path, create a new list widget item and add it to the list widget:

item = QListWidgetItem()
item.setText(filename)
self.ui.listWidget.addItem(item)

To display the image in the Qt label, we need to convert Mat to QImage:

def showImage(self, filename):
frame = cv2.imread(filename)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
image = QImage(frame, frame.shape[1], frame.shape[0], frame.strides[0], QImage.Format_RGB888)
pixmap = QPixmap.fromImage(image)
self._pixmap = self.resizeImage(pixmap)
self.ui.label.setPixmap(self._pixmap)
return frame

Step 3: Get the slider value and convert image to WebP

Because the latest OpenCV supports WebP, it is convenient to convert an image to WebP using cv2.imwrite():

quality = int(self.ui.horizontalSlider.value())
frame = cv2.imread(filename)
webp_file = filename.split('.')[0] + '.webp'
cv2.imwrite(webp_file, frame, [cv2.IMWRITE_WEBP_QUALITY, quality])

Considering the performance of operating multiple images, we move the WebP conversion code to a worker thread. In addition, showing a progress bar makes the application more responsive.

class Worker(QObject):
finished = Signal()
progress = Signal(object)

def __init__(self, files, quality):
super(Worker, self).__init__()
self.files = files
self.total = len(files)
self.isRunning = True
self.quality = quality

def run(self):
count = 0
keys = list(self.files.keys())
while self.isRunning and len(self.files) > 0:
filename = keys[count]
count += 1
print(filename)
frame = cv2.imread(filename)
webp_file = filename.split('.')[0] + '.webp'
cv2.imwrite(webp_file, frame, [cv2.IMWRITE_WEBP_QUALITY, self.quality])
self.progress.emit((webp_file, count, self.total))
self.files.pop(filename)

self.finished.emit()

def reportProgress(self, data):
filename, completed, total = data
self.addImage(filename)
if not self.isProcessing:
return

progress = completed
self.progress_dialog.setLabelText(str(completed) +"/"+ str(total))
self.progress_dialog.setValue(progress)
if completed == total:
self.onProgressDialogCanceled()
self.showMessageBox('WebP Conversion', "Done!")

def onProgressDialogCanceled(self):
self.isProcessing = False
self.worker.isRunning = False
self.progress_dialog.cancel()

def runLongTask(self):
if (len(self._all_images) == 0):
return

self.isProcessing = True
self.progress_dialog = QProgressDialog('Progress', 'Cancel', 0, len(self._all_images), self)
self.progress_dialog.setLabelText('Progress')
self.progress_dialog.setCancelButtonText('Cancel')
self.progress_dialog.setRange(0, len(self._all_images))
self.progress_dialog.setValue(0)
self.progress_dialog.setMinimumDuration(0)
self.progress_dialog.show()
self.progress_dialog.canceled.connect(self.onProgressDialogCanceled)

self.thread = QThread()
self.worker = Worker(self._all_images, int(self.ui.label_slider.text()))
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.worker.progress.connect(self.reportProgress)
self.thread.start()

Step 4: Run the application to convert images to WebP

python main.py

Source Code

https://github.com/yushulx/webp-image-conversion

Originally published at https://www.dynamsoft.com on September 22, 2021.

Manager of Dynamsoft Open Source Projects | Tech Lover