Setting custom paper size with QPrinter doesn't print correctly - qt

I need to be able to print from qt (the rendered contents of a QGraphicsScene, or a QImage), to scale, on normal printer, pdf, but also on custom printers, including roll fed.
It seems that anything that works for standard printers fails on custom printers, and the reverse.
I have made the printing work as expected on custom printers now (going back and forth between what works in the different printers).
I set the custom size required, and the preferred orientation, based on length/width ratio.
I open a print dialog (and even check the supply- the paper is set to desired size, and the orientation is set as expected)
print:
On custom printer, I get the correct size, and if the supply is smaller, the print clips as needed. margins are set correctly too.
On Pdf, I get a document of custom size as requested, printed correctly - but the orientation is not respected !!! (even though print dialog showed it correctly) - see image
On HP printer, I get a white page - nothing gets printed.
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QPrinter>
#include <QPrintDialog>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// some scene to print - show rectangle for easy measure
QGraphicsScene* s = new QGraphicsScene();
s->setSceneRect(-500, -500, 1500, 1500);
s->setBackgroundBrush(Qt::red);
QGraphicsView* view = new QGraphicsView();
view->setScene(s);
view->show();
qreal in = 72;
QRectF canvasRect(-0.1*in, -0.1*in, 6*in, 4*in);
qreal margins = 0.1*in;
QRectF actualCanvasRect = canvasRect.adjusted(margins,margins,-margins,-margins);
// this is to show actual scene
QGraphicsRectItem* contourItem = new QGraphicsRectItem(actualCanvasRect);
contourItem->setBrush(Qt::blue);
s->addItem(contourItem);
// an item partially on canvas (so I can check margins)
QGraphicsRectItem* item = new QGraphicsRectItem(-.5*in, -in, 2*in, 3*in);
item->setBrush(Qt::yellow);
s->addItem(item);
// actual printing:
// print the scene, to scale, using user margins, on given printer
QPrinter printer;
QPrinter::Orientation orient = (actualCanvasRect.width() >
actualCanvasRect.height() ?
QPrinter::Landscape : QPrinter::Portrait);
printer.setOrientation(orient);
printer.setPaperSize(canvasRect.size(), QPrinter::Point);
printer.setPageMargins(margins, margins, margins, margins, QPrinter::Point);
QPrintDialog printDialog(&printer);
if (printDialog.exec() != QDialog::Accepted)
{
qDebug("dialog canceled");
return 1;
}
QPainter painter;
if (! painter.begin(&printer))
{
qDebug("failed to open printer");
return 1;
}
// render the contents, clipped to printer page size, and scaled from point to device pixel
QRectF source = actualCanvasRect;
// convert target rect to DevicePixel and clip to page
QRectF page = printer.pageRect(QPrinter::DevicePixel);
qreal scale = printer.resolution()/in;
QRectF target = QRectF(page.topLeft(), source.size() * scale);
target &= page; // clip target rect to page
// clip source rect to page - without this, if printer paper is smaller I get unwanted scaling
source &= printer.pageRect(QPrinter::Point);
s->render(&painter, target, source);
painter.end();
return app.exec();
}
I don't understand why pdf creates a portrait page even though I clearly requested landscape (without changing the print dialog: see image). (the width and height are reversed, yet correct - Document Properties shows 4x6, and the page attempts to print correctly and to scale)
More important, I don't understand why a typical laser jet printer prints nothing - blank page - or sometimes for a simple canvas it scales to fit.
BUT if I change any properties in the print dialog from HP, anything irrelevant (like paper source, or paper type.... ), it prints correctly.
What am I doing wrong ?
(using Qt 4.7 and 5.5, must work on 4.7 - Windows, have yet to try Linux)

Related

OpenCV and Qt: Mat to QLabel QPixmap

I'm having some trouble with the following code. I'm attempting to set a Mat as a QLabel pixmap. When I call QLabel::setPixmap(), nothing happens! The label in the dialog box stays exactly the same, showing placeholder text. It has been properly promoted to MyLabel.
//include stuff above
MyLabel::MyLabel(QWidget *parent): QLabel(parent)
{
boats = cv::imread("C:/boats.jpg");
setPixmap(boats);
}
void MyLabel::setPixmap(cv::Mat image){
cv::Mat converted;
cv::cvtColor(image, converted, CV_BGR2RGB);
QImage result = QImage((const unsigned char*)(converted.data),
converted.cols, converted.rows, QImage::Format_RGB888);
QLabel::setPixmap(QPixmap::fromImage(result));
}
I have also tried setting the pixmap in the following way:
QLabel::setPixmap(QPixmap("C:/boats.jpg"));
But this produces the same effect. Any ideas?
You should set image to the interface. For instance, you can set like this way:
ui->label_15->setPixmap(QPixmap::fromImage(result));

Rendering a large QGraphicsScene on a QImage clips it off

I am creating some images rendering the contents of a QGraphicsScene.
My project requirement is that it should handle a canvas size of 10 ft by 8 inches. On screen, and scene size, that is 8640 x 576 pixels.
I can render it fine.
The thing is, the output images need to have 300 resolution.
That means, the rendered image will have a width of 36000, which is over 2^15 - 1 = 32767 pixels.....
The output is clipped - in the code below, I would get a QImage of correct expected size (36000) but the QGraphicsScene only renders to 32767 pixels.
That is confusing... I cannot explain the outcome - if the QImage limitations were 32767 pixels, then I should not be able to create one in the first place. But I checked and the QImage "sanity check" is much higher.
Once the image is created, I do not see anything in the code for rendering QGraphicsScene that would clip at any value....
This is a simple code that is trying to expose my problem.
It creates a QImage of required size, and fills with yellow (for control).
Then it renders a QGraphicsScene with blue background brush and a red rectangle close to the right margin.
If it works correctly, the result should be: an image of width 36000, blue with a tiny red rectangle at the far right.
But... as it is, the result is an image of width 36000, blue for the first 32766 pixels then yellow for the rest, no red rectangle.
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QPainter>
void printScene(QGraphicsScene* s, qreal ratio) {
qreal w = s->width() * ratio;
qreal h = s->height() * ratio;
QRectF target(0, 0, w, h);
QImage image = QImage(w, h, QImage::Format_ARGB32_Premultiplied);
image.fill(QColor(Qt::yellow).rgb());
QPainter painter;
painter.begin(&image);
s->render(&painter, target);
painter.end();
image.save("image.png");
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QGraphicsScene s;
s.setSceneRect(0, 0, 8640, 576);
s.setBackgroundBrush(Qt::blue);
QGraphicsView view(&s);
view.show();
QGraphicsRectItem* r = s.addRect(8530, 250, 100, 100);
r->setBrush(Qt::red);
qreal ratio = 300/72.;
printScene(&s, ratio);
return app.exec();
}
As seen in sample images, the QImage is created successfully, QGraphicsScene though only renders to 2^15 - 1... But I stepped through their code and I didn't see it stop....
(I also tried creating the original scene 36000 x something (and setting the ratio to 1), and it displays fine... it just won't render to QImage anything beyond 32767 pixels)
Am I missing some setting ? What could be the cause of the QGraphicsScene::render() to not render more ?
I would love to find out how I can render the size I want - width of 36000 pixels - or a reason why this is not possible.
I am running this in Windows 7, 32 bit Qt 5.5.1 or 4.7.4
I have found the reason for the clipping - and imagined 2 workarounds.
Why:
Stepping through the rendering code, the clip rect gets limited to 32767:
bool QRasterPaintEngine::setClipRectInDeviceCoords(const QRect &r, Qt::ClipOperation op)
{
Q_D(QRasterPaintEngine);
QRect clipRect = r & d->deviceRect;
...
}
Where deviceRect is set by
void QRasterPaintEnginePrivate::systemStateChanged()
{
deviceRectUnclipped = QRect(0, 0,
qMin(QT_RASTER_COORD_LIMIT, device->width()),
qMin(QT_RASTER_COORD_LIMIT, device->height()));
QRegion clippedDeviceRgn = systemClip & deviceRectUnclipped;
deviceRect = clippedDeviceRgn.boundingRect();
baseClip->setClipRegion(clippedDeviceRgn);
...
}
and
// This limitations comes from qgrayraster.c. Any higher and
// rasterization of shapes will produce incorrect results.
const int QT_RASTER_COORD_LIMIT = 32767;
Options:
1) Render to a max of 32767 and, if the target must be bigger, scale result. (should give slightly lower quality)
2) Create 2 images and combine them (I still need to figure that out but I think it is the better fix)

How do I get a QMenu to react to text size changes on High DPI diplays

The text in menubars and menues automatically change size when a window is moved from a 96 dpi screen to 192 dpi screen. I naively thought Qt would automatically resize menues when the menu text size changed but I'm obviously wrong here.
How can I get QMenuBar and QMenu to change size when the text changes size? (Specifically when the window is dragged to the 192 dpi screen)
Text size only change when I move the window to a 192 dpi screen. If the window is initially shown on the 192 screen it will draw the smaller 96 dpi text.
How do I ensure that the text is the correct size when the mainwindow opens on a 192 dpi screen?
I've tested playing around with the QT_DEVICE_PIXEL_RATIO env variable but this doesn't solve anything. I can force the menues to become larger by setting the value to 2, but I need them to change size depending on the screen in use.
And the application must be Per-Monitor DPI Aware on Windows so leaving it to the window manager auto scale is not an option.
I've also tested this with the Fusion style to rule out it being related to the native Windows style.
A trivial test case:
#include <QApplication>
#include <QMainWindow>
#include <QMenuBar>
#include <QStyle>
//#include <QStyleFactory>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//a.setStyle((QStyleFactory::create(("Fusion"))));
QMainWindow w;
QMenuBar *menuBar = w.menuBar();
QMenu *menuFile = menuBar->addMenu("File");
QMenu *menuEdit = menuBar->addMenu("Edit");
QMenu *menuCompany = menuBar->addMenu("&Company");
QMenu *menuArrange = menuBar->addMenu("Arrange");
// file menu
menuFile->addAction(a.style()->standardIcon(QStyle::SP_DirOpenIcon), "Open", nullptr, nullptr, QKeySequence::Open);
menuFile->addAction(a.style()->standardIcon(QStyle::SP_DriveFDIcon), "Save", nullptr, nullptr, QKeySequence::Save);
QAction* actionQuit = menuFile->addAction("Quit");
QObject::connect(actionQuit, &QAction::triggered, &w, &QMainWindow::close);
// edit menu
menuEdit->addAction(a.style()->standardIcon(QStyle::SP_ArrowLeft), "Undo", nullptr, nullptr, QKeySequence::Undo);
menuEdit->addAction(a.style()->standardIcon(QStyle::SP_ArrowRight), "Redo", nullptr, nullptr, QKeySequence::Redo);
// company menu
menuCompany->addAction(a.style()->standardIcon(QStyle::SP_DriveNetIcon), "DB Connect", nullptr, nullptr, QKeySequence(Qt::SHIFT + Qt::Key_Insert));
w.setCentralWidget(new QWidget);
w.show();
return a.exec();
}
From above images attached we can assume you deal with Windows.
How would I attempt to solve that? Let's make the app to react on WM_DPICHANGED. That is likely require you to become more familiar with SetProcessDPIAware, IsProcessDPIAware and other related API functions. And what Qt has to offer in this context as well.
Then, having DPI change intercepted we can take advantage of QWidget SaveGeometry / RestoreGeometry calls targeting that QMenu object. This approach should invalidate the control.
P.S. Qt Widgets in general are well adapted to dynamic monitor dimension changes and I agree with commenter that that is probably a bug.

QT change QImage alpha

I need to change the alpha of a QImage I have so it blends with the other QImages behind and infront of it. This needs to be toggled quickly on and off.
Previously I've had to recreate every single image and give them new colors with just a different alpha value. But now I want to retain that same original image instead of redrawing and painting it.
I'm trying to do it like this now:
QImage image;
unsigned int rgb;
for(int y=0;y<image.height();y++){
for(int x=0;x<image.width();x++){
rgb=image.pixel(x,y);
image.setPixel(x,y,qRgba(qRed(rgb),qGreen(rgb),qRed(rgb),120));
}
}
I'm getting some fairly unpredictable behavior. When I toggle the image sometimes I lose colors or the alpha isn't changed. And if the alpha did get changed when I toggle back (I hardcode the alpha 255 elsewhere instead of 120) it doesn't go back to normal.
This doesn't seem like the right way to do this anyways, it shouldn't be this difficult. It seems like there should be a single function call on an image to change the alpha but I haven't found one yet.
If you are using the QImage in QGraphicsView or in some other QWidget, you should look into this QGraphicsEffect:
http://qt-project.org/doc/qt-4.8/qgraphicsopacityeffect.html
http://doc-snapshot.qt-project.org/4.8/qwidget.html#setGraphicsEffect
http://doc-snapshot.qt-project.org/4.8/qgraphicsitem.html#setGraphicsEffect
If you are using a QLabel, I would try this out:
#include <QLabel>
#include <QPainter>
class TransparentQLabel : public QLabel
{
Q_OBJECT
public:
explicit TransparentQLabel() : QLabel() {}
~TransparentQLabel(){}
void setOpacity(const qreal & val)
{
if (this->pixmap() == null || this->pixmap().isNull())
return;
QPixmap result(this->pixmap()->size());
result.fill(Qt::transparent);
QPainter painter;
painter.begin(&result);
painter.setOpacity(val);
painter.drawPixmap(0, 0, *(this->pixmap()));
painter.end();
QLabel::setPixmap(result);
}
};
This next bit is only slightly related to your question, but it is nice to know. If you are layering outside of your QApplication onto the operating system, you need some things like this:
this->setWindowFlags( Qt::WindowStaysOnTopHint |
Qt::FramelessWindowHint | Qt::Tool);
this->setAttribute(Qt::WA_TranslucentBackground, true);
this->setAttribute (Qt::WA_TransparentForMouseEvents, true);
Here is an example of this stuff in action:
http://qt-project.org/wiki/QSplashScreen-Replacement-for-Semitransparent-Images
Hope that helps.

QTextBrowser - how to identify image from mouse click position

I'm using a QTextBrowser to display rich text including a number of images, each of them specified with a HTML <img> tag and added as resources using QTextDocument::addResource().
What I'd like to be able to do is, in a context menu handler (i.e. with a mouse click position available), identify the image that the click was over. It's possible to tell whether the click is over an image, because cursorForPosition(event->pos()).block().text() returns a string starting with Unicode 0xFFFC. Unfortunately the same string is returned for every image in the view.
It's possible to get all of the formats in use with QTextDocument::allFormats(), identify which of those are image formats, and get their image resource name. Unfortunately there seems to be no way to get their actual display position or bounding rectangle.
From the documentation:
Inline images are represented by an object replacement character (0xFFFC in Unicode) which has an associated QTextImageFormat. The image format specifies a name with setName() that is used to locate the image.
You can use charFormat().toImageFormat().name() on the cursor to extract the image's URL. Below is a self-contained example. There are two noteworthy details:
The cursor will sometimes point one character prior to the image. Thus the workaround; it seems necessary for both Qt 4.8.5 and 5.1.1.
The pop-up menus should be shown asynchronously so as not to block the rest of the application. The example code provided in the documentation is a source of bad user experience and should be considered an evil abomination. All widgets can automatically delete themselves when they get closed, so the menus won't leak. A QPointer is used only to demonstrate this fact. It tracks the menu's lifetime and nulls itself when the menu deletes itself.
#include <QApplication>
#include <QTextBrowser>
#include <QImage>
#include <QPainter>
#include <QMenu>
#include <QContextMenuEvent>
#include <QTextBlock>
#include <QPointer>
#include <QDebug>
class Browser : public QTextBrowser
{
QPointer<QMenu> m_menu;
protected:
void contextMenuEvent(QContextMenuEvent *ev) {
Q_ASSERT(m_menu.isNull()); // make sure the menus aren't leaking
m_menu = createStandardContextMenu();
QTextCursor cur = cursorForPosition(ev->pos());
QTextCharFormat fmt = cur.charFormat();
qDebug() << "position in block" << cur.positionInBlock()
<< "object type" << cur.charFormat().objectType();
if (fmt.objectType() == QTextFormat::NoObject) {
// workaround, sometimes the cursor will point one object to the left of the image
cur.movePosition(QTextCursor::NextCharacter);
fmt = cur.charFormat();
}
if (fmt.isImageFormat()) {
QTextImageFormat ifmt = fmt.toImageFormat();
m_menu->addAction(QString("Image URL: %1").arg(ifmt.name()));
}
m_menu->move(ev->globalPos());
m_menu->setAttribute(Qt::WA_DeleteOnClose); // the menu won't leak
m_menu->show(); // show the menu asynchronously so as not to block the application
}
};
void addImage(QTextDocument * doc, const QString & url) {
QImage img(100, 100, QImage::Format_ARGB32_Premultiplied);
img.fill(Qt::white);
QPainter p(&img);
p.drawRect(0, 0, 99, 99);
p.drawText(img.rect(), url);
doc->addResource(QTextDocument::ImageResource, QUrl(url), img);
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTextDocument doc;
Browser browser;
doc.setHtml("<img src=\"data://image1\"/><br/><img src=\"data://image2\"/>");
addImage(&doc, "data://image1");
addImage(&doc, "data://image2");
browser.show();
browser.setDocument(&doc);
return a.exec();
}

Resources