Fastest way to draw QGraphicItems on an "equivalent" QPixmap - c++

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);
}

Related

How to correctly subscribe the pixel value over the pixmap in the QGraphicsScene (as it is in OpenCV namedWindow)?

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);

How do I rotate photos in QPixmap?

Marked a needle on the speedometer.
// 이미지 출력
QPixmap pix("C:\\Users\\WORK002\\Desktop\\speedmeter.png");
QPixmap pix2("C:\\Users\\WORK002\\Desktop\\pointer.png");
QPixmap pointer = pix2.scaled(300, 300);
scene.addPixmap(pix);
scene.addPixmap(pointer);
ui-> graphicsView ->setScene(&scene);
ui-> graphicsView ->show();
I want rotate and reposition.
How can i do this?
You don't have to mess with the QPixmap, you can manipulate the QGraphicsPixmapItem returned by QGraphicsScene::addPixmap:
QGraphicsPixmapItem *pixmap_item = scene.addPixmap(pix);
To set the position, you can use QGraphicsItem::setPos.
To set the rotation, first set the transform origin point with QGraphicsItem::setTransformOriginPoint,(this will set the point around which your item will be rotated) and then set the rotation with QGraphicsItem::setRotation:
pixmap_item->setPos(50, 0);
pixmap_item->setTransformOriginPoint(pixmap_item->boundingRect().center());
pixmap_item->setRotation(90);
You will have to set the correct values yourself, but this should lead you in the right way.
You can look into QPixmap::transformed()
QPixmap QPixmap::transformed(const QTransform &transform, Qt::TransformationMode mode = Qt::FastTransformation) const
The specification can be given through the QTransform object:
rotate() for rotation
translate() for reposition
may this one can help:
//Please note this method takes 2 mseg to finish for a 20x20 pixmap.
QPixmap rotatedPixmap(m_pixOriginal.size());
rotatedPixmap.fill(QColor::fromRgb(0, 0, 0, 0)); //the new pixmap must be transparent.
QPainter* p = new QPainter(&rotatedPixmap);
QSize size = m_pxOriginal.size();
p->translate(size.height()/2,size.height()/2);
p->rotate(m_iAngle);
p->translate(-size.height()/2,-size.height()/2);
p->drawPixmap(0, 0, m_pxOriginal);
p->end();
delete p;
m_pxItem->setPixmap(rotatedPixmap);
copied from:
Read this forum thread

Replace a part of a QPixMap with a smaller QPixMap

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);
}

Proper data model for a 2D Tilemap (C++, Qt)

I made a small 2D level editor where you can create 2D tile based maps..however, the performance inside my application is really really bad. I am currently thinking to start all over again.
The Problem is, I currently use QGraphicsItem's to represent a single tile inside a QGraphicsScene. A tile has some properties..including an image. When a map is created, I create an item for each tile which draws an image for each tile..which basically is a lot of graphicitems and it slows down the whole application. This is the function that populates a map once it is created :
for(int i=0;i<map->m_rows;i++)
{
for(int j=0;j<map->m_cols;j++)
{
Tile* thetile=map->getAt(i,j);
if(thetile)
{
if(map->getType()==twoditor::RECTANGLETILE)
{
QGraphicsItem* item= new TileGraphicsItem(thetile);
m_scene->addItem(item);
}
else if(map->getType()==twoditor::HEXAGONTILE)
{
QGraphicsItem* item= new HexagonGraphicsItem(thetile);
m_scene->addItem(item);
}
}
}
}
This works for a map with 100x100 Tiles. But if i want to create even larger maps..the loading time is really unbearable..
Can someone give me advice for a better representation of a tile map? Are there other convenient ways to show a map and edit cells(tiles) inside it?
EDIT: TileGraphicItem paint function:
void TileGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget){
setZValue(0);
if(!m_thetile->getImage().isNull())
{
painter->drawImage(0,0,m_thetile->getImage());
}
QPainterPath circle_path;
QRect duwagrect(boundingRect().x(),boundingRect().y(),boundingRect().width(),boundingRect().height());
circle_path.addRect(duwagrect);
m_pen.setStyle(Qt::SolidLine);
m_pen.setColor(Qt::black);
m_pen.setWidth(1);
painter->setPen(m_pen);
painter->drawPath(circle_path);
if(m_thetile->getProperty()->getBlocks())
{
QPainterPath circle_path;
QRect duwagrect(boundingRect().x()+2,boundingRect().y()+2,boundingRect().width()-3,boundingRect().height()-3);
circle_path.addRect(duwagrect);
m_pen.setStyle(Qt::DotLine);
m_pen.setColor(Qt::red);
m_pen.setWidth(2);
painter->setPen(m_pen);
painter->drawPath(circle_path);
}
if(this->isSelected())
{
QPainterPath circle_path;
QRect duwagrect(boundingRect().x()+2,boundingRect().y()+2,boundingRect().width()-3,boundingRect().height()-3);
circle_path.addRect(duwagrect);
m_pen.setStyle(Qt::SolidLine);
m_pen.setColor(Qt::green);
m_pen.setWidth(3);
painter->setPen(m_pen);
painter->drawPath(circle_path);
}
if(option->state & QStyle::State_MouseOver)
{
QPainterPath circle_path;
QRect duwagrect(boundingRect().x()+2,boundingRect().y()+2,boundingRect().width()-3,boundingRect().height()-3);
circle_path.addRect(duwagrect);
m_pen.setStyle(Qt::SolidLine);
m_pen.setColor(Qt::cyan);
m_pen.setWidth(2);
painter->setPen(m_pen);
painter->drawPath(circle_path);
}
}
Problem is that you are showing everything even things not needed.
You should create only visible items (items in some visible region).
Another faster approach is to create custom QGraphicsItem which paints hole map, and paint only visible tiles (no tiles as sub items).

What is the fastest way to get QWidget pixel color under mouse?

I need to get the color of pixel under mouse, inside mouseMoveEvent of a QWidget (Breadboard). Currently I have this code->
void Breadboard::mouseMoveEvent(QMouseEvent *e)
{
QPixmap pixmap = QPixmap::grabWindow(winId());
QRgb color = pixmap.toImage().pixel(e->x(), e->y());
if (QColor(color) == terminalColor)
QMessageBox::information(this, "Ter", "minal");
}
Take a look at (scaled down) screenshot below-
When user moves his mouse on breadboard, the hole should get highlighted with some different color (like in red circle). And when the mouse exits, the previous color (grey) should be restored. So I need to do following steps-
Get color under mouse
According to color, floodfill the hole. (Different holes are distinguished using color)
On mouse out, restore the color. There would be wires going over holes, so I can't update the small rectangle (hole) only.
What is the fastest way of doing this? My attempt to extract color is not working i.e the Message box in my above code never displays. Moreover I doubt if my existing code is fast enough for my purpose. Remember, how fast you will be moving your mouse on breadboard.
Note - I was able to do this using wxWidgets framework. But due to some issues that project got stalled. And I am rewriting it using Qt now.
You are invited to look at code https://github.com/vinayak-garg/dic-sim
The "idiomatic" way of doing this in Qt is completely different from what you're describing. You'd use the Graphics View Framework for this type of thing.
Graphics View provides a surface for managing and interacting with a large number of custom-made 2D graphical items, and a view widget for visualizing the items, with support for zooming and rotation.
You'd define your own QGraphicsItem type for the "cells" in the breadboard that would react to hover enter/leave events by changing their color. The connections between the cells (wires, resistors, whatever) would also have their own graphics item types with the features you need for those.
Here's a quick and dirty example for you. It produces a 50x50 grid of green cells that become red when the mouse is over them.
#include <QtGui>
class MyRect: public QGraphicsRectItem
{
public:
MyRect(qreal x, qreal y, qreal w, qreal h)
: QGraphicsRectItem(x,y,w,h) {
setAcceptHoverEvents(true);
setBrush(Qt::green);
}
protected:
void hoverEnterEvent(QGraphicsSceneHoverEvent *) {
setBrush(Qt::red);
update();
}
void hoverLeaveEvent(QGraphicsSceneHoverEvent *) {
setBrush(Qt::green);
update();
}
};
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QGraphicsScene scene;
for (int i=0; i<50; i++)
for (int j=0; j<50; j++)
scene.addItem(new MyRect(10*i, 10*j, 8, 8));
QGraphicsView view(&scene);
view.show();
return app.exec();
}
You could modify the hover event handlers to talk to your "main window" or "controller" indicating what's currently under the mouse so you can update your caption, legend box or tool palette.
For best speed, render only the portion of the widget you're interested in into a QPaintDevice (like a QPixmap). Try something like this:
void Breadboard::mouseMoveEvent(QMouseEvent *e)
{
// Just 1 pixel.
QPixmap pixmap(1, 1);
// Target coordinates inside the pixmap where drawing should start.
QPoint targetPos(0, 0);
// Source area inside the widget that should be rendered.
QRegion sourceArea( /* use appropriate coordinates from the mouse event */ );
// Render it.
this->render(&pixmap, targetPos, sourceArea, /* look into what flags you need */);
// Do whatever else you need to extract the color from the 1 pixel pixmap.
}
Mat's answer is better if you're willing to refactor your application to use the graphics view API.