I am using Qt to draw a spectrogram like this:
.
I also want to be able to select regions of the graph and edit them, as well as scrolling and zooming around.
I am considering QGraphicsView class but not sure about its performance. As I know, the objects in QGraphicsView are stored individually and drawing a huge amount of dots might impact performance.
What Qt classes should I use to achieve this?
Definitely do not use the QGraphicsItem for every dot/mark. Good approach would be to generate a QPixmap representing your spectrogram and than place this pixmap into the QGraphicsScene as a single item (QGraphicsPixmapItem can be used for that).
To draw on the QPixmap use the QPainter. Maybe a little example will be useful:
const int spectrWidth = 1000;
const int spectrHeight = 500;
QPixmap spectrPixmap(spectrWidth, spectrHeight);
QPainter p(&spectrPixmap);
for (int ir = 0; ir < spectrHeight; ir++)
{
for (int ic = 0; ic < spectrWidth; ic++)
{
double data = getDataForCoordinates(ic, ir); // your function
QColor color = getColorForData(data); // your function
p.setPen(color);
p.drawPoint(ic, ir);
}
}
The getDataForCoordinates() and getColorForData() are just example functions demonstrating how it could work. You have probably different way how to obtain data and it's color.
EDIT
But if you do not need a zoom/pan functionality than even easier would be just to paint directly on the QWidget in the QWidget::paintEvent() and not using the QGraphicsView/QGraphicScene at all.
I would suggest you to use either QCustomPlot or Qwt for this purpose.
Both have very good documentation.
If you choose to work with Qwt take a look at QwtPlotSpectrogram Class.
QCustomPlot is external library, if you are looking for something native to QT then have a look at QPainter class.
Related
I'm working on a Qt 5 mac application and I'm looking for a method to smoothly scale and render a fractional section of a QPixmap as defined by a QRectF.
What I want to do actually is exactly what void QPainter::drawPixmap(const QRectF &targetRect, const QPixmap &pixmap, const QRectF &sourceRect) is supposed to do:
QRectF sourceRect = ...;
QRectF targetRect = ...;
painter.setRenderHints(QPainter::SmoothPixmapTransform | QPainter::Antialiasing);
painter.drawPixmap(targetRect, pixmap, sourceRect);
However, as mentioned in this thread and this bug report, QPainter::SmoothPixmapTransform is ignored and this method does not work.
Short of writing my own efficient bilinear scaling method from scratch (which I am not inclined to do), is there any other method I can use to accomplish the same thing?
QPixmap::scaled() only allows scaling to integer dimensions, and provides no way to crop out a section defined by a QRectF, so that's a non-starter.
Just tossing out ideas here, but is it possible to somehow use OpenGL to render the portion of the image I want at the right scale and then render it back into a QImage or QPixmap? This kind of scaling is one of the most basic things OpenGL can do very easily and quickly, but I don't have any experience using OpenGL in a Qt desktop app so far so I'm not sure if this is possible.
I'm developing a painter-like application and i want my application to have multiple layers for different drawings. For that purpose i have an array, that contains QPixmaps with transparent background and i have a function that merges two QPixmaps (draws one on another). It's done like so:
void MeasuresWidget::MergePixmaps(QPixmap source, QPixmap target)//draw source on target
{
QPainter painter(&target);
painter.drawPixmap(target.rect(),source,source.rect());
painter.end();
imageLabel->setPixmap(target);
}
I'm for 100% sure that array of pixmaps (AllLayers array) contains all the drawing i want. Now i want to consistently merge all the drawings with the original image. Here's how i'm trying to achieve this:
void MeasuresWidget::on_actionAct_triggered()
{
ForMerging = &OriginalImage;
for(int i=0;i<5;i++)
MergePixmaps(AllLayers[i], *ForMerging);
}
where ForMerging is temporary QPixmap object for, well, merging, and OriginalImage is, undoubtedly, QPixMap, that contains original image.
Again, i'm 100% sure, that all layers contain it's image on transparent background. The problem i'm facing is that in result original image is merged only with the last drawing, i.e. with AllLayers[4]. If i make i to run from 0 to 2(not including), for example, the result will be original image merged only with AllLayers[1]. I've struggled with this problem for a period of time and have no idea what might be wrong so i'm seeking for any help possible.
Try merge all QPixmap at the same call to MergePixmaps. For this change your Source variable at MergePixmap function to your AllLayers object like this:
void MeasuresWidget::MergePixmaps(AllLayers *source, QPixmap target)//draw source on target
{
QPainter painter(&target);
for(int i = 0; i < source->lenght();i++){
painter.drawPixmap(target.rect(),source->at(i),source->at(i).rect());
}
painter.end();
imageLabel->setPixmap(target);
}
I am making use of a QGraphicsScene and I am adding regular widgets (QLineEdit, QComboBox, etc.) to it via implicitly created QGraphicsProxyWidget objects:
m_pLineEdit = new QLineEdit("", 0);
m_pProxy = m_pGraphicsScene->addWidget(m_pLineEdit);
I am currently searching for a way to later retrieve those widgets from the scene again for processing, but am not able to find one.
I tried the following approaches already:
Since I cannot pass the graphics scene as parent to the widget constructor, retrieving the widgets via m_pGraphicsScene->findChildren(QLineEdit*) does not work, since there is no direct relation.
The graphics scene does have a QGraphicsSceneBspTreeIndex child, but that is not part of the official Qt API and therefore relying on it cannot be the way to go.
Bottom-line: How can I get all the QGraphicsProxyWidget objects from a Qt graphics scene? Can this be done in the Qt standard or do I have to subclass QGraphicsScene and try to manage the widgets myself?
Bottom-line: How can I get all the QGraphicsProxyWidget objects from a Qt graphics scene?
Get the list of all the items in the scene via scene->items() , then check if they're of the right class:
// Horrible C++98 code which doesn't even feature algorithms
QList<QGraphicsItem *> items = scene->items();
foreach (QGraphicsItem *item, items) {
QGraphicsProxyWidget *w;
if (w = qgraphicsitem_cast<QGraphicsProxyWidget *>(item)) {
use(w);
}
}
However, I'd like to stress that you should really keep track of the items you put in the scene. (At least, the ones you're interested in using afterwards). Walking over the scene and retrieving the items like this seems very fragile, and it's a signal of poor code quality and poor design. Note that you have the proxy returned by the addWidget call, just save it somewhere.
Just after posting the question I by chance found a solution in the Qt source code. The widget proxies are treated as regular QGraphicsItem internally and can be casted via qgraphicsitem_cast:
QList<QGraphicsItem*> graphicsItemList = m_pGraphicsScene->items();
foreach(QGraphicsItem* pGraphicsItems, graphicsItemList)
{
QGraphicsProxyWidget* pProxy = qgraphicsitem_cast<QGraphicsProxyWidget*>(pGraphicsItems);
if(pProxy)
{
QLineEdit* pLineEdit = qobject_cast<QLineEdit*>(pProxy->widget());
if(pLineEdit)
// do stuff
}
}
If someone knows a simpler/faster method, I'd be happy to hear about it. Until then I will use the approach outlined above.
I'm programming my bachelor's thesis application and there will be three different types of drawings. I need to render/draw/paint fractal structures made by:
Iterated function systems (draw line or simple path, make copy of the drawing (or part of it), do a few transformations with the copy and draw it iteratively)
Escape time algorithm (go through each pixel of the canvas, calculate its color and color the pixel in)
Elliot waves samples (path through given points - time sequence graph; graph will be made by many points and it won't fit on the screen, so I'll need some simple control of move (two buttons with click event are good enough))
Now, my question is, can you please recommend some Qt method, which suits my purposes (the easiest one, if it's be possible)?
I found these ones:
Qt Graphics View Framework (I think, this is too "heavy")
Draw into the pixmap
Inherit widget and override paintEvent (as it is showed in the Basic Drawing Example of Qt)
Qt Canvas of Qt Quick (but I don't know anything about it)
Should I choose some of these options or anything else?
Thank you very much, you'll be very helpful.
Re 1. QPainter on anything (a widget, a pixmap, a printer,...) is a simple choice.
Re 2. Work on pixels in the QImage, then draw the image on a painter.
Re 3. Painting on a widget is sufficient. You can base your widget on QAbstractScrollArea.
The biggest point of the Graphics View Framework is interactivity with items in the scene. It makes life much easier then, and should be used to one's advantage.
If your view is non-interactive, then the only benefit would be the binary space partitioning index that culls the amount of work needed to do partial updates or updates when zoomed in. Unless you allow zooming in/panning, or do partial changes, it's pointless, since Qt windows are double-buffered and you essentially never do partial painting. For partial changes, the changed items need to be reindexed unless their geometry remains constant.
With panning/zooming, you should only use the graphics view if you have no easy way of iterating a subset of items to be drawn. The "hard" but generic way of doing it is to have a BSP index, and the Graphics View system provides this. I think in your case it should be easy to iterate over items/primitives that are within a given scene rectangle.
For drawing using QPainter, it doesn't matter really what you draw on, it's a minor detail. You can factor out your drawing into a class that holds the data that needs to be drawn, for example:
class IRenderable {
protected:
/// Implementation of rendering.
virtual void renderImpl(QPainter & painter, QRect target) = 0;
public:
/// Draws all data (or the current view of it)
/// on the \a target rectangle of the \a painter.
void render(QPainter & painter, QRect target) {
renderImpl(painter, target);
}
};
class IteratedFunctionSystem : public IRenderable {
... // members describing the IFS etc.
/// Draws the entire IFS on the \a target rectangle of the \a painter.
void renderImpl(QPainter & painter, QRect target) Q_DECL_OVERRIDE;
public:
...
};
You can then use it in a generic widget:
class RenderableVisualizer : public QWidget {
QSharedPointer<IRenderable> m_renderable;
void paintEvent(QPaintEvent * ev) {
QPainter painter(this);
m_renderable->render(painter, rect());
}
public:
RenderableVisualizer(
QSharedPointer<IRenderable> renderable, QWidget * parent = 0
) : QWidget(parent), m_renderable(renderable)
{}
};
This approach could be extended to add an option to RenderableVisualizer to have a local backing store and render to it from a separate thread. It'd provide smoother GUI operation if the rendering were to be lengthy.
See my answer here for some recent alternatives. I'd recommend using Qt Quick with QQuickPaintedItem, as it offers the familiar QPainter API and it's better to do the types of operations you're describing in C++.
For information on the QPainter API, see Painting Examples and for basic drawing, the Basic Drawing Example.
I have a QML2 document which is significantly larger than the display on which it's displayed with a QQuickView.
In QML1 and QtDeclarative it was possible to use QGraphicsView::fitInView to scale the whole scene (including correct MouseEvent mapping, etc).
Is there something similar for QML2 I just didn't find yet?
I came up with a solution that somehow behaves like QGraphicsView::fitInView(aRect, Qt::KeepAspectRatio).
void MyQuickView::fitInView(const QRectF & newRect)
{
QSizeF newSize = newRect.size();
qreal horizontalScale = size().width() / newSize.width();
qreal verticalScale = size().height() / newSize.height();
// You might want to use another origin
rootObject()->setTransformOrigin(QQuickItem::TopLeft);
rootObject()->setSize(newSize);
rootObject()->setScale(qMin(horizontalScale, verticalScale));
}
This does mostly the job, although I haven't thoroughly tested all of my old Qt4.8 code.