Advanced gradient - python

I'm trying to make simple color picker. At the beginning, it consisted of r, g, b and h, s, l sliders but later I decided to go with the more common picker, the one with a hue slider and the thing like in this image
This is my first time dealing with colors/gradients and tried to make the thing and achieve the gradient with qlineargradient and realized that's not how the gradient works. I'm not sure how to paint the gradient like in the picture programmatically. How can I paint something like it? I'm not talking about making the color picker itself but only the gradient in the image.
The following is what I've tried;
import sys
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
import pathlib
import customColorsList
class widget(QWidget):
def __init__(self):
super(widget, self).__init__()
self.resize(300, 300)
self.setStyleSheet("background: qlineargradient("
"x1:0, y1:0,"
"x2:1, y2:1,"
"stop:0 white,"
"stop:0.5 green,"
"stop:1 black);")
if __name__ == '__main__':
app = QApplication(sys.argv)
w = widget()
w.show()
sys.exit(app.exec_())

You can't achieve that using simple gradients, but you can use basic composition, with an horizontal QLinearGradient for the color, and a vertical for the black component.
The basic concept is like this:
self.gradient = QtGui.QLinearGradient(0, 0, 1, 0)
self.gradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
self.gradient.setColorAt(0, QtCore.Qt.white)
self.gradient.setColorAt(0, QtCore.Qt.green)
self.overlay = QtGui.QLinearGradient(0, 0, 0, 1)
self.overlay.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
self.overlay.setColorAt(0, QtCore.Qt.transparent)
self.overlay.setColorAt(1, QtCore.Qt.black)
In the following example, I'm implementing a basic widget to show the color and a simple interface to change it:
from PyQt5 import QtCore, QtGui, QtWidgets
class Picker(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setMinimumSize(250, 250)
self.gradient = QtGui.QLinearGradient(0, 0, 1, 0)
self.gradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
self.gradient.setColorAt(0, QtCore.Qt.white)
self.overlay = QtGui.QLinearGradient(0, 0, 0, 1)
self.overlay.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
self.overlay.setColorAt(0, QtCore.Qt.transparent)
self.overlay.setColorAt(1, QtCore.Qt.black)
def setColor(self, color):
self.gradient.setColorAt(1, color)
self.update()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.fillRect(self.rect(), self.gradient)
qp.fillRect(self.rect(), self.overlay)
class Test(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout(self)
self.picker = Picker()
layout.addWidget(self.picker)
self.redSlider = QtWidgets.QSlider(QtCore.Qt.Horizontal, maximum=255)
layout.addWidget(self.redSlider)
self.greenSlider = QtWidgets.QSlider(QtCore.Qt.Horizontal, maximum=255)
layout.addWidget(self.greenSlider)
self.blueSlider = QtWidgets.QSlider(QtCore.Qt.Horizontal, maximum=255)
layout.addWidget(self.blueSlider)
self.redSlider.valueChanged.connect(self.updateColor)
self.greenSlider.valueChanged.connect(self.updateColor)
self.blueSlider.valueChanged.connect(self.updateColor)
self.setColor(QtGui.QColor(QtCore.Qt.green))
def setColor(self, color):
self.redSlider.blockSignals(True)
self.redSlider.setValue(color.red())
self.redSlider.blockSignals(False)
self.greenSlider.blockSignals(True)
self.greenSlider.setValue(color.green())
self.greenSlider.blockSignals(False)
self.blueSlider.blockSignals(True)
self.blueSlider.setValue(color.blue())
self.blueSlider.blockSignals(False)
self.updateColor()
def updateColor(self):
color = QtGui.QColor(
self.redSlider.value(),
self.greenSlider.value(),
self.blueSlider.value()
)
self.picker.setColor(color)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
test = Test()
test.show()
sys.exit(app.exec_())

Related

How to integrate a cursor in a QtWidgets app

I am new to QtWidgets and trying to build an app in QtWidgets and Python (3.x). the end goal of the app is to show images and a
superposed cursor (to be exact, a "plus" sign of 2cm) that can be moved along the image reacting to mouse events. I concentrate now first on this cursor. So far, I read examples on how to do it on matplotlib. however, i have trouble to understand how to integrate matplotlib on my code.
Also, is matplotlib the easiest way to do it on this code. or there might be a better way to do it.
any hint would be helpful
thank you in advance.
here is my desired output and the code of my app
import sys
from PySide2 import QtWidgets
from vispy import scene
from PySide2.QtCore import QMetaObject
from PySide2.QtWidgets import *
class SimpleItem(QtWidgets.QGraphicsItem):
def __init__(self):
QtWidgets.QGraphicsItem.__init__(self)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
def boundingRect(self):
penWidth = 1.0
return QRectF(-10 - penWidth / 2, -10 - penWidth / 2,
20 + penWidth, 20 + penWidth)
def paint(self, painter, option, widget):
rect = self.boundingRect()
painter.drawRect(rect)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.groupBox = QGroupBox(self.centralwidget)
self.groupBox.setObjectName("groupBox")
self.gridLayout.addWidget(self.groupBox, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
QMetaObject.connectSlotsByName(MainWindow)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# OpenGL drawing surface
self.canvas = scene.SceneCanvas(keys='interactive')
self.canvas.create_native()
self.canvas.native.setParent(self)
self.view = self.canvas.central_widget.add_view()
self.view.bgcolor = '#ffffff' # set the canva to a white background
scene2 = QGraphicsScene()
item = SimpleItem()
scene2.addItem(item)
self.setWindowTitle('MyApp')
def main():
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('my_gui')
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
edit: I added a class (here it is a rectangle just as an example) to illustrate my problem. i have trouble integrating that snippet of the code (with SimpleItem) to OpenGL canvas
You can use the QApplication.setOverrideCursor method to assign a .png image file as your cursor when it appears inside of the Qt program.
Here is an example that is mostly based on the code in your question. And below is a gif that demonstrates the example. And the last image is the image I used in the code as cursor.png
Hope this helps
import sys
from PySide2.QtCore import *
from PySide2.QtWidgets import *
from PySide2.QtGui import *
class SimpleItem(QtWidgets.QGraphicsItem):
def __init__(self):
QtWidgets.QGraphicsItem.__init__(self)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self._brush = QBrush(Qt.black)
def boundingRect(self):
penWidth = 1.0
return QRectF(-50 - penWidth / 2, -50 - penWidth / 2,
50 + penWidth, 50 + penWidth)
def paint(self, painter, option, widget):
rect = self.boundingRect()
painter.drawRect(rect)
painter.fillRect(rect, self._brush)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.resize(800, 600)
self.scene = QGraphicsScene()
self.canvas = scene.SceneCanvas(keys='interactive')
self.view = QGraphicsView(self.scene)
item = SimpleItem()
self.scene.addItem(item)
self.setCentralWidget(self.view)
self.setWindowTitle('MyApp')
def main():
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID('my_gui')
app = QtWidgets.QApplication([])
app.setOverrideCursor(QCursor(QPixmap('cursor.png')))
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

How to implement an own QScrollArea

I am trying to understand the way a QScrollArea is working to implement my own MyQScrollArea widget. MyQScrollArea should make use of setViewportMargins. For this I wrote a minimum working example shown below:
from PyQt5 import QtWidgets
import sys
class MyScrollArea(QtWidgets.QAbstractScrollArea):
def __init__(self):
super().__init__()
self.label = QtWidgets.QLabel(", ".join(map(str, range(100))), self)
hScrollBar = self.horizontalScrollBar()
hScrollBar.setRange(0, self.label.sizeHint().width() - self.sizeHint().width())
hScrollBar.valueChanged.connect(self._HScrollBarValueChanged)
self.setViewportMargins(100, 0, 0, 0)
self._HScrollBarValueChanged(0)
def _HScrollBarValueChanged(self, value):
self.label.move(-value + self.viewportMargins().left(), 0)
def main():
app = QtWidgets.QApplication(sys.argv)
scroll = MyScrollArea()
scroll.show()
app.exec_()
if __name__ == "__main__":
main()
The result of the code is shown below:
However, after I scroll the inner widget moves out of the viewport and paints itself in an area I do not want it to be painted:
What am I doing wrong and how can I get the setViewportMargins functionality working?
You have to set it the QLabel as a child of the viewport, in addition to modifying the properties of the QScrollBar every time the geometry of the widgets is modified:
import sys
from PyQt5 import QtWidgets
class MyScrollArea(QtWidgets.QAbstractScrollArea):
def __init__(self):
super().__init__()
self.label = QtWidgets.QLabel(", ".join(map(str, range(100))), self.viewport())
self.setViewportMargins(100, 0, 0, 0)
self.horizontalScrollBar().valueChanged.connect(
self.on_hscrollbar_value_changed
)
self.update_scrollbar()
def on_hscrollbar_value_changed(self, value):
self.label.move(-value, 0)
def update_scrollbar(self):
self.horizontalScrollBar().setRange(
0, self.label.sizeHint().width() - self.viewport().width()
)
self.horizontalScrollBar().setPageStep(self.viewport().width())
def resizeEvent(self, event):
self.update_scrollbar()
super().resizeEvent(event)
def main():
app = QtWidgets.QApplication(sys.argv)
scroll = MyScrollArea()
scroll.show()
app.exec_()
if __name__ == "__main__":
main()

How to create a basic custom QGraphicsEffect in Qt?

I have been trying to create a basic QGraphicsEffect to change the colors of the widget, but first I tried to make an effect that does nothing like so:
class QGraphicsSepiaEffect(QtWidgets.QGraphicsEffect):
def draw(painter):
pixmap = sourcePixmap()
painter.drawPixmap(pixmap.rect(), pixmap)
I am using PySide2. Though I checked all over the internet but couldn't find any sample, neither a template nor a real custom effect.
How can I write a basic effect to alter the colors of my widget?
As your question is basically how to create a custom effect then based on an example offered by the Qt community I have translated it to PySide2:
import random
import sys
from PySide2 import QtCore, QtGui, QtWidgets
# or
# from PyQt5 import QtCore, QtGui, QtWidgets
class HighlightEffect(QtWidgets.QGraphicsEffect):
def __init__(self, offset=1.5, parent=None):
super(HighlightEffect, self).__init__(parent)
self._color = QtGui.QColor(255, 255, 0, 128)
self._offset = offset * QtCore.QPointF(1, 1)
#property
def offset(self):
return self._offset
#property
def color(self):
return self._color
#color.setter
def color(self, color):
self._color = color
def boundingRectFor(self, sourceRect):
return sourceRect.adjusted(
-self.offset.x(), -self.offset.y(), self.offset.x(), self.offset.y()
)
def draw(self, painter):
offset = QtCore.QPoint()
try:
pixmap = self.sourcePixmap(QtCore.Qt.LogicalCoordinates, offset)
except TypeError:
pixmap, offset = self.sourcePixmap(QtCore.Qt.LogicalCoordinates)
bound = self.boundingRectFor(QtCore.QRectF(pixmap.rect()))
painter.save()
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(self.color)
p = QtCore.QPointF(offset.x() - self.offset.x(), offset.y() - self.offset.y())
bound.moveTopLeft(p)
painter.drawRoundedRect(bound, 5, 5, QtCore.Qt.RelativeSize)
painter.drawPixmap(offset, pixmap)
painter.restore()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QWidget()
lay = QtWidgets.QVBoxLayout(w)
for _ in range(3):
o = QtWidgets.QLabel()
o.setStyleSheet(
"""background-color : {}""".format(
QtGui.QColor(*random.sample(range(255), 3)).name()
)
)
effect = HighlightEffect(parent=o)
o.setGraphicsEffect(effect)
lay.addWidget(o)
w.show()
w.resize(640, 480)
sys.exit(app.exec_())

How to update a QPainter pixmap based on QTimer

I would like to continuously rotate a QPainter pixmap every tick based from a QTimer - in this example a clock arm. I can rotate the clock arm, however I dont have the skills to make the rotation dynamic. Here is the clock I would like to make and below is my sample code. Let me know if you can help me on the way, thanks!
import sys
import random
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.label = QtWidgets.QLabel()
self.setCentralWidget(self.label)
self.Clock_pixmap = QtGui.QPixmap("clock.png")
self.Arm_pixmap = QtGui.QPixmap("clockarm.png")
self.painter = QtGui.QPainter(self.Clock_pixmap)
self.painter.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform)
self.painter.drawPixmap(QtCore.QPoint(), self.Arm_pixmap)
self.painter.end()
self.label.setPixmap(self.Clock_pixmap.scaled(self.label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
self.label.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.label.setMinimumSize(150, 150)
self.w1 = self.Arm_pixmap.width()/2
self.h1 = self.Arm_pixmap.height()/2
self.rotationData = random.sample(range(100), 100)
timer = QtCore.QTimer(self, timeout=self.rotateArm, interval=100)
timer.start()
self.n=0
def rotateArm(self):
self.n+=1
self.painter.translate(self.w1,self.h1)
self.painter.rotate(self.rotationData[self.n])
self.painter.translate(-self.w1,-self.h1)
self.update()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
You are painting on a QPainter where you indicated that it was finished painting since you used the end() method. So it is not necessary to make a class attribute to QPainter but only a local variable. Considering the above, the solution is:
import sys
import random
from PyQt5 import QtCore, QtGui, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self._angle = 0
self.label = QtWidgets.QLabel()
self.setCentralWidget(self.label)
self.clock_pixmap = QtGui.QPixmap("clock.png")
self.arm_pixmap = QtGui.QPixmap("clockarm.png")
rotation_data = random.sample(range(100), 100)
self.data_iter = iter(rotation_data)
timer = QtCore.QTimer(self, timeout=self.rotate_arm, interval=100)
timer.start()
def rotate_arm(self):
try:
angle = next(self.data_iter)
except StopIteration:
pass
else:
self.draw(angle)
def draw(self, angle):
pixmap = self.clock_pixmap.copy()
painter = QtGui.QPainter(pixmap)
painter.setRenderHints(
QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform
)
painter.translate(pixmap.rect().center())
painter.rotate(angle)
painter.translate(-pixmap.rect().center())
painter.drawPixmap(QtCore.QPoint(), self.arm_pixmap)
painter.end()
self.label.setPixmap(pixmap)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

Overlay two pixmaps with alpha value using QPainter

I'm trying to overlay 2 pixmaps and convert them into a single pixmap in a QGraphics scene. Both pixmaps are transparent at certain locations. I want to combine the maps using the 'SourceOver' blend type listed here: I have a simple toy example below to illustrate my issue where I have created two dummy transparent pixmaps, one green and one blue. In reality, these maps are loaded from images and painted over, but this example reproduces the problem. Based on this How to add an image on the top of another image?, the approach I tried (4 lines commented out) was to create a QPainter with one of the pixmaps and then draw the other pixmap on top of it, however that crashes the program. Any ideas on how to fix this? I eventually want to be able to save the combined pixmap.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QPixmap, QPainter, QPen, QBrush, QPainterPath
from PyQt5.QtCore import (QLineF, QPointF, QRectF, Qt)
class Viewer(QtWidgets.QGraphicsView):
def __init__(self, parent):
super(Viewer, self).__init__(parent)
self._scene = QtWidgets.QGraphicsScene(self)
self.photo = QtWidgets.QGraphicsPixmapItem()
self.label = QtWidgets.QGraphicsPixmapItem()
self._scene.addItem(self.photo)
self._scene.addItem(self.label)
self.setScene(self._scene)
def overlayMaps(self):
blue = QtGui.QPixmap(600, 600)
blue.fill(QtGui.QColor(0,0,255,0))
p = QPainter(blue)
self.pen = QPen()
self.pen.setColor(QtGui.QColor(0,0,255,255))
self.pen.setWidth(10)
p.setPen(self.pen)
p.drawLine(0,0,600,600)
green = QtGui.QPixmap(600, 600)
green.fill(QtGui.QColor(0,255,0,0))
p = QPainter(green)
self.pen = QPen()
self.pen.setColor(QtGui.QColor(0,255,0,255))
self.pen.setWidth(10)
p.setPen(self.pen)
p.drawLine(600,0,0,600)
self.photo.setPixmap(blue)
self.label.setPixmap(green)
resultPixmap = QtGui.QPixmap(self.photo.pixmap().width(), self.photo.pixmap().height())
# resultPainter = QtGui.QPainter(resultPixmap)
# resultPainter.setCompositionMode(QtGui.QPainter.CompositionMode_SourceOver)
# resultPainter.drawPixmap(300,300, self.photo.pixmap())
# resultPainter.drawPixmap(300,300, self.label.pixmap())
def saveOverlayMap(self):
pass
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.viewer = Viewer(self)
self.viewer.overlayMaps()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 600, 600)
window.show()
sys.exit(app.exec_())
I have implemented a function that performs the action of joining depending on the mode, for a better appreciation I have moved the items.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
def join_pixmap(p1, p2, mode=QtGui.QPainter.CompositionMode_SourceOver):
s = p1.size().expandedTo(p2.size())
result = QtGui.QPixmap(s)
result.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(result)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.drawPixmap(QtCore.QPoint(), p1)
painter.setCompositionMode(mode)
painter.drawPixmap(result.rect(), p2, p2.rect())
painter.end()
return result
class Viewer(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(Viewer, self).__init__(parent)
self._scene = QtWidgets.QGraphicsScene(self)
self.setScene(self._scene)
blue = QtGui.QPixmap(100, 100)
blue.fill(QtCore.Qt.transparent)
p = QtGui.QPainter(blue)
pen = QtGui.QPen(QtGui.QBrush(QtGui.QColor(0,0,255)), 10)
p.setPen(pen)
p.drawLine(0, 0, 100, 100)
p.end()
self.photo = self._scene.addPixmap(blue)
green = QtGui.QPixmap(100, 100)
green.fill(QtCore.Qt.transparent)
p = QtGui.QPainter(green)
pen = QtGui.QPen(QtGui.QBrush(QtGui.QColor(0, 255, 0, 255)), 10)
p.setPen(pen)
p.drawLine(100, 0, 0, 100)
p.end()
self.label = self._scene.addPixmap(green)
self.label.setPos(200, 0)
self.overlayMaps()
def overlayMaps(self):
p1 = QtGui.QPixmap(self.photo.pixmap())
p2 = QtGui.QPixmap(self.label.pixmap())
result_pixmap = join_pixmap(self.photo.pixmap(), self.label.pixmap())
self.result_item = self._scene.addPixmap(result_pixmap)
self.result_item.setPos(100, 200)
result_pixmap.save("result_pixmap.png")
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Viewer()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
result_pixmap.png

Categories

Resources