I am learning Qt5 and I use Qt 5.9.1. Now I have a problem: how to keep the graphics in the center of main window even when the size of main window changes?
Last year I learned MFC in my class and the teacher told us that in order to make the graphics always stay in the window, we should do as followings:
Let the origin of viewport be the center of client area;
Let the size of viewport be the size of client area;
Let the origin of window be the center of graphics' bounding rectangle;
Let the size of window be the size of graphics' bounding rectangle .
So I do the same thing in Qt5:
// main.cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QtGuiApplication1 w;
// get the size and center of client area
// and then pass the values to QtGuiApplication
int width = QApplication::desktop()->availableGeometry().width();
int height = QApplication::desktop()->availableGeometry().height();
QPoint center = QApplication::desktop()->availableGeometry().center();
w.setViewport(center,width,height);
w.show();
return a.exec();
}
// GtGuiApplication.cpp
void QtGuiApplication1::paintEvent(QPaintEvent *)
{
QPainter painter(this);
// set the viewport
painter.setViewport(centerOfViewport.x(),centerOfViewport.y(), widthOfViewport, heightOfViewport);
static const QPointF points[4] = {
QPointF(10.0, 10.0),
QPointF(10.0, 80.0),
QPointF(50.0, 80.0),
QPointF(10.0, 10.0)
};
// set the window
painter.setWindow(30,45,40,70);
painter.drawPolyline(points, 4);
}
However all these things didn't work. Before I set the viewport and window:
And after I did the setting:
I do not understand what you indicate in the line, maybe it is fulfilled within MFC, but the rules are not accepted without analyzing them, you have to understand them and it seems that you want to apply them without understanding them correctly.
According to what you point out, you want the polygon to always be centered within the window, and in your code you use the size of the window which does not make sense since the window can be anywhere on the screen, from there we go badly.
If you want the center of the polygon to be the center of the window, then you must calculate both points considering in the first reference source the topleff of the rectangle bordering the polygon, and in the second the rectangle of the window, if we subtract both positions we obtain what must be moved by the painter so that both points coincide.
void QtGuiApplication1::paintEvent(QPaintEvent *)
{
QPainter painter(this);
static const QVector<QPointF> points = {
QPointF(10.0, 10.0),
QPointF(10.0, 80.0),
QPointF(50.0, 80.0),
QPointF(10.0, 10.0)
};
QPainterPath path;
path.addPolygon(QPolygonF(points));
QPointF center_path = path.boundingRect().center();
painter.translate(rect().center()-center_path);
painter.drawPath(path);
}
Related
My goal is to create a simple video player QWidget that allows for some overlayed graphics (think subtitles or similar).
I started with the naive approach, which was to use QVideoWidget with another set of QWidget on top (my overlays). Unfortunately this does not work because Qt does not allow widgets with transparent background to render on top of video. The background shows as black instead of the actual video.
Next idea is to use QGraphicsScene et al. which is supposed to allow this kind of compositing, so I create a dead simple setup like this:
// The widget we will use as viewport
auto viewport= new QWidget();
//Set an easily recognizable bg color for debugging
palette.setColor(QPalette::Window, Qt::green);
viewport->setPalette(palette);
viewport->setAutoFillBackground(true);
// Our scene
auto mScene=new QGraphicsScene(this);
// The video
auto mVideoItem = new QGraphicsVideoItem();
mVideoItem->setPos(0,0);
myVideoSource.setVideoOutput(mVideoItem); // ... not shown: setting up of the video source
mScene->addItem(mVideoItem);
// Yellow line starting at 0,0 for debugging
auto line=new QGraphicsLineItem (0,0,100,100);
line->setPos(0,0);
line->setPen(QPen(Qt::yellow, 2));
mScene->addItem(line);
// A Text string
auto text=new QGraphicsTextItem("Its Wednesday my dudes", mVideoItem);
text->setPos(10, 10);
// Our view
mView=new QGraphicsView;
mView->setScene(mScene);
mView->setViewport(viewport);
viewport->show()
Now this looks promising because I can see compositing works; the line and text render flawlessly on top of the video. However the video is positioned in a seemingly random place in the widget. (see screensdhot)
At this point I have tried every conceivable and inconceivable combination of
mVideoItem->setSize();
mVideoItem->setOffset();
mScene->setSceneRect();
mView->fitInView();
mView->ensureVisible();
mView->centerOn()
Trying to fill the viewport widget with the video item but nothing seems logical at all. Instead of centering the content, it seems to fly around the screen in logic defying ways and I have given up. I put my code in the viewport widget's resizeEvent and use the viewport widget's size() as the base.
So my question is; How can I fill viewport widget with video item on resize?
I dont think QGraphicsVideoItem is good for this task.
You can implement QAbstractVideoSurface that receives QVideoFrames and feeds them to QWidget that converts them to QImage, scales and draws them in paintEvent. Since you control paintEvent you can draw anything over your video, and get "fill viewport" feature for free.
Gist:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Surface surface;
Widget widget(surface);
widget.show();
QMediaPlayer player;
player.setVideoOutput(&surface);
player.setMedia(QMediaContent(QUrl("path/to/media")));
player.play();
return a.exec();
}
bool Surface::present(const QVideoFrame &frame)
{
mFrame = frame;
emit frameReceived();
return true;
}
Widget::Widget(Surface &surface, QWidget *parent)
: QWidget{parent}, mSurface(surface)
{
connect(&mSurface,SIGNAL(frameReceived()),this,SLOT(update()));
}
void Widget::paintEvent(QPaintEvent *event)
{
QVideoFrame frame = mSurface.frame();
if (frame.map(QAbstractVideoBuffer::ReadOnly)) {
QPainter painter(this);
int imageWidth = mSurface.imageSize().width();
int imageHeight = mSurface.imageSize().height();
auto image = QImage(frame.bits(),
imageWidth,
imageHeight,
mSurface.imageFormat());
double scale1 = (double) width() / imageWidth;
double scale2 = (double) height() / imageHeight;
double scale = std::min(scale1, scale2);
QTransform transform;
transform.translate(width() / 2.0, height() / 2.0);
transform.scale(scale, scale);
transform.translate(-imageWidth / 2.0, -imageHeight / 2.0);
painter.setTransform(transform);
painter.drawImage(QPoint(0,0), image);
painter.setTransform(QTransform());
painter.setFont(QFont("Arial", 20));
int fontHeight = painter.fontMetrics().height();
int ypos = height() - (height() - imageHeight * scale) / 2 - fontHeight;
QRectF textRect(QPoint(0, ypos), QSize(width(), fontHeight));
QTextOption opt(Qt::AlignCenter);
painter.setPen(Qt::blue);
painter.drawText(textRect, "Subtitles sample", opt);
frame.unmap();
}
}
Full source: draw-over-video
Based on customvideosurface example from Qt.
I would like to create a QGraphicsRectItem and display its name with a QGraphicsSimpleTextItem. I want the size of the text to be unaffected by the zoom. I also want the position of the text to be centered on the QGraphicsRectItem.
Here is my attempt so far :
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QPen>
#include <QWheelEvent>
#include <cmath>
#include <QDebug>
class MainView : public QGraphicsView {
public:
MainView(QGraphicsScene *scene) : QGraphicsView(scene) { setBackgroundBrush(QBrush(QColor(255, 255, 255)));}
protected:
void wheelEvent(QWheelEvent *event) {
double scaleFactor = pow(2.0, event->delta() / 240.0);
scale(scaleFactor, scaleFactor);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.setSceneRect(0, 0, 800, 800);
QGraphicsRectItem* rectItem = new QGraphicsRectItem(QRectF(0, 0, 400, 200));
rectItem->setPos(200, 200);
rectItem->setBrush(QColor(255, 0, 0));
scene.addItem(rectItem);
QGraphicsSimpleTextItem *nameItem = new QGraphicsSimpleTextItem("name", rectItem);
QFont f = nameItem->font();
f.setPointSize(12);
nameItem->setFont(f);
nameItem->setFlag(QGraphicsItem::ItemIgnoresTransformations);
nameItem->setPos(rectItem->rect().center());
MainView view(&scene);
view.show();
return a.exec();
}
Unfortunately you can see : on the capture that when I unzoom ( on the right ), the text doesn't stay inside the rectangle.
How to keep the text inside the rectangle and centered ?
Thank you.
I also want the position of the text to be centered on the QGraphicsRectItem
What you're seeing is correct, since you're scaling about the top left of the QGraphicsView and the text item is placed in the centre of the rectangle.
If you scaled about the centre of your QGraphicsRectItem, you'd see the text would maintain its position in the centre of the rect.
Another way of looking at this is to position the text in the top left corner of the rectangle. You'll note that when you scale here, the text will appear correct, up until it can no longer fit in the rectangle.
Continue scaling and you'll see that the top left of the text is still in the centre, but since the text does not obey the transform, it is pushed outside
It may appear that the top left of the text is below the rectangle, but the bounding rect of the text takes accents into consideration (e.g รจ).
So, by having the text positioned in the centre of the rect, rather than the top left, the appearance of the text being outside the rect is exacerbated.
Once you've zoomed out so far, the transform of the rect is dealing in fractions of a point size, but the non transformed text is unaffected and so the difference between, for example, 0.6 and 0.9 of a pixel is irrelevant and will be positioned at the same pixel.
You need to consider what you're trying to achieve. Is it really necessary to zoom to that extent or can you restrict it beyond a certain point, where you won't notice this issue?
I am working with QT 5.7 and C++.
At the moment I try to get used to draw my own widgets with the QPainter class.
But I noticed a problem I couldn't solve.
I try to draw a border line extactly at the widget border but if I do so:
void MyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter;
painter.begin(this);
painter.setBrush(Qt::cyan);
QBrush brush(Qt::black);
QPen pen(brush, 2);
painter.setPen(pen);
painter.drawRect(0, 0, size().width() - 1, size().height() - 1);
painter.end();
}
The Line is at the bottom and right site bigger than the others:
And before someone is telling me I have to remove the two -1 expressions,
you should know if I do this and also set the pen width to 1 there is no line anymore at the bottom and right side.
I think this artifact is caused by the "line aligment".
QT tries to tint the the pixels near the logical lines defined by the rectangle but actually because finally all have to be in pixels it has to decide.
If I am right, why there is no method to set the line aligment of the pen like in GDI+?
And how I can solve this?
Everything depends on whether you want the entire pen's width to be visible or not. By drawing the rectangle starting at 0,0, you're only showing half of the pen's width, and that makes things unnecessarily complicated - never mind that the line appears too thin. In Qt, the non-cosmetic pen is always drawn aligned to the middle of the line. Qt doesn't let you change it: you can change the drawn geometry instead.
To get it right for odd line sizes, you must give rectangle's coordinates as floating point values, and they must be fall in the middle of the line. So, e.g. if the pen is 3.0 units wide, the rectangle's geometry will be (1.5, 1.5, width()-3.0, width()-3.0).
Here's a complete example:
// https://github.com/KubaO/stackoverflown/tree/master/questions/widget-pen-wide-38019846
#include <QtWidgets>
class Widget : public QWidget {
Q_OBJECT
Q_PROPERTY(qreal penWidth READ penWidth WRITE setPenWidth)
qreal m_penWidth = 1.0;
protected:
void paintEvent(QPaintEvent *) override {
QPainter p{this};
p.setPen({Qt::black, m_penWidth, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin});
p.setBrush(Qt::cyan);
qreal d = m_penWidth/2.0;
p.drawRect(QRectF{d, d, width()-m_penWidth, height()-m_penWidth});
}
public:
explicit Widget(QWidget * parent = 0) : QWidget{parent} { }
qreal penWidth() const { return m_penWidth; }
void setPenWidth(qreal width) {
if (width == m_penWidth) return;
m_penWidth = width;
update();
}
QSize sizeHint() const override { return {100, 100}; }
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget top;
QVBoxLayout layout{&top};
Widget widget;
QSlider slider{Qt::Horizontal};
layout.addWidget(&widget);
layout.addWidget(&slider);
slider.setMinimum(100);
slider.setMaximum(1000);
QObject::connect(&slider, &QSlider::valueChanged, [&](int val){
widget.setPenWidth(val/100.0);
});
top.show();
return app.exec();
}
#include "main.moc"
Let's say I have a widget in main window, and want to track mouse position ONLY on the widget: it means that left-low corner of widget must be local (0, 0).
Q: How can I do this?
p.s. NON of functions below do that.
widget->mapFromGlobal(QCursor::pos()).x();
QCursor::pos()).x();
event->x();
I am afraid, you won't be happy with your requirement 'lower left must be (0,0). In Qt coordinate systems (0,0) is upper left. If you can accept that. The following code...
setMouseTracking(true); // E.g. set in your constructor of your widget.
// Implement in your widget
void MainWindow::mouseMoveEvent(QMouseEvent *event){
qDebug() << event->pos();
}
...will give you the coordinates of your mouse pointer in your widget.
If all you want to do is to report position of the mouse in coordinates as if the widget's lower-left corner was (0,0) and Y was ascending when going up, then the code below does it. I think the reason for wanting such code is misguided, though, since coordinates of everything else within said widget don't work this way. So why would you want it, I can't fathom, but here you go.
#include <QtWidgets>
class Window : public QLabel {
public:
Window() {
setMouseTracking(true);
setMinimumSize(100, 100);
}
void mouseMoveEvent(QMouseEvent *ev) override {
// vvv That's where the magic happens
QTransform t;
t.scale(1, -1);
t.translate(0, -height()+1);
QPoint pos = ev->pos() * t;
// ^^^
setText(QStringLiteral("%1, %2").arg(pos.x()).arg(pos.y()));
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
I'm trying to make a Paint application in C++ with Qt. Everytime I click or click & drag the mouse, the program will draw something on a pixmap. After that, it updates the window calling paintEvent(), which will draw the pixmap onto the window.
void QPaintArea::mousePressEvent(QMouseEvent *event){
startpoint = event->pos();
drawPoint(startpoint);
is_pressed = true;
}
void QPaintArea::mouseReleaseEvent(QMouseEvent *event){
is_pressed = false;
}
void QPaintArea::mouseMoveEvent(QMouseEvent *event){
if(is_pressed == true){
endpoint = event->pos();
drawLine(startpoint, endpoint);
startpoint = endpoint;
}
else{
return;
}
}
void QPaintArea::paintEvent(QPaintEvent *event){
QDesktopWidget *desktop = QApplication::desktop();
int x = (desktop->width() - 800) / 2;
int y = (desktop->height() - 600) / 2;
QPainter painter(this);
QRect target(QPoint(x, y - 35), QSize(800, 600));
QRect dirtyrect(QPoint(0,0), QSize(800, 600));
painter.drawPixmap(target, *pixmap, dirtyrect);
}
The problem is that, the program is not printing the pixmap onto the window as expected. For example, I press the mouse at x: 17, y: 82 trying to draw something. The program will print what I drew but at an offset location, like x + 20, y.
Maybe I don't fully understand how QRect or drawPixmap works, but the pixmap is 800x600. "dirtyrect" is supposed to save the entire pixmap (starting a x: 0, y: 0, and the size 800x600).
drawPixmap(target, pixmap, source) paints on target rect of painter area (QPaintArea in this case) source part of pixmap. So you paint whole pixmap (0,0,800,600) at some (x,y-35,800,600) rect of QPaintArea. If you want to paint whole pixmap on whole QPaintArea just use drawPixmap(QPoint(0,0), *pixmap).
// EDIT
But if you expected, that pixmap will be painted with some offset from QPaintArea top left corner, then your calculations are wrong, and if you wont explain what did you want to achieve we won't be able to help you. Explain us your calculations of x,y (and magic -35 for y), and maybe we will be able to figure something out
// EDIT
You don't have to use window offsets like -35 if you're painting on widget. 0,0 of the widget is not top left corner of window frame, but of widget contents. How do you expect it to behave on other platforms?
If you want to paint it in the middle of your window simply use:
void QPaintArea::paintEvent(QPaintEvent *event){
QPoint middle = geometry.center();
int x = middle.x() - 800/2; // probably best would be pixmap->width()/2
int y = middle.y() - 600/2; // probably best would be pixmap->height()/2
QPainter painter(this);
painter.drawPixmap(QPoint(x,y), *pixmap);
}