How do I combine a QImage and QPixmap? - qt

I'm using PyQt, and I've loaded an image from disk into a QPixmap. I've also created a mask, using:
self.mask = QImage(self.image.width(), self.image.height(), QImage.Format_Mono)
self.mask.fill(0)
I'd like to combine the two for display, such that any pixels colored black in the mask are drawn in translucent red over the image when I render it.
I've created a custom widget, that renders the image in the paint event like so:
def paintEvent(self, event):
p = QPainter(self)
r = event.rect()
p.drawPixmap(r, self.image, r)
This works fine. What I'm less clear on is how to take the data in the mask and paint a translucent red only over those pixels in the destination image.
I've tried turning the mask into a clipping region, like this:
mask = QPixmap.fromImage(self.mask.createMaskFromColor(self.mask.color(0)))
p.setClipRegion(QRegion(mask))
color = QColor(255, 0, 0, 128)
p.setPen(Qt.NoPen)
p.setBrush(QBrush(color))
p.drawRect(r)
... but it doesn't draw anything (and draws a translucent red box over the whole image if I don't call setClipRegion).
I also tried creating the mask as a QImage.Format_ARGB4444_Premultiplied, and using transparency. And while this does work, and I can edit the mask in my program (and verify that some portions of the mask are transparent and some portions are opaque) the self.mask.createAlphaMask() method returns a solid white rectangle.
Do the "create mask" methods actually do anything?

Either change your mask's design or create a new QImage based on your mask. Then draw the QImage on the destination. It is not only a method that works, but also a method that is faster than drawing single pixels or sth. similar. I tried several ways and this was the fastest so far (on a QGLWidget).
The idea is that you encode the transparency, and also the non-marked pixels, in the QImage directly, like this:
QImage dest(<width>, <height>, QImage::Format_ARGB32);
dest.fill(qRgba(0, 0, 0, 0));
for (int y = 0; y < <height>; ++y) {
QRgb *destrow = (QRgb*)dest.scanLine(y);
for (int x = 0; x < <width>; ++x) {
if (<should be marked>)
destrow[x] = qRgba(255, 0, 0, 127);
}
}
painter.drawImage(0, 0, dest);
For reference, take a look at the code here:
https://sourceforge.net/p/gerbil/svn/60/tree/trunk/gerbil-gui/bandview.cpp#l59

Related

How to disable linear filtering for drawImage on canvas in javafx

I'm trying to draw scaled image on canvas in javafx. Using this code:
Image image = ...;
canvas.setWidth(scale * width);
canvas.setHeight(scale * height);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.drawImage(image, 0, 0, scale * width, scale * height);
// this gives same result
// gc.scale(scale, scale);
// gc.drawImage(editableImage, 0, 0, width, height);
It works really fast but makes blurred images like this:
This is not what I'd like to see. Instead I want to get this picture:
Which can be drawn by manually setting each pixel color with such code:
PixelReader reader = image.getPixelReader();
PixelWriter writer = gc.getPixelWriter();
for (int y = 0; y < scale * height; ++y) {
for (int x = 0; x < scale * width; ++x) {
writer.setArgb(x, y, reader.getArgb(x / scale, y / scale));
}
}
But I cannot use this approach as it's too slow. It took couple of seconds to draw 1Kb image scaled 8 times. So I ask if there's any way to disable this blurry effect for drawing on canvas?
UPD 10/07/2019:
Looks like the issue is fixed! Now GraphicsContext should have property "image smoothing" controlling this behavior.
INITIAL ANSWER
I guess I've found answer to my question. As this issue says that there's no way to specify filtering options in graphics context.
Description:
When drawing an image in a GraphicsContext using the drawImage()
method to enlarge a small image to a larger canvas, the image is being
interpolated (possibly using a bilinear or bicubic algorithm). But
there are times like when rendering color maps (temperature,
zooplancton, salinity, etc.) or some geographical data (population
concentration, etc.) where we want to have no interpolation at all
(ie: use the nearest neighbor algorithm instead) in order to represent
accurate data and shapes.
In Java2D, this is possible by setting the appropriate
RenderingHints.KEY_RENDERING on the Graphics2D at hand. Currently on
JavaFX's GraphicsContext there is no such way to specify how the image
is to be interpolated.
The same applies when shrinking images too.
This could be expanded to support a better form of smoothing for the
"smooth" value that is available in both Image and ImageView and that
does not seem to work very well currently (at least on Windows).
The issue was created in 2013 but it's still untouched so unlikely it will be resolved soon.

What is the gradient orientation and gradient magnitude?

I am currently studying a module in computer vision called edge detection.
I am trying to understand the meaning of gradient orientation and gradient magnitude.
As explained by Dima in his answer, you should be familiar with the mathematical concept of gradient in order to better understand the gradient in the field of image processing.
My answer is based on the answer of mevatron to this question.
Here you find a simple initial image of a white disk on a black background:
you can compute an approximation of the gradient of this image. As Dima explained in his answer, you have two component of the gradient, an horizontal and a vertical component.
The following images shows you the horizontal component:
it shows how much the gray levels in your image change in the horizontal direction (it is the direction of positive x, scanning the image from left to right), this change is "encoded" in the grey level of the image of the horizontal component: the mean grey level means no change, the bright levels mean change from a dark value to a bright value, the dark levels mean a change from a bright value to a dark value. So, in the above image you see the brighter value in the left part of the circle because it is in the left part of the initial image that you have the black to white transition that gives you the left edge of the disk; similarly, in the above image you see the darker value in the right part of the circle because it is in the right part of the initial image that you have the white to black transition that gives you the right edge of the disk. In the above image, the inner part of the disk and the background are at a mean grey level because there is no change inside the disk and in the background.
We can make analogous observations for the vertical component, it shows how the image change in the vertical direction, i.e. scanning the image from the top to the bottom:
You can now combine the two components in order to get the magnitude of the gradient and the orientation of the gradient.
The following image is the magnitude of the gradient:
Again, in the above image the change in initial image is encoded in the gray level: here you see that white means an high change in the initial image while black means no change at all.
So, when you look at the image of the magnitude of the gradient you can say "if the image is bright it means a big change in the initial image; if it is dark it means no change or very llittle change".
The following image is the orientation of the gradient:
In the above image the orientation is again encoded as gray levels: you can think at the orientation as the angle of an arrow pointing from the the dark part of the image to the bright part of the image; the angle is referred to an xy frame where the x runs from left to right while the y runs from top to bottom. In the above image you see all the grey level from the black (zero degree) to the white (360 degree). We can encode the information with color:
in the above image the information is encode in this way:
red: the angle is between 0 and 90 degree
cyan: the angle is between 90 and 180 degree
green: the angle is between 180 and 270 degree
yellow: the angle is between 270 and 360 degree
Here it is the C++ OpenCV code for producing the above images.
Pay attention to the fact that, for the computation of the orientation, I use the function cv::phase which, as explained in the doc, gives an angle of 0 when both the vertical component and the horizontal component of the gradient are zero; that may be convenient but from a mathematical point of view is plainly wrong because when both components are zero the orientation is not defined and the only meaningful value for an orientation kept in a floating-point C++ type is a NaN.
It is plainly wrong because a 0 degree orientation, for example, is already related to an horizontal edge and it cannot be used to represent something else like a region with no edges and so a region where orientation is meaningless.
// original code by https://stackoverflow.com/users/951860/mevatron
// see https://stackoverflow.com/a/11157426/15485
// https://stackoverflow.com/users/15485/uvts-cvs added the code for saving x and y gradient component
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
Mat mat2gray(const cv::Mat& src)
{
Mat dst;
normalize(src, dst, 0.0, 255.0, cv::NORM_MINMAX, CV_8U);
return dst;
}
Mat orientationMap(const cv::Mat& mag, const cv::Mat& ori, double thresh = 1.0)
{
Mat oriMap = Mat::zeros(ori.size(), CV_8UC3);
Vec3b red(0, 0, 255);
Vec3b cyan(255, 255, 0);
Vec3b green(0, 255, 0);
Vec3b yellow(0, 255, 255);
for(int i = 0; i < mag.rows*mag.cols; i++)
{
float* magPixel = reinterpret_cast<float*>(mag.data + i*sizeof(float));
if(*magPixel > thresh)
{
float* oriPixel = reinterpret_cast<float*>(ori.data + i*sizeof(float));
Vec3b* mapPixel = reinterpret_cast<Vec3b*>(oriMap.data + i*3*sizeof(char));
if(*oriPixel < 90.0)
*mapPixel = red;
else if(*oriPixel >= 90.0 && *oriPixel < 180.0)
*mapPixel = cyan;
else if(*oriPixel >= 180.0 && *oriPixel < 270.0)
*mapPixel = green;
else if(*oriPixel >= 270.0 && *oriPixel < 360.0)
*mapPixel = yellow;
}
}
return oriMap;
}
int main(int argc, char* argv[])
{
Mat image = Mat::zeros(Size(320, 240), CV_8UC1);
circle(image, Point(160, 120), 80, Scalar(255, 255, 255), -1, CV_AA);
imshow("original", image);
Mat Sx;
Sobel(image, Sx, CV_32F, 1, 0, 3);
Mat Sy;
Sobel(image, Sy, CV_32F, 0, 1, 3);
Mat mag, ori;
magnitude(Sx, Sy, mag);
phase(Sx, Sy, ori, true);
Mat oriMap = orientationMap(mag, ori, 1.0);
imshow("x", mat2gray(Sx));
imshow("y", mat2gray(Sy));
imwrite("hor.png",mat2gray(Sx));
imwrite("ver.png",mat2gray(Sy));
imshow("magnitude", mat2gray(mag));
imshow("orientation", mat2gray(ori));
imshow("orientation map", oriMap);
waitKey();
return 0;
}
The gradient of a function of two variables x, y is a vector of the partial derivatives in the x and y direction. So if your function is f(x,y), the gradient is the vector (f_x, f_y). An image is a discrete function of (x,y), so you can also talk about the gradient of an image.
The gradient of the image has two components: the x-derivative and the y-derivative. So, you can think of it as vectors (f_x, f_y) defined at each pixel. These vectors have a direction atan(f_y / fx) and a magnitude sqrt(f_x^2 + f_y^2). So, you can represent the gradient of an image either an x-derivative image and a y-derivative image, or as direction image and a magnitude image.

Qt : draw triangle image

I need to do something similar to QPainter::drawImage, but drawing a triangle part of the given picture (into a triangular region of my widget) instead of working with rectangles.
Any idea how I could do that, besides painfully trying to redraw every pixel?
Thanks for your insights!
If it is feasible for you to use a QPixmap instead of a QImage, you can set a bitmap mask for the QPixmap which defines which of the pixels are shown and which are transparent:
myPixmap->setMask(myTriangleMask);
painter->drawPixmap(myPixmap);
Here is another solution based on QImage:
MaskWidget::MaskWidget(QWidget* parent) : QWidget(parent) {
img = QImage("Sample.jpg"); // The image to paint
mask = QImage("Mask.png"); // An indexed 2-bit colormap image
QPainter imgPainter(&img);
imgPainter.drawImage(0, 0, mask); // Paint the mask onto the image
}
void MaskWidget::paintEvent ( QPaintEvent * event ) {
QPainter painter(this);
painter.drawImage(10, 10, img);
}
Mask.png is an image file with the same size as Sample.jpg. It contains an alpha channel to support transparency. You can create this file easily with The GIMP, for example. I added an alpha channel, changed all areas I want to have painted to transparent and all other areas to white. To reduce the size, I finally converted it to an indexed 2-bit image.
You could even create the mask image programmatically with Qt, if you need your triangle be computed based on various parameters.

Wrong alpha blending when rendering on a QGLFramebufferObject

I write an OpenGL based vector graphics renderer for my application. It needs to render to a framebuffer object rather to the screen directly. Since I write the application in Qt, I use a QGLFramebufferObject which is a wrapper class for a OpenGL framebuffer object.
I created a minimal example which shows a wrong result I also get when rendering more complex stuff (for example using a fragment shader which sets colors with a non-one alpha value). I just render a red circle and a half-transparent green one on a black cleared screen, and then the same on the FBO:
void MainWidget::initializeGL()
{
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0, 0, 0, 0);
}
void MainWidget::resizeGL(int w, int h)
{
glViewport(0, 0, w, h);
}
void MainWidget::paintGL()
{
// DRAW ON THE SCREEN
{
glClear(GL_COLOR_BUFFER_BIT);
glPointSize(100);
glEnable(GL_POINT_SMOOTH);
glBegin(GL_POINTS);
glColor4f(1, 0, 0, 1);
glVertex2f(-.2, 0);
glColor4f(0, 1, 0, .5);
glVertex2f( .2, 0);
glEnd();
}
QGLFramebufferObject fbo(width(), height());
fbo.bind();
// DRAW ON THE FBO USING THE SAME CODE AND THE SAME CONTEXT
{
glClear(GL_COLOR_BUFFER_BIT);
glPointSize(100);
glEnable(GL_POINT_SMOOTH);
glBegin(GL_POINTS);
glColor4f(1, 0, 0, 1);
glVertex2f(-.2, 0);
glColor4f(0, 1, 0, .5);
glVertex2f( .2, 0);
glEnd();
}
fbo.release();
fbo.toImage().save("debug.png");
}
The result looks like this on the screen (scaled 400%):
The rendering to the QGLFramebufferObject looks like this (also scaled 400%):
Note that this image is not fully opaque, so here it is the same image with a checkerboard added behind it:
Even the area in which the two circles overlap isn't fully opaque. And the anti-aliasing looks pretty ugly.
How does this happen? And how can I fix this?
I already tried:
Different blend functions.
Explicitly disabling the depth buffer, stencil buffer and sampling on the QGLFramebufferObject. I'm not sure if the QGLFramebufferObject default format adds something I don't want.
Try the following:
QGLFramebufferObjectFormat fmt;
fmt.setSamples(1); // or 4 or disable this line
fmt.setInternalTextureFormat(GL_RGBA8);
QGLFramebufferObject fbo(width(), height(), fmt);
This forces a specific pixel format and also disables rendering to a texture by using multisampling (otherwise QT always renders to a texture). That might produce different results. You can also experiment with the format.
Also, what is your hardware? My maximal point size is only 64 pixels (GTX 260), you are trying to render 100 pixel points. That might be an issue. Are any OpenGL errors generated? Does the same happen on small points?
You might also try hinting (if it's possible in QT):
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
But i wouldn't expect this to change anything.

How to add an image on the top of another image?

I want to show difference between a trimed clip and non trimed clip in my video editor application, i.e. I want to add a small film image on my thumbnail for a trimed clip. How can I do this?
It would be just to show the difference between an image and a video in our gallery application.
How to add an image on the top of another one in Qt?
Open the QPainter on the bottom image and draw the top image using its drawPixmap()/drawImage() methods.
QPixmap base, overlay; // come from your code
{
QPainter painter(base);
painter.drawPixmap(100, 100, overlay);
}
If your overlay contains an alpha channel (e.g. fancy PNG icon) and your base image does not, you should create a new QPixmap with an alpha channel and draw both images into it:
QPixmap base, overlay; // come from your code
QPixmap result(base.width(), base.height());
result.fill(Qt::transparent); // force alpha channel
{
QPainter painter(&result);
painter.drawPixmap(0, 0, base);
painter.drawPixmap(100, 100, overlay);
}
QPixmaps and QImages can be used interchangeably, although not all combinations give good performance).
If it's just about showing an image above another, then you could also go with this answer.
QGridLayout *layout = new QGridLayout(widget);
Pixmap base, overlay;
QLabel *background = new Label();
background->setPixmap(&base);
QLabel *lOverlay = new QLabel();
lOverlay->setPixmap(&overlay);
//label gets positioned above textBrowser and is an overlay
layout->addWidget(background, 0, 0, Qt::AlignLeft | Qt::AlignTop);
layout->addWidget(lOverlay, 0, 0, Qt::AlignRight | Qt::AlignBottom);
Of course then the QPixbuf of the background doesn't contain the QPixbuf of the overlay-image, but it only appears to do.

Resources