How to translate/crop a QImage with subpixel accuracy? - qt

The use case is a 2D map with a vehicle at the origin. The map shall also be translated in case the vehicle moves e.g. 0.5 pixels. I believe this should be feasible using bilinear interpolation or similar.
If there is no simple solution using Qt, I would appreciate hints to non-Qt-solutions.
Minimal example:
#include <QtWidgets/QApplication>
#include <QtGui/QImage>
#include <QLabel>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// Parameters
QString PATH_IMG_IN = "../img_test_rect.jpg";
QString PATH_IMG_OUT = "../img_out.png";
float TRANSLATE_IN_PX = 0.5;
// load image
QImage img;
img.load(PATH_IMG_IN);
// rotate image.
QTransform trans;
trans.translate(0,TRANSLATE_IN_PX);
QImage img_new = img.transformed(trans, Qt::SmoothTransformation);
// save image
img_new.save(PATH_IMG_OUT, nullptr, 100);
// optional: Get info about true transformation matrix
QTransform trans_true = QImage::trueMatrix(trans, img.width(), img.height());
return app.exec();
}
Given an input image with a sharp border (see below), I would expect the output image to have a blurred border. This is not the case:
How to fix that?

I tested openCV and its function cv::warpAffine allows translation with sub-pixel precision (see MWE below).
After founding some old, unanswered threads on qtcentre.org, it seems to me that Qt simply does not allow translation with sub-pixel precision. Please correct me if I am wrong.
For Qt, I only found workarounds to scale the image first, translate with pixel accuracy and scale down again. Unfortunately, this approach is too computationally expensive for my use case.
MWE with opencv:
#include "opencv2/opencv.hpp"
#include "opencv2/highgui/highgui.hpp"
int main(int argc, char** argv) {
// parameters
std::string PATH_IMG_IN = "../img_test_rect.jpg";
std::string PATH_IMG_OUT = "../img_out.jpg";
// load image
cv::Mat img = cv::imread(PATH_IMG_IN, CV_LOAD_IMAGE_GRAYSCALE);
if (!img.data) // Check for invalid input
{
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
// rotate image
cv::Mat img_new = cv::Mat::ones(img.size(), img.type()) * 0.5; // another type = CV_8U
cv::Mat mat_transform = (cv::Mat_<float>(2, 3) << 1, 0, 0.5, 0, 1, 0);
cv::warpAffine(img, img_new, mat_transform, img_new.size());
// show image
cv::imshow("Display window", img_new);
// save image
cv::imwrite(PATH_IMG_OUT, img_new);
// wait for the user to press any key:
cv::waitKey(0);
return 0;
}

Related

Qt - How to create Image that scale with window, and keeps aspect ratio?

I'm trying to create image in QT (inside label) that would change size according to changes in window size, but also would keep aspect ratio.
What's the best way to do it?
You tagged this question with linux. I develop on Windows 10 - the closest to Linux I have at hand is cygwin. Thus, I solved it in VS2013 but, hey, this is C++ with Qt. It should be portable...
Actually, QPixmap::scaled() has everything built-in what's necessary for scaling by keeping the aspect ratio. Thus, my solution is rather short plugging the QLabel and QPixmap together.
// standard C++ header:
#include <iostream>
#include <string>
// Qt header:
#include <QApplication>
#include <QResizeEvent>
#include <QLabel>
#include <QMainWindow>
#include <QPixmap>
#include <QTimer>
using namespace std;
class LabelImage: public QLabel {
private:
QPixmap _qPixmap, _qPixmapScaled;
public:
void setPixmap(const QPixmap &qPixmap) { setPixmap(qPixmap, size()); }
protected:
virtual void resizeEvent(QResizeEvent *pQEvent);
private:
void setPixmap(const QPixmap &qPixmap, const QSize &size);
};
void LabelImage::resizeEvent(QResizeEvent *pQEvent)
{
QLabel::resizeEvent(pQEvent);
setPixmap(_qPixmap, pQEvent->size());
}
void LabelImage::setPixmap(const QPixmap &qPixmap, const QSize &size)
{
_qPixmap = qPixmap;
_qPixmapScaled = _qPixmap.scaled(size, Qt::KeepAspectRatio);
QLabel::setPixmap(_qPixmapScaled);
}
int main(int argc, char **argv)
{
cout << QT_VERSION_STR << endl;
// main application
#undef qApp // undef macro qApp out of the way
QApplication qApp(argc, argv);
// setup GUI
QMainWindow qWin;
#if 0 // does not consider aspect ratio
QLabel qLblImg;
qLblImg.setScaledContents(true);
#else // (not) 0
LabelImage qLblImg;
#endif // 0
qLblImg.setAlignment(Qt::AlignCenter);
qLblImg.setSizePolicy(
QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
QPixmap qPM;
if (qPM.load("cats.jpg")) qLblImg.setPixmap(qPM);
else {
qLblImg.setText(
QString::fromLatin1("Sorry. Cannot find file 'cats.jpg'."));
}
qWin.setCentralWidget(&qLblImg);
qWin.show();
// run application
return qApp.exec();
}
Notes:
I overloaded the QLabel::setPixmap() method and store actually two versions of the pixmap - the original and the scaled. I'm not sure if this is necessary - it's the first time I used QPixmap.
While reading the Qt docs I found QLabel::setScaledContents(). I gave it a try but it does not consider the aspect ratio of the pixmap. I couldn't find a way to set this as extra option. (May be, I did not search enough. I disabled this code but left it in to remember this as "wrong direction".)
In an intermediate version, I was able to enlarge the application (and scaling was fine) but I could not shrink it. Googling a little bit I found SO: Enable QLabel to shrink even if it truncates text. This solved the issue.
To keep the sample short, I hardcoded the image file name. It is actually unnecessary to say that the current directory of the application must be the one where the file is located. (This is probably no issue in Linux but I had to adjust the debug settings in VS2013 appropriately.)
Below is a snapshot of my test appl.:
This should work (of course) with any image file which can be loaded into Qt. However, to make the sample complete (and because Internet and pictures of cats belong really together) I provide the sample image also (for download).
The left is Max, the right is Moritz. (Or vice versa?)
Edit:
According to the feedback of Shefy Gur-ary, this didn't work properly in a QLayout. Thus, I modified the original version and added a QGridLayout to my sample code to examine this topic:
// standard C++ header:
#include <iostream>
#include <string>
// Qt header:
#include <QApplication>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QMainWindow>
#include <QPixmap>
#include <QResizeEvent>
#include <QTimer>
using namespace std;
class LabelImage: public QLabel {
private:
QPixmap _qPixmap, _qPixmapScaled;
public:
void setPixmap(const QPixmap &qPixmap) { setPixmap(qPixmap, size()); }
protected:
virtual void resizeEvent(QResizeEvent *pQEvent);
private:
void setPixmap(const QPixmap &qPixmap, const QSize &size);
};
void LabelImage::resizeEvent(QResizeEvent *pQEvent)
{
QLabel::resizeEvent(pQEvent);
setPixmap(_qPixmap, pQEvent->size());
}
void LabelImage::setPixmap(const QPixmap &qPixmap, const QSize &size)
{
_qPixmap = qPixmap;
_qPixmapScaled = _qPixmap.scaled(size, Qt::KeepAspectRatio);
QLabel::setPixmap(_qPixmapScaled);
}
int main(int argc, char **argv)
{
cout << QT_VERSION_STR << endl;
// main application
#undef qApp // undef macro qApp out of the way
QApplication qApp(argc, argv);
// setup GUI
QMainWindow qWin;
QGroupBox qBox;
QGridLayout qGrid;
// a macro for the keyboard lazy:
#define Q_LBL_WITH_POS(ROW, COL) \
QLabel qLbl##ROW##COL(QString::fromLatin1(#ROW", "#COL)); \
/*qLbl##ROW##COL.setFrameStyle(QLabel::Raised | QLabel::Box);*/ \
qGrid.addWidget(&qLbl##ROW##COL, ROW, COL, Qt::AlignCenter)
Q_LBL_WITH_POS(0, 0);
Q_LBL_WITH_POS(0, 1);
Q_LBL_WITH_POS(0, 2);
Q_LBL_WITH_POS(1, 0);
LabelImage qLblImg;
qLblImg.setFrameStyle(QLabel::Raised | QLabel::Box);
qLblImg.setAlignment(Qt::AlignCenter);
//qLblImg.setMinimumSize(QSize(1, 1)); // seems to be not necessary
qLblImg.setSizePolicy(
QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
QPixmap qPM;
if (qPM.load("cats.jpg")) qLblImg.setPixmap(qPM);
else {
qLblImg.setText(
QString::fromLatin1("Sorry. Cannot find file 'cats.jpg'."));
}
qGrid.addWidget(&qLblImg, 1, 1, Qt::AlignCenter);
qGrid.setRowStretch(1, 1); // tell QGridLayout to stretch this cell...
qGrid.setColumnStretch(1, 1); // ...prior to other cells (w/ stretch 0)
Q_LBL_WITH_POS(1, 2);
Q_LBL_WITH_POS(2, 0);
Q_LBL_WITH_POS(2, 1);
Q_LBL_WITH_POS(2, 2);
qBox.setLayout(&qGrid);
qWin.setCentralWidget(&qBox);
qWin.show();
// run application
return qApp.exec();
}
Notes:
The aspect ratio of image was still correct but the resizing didn't work anymore. Thus, I added QGrid::setRowStretch() and QGrid::setColumnStretch(). Unfortunately, this didn't change much.
I googled this topic and found SO: Change resize behavior in Qt layouts. Actually, This didn't help really also but made me suspective that the QGridLayout could be the actual source of this layout issue.
For better visualization of this layout issue, I added frames to all my widgets. To my surprise, it worked suddenly.
I assume that the layout in the QGridLayout works somehow not like expected (although I wouldn't dare to call it a bug). However, the workaround to make a frame around the image is something I could live with. (Actually, it looks not that bad.)
A snapshot of the updated code sample:

qwt zoom out with wrong reect

I have a qwt plot, here a simple example, with a QWtPlotZoomer. The zoom in works, but zoom back reset the scale to 0-1000, instead the original scale.
I tried to solve this with (what is the difference between these lines ?)
zoomer->setZoomBase(false);
zoomer->zoom(0);
but that has no effect. What needs to be done to get the correct initial scale for the zoomer? A trivial solution is to implement the zoomer after the curve attachment, but in a real work example that is not applicable:
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>
#include <QDebug>
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_plot_zoomer.h>
#include <vector>
using std::vector;
int main( int argc, char **argv )
{
QApplication a( argc, argv );
QwtPlot * plot = new QwtPlot();
plot->setAxisAutoScale(QwtPlot::xBottom);
plot->setAxisAutoScale(QwtPlot::yLeft);
QwtPlotZoomer *zoomer;
zoomer = new QwtPlotZoomer( QwtPlot::xBottom, QwtPlot::yLeft, plot->canvas() );
// create data
vector<double> x(100);
vector<double> y1(x.size());
for (size_t i = 0; i< x.size(); ++i) { x[i] = int(i)-50; }
for (size_t i = 0; i< y1.size(); ++i) { y1[i] = pow(double(abs(i-50))/10,2); }
// first curve
QwtPlotCurve *curve = new QwtPlotCurve();
curve->setRawSamples(&x[0], &y1[0], x.size());
curve->attach( plot );
zoomer->setZoomBase(false);
zoomer->zoom(0);
plot->replot();
QMainWindow window;
window.setCentralWidget(plot);
window.resize(800, 600);
window.show();
return a.exec();
}
Create the zoomer after attaching the curves ( having valid data ) or modify your code to zoomer->setZoomBase(true);
The line zoomer->zoom(0) is pointless and the final replot is not necessary as it is done by the zoomer to initialize its zoom stack.
When having a zoom base of [0,1000] you usually have initialized the zoom stack of your zoomer with a plot, where the scales have not been calculated before.
Note that attaching the curves does not update the ranges immediately - it is done before the next replot ( or better QwtPlot::updateAxes() ).

convert GIcon to QIcon

Is there a way to convert between these datatypes? I'm working with Qt but still need some of glib capabilities and I haven't found a way to do this. I need to get a list of the installed applications with GAppInfo and show it in a QListView and to do so I need to get the icon for those applications. Extracting it using g_app_info_get_icon returns a GIcon and what I need to work with is a QIcon in order to get it's QVariant.
GIcon does not provide the actual pixmap for the icon.
You need to load an actual pixmap by requesting icon of certain size.
#include <gio/gdesktopappinfo.h>
#include <gtk/gtk.h>
#include <QtGui>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QIcon icon;
GAppInfo *appInfo = (GAppInfo *)g_desktop_app_info_new("vlc.desktop");
GIcon *gicon = g_app_info_get_icon(appInfo);
QList<int> sizes; sizes << 16 << 24 << 32 << 48 << 64;
foreach (int size, sizes) {
GtkIconInfo *iconInfo = gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_default(), gicon, size, (GtkIconLookupFlags)0);
GdkPixbuf *pixbuf = gtk_icon_info_load_icon(iconInfo, NULL);
if (pixbuf == NULL)
continue;
QImage image(
gdk_pixbuf_get_pixels(pixbuf),
gdk_pixbuf_get_width(pixbuf),
gdk_pixbuf_get_height(pixbuf),
gdk_pixbuf_get_rowstride(pixbuf),
QImage::Format_ARGB32);
g_object_unref(pixbuf);
image.save("icon-" + QString::number(size) + ".png");
icon.addPixmap(QPixmap::fromImage(image));
}
g_object_unref(gicon);
return 0;
}

How to compute a QPainterPath from a pixmap

I have a QGraphicsPixmapItem that rotates through different pixmaps to simulate animation. I need to accurately implement the shape() function so the scene can properly determine collision with other objects. Each pixmap obviously has slightly different collision paths. Is there a simple way to create a QPainterPath from a pixmap by outlining the colored pixels of the actual image that border the alpha background of the bounding rect without having to write my own complex algorithm that tries to create that path manually?
I plan on having these paths pre-drawn and cycle through them the same way I do as the pixmaps.
You can use QGraphicsPixmapItem::setShapeMode() with either QGraphicsPixmapItem::MaskShape or QGraphicsPixmapItem::HeuristicMaskShape for this:
#include <QtGui>
#include <QtWidgets>
class Item : public QGraphicsPixmapItem
{
public:
Item() {
setShapeMode(QGraphicsPixmapItem::MaskShape);
QPixmap pixmap(100, 100);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setBrush(Qt::gray);
painter.setPen(Qt::NoPen);
painter.drawEllipse(0, 0, 100 - painter.pen().width(), 100 - painter.pen().width());
setPixmap(pixmap);
}
enum { Type = QGraphicsItem::UserType };
int type() const {
return Type;
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsView view;
view.setScene(new QGraphicsScene());
Item *item = new Item();
view.scene()->addItem(item);
// Comment out to see the item.
QGraphicsPathItem *shapeItem = view.scene()->addPath(item->shape());
shapeItem->setBrush(Qt::red);
shapeItem->setPen(Qt::NoPen);
view.show();
return app.exec();
}

Animated QGraphicsItem with dashed pen

I'd like to create a rotating circle drawn with a Qt:DotLine pen, using the Graphics View Framework. Using QGraphicsItemAnimation, I can rotate other shapes but not the circle. The program below demonstrates the problem: instead of the rectangle and the circle rotating together, the circle jerks around while the rectangle rotates gracefully.
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsItem>
#include <QTimeLine>
#include <QGraphicsItemAnimation>
QRectF rect (int r)
{
return QRectF (-r, -r, r * 2, r * 2);
}
void setupRot (QTimeLine *timeline, QGraphicsItem *item)
{
QGraphicsItemAnimation *animation = new QGraphicsItemAnimation;
animation->setItem(item);
animation->setTimeLine(timeline);
animation->setRotationAt (1, 360);
QObject::connect (timeline, SIGNAL(finished()), animation, SLOT(deleteLater()));
}
int main(int argc, char *argv[])
{
QApplication app (argc, argv);
QGraphicsScene scene;
QTimeLine *timeline = new QTimeLine;
timeline->setDuration (3000);
timeline->setCurveShape (QTimeLine::LinearCurve);
QObject::connect (timeline, SIGNAL(finished()), timeline, SLOT(deleteLater()));
setupRot (timeline, scene.addEllipse (rect (50), QPen (QBrush (QColor ("blue")), 8, Qt::DotLine)));
setupRot (timeline, scene.addRect (rect (60)));
scene.addEllipse (rect (40), QPen (QBrush (QColor ("red")), 8));
scene.setSceneRect (-100, -100, 200, 200);
QGraphicsView view (&scene);
view.show ();
timeline->setLoopCount (0);
timeline->start();
return app.exec ();
}
p.s.: I've found some sample code on the web where people are creating intermediate animation steps manually, like this:
const int steps = 100;
for (int i = 0; i < steps; ++i)
animation->setRotationAt (i / (float)steps, 360 / (float)steps * i);
Is this just a sign of people not understanding the concept of interpolation, or is there some advantage of setting (seemingly superfluous) control points?
Which version/platform? If I run your code as is (or slowed down 2x), the dotted circle rotation looks as good as the rectangle in Windows with Qt 4.7.

Resources