Drawing border around image QT. - c++

I am having a few issues with this code to draw a border around an image in QT, can anyone tell me what i am missing:
void imageLabel::paintEvent(QPaintEvent *event)
{
QLabel::paintEvent(event);
if (!m_qImage.isNull())
{
QImage qImageScaled = m_qImage.scaled(QSize(width(),height()),Qt::KeepAspectRatio,Qt::FastTransformation);
double dAspectRatio = (double)qImageScaled.width()/(double)m_qImage.width();
int iX = m_iX*dAspectRatio;
int iY = m_iY*dAspectRatio;
int iWidth = m_iWidth*dAspectRatio;
int iHeight = m_iHeight*dAspectRatio;
QPainter qPainter(this);
qPainter.drawImage(0,0,qImageScaled);
qPainter.setBrush(Qt::NoBrush);
qPainter.setPen(Qt::red);
qPainter.drawRect(iX,iY,iWidth,iHeight);
}
}

You can use QFrame to simplify the task of adding a frame around a widget like QLabel.
In QtCreator, simply select the label and scroll down until you see the turquoise section of the properties editor and play with the values there.
The result looks like this:
Hope this helps you along!

Related

Qt, Unable to get QLabel dimmensions to correctly scale a QPixmap to its fully fit QLabel

So I'm trying to scale an Image to fully fit a label from top to bottom. The problem is the outputs from the labels width, and height is not pixel values so I'm not sure how to correctly go about this. Here's my code.
void MainWindow::drawPixmap()
{
int labelWidth = ui->picLabel->width();
int labelHeight = ui->picLabel->height();
if(labelWidth <= labelHeight){
pixMap = this->pixMap.scaledToWidth(labelWidth);
ui->picLabel->setPixmap(pixMap);
}
else{
pixMap = this->pixMap.scaledToHeight(labelHeight);
ui->picLabel->setPixmap(pixMap);
}
}
Here's a visual of my problem, the black box is a border around the label box. I'm trying to get the image in the center to have its top and bottom touch the black box on respective sides.
Thanks for any help!
The problem could be when you calling this function. If you have called this function prior to making the label visible, then the size of the label could be incorrect after the label becomes visible.
Also, think about what happens when the label resize (if ever). You will still have to update the size of pixmap.
If your label is never going to change size, try calling it after the label has become visible.
Alternatively, try setting QLabel::setScaledContents(true)
If both the above doesn't work, try installing an eventFilter on the label to get the label's size change event, in there, you could do your above code
MainWindow::MainWindow()
{
....
ui->picLabel->installEventFilter(this);
}
bool MainWindow::eventFilter(QObject* object, QEvent* event)
{
bool res = Base::eventFilter(object, event);
if (object == ui->picLabel && event->type() == QEvent::Resize)
{
int labelWidth = ui->picLabel->width();
int labelHeight = ui->picLabel->height();
if(labelWidth <= labelHeight){
pixMap = this->pixMap.scaledToWidth(labelWidth);
ui->picLabel->setPixmap(pixMap);
}
else{
pixMap = this->pixMap.scaledToHeight(labelHeight);
ui->picLabel->setPixmap(pixMap);
}
}
return res;
}

QTextDocument and selection painting

I am painting a block of text on a widget with QTextDocument::drawContents. My current goal is to intercept mouse events and emulate text selection. It's pretty clear how to handle the mouse, but displaying the result puzzles me a lot.
Just before we start: I can not use QLabel and let it handle selection on it's own (it has no idea how to draw unusual characters and messes up line height (https://git.macaw.me/blue/squawk/issues/59)), nor I can not use QTextBrowser there - it's just a message bubble, I'm not ready to sacrifice performance there.
There is a very rich framework around QTextDocument, but I can not find any way to make it color the background of some fragment of text that I would consider selected. Found a way to make a frame around a text, found a way to draw under-over-lined text, but it looks like there is simply just no way this framework can draw a background behind text.
I have tried doing this, to see if I can take selected fragment under some QTextFrame and set it's style:
QTextDocument* bodyRenderer = new QTextDocument();
bodyRenderer->setHtml("some text");
bodyRenderer->setTextWidth(50);
painter->setBackgroundMode(Qt::BGMode::OpaqueMode); //this at least makes it color background under all text
QTextFrameFormat format = bodyRenderer->rootFrame()->frameFormat();
format.setBackground(option.palette.brush(QPalette::Active, QPalette::Highlight));
bodyRenderer->rootFrame()->setFrameFormat(format);
bodyRenderer->drawContents(painter);
Nothing of this works too:
QTextBlock b = bodyRenderer->begin();
QTextBlockFormat format = b.blockFormat();
format.setBackground(option.palette.brush(QPalette::Active, QPalette::Highlight));
format.setProperty(QTextFormat::BackgroundBrush, option.palette.brush(QPalette::Active, QPalette::Highlight));
QTextCursor cursor(bodyRenderer);
cursor.setBlockFormat(format);
b = bodyRenderer->begin();
while (b.isValid() > 0) {
QTextLayout* lay = b.layout();
QTextLayout::FormatRange range;
range.format = b.charFormat();
range.start = 0;
range.length = 2;
lay->draw(painter, option.rect.topLeft(), {range});
b = b.next();
}
Is there any way I can make this framework do a simple thing - draw a selection background behind some text? If not - is there a way I can unproject cursor position into coordinate translation, like can I do reverse operation from QAbstractTextDocumentLayout::hitTest just to understand where to draw that selection rectangle myself?
You can use QTextCursor to change the background of the selected text. You only need to select one character at a time to keep the formatting. Here is an example of highlighting in blue (the color of the text is highlighted in white for contrast):
void MainWindow::paintEvent(QPaintEvent *event) {
QPainter painter(this);
painter.fillRect(contentsRect(), QBrush(QColor("white")));
QTextDocument document;
document.setHtml(QString("Hello <font size='20'>world</font> with Qt!"));
int selectionStart = 3;
int selectionEnd = selectionStart + 10;
QTextCursor cursor(&document);
cursor.setPosition(selectionStart);
while (cursor.position() < selectionEnd) {
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); // select one symbol
QTextCharFormat selectFormat = cursor.charFormat();
selectFormat.setBackground(Qt::blue);
selectFormat.setForeground(Qt::white);
cursor.setCharFormat(selectFormat); // set format for selection
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1);
}
document.drawContents(&painter, contentsRect());
QMainWindow::paintEvent(event);
}

Screenshot capture of dual monitor in Qt 5

In the context of a Qt application, I'm using the following code snippet for taking a screenshot of full desktop:
QDesktopWidget* dw = QApplication::desktop();
QPixmap pixmap = QPixmap::grabWindow(dw->winId(), 0, 0,
dw->width(), dw->height());
pixmap.save(name, "JPG", screenshot_quality);
This approach works pretty well in Linux and Windows and with dual monitor, independently of screen's resolutions; that is, it works still if the two monitors are working with different resolutions. However, with Qt 5 I get the following run-time warning:
static QPixmap QPixmap::grabWindow(WId, int, int, int, int) is deprecated, use QScreen::grabWindow() instead. Defaulting to primary screen.
So I reviewed the Qt 5 doc and I wrote this:
QScreen * screen = QGuiApplication::primaryScreen();
QPixmap pixmap = screen->grabWindow(0);
pixmap.save(name, "JPG", screenshot_quality);
But this approach does not capture the second screen.
So I searched a little more and, according to this thread, Taking Screenshot of Full Desktop with Qt5, I designed the screenshot capture as follows:
QScreen * screen = QGuiApplication::primaryScreen();
QRect g = screen->geometry();
QPixmap pixmap = screen->grabWindow(0, g.x(), g.y(), g.width(), g.height());
pixmap.save(name, "JPG", screenshot_quality);
Unfortunately, this does not work too.
What catches my attention is that the method with Qt 4 works well. Since I imagine there must be some way to make it in Qt 5.
So, my question is how can be done with Qt 5?
EDIT: This is the way as I solved:
QPixmap grabScreens()
{
QList<QScreen*> screens = QGuiApplication::screens();
QList<QPixmap> scrs;
int w = 0, h = 0, p = 0;
foreach (auto scr, screens)
{
QRect g = scr->geometry();
QPixmap pix = scr->grabWindow(0, g.x(), g.y(), g.width(), g.height());
w += pix.width();
h = max(h, pix.height());
scrs.append(pix);
}
QPixmap final(w, h);
QPainter painter(&final);
final.fill(Qt::black);
foreach (auto scr, scrs)
{
painter.drawPixmap(QPoint(p, 0), scr);
p += scr.width();
}
return final;
}
Thanks to #ddriver!
Naturally, QGuiApplication::primaryScreen() will give you a single screen.
You could use QList<QScreen *> QGuiApplication::screens() to get all screens associated with the application, take screenshots for all of them, then create another blank image, size it according to how you want to compose the screens, and manually compose into a final image using QPainter.
QPixmap grabScreens() {
auto screens = QGuiApplication::screens();
QList<QPixmap> scrs;
int w = 0, h = 0, p = 0;
foreach (auto scr, screens) {
QPixmap pix = scr->grabWindow(0);
w += pix.width();
if (h < pix.height()) h = pix.height();
scrs << pix;
}
QPixmap final(w, h);
QPainter painter(&final);
final.fill(Qt::black);
foreach (auto scr, scrs) {
painter.drawPixmap(QPoint(p, 0), scr);
p += scr.width();
}
return final;
}
Also, you can use primary screen's (desktop) virtual geometry and capture entire desktop without additional loops and calculations:
QRect desktopGeometry = qApp->primaryScreen()->virtualGeometry();
QPixmap desktopPixmap = qApp->primaryScreen()->grabWindow(qApp->desktop()->winId(), desktopGeometry.x(), desktopGeometry.y(), desktopGeometry.width(), desktopGeometry.height());
See also: QDesktopWidget
Update:
At the moment, QApplication::desktop() and QDesktopWidget are marked as obsolete by the Qt for some reason, so for new projects it's recommended to use approach with screen enumerating. Anyway, for old and current Qt versions this solution has to work as expected.

Lock app orientation to landscape in Qt

I would like to lock my app developed with Qt to landscape orientation, even if the screen display is portrait. I have tried adding to my code the resizeEvent method found here: http://qt-project.org/doc/qt-4.8/widgets-orientation.html, but my app still does not display correctly. Here is my code for resizeEvent:
void MainWindow::resizeEvent(QResizeEvent *event)
{
QSize size = event->size();
qDebug() << size;
bool isLandscape = size.width() > size.height();
if (isLandscape == false){
size.transpose();
}
this->setFixedSize(size);
}
Does anyone know how to do this in Qt 4.8.5? I am trying to display an app for a 320x240 display.
Thanks
You can't really follow that example. It shows two different widgets depending on the orientation. Furthermore the doc warns about modifying size properties inside resizeEvent.
One solution would be to set a fix aspect ratio similar to 320x240 by overloading QWidget::heightForWidth. You wouldn't need to overload resizeEvent.
It will look like
int MainWindow::heightForWidth( int w ) {
return (w * 240 )/320;
}
heightForWidth is discussed in detail in https://stackoverflow.com/a/1160476/1122645.
edit:
sizeHint is used by the layout of the parent to compute the size of children widgets. In general, it make sense to implement it as
QSize MainWindow::sizeHint() const
{
int w = //some width you seem fit
return QSize( w, heightForWidth(w) );
}
but if MainWindow is top level then it will not change anything. You can also alter heightForWidth flag of your current size policy
QSizePolicy currPolicy = this->sizePolicy();
currPolicy->setHeightForWidth(true);
this->SetSizePolicy(currPolicy);
But again, if it is a top level widget I doesnt change much.
Anyway you don't need to call updateGeometry.

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.