Мне нужно напечатать QGraphicsScene в реальном масштабе (дюйм / мм)

Я пытаюсь распечатать содержимое QGraphicsScene, Целевым принтером может быть что угодно - от обычных принтеров до специальных принтеров нестандартных размеров. Он должен печатать вещи в реальном размере (дюймы, мм....).
в QGraphicsScene Я использую предположение 72 ppi.

Я предположил, что:
1) рендеринг сцены на принтер будет делать это в зависимости от разрешения принтера, чтобы я получал элементы в реальном размере (дюймах / мм), аналогичном тому, что они показывают на экране.
2) Я могу установить размер бумаги для принтера на желаемый размер холста (который представляет собой прямоугольник на очень большой сцене), и ничего кроме этого не будет печатать
3) Я могу установить поля, и содержимое вне "фактического холста" не будет напечатано, включая то, что находится на полях.

Все мои предположения пока неверны:
1) для разных принтеров кажется, что рендеринг предназначен для максимальной подгонки (с использованием соотношения сторон), если я предлагаю нестандартный размер, близкий к размеру бумаги по умолчанию (или если я не установил размер бумаги);
Если я устанавливаю размер бумаги, который не является близким (например, 4x4 дюйма на принтере с размером по умолчанию "ПИСЬМО"), он просто печатает пустую страницу.
2-3) В случае, когда есть отпечаток, и принтер просто растягивает холст до полной страницы, все элементы, которые находятся за пределами области рисования, все еще печатаются.
Я попытался обрезать, либо на рисователе, либо установив целевой прямоугольник на рендере, и в результате получилось очень странное отсечение небольшого фрагмента сцены.

Я пробовал использовать HP LaserJet, Adobe PDF и некоторые нестандартные принтеры определенных размеров (например, 4x6 дюймов). Все они масштабируют сцену до максимального размера в зависимости от того, указываю ли я "Портрет" или "Пейзаж", и полностью игнорируем мой запрос размера бумаги или фактические размеры.

Вот небольшой пример программы, чтобы воспроизвести то, что я пытаюсь сделать.
Комментарии в коде показывают некоторые варианты, которые я пробовал.

#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QGraphicsEllipseItem>
#include <QPrinter>
#include <QPrintDialog>


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QGraphicsScene* s = new QGraphicsScene();
    s->setSceneRect(-500, -500, 1500, 1500);
    QGraphicsView* view = new QGraphicsView();
    view->setScene(s);
    view->show();

    int canvasSize = 288;    // 4 in
    QRectF canvasRect(0, 0, canvasSize, canvasSize);
    // this is to show actual scene
    QGraphicsRectItem* sss = new QGraphicsRectItem(canvasRect);
    sss->setBrush(Qt::blue);
    s->addItem(sss);
    // this item is partially outside top left
    QGraphicsEllipseItem* e1 = new QGraphicsEllipseItem(-50, -75, 100, 150);
    e1->setBrush(Qt::yellow);
    s->addItem(e1);
    // this item is partially outside center
    QGraphicsEllipseItem* e2 = new QGraphicsEllipseItem(100, 150, 250, 50);
    e2->setBrush(Qt::yellow);
    s->addItem(e2);
    // this item is partially outside right
    QGraphicsEllipseItem* e3 = new QGraphicsEllipseItem(200, 200, 75, 125);
    e3->setBrush(Qt::yellow);
    s->addItem(e3);

    QPrinter printer;
    // QPrinter printer(QPrinter::HighResolution);  // this makes no difference except it rotates the output, strange

    // without this just to use default printer, if you like
    QPrintDialog printDialog(&printer);
    if (printDialog.exec() != QDialog::Accepted)
        return 1;

    printer.setFullPage(false); // I see no diference between true and false

    // this results in empty page (or is ignored if my rect is 8 in)
    //printer.setPaperSize(canvasRect, QPrinter::Point);

    printer.setOrientation(QPrinter::Landscape);
    printer.setPageMargins(0, 0, 0, 0, QPrinter::Point);

    QPainter painter;

    if (painter.begin(&printer))
    {
//        painter.setClipRect(canvasRect);  // this creates a small clipping, only a tiny corner
        s->render(&painter, QRectF(), canvasRect, Qt::KeepAspectRatio);
        // doing this instead clips to a tiny rectangle also
//        s->render(&painter, canvasRect, canvasRect, Qt::KeepAspectRatio);
        painter.end();
    }

    return app.exec();
}

Выполнение:

QPrinter printer(QPrinter::HighResolution);
qreal resolutionFactor = printer.resolution() / 1200.;
...
painter.scale(resolutionFactor, resolutionFactor);

исправляет печать LaserJet (масштабирование, а не рисование вне реальной страницы), но приводит к крошечной, почти невидимой печати на принтере с разрешением 300 точек на дюйм.

Как я могу получить напечатанный вывод в реальном масштабе (чтобы я мог измерить дюймы / мм на бумаге и чтобы они были правильными)?

Также, как я могу получить вывод, который будет обрезан по фактическому прямоугольнику холста?

1 ответ

Решение

Это все действительно просто. render Метод делает только две вещи:

  1. Он отображается от исходного прямоугольника в единицах сцены до целевого прямоугольника в единицах устройства.
  2. Он рисует только внутри целевого прямоугольника.

Ваша ошибка заключалась в передаче нулевого целевого прямоугольника: тогда эффективного отсечения нет (оно обрезается до размера устройства), и вы также печатаете с неправильным масштабированием, если только ваша сцена не имеет точно такой же размер, как размер устройства.

Отображение DPI между единицами измерения устройства и дюймами определяется как QPrinter::resolutionс точки зрения DPI (количество устройств на дюйм).

Чтобы распечатать canvasRect в правильном масштабе, внутри и обрезать выбранный прямоугольник страницы, выполните следующее, где in 1 дюйм в единицах сцены (72.0f в твоем случае):

auto source = canvasRect;
auto scale = printer.resolution()/in;
auto page = printer.pageRect(QPrinter::DevicePixel);
auto target = QRectF(page.topLeft(), source.size()*scale);
target &= page; // clip target rect to page
qDebug() << page << scale << source << target;
scene.render(&painter, target, source);

Модули устройств принтера в Qt выглядят прямоугольными, но, возможно, это потому, что я не пробовал достаточно странные устройства. В случае, если они не были прямоугольными, вы могли бы вывести их из вывода pageRect:

qreal resolution(QPrinter & printer, Qt::Orientation orientation) {
  auto in = printer.pageRect(QPrinter::Inch);
  auto dev = printer.pageRect(QPrinter::DevicePixel);
  return (orientation == Qt::Horizontal) ? dev.width()/in.width()
         : dev.height()/in.height();
}
...
auto scaleX = resolution(printer, Qt::Horizontal);
auto scaleY = resolution(printer, Qt::Vertical);
auto target = QRectF(page.left(), page.top(),
                     source.width()*scaleX, source.height()*scaleY);
...

Полный пример приведен ниже. Вывод идентичен независимо от того, какова стоимость in, поскольку мы используем явную, не косметическую ручку для контуров фигур. Там нет причин для установки in к какому-либо конкретному значению, если ваши натуральные единицы измерения в дюймах, просто установите in=1.0f,

// https://github.com/KubaO/stackrun/tree/master/questions/scene-print-37708423
#include <QtWidgets>
#include <QtPrintSupport>

int main(int argc, char *argv[])
{
   QApplication app(argc, argv);
   QGraphicsScene scene;
   QGraphicsView view(&scene);

   auto in = 72.0f;
   auto pen = QPen(Qt::black, 0.01*in);
   QRectF canvasRect(0, 0, 4*in, 4*in);
   // this is to show actual scene
   QGraphicsRectItem sss(canvasRect);
   sss.setPen(pen);
   sss.setBrush(Qt::blue);
   scene.addItem(&sss);
   // this item is partially outside top left
   QGraphicsEllipseItem e1(-0.5*in, -0.5*in, 1*in, 1*in);
   e1.setPen(pen);
   e1.setBrush(Qt::yellow);
   scene.addItem(&e1);
   // this item is partially outside center
   QGraphicsEllipseItem e2(2*in, 2*in, 2.5*in, 1*in);
   e2.setPen(pen);
   e2.setBrush(Qt::yellow);
   scene.addItem(&e2);
   // this item is partially outside right
   QGraphicsEllipseItem e3(3.5*in, 3.5*in, 1*in, 1*in);
   e3.setPen(pen);
   e3.setBrush(Qt::yellow);
   scene.addItem(&e3);

   view.fitInView(scene.sceneRect(), Qt::KeepAspectRatio);
   view.show();

   QPrinter printer;
   QPrintDialog printDialog(&printer);
   QObject::connect(&printDialog, &QDialog::accepted, [&]{
      printer.setOrientation(QPrinter::Landscape);
      QPainter painter(&printer);

      auto source = canvasRect;
      auto scale = printer.resolution()/in;
      auto page = printer.pageRect(QPrinter::DevicePixel);
      auto target = QRectF(page.topLeft(), source.size()*scale);
      target &= page; // clip target rect to page
      qDebug() << page << scale << source << target;
      scene.render(&painter, target, source);
   });
   printDialog.show(); // modal on OS X thus must follow `connect` above
   return app.exec();
}
Другие вопросы по тегам