In the program below, load the background image and paint it on it.
But, I got a problem.
In this program, when i use 'eraser' tool, the background image is erased too!
Actually, I just want to erase what i painted, except background image.
And then, I'd like to save only the painted ones(layer) as an image.
In this case, What should i do?
import sys
from PyQt5.QtCore import *
from PyQt5.QtCore import Qt
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtWidgets import (QApplication, QCheckBox, QGridLayout, QGroupBox,
QPushButton, QVBoxLayout, QWidget, QSlider)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
class CWidget(QWidget):
def __init__(self):
super().__init__()
# 전체 폼 박스
formbox = QHBoxLayout()
self.setLayout(formbox)
# 좌, 우 레이아웃박스
left = QVBoxLayout()
right = QVBoxLayout()
# 그룹박스2
gb = QGroupBox('펜 설정')
left.addWidget(gb)
grid = QGridLayout()
gb.setLayout(grid)
label = QLabel('펜 색상')
grid.addWidget(label, 1, 0)
self.pencolor = QColor(0, 0, 0)
self.penbtn = QPushButton()
self.penbtn.setStyleSheet('background-color: rgb(0,0,0)')
self.penbtn.clicked.connect(self.showColorDlg)
grid.addWidget(self.penbtn, 1, 1)
label = QLabel('펜 굵기')
grid.addWidget(label, 2, 0)
self.slider = QSlider(Qt.Horizontal)
self.slider.setMinimum(3)
self.slider.setMaximum(21)
self.slider.setValue(5)
self.slider.setFocusPolicy(Qt.StrongFocus)
self.slider.setTickPosition(QSlider.TicksBothSides)
self.slider.setTickInterval(1)
self.slider.setSingleStep(1)
grid.addWidget(self.slider)
# 그룹박스4
gb = QGroupBox('Eraser')
left.addWidget(gb)
hbox = QHBoxLayout()
gb.setLayout(hbox)
self.checkbox = QCheckBox('Eraser')
self.checkbox.stateChanged.connect(self.checkClicked)
hbox.addWidget(self.checkbox)
left.addStretch(1)
self.view = CView(self)
right.addWidget(self.view)
formbox.addLayout(left)
formbox.addLayout(right)
formbox.setStretchFactor(left, 0)
formbox.setStretchFactor(right, 1)
self.setGeometry(100, 100, 800, 500)
def checkClicked(self, state):
pass
def createExampleGroup(self):
groupBox = QGroupBox("Slider Example")
slider = QSlider(Qt.Horizontal)
slider.setFocusPolicy(Qt.StrongFocus)
slider.setTickPosition(QSlider.TicksBothSides)
slider.setTickInterval(10)
slider.setSingleStep(1)
vbox = QVBoxLayout()
vbox.addWidget(slider)
vbox.addStretch(1)
groupBox.setLayout(vbox)
return groupBox
def showColorDlg(self):
color = QColorDialog.getColor()
sender = self.sender()
self.pencolor = color
self.penbtn.setStyleSheet('background-color: {}'.format(color.name()))
# QGraphicsView display QGraphicsScene
class CView(QGraphicsView):
def __init__(self, parent):
super().__init__(parent)
self.scene = QGraphicsScene()
self.setScene(self.scene)
self.items = []
self.start = QPointF()
self.end = QPointF()
self.backgroundImage = None
self.graphicsPixmapItem = None
self.setRenderHint(QPainter.HighQualityAntialiasing)
self.open()
def moveEvent(self, e):
rect = QRectF(self.rect())
rect.adjust(0, 0, -2, -2)
self.scene.setSceneRect(rect)
def mousePressEvent(self, e):
if e.button() == Qt.LeftButton:
# 시작점 저장
self.start = e.pos()
self.end = e.pos()
def mouseMoveEvent(self, e):
# e.buttons()는 정수형 값을 리턴, e.button()은 move시 Qt.Nobutton 리턴
if e.buttons() & Qt.LeftButton:
self.end = e.pos()
if self.parent().checkbox.isChecked():
pen = QPen(QColor(255, 255, 255), 10)
path = QPainterPath()
path.moveTo(self.start)
path.lineTo(self.end)
self.scene.addPath(path, pen)
self.start = e.pos()
return None
pen = QPen(self.parent().pencolor, self.parent().slider.value())
# Path 이용
path = QPainterPath()
path.moveTo(self.start)
path.lineTo(self.end)
self.scene.addPath(path, pen)
# 시작점을 다시 기존 끝점으로
self.start = e.pos()
def stretch(self, state):
self._set_image(state == 2)
def open(self):
fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath(), filter='Images (*.png *.xpm *.jpg *jpeg)')
if fileName:
image = QImage(fileName)
if image.isNull():
QMessageBox.information(self, "Image Viewer",
"Cannot load %s." % fileName)
return
self.backgroundImage = fileName
self._set_image(False)
def _set_image(self, stretch: bool):
tempImg = QPixmap(self.backgroundImage)
if stretch:
tempImg = tempImg.scaled(self.scene.width(), self.scene.height())
if self.graphicsPixmapItem is not None:
self.scene.removeItem(self.graphicsPixmapItem)
self.graphicsPixmapItem = QGraphicsPixmapItem(tempImg)
self.scene.addItem(self.graphicsPixmapItem)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = CWidget()
w.show()
sys.exit(app.exec_())
You can create another transparent item where you draw and that is on the QGraphicsPixmapItem. For painting it is only necessary to draw on a transparent QPixmap that is in the transparent item, and for the deletion we use the composition mode QPainter::CompositionMode_Clear as I indicate in this answer.
Considering the above the solution is:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class LayerItem(QtWidgets.QGraphicsRectItem):
DrawState, EraseState = range(2)
def __init__(self, parent=None):
super().__init__(parent)
self.current_state = LayerItem.DrawState
self.setPen(QtGui.QPen(QtCore.Qt.NoPen))
self.m_line_eraser = QtCore.QLineF()
self.m_line_draw = QtCore.QLineF()
self.m_pixmap = QtGui.QPixmap()
def reset(self):
r = self.parentItem().pixmap().rect()
self.setRect(QtCore.QRectF(r))
self.m_pixmap = QtGui.QPixmap(r.size())
self.m_pixmap.fill(QtCore.Qt.transparent)
def paint(self, painter, option, widget=None):
super().paint(painter, option, widget)
painter.save()
painter.drawPixmap(QtCore.QPoint(), self.m_pixmap)
painter.restore()
def mousePressEvent(self, event):
if self.current_state == LayerItem.EraseState:
self._clear(event.pos().toPoint())
elif self.current_state == LayerItem.DrawState:
self.m_line_draw.setP1(event.pos())
self.m_line_draw.setP2(event.pos())
super().mousePressEvent(event)
event.accept()
def mouseMoveEvent(self, event):
if self.current_state == LayerItem.EraseState:
self._clear(event.pos().toPoint())
elif self.current_state == LayerItem.DrawState:
self.m_line_draw.setP2(event.pos())
self._draw_line(
self.m_line_draw, QtGui.QPen(self.pen_color, self.pen_thickness)
)
self.m_line_draw.setP1(event.pos())
super().mouseMoveEvent(event)
def _draw_line(self, line, pen):
painter = QtGui.QPainter(self.m_pixmap)
painter.setPen(pen)
painter.drawLine(line)
painter.end()
self.update()
def _clear(self, pos):
painter = QtGui.QPainter(self.m_pixmap)
r = QtCore.QRect(QtCore.QPoint(), 10 * QtCore.QSize())
r.moveCenter(pos)
painter.setCompositionMode(QtGui.QPainter.CompositionMode_Clear)
painter.eraseRect(r)
painter.end()
self.update()
#property
def pen_thickness(self):
return self._pen_thickness
#pen_thickness.setter
def pen_thickness(self, thickness):
self._pen_thickness = thickness
#property
def pen_color(self):
return self._pen_color
#pen_color.setter
def pen_color(self, color):
self._pen_color = color
#property
def current_state(self):
return self._current_state
#current_state.setter
def current_state(self, state):
self._current_state = state
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super().__init__(parent)
self.setScene(QtWidgets.QGraphicsScene(self))
self.setRenderHint(QtGui.QPainter.HighQualityAntialiasing)
self.setAlignment(QtCore.Qt.AlignCenter)
self.background_item = QtWidgets.QGraphicsPixmapItem()
self.foreground_item = LayerItem(self.background_item)
self.scene().addItem(self.background_item)
def set_image(self, image):
self.scene().setSceneRect(
QtCore.QRectF(QtCore.QPointF(), QtCore.QSizeF(image.size()))
)
self.background_item.setPixmap(image)
self.foreground_item.reset()
self.fitInView(self.background_item, QtCore.Qt.KeepAspectRatio)
self.centerOn(self.background_item)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
menu = self.menuBar().addMenu(self.tr("File"))
open_action = menu.addAction(self.tr("Open image..."))
open_action.triggered.connect(self.open_image)
pen_group = QtWidgets.QGroupBox(self.tr("Pen settings"))
eraser_group = QtWidgets.QGroupBox(self.tr("Eraser"))
self.pen_button = QtWidgets.QPushButton(clicked=self.showColorDlg)
color = QtGui.QColor(0, 0, 0)
self.pen_button.setStyleSheet(
"background-color: {}".format(color.name())
)
self.pen_slider = QtWidgets.QSlider(
QtCore.Qt.Horizontal,
minimum=3,
maximum=21,
value=5,
focusPolicy=QtCore.Qt.StrongFocus,
tickPosition=QtWidgets.QSlider.TicksBothSides,
tickInterval=1,
singleStep=1,
valueChanged=self.onThicknessChanged,
)
self.eraser_checkbox = QtWidgets.QCheckBox(
self.tr("Eraser"), stateChanged=self.onStateChanged
)
self.view = GraphicsView()
self.view.foreground_item.pen_thickness = self.pen_slider.value()
self.view.foreground_item.pen_color = color
# layouts
pen_lay = QtWidgets.QFormLayout(pen_group)
pen_lay.addRow(self.tr("Pen color"), self.pen_button)
pen_lay.addRow(self.tr("Pen thickness"), self.pen_slider)
eraser_lay = QtWidgets.QVBoxLayout(eraser_group)
eraser_lay.addWidget(self.eraser_checkbox)
vlay = QtWidgets.QVBoxLayout()
vlay.addWidget(pen_group)
vlay.addWidget(eraser_group)
vlay.addStretch()
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QHBoxLayout(central_widget)
lay.addLayout(vlay, stretch=0)
lay.addWidget(self.view, stretch=1)
self.resize(640, 480)
#QtCore.pyqtSlot(int)
def onStateChanged(self, state):
self.view.foreground_item.current_state = (
LayerItem.EraseState
if state == QtCore.Qt.Checked
else LayerItem.DrawState
)
#QtCore.pyqtSlot(int)
def onThicknessChanged(self, value):
self.view.foreground_item.pen_thickness = value
#QtCore.pyqtSlot()
def showColorDlg(self):
color = QtWidgets.QColorDialog.getColor(
self.view.foreground_item.pen_color, self
)
self.view.foreground_item.pen_color = color
self.pen_button.setStyleSheet(
"background-color: {}".format(color.name())
)
def open_image(self):
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self,
"Open File",
QtCore.QDir.currentPath(),
filter="Images (*.png *.xpm *.jpg *jpeg)",
)
if filename:
pixmap = QtGui.QPixmap(filename)
if pixmap.isNull():
QtWidgets.QMessageBox.information(
self, "Image Viewer", "Cannot load %s." % filename
)
return
self.view.set_image(pixmap)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Related
I want to draw a line (or paint) on an image that I already opened. I can open the image, but I can't draw on the image. But if I don't open any image, I can draw anything I want on blank space.
So, How can I draw with a brush on an image ? For now eraser or etc. It's not important. I just want to do something with a brush on an image, which image I already open in my computer.
I didn't get any error or something. So I can't search specifically.
Here is my code;
from PyQt5.QtWidgets import QMainWindow, QApplication, QMenu, QMenuBar, QAction, QFileDialog, QLabel
from PyQt5.QtGui import QIcon, QImage, QPainter, QPen, QBrush, QPixmap
from PyQt5.QtCore import Qt, QPoint
import sys
class Window(QMainWindow):
def __init__(self):
super().__init__()
title = "Paint Application"
top = 400
left = 400
width = 800
height = 600
icon = "icons/pain.png"
self.setWindowTitle(title)
self.setGeometry(top, left, width, height)
self.setWindowIcon(QIcon(icon))
self.image = QImage(self.size(), QImage.Format_RGB32)
self.image.fill(Qt.white)
self.drawing = False
self.brushSize = 2
self.brushColor = Qt.black
self.lastPoint = QPoint()
mainMenu = self.menuBar()
fileMenu = mainMenu.addMenu("File")
brushSize = mainMenu.addMenu("Brush Size")
brushColor = mainMenu.addMenu("Brush Color")
openAction = QAction('Open Image', self)
openAction.triggered.connect(self.openImage)
fileMenu.addAction(openAction)
closeAction = QAction('Exit', self)
closeAction.triggered.connect(self.close)
fileMenu.addAction(closeAction)
self.label = QLabel()
self.setCentralWidget(self.label)
saveAction = QAction(QIcon("icons/save.png"), "Save",self)
saveAction.setShortcut("Ctrl+S")
fileMenu.addAction(saveAction)
saveAction.triggered.connect(self.save)
clearAction = QAction(QIcon("icons/clear.png"), "Clear", self)
clearAction.setShortcut("Ctrl+C")
fileMenu.addAction(clearAction)
clearAction.triggered.connect(self.clear)
threepxAction = QAction( QIcon("icons/threepx.png"), "3px", self)
brushSize.addAction(threepxAction)
threepxAction.triggered.connect(self.threePixel)
fivepxAction = QAction(QIcon("icons/fivepx.png"), "5px", self)
brushSize.addAction(fivepxAction)
fivepxAction.triggered.connect(self.fivePixel)
sevenpxAction = QAction(QIcon("icons/sevenpx.png"),"7px", self)
brushSize.addAction(sevenpxAction)
sevenpxAction.triggered.connect(self.sevenPixel)
ninepxAction = QAction(QIcon("icons/ninepx.png"), "9px", self)
brushSize.addAction(ninepxAction)
ninepxAction.triggered.connect(self.ninePixel)
blackAction = QAction(QIcon("icons/black.png"), "Black", self)
blackAction.setShortcut("Ctrl+B")
brushColor.addAction(blackAction)
blackAction.triggered.connect(self.blackColor)
whitekAction = QAction(QIcon("icons/white.png"), "White", self)
whitekAction.setShortcut("Ctrl+W")
brushColor.addAction(whitekAction)
whitekAction.triggered.connect(self.whiteColor)
redAction = QAction(QIcon("icons/red.png"), "Red", self)
redAction.setShortcut("Ctrl+R")
brushColor.addAction(redAction)
redAction.triggered.connect(self.redColor)
greenAction = QAction(QIcon("icons/green.png"), "Green", self)
greenAction.setShortcut("Ctrl+G")
brushColor.addAction(greenAction)
greenAction.triggered.connect(self.greenColor)
yellowAction = QAction(QIcon("icons/yellow.png"), "Yellow", self)
yellowAction.setShortcut("Ctrl+Y")
brushColor.addAction(yellowAction)
yellowAction.triggered.connect(self.yellowColor)
def openImage(self):
imagePath, _ = QFileDialog.getOpenFileName()
pixmap = QPixmap(imagePath)
self.label.setPixmap(pixmap)
self.resize(pixmap.size())
self.adjustSize()
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.drawing = True
self.lastPoint = event.pos()
#print(self.lastPoint)
def mouseMoveEvent(self, event):
if(event.buttons() & Qt.LeftButton) & self.drawing:
painter = QPainter(self.image)
painter.setPen(QPen(self.brushColor, self.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
painter.drawLine(self.lastPoint, event.pos())
self.lastPoint = event.pos()
self.update()
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
self.drawing = False
def paintEvent(self, event):
canvasPainter = QPainter(self)
canvasPainter.drawImage(self.rect(),self.image, self.image.rect() )
def save(self):
filePath, _ = QFileDialog.getSaveFileName(self, "Save Image", "", "PNG(*.png);;JPEG(*.jpg *.jpeg);;All Files(*.*) ")
if filePath == "":
return
self.image.save(filePath)
def clear(self):
self.image.fill(Qt.white)
self.update()
def threePixel(self):
self.brushSize = 3
def fivePixel(self):
self.brushSize = 5
def sevenPixel(self):
self.brushSize = 7
def ninePixel(self):
self.brushSize = 9
def blackColor(self):
self.brushColor = Qt.black
def whiteColor(self):
self.brushColor = Qt.white
def redColor(self):
self.brushColor = Qt.red
def greenColor(self):
self.brushColor = Qt.green
def yellowColor(self):
self.brushColor = Qt.yellow
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()
Instead of constructing a QPixmap from the filepath, construct a QImage and assign it to the variable self.image. You won't have to change anything else as the paintEvent will draw the QImage object that pointed to by self.image.
def openImage(self):
imagePath, _ = QFileDialog.getOpenFileName()
self.image = QImage(imagePath)
self.resize(self.image.size())
I can add QWidget to QMainWindow and set its position to cursor position.
But every single time, I want to add new QWidget to QMainWindow. I don't maybe given codes add new QWidget but at canvas, I see just one QWidget. Here my codes:
from PyQt5.QtWidgets import QMainWindow, QApplication, QMenu, QMenuBar, QAction, QFileDialog, QWidget, QLabel
from PyQt5.QtGui import QIcon, QImage, QPainter, QPen, QBrush
from PyQt5.QtCore import Qt, QPoint
import sys
class Window(QMainWindow):
def __init__(self):
super().__init__()
title = "Paint Application"
top = 400
left = 400
width = 800
height = 600
self.objStr = "berkayy"
self.count = 0
# icon = "icons/pain.png"
self.setAcceptDrops(True)
self.setWindowTitle(title)
self.setGeometry(top, left, width, height)
# self.setWindowIcon(QIcon(icon))
self.image = QImage(self.size(), QImage.Format_RGB32)
self.image.fill(Qt.white)
self.drawing = False
self.brushSize = 2
self.brushColor = Qt.black
self.lastPoint = QPoint()
mainMenu = self.menuBar()
fileMenu = mainMenu.addMenu("File")
brushSize = mainMenu.addMenu("Brush Size")
brushColor = mainMenu.addMenu("Brush Color")
saveAction = QAction(QIcon("icons/save.png"), "Save",self)
saveAction.setShortcut("Ctrl+S")
fileMenu.addAction(saveAction)
saveAction.triggered.connect(self.save)
clearAction = QAction(QIcon("icons/clear.png"), "Clear", self)
clearAction.setShortcut("Ctrl+C")
fileMenu.addAction(clearAction)
clearAction.triggered.connect(self.clear)
threepxAction = QAction( QIcon("icons/threepx.png"), "3px", self)
brushSize.addAction(threepxAction)
threepxAction.triggered.connect(self.threePixel)
fivepxAction = QAction(QIcon("icons/fivepx.png"), "5px", self)
brushSize.addAction(fivepxAction)
fivepxAction.triggered.connect(self.fivePixel)
sevenpxAction = QAction(QIcon("icons/sevenpx.png"),"7px", self)
brushSize.addAction(sevenpxAction)
sevenpxAction.triggered.connect(self.sevenPixel)
ninepxAction = QAction(QIcon("icons/ninepx.png"), "9px", self)
brushSize.addAction(ninepxAction)
ninepxAction.triggered.connect(self.ninePixel)
blackAction = QAction(QIcon("icons/black.png"), "Black", self)
blackAction.setShortcut("Ctrl+B")
brushColor.addAction(blackAction)
blackAction.triggered.connect(self.blackColor)
whitekAction = QAction(QIcon("icons/white.png"), "White", self)
whitekAction.setShortcut("Ctrl+W")
brushColor.addAction(whitekAction)
whitekAction.triggered.connect(self.whiteColor)
redAction = QAction(QIcon("icons/red.png"), "Red", self)
redAction.setShortcut("Ctrl+R")
brushColor.addAction(redAction)
redAction.triggered.connect(self.redColor)
greenAction = QAction(QIcon("icons/green.png"), "Green", self)
greenAction.setShortcut("Ctrl+G")
brushColor.addAction(greenAction)
greenAction.triggered.connect(self.greenColor)
yellowAction = QAction(QIcon("icons/yellow.png"), "Yellow", self)
yellowAction.setShortcut("Ctrl+Y")
brushColor.addAction(yellowAction)
yellowAction.triggered.connect(self.yellowColor)
def rectangle(self, e, pos):
print(pos)
painter = QPainter(self)
painter.setPen(QPen(Qt.black, 5, Qt.SolidLine))
painter.setBrush(QBrush(Qt.green, Qt.DiagCrossPattern))
painter.drawRect(100, 15, 400, 200)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.drawing = True
self.lastPoint = event.pos()
#print(self.lastPoint)
def contextMenuEvent(self, event):
contextMenu = QMenu(self)
newAct = contextMenu.addAction("New")
openAct = contextMenu.addAction("Open")
closeAct = contextMenu.addAction("Close")
action = contextMenu.exec_(self.mapToGlobal(event.pos()))
if action == closeAct:
self.close()
elif action == openAct:
self.berkay(event.pos())
def mouseMoveEvent(self, event):
if(event.buttons() & Qt.LeftButton) & self.drawing:
painter = QPainter(self.image)
painter.setPen(QPen(self.brushColor, self.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
painter.drawLine(self.lastPoint, event.pos())
self.lastPoint = event.pos()
self.update()
def berkay(self, pos):
wid = QWidget(self)
btn = QLabel(wid)
btn.setText("skjdf")
btn.setObjectName(self.objStr + str(self.count) )
btn.move(pos)
self.setCentralWidget(wid)
self.count += 1
# btn.setDragEnabled(True)
print(btn.objectName())
# self.show()
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
self.drawing = False
def paintEvent(self, event):
canvasPainter = QPainter(self)
canvasPainter.drawImage(self.rect(),self.image, self.image.rect() )
def save(self):
filePath, _ = QFileDialog.getSaveFileName(self, "Save Image", "", "PNG(*.png);;JPEG(*.jpg *.jpeg);;All Files(*.*) ")
if filePath == "":
return
self.image.save(filePath)
def clear(self):
self.image.fill(Qt.white)
self.update()
def threePixel(self):
self.brushSize = 3
def fivePixel(self):
self.brushSize = 5
def sevenPixel(self):
self.brushSize = 7
def ninePixel(self):
self.brushSize = 9
def blackColor(self):
self.brushColor = Qt.black
def whiteColor(self):
self.brushColor = Qt.white
def redColor(self):
self.brushColor = Qt.red
def greenColor(self):
self.brushColor = Qt.green
def yellowColor(self):
self.brushColor = Qt.yellow
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()
I want to create new QWidget when user clicked context menu item every single time.
A QMainWindow can have only one central widget. So, adding the new QLabel as central widget will remove the previous. That's why you can see only the last label.
Create a single widget and define it as central widget. Then, add the label as child of the central widget:
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.area = QWidget(self)
self.setCentralWidget(self.area)
def berkay(self, pos):
btn = QLabel("BOOH", self.area)
btn.move(self.area.mapFromParent(pos)) # Map the pos in the coord system of self.area
btn.show()
First, for every single widget, store x and y value in a list. After that, adding new and current widgets to window central widget.
Edit
We do not need store widget x and y, anymore.
Thank you Romha for optimisation suggestion.
import sys
from PyQt5.QtWidgets import QMainWindow, QMenu, QApplication, QWidget, QPushButton, qApp
from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import Qt
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.area = QWidget(self)
self.setCentralWidget(self.area)
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Context menu')
self.setStyleSheet("QMainWindow {background: 'white';}")
self.show()
def contextMenuEvent(self, event):
cmenu = QMenu(self)
addBtnAct = cmenu.addAction("Add Button")
quitAct = cmenu.addAction("Quit")
action = cmenu.exec_(self.mapToGlobal(event.pos()))
if action == quitAct:
qApp.quit()
elif action == addBtnAct:
self.addLabel(event.pos())
def addLabel(self, pos):
btn = QLabel("BOOH", self.area)
btn.move(self.area.mapFromParent(pos)) # Map the pos in the coord system of self.area
btn.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Following image showing an app with one input widget and one paintwidget.
and there are two paint classes in the code. The idea is when a button is clicked, calling first paint class and show it in Paintwidget, well when another paint class is called, wants to show in same Paintwidget. Not really overlap each other, rather than showing in same widget.
How could that be done?
When round button clicks this codeline changes from
self.mainSplitter.addWidget(self.paint1)
to
self.mainSplitter.addWidget(self.paint2)
And vice versa
Visualization:
The code:
from PyQt5 import QtCore, QtGui, QtWidgets
class Foo(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(Foo, self).__init__(parent)
self.setGeometry(QtCore.QRect(200, 100, 800, 650))
self.button = Button()
self.paint1 = Paintwidget1()
self.paint2 = Paintwidget2()
self.button.valuesChanged.connect(self.paint1.set_size_squares)
self.button.valueChanged.connect(self.paint2.set_size_round)
self.mainSplitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
self.mainSplitter.addWidget(self.button)
self.mainSplitter.addWidget(self.paint1)
self.setCentralWidget(self.mainSplitter)
class Paintwidget1(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.sizeHint()
self.setBackgroundRole(QtGui.QPalette.Base)
self.setAutoFillBackground(True)
self._size = QtCore.QSizeF()
self._path = QtGui.QPainterPath()
self._rect = QtCore.QRectF()
self._type = QtGui.QRegion.Rectangle
self._factor = 1.0
self._pos = QtCore.QPointF()
self._initial_flag = False
fnt = self.font()
fnt.setPointSize(20)
self.setFont(fnt)
def showEvent(self, event):
if not self._initial_flag:
self._pos = self.rect().center()
self._initial_flag = True
#QtCore.pyqtSlot(int, int)
def set_size_squares(self, w, h):
self._path = QtGui.QPainterPath()
self._size = QtCore.QSizeF(w, h)
self._type = QtGui.QRegion.Rectangle
self.updatePath()
def paintEvent(self, event):
pen = QtGui.QPen()
brush = QtGui.QBrush(QtCore.Qt.black)
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(pen)
painter.setBrush(brush)
painter.translate(self.rect().center())
painter.scale(self._factor, self._factor)
painter.translate(-self.rect().center())
painter.translate(self._pos)
painter.drawPath(self._path)
if self._type == QtGui.QRegion.Rectangle:
painter.fillRect(self._rect, QtGui.QBrush(QtCore.Qt.gray, QtCore.Qt.Dense7Pattern))
painter.setBrush(QtGui.QBrush(QtCore.Qt.NoBrush))
painter.drawRect(self._rect)
def mousePressEvent(self, event):
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor))
self._initial_pos = event.pos()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
delta = event.pos() - self._initial_pos
self._path.translate(delta)
self._rect.translate(delta)
self.update()
self._initial_pos = event.pos()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
QtWidgets.QApplication.restoreOverrideCursor()
super().mouseReleaseEvent(event)
def updatePath(self):
r = QtCore.QRectF(QtCore.QPointF(), self._size)
r.moveCenter(QtCore.QPointF())
self._rect = QtCore.QRectF(r)
self.update()
def wheelEvent(self, event):
self._factor *= 1.01**(event.angleDelta().y()/15.0)
self.update()
super().wheelEvent(event)
class Paintwidget2(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.sizeHint()
self.setBackgroundRole(QtGui.QPalette.Base)
self.setAutoFillBackground(True)
self._size = QtCore.QSizeF()
self._path = QtGui.QPainterPath()
self._rect = QtCore.QRectF()
self._type = QtGui.QRegion.Rectangle
self._factor = 1.0
self._pos = QtCore.QPointF()
self._initial_flag = False
fnt = self.font()
fnt.setPointSize(20)
self.setFont(fnt)
def showEvent(self, event):
if not self._initial_flag:
self._pos = self.rect().center()
self._initial_flag = True
#QtCore.pyqtSlot(int)
def set_size_round(self, v):
self._path = QtGui.QPainterPath()
self._size = QtCore.QSizeF(v, 0.8*v)
self._type = QtGui.QRegion.Ellipse
self.updatePath()
def paintEvent(self, event):
pen = QtGui.QPen()
brush = QtGui.QBrush(QtCore.Qt.black)
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(pen)
painter.setBrush(brush)
painter.translate(self.rect().center())
painter.scale(self._factor, self._factor)
painter.translate(-self.rect().center())
painter.translate(self._pos)
painter.drawPath(self._path)
if self._type == QtGui.QRegion.Ellipse:
painter.setBrush(QtGui.QBrush(QtCore.Qt.gray, QtCore.Qt.Dense7Pattern))
painter.drawEllipse(self._rect)
def mousePressEvent(self, event):
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor))
self._initial_pos = event.pos()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
delta = event.pos() - self._initial_pos
self._path.translate(delta)
self._rect.translate(delta)
self.update()
self._initial_pos = event.pos()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
QtWidgets.QApplication.restoreOverrideCursor()
super().mouseReleaseEvent(event)
def updatePath(self):
r = QtCore.QRectF(QtCore.QPointF(), self._size)
r.moveCenter(QtCore.QPointF())
self._rect = QtCore.QRectF(r)
self.update()
def wheelEvent(self, event):
self._factor *= 1.01**(event.angleDelta().y()/15.0)
self.update()
super().wheelEvent(event)
class Button(QtWidgets.QWidget):
valueChanged = QtCore.pyqtSignal(int)
valuesChanged = QtCore.pyqtSignal(int,int)
def __init__(self, parent=None):
super(Button, self).__init__(parent)
roundbutton = QtWidgets.QPushButton('Round')
squarebutton = QtWidgets.QPushButton('Square')
Alay = QtWidgets.QVBoxLayout(self)
Alay.addWidget(roundbutton)
Alay.addWidget(squarebutton)
self.value = QtWidgets.QLabel()
roundbutton.clicked.connect(self.getbuttonfunc)
squarebutton.clicked.connect(self.sqaurebuttonfunc)
#QtCore.pyqtSlot()
def getbuttonfunc(self):
number, ok = QtWidgets.QInputDialog.getInt(self, self.tr("Set Number"),
self.tr("Input:"), 1, 1)
if ok:
self.valueChanged.emit(number)
#QtCore.pyqtSlot()
def sqaurebuttonfunc(self):
number, ok = QtWidgets.QInputDialog.getInt(self, self.tr("Set Number"),
self.tr("Input:"), 1, 1)
if ok:
self.valuesChanged.emit(number, number)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Foo()
w.show()
sys.exit(app.exec_())
You have to use a QStackedWidget to exchange widget, also set the sizeHint() of Paintwidget1 and Paintwidget2:
class Foo(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(Foo, self).__init__(parent)
self.setGeometry(QtCore.QRect(200, 100, 800, 650))
self.button = Button()
self.paint1 = Paintwidget1()
self.paint2 = Paintwidget2()
self.button.valuesChanged.connect(self.on_paint1)
self.button.valueChanged.connect(self.on_paint2)
self.mainSplitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
self._stacked_widget = QtWidgets.QStackedWidget()
self.mainSplitter.addWidget(self.button)
self.mainSplitter.addWidget(self._stacked_widget)
self.setCentralWidget(self.mainSplitter)
self._stacked_widget.addWidget(self.paint1)
self._stacked_widget.addWidget(self.paint2)
#QtCore.pyqtSlot(int, int)
def on_paint1(self, w, h):
self._stacked_widget.setCurrentIndex(0)
self.paint1.set_size_squares(w, h)
#QtCore.pyqtSlot(int)
def on_paint2(self, v):
self._stacked_widget.setCurrentIndex(1)
self.paint2.set_size_round(v)
class Paintwidget1(QtWidgets.QWidget):
# ...
def sizeHint(self):
return QtCore.QSize(640, 480)
class Paintwidget2(QtWidgets.QWidget):
# ...
def sizeHint(self):
return QtCore.QSize(640, 480)
I am using PyQt5 to build a GUI and inside it I am using a Class Drawer to enable for the user to draw using the mouse but when I am saving the image it is always empty can any one tell me why?
class Drawer(QWidget):
newPoint = pyqtSignal(QPoint)
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setAttribute(QtCore.Qt.WA_StaticContents)
self.modified = False
self.scribbling = False
imageSize = QtCore.QSize(9500, 9500)
h=400
w=400
self.myPenWidth = 13
self.myPenColor = QtCore.Qt.black
self.image = QtGui.QImage()
self.image=QtGui.QImage(w,h,QtGui.QImage.Format_RGB32)
self.path = QPainterPath()
def setPenColor(self, newColor):
self.myPenColor = newColor
def setPenWidth(self, newWidth):
self.myPenWidth = newWidth
def clearImage(self):
self.path = QPainterPath()
self.image.fill(QtGui.qRgb(255, 255, 255)) ## switch it to else
self.modified = True
self.update()
def saveImage(self, fileName, fileFormat):
self.image.save(fileName,fileFormat)
def paintEvent(self, event):
painter = QPainter(self)
#painter.setPen(QColor(0, 0, 0))
painter.setPen(QtGui.QPen(self.myPenColor,
self.myPenWidth,QtCore.Qt.SolidLine, QtCore.Qt.RoundCap,
QtCore.Qt.RoundJoin))
#painter.setFont(QFont('Decorative', 10))
painter.drawImage(event.rect(), self.image)
painter.drawPath(self.path)
def mousePressEvent(self, event):
self.path.moveTo(event.pos())
self.update()
def mouseMoveEvent(self, event):
self.path.lineTo(event.pos())
self.newPoint.emit(event.pos())
self.update()
def sizeHint(self):
return QSize(300, 300)
when I am calling it i use this function that calls the function inside the Drawer class
def saveFile(self):#, fileFormat):
fileFormat="PNG"
fileName="ar.png"
self.draw2.saveImage(fileName,fileFormat)
It is always empty because you have never painted the image, what you have painted has been the background of the QWidget, in the following code I have created a QPainter that takes as a base the image and draws on it and in the paintEvent() only the image is painted :
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Drawer(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setAttribute(Qt.WA_StaticContents)
h = 400
w = 400
self.myPenWidth = 13
self.myPenColor = Qt.black
self.image = QImage(w, h, QImage.Format_RGB32)
self.path = QPainterPath()
self.clearImage()
def setPenColor(self, newColor):
self.myPenColor = newColor
def setPenWidth(self, newWidth):
self.myPenWidth = newWidth
def clearImage(self):
self.path = QPainterPath()
self.image.fill(Qt.white) ## switch it to else
self.update()
def saveImage(self, fileName, fileFormat):
self.image.save(fileName, fileFormat)
def paintEvent(self, event):
painter = QPainter(self)
painter.drawImage(event.rect(), self.image, self.rect())
def mousePressEvent(self, event):
self.path.moveTo(event.pos())
def mouseMoveEvent(self, event):
self.path.lineTo(event.pos())
p = QPainter(self.image)
p.setPen(QPen(self.myPenColor,
self.myPenWidth, Qt.SolidLine, Qt.RoundCap,
Qt.RoundJoin))
p.drawPath(self.path)
p.end()
self.update()
def sizeHint(self):
return QSize(300, 300)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = QWidget()
btnSave = QPushButton("Save image")
btnClear = QPushButton("Clear")
drawer = Drawer()
w.setLayout(QVBoxLayout())
w.layout().addWidget(btnSave)
w.layout().addWidget(btnClear)
w.layout().addWidget(drawer)
btnSave.clicked.connect(lambda: drawer.saveImage("image.png", "PNG"))
btnClear.clicked.connect(drawer.clearImage)
w.show()
sys.exit(app.exec_())
I'm trying to paint some ticks in my custom progressbar but I'm not clear on why the line isn't showing up at all?
import sys
import os
sys.path.append('Z:\\pipeline\\site-packages')
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from PySide import QtGui, QtCore
class QProgressBarPro(QtGui.QProgressBar):
progressClicked = QtCore.Signal()
progressChanging = QtCore.Signal()
progressChanged = QtCore.Signal()
def __init__(self, parent=None):
super(QProgressBarPro, self).__init__(parent)
self.default_value = 50.0
self.lmb_pressed = False
self.setFormat('%p')
self.setRange(0.0, 100.0)
self.stepEnabled = True
self.step = 5
self.setToolTip('<strong>Press+Hold+Ctrl</strong> for percise values<br><strong>Right-Click</strong> to reset default value')
def step_round(self, x, base=5):
return int(base * round(float(x)/base))
def set_value_from_cursor(self, xpos):
width = self.frameGeometry().width()
percent = float(xpos) / width
val = self.maximum() * percent
if self.stepEnabled:
modifiers = QtGui.QApplication.keyboardModifiers()
if modifiers != QtCore.Qt.ControlModifier:
val = self.step_round(val, self.step)
self.setValue(val)
def mousePressEvent(self, event):
self.progressClicked.emit()
mouse_button = event.button()
if mouse_button == QtCore.Qt.RightButton:
self.setValue(self.default_value)
else:
xpos = event.pos().x()
self.set_value_from_cursor(xpos)
self.lmb_pressed = True
self.progressChanging.emit()
def mouseReleaseEvent(self, event):
self.lmb_pressed = False
self.progressChanged.emit()
def mouseMoveEvent(self, event):
if self.lmb_pressed:
xpos = event.pos().x()
self.set_value_from_cursor(xpos)
self.progressChanging.emit()
def paintEvent(self, event):
painter = QtGui.QPainter()
painter.drawLine(10, 0, 10, 10)
QtGui.QProgressBar.paintEvent(self, event)
# DEMO
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.ui_progress = QProgressBarPro()
self.ui_progress.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.ui_progress.setValue(10)
gdl = QtGui.QVBoxLayout()
gdl.addWidget(self.ui_progress)
self.setLayout(gdl)
self.resize(300, 300)
self.setWindowTitle('Tooltips')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You need to change your paintEvent function.
I wrote a first approach that provides same result as in your image:
def paintEvent(self, event):
QtGui.QProgressBar.paintEvent(self, event)
painter = QtGui.QPainter(self)
brush = QtGui.QBrush(QtCore.Qt.SolidPattern)
# Set gray color
brush.setColor(QtGui.QColor(204,204,204))
painter.setPen(QtGui.QPen(brush, 2, QtCore.Qt.SolidLine,QtCore.Qt.RoundCap))
#print(str(self.width())+","+str(self.height()))
progressbarwidth = self.width()
progressbarheight = self.height()
## Drawing one vertical line each 1/5
painter.drawLine(progressbarwidth*1/5, 0, progressbarwidth*1/5, progressbarheight)
painter.drawLine(progressbarwidth*2/5, 0, progressbarwidth*2/5, progressbarheight)
painter.drawLine(progressbarwidth*3/5, 0, progressbarwidth*3/5, progressbarheight)
painter.drawLine(progressbarwidth*4/5, 0, progressbarwidth*4/5, progressbarheight)
The achieved outcome is shown here.