489 lines
16 KiB
Python
489 lines
16 KiB
Python
"""SkeletonApp."""
|
|
# from PyQt6 import Qt
|
|
|
|
from PyQt6 import QtGui
|
|
from PyQt6 import QtCore
|
|
from PyQt6 import QtWidgets
|
|
|
|
from PyQt6.QtWidgets import QApplication, QSizePolicy, QDialog, QMenu
|
|
from PyQt6.QtWidgets import QMainWindow
|
|
from PyQt6.QtWidgets import QWidget
|
|
|
|
|
|
# include <QToolBar>
|
|
from PyQt6.QtWidgets import QToolBar
|
|
|
|
# include <QIcon>
|
|
from PyQt6.QtGui import QIcon, QPalette, QGuiApplication, QImage, QPixmap, qRed, qGreen, qBlue, qAlpha, qRgba, QAction, QKeySequence, qRgb
|
|
|
|
# include <QClipboard>
|
|
from PyQt6.QtGui import QClipboard
|
|
|
|
# include <QColorSpace>
|
|
from PyQt6.QtGui import QColorSpace
|
|
|
|
# include <QDir>
|
|
from PyQt6.QtCore import QDir, QTranslator, Qt
|
|
|
|
# include <QFileDialog>
|
|
from PyQt6.QtWidgets import QFileDialog
|
|
|
|
# include <QImageReader>
|
|
from PyQt6.QtGui import QImageReader
|
|
|
|
# include <QImageWriter>
|
|
from PyQt6.QtGui import QImageWriter
|
|
|
|
# include <QBuffer>
|
|
from PyQt6.QtCore import QBuffer
|
|
|
|
# include <QLabel>
|
|
from PyQt6.QtWidgets import QLabel
|
|
|
|
# include <QMenuBar>
|
|
from PyQt6.QtWidgets import QMenuBar
|
|
|
|
# include <QMessageBox>
|
|
from PyQt6.QtWidgets import QMessageBox
|
|
|
|
# include <QMimeData>
|
|
from PyQt6.QtCore import QMimeData
|
|
|
|
# include <QPainter>
|
|
from PyQt6.QtGui import QPainter
|
|
|
|
# include <QScreen>
|
|
from PyQt6.QtGui import QScreen
|
|
|
|
# include <QScrollArea>
|
|
from PyQt6.QtWidgets import QScrollArea
|
|
|
|
# include <QScrollBar>
|
|
from PyQt6.QtWidgets import QScrollBar
|
|
|
|
# include <QStandardPaths>
|
|
from PyQt6.QtCore import QStandardPaths
|
|
|
|
# include <QStatusBar>
|
|
from PyQt6.QtWidgets import QStatusBar
|
|
|
|
|
|
from io import BytesIO
|
|
# include <iostream>
|
|
|
|
from io import FileIO
|
|
# include <fstream>
|
|
|
|
#
|
|
# class ScrollArea:
|
|
# """Scroll area."""
|
|
# def __init__(self, scroll_area=QScrollArea()):
|
|
# pass
|
|
#
|
|
#
|
|
# class ImageLabel:
|
|
# """Image label."""
|
|
# def __init__(self, image_label=QLabel()):
|
|
# pass
|
|
|
|
|
|
class SkeletonApp(QMainWindow):
|
|
"""SkeletonApp."""
|
|
|
|
firstDialog = None
|
|
|
|
def __init__(self, parent=None):
|
|
super(SkeletonApp, self).__init__(parent)
|
|
|
|
self.image_label = QLabel()
|
|
self.image_label.setBackgroundRole(QPalette.ColorRole.Base)
|
|
self.image_label.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Ignored)
|
|
|
|
self.image_label.setScaledContents(True)
|
|
|
|
self.scroll_area = QScrollArea()
|
|
self.scroll_area.setBackgroundRole(QPalette.ColorRole.Dark)
|
|
self.scroll_area.setWidget(self.image_label)
|
|
self.scroll_area.setVisible(False)
|
|
self.setCentralWidget(self.scroll_area)
|
|
|
|
self.resize(QGuiApplication.primaryScreen().availableSize() * 3 / 5)
|
|
|
|
self.fileMenu: QMenu = self.menuBar().addMenu(self.tr("&File"))
|
|
self.editMenu: QMenu = self.menuBar().addMenu(self.tr("&Edit"))
|
|
self.viewMenu: QMenu = self.menuBar().addMenu(self.tr("&View"))
|
|
self.helpMenu: QMenu = self.menuBar().addMenu(self.tr("&Help"))
|
|
self.filtersMenu: QMenu = self.menuBar().addMenu(self.tr("&Filters"))
|
|
|
|
self.data = []
|
|
self.reader = None
|
|
self.pixmap = QPixmap()
|
|
self.image = self.pixmap.toImage()
|
|
self.bit_planes = 0
|
|
self.depth = 0
|
|
self.format = None
|
|
self.bytes_per_line = 0
|
|
self.nbChannels = 1
|
|
self.nbRealChannels = 1
|
|
self.pixel = None
|
|
self.row = []
|
|
|
|
self.image_width = 0
|
|
self.image_height = 0
|
|
self.translator = QTranslator(self)
|
|
self.scaleFactor = 1.0
|
|
|
|
self.pictures_locations = None
|
|
self.mimeTypeFilters = []
|
|
self.dataResult = []
|
|
|
|
self.createActions()
|
|
self.updateActions()
|
|
|
|
def loadFile(self, file_name: str):
|
|
"""Load file."""
|
|
self.data.clear()
|
|
self.reader = QImageReader(file_name)
|
|
self.reader.setAutoTransform(True)
|
|
|
|
# const
|
|
_new_image: QImage = self.reader.read()
|
|
|
|
if _new_image.isNull():
|
|
QMessageBox.information(
|
|
self, QGuiApplication.applicationDisplayName(),
|
|
self.tr(F"Cannot load {QDir.toNativeSeparators(file_name)}:{self.reader.errorString()}"))
|
|
return False
|
|
|
|
self.filtersMenu.setDisabled(False)
|
|
|
|
self.setImage(new_image=_new_image)
|
|
|
|
self.image_width = self.image.width()
|
|
self.image_height = self.image.height()
|
|
self.bit_planes = int(self.image.bitPlaneCount())
|
|
self.depth = int(self.image.depth())
|
|
self.format = self.image.format()
|
|
print(type(self.format))
|
|
|
|
self.bytes_per_line = int(self.image.bytesPerLine())
|
|
self.nbChannels = 1
|
|
|
|
if self.depth > 1:
|
|
self.nbChannels = self.depth / 8
|
|
|
|
self.nbRealChannels = 1
|
|
if self.bit_planes > 1:
|
|
self.nbRealChannels = int(self.bit_planes / 8)
|
|
|
|
file = open("test_zapisu.txt", 'w')
|
|
|
|
if file:
|
|
for i in range(self.image_height):
|
|
row = []
|
|
for j in range(self.image_width):
|
|
# działa w logice pixel(x,y) - najpierw specyfikuje kolumnę
|
|
impix: qRgb = self.image.pixel(j, i)
|
|
|
|
pixel = []
|
|
|
|
if self.nbChannels == 1:
|
|
pixel = [qRed(impix)]
|
|
file.writelines(str(pix) + " " for pix in pixel)
|
|
|
|
elif self.nbChannels == 3:
|
|
pixel = [qRed(impix), qGreen(impix), qBlue(impix)]
|
|
file.writelines(str(pix) + " " for pix in pixel)
|
|
|
|
elif self.nbChannels == 4:
|
|
pixel = [qRed(impix), qGreen(impix), qBlue(impix), qAlpha(impix)]
|
|
file.writelines(str(pix) + " " for pix in pixel)
|
|
|
|
file.write("\n")
|
|
row.append(pixel)
|
|
|
|
self.data.append(row)
|
|
|
|
file.close()
|
|
|
|
self.setWindowFilePath(file_name)
|
|
|
|
message = self.tr(f"Opened \"{QDir.toNativeSeparators(file_name)}\", {self.image.width()}x{self.image.height()}, Depth: {self.image.depth()}, "
|
|
f"Bit planes: {self.image.bitPlaneCount()}")
|
|
|
|
self.statusBar().showMessage(message)
|
|
return True
|
|
|
|
def setImage(self, new_image: QImage):
|
|
"""Set image."""
|
|
self.image = new_image
|
|
if self.image.colorSpace().isValid():
|
|
self.image.convertToColorSpace(QColorSpace(QColorSpace.NamedColorSpace.SRgb))
|
|
|
|
self.image_label.setPixmap(QPixmap.fromImage(self.image))
|
|
self.scaleFactor = 1.0
|
|
self.scroll_area.setVisible(True)
|
|
# self.printAct.setEnabled(True)
|
|
self.fitToWindowAct.setEnabled(True)
|
|
self.updateActions()
|
|
|
|
if not self.fitToWindowAct.isChecked():
|
|
self.image_label.adjustSize()
|
|
|
|
def scaleImage(self, factor: float):
|
|
"""Scale image."""
|
|
self.scaleFactor *= factor
|
|
|
|
self.image_label.resize(self.scaleFactor * self.image_label.pixmap().width(),
|
|
self.scaleFactor * self.image_label.pixmap().height())
|
|
|
|
self.adjustScrollBar(self.scroll_area.horizontalScrollBar(), factor)
|
|
self.adjustScrollBar(self.scroll_area.verticalScrollBar(), factor)
|
|
|
|
self.zoomInAct.setEnabled(self.scaleFactor < 3.0)
|
|
self.zoomOutAct.setEnabled(self.scaleFactor > 0.333)
|
|
|
|
def adjustScrollBar(self, scroll_bar: QScrollBar, factor: float):
|
|
"""adjust scroll bar"""
|
|
scroll_bar.setValue(int(factor * scroll_bar.value() + ((factor - 1) * scroll_bar.pageStep() / 2)))
|
|
|
|
def about(self):
|
|
"""about"""
|
|
QMessageBox.about(self, self.tr("About skeletonApp"),
|
|
self.tr("<p>Aplikacja<b> szkieletowa</b> do kursu <i>Przetwarzanie obrazu</i></p>"))
|
|
|
|
def saveFile(self, file_name: str):
|
|
"""Save."""
|
|
writer = QImageWriter(file_name)
|
|
|
|
if not writer.write(self.image):
|
|
QMessageBox.information(
|
|
self, QGuiApplication.applicationDisplayName(),
|
|
self.tr(F"Cannot write {QDir.toNativeSeparators(file_name)}: {writer.errorString()}"))
|
|
|
|
return False
|
|
|
|
message = self.tr(f"Wrote \"{QDir.toNativeSeparators(file_name)}\"")
|
|
self.statusBar().showMessage(message)
|
|
return True
|
|
|
|
def initializeImageFileDialog(self, dialog, acceptMode):
|
|
"""Initialize Image File Dialog."""
|
|
self.firstDialog = True
|
|
|
|
if self.firstDialog:
|
|
self.firstDialog = False
|
|
self.pictures_locations: list = QStandardPaths.standardLocations(QStandardPaths.StandardLocation.PicturesLocation)
|
|
|
|
dialog.setDirectory(QDir.currentPath() if not self.pictures_locations else self.pictures_locations[-1])
|
|
|
|
self.mimeTypeFilters = []
|
|
supported_mime_types = None
|
|
if acceptMode == QFileDialog.AcceptMode.AcceptOpen:
|
|
supported_mime_types = QImageReader.supportedMimeTypes().__str__()
|
|
else:
|
|
supported_mime_types = QImageWriter.supportedMimeTypes().__str__()
|
|
|
|
for mime_type_name in supported_mime_types:
|
|
self.mimeTypeFilters.append(mime_type_name)
|
|
|
|
self.mimeTypeFilters.sort()
|
|
|
|
dialog.setMimeTypeFilters(self.mimeTypeFilters)
|
|
dialog.selectMimeTypeFilter("image/x-portable-pixmap")
|
|
if acceptMode == QFileDialog.AcceptMode.AcceptSave:
|
|
dialog.setDefaultSuffix("ppm")
|
|
|
|
def open(self):
|
|
"""Open file"""
|
|
dialog = QFileDialog(self, self.tr("Open File"))
|
|
self.initializeImageFileDialog(dialog, QFileDialog.AcceptMode.AcceptOpen)
|
|
dialog.exec()
|
|
if dialog.accepted:
|
|
self.loadFile(dialog.selectedFiles()[0])
|
|
|
|
def saveAs(self):
|
|
"""Save file"""
|
|
dialog = QFileDialog(self, self.tr("Save File As"))
|
|
self.initializeImageFileDialog(dialog, QFileDialog.AcceptMode.AcceptSave)
|
|
dialog.exec()
|
|
if dialog.accepted:
|
|
self.saveFile(dialog.selectedFiles()[0])
|
|
|
|
def copy(self):
|
|
"""Copy image"""
|
|
QGuiApplication.clipboard().setImage(self.image)
|
|
|
|
def clipboardImage(self) -> QImage:
|
|
"""Clipboard Image"""
|
|
mimeData: QMimeData = QGuiApplication.clipboard().mimeData()
|
|
if mimeData:
|
|
if mimeData.hasImage():
|
|
image: QImage = QImage(mimeData.imageData())
|
|
if not image.isNull():
|
|
return image
|
|
return QImage()
|
|
|
|
def paste(self):
|
|
"""Paste image."""
|
|
newImage: QImage = self.clipboardImage()
|
|
if newImage.isNull():
|
|
self.statusBar().showMessage(self.tr("No image in clipboard"))
|
|
|
|
else:
|
|
self.setImage(newImage)
|
|
self.setWindowFilePath(str(''))
|
|
message: str = self.tr(
|
|
f"Obtained image from clipboard, {newImage.width()}x{newImage.height()}, Depth: {newImage.depth()}")
|
|
self.statusBar().showMessage(message)
|
|
|
|
def zoomIn(self):
|
|
"""Zoom in"""
|
|
self.scaleImage(1.25)
|
|
|
|
def zoomOut(self):
|
|
"""Zoom out"""
|
|
self.scaleImage(0.8)
|
|
|
|
def normalSize(self):
|
|
"""Normalize Size"""
|
|
self.image_label.adjustSize()
|
|
self.scaleFactor = 1.0
|
|
|
|
def fitToWindow(self):
|
|
"""Fit to window"""
|
|
fit: bool = self.fitToWindowAct.isChecked()
|
|
print(fit)
|
|
self.scroll_area.setWidgetResizable(fit)
|
|
if not fit:
|
|
self.normalSize()
|
|
self.updateActions()
|
|
|
|
def applyTestFilter(self):
|
|
"""Apply test filter"""
|
|
self.dataResult.clear()
|
|
|
|
for i in range(0, self.height()):
|
|
row = [[]]
|
|
|
|
for j in range(0, self.width()):
|
|
pixel = []
|
|
|
|
for k in range(0, self.nbRealChannels):
|
|
print(self.nbChannels)
|
|
print(i, j, k)
|
|
pixel.append(255 - int(self.data[i][j][k]))
|
|
|
|
if self.nbRealChannels < self.nbChannels:
|
|
pixel.append(255)
|
|
|
|
row.append(pixel)
|
|
|
|
self.dataResult.append(row)
|
|
|
|
imageResult = QImage(width=self.width(), height=self.height(), format=self.image.format())
|
|
|
|
if self.nbChannels > 1:
|
|
for i in range(0, self.image.height()):
|
|
for j in range(0, self.image.width()):
|
|
imageResult.setPixel(
|
|
j, i, qRgba(self.dataResult[i][j][0], self.dataResult[i][j][1],
|
|
self.dataResult[i][j][2], self.dataResult[i][j][3]))
|
|
else:
|
|
if self.image.bitPlaneCount() == 8:
|
|
for i in range(0, self.image.height()):
|
|
for j in range(0, self.image.width()):
|
|
imageResult.setPixel(
|
|
j, i, qRgba(self.dataResult[i][j][0], self.dataResult[i][j][0],
|
|
self.dataResult[i][j][0], 255))
|
|
else:
|
|
imageResult.setColorCount(2)
|
|
imageResult.setColor(0, qRgba(0, 0, 0, 255))
|
|
imageResult.setColor(1, qRgba(255, 0, 0, 0))
|
|
|
|
for i in range(0, self.image.height()):
|
|
for j in range(0, self.image.width()):
|
|
if self.dataResult[i][j][0] == 0:
|
|
imageResult.setPixel(j, i, 0)
|
|
else:
|
|
imageResult.setPixel(j, i, 1)
|
|
|
|
self.setImage(imageResult)
|
|
|
|
def updateActions(self):
|
|
"""update actions"""
|
|
self.saveAsAct.setEnabled(not self.image.isNull())
|
|
self.copyAct.setEnabled(not self.image.isNull())
|
|
self.zoomInAct.setEnabled(not self.fitToWindowAct.isChecked())
|
|
self.zoomOutAct.setEnabled(not self.fitToWindowAct.isChecked())
|
|
self.normalSizeAct.setEnabled(not self.fitToWindowAct.isChecked())
|
|
|
|
def createActions(self):
|
|
"""Create actions."""
|
|
self.openAct = QAction(self.tr("&Open..."), self)
|
|
self.openAct.triggered.connect(self.open)
|
|
self.openAct.setShortcut(QKeySequence.StandardKey.Open)
|
|
self.fileMenu.addAction(self.openAct)
|
|
|
|
self.saveAsAct = QAction(self.tr("&Save As..."), self)
|
|
self.saveAsAct.triggered.connect(self.saveAs)
|
|
self.saveAsAct.setShortcut(self.tr("Shift+S"))
|
|
self.fileMenu.addAction(self.saveAsAct)
|
|
|
|
self.fileMenu.addSeparator()
|
|
|
|
self.exitAct = QAction(self.tr("&Exit"), self)
|
|
self.exitAct.triggered.connect(QWidget.close)
|
|
self.exitAct.setShortcut(self.tr("Ctrl+Q"))
|
|
self.fileMenu.addAction(self.exitAct)
|
|
|
|
self.copyAct = QAction(self.tr("&Copy"), self)
|
|
self.copyAct.triggered.connect(self.copy)
|
|
self.copyAct.setShortcut(QKeySequence.StandardKey.Copy)
|
|
self.editMenu.addAction(self.copyAct)
|
|
|
|
self.pasteAct = QAction(self.tr("&Paste"), self)
|
|
self.pasteAct.triggered.connect(self.paste)
|
|
self.pasteAct.setShortcut(QKeySequence.StandardKey.Paste)
|
|
self.editMenu.addAction(self.pasteAct)
|
|
|
|
self.zoomInAct = QAction(self.tr("&Zoom In (25%)"), self)
|
|
self.zoomInAct.triggered.connect(self.zoomIn)
|
|
self.zoomInAct.setShortcut(QKeySequence.StandardKey.ZoomIn)
|
|
self.viewMenu.addAction(self.zoomInAct)
|
|
|
|
self.zoomOutAct = QAction(self.tr("&Zoom Out (25%)"), self)
|
|
self.zoomOutAct.triggered.connect(self.zoomOut)
|
|
self.zoomOutAct.setShortcut(QKeySequence.StandardKey.ZoomOut)
|
|
self.viewMenu.addAction(self.zoomOutAct)
|
|
|
|
self.normalSizeAct = QAction(self.tr("&Normal Size"), self)
|
|
self.normalSizeAct.triggered.connect(self.normalSize)
|
|
self.normalSizeAct.setShortcut(self.tr("Ctrl+N"))
|
|
self.viewMenu.addAction(self.normalSizeAct)
|
|
|
|
self.fitToWindowAct = QAction(self.tr("&Fit to Window"), self)
|
|
self.fitToWindowAct.triggered.connect(self.fitToWindow)
|
|
self.fitToWindowAct.setShortcut(self.tr("Ctrl+F"))
|
|
self.viewMenu.addAction(self.fitToWindowAct)
|
|
|
|
self.testFilterAct = QAction(self.tr("&TestFilter"), self)
|
|
self.testFilterAct.triggered.connect(self.applyTestFilter)
|
|
self.filtersMenu.addAction(self.testFilterAct)
|
|
|
|
self.aboutAct = QAction(self.tr("&About"), self)
|
|
self.aboutAct.triggered.connect(self.about)
|
|
self.aboutAct.setShortcut(self.tr("Ctrl+I"))
|
|
self.helpMenu.addAction(self.aboutAct)
|
|
|
|
self.fitToWindowAct.setCheckable(True)
|
|
self.filtersMenu.setDisabled(True)
|
|
# self.testFilter.setDisabled(True)
|
|
self.saveAsAct.setEnabled(False)
|
|
self.copyAct.setEnabled(False)
|
|
self.zoomInAct.setEnabled(False)
|
|
self.zoomOutAct.setEnabled(False)
|
|
self.normalSizeAct.setEnabled(False)
|
|
self.fitToWindowAct.setEnabled(False)
|
|
self.aboutAct.setEnabled(True)
|