QGraphicsItem paint delay - python

What could be the possible reason for this? When i zoom in the QGraphicsView and move the QGraphicsItem, I get this weird result. It does update if I zoom or pan the View again or if I focus on other widgets. Im using PySide. And the painter function is this
def paint(self, painter, option, widget):
if self.isSelected():
brush = self.highlight_brush
pen = self.highlight_pen
else:
brush = self.dormant_brush
pen = self.dormant_pen
painter.setBrush(brush)
painter.setPen(pen)
painter.drawRect(0, 0, 100, 100)
Why does this happen even for this basic paint event? This problem is not seen if there is no Pen. If I increase the pen width, this issue is disturbingly visible.

I don't know the actual solution for this rendering artifacts. But, updating the view during mouseMoveEvent did fix the issue.
def mouseMoveEvent(self, event):
QGraphicsView.mouseMoveEvent(self, event)
if self.scene().selectedItems():
self.update()

The error you are seeing is probably because parts of what you are drawing are outside the bounding rectangle. My guess is you are using the same values to calculate the rectangle you are drawing as you are to calculate the bounding rectangle. Applying a pen then will make the drawn rectangle wider than the bounds and so will result in the smearing you are seeing.

I had the same problem. This is my solution:
As #Nathan Mooth said, the problem was that I was drawing outside of the boundingRect, so I just made my rounded rectangle(what I'm drawing in the paint() method) 10 units width and height less than the boundingRect:
# Setup Rect
frameRect = self.boundingRect()
frameRect.setWidth(self.boundingRect().width() - 10)
frameRect.setHeight(self.boundingRect().height() - 10)
This is how it was looking before(GIF):
This is how it looks now(GIF)
Note: I added color selection and changed the color of the drop shadow. So it looks a bit different.

Related

How to make QPixmap display the svg?

I have a big svg(70000 * 2000) and I want to display it completely.
I Used QPixmap and I found it was incomplete.
This is my code:
self.img = QPixmap('test.svg')
self.scaled_img = self.img
def paintEvent(self, e):
painter = QPainter()
painter.begin(self)
self.draw_img(painter)
painter.end()
def draw_img(self, painter):
painter.drawPixmap(self.point, self.scaled_img)
According to the QPainter documentation:
If you are using coordinates with Qt's raster-based paint engine, it is important to note that, while coordinates greater than +/- 215 can be used, any painting performed with coordinates outside this range is not guaranteed to be shown; the drawing may be clipped.
This seems to be a limitation valid for QImage and QPixmap too, as explained in the answer to QImage/QPixmap size limitations?, which means that the image will only be rendered up to 32767x32767.
You may want to try using QSvgRenderer.render(painter, rect) in order to scale it, or setViewBox() and then the basic render() to show portions of it.

Scaling positions of GraphicsItems on a GraphicsScene without changing other properties

I layout a bunch of nodes on a QGraphicsScene. The nodes are basic ellipses (QGraphicsEllipseItems). It works reasonably well.
However I would like to know how to size the ellipses. Currently I have a hard-coded radius to 80 units this works fine when there are the number of ellipses are few hundred, however when I have a few thousand ellipses it looks all wrong as they are too small for the scale of the scene.
Conversely when there are only a few 10s the scene being smaller the ellipses are way to large.
I am looking to find a formula that better balances the size of an ellipse, with the number of ellipses on the scene and the scale of the scene.
Also as I zoom in and out I would like the ellipses to remain appropriately sized.
Can anyone advise on how to best achieve a balanced arrangement?
The scene has a certain bounding rectangle that encloses all graphics items you put into it while the view has a certain size on the screen.
Between the scene and the view there is a transformation matrix (2x3 for scaling, rotate, shear and translation). You can get it by QGraphicsView.transform().
Now if you put more ellipses into your plot increasing the scene size but want to still see all of them, you must zoom out and accordingly the widths of the ellipses will shrink too.
You don't want that. Okay, so why not resizing them (according to the current scaling factor) everytime the scale changes. Well, this is probably not very efficient.
The better solution is to not change the scale of the view, but just scale the positions manually while keeping the zoom fixed. That way no properties of the items except their position has to be changed.
Example (using PySide and Python 3 but easily adjustable to PyQt and Python 2):
from PySide import QtGui, QtCore
import random
class MyGraphicsView(QtGui.QGraphicsView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def wheelEvent(self, event):
if event.delta() > 0:
scaling = 1.1
else:
scaling = 1 / 1.1
# reposition all items in scene
for item in self.scene().items():
r = item.rect()
item.setRect(QtCore.QRectF(r.x() * scaling, r.y() * scaling, r.width(), r.height()))
app = QtGui.QApplication([])
scene = QtGui.QGraphicsScene()
scene.setSceneRect(-200, -200, 400, 400)
for i in range(100):
rect = QtCore.QRectF(random.uniform(-180, 180), random.uniform(-180, 180), 10, 10)
scene.addEllipse(rect)
view = MyGraphicsView(scene)
view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
view.resize(400, 400)
view.show()
app.exec_()
With the mousewheel you can scale the positions and then it looks like this:
As for the balance of size and number of ellipses - well it's all your choice. There is no generic rule. I recommend to not make the ellipses larger than the distance between the ellipses or they will overlap. In general I work with scene coordinates that are 1:1 with pixel sizes of the view (as I did in the example above, 400 pixels width of the view, 400 units width of the scene rectangle). Then I can easily imagine what a size of an ellipse of 10 will be, that is 10 pixels. If I want more I use more and if I want less I use less. But there is no rule for it, it's up to what you want.

pygame drawing a filled rectangle overwrite border, but only sometimes?

I have a class in my pygame program that extends sprite, and effectively gives collision and whatnot to a rectangle. The __init__ of the class is as follows:
def __init__(self,topleft,size,label):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(size)
self.rect = self.image.get_rect()
self.rect.topleft = topleft
self.image.fill([128,128,128])
pygame.draw.rect(self.image, [200,200,200], self.rect, 5)
And this works great... for one rectangle. Problem is, after that rectangle, any others are solidly colored rectangles set to the fill color (128,128,128) instead of being a filled rectangle with a differently colored (200,200,200) border. I'm expecting this is some sort of issue with class variables as opposed to instance variables, but I'm unsure where the problem lies.
Okay, I figured it out. The Rect argument in the draw function is relative to the position of the image. Since I had set its rect to its location on the screen, it was drawing the rectangle way offset from the corner. It worked on my first one only because it happened to start at [0,0]. Here is the fixed draw code:
pygame.draw.rect(self.image, [200,200,200],pygame.Rect([0,0],size), 5)

Pyglet: How to change resolution when you go fullscreen?

I'm using Pyglet and I have a little that includes an object moving over a background. Both of them are represented by images (png and jpg).
I've created a non-fullscreen window with size 800x600 and it works fine, but when I toggle to fullscreen... background and object have the same size as before and the rest of the screen is filled with black (empty color).
What I want to do is to "scale" the images or change the resolution when I toggle fullscreen mode.
I've read the documentation, but I can't find the answer to this.
I know that with Pygame, this problem solves itself automatically (if you change the window size, everything rescales automatically)... but how do you do this with pyglet?
This is my relevant code:
import pyglet
WIDTH = 800
HEIGHT = 600
working_dir = '/where/i/have/my/images/'
window = pyglet.window.Window(WIDTH, HEIGHT)
background = pyglet.image.load(working_dir + 'background.jpg')
flying_thing = pyglet.image.load(working_dir + 'flying_thing.png')
#window.event
def on_draw():
window.clear()
background.blit(0, 0)
flying_thing.blit(WIDTH // 2, HEIGHT // 2)
#window.event
def on_key_press(symbol, modifiers):
if symbol == pyglet.window.key.SPACE:
window.set_fullscreen(not window.fullscreen)
pyglet.app.run()
You can try this code changing working_dir, background.jpg and flying_thing.png to a working directory of yours and two images in it.
I didn't tried, but from pyglet docs, blit supports width and height. Its signature is
blit(self, x, y, z=0, width=None, height=None)
Have you tried using
background.blit(width=window.width, height=windows.height)
instead? (I'm not sure the window.width changes on full_screen, let's see...).
This answer can also be relevant to your question: https://stackoverflow.com/a/11183462/931303.

drawPie() with customized borders

Is it possible to draw a pie shape with no border at the arc, but with borders at straight lines? I have attached a picture below:
Currently I have implemented this by first calling calling drawPie() with painter.setPen(QtCore.Qt.NoPen), and then later using QLineF to draw the lines separately based on the center and angles of the pie shape.
But the problem is that the line position does not sync with the pie shape if the angles are not multiples of 90. Attached another picture showing the problem.
Is there a simple/elegant way to do this?
Thanks!
Assuming your custom Pie is a subclassed QGraphicsRectItem, you could try something like this:
class CustomPie(QtGui.QGraphicsRectItem):
angle = 2000
def paint(self, painter, option, widget):
# Create the path to draw the lines
path = QtGui.QPainterPath()
path.moveTo(self.rect().width()/2, self.rect().height()/2)
path.lineTo(self.rect().width(), self.rect().height()/2)
path.arcMoveTo(self.rect(), self.angle/16) # arcMoveTo in degrees
path.lineTo(self.rect().width()/2, self.rect().height()/2)
# draw a pie with no Pen
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
painter.setBrush(QtGui.QBrush(QtCore.Qt.lightGray))
painter.drawPie(self.rect(), 0, self.angle)
# Draw the path with a custom Pen
painter.setPen(QtGui.QPen(QtCore.Qt.black, 2))
painter.drawPath(path)
Here we override paint to draw a Pie and a path (actually quite similar to your own method).
You would have to override __init__ as well (angle as a class attribute is probably not what you want) but that's the idea.

Categories