I'm new to Qt and Qt Graphics API.
I have a larger QPixMap and a smaller QPixMap. I need to replace a portion (a QRect) of the larger one with the smaller one.
How am I supposed to achieve this?
Thanks.
UPDATE
QPainter::drawPixmap() does not update the image represented by pImage->p_PixMap.
Code
class GraphicImage : public QObject,
public QGraphicsPixmapItem
{
Q_OBJECT
public:
GraphicImage(QPixmap* oImage,GraphiItemCtrl* pParent);
virtual ~GraphicImage(void);
QPixmap* p_PixMap;
};
- - - -
GraphicImage::GraphicImage(QPixmap* oImage,GraphiItemCtrl* pParent)
:QGraphicsPixmapItem(*oImage), p_Parent(pParent)
{
p_PixMap = oImage;
}
- - - -
void GraphiItemCtrl::SetImagePortion( QString sFileName, QRect rect, QPixmap pChildPixMap )
{
GraphicImage* pImage = map_CurrentImages[sFileName];
if ( !pImage )
return;
pChildPixMap.save("test.jpg");
QPixmap* pMap = pImage->p_PixMap;
QPainter pPainter(pMap);
pPainter.drawPixmap(rect, pChildPixMap);
qDebug() << rect.topLeft();
}
pChildPixMap.save("test.jpg"); saves the required portion of the image without an issue.
NOTE :
pImage is inherited from QObject and QGraphicsPixmapItem.
pMap is not NULL
The function you are looking for is:
void QPainter::drawPixmap(const QRect &rectangle, const QPixmap &pixmap)
It will draw the pixmap into a rectangle portion of the painter's target.
You may also want to use this one:
void QPainter::drawPixmap(const QRect &target, const QPixmap &pixmap, const QRect &source)
Which will draw a portion of the source into a portion of the target.
In both cases if the sizes mismatch the image will be scaled, so if you are getting poor results, you will additionally need to tweak the scaling method.
As established in this answer, setting setRenderHint(QPainter::SmoothPixmapTransform); by itself does not seem to produce optimal results. If you want the best quality you will need to manually scale() the pixmap and then draw it, which produces much better results than scaling it on the fly while painting.
Quick pseudocode:
QPainter painter(pixmap1);
painter.drawPixmap(QRect, pixmap2);
Take a look at the documentation here
You need to use a painter on the destination pixmap to draw the source pixmap in a given destination rectangle:
void draw(QPixmap &dst, const QRect &dstRect, const QPixmap &src) {
QPainter(dst).drawPixmap(dstRect, src);
}
If you're drawing multiple such pixmaps on one destination, you should hold on to the painter - it'd be wasteful to construct new painter over and over in a loop:
struct Replacement {
QRect dstRect;
QPixmap source;
};
void draw(QPixmap &dst, const QList<Replacement> &replacements) {
QPainter painter{dst};
for (auto & r : replacements)
painter.drawPixmap(r.dstRect, r.source);
}
Related
I am trying to implement the same functionality in my widget as it is in cv:: namedWindow.
The goal is to enable zooming and to make the overlay with the grid and the values of pixel's colors directly over the original pixmap. Here is the example: сv picture zoomed:
I inherited the QGraphicsView widget, added to QGraphicsScene the QGraphicsPixmapItem and reimplemented the QWheelEvent so that zooming in and out works correctly now. The problem starts with creating an overlay.
Instead of creating a pack of QGraphicsLineItems and adding them to the scene in order to make the grid, I inherit the QGraphicsRectItem and draw the whole grid on it.
void QGraphicsOverlayItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QPen pen(Qt::black);
pen.setWidthF(0.02);
painter->setPen(pen);
painter->setBrush(Qt::BrushStyle::NoBrush);
QVector<QLineF> crossLines = createCrossLines();
painter->drawLines(crossLines);
}
This works very fast. But when I try to drawText with the QPainter and set the QFont:: pointSizeF() as small as possible, it works incorrectly (symbols change their size from small to huge during zooming or even disappear at all). Nevertheless, the best result that I get this way is the following:
the QPainter's drawText() result:
QFont font(painter->font());
font.setPointSizeF(0.1);
font.setLetterSpacing(QFont::SpacingType::AbsoluteSpacing,0.01);
painter->setFont(font);
painter->drawText(432,195,"123");
The easiest way is to add to scene a lot of QGraphicsTextItems and scale them to correct size, but it is too slow.
So the question is how can I subscribe the pixel's color value in the QGraphicsScene directly over the QPixmapItem?
I finally watched through the openCV source code and found what I looked for.
The answer for me was the QTransform matrix. OpenCV developers show the image not by using the scene in the QGraphicsView, but actually painting the image directly on the viewport in the paintEvent.
The QTransform matrix is stored in the class and is passed to QPainter in the beginning of the paintEvent.
void DefaultViewPort::paintEvent(QPaintEvent *event)
{
QPainter painter(viewport());
painter.setWorldTransform(param_matrixWorld);
painter.drawImage(QRect(0,0,viewport()->width(),viewport()->height()),image2Draw,QRect(0,0,image2Draw.width(),image2Draw.height()));
If you know the ratio of the image's size to the widget's size, and you also know the scale of QTransform matrix used to paint the image, it is easy to calculate how much area does the single source pixel take on the screen:
qreal ratioX = width() / float(image2Draw.width());
qreal ratioY = height() / float(image2Draw.height());
double pixel_width = qtransform_matrixWorld.m11()*ratioX;
double pixel_height = qtransform_matrixWorld.m11()*ratioY;
If we know the pixel_height, we can just set the QFont::pixelSize like this:
QFont font = painter->font();
font.setPixelSize(pixel_height/5);
painter->setFont(font);
I have started to learn Qt, and try to improve my basic C++ skills.
In GraphicsScene, I try to draw a line by using the mouse (mouse events).
When I start drawing a line in GraphicsScene, a thin dashed line is drawn from the origin, where I clicked first to the current mouse position and moves with the mouse, before the second point is clicked. To erase it, I draw it in black. If I hover over already draw lines, you will see the black drawn lines on them. To undraw it without leaving marks, an XOR operation on GraphicsScene would come in handy, or if I could draw in a different layer and not touching the other layer could be handy. But I couldn't yet figure how to do it. The example is on https://github.com/JackBerkhout/QT_Draw001
In line.cpp is the function setLineP2(int x, int y), which draws and erases that thin dashed line.
Can anybody help me with this, please?
The major misconception is thinking of a QGraphicsScene as some sort of a bitmap: it's not! It is a collection of items that can render themselves, and a spatial index for them. In a scene, if you wish to delete/hide something, you must not overpaint it - instead simply delete/hide the item in question as desired. The scene will handle all the details - that's what it's for
You must forget about GDI-anything at this point. You're not painting on the raw DC here. Even when using raw GDI, you do not want to paint on the window's DC as that flickers, you should paint on a bitmap and blit the bitmap to the window.
For example, your eraseScene method adds a rectangle on top of the scene, wasting memory and resources as all the previous items are retained (you can iterate through them), whereas all it should do is to clear the scene (or its equivalent):
void MainWindow::eraseScreen(void)
{
[...]
scene->addRect(0, 0, width()+1000, height()+1000, pen, brush);
}
vs. the correct:
void MainWindow::eraseScreen(void)
{
scene->clear();
}
Below is a complete example that approximates what you presumably meant to do in your code. It is 120 lines long. It was somewhat hard to figure out what exactly you meant to do as your code is so convoluted - it's useful to describe the exact behavior in simple terms in the question.
The example uses QPainterPath to keep a list of MoveTo and LineTo elements that a QPainterPathItem renders. It also uses a QGraphicsLineItem to display the transient line.
The MyScene::PathUpdater is used to enclose the context where a path is modified, and ensure that proper pre- and post-conditions are maintained. Namely:
Since QPainterPath is implicitly shared, you should clear the path held by QGraphicsPathItem to avoid an unnecessary implicit copy. That's the precondition necessary before modifying m_path.
After m_path has been modified, the path item must be updated, as well as a new status emitted.
The following other points are worth noting:
Holding the members by value leads to a notable absence of any memory management code (!) - the compiler does it all for us. You won't find a single new or delete anywhere. They are not necessary, and we're paying no additional cost for not doing this manually. Modern C++ should look exactly like this.
The clear split between the display MainWindow and MyScene. The MainWindow knows nothing about the specifics of MyScene, and vice-versa. The code within main acts as an adapter between the two.
The leveraging of C++11.
Succinct style necessary for SO test cases and examples: for learning it's best to keep it all in one file to easily see all the parts of the code. It's only 120 lines, vs. more than twice that if split across files. Our brains leverage the locality of reference. By splitting the code you're making it harder for yourself to comprehend.
See also
Another demo of interactive item creation.
A more advanced example of status notifications.
// https://github.com/KubaO/stackoverflown/tree/master/questions/scene-polygon-7727656
#include <QtWidgets>
class MainWindow : public QWidget
{
Q_OBJECT
QGridLayout m_layout{this};
QPushButton m_new{"New"};
QPushButton m_erase{"Erase All"};
QLabel m_label;
QGraphicsView m_view;
public:
MainWindow() {
m_layout.addWidget(&m_new, 0, 0);
m_layout.addWidget(&m_erase, 0, 1);
m_layout.addWidget(&m_label, 0, 2);
m_layout.addWidget(&m_view, 1, 0, 1, 3);
m_view.setBackgroundBrush(Qt::black);
m_view.setAlignment(Qt::AlignBottom | Qt::AlignLeft);
m_view.scale(1, -1);
connect(&m_new, &QPushButton::clicked, this, &MainWindow::newItem);
connect(&m_erase, &QPushButton::clicked, this, &MainWindow::clearScene);
}
void setScene(QGraphicsScene * scene) {
m_view.setScene(scene);
}
Q_SIGNAL void newItem();
Q_SIGNAL void clearScene();
Q_SLOT void setText(const QString & text) { m_label.setText(text); }
};
class MyScene : public QGraphicsScene {
Q_OBJECT
public:
struct Status {
int paths;
int elements;
};
private:
bool m_newItem = {};
Status m_status = {0, 0};
QPainterPath m_path;
QGraphicsPathItem m_pathItem;
QGraphicsLineItem m_lineItem;
struct PathUpdater {
Q_DISABLE_COPY(PathUpdater)
MyScene & s;
PathUpdater(MyScene & scene) : s(scene) {
s.m_pathItem.setPath({}); // avoid a copy-on-write
}
~PathUpdater() {
s.m_pathItem.setPath(s.m_path);
s.m_status = {0, s.m_path.elementCount()};
for (auto i = 0; i < s.m_status.elements; ++i) {
auto element = s.m_path.elementAt(i);
if (element.type == QPainterPath::MoveToElement)
s.m_status.paths++;
}
emit s.statusChanged(s.m_status);
}
};
void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
PathUpdater updater(*this);
auto pos = event->scenePos();
m_lineItem.setLine(0, 0, pos.x(), pos.y());
m_lineItem.setVisible(true);
if (m_path.elementCount() == 0 || m_newItem)
m_path.moveTo(pos);
m_path.lineTo(pos.x()+1,pos.y()+1); // otherwise lineTo is a NOP
m_newItem = {};
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
PathUpdater updater(*this);
auto pos = event->scenePos();
m_lineItem.setLine(0, 0, pos.x(), pos.y());
m_path.setElementPositionAt(m_path.elementCount()-1, pos.x(), pos.y());
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *) override {
m_lineItem.setVisible(false);
}
public:
MyScene() {
addItem(&m_pathItem);
addItem(&m_lineItem);
m_pathItem.setPen({Qt::red});
m_pathItem.setBrush(Qt::NoBrush);
m_lineItem.setPen({Qt::white});
m_lineItem.setVisible(false);
}
Q_SLOT void clear() {
PathUpdater updater(*this);
m_path = {};
}
Q_SLOT void newItem() {
m_newItem = true;
}
Q_SIGNAL void statusChanged(const MyScene::Status &);
Status status() const { return m_status; }
};
int main(int argc, char *argv[])
{
using Q = QObject;
QApplication app{argc, argv};
MainWindow w;
MyScene scene;
w.setMinimumSize(600, 600);
w.setScene(&scene);
Q::connect(&w, &MainWindow::clearScene, &scene, &MyScene::clear);
Q::connect(&w, &MainWindow::newItem, &scene, &MyScene::newItem);
auto onStatus = [&](const MyScene::Status & s){
w.setText(QStringLiteral("Paths: %1 Elements: %2").arg(s.paths).arg(s.elements));
};
Q::connect(&scene, &MyScene::statusChanged, onStatus);
onStatus(scene.status());
w.show();
return app.exec();
}
#include "main.moc"
I'm trying to create customObject (rectangle and it inherit from QGraphicsItem) that will be painted on scene with ceratin text(stored in attribute), but when I scale it - i wish to keep same size of text. Here is my over. paint function:
void CustomRectangle::paint(QPainter *painter, const QStyleOptionGraphicsItem *options, QWidget *widget)
{
QColor currentColor = get_ColorByCurrentState();
QRectF rect = boundingRect();
QPen pen(currentColor, Own_LineWidith);
painter->setPen(pen);
painter->drawRect(rect);
QRectF rect_text(rect.x(), rect.y(),100,100);
painter->drawText(rect_text,this->getText() );
}
and my two scaling functions:
void CustomObject::scaleUp()
{
scale(ScaleFactor_X,ScaleFactor_Y);
}
void CustomObject::scaleDown()
{
scale(1/ScaleFactor_X,1/ScaleFactor_Y);
}
But text still keep scaling along with rectangle.
EDIT 1
I tried adding it another way, i nfucntion that creates and adds my rectangle to scene (here - named "newObject"), but result is still the same.
QGraphicsTextItem* GTI = new QGraphicsTextItem(newObject->toStringForScene(), newObject);
I'm beginign to think that I shoud create each text object as separeted object and save it different list. Ofcours, i would have to update it then, whenever it's object moved.
Try this:
QGraphicsTextItem* gti = new QgraphicsTextItem("text");
gti->setFont(QFont("Arial", 18));
// this is important
gti->setFlag(QGraphicsTextItem::ItemIgnoresTransformations, true);
scene->addItem(gti);
The QGraphicsItem::ItemIgnoresTransformations flag prevents your graphics item to be scaled when you scale your view (QGraphicsView).
That means that you need a separated item for rendering text. But it can be a child item of your rectangle item.
I resolved this with QGraphicsTextItem's poitner as class's attribute.
QGraphicsTextItem* GTI;
I initialzie it in constructor:
GTI_Description = new QGraphicsTextItem(this->toStringForScene());
and then I call function to updated it's X and Y:
void updateTextPosition()
{
GTI->setX( this->x() );
GTI->setY( this->y() );
}
and to add it to the scene:
addTextToScene(DragScene* _scene)
{
updateDescriptionPosition();
_scene->addItem(GTI_GTI);
_scene->update();
}
Then i just call updateTextPosition() whenerver I change positions (in my mouseRelease event's handler).
I'm looking for the most effective way to size a QGraphicsItem based on the length of a given QString, so that the text is always contained within the QGraphicsItem's boundaries. The idea is to keep the QGraphicsItem as small as possible, while still containing the text at a legible size. Wrapping onto multiple lines at a certain width threshold would be ideal as well. For example,
TestModule::TestModule(QGraphicsItem *parent, QString name) : QGraphicsPolygonItem(parent)
{
modName = name;
// what would be the best way to set these values?
qreal w = 80.0;
qreal h = 80.0;
QVector<QPointF> points = { QPointF(0.0, 0.0),
QPointF(w, 0.0),
QPointF(w, h),
QPointF(0.0, h) };
baseShape = QPolygonF(points);
setPolygon(baseShape);
}
void TestModule::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QBrush *brush = new QBrush(Qt::gray, Qt::SolidPattern);
painter->setBrush(*brush);
painter->drawPolygon(baseShape);
painter->drawText(QPointF(0.0, 40.0), modName);
}
what code could I add to the constructor to make my requirement work? Setting the width based on the total length of the string, making assumptions for how much pixel space each character takes up is the most obvious solution, but I'm looking for something a little more elegant. Any ideas? Thank you in advance for any help.
The QFontMetrics class has a function called boundingRect which takes the string that you're wanting to print and returns the QRect for the string, based on the QFont that you used to initalise QFontMetrics.
If you want to wrap, then you'd need to work out the maximum number of words in your string that will allow boundingRect to return a QRect that fits within your QGraphicsItem's boundingRect.
Take a look to QFontMetrics
You can ask your widget for the font
And check this snippet from QFontMetrics docs
QFont font("times", 24);
QFontMetrics fm(font);
int pixelsWide = fm.width("What's the width of this text?");
int pixelsHigh = fm.height();
Edit: As Merlin said in comment, use
QRect QFontMetrics::boundingRect ( const QString & text ) const
So:
int pixelsWide = fm.boundingRect("What's the width of this text?").width();
I want to draw colored tiles as background for a QGraphicsscene and provide pan and zoom functionality for the scene using a QGraphicsView. First I used QGraphicItems to draw each tile. Since I have many tiles this was quite a performance problem when panning or zooming but since I do not need to modify any part of the tiles afterwards I switched to generating a QPixmap using the following code:
void plotGrid(){
Plotable::GraphicItems items;
append(items,mParticleFilter.createGridGraphics());
append(items,mParticleFilter.getRoi().mRectangle.createGraphics(greenPen()));
scaleItems(items,1.0,-1.0);
QGraphicsScene scene;
showItemsOnScene(items,&scene);
QRectF boundingRect = scene.itemsBoundingRect();
double cScale = ceil(1920.0/boundingRect.size().width());
QSize size(boundingRect.size().toSize()*cScale);
QPixmap pixmap(size);
pixmap.fill(Qt::transparent);
QPainter p(&pixmap);
//p.setRenderHint(QPainter::Antialiasing);
scene.render(&p);
p.end();
QGraphicsPixmapItem* item = new QGraphicsPixmapItem(pixmap);
item->setOffset(boundingRect.topLeft()*cScale);
item->scale(1/cScale,1/cScale);
mpView->showOnScene(item);
}
While this solves the zoom and pan problem, the time to generate the pixmap introduces some significant delay, probably because I first create a scene and then render it. Is there a faster way of producing a QPixmap on the fly starting from QGraphicItems ?
Just for completeness an image of the tiles:
So I finally got at least past using an intermediary scene. The following code only relies on a QPainter for rendering the pixmap. My main problem was to get all transformations right. Otherwise it is quite straight forward....
This version halves the processing time to 500ms in my scenario. 450ms are spent on painting the items. If someone has more suggestions for more improvement it would be most welcome (cahnging the resolution does not help much by the way)
void SceneWidget::showAsPixmap(Plotable::GraphicItems const& items){
QRectF boundingRect;
boostForeach(QGraphicsItem* pItem,items) {
boundingRect = boundingRect.united(pItem->boundingRect());
}
QSize size(boundingRect.size().toSize());
double const cMaxRes =1920;
double const scale = cMaxRes/boundingRect.size().width();
QPixmap pixmap(size*scale);
pixmap.fill(Qt::transparent);
QPainter p(&pixmap);
//p.setCompositionMode( QPainter::CompositionMode_Source );
p.translate(-boundingRect.topLeft()*scale);
p.scale(scale,scale);
QStyleOptionGraphicsItem opt;
boostForeach(QGraphicsItem* item,items) {
item->paint(&p, &opt, 0);
}
p.end();
QGraphicsPixmapItem* item = new QGraphicsPixmapItem(pixmap);
item->scale(1.0/scale,-1.0/scale);
item->setOffset(boundingRect.topLeft()*scale);
showOnScene(item);
}