PDA

View Full Version : How to draw a Qgraphicsitem on a Qimage?



eternalthinker
1st February 2012, 12:56
I have a QgraphicsScene where user can manually add items like reactangles, freehand lines etc (mouse drawing).

I use QGraphicsScene because I want to provide the following functionality:
After drawing an item, user can drag it around and apply some transforms before it is permanently added to the scene (no more editable)

Now, I want to provide a flood fill function also.
As you can see, this requires all of the scene to be somehow converted into a Qimage so that I can access the pixels
(Let me know if there is some way to do this in the Qgraphicscene itself !)

I was thinking it'd be convinient if I could draw each Qgraphicsitem onto a QImage once they are permanently added to the scene (after user does required transformations). So when the floodfill is invoked, I can simply access this QImage and do the processing!
Can this be done?

( As I have said, using Qgraphicscene is required for above mentioned reasons. Hoping to find a way in which I can draw a QgraphicsItem onto an image without implementing the whole (mouse) drawing logic for a QPainter too! )

wysota
1st February 2012, 13:23
It's possible that you can do flood fill by composing all relevant items in the scene into QPainterPath. Otherwise, if you want to go through QImage, you can call QGraphicsScene::render() on a painter opened on QImage to render the scene there.

eternalthinker
2nd February 2012, 03:24
It's possible that you can do flood fill by composing all relevant items in the scene into QPainterPath.
Could you elaborate a bit more on this part?
Determining the flood fill area will require accessing the pixels, right? Or you are implying that I can do it by analyzing the scene items in some other way?



Otherwise, if you want to go through QImage, you can call QGraphicsScene::render() on a painter opened on QImage to render the scene there.

I have to rule out rendering the scene, because there might be some 'floating' items at any given time. (As I mentioned above, they are still editable by the user and may not be in their finalized position. And I certainly don't want them to affect the floodfill area!)

And I suppose, there is no real solution the actual question (draw a Qgraphicsitem on a Qimage) ? :(

wysota
2nd February 2012, 09:28
Determining the flood fill area will require accessing the pixels, right? Or you are implying that I can do it by analyzing the scene items in some other way?
If all items can be represented by paths then calculating the flood area is a purely mathematical operation of intersecting path segments.



I have to rule out rendering the scene, because there might be some 'floating' items at any given time. (As I mentioned above, they are still editable by the user and may not be in their finalized position. And I certainly don't want them to affect the floodfill area!)
Then hide them before calling render(). However I think going through QImage is a really inefficient idea.


And I suppose, there is no real solution the actual question (draw a Qgraphicsitem on a Qimage) ? :(
The real solution is to use render().

eternalthinker
3rd February 2012, 13:26
I am thinking about two options here:

1)
As you suggested, represent all items as paths and find proper intersection to determine the fill area.
(Although I'm not exactly sure how to find a closed intersected path around a given point if one exists, or to determine the case when one doesn't exist)

2)
Override the paint() method in an extended QGraphicsItem, and use a QImage inside this item. So all painting basically happens on this QImage.

Also, remember that the user can create free hand lines. Assuming they try to sketch drawings, there might be a large number of tiny paths on the scene.

Considering these, which of the above two options do you think is more efficient for floodfill?

wysota
3rd February 2012, 14:28
1)
As you suggested, represent all items as paths and find proper intersection to determine the fill area.
(Although I'm not exactly sure how to find a closed intersected path around a given point if one exists, or to determine the case when one doesn't exist)
I'd start by looking at QPainterPath API.


2)
Override the paint() method in an extended QGraphicsItem, and use a QImage inside this item. So all painting basically happens on this QImage.
I really don't see the point of doing anything like that.


Also, remember that the user can create free hand lines. Assuming they try to sketch drawings, there might be a large number of tiny paths on the scene.
Tough luck.


Considering these, which of the above two options do you think is more efficient for floodfill?
Certainly calculating intersections is faster than checking each pixel of the image.

eternalthinker
3rd February 2012, 15:22
Certainly calculating intersections is faster than checking each pixel of the image.

Okay, thanks a lot for the insight ! :)

I'll start implementing it in terms of pathItems. Let there be paths ;)

eternalthinker
3rd February 2012, 19:12
I'd start by looking at QPainterPath API.

Yes yes, I am aware of the 3 or so intersect* methods available for QPainterPath.

I was rather talking about the base logic in finding paths starting at a coordinate.

On first look, the logic seems to be along the same lines of usual floodfill, ie, starting at the point and propagating in all directions until any paths are encountered.
We might have a long list of paths.
To determine whether *any* path goes through a coordinate, I really don't see a method other than iterating through *all* paths! (At all points) :eek:

wysota
3rd February 2012, 19:27
Paths don't intersect if their bounding rects don't intersect so you don't have to check all paths, only those that have a chance of intersecting. If you do that in a clever fashion by checking intersections as you are adding paths (instead of checking everything in one go when you want to do a fill), you can greatly limit the amount of checks required. Besides, QPainterPath has more useful methods than just checking intersections.

d_stranz
4th February 2012, 17:49
@thinker: I am not clear on exactly *what* it is that you want your users to be able to flood fill - the graphical items they have added, the background, what? You should think about composing your scene in layers, by using the z-value property of graphics items. Items with a higher z-value will clip items with lower z-value (ignoring transparency for now), so in essence the scene will do all of the work needed to calculate intersections and such. Whatever you want to flood fill should have a lower z-value than the things on top.

I agree with Wysota - you are going about this totally backward if you think that -you- need to convert your scene to an image and do all the work of filling flooded areas yourself. All the tools are there in the Graphics / View architecture, you just need to learn how to use them.

eternalthinker
4th February 2012, 18:16
@thinker: I am not clear on exactly *what* it is that you want your users to be able to flood fill - the graphical items they have added, the background, what?

Think about flood filling in a typical drawing application - this case is just the same.

So users might draw circles, rectangles, and free hand curves which are not closed.
And a flood fill might fill a complex closed shape made by free hand lines, or leak out of an open shape and fill the whole background.

And user might really click anywhere, not just on the lower z valued items. May be on the very lines themselves!

wysota
4th February 2012, 23:13
Does the fill create a new object or modify existing ones?

eternalthinker
5th February 2012, 01:31
Does the fill create a new object or modify existing ones?

It can be done either way, depending upon what supports the logic better.
The aim here is just to create floodfill. So a new object can be created if required!

wysota
5th February 2012, 15:30
There's a fundamental difference between the two. If you don't care about it then maybe you shouldn't be using graphics view at all? If you're just after raster painting then there are no benefits in using graphics view and you can reduce your implementation to a widget displaying QPixmap/QImage.

eternalthinker
5th February 2012, 17:25
There's a fundamental difference between the two. If you don't care about it then maybe you shouldn't be using graphics view at all? If you're just after raster painting then there are no benefits in using graphics view and you can reduce your implementation to a widget displaying QPixmap/QImage.

As I have mentioned in the question, the most recent item is editable by the user.
ie, it can be dragged around, resized and rotated before the final permamnent version is drawn.
I thought this could be easier to implement with QGraphicsView

wysota
5th February 2012, 21:02
As I have mentioned in the question, the most recent item is editable by the user.
ie, it can be dragged around, resized and rotated before the final permamnent version is drawn.
I thought this could be easier to implement with QGraphicsView

That doesn't require graphics view. Nevertheless my question still stands -- if your fill is to create a new "most recent item" that is to be "editable by the user" then it is much different from filling an existing item or canvas.

eternalthinker
6th February 2012, 02:26
That doesn't require graphics view. Nevertheless my question still stands -- if your fill is to create a new "most recent item" that is to be "editable by the user" then it is much different from filling an existing item or canvas.

Actually 'editable' apply to only a selected few items in the application. This excludes floodfills and freehand lines.

eternalthinker
15th February 2012, 03:53
And I suppose, there is no real solution to the actual question (draw a Qgraphicsitem on a Qimage) ?

The real solution is to use render().

It IS possible to code a method of the form: QImage.addItem(QGraphicsItem), which in this case is the answer to my original question.

wysota
15th February 2012, 10:09
Is it a question or a statement?

eternalthinker
15th February 2012, 15:39
Is it a question or a statement?

Statement. I capitalized the 'IS' to avoid that ambiguity :)