Created
July 22, 2016 09:45
-
-
Save mhugo/f809970c952f318cb2abc57af1a2446d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# This set of classes demonstrate how to implement a defered renderer with QT | |
# | |
# The idea is to be source compatible with existing QPainter operations (drawXXX, setBrush, etc.) | |
# and add extra commands that allow to "insert back" operations in the pipeline before they are actually rendered. | |
# | |
# This is a possible component for the "symbol clipping" feature of QGIS | |
# | |
# The rendering is split into "rendering layers" that correspond to "symbol layers" of QGIS. | |
# Each rendering layer is made of a QPicture allowing to serialize painting commands and a clipping path defined as a QPainterPath | |
# | |
# The augmented MyPainter class has two new methods: | |
# * setRenderingLayer(name): switch to another rendering layer with a given name | |
# * addToClickMask(path, [names]): add to the clipping mask of each layer given in parameter | |
# * paintOn(device): unstack every painting commands and render them on the given device (replaces end()) | |
import sys | |
from PyQt4.QtCore import * | |
from PyQt4.QtGui import * | |
class RenderingLayer: | |
def __init__(self, name): | |
self._picture = QPicture() | |
self._name = name | |
self._clip_mask = QPainterPath() | |
def picture(self): | |
return self._picture | |
def name(self): | |
return self._name | |
def mask(self): | |
return self._clip_mask | |
def add_to_mask(self, path): | |
self._clip_mask = self._clip_mask.united(path) | |
class MyPainter(QPainter): | |
# QPainter augmented with two methods | |
def __init__(self): | |
# rendering layers | |
self.layers = [RenderingLayer("main")] | |
self.current_layer = self.layers[0] | |
self.layer = {"main":self.current_layer} | |
QPainter.__init__(self, self.current_layer.picture()) | |
def setRenderingLayer(self, name): | |
"Sets the current layer where every following painting commands will be part of" | |
if self.current_layer.name() == name: | |
return | |
# switch to another layer | |
self.current_layer = RenderingLayer(name) | |
self.layer[name] = self.current_layer | |
self.layers.append(self.current_layer) | |
self.end() | |
self.begin(self.current_layer.picture()) | |
def addToClipMask(self, path, layers): | |
"""Sets the clip mask for a set of layers | |
:param path: a QPainterPath | |
:param layers: a list of layers defined by setRenderingLayer | |
""" | |
for layer in layers: | |
self.layer[layer].add_to_mask(path) | |
# to be called instead of end() | |
def paintOn(self, device): | |
print "paintOn" | |
self.end() | |
painter = QPainter(device) | |
for layer in self.layers: | |
print "rendering layer", layer.name() | |
# "invert" the mask | |
path = QPainterPath() | |
path.addRect(0, 0, device.width(), device.height()) | |
path = path.subtracted(layer.mask()) | |
painter.setClipPath(path) | |
# replay painting commands | |
layer.picture().play(painter) | |
class Window(QWidget): | |
def __init__(self): | |
QWidget.__init__(self) | |
def paintEvent(self, event): | |
painter = MyPainter() | |
painter.setRenderHint(QPainter.Antialiasing) | |
painter.fillRect(event.rect(), QBrush(QColor("white"))) | |
painter.fillRect(QRect(0, 0, 100, 100), QBrush(QColor("green"))) | |
# tag any following commands as part of the "L1" layer (simulate symbol layer rendering) | |
painter.setRenderingLayer("L1") | |
painter.setPen(QPen(QBrush(QColor("black")), 6)) | |
painter.drawLine(0, 0, 20, 20) | |
painter.drawLine(20, 20, 50, 20) | |
painter.drawLine(50, 20, 70, 70) | |
painter.setPen(QPen(QBrush(QColor("yellow")), 3)) | |
painter.drawLine(0, 0, 20, 20) | |
painter.drawLine(20, 20, 50, 20) | |
painter.drawLine(50, 20, 70, 70) | |
# tag any following commands as part of the "L2" layer (~ label "layer") | |
painter.setRenderingLayer("L2") | |
mask = QPainterPath() | |
mask.addRect(40, 25, 40, 20) | |
painter.drawText(40, 40, "Label") | |
# Set the clipping mask for the current layer | |
painter.addToClipMask(mask, ["L1"]) | |
mask = QPainterPath() | |
mask.addRect(0, 5, 50, 20) | |
painter.drawText(0, 20, "Label2") | |
# Set the clipping mask for the current layer | |
painter.addToClipMask(mask, ["L1"]) | |
# Actually render | |
painter.paintOn(self) | |
def sizeHint(self): | |
return QSize(500, 500) | |
if __name__ == "__main__": | |
app = QApplication(sys.argv) | |
window = Window() | |
window.show() | |
sys.exit(app.exec_()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment