Qt QPainter in millimetres instead of inches - c++

I have a QPrinter that prints A4 either directly to a physical printer or a PDF. Now I'd like to use QPainter to draw in millimetres, but the current coordinate system seems to be the width and height of an A4 in inches times the resolution of the printer.
8.26 inch x 1200 res = 9912
11.69 inch x 1200 res = 14028
I have tried the following but text just ended up huge.
auto page = printer.pageRect(QPrinter::Unit::Millimeter);
painter.setWindow(QRect(0, 0, page.width(), page.height()));
How do I change this so my QPainter can draw to 210 x 297 mm instead of the above system?
This is on Windows 10 and with Qt 5.10.

I tested this method on X11 (ubuntu linux) PDF print, using ScreenResolution printer mode:
painter.begin(printer);
int log_w = 210;
int log_h = 297;
painter.setWindow(0, 0, log_w, log_h);
int phys_w = printer->width();
int phys_h = printer->height();
painter.setViewport(0, 0, phys_w, phys_h);
Basically, set your logical size in mm using the painter window, and give the painter's viewport the printer's physical size.
This line should print a rectangle around the page with a border of 10 mm:
painter.drawRect(10, 10, log_w - 20, log_h -20);
Text should work accordingly. This code should print the word Ok at the top left corner of the rectangle:
QFont font = painter.font();
font.setPointSize(10); //1 cm height
painter.setFont(font);
painter.drawText(10, 20, "Ok");
painter.end();
Using HighResolution printer mode, font size must be set using
font.setPixelSize(10); //1 cm height
and a QPen must be set to the painter:
QPen pen(Qt::black);
pen.setWidthF(0.2);
painter.setPen(pen);
painter.drawRect(10, 10, log_w - 20, log_h - 20);
About loss of device dependency using setPixelSize, I'm aware that here is stated:
It is possible to set the height of characters shown on the screen to
a specified number of pixels with setPixelSize(); however using
setPointSize() has a similar effect and provides device independence.
but I think it refers to screen only, given that here is stated:
When rendering text on a QPrinter device, it is important to realize
that the size of text, when specified in points, is independent of the
resolution specified for the device itself. Therefore, it may be
useful to specify the font size in pixels when combining text with
graphics to ensure that their relative sizes are what you expect.

I think that you are looking for the QTransform class, according to the official doc:
The QTransform class specifies 2D transformations of a coordinate
system. A transformation specifies how to translate, scale, shear,
rotate or project the coordinate system, and is typically used when
rendering graphics.
You can initialise your custom transform class:
QTransform transform = QTransform::fromScale(painter.device()->physicalDpiX() / scale, painter.device()->physicalDpiY() / scale);
A think that this could be helpfull, the number of dots per militmeter:
const int dot_per_millimeter = qRound(qApp->primaryScreen()->physicalDotsPerInch() / 25.40);
Customise then your scale & apply it using a QPainter:
QPainter painter(parent);
painter.setWorldTransform(transform, false);

Your approach is correct. Here is an example of how to set up a printer/painter pair. I don't fiddle around with the transformation matrix since it's sufficient to specify a window/viewport pair. I don't even specify the viewport explicitly, since it is automatically set to the metrics of the paint device (in this case the QPrinter object).
#include <QPrinter>
#include <QPainter>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QPrinter printer(QPrinter::PrinterResolution);
printer.setOrientation(QPrinter::Portrait);
printer.setPageSize(QPageSize(QPageSize::A4));
printer.setResolution(300 /*dpi*/);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName("ellipse.pdf");
QPainter painter(&printer);
auto page = printer.pageRect(QPrinter::Unit::Millimeter);
painter.setWindow(page.toRect());
// Draw a 5mm thick ellipse across the whole page.
painter.setPen(QPen(Qt::black, 5.0));
painter.drawEllipse(0, 0, 210, 297);
return 0;
}
It is hard to tell what goes wrong in your case without seeing the rest of the code

Related

Qt text size in points

I'm trying to print an invoice document on A4 in millimetres instead of the default device units. Except that when changing the units to millimetres the pointsize of text on the printed document no longer matches up with the pointsize in for instance Word or Adobe Illustrator. I tried converting the point size to the corresponding pixel size, but they had issues.
QFont::SetPixelSize only takes an int so if the calculations falls below 1 it will trunctuate to 0
font.setPixelSize((9.0 * 72.0) / printer.resolution());
And the other method made the text the correct vertical size, but there are some artefacts:
int phys_w = printer.width();
font.setPointSizeF((9.0 / phys_w) * 210.0);
Where you can see the unusually large gaps between some characters. (Perhaps there is some of precision issue inside Qt its text drawing code?)
Here is a minimal example showing the issues:
QPrinter printer(QPrinter::HighResolution);
printer.setPageSize(QPrinter::A4);
printer.setOrientation(QPrinter::Portrait);
printer.setFullPage(true);
printer.setPageMargins(QMarginsF(0, 0, 0, 0));
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName("invoice.pdf");
QPainter painter(&printer);
auto page_size = printer.pageRect(QPrinter::Unit::Millimeter);
painter.setWindow(page_size.toRect());
QFont font = painter.font();
// Either this
font.setPixelSize((9.0 * 72.0) / printer.resolution());
// or this
int phys_w = printer.width();
font.setPointSizeF((9.0 / phys_w) * 210.0);
painter.setFont(font);
painter.drawText(35, 46, "John Doe");
How can I have the positioning in Millimetres (or any arbitrary unit) and have the text size be correct in points (or some correct recalculation)?
This is on Qt 5.10.0 and Windows 10.
EDIT
In the end I opted to go for a 10x scale increase (so tenths of a millimetre) which fixed the kerning issues visible for setPointSizeF. Now the last issue I'm having with the scale is that of setting the width of a line and other shapes (QPen::setWidth) and I cant find a calculation so it's in millimetres.
EDIT
In the end the linewidth didn't need any recalculations. The final code is below:
QPrinter printer(QPrinter::HighResolution);
printer.setPageSize(QPrinter::A4);
printer.setOrientation(QPrinter::Portrait);
printer.setFullPage(true);
printer.setPageMargins(QMarginsF(0, 0, 0, 0));
printer.setOutputFormat(QPrinter::NativeFormat);
QPainter painter(&printer);
painter.setWindow(0, 0, 2100, 2970);
painter.setViewport(0, 0, printer.width(), printer.height());
QFont font(fontFamily, 0, weight, italic);
font.setPointSizeF(static_cast<float>(pixelSize) / printer.width() * 2100);
I think you're dividing where you should multiply and vice versa. Take a look with the units written out explicitly:
9 points * (1 inch / 72 points) * (printer.res pixels/inch)
multiplying the units, the numerator gets (points * inch * pixels) , denominator gets (points * inch) . Cancel out the like units and you get pixels in the numerator. So the calculation should be:
font.setPixelSize(9.0 / 72.0 * printer.resolution());
For your second problem,
QPen::setWidthF(w*printer.resolution()/25.4);
where w is your desired width in mm.
By making detailed observations (printing a long sentence) it's noticed that these gaps between characters are directly related to characters themselves, wide characters and Capital letters eat up the gap (J, s , r, c) while narrow characters leave more gap (i , l , t) .. this is just to say its not a random behavior.
In general this is known as kerning nicely explained here. To minimize this, Qt sets QFont kerning to true (default) QFont Kerning ... but the problem is that kerning is much dependent on the used font and pixel, and Qt kerning enabled sometimes has not effect as in this post, probably because setting Kerning to true
(Will apply kerning between adjacent glyphs. Note that OpenType GPOS
based kerning is currently not supported QRawFont::LayoutFlags
Which means that some font Ascent / Descent will still cause limitation how the gap is controlled.
Two solutions are considered below, the first is with still sticking to default painter font, yet you can stretch the characters to enforce reasonable gab:
font.setStretch(70); // value is experimental
I don't think this is a robust solution, unless one is limited to use specific font, while font itself is not important for invoice printing, the second attractive approach is to find out best font that meets requirements: render well with set resolution / little loss when drawn to PDF (from Qt) / and most important efficient for kerning!
"MS Gothic" (ttf) for example performs well for such setup, here how it performs (without stretch)
painter.setFont(QFont("MS Gothic"));
QFont font = painter.font();
int phys_w = printer.width();
font.setPointSizeF((9.0 / phys_w) * 210.0);
//font.setStretch(70);
painter.setFont(font);
The good thing about selecting a suitable font is that more control is possible (especially for invoice printing where small space is valuable).
For example the same can printed with less space by reducing the gap between letters:
font.setLetterSpacing(QFont::PercentageSpacing,65); // 65% gap of default
And one can go even for lower font size without loosing visual clearance.
sharing below some work while trying to clarify how printing small font size is affecting gap between letters. the idea is to draw each character separately inside QRect and at the same time draw equivalent QRectF on same area.
That's to see when font size is large QRectF are drawn fit next to each other, so also each character ... while when font size goes low the adjacent QRectFs will severely overlap and gaps between characters start to get disordered.
QPen pen=painter.pen();
pen.setWidth(0.1);
painter.setPen(pen);
QString td("John Doe");
auto spacer = font.pointSizeF(); // font size used to set width of QRect of each character.
spacer *=30.0; // large Rect width.
auto b = 35.0;
for (int i=0; i < td.length() ;i++ )
{
QRectF rectf(b+=spacer,47.0,4.0,4.0);
QRect rect(b, 47.0,4.0,4.0);
QString ch = td.at(i);
//painter.drawText(b+=spacer,46,ch);
painter.drawText(rect,Qt::AlignCenter,ch);
painter.drawRect(rectf);
}
painter.end();
Result for large font size:
Next make QRectF overlap:
spacer *=10.0;
Result, letters get less gaps and wide adjacent characters get narrow gap.

Is there a way to save a Graphics object in Codename One without without taking a screenshot?

The question is in the title. For example how can I save g in a file in the following snippet ?
public void paints(Graphics g, Image background, Image watermark, int width, int height) {
g.drawImage(background, 0, 0);
g.drawImage(watermark, 0, 0);
g.setColor(0xFF0000);
// Upper left corner
g.fillRect(0, 0, 10, 10);
// Lower right corner
g.setColor(0x00FF00);
g.fillRect(width - 10, height - 10, 10, 10);
g.setColor(0xFF0000);
Font f = Font.createTrueTypeFont("Geometos", "Geometos.ttf").derive(220, Font.STYLE_BOLD);
g.setFont(f);
// Draw a string right below the M from Mercedes on the car windscreen (measured in Gimp)
g.drawString("HelloWorld",
(int) (848 ),
(int) (610)
);
// NOW how can I save g in a file ?
}
The reaseon why I don't want to take a screenshot is because I want to keep the full resolution of g (eg : 2000 x 1500).
I would be so grateful to anyone that can tell me how to do that with Codename one. If not possible then it is already good to know it!
Cheers,
What you could do is to create an Image as buffer, get the graphics object from the image an do all your drawings operations on it. Then draw the whole image to the display and save it as a file:
int height = 2000;
int width = 1500;
float saveQuality = 0.7f;
// Create image as buffer
Image imageBuffer = Image.createImage(width, height, 0xffffff);
// Create graphics out of image object
Graphics imageGraphics = imageBuffer.getGraphics();
// Do your drawing operations on the graphics from the image
imageGraphics.drawWhaterver(...);
// Draw the complete image on your Graphics object g (the screen I guess)
g.drawImage(imageBuffer, w, h);
// Save the image with the ImageIO class
OutputStream os = Storage.getInstance().createOutputStream("storagefilename.png");
ImageIO.getImageIO().save(imageBuffer, os, ImageIO.FORMAT_PNG, saveQuality);
Note, that I have not tested it, but it should work like that.
Graphics is just a proxy to a surface, it has no knowledge or access to the underlying surface to which it is drawing and the reason for that is quite simple. It can draw to a hardware accelerated "surface" where there is physically no underlying image.
This is the case both on iOS and Android where the "screen" is natively drawn and has no buffer.

Scaling items and rendering

I am making a small game in C++11 with Qt. However, I am having some issues with scaling.
The background of my map is an image. Each pixel of that image represents a tile, on which a protagonist can walk and enemies/healthpacks can be.
To set the size of a tile, I calculat the maximum amount like so (where imageRows & imageCols is amount of pixels on x- and y-axis of the background image):
QRect rec = QApplication::desktop()->screenGeometry();
int maxRows = rec.height() / imageRows;
int maxCols = rec.width() / imageCols;
if(maxRows < maxCols){
pixSize = maxRows;
} else{
pixSize = maxCols;
}
Now that I have the size of a tile, I add the background-image to the scene (in GameScene ctor, extends from QGraphicsScene):
auto background = new QGraphicsPixmapItem();
background->setPixmap(QPixmap(":/images/map.png").scaledToWidth(imageCols * pixSize));
this->addItem(background);
Then for adding enemies (they extend from a QGraphicsPixMapItem):
Enemy *enemy = new Enemy();
enemy->setPixmap(QPixmap(":/images/enemy.png").scaledToWidth(pixSize));
scene->addItem(enemy);
This all works fine, except that on large maps images get scaled once (to a height of lets say 2 pixels), and when zooming in on that item it does not get more clear, but stays a big pixel. Here is an example: the left one is on a small map where pixSize is pretty big, the second one has a pixSize of pretty small.
So how should I solve this? In general having a pixSize based on the screen resolution is not really useful, since the QGrapicsScene is resized to fit the QGraphicsView it is in, so in the end the view still determines how big the pixels show on the screen.
MyGraphicsView w;
w.setScene(gameScene);
w.fitInView(gameScene->sceneRect(), Qt::KeepAspectRatio);
I think you might want to look at the chip example from Qt (link to Qt5 but also works for Qt4).
The thing that might help you is in the chip.cpp file:
in the paint method:
const qreal lod = option->levelOfDetailFromTransform(painter->worldTransform());
where painter is simply a QPainter and option is of type QStyleOptionGraphicsItem. This quantity gives you back a measure of the current zoom level of your QGraphicsView and thus as in the example you can adjust what is being drawn at which level, e.g.
if (lod < 0.2) {
if (lod < 0.125) {
painter->fillRect(QRectF(0, 0, 110, 70), fillColor);
return;
}
QBrush b = painter->brush();
painter->setBrush(fillColor);
painter->drawRect(13, 13, 97, 57);
painter->setBrush(b);
return;
}
[...]
if (lod >= 2) {
QFont font("Times", 10);
font.setStyleStrategy(QFont::ForceOutline);
painter->setFont(font);
painter->save();
painter->scale(0.1, 0.1);
painter->drawText(170, 180, QString("Model: VSC-2000 (Very Small Chip) at %1x%2").arg(x).arg(y));
painter->drawText(170, 200, QString("Serial number: DLWR-WEER-123L-ZZ33-SDSJ"));
painter->drawText(170, 220, QString("Manufacturer: Chip Manufacturer"));
painter->restore();
}
Does this help?

change the size of the font on button in gtk+2.0 in C++

I'm trying to change the size of the font on the button but not able to do it.
I tried using pango on the widget. For some reason it does not work. Is there an alternate way to do this?
#define font "Sans 30"
button = gtk_button_new_with_label("Button text");
gtk_widget_set_size_request(button, 150, 100);
gtk_widget_modify_bg (button, GTK_STATE_NORMAL, &blue_button);
PangoFontDescription *font_desc;
font_desc = pango_font_description_from_string (font);
gtk_widget_modify_font(GTK_WIDGET(button), font_desc);
Devhelp excerpt:
void pango_font_description_set_size (PangoFontDescription *desc,
gint size);
Sets the size field of a font description in fractional points. This
is mutually exclusive with pango_font_description_set_absolute_size().
desc :
a PangoFontDescription size :
the size of the font in points, scaled by PANGO_SCALE. (That is, a
size value of 10 * PANGO_SCALE is a 10 point font. The conversion
factor between points and device units depends on system configuration
and the output device. For screen display, a logical DPI of 96 is
common, in which case a 10 point font corresponds to a 10 * (96 / 72)
= 13.3 pixel font. Use pango_font_description_set_absolute_size() if you need a particular size in device units.

Insane CPU usage in QT 5.0

I'm having trouble using the QT framework, particularly with the paintEvent of QWidget. I have a QWidget set up, and am overriding the paintEvent of it. I need to render a bunch of rectangles (grid system), 51 by 19, leading to 969 rectangles being drawn. This is done in a for loop. Then I also need to draw an image on each on of these grids. The QWidget is added to a QMainWindow, which is shown.
This works nicely, but it's using up 47% of CPU per window open! And I want to allow the user to open multiple windows like this, likey having 3-4 open at a time, which puts the CPU close to 150%.
Why does this happen? Here is the paintEvent contents. The JNI calls don't cause the CPU usage, commenting them out doesn't lower it, but commenting out the p.fillRect and Renderer::renderString (which draws the image) lowers the CPU to about 5%.
// Background
QPainter p(this);
p.fillRect(0, 0, this->width(), this->height(), QBrush(QColor(0, 0, 0)));
// Lines
for (int y = 0; y < Global::terminalHeight; y++) {
// Line and color method ID
jmethodID lineid = Manager::jenv->GetMethodID(this->javaClass, "getLine", "(I)Ljava/lang/String;");
error();
jmethodID colorid = Manager::jenv->GetMethodID(this->javaClass, "getColorLine", "(I)Ljava/lang/String;");
error();
// Values
jstring jl = (jstring) Manager::jenv->CallObjectMethod(this->javaObject, lineid, jint(y));
error();
jstring cjl = (jstring) Manager::jenv->CallObjectMethod(this->javaObject, colorid, jint(y));
error();
// Convert to C values
const char *l = Manager::jenv->GetStringUTFChars(jl, 0);
const char *cl = Manager::jenv->GetStringUTFChars(cjl, 0);
QString line = QString(l);
QString color = QString(cl);
// Render line
for (int x = 0; x < Global::terminalWidth; x++) {
QColor bg = Renderer::colorForHex(color.mid(x + color.length() / 2, 1));
// Cell location on widget
int cellx = x * Global::cellWidth + Global::xoffset;
int celly = y * Global::cellHeight + Global::yoffset;
// Background
p.fillRect(cellx, celly, Global::cellWidth, Global::cellHeight, QBrush(bg));
// String
// Renders the image to the grid
Renderer::renderString(p, tc, text, cellx, celly);
}
// Release
Manager::jenv->ReleaseStringUTFChars(jl, l);
Manager::jenv->ReleaseStringUTFChars(cjl, cl);
}
The problem you're having is that every time you call a draw function on QPainter, it has an overhead in setting up the painter for what it needs to draw, especially when the pen and brush needs changing. As you're calling the function over 900 times, that's over 900 times that Painter needs to change its internal properties for rendering and would explain why commenting out the drawRect and drawString functions reduce the CPU usage.
To solve this problem you need to batch up all the same types of paint calls, where a type uses the same brush and pen. To do this you can use the class QPainterPath and add the objects that you need with functions such as addRect(..); ensuring you do this outside of the paint function!
If, for example, you were going to draw a chess board pattern, you would create two QPainterPath objects and add all the white squares to one and all the black to another. Then when it comes to drawing use the QPainter function drawPath. This would only require two calls to draw the entire board.
Of-course, if you need each square to be a different colour, then you're still going to have the same problem, in which case I suggest looking to generate an image of what you need and rendering that instead.