Visual C++, 2D Drawing Canvas Class with Zoom, Snapshot and Capture - c++

I occasionally develop scientific simulation software and want to visualize results on a 2D view.
Is there any ready made (preferably open source) Win32 class which creates a drawing canvas with
zoom,
snapshot and
possibly video capture capability?
I need limited API which includes fast pixel drawing, draw bmp from memory buffer and possibly texts.

Look at Qt together with OpenGl. Here is some code I frequently use, that might be of help for you
//piview.h
#include <QGraphicsView>
#include <QGraphicsScene>
class PiView : public QGraphicsView{
Q_OBJECT
public:
explicit PiView(QGraphicsScene * =0, QWidget *parent = 0);
void enableSmoothRendering();
void enableOpenGl();
void fit();
void renderToFile(QString const& path)const;
private:
//void mouseMoveEvent(QMouseEvent *event);
virtual void mousePressEvent(QMouseEvent *event);
virtual void wheelEvent(QWheelEvent *event);
virtual void resizeEvent(QResizeEvent * event );
signals:
void pointRightClicked(QPointF) const;
void pointLeftClicked(QPointF) const;
};
PiView* createMyPiView(QGraphicsScene* pScene);
//piview.cpp
#include "piview.h"
#ifdef OPENGL
#include <QtOpenGL>
#include <QGLWidget>
#endif
#include <QMouseEvent>
#include <QWheelEvent>
#include <qdebug.h>
PiView::PiView(QGraphicsScene* scene, QWidget *parent) : QGraphicsView(scene , parent){
this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
this->setDragMode(QGraphicsView::ScrollHandDrag);
}
void PiView::enableSmoothRendering(){
this->setRenderHints(QPainter::Antialiasing|QPainter::TextAntialiasing|QPainter::SmoothPixmapTransform);
}
#ifdef OPENGL
void PiView::enableOpenGl(){
QGLFormat fmt = QGLFormat::defaultFormat();
fmt.setSampleBuffers(true);
fmt.setDoubleBuffer(true);
//fmt.setSamples(256);
fmt.setDirectRendering(true);
//qDebug() << "SampleBuffers:" << fmt.sampleBuffers() << fmt.samples();
this->setViewport(new QGLWidget(fmt));
this->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
this->setCacheMode(QGraphicsView::CacheBackground);
//this->scene()->setItemIndexMethod(QGraphicsScene::NoIndex);
}
#else
void PiView::enableOpenGl(){
qDebug() << "Opengl was not enabled, enable it using opengl as qmake parameter";
}
#endif
void PiView::mousePressEvent(QMouseEvent *event){
if (event->button()==Qt::RightButton) {
emit pointRightClicked(mapToScene(event->pos()));
}
else if (event->button()==Qt::LeftButton) {
emit pointLeftClicked(mapToScene(event->pos()));
}
QGraphicsView::mousePressEvent(event);
}
//Zooming using the mousewheel
void PiView::wheelEvent(QWheelEvent *event){
double scaleFactor = 1.15; //How fast we zoom
if (event->orientation()==Qt::Vertical){
if(event->delta() > 0) {
//Zoom in
scale(scaleFactor, scaleFactor);
} else {
//Zooming out
scale(1.0 / scaleFactor, 1.0 / scaleFactor);
}
}
else if (event->orientation()==Qt::Horizontal) {
if(event->delta() > 0) {
scroll(10,0);
}
else {
scroll(-10,0);
}
}
}
void PiView::resizeEvent(QResizeEvent *event){
this->fit();
QGraphicsView::resizeEvent(event);
}
void PiView::fit(){
this->fitInView(this->sceneRect(),Qt::KeepAspectRatio);
}
PiView* createMyPiView(QGraphicsScene* pScene){
PiView* view = new PiView(pScene);
view->enableOpenGl();
view->enableSmoothRendering();
return view;
}
For Snapshots take a look at the following:
void SceneInteractor::saveSelection(){
if (mpScene->selectedItems().isEmpty()) return;
QList<QGraphicsItem*> items(mpScene->selectedItems());
QRect sourceRect;
boostForeach(QGraphicsItem* item, items) {
sourceRect = sourceRect.united(item->sceneBoundingRect().toRect());
}
QImage img(sourceRect.size(),QImage::Format_ARGB32_Premultiplied);
img.fill(Qt::transparent);
QPainter p(&img);
p.setRenderHint(QPainter::Antialiasing);
QGraphicsView::CacheMode cache = mpView->cacheMode();
mpView->setCacheMode(QGraphicsView::CacheNone);
mpView->scene()->clearSelection();
mpView->scene()->render(&p, QRectF(), sourceRect);
p.end();
QString path = QFileDialog::getSaveFileName(0,tr("Choose Image-File Destination"));
img.save(path,"PNG",100);
mpView->setCacheMode(cache);
}

Related

How to get global geometry of child widget

I have label inside custom frame.
I try to movement of MainWindow (all aplication) on mouse event:
void settingslogolabel::mouseMoveEvent(QMouseEvent *ev)
{
if ((ev->buttons() & Qt::LeftButton) && firstCIsNotNull){
window()->move( mapToGlobal(ev->pos() - m_dragPosition - this->geometry().topLeft()));
}
}
BUT! this->geometry() returns local geometry.
So, how can I get global geometry of child?
What I try to make:
When you press mouse and move - all application should move as your cursor move, until you up mouse button. I want to make this interactive for label.
Full code:
HPP:
#ifndef SETTINGSLOGOLABEL_H
#define SETTINGSLOGOLABEL_H
#include <QLabel>
#include <QWidget>
#include <QMouseEvent>
class settingslogolabel : public QLabel
{
Q_OBJECT
public:
explicit settingslogolabel(QWidget *parent = 0);
void mouseMoveEvent(QMouseEvent *ev);
void mousePressEvent(QMouseEvent *ev);
void mouseReleaseEvent(QMouseEvent *ev);
private:
QPoint m_dragPosition;
bool firstCIsNotNull = true;
private:
};
#endif // SETTINGSLOGOLABEL_H
CPP:
#include "settingslogolabel.hpp"
settingslogolabel::settingslogolabel(QWidget *parent) :
QLabel(parent)
{
}
void settingslogolabel::mouseMoveEvent(QMouseEvent *ev)
{
if ((ev->buttons() & Qt::LeftButton) && firstCIsNotNull){
window()->move( mapToGlobal(ev->pos() - m_dragPosition - this->geometry().topLeft()));
}
}
void settingslogolabel::mousePressEvent(QMouseEvent *ev)
{
if (ev->button() == Qt::LeftButton) {
m_dragPosition = ev->pos();
firstCIsNotNull = true;
}
}
void settingslogolabel::mouseReleaseEvent(QMouseEvent *ev)
{
if (ev->button() == Qt::LeftButton) {
firstCIsNotNull = false;
}
}
Not sure I fully understand the problem but, the global coordinates of the top left corner of a widget can be found -- from within that widget's member functions -- using...
mapToGlobal(QPoint(0, 0));
Similarly, the global geometry would be...
rect().translated(mapToGlobal(QPoint(0, 0)));
Edit:
If the aim is to allow dragging of the top level window then your mouseMoveEvent implementation should be something like (untested)...
void settingslogolabel::mouseMoveEvent (QMouseEvent *ev)
{
if ((ev->buttons() & Qt::LeftButton) && firstCIsNotNull) {
auto delta = ev->pos() - m_dragPosition;
window()->move(window()->pos() + delta);
}
}

Keyboard input in custom QQuickPaintedItem

I defined my custom QQuickPaintedItem in such a way:
class NiceItem : public QQuickPaintedItem
{
...
public:
...
void keyPressEvent(QKeyEvent * event);
void paint(QPainter *painter);
...
};
Here you have keyPressEventcode:
void NiceItem::keyPressEvent(QKeyEvent * event)
{
if(event->key() == Qt::Key_Left)
playerX--;
else if(event->key() == Qt::Key_Right)
playerX++;
else if(event->key() == Qt::Key_Up)
playerY--;
else if(event->key() == Qt::Key_Down)
playerY++;
}
Here's paint code:
void NiceItem::paint(QPainter *painter)
{
QPen pen(m_color, 2);
painter->setPen(pen);
QRectF rectangle(playerX, playerY, 80.0, 60.0);
painter->drawRect(rectangle);
update();
}
As you can see code is really simple. Rectangle is successfully drawn on the screen, but pressing arrows does nothing. Is this a wrong method? If so, how can I process the keyboard input?
For an Item to receive the mouse event, it must meet the following:
Have the flag QQuickItem::ItemIsFocusScope enabled.
Having focus, this can be done in C++ with setFocus(true) or in QML: focus: true.
On the other hand, if your objective is to draw a rectangle on the window, then you have an error in the concept of what coordinates are used for the painting, the painting uses the local coordinates and not the window or screen. So instead of creating new attributes you should only use position(), x(), y() (and their setters) to modify the geometry since these elements are with respect to the parent's coordinates.
#ifndef NICEITEM_H
#define NICEITEM_H
#include <QQuickPaintedItem>
class NiceItem : public QQuickPaintedItem
{
Q_OBJECT
public:
NiceItem(QQuickItem *parent=nullptr);
void paint(QPainter *painter);
protected:
void keyPressEvent(QKeyEvent *event);
private:
QColor m_color;
};
#endif // NICEITEM_H
#include "niceitem.h"
#include <QPainter>
#include <QPen>
NiceItem::NiceItem(QQuickItem *parent):
QQuickPaintedItem(parent), m_color(QColor("red"))
{
setFlag(QQuickItem::ItemIsFocusScope, true);
setFocus(true);
setSize(QSizeF(80.0, 60.0));
}
void NiceItem::paint(QPainter *painter)
{
QPen pen(m_color, 2);
painter->setPen(pen);
painter->drawRect(boundingRect());
}
void NiceItem::keyPressEvent(QKeyEvent *event)
{
QPointF delta;
if(event->key() == Qt::Key_Left)
delta = QPointF(-1, 0);
else if(event->key() == Qt::Key_Right)
delta = QPointF(+1, 0);
else if(event->key() == Qt::Key_Up)
delta = QPointF(0, -1);
else if(event->key() == Qt::Key_Down)
delta = QPointF(0, +1);
setPosition(position() + delta);
}

How to draw rectangle on custom video widget t in QT?

I want to select an area on a custom video widget and draw rectangle on selected area.
So far I can select an area with QRubberband but I am having trouble with drawing the rectangle after releasing left click.
Whenever I click-drag then release to draw rectangle it gives this error:
QBackingStore::endPaint() called with active painter on backingstore paint device
The program has unexpectedly finished.
Here is my code:
myvideoobject.h
#ifndef MYVIDEOOBJECT_H
#define MYVIDEOOBJECT_H
#include <QObject>
#include <QVideoWidget>
#include <QRubberBand>
#include <QPainter>
#include <QPen>
#include <QPaintEvent>
#include <QRect>
#include <QMouseEvent>
#include <QDebug>
class MyVideoObject : public QVideoWidget
{
Q_OBJECT
public:
explicit MyVideoObject(QWidget *parent = 0);
void mouseMoveEvent(QMouseEvent *ev);
void mousePressEvent(QMouseEvent *ev);
void mouseReleaseEvent(QMouseEvent *ev);
void paintEvent(QPaintEvent *ev);
private:
QRubberBand* rubberBand;
QPainter* painter;
//QRect *rectangle;
QPoint origin;
QPoint endPoint;
};
#endif // MYVIDEOOBJECT_H
myvideoobject.cpp
#include "myvideoobject.h"
MyVideoObject::MyVideoObject(QWidget* parent) :
QVideoWidget(parent)
{
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
rubberBand->setGeometry(0,0,50,50);//ileride silebilrisin
}
void MyVideoObject::mouseMoveEvent(QMouseEvent *ev)
{
rubberBand->setGeometry(QRect(origin,ev->pos()).normalized());
}
void MyVideoObject::mousePressEvent(QMouseEvent *ev)
{
origin = ev->pos();
if(!rubberBand)
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
rubberBand->setGeometry(QRect(origin,QSize()));
rubberBand->show();
}
void MyVideoObject::mouseReleaseEvent(QMouseEvent *ev)
{
rubberBand->hide();
endPoint = ev->pos();
painter->begin(this);
painter->drawRect(QRect(origin,endPoint));
}
void MyVideoObject::paintEvent(QPaintEvent *ev)
{
QRect rect = ev->rect();
painter = new QPainter(this);
painter->setRenderHint(QPainter::Antialiasing);
painter->setPen(Qt::black);
painter->drawText(rect,Qt::AlignCenter,"Data");
painter->drawRect(rect);
//painter->setPen(Qt::red);
}
I didn't add mainwindow.cpp and mainwindow.h cuz there isn't much code in those other than selecting video with openfiledialog.
When you create a pointer: QPainter *painter, this can point to any memory since it has garbage. so when you do painter->begin(this) you are accessing uninitialized memory, that's why you get that error. On the other hand in a QWidget such as QVideoWidget should only be painted in the method paintEvent, the strategy is to have variables that save the state of what you want to paint, for example the QRect, and call update to paint it.
myvideoobject.h
#ifndef MYVIDEOOBJECT_H
#define MYVIDEOOBJECT_H
#include <QVideoWidget>
class QRubberBand;
class MyVideoObject : public QVideoWidget
{
public:
MyVideoObject(QWidget *parent = nullptr);
protected:
void mouseMoveEvent(QMouseEvent *ev);
void mousePressEvent(QMouseEvent *ev);
void mouseReleaseEvent(QMouseEvent *ev);
void paintEvent(QPaintEvent *ev);
private:
QRubberBand *rubberBand;
QPoint origin;
QRect rect;
};
#endif // MYVIDEOOBJECT_H
myvideoobject.cpp
#include "myvideoobject.h"
#include <QMouseEvent>
#include <QPainter>
#include <QRubberBand>
MyVideoObject::MyVideoObject(QWidget *parent):
QVideoWidget(parent),
rubberBand(nullptr){}
void MyVideoObject::mousePressEvent(QMouseEvent *ev)
{
origin = ev->pos();
if(!rubberBand)
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
rubberBand->setGeometry(QRect(origin,QSize()));
rubberBand->show();
QVideoWidget::mousePressEvent(ev);
}
void MyVideoObject::mouseMoveEvent(QMouseEvent *ev)
{
rubberBand->setGeometry(QRect(origin,ev->pos()).normalized());
QVideoWidget::mouseMoveEvent(ev);
}
void MyVideoObject::mouseReleaseEvent(QMouseEvent *ev)
{
rect = rubberBand->geometry();
update();
QVideoWidget::mouseReleaseEvent(ev);
}
void MyVideoObject::paintEvent(QPaintEvent *ev)
{
QVideoWidget::paintEvent(ev);
QPainter painter(this);
painter.save();
painter.setBrush(Qt::red);
if(!rect.isNull())
painter.drawRect(rect);
painter.restore();
}

How to remove cropped rect from QImage/QLabel?

I did sub-classing to include mouse click function. Here, a rectangle can be chosen by mousePressEvent, mouseMoveEvent and mouseReleaseEvent. When I am trying to chose another rectangle, my previous rectangle is not being removed. It is still displaying with my previous drawn rectangle, which I don't want to display. I want to chose and display only one rectangle. I meant when I press again to chose another rectange, the previous one should be removed.
I included here my subclass named mouse_crop
mouse_crop.h is as follows
#ifndef MOUSE_CROP_H
#define MOUSE_CROP_H
#include <QMainWindow>
#include <QObject>
#include <QWidget>
#include <QMouseEvent>
#include <QLabel>
#include <QRubberBand>
class mouse_crop : public QLabel
{
Q_OBJECT
public:
mouse_crop(QWidget *parent=0);
QRubberBand *rubberBand;
QPoint origin, ending;
protected:
void mousePressEvent(QMouseEvent *ev);
void mouseMoveEvent(QMouseEvent *ev);
void mouseReleaseEvent(QMouseEvent *ev);
signals:
void sendMousePosition(QPoint&);
void sendMouseEnding(QPoint&);
};
#endif // MOUSE_CROP_H`
And mouse_crop.cpp is as follows
#include "mouse_crop.h"
mouse_crop::mouse_crop(QWidget *parent):QLabel (parent)
{
}
void mouse_crop::mousePressEvent(QMouseEvent *ev)
{
origin = ev->pos();
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
if(ev->button()== Qt::LeftButton || ev->button()== Qt::RightButton)
{
rubberBand->show();
emit sendMousePosition(origin);
}
}
void mouse_crop::mouseMoveEvent(QMouseEvent *ev)
{
rubberBand->setGeometry(QRect(origin, ev->pos()).normalized());
}
void mouse_crop::mouseReleaseEvent(QMouseEvent *ev)
{
ending = ev->globalPos();
if(ev->button()== Qt::LeftButton || ev->button()== Qt::RightButton)
{
emit sendMouseEnding(ending);
}
}
Can any one tell me how to solve this? Thanks in advance.
The problem is caused because every time you press the mouse you are creating a new QRubberBand, what you must do is create only a QRubberBand, hide it and show it when necessary.
mouse_crop::mouse_crop(QWidget *parent)
: QLabel(parent)
{
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
rubberBand->hide();
}
void mouse_crop::mousePressEvent(QMouseEvent *ev)
{
origin = ev->pos();
rubberBand->setGeometry(QRect(origin, origin));
if(ev->button()== Qt::LeftButton || ev->button()== Qt::RightButton)
{
rubberBand->show();
emit sendMousePosition(origin);
}
}
void mouse_crop::mouseMoveEvent(QMouseEvent *ev)
{
rubberBand->setGeometry(QRect(origin, ev->pos()).normalized());
}
void mouse_crop::mouseReleaseEvent(QMouseEvent *ev)
{
ending = ev->globalPos();
if(ev->button()== Qt::LeftButton || ev->button()== Qt::RightButton)
{
emit sendMouseEnding(ending);
}
}

How can I use QtConcurrent::Run and QThread?

My code is about images. It can be open image, changing quality, resize, showing image size... For resize and change quaity, I use slider and when I change sliders values, image is read from buffer again and again. Because of this, freezing is happening in my program. So, to solve the problem I want to use QtConcurrent::run and QThread or QFuture. Actually I have no idea how can I use them and I would like to your help to solve my problem.
Here is my code. The functions are that to cause freezing:
void MainWindow::reprocess_image(int scale, int quality) {
rescale_image(scale);
requality_image(quality);
show_pixmap();
}
void MainWindow::rescale_image(int scale) {
int w = m_image->width();
int h = m_image->height();
int new_w = (w * scale)/100;
int new_h = (h * scale)/100;
ui->lbl_width->setText(QString::number(new_w));
ui->lbl_height->setText(QString::number(new_h));
m_pixmap = QPixmap::fromImage(
m_image->scaled(new_w, new_h, Qt::KeepAspectRatio, Qt::FastTransformation));
ui->lbl_scale->setText(QString::number(scale));
}
void MainWindow::requality_image(int quality) {
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
m_pixmap.save(&buffer, "WEBP", quality);
auto l_size_b = buffer.size();
double l_size_kb = buffer.size() / 1024.00;
ui->lbl_size->setText(QString::number(l_size_kb));
QImage image;
image.loadFromData(ba);
m_pixmap = QPixmap::fromImage(image);
ui->lbl_quality->setText(QString::number(quality));
double comp_p = 100.0 * l_size_b / m_orig_size;
if(comp_p>100) {
ui->lbl_compression->setText(QString::number(comp_p));
QLabel* m_label = ui->lbl_size;
m_label->setStyleSheet("QLabel { background-color : red; color : black; }");
}
else if(comp_p<=100) {
ui->lbl_compression->setText(QString::number(comp_p));
QLabel* m_label = ui->lbl_size;
m_label->setStyleSheet("QLabel { background-color : rgba(0,0,0,0%); color : black; }");
}
}
void MainWindow::on_sld_quality_valueChanged(int value) {
reprocess_image(ui->sld_scale->value(), value);
}
void MainWindow::on_sld_scale_valueChanged(int scale) {
reprocess_image(scale, ui->sld_quality->value());
}
And this is my mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QGraphicsPixmapItem>
QT_FORWARD_DECLARE_CLASS(QGraphicsScene)
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
protected:
virtual void showEvent(QShowEvent *e) override;
private slots:
void on_openButton_clicked();
void on_sld_quality_valueChanged(int value);
void on_sld_scale_valueChanged(int value);
void on_saveButton_clicked();
private:
void reprocess_image(int scale, int quality);
void rescale_image(int);
void requality_image(int);
void show_pixmap();
void change_size();
Ui::MainWindow *ui;
QPixmap m_pixmap;
QImage *m_image;
qint64 m_orig_size;
QGraphicsScene *m_scene;
};
#endif // MAINWINDOW_H
How can I integrate QtConcurrent::run(), QThread and QFuture to my code?
The whole point of QtConcurrent::run is that you're not managing your own threads. So there's nothing to integrate. To get notified about when code submitted to QtConcurrent::run has finished you can use QFutureWatcher or emit a signal from the callable.