PDA

View Full Version : QGraphicsScene and fonts/shapes sizes



olelukoie
25th January 2009, 12:13
Hi!
I've recently (a couple of months ago) began learning Qt programming and have a question about coordinate systems in QGraphics classes.

In my test app I have successfully created QGraphicsView control, created scene and added some QGraphicsItem-derived shapes (something like flowchart shapes). This was very easy - thank you devs! The problem occurred when I tried to add QGraphicsTextItem item.

For my program I need that all the shapes sizes to be expressed in metric units (millimeters) and font sizes to be expressed in pts (standard typographic point equal 1/72 inch, AFAIK). I've pretended that logical unit in QGraphicScene is equal to 1 mm and created rectangle of 30x20 mm. Then I've added text using Times New Roman, 12pt font. According to real world units the heights of these two objects have to be related approximately as 5:1 (12pt = 4.233(3)mm while the height of rectangle is 20mm). But I see that on the scene the height of the text is even larger then the height of rectangle.

I've decided that I need to scale dimensions of objects of different types to make them correct for the coordinate system of the scene. First I've tried to scale font, but that small font size (12pt/5 - less than 1mm) completely ruins font kerning and hinting. So now I see that I have to scale my graphical objects.

So my question is how to do that with minimal efforts so that all objects including text look correctly proportional on the screen and on the paper after printing? Is there any standard or commonly used or recommended method of mapping real world units on the QGraphicsScene coordinate system? Is there any example or tutorial of this mapping?

Thanks in advance.

wysota
25th January 2009, 12:39
A naive try would be to map the smallest unit (a font point in your case probably) to 1, then calculate how many points are there to fit into the other unit and then find the least multiplicator causing both values to be integer and rescale everything by that factor.

For instance if you had units where one is 2.5 times the other then after using the factor of 2 both values will become integer. This factor tells you how many units make a single initial unit. In other words you have to take the unit two times smaller than you initially tried. Note that using integers here is only because you (human) can calculate in integers much easier than with real values. QGraphicsView doesn't care - you can calculate everything in pts, it will adjust.

olelukoie
25th January 2009, 19:28
Thank you wysota, but you described the math needed to scale objects while I'm interested in Qt4 features that can ease my life by simplifying this task :) . And also I'm interested in a most effective approach to implementing this mapping from the QGraphics class hierarhy point of view - on which level (view, scene or item) it is recommended to do?

Currently for testing purposes I've just called logicalDpiX() and logicalDpiY() methods of my view and found scale factor for graphical objects (25.4/dpi_x and 25.4/dpi_y - this values can be different, right? And yes, I assume that initially - when there are no any scene transformations used - scene coords are equal to view coords. At least that's what I see using mapToScene/mapFromScene methods. May be I'm wrong.). Then I'm passing this values to every constructor of every graphical object and use them in their paint methods. This approach doesn't fully satisfy me due to a lot of manually performed math (scattered around my project), additional constructor parameters I have to pass to every object and because I don't know how to deal with printer resolutions in this approach.

May be the method you described (with least multiplicator and without linkage to dpi values of the device) solves the problem with printer resolutions, but only in case there is a guaranty that the scene logical units are always square (have the same physical step in horizontal and vertical directions) regardless of the size of device's units and their form, and mapping of scene coords to view or device coords are always performed with this issue taken into account. Is this the case?

wysota
25th January 2009, 21:37
Thank you wysota, but you described the math needed to scale objects while I'm interested in Qt4 features that can ease my life by simplifying this task :) .
QGraphicsView itself has no facilities for this but you can implement a veeeeery simple function to map between units.


And also I'm interested in a most effective approach to implementing this mapping from the QGraphics class hierarhy point of view - on which level (view, scene or item) it is recommended to do?
Units and coordinates are related strictly to the scene.


Currently for testing purposes I've just called logicalDpiX() and logicalDpiY() methods of my view and found scale factor for graphical objects (25.4/dpi_x and 25.4/dpi_y - this values can be different, right?
Yes.


And yes, I assume that initially - when there are no any scene transformations used - scene coords are equal to view coords. At least that's what I see using mapToScene/mapFromScene methods.
Yes this is true, provided you don't set a size of the scene. If you do, one pixel of the view equals one unit in the scene as long as you don't do any scaling.


Then I'm passing this values to every constructor of every graphical object and use them in their paint methods.
Eeem... what values?


This approach doesn't fully satisfy me due to a lot of manually performed math (scattered around my project), additional constructor parameters I have to pass to every object and because I don't know how to deal with printer resolutions in this approach.
I'm not sure what you mean but this shouldn't be the case. Once you have units set and you operate in these units, you don't have to do any math.


but only in case there is a guaranty that the scene logical units are always square (have the same physical step in horizontal and vertical directions) regardless of the size
Logical units are always square. Physical units may not be square if you provide different transformations in horizontal and vertical directions.

olelukoie
25th January 2009, 23:32
QGraphicsView itself has no facilities for this but you can implement a veeeeery simple function to map between units.

Yes, I see, but I thought there is some method of automatic mapping real world units to scene units like it is in the case of font sizes (I don't need to rescale font size given in real world typographic points to match scene logical units by myself. Instead it is done automatically inside Qt. And I wanted something like this behavior for all other graphical shapes. :) )


Eeem... what values?

Horizontal and vertical scale factors (25.4/logicalDpiX() and 25.4/logicalDpiY()).


I'm not sure what you mean but this shouldn't be the case. Once you have units set and you operate in these units, you don't have to do any math.
...
Logical units are always square. Physical units may not be square if you provide different transformations in horizontal and vertical directions.

Well, just to clarify the idea and my question. For example, I have 19-inch LCD monitor with "native" resolution of 1280x1024. This resolution has relation of 5:4 and pixels (units of the view) are square. But if I change resolution of this monitor to, say, 1152x864 or 1024x768 (both are 4:3) logical pixels of my device would become rectangles. Right? Now if I call logicalDpiX and logicalDpiY methods - would their return values still equal? (I think they won't but haven't tried yet. :) ) And if I add a circle to the scene and scale it with logicalDpiX value (I don't know how to scale a circle vertically and horizontally by different scale factors :) ) - would it look like circle in any resolution or it would become ellipse (visually) on some of them? And how would it look like on a printer with resolution 300x300 and square pixels?

This is what I meant.

wysota
26th January 2009, 01:47
Yes, I see, but I thought there is some method of automatic mapping real world units to scene units
There are no "scene units" - you define what "scene units" are - you can make them metres, aquatic miles, nanometres or sizes of your little finger. GraphicsView doesn't care as long as you stick to using them.


like it is in the case of font sizes (I don't need to rescale font size given in real world typographic points to match scene logical units by myself. Instead it is done automatically inside Qt.
No, that's not true. All sizes are calculated relative to the scene size and the physical device capabilities which are well determined by the transformation between the destination device and the scene. The initial transformation between the view and the scene is 1:1 therefore a font size of "10" will be the size of font size "10" as seen on the screen. But if you paint it on some other device with different resolution, it won't adjust to the device resolutio - it will still be "10" (pixels or points) as seen on the screen.


And I wanted something like this behavior for all other graphical shapes. :) )

If you want to map between points/picas/whatever and the scene dimensions you set, simply subclass QGraphicsScene and add a method to do the transformation.


Horizontal and vertical scale factors (25.4/logicalDpiX() and 25.4/logicalDpiY()).
But those values don't have any global meaning. They are only relevant in regard to a particular paint device. If you take another device (a pixmap of different size, a printer, a widget, a GL texture, SVG canvas) their interpretation will change. And by the way, you don't have to pass it to every object - each item has a pointer to the scene it is sitting in, just retrieve that value from the scene if you need it.


Well, just to clarify the idea and my question. For example, I have 19-inch LCD monitor with "native" resolution of 1280x1024. This resolution has relation of 5:4 and pixels (units of the view) are square. But if I change resolution of this monitor to, say, 1152x864 or 1024x768 (both are 4:3) logical pixels of my device would become rectangles. Right?
These are physical units. Logical units are units of the scene which are completely independent of the device you will be painting them on.

If you want to obtain the same real dimensions on different physical devices, you have to apply different transformations between the scene and the physical device you will be painting on. The scene doesn't act in terms of "milimetres" or "feet" - it is simply your interpretation of the word "unit". And these are world coordinates, by the way. So if you had an architectonic canvas where you sketch a house that has 20x8 metres in size, you would calibrate your scene in terms of metres or centimetres and not milimetres of the paper sheet you will be printing the sketch on after you have drawn it. You want to tell the scene "make me a wall that has 8 metres" not "make me a wall that will have 7 millimetres on the printout and at the same time 7 millimetres on the computer screen and at the same time 7 millimetres on a cinema screen".

seneca
26th January 2009, 08:33
Allthough the "scene units" can be declared as whatever you want, I found it most convenient to use "logical dots" because it facilates font handling most. So while my shapes are basically measured in mm, I need to calculate them to dots when creating a graphics item, but fonts are automatically drawn in theire optimal geometrics.

Dont mix the logical dots with the physical dots of screen and printer, physical dots should be only cared about at the level of the paint device (* and somewhat on the view), but not at level of the scene where everything is handled as logical dots which are allways square.

In your application you should take care about the facts that the size of logical dots are not a constant, but can vary for devices. For the screen on windows xp they are for example 96 DPI, while for a high resolution printer they might be 1200, 600 or 300 depending on the resolution selected in the print dialog.

(*) If you want the representation on the screen to be as exact as possible with real world units, you should do that in the zoom factor of the graphics view.

Here is an example how to find scale factors for exact screen zooming:


extern int qt_defaultDpi();

int screenLogicalResolution = qt_defaultDpi();
qreal dot2mm = 25.4 / qreal(screenLogicalResolution);
qreal mm2dot = qreal(screenLogicalResolution) / 25.4;
QDesktopWidget* desktop = QApplication::desktop();
qreal hscale = qreal(desktop->width()) / qreal(desktop->widthMM()) * dot2mm;
qreal vscale = qreal(desktop->height()) / qreal(desktop->heightMM()) * dot2mm;
myGrapicsView->scale(hscale, vscale);

olelukoie
26th January 2009, 21:41
seneca, thank you! These are my questions :)

What do you mean saying "logical dots"? How do you calculate them? And how do you apply them to fonts' and geometric objects' sizes?

And there are some questions about your example:
1. The fact of presence of this example implies that scene is mapped to the view 1:1 without taking into account the fact that the view units may be non-square. And to correct this we need to apply appropriate transformation and without it a circle in the scene would look like ellipse if horizontal and vertical dpi values are different. Right?
2. Why you use the same dpi value for both vertical and horizontal scale factors. They can be different - as in my previous example of using 1024x768 resolution on monitor with physical resolution of 1280x1024.
3. You set up mapping scene to view with simple transformation, applying the scale factors to the view. But this transformation affects all scene items - both fonts and graphics. Would the fonts still look correct after this transformation? (I thought about this simple mapping but I doubt in its correctness from the fonts sizes point of view). In Qt4 documentation stated that values of logicalDpi[X|Y]() methods are used to map fonts (quote: "Returns the horizontal resolution of the device in dots per inch, which is used when computing font sizes"). Would your method of mapping cause duplicated use of dpi values for fonts or not?
4. I can use this transformation in my zoom method (I use mouse wheel event handler for this), but where should I use it when printing? (Well, honestly I haven't read anything about printing in Qt4 yet except of a bit of info about QPrinter, so the answer may be absolutely obvious and trivial. Just point me to the right direction in docs.)

wysota,
there is some strangeness in your words. First you write that scene logical units are always square and then you write that there are no scene units at all and then once again write "Logical units are units of the scene". Very strange ;) How can coordinate system exist without units of measure? It's absurd from the math point of view. I think these units exist but are abstract meaning they do not tied to any real world units. This type of units is called "logical". This is normal and absolutely clear for me (I have some experience in OpenGL programming and I'm a bit of mathematician). :)
Second, you write that "These are physical units. Logical units are units of the scene". You are wrong because every LCD monitor (and not only monitors) has both physical and logical pixels. Physical pixels are semiconductor (or whatever else - OLEDs are organic) elements of LCD matrix and you can obtain their dpi value with physicalDpi[X|Y]() methods. These values are always the same for the given device (at least they should be). Logical pixels are pixels used to form picture on the screen and they can vary depending on used resolution of the screen. If you use "native" resolution those pixels (physical and logical) would be the same, but changing resolution to any "non-native" value makes them different. To get logical dpi values you use logicalDpi[X|Y]() pair of methods. :) This is how I see the situation with output devices. Logical units of the scene is a third level (not second!) of coordinate systems in graphics view and local units of items (their local coordinate system) of the scene is a fourth one. BTW do item's local units are always of the same size as the scene units or they can be different? I see there is the scale() method in QGraphicsItem class - what does it used for? Does it map single item's unit to several scene units (and vice versa if scale factor < 1.0) or just multiplies item's size (bounding rect?) by scale factors? The word "scales" used in documentation can mean both this actions. And does it mirror item if scale factors are negative?

And thank you again wysota and seneca.

seneca
26th January 2009, 22:02
Maybe the term of logical units is confusing you. So lets talk about scene units instead. And yes, scene units are assumed to be square in my example, because I am drawing shapes and it is most convenient to assume that.

But basically scene units need not be square, for example in a diagram the x unit of the scene may be meters and the y unit may be seconds - it all depends on the application. Say now your boss likes your diagram, but would rather see kilometers to hours - no problem, the scene stays the same and you just zoom the view. Get the idea?

I know it may be confusing first, my advice is to program some basic samples and learn by doing.

wysota
26th January 2009, 22:23
First you write that scene logical units are always square and then you write that there are no scene units at all and then once again write "Logical units are units of the scene". Very strange ;)

Ok, let me rephrase that. The scene has its own coordinate system (world coordinates) but the unit of that coordinate system is undetermined - it is only your interpretation that gives it meaning.


How can coordinate system exist without units of measure? It's absurd from the math point of view.
Math doesn't assume any unit names, people do. Human race defined what is a "metre" and what is a "second", the world would be exactly the same if we called them "foo" and "bar". It is your choice how you call your Unit. What is relevant is that it can be mapped to 2D real values.


Second, you write that "These are physical units. Logical units are units of the scene". You are wrong because every LCD monitor (and not only monitors) has both physical and logical pixels.
You can't access semiconductors from within your application. From our (programmers') point of view a physical system is a system based on the device we draw on - widgets (and the screen itself) is represented by pixels and a printer is represented by dots. The fact that those dots are composed from ink particles is meaningless for us.


This is how I see the situation with output devices.
You think too much. If we dealt with everything ourselves, we'd still be tied to the hardware and programming in assembly. In Qt (and especially GraphicsView) we have two coordinate systems - logical (world, real values) and physical (device, integer values), that's that.


BTW do item's local units are always of the same size as the scene units or they can be different?
The same, they reside in the coordinate system of the scene. Just remember that if you scale an item, its scene coordinates will change (but still the item will be anchored in the scene). So the answer to this question is both yes and no.


I see there is the scale() method in QGraphicsItem class - what does it used for?
For transforming the coordinate system of the item relative to its parent.

Does it map single item's unit to several scene units (and vice versa if scale factor < 1.0) or just multiplies item's size (bounding rect?) by scale factors? The word "scales" used in documentation can mean both this actions.
The word is used as a verb. This should answer your question.


And does it mirror item if scale factors are negative?
Yes. But it would really be simplest if you just opened a text editor and tried all that yourself. Qt examples are a good place to start and discussion about subpixel coordinates of an LCD screen is pointless if you have no control over it.

olelukoie
28th January 2009, 09:33
Yes. But it would really be simplest if you just opened a text editor and tried all that yourself. Qt examples are a good place to start and discussion about subpixel coordinates of an LCD screen is pointless if you have no control over it.
The only example on Graphics View I've found so far is a demo of a very simple flowcharting app. Also I've found a book with another example (don't remember its title/author). And I have almost no time for extensive experimenting so I need a good and possibly complete textual description of this relatively new subsystem of Qt4 technology. There is a lot of gaps in official docs (for example, no mention about mirroring effect of scale() methods, lack of description of difference between physicalDpiX|Y and logicalDpiX|Y pairs of methods, etc). If there were 5-7 different example apps of different level of complexity (current existing demo is just a basic one that leaves more questions than gives answers) and really full docs I possibly won't have any questions at all.

But anyway I'll continue my experimenting and thank you once again for your answers.

wysota
28th January 2009, 10:15
The only example on Graphics View I've found so far is a demo of a very simple flowcharting app.
Run qtdemo and click on "GraphicsView". By the way, qtdemo is written using GraphicsView as well.


There is a lot of gaps in official docs (for example, no mention about mirroring effect of scale() methods,
Hmm... isn't it obvious? The scaling is done using a matrix, so it's not hard to calculate that given negative values this will make the coordinates "flipped" around the respective axis.


lack of description of difference between physicalDpiX|Y and logicalDpiX|Y pairs of methods,
Really? What's this then?

For example, when printing, this resolution refers to the physical printer's resolution. The logical DPI on the other hand, refers to the resolution used by the actual paint engine.
Note that if the physicalDpiX() doesn't equal the logicalDpiX(), the corresponding QPaintEngine must handle the resolution mapping.



If there were 5-7 different example apps of different level of complexity (current existing demo is just a basic one that leaves more questions than gives answers) and really full docs I possibly won't have any questions at all.
It's all there, you just didn't look good enough :) Did you open Assistant and type in "Graphics View" (or even "Graphics View example") in the search tab? I guess you didn't... You could have also chosen "Qt examples" from the main documentation page and then click on "Graphics View" category.

olelukoie
28th January 2009, 15:39
Run qtdemo and click on "GraphicsView". By the way, qtdemo is written using GraphicsView as well.

Oh, I've just incorrectly expressed what I meant. I've seen these demos and talked about flowcharting demo ("Diagram Scene"). All others have nothing (almost nothing - there are some interesting ideas I've found useful for me) to do with flowcharting and CADs - there are demos on working with pictures, animation and other such things. There're no examples on working with fonts in Graphics View. And when I said there is only one example I talked about charting/CAD/scientific examples.


Hmm... isn't it obvious? The scaling is done using a matrix, so it's not hard to calculate that given negative values this will make the coordinates "flipped" around the respective axis.

It's obvious for you and - after some time of thinking - for me. But it should be stated in docs. It's not so difficult to describe and without it I can not treat docs to be complete - too many different "small" aspects are supposed to be obvious and thus are not described. It's bad. IMHO.


Really? What's this then?

Mmm, somehow I've missed this though I've searched for this info in Assistant. May be because I haven't used physicalDpi methods and haven't paid enough attention to their description. :o
And after reading this I do not see what the following means: "the corresponding QPaintEngine must handle the resolution mapping". Does it mean "there is no any handling of resolution mapping and the user must provide his/her own one" or "there is default handling of resolution mapping and in some non-standard cases user can override it" or something else? May be there is an answer in the deep of the docs, but I think it should be here in the same paragraph.


It's all there, you just didn't look good enough :) Did you open Assistant and type in "Graphics View" (or even "Graphics View example") in the search tab? I guess you didn't... You could have also chosen "Qt examples" from the main documentation page and then click on "Graphics View" category.
Yes, surely, I've read it and seen these demos and examples. I've already told that I'm playing with Graphics View for about two months, but I could easily miss something and even more easily could misunderstand some points - English is not my native language and there are no any translations of Graphics View docs into Russian (and there are no Russian books on this technology) :( .

wysota
28th January 2009, 15:54
Oh, I've just incorrectly expressed what I meant. I've seen these demos and talked about flowcharting demo ("Diagram Scene"). All others have nothing (almost nothing - there are some interesting ideas I've found useful for me) to do with flowcharting and CADs - there are demos on working with pictures, animation and other such things. There're no examples on working with fonts in Graphics View. And when I said there is only one example I talked about charting/CAD/scientific examples.
Everything in QPainter and family regarding fonts applies to GV as well. And besides that fonts are not treated specially in any way, they are just shapes that are rendered to the canvas.


It's obvious for you and - after some time of thinking - for me. But it should be stated in docs.
I don't agree, you can't state everything in the docs, it's simply not possible. You have to assume some level of knowledge of the reader and some level of curiosity and ability of finding information in outside sources.


Does it mean "there is no any handling of resolution mapping and the user must provide his/her own one" or "there is default handling of resolution mapping and in some non-standard cases user can override it" or something else? May be there is an answer in the deep of the docs, but I think it should be here in the same paragraph.
Please read about QPaintDevice and QPaintEngine and everything should become clear.

olelukoie
28th January 2009, 19:23
I don't agree, you can't state everything in the docs, it's simply not possible. You have to assume some level of knowledge of the reader and some level of curiosity and ability of finding information in outside sources.
I agree that it's not possible to describe everything down to the very basic things in docs but there are still things that should be described. I can give you another example: Z value of an item. There is a good description in setZValue method. It misses only one aspect: negative Z values and their meaning. :) Data type 'qreal' supposes that the value can be negative, zero and positive. But this description doesn't mentions negative values at all. Would top-level item with negative Z value be drawn above background layer or beneath it? Would child item with negative Z value still be drawn above its parent or it will be drawn behind it? Yes, I can guess and can experiment - but it should be described in docs. And BTW there are no mention about minimum and maximum limits of Z values acceptable by GV system. By default I assume they are equal to limits of 'qreal' data type ('double' in terms of C/C++ programming languages for all platforms except for ARM) - is it correct?

wysota
28th January 2009, 22:46
I agree that it's not possible to describe everything down to the very basic things in docs but there are still things that should be described.
Unfortunately you think the feature X should be described in more detail and someone else thinks feature Y deserves more attention. If we continue this trend, Qt Reference Manual wouldn't fit onto a DVD and still many people would be not satisfied with the contents. One thing is sure, the docs would become too bloated and it wouldn't be possible to find anything there.


I can give you another example: Z value of an item. There is a good description in setZValue method. It misses only one aspect: negative Z values and their meaning. :)
I'm not sure what you mean. Negative values of Z work exactly the same as positive ones.


Would top-level item with negative Z value be drawn above background layer or beneath it?
It's stated clearly in the docs that the item layer is above the background layer and items are positioned in ascending values of the Z-value. This makes the answer to your question obvious.


Would child item with negative Z value still be drawn above its parent or it will be drawn behind it?
It's also written in the docs that children are always drawn on top of their parents (in ascending Z order). That sentence answers your question as well.


Yes, I can guess and can experiment - but it should be described in docs.
It is :)


And BTW there are no mention about minimum and maximum limits of Z values acceptable by GV system.
So it clearly suggests the value is limited only by the datatype.


By default I assume they are equal to limits of 'qreal' data type ('double' in terms of C/C++ programming languages for all platforms except for ARM) - is it correct?

Yes, that's correct but this information is completely meaningless unless you want to add zillions of items to the scene but this would be stupid because of performance reasons :)

I think your problem is that you read some paragraph from the documentation and if you don't find the information you are looking for, you don't bother to look elsewhere. It might be wise to use Assistant's search tab more extensively.