I am subclassing QLabel, on which I set QPixmap. I want to zoom into the image displayed in the pixmap (without losing quality). I don't want to see the entire image enlarged, just to zoom in.
I have tried many ways to scale the pixmap, but was unable to get good results. The following code resizes the image, but with very bad quality. What is the right way?
from PyQt5 import QtWidgets, QtCore, QtGui
class ImageLabel(QtWidgets.QLabel):
def __init__(self, img):
self.set_image(img)
def set_image(self, image):
qimg = QtGui.QPixmap.fromImage(image)
self._displayed_pixmap = QtGui.QPixmap(qimg)
# scale image to fit label
self._displayed_pixmap.scaled(self.width(), self.height(), QtCore.Qt.KeepAspectRatio)
self.setScaledContents(True)
self.setMinimumSize(512, 512)
self.show()
def zoom_image(self):
image_size = self._displayed_pixmap.size()
image_size.setWidth(image_size.width() * 0.9)
image_size.setHeight(image_size.height() * 0.9)
self._displayed_pixmap = self._displayed_pixmap.scaled(image_size, QtCore.Qt.KeepAspectRatio)
self.update() # call paintEvent()
def wheelEvent(self, event):
modifiers = QtWidgets.QApplication.keyboardModifiers()
if modifiers == QtCore.Qt.ControlModifier:
self._zoom_image(event.angleDelta().y())
def paintEvent(self, paint_event):
painter = QtGui.QPainter(self)
painter.drawPixmap(self.rect(), self._displayed_pixmap)
You can try using this:
this work for me when I put pictures in PYQT
self._displayed_pixmap.scaled(self.width(), self.height(), QtCore.Qt.SmoothTransformation)
Hope it helps
Related
Am using pyqt5 and Python in my program which is drawing shapes.
My problem is after am drawing my shapes when I zoom in they look in a really bad way like the image below.
I want to get sharpness like in the SVG image.
So how to get these results in pyqt5?
This what I get:
And this what I want:
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtCore import Qt
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 900, 900)
self.setWindowTitle('Pen styles')
self.show()
def paintEvent(self, e):
qp = QPainter()
qp.begin(self)
self.drawLines(qp)
qp.end()
def drawLines(self, qp):
pen = QPen(Qt.black, 2, Qt.SolidLine)
qp.setPen(pen)
qp.drawLine(20, 40, 800, 800)
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
By default QPainter only uses the TextAntialiasing render hint, so shapes are always aliased.
In order to draw "smooth" lines, you need to activate the Antialiasing hint.
Note that you don't need to call begin() if you provide the paint device in the QPainter constructor, and since it's a local variable in the scope of paintEvent() you don't need to call end() either, as it will be called anyway when the painter is destroyed by the garbage collector when the function returns.
def paintEvent(self, e):
qp = QPainter(self)
qp.setRenderHints(qp.Antialiasing)
self.drawLines(qp)
I'm currently trying to reproduce the Window 10 Start Menu scroll bar in Qt (python) but I can't figure how to resize my custom QScrollBar to change it's width in runtime.
I tried to resize it using the QScrollBar.resize method (in the enterEvent and leaveEvent) but it scale the widget outside it's "drawing area".
For example, my scroll bar is set to a QScrollArea and when I try to resize it, it doesn't take more space and move the widgets, instead of that it just scale on it's right, where I can't see it.
The only solution I've found for now is to use StyleSheet but I can't animate this to have the smooth resize I'm looking for.
There is some code for you to test and see what's wrong:
from PySide2 import QtWidgets, QtCore
from functools import partial
class MyTE(QtWidgets.QPlainTextEdit):
def __init__(self):
super(MyTE, self).__init__()
self.setVerticalScrollBar(MyScrollBar(self))
self.setPlainText('mmmmmmmmmmmmmmmmmmmmmmmmmmmmm'*50)
class MyScrollBar(QtWidgets.QScrollBar):
def __init__(self, parent=None):
super(MyScrollBar, self).__init__(parent=parent)
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
def enterEvent(self, event):
super(MyScrollBar, self).enterEvent(event)
self.resize(QtCore.QSize(4, self.height()))
def leaveEvent(self, event):
super(MyScrollBar, self).leaveEvent(event)
self.resize(QtCore.QSize(10, self.height()))
wid = MyTE()
wid.show()
To make the width change smoother then you can use a QVariantAnimation:
from PySide2 import QtWidgets, QtCore
class MyTE(QtWidgets.QPlainTextEdit):
def __init__(self):
super(MyTE, self).__init__()
self.setVerticalScrollBar(MyScrollBar(self))
self.setPlainText("mmmmmmmmmmmmmmmmmmmmmmmmmmmmm" * 50)
class MyScrollBar(QtWidgets.QScrollBar):
def __init__(self, parent=None):
super(MyScrollBar, self).__init__(parent=parent)
self._animation = QtCore.QVariantAnimation(
startValue=10, endValue=25, duration=500
)
self._animation.valueChanged.connect(self.change_width)
def enterEvent(self, event):
super(MyScrollBar, self).enterEvent(event)
self._animation.setDirection(QtCore.QAbstractAnimation.Forward)
self._animation.start()
def leaveEvent(self, event):
super(MyScrollBar, self).leaveEvent(event)
self._animation.setDirection(QtCore.QAbstractAnimation.Backward)
self._animation.start()
def change_width(self, width):
self.setStyleSheet("""QScrollBar:vertical{ width: %dpx;}""" % (width,))
if __name__ == "__main__":
app = QtWidgets.QApplication()
wid = MyTE()
wid.show()
app.exec_()
.0I've finally found how to do it without using stylesheet !
Thanks eyllanesc for your answer, it give me a new bases to work on, I've finally understand how Qt handle the resizing (I guess), I use the setFixedSize in a method called everytime the value of my animation change.
To work, this need to have an overrided sizeHint method that return the value of the animation for the width.
Also, this works on Autodesk Maya (unfortunately the solution offered by eyllanesc didn't worked in Maya for unknown reason).
There is my solution:
from PySide2 import QtWidgets, QtCore, QtGui
class MyTE(QtWidgets.QPlainTextEdit):
def __init__(self):
super(MyTE, self).__init__()
self.setVerticalScrollBar(MyScrollBar(self))
self.setPlainText("mmmmmmmmmmmmmmmmmmmmmmmmmmmmm" * 50)
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
def resizeEvent(self, event):
super(MyTE, self).resizeEvent(event)
class MyScrollBar(QtWidgets.QScrollBar):
def __init__(self, parent=None):
super(MyScrollBar, self).__init__(parent=parent)
self._animation = QtCore.QVariantAnimation(
startValue=10.0, endValue=25.0, duration=300
)
self._animation.valueChanged.connect(self.changeWidth)
self._width = 10
def enterEvent(self, event):
super(MyScrollBar, self).enterEvent(event)
self._animation.setDirection(QtCore.QAbstractAnimation.Forward)
self._animation.start()
def leaveEvent(self, event):
super(MyScrollBar, self).leaveEvent(event)
self._animation.setDirection(QtCore.QAbstractAnimation.Backward)
self._animation.start()
def sizeHint(self):
"""This must be overrided and return the width processed by the animation
.. note:: In my final version I've replaced self.height() with 1 because it does not change
anything but self.height() was making my widget grow a bit each time.
"""
return QtCore.QSize(self._width, self.height())
def changeWidth(self, width):
self._width = width # This will allow us to return this value as the sizeHint width
self.setFixedWidth(width) # This will ensure to scale the widget properly.
if __name__ == "__main__":
app = QtWidgets.QApplication()
wid = MyTE()
wid.show()
app.exec_()
Note: startValue and endValue of the QVariantAnimation must be float, animation won't work if they are of type int (Work in Qt 5.6.1 (Maya 2018) but not Qt 5.12.5 (Maya 2020)
PS: If someone is interrested about my final Widget (Window 10 Start menu scroll bar) let ask me in private.
I have created a square at random position in canvas, but I don't know how do I move it to somewhere else in canvas by dragging it to desired position, please suggest some edits or a new method to achieve the proposed task, I am learning while doing so.
P.S. Attached a screenshot of the output window.
import sys
from random import randint
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow,QPushButton,QWidget
from PyQt5 import QtGui
from PyQt5.QtCore import QRect,Qt
from PyQt5.QtGui import QPainter,QBrush, QPen
from PyQt5 import QtCore
class Window(QMainWindow):
def __init__(self):
super(Window,self).__init__()
title="TeeSquare"
left=500
top=200
width=500
height=400
iconName="square.jpg"
self.setWindowTitle(title)
self.setWindowIcon(QtGui.QIcon(iconName))
self.setGeometry(left, top, width, height)
self.should_paint_Rect = False
self.windowcomponents()
self.initUI()
self.show()
def initUI(self):
if self.should_paint_Rect:
self.label=QtWidgets.QLabel(self)
self.label.setText("circle")
def windowcomponents(self):
button=QPushButton("Add", self)
button.setGeometry(QRect(0, 0, 50, 28))
button.setIcon(QtGui.QIcon("Add.png"))
button.setToolTip("Create Square")
button.clicked.connect(self.paintRect)
def paintEvent(self, event):
super().paintEvent(event)
if self.should_paint_Rect:
painter = QtGui.QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setPen(QPen(Qt.black, 5, Qt.SolidLine))
painter.drawRect(randint(0,500), randint(0,500), 100, 100)
self.initUI()
self.label.move(60,100)
def paintRect(self, painter):
self.should_paint_Rect = True
self.update()
app = QApplication(sys.argv)
Rect=Window()
Rect.show()
sys.exit(app.exec_())
The logic of creating a dynamic element is to indicate a set of specific characteristics that by modifying these, the element is modified.
In this case you could use the center of the square, the dimensions of the square, etc. and that data must be implemented through a data structure that can be created from scratch for example by creating a class that has the information of the rectangle, but in Qt it is not necessary to create that element since it already exists and is QRect.
Now that that element has been identified, you can create a QRect whose top-left is random when the button is pressed, and use that QRect to paint it.
For dragging the procedure is:
Get the mouse click position.
Verify that the click is inside the rectangle.
Calculate the position relative to the rectangle.
When moving the mouse, the position of the rectangle must be updated based on the position of the mouse press.
Considering all of the above, the solution is:
import random
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.rect = QtCore.QRect()
self.drag_position = QtCore.QPoint()
button = QtWidgets.QPushButton("Add", self)
button.clicked.connect(self.on_clicked)
self.resize(640, 480)
#QtCore.pyqtSlot()
def on_clicked(self):
if self.rect.isNull():
self.rect = QtCore.QRect(
QtCore.QPoint(*random.sample(range(200), 2)), QtCore.QSize(100, 100)
)
self.update()
def paintEvent(self, event):
super().paintEvent(event)
if not self.rect.isNull():
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtGui.QPen(QtCore.Qt.black, 5, QtCore.Qt.SolidLine))
painter.drawRect(self.rect)
def mousePressEvent(self, event):
if self.rect.contains(event.pos()):
self.drag_position = event.pos() - self.rect.topLeft()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if not self.drag_position.isNull():
self.rect.moveTopLeft(event.pos() - self.drag_position)
self.update()
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.drag_position = QtCore.QPoint()
super().mouseReleaseEvent(event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Rect = Window()
Rect.show()
sys.exit(app.exec_())
I wanted to include a QToolbar in a Qwidget, but I found that I can only create a QToolbar in a QMainWindow. So, instead I want to create a Qlabel with an arrow icon in it. I downloaded an image with transparent background (I suppose). But, in the code, the image is not really transparent as I expected, it looks ugly. Is there any way to show only the arrow without the background. Below is a sample code
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class App(QMainWindow):
def __init__(self):
super().__init__()
self.title = 'test'
self.left = 0
self.top = 0
self.width = 300
self.height = 500
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.table_widget = MyTableWidget(self)
self.setCentralWidget(self.table_widget)
self.show()
class MyTableWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
# Create first tab
label3 = QLabel()
pixmap = QPixmap("index2.png")
smaller_pixmap = pixmap.scaled(32, 32, Qt.KeepAspectRatio, Qt.FastTransformation)
label3.setPixmap(smaller_pixmap)
label3.mouseReleaseEvent = self.on_click
self.layout.addWidget(label3)
self.setLayout(self.layout)
#pyqtSlot()
def on_click(self, event):
print('yes')
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
You are getting an "ugly" image because that image has lines that are partially transparent. I've increased the alpha threshold to better show them:
Those lines are part of the image and Qt cannot "guess" what portions of the image are "important" to you or not.
There's fundamentally no easy way to remove them by code, and even you'd succeed the result would be ugly anyway (some partial transparency is required around the border of the image to keep them smooth) and it wouldn't be worth the effort.
Just look for a different image, or edit it by clipping it to the arrow borders.
I'm trying to create a character map visualization tool with PyQt5. With fontTools library, I'm extracting the UNICODE code points supported in a given ttf file. Then using QPainter.drawText I'm drawing the glyphs on labels. The labels are stored in a QGridLayout and the layout is in a QScrollArea
Everything works fine, except when I try to scroll. The drawn images are overlapped whenever I try to scroll too fast. It looks like this.
The labels are redrawn properly the moment the window loses focus.
Here's an MWE of what I've so far.
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFontDatabase, QFont, QColor, QPainter
from fontTools.ttLib import TTFont
class App(QWidget):
def __init__(self):
super().__init__()
self.fileName = "mukti.ttf" #the ttf file is located in the same path as the script
self.initUI()
def initUI(self):
self.setWindowTitle("Glyph Viewer")
self.setFixedSize(640, 480)
self.move(100, 100)
vBox = QtWidgets.QVBoxLayout()
self.glyphView = GlyphView()
vBox.addWidget(self.glyphView)
self.setLayout(vBox)
self.showGlyphs()
self.show()
def showGlyphs(self):
#Using the fontTools libray, the unicode blocks are obtained from the ttf file
font = TTFont(self.fileName)
charMaps = list()
for cmap in font['cmap'].tables:
charMaps.append(cmap.cmap)
charMap = charMaps[0]
fontDB = QFontDatabase()
fontID = fontDB.addApplicationFont("mukti.ttf")
fonts = fontDB.applicationFontFamilies(fontID)
qFont = QFont(fonts[0])
qFont.setPointSize(28)
self.glyphView.populateGrid(charMap, qFont)
class GlyphView(QtWidgets.QScrollArea):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.setWidgetResizable(True)
def populateGrid(self, charMap, qFont):
glyphArea = QtWidgets.QWidget(self)
gridLayout = QtWidgets.QGridLayout()
glyphArea.setLayout(gridLayout)
row, col = 1, 1
for char in charMap:
uni = charMap[char]
gridLayout.addWidget(Glyph(qFont, chr(char)), row, col)
if not col % 4:
col = 1
row += 1
else:
col += 1
self.setWidget(glyphArea)
class Glyph(QtWidgets.QLabel):
def __init__(self, font, char):
super().__init__()
self.font = font
self.char = char
self.initUI()
def initUI(self):
self.setFixedSize(48, 48)
self.setToolTip(self.char)
def paintEvent(self, event):
qp = QPainter(self)
qp.setBrush(QColor(0,0,0))
qp.drawRect(0, 0, 48, 48)
qp.setFont(self.font)
qp.setPen(QColor(255, 255, 255))
qp.drawText(event.rect(), Qt.AlignCenter, self.char)
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
I'm not sure what is causing this. Any help is appreciated!
The paintEvent() method gives us an object that belongs to QPaintEvent, that object provides a QRect through the rect() method, that QRect is the area that is currently visible, and that information could be used to optimize the painting, for example let's say we have a widget that shows texts in several lines, if the text is large few lines will look so painting all is a waste of resources, so with a proper calculation using the aforementioned QRect we could get those lines and paint that part spending few resources. In this answer I show an example of the use of event.rect().
In your case there is no need to use event.rect() since you would be painting the text in a part of the widget widget. what you should use is self.rect():
def paintEvent(self, event):
qp = QPainter(self)
qp.setBrush(QColor(0,0,0))
qp.drawRect(0, 0, 48, 48)
qp.setFont(self.font())
qp.setPen(QColor(255, 255, 255))
# change event.rect() to self.rect()
qp.drawText(self.rect(), Qt.AlignCenter, self.text())
I also see unnecessary to overwrite paintEvent() method since you can point directly to the QLabel the font, the text and the alignment:
class Glyph(QtWidgets.QLabel):
def __init__(self, font, char):
super().__init__(font=font, text=char, alignment=Qt.AlignCenter, toolTip=char)