QGradient ellipse blending - qt

I am currently working on generating "heat-maps" with QPainter and QImage. My method consists of drawing multiple circles with black to transparent QRadialGradients as the QBrush (see "Intensity Map"). Then I apply a gradient map to the intensity map to get the desired "heat-map" effect (see "After Gradient Map").
The issue I am having, which is more apparent in the "After Gradient Map" image, is that the circles are not blending correctly. Where circles overlap do seem to partially blend, but towards the edges you can clearly see where the circles end (almost a outer-glow). I would like an effect which has no visible borders between the circles and blends correctly.
Intensity Map
After Gradient Map (different intensity map)
Code
// Setup QImage and QPainter
QImage *map = new QImage(500, 500, QImage::Format_ARGB32);
map->fill(QColor(255, 255, 255, 255));
QPainter paint(map);
paint.setRenderHint(QPainter::HighQualityAntialiasing);
// Create Intensity map
std::vector<int> record = disp_data[idx]; // Data
for(int j = 1, c = record.size(); j < c; ++j) {
int dm = 150 + record[j] * 100 / 255; // Vary the diameter
QPen g_pen(QColor(0, 0, 0, 0));
g_pen.setWidth(0);
QRadialGradient grad(sensors[j-1].x, sensors[j-1].y, dm/2); // Create Gradient
grad.setColorAt(0, QColor(0, 0, 0, record[j])); // Black, varying alpha
grad.setColorAt(1, QColor(0, 0, 0, 0)); // Black, completely transparent
QBrush g_brush(grad); // Gradient QBrush
paint.setPen(g_pen);
paint.setBrush(g_brush);
paint.drawEllipse(sensors[j-1].x-dm/2, sensors[j-1].y-dm/2, dm, dm); // Draw circle
}
// Convert to heat map
for(int i = 0; i < 500; ++i) {
for(int j = 0; j < 500; ++j) {
int b = qGray(map->pixel(i, j));
map->setPixel(i, j, grad_map->pixel(b, 0)); //grad_map is a QImage gradient map
}
}
As you can see, there is no QPen for the circles. I have been trying a variety of blending modes with no success. I have also changed the rendering hint to HighQualityAntialiasing. I have also tried making the circles much larger than the radial gradient, so there is no way the gradient is cut-off or a border is applied to the outside of the circle.
Any ideas? Thanks!

I think this is a form of mach-banding, which is an optical illusion where changes in luminance are enhanced by the visual system, causing the appearance of bright or dark bands which are not actually present in the image. Typically these are seen on the boundary between two distinct areas, but in the case here I believe it is the sharp discontinuity in the gradients being observed.
Here are some images to demonstrate the issue:
This first image is calculated in software, and consists of three circles each drawn with a radial linear gradient. Mach-band effects should be visible at the edges of the overlap between circles, as these are the points where the gradient sharply changes.
This second image is exactly the same calculation, but instead of being linear along the radius, the gradient is mapped to a curve (I used the first hermite basis function). The bands should almost entirely have disappeared:
As to why this affects a colourised image more, I'm not sure it does. I think in the case above, perhaps there is additional banding caused by the colourisation effectively being a palette lookup, resulting in additional banding.
I performed roughly the same colourisation locally, also simply mapping a palette, and the effect is similar:
Fixing this using QT's linear gradients is probably non-trivial (you could try adding a lot more control points to the gradient, but you'll have to add quite a few...), but calculating such an image in software is not hard. You could also consider some other post-processing effects, such as adding a blur, and/or adding noise. Anything breaking the discontinuity in the gradient would likely help.

I agree with JasonD.
Furthermore, please keep in mind that Qt is doing linear blending in sRGB color space, which is not linear (it has a gamma 2.2 applied).
To do this right, you need to do the blending or interpolation in linear light, then convert to sRGB (apply gamma) for display.

Related

Does Qt have an additive blend mode?

QPainter has many composition modes but none called additive. I'm interested because additive blending is used all the time in games for lighting / particles whatever.
The overlay mode is the only one that had something like the effect of lighting.
EDIT: I figured it out, heres how you can efficiently make different coloured lights in Qt.
In constructor or where ever, not in paint event:
light = QPixmap("light.png");
QPainter pix(light);
pix.setCompositionMode(QPainter::CompositionMode_Overlay);
pix.fillRect(light.rect(), QColor(255, 0, 0, 255)); // colorize the light in any color
Paint Event:
// Do drawing, e.g. a background
p.drawPixmap(0, 0, QPixmap("background.png"));
// draw the lighting
p.setCompositionMode(QPainter::CompositionMode_Plus);
p.drawPixmap(100, 100, light);
You can reuse the same pixmap as much as you like and draw it with different opacity or size etc.
The documentation for QPainter::CompositionMode_Plus says:
Both the alpha and color of the source and destination pixels are added together.

masking, or clipping mask with p5.js

I want to use one shape (for instance a rectangle) to act as a mask or clipping path for another shape (for instance a circle, or line) in P5.js
I can see solutions for using images as masks, but not shapes. It seems mask() is not a function of shapes:
https://p5js.org/reference/#/p5.Image/mask
yes, you can.
create an extra rendering context with createGraphics().
In the draw loop draw something to this context which will be your
mask. Whatever should be visible in your result has to be colored
with the alpha channel, for example fill('rgba(0, 0, 0, 1)'.
Apply the mask to your original image myImage.mask(circleMask).
Your original image has now been modified by the mask, render it on
the screen: image(myImage, x, y, w, h)
Here is a working code example:
let circleMask;
let myImage;
function setup() {
createCanvas(400, 400);
circleMask = createGraphics(128, 128);
myImage = loadImage('FzFH41IucIY.jpg');
}
function draw() {
background(255);
circleMask.fill('rgba(0, 0, 0, 1)');
circleMask.circle(64, 64, 128);
myImage.mask(circleMask);
image(myImage, 200 - 64, 200 - 64, 128, 128);
}
There isn't a way to do this out of the box with P5.js.
Right now your question is more of a math question than it is a P5.js question. I'd recommend searching for something like "circle rectangle intersection" for a ton of results, including this one: Circle-Rectangle collision detection (intersection)
Depending on what you want to do, you could get away with drawing the shapes to images and then using those images as a mask. But more likely you're going to have to calculate the intersection yourself. You might be able to find a library that does this for you, but again, there isn't a simple out of the box way with P5.js.

Add Shape created by .intersect to a Canvas?

I am trying to highlight an Area that gets intersected by 2 Circle:
Example 1.:
The Yellow dots get, for testing purposes, random values. Those are used to draw a circle around, as well as to store an ellipse in the Background. In the Case of no intersection, the GUI acts correctly and display this:
After the random Values, the Shapes intersect. As I cannot seem to be able to add the new Shape made trough .intersect(), I just did a quick sp.setContent(), and got this image:
This basicly shows me the intersected space and colors it blue.
Everything is drawn on a Canvas, which basicly does the following:
Canvas canvas = new Canvas(250, 250);
....
gc = canvas.getGraphicsContext2D();
canvas.setHeight(imgTemp.getHeight());
canvas.setWidth(imgTemp.getWidth());
gc.drawImage(imgTemp, 0, 0);
Aswell as with some other Loops to draw the shapes and the circles.
Now, the code for the intersect is the following:
if (!(e.equals(eT))) {
if (e.getBoundsInParent().intersects(eT.getBoundsInParent())) {
System.out.println("Collision detected!");
Shape inter = Shape.intersect(e, eT);
if(inter.getBoundsInLocal().getWidth() > 0 && 0 < inter.getBoundsInLocal().getHeight()){
inter.setFill(BLUE);
inter.setStrokeWidth(3);
sp.setContent(inter);
}
}
I'm not that used to JavaFX and have only begun really working on it this Weekend for a small Project, but I am guessing that I might need to change from canvas to something else to make use of the shapes? Or is there a way to "tranform" the Shape of the intersect into something drawable by GraphicsContext2D?
Why don't you just put your Canvas into a Group and then add your shapes to the same Group. Why would you insist on drawing everything into the Canvas? A Canvas is just a Node like all the other Shapes and you can mix them freely in the SceneGraph.
Another question would be why you are using the Canvas at all if you have already realized that this leads to problems in your case.

QPainter::drawPixmap() doesn't look good and has low quality?

I'm trying to draw an icon(.png) inside a QWidget with QPainter::drawPixmap()
:
QPixmap _source = "/.../.png";
painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.drawPixmap(rect(), _source);
but in comparing to QLabel (for example) and in lower size (19*19 in my case) the result isn't perfect.
What can I do?
****Edit****
QLabel with pixmap # size 19*19:
My painting # size 19*19 via SmoothPixmapTransform render type:
You are setting the wrong render hint, you need QPainter::SmoothPixmapTransform to get smooth resizing. By default the nearest neighbor method is used, which is fast but has very low quality and pixelates the result.
QPainter::HighQualityAntialiasing is for when drawing lines and filling paths and such, i.e. when rasterizing geometry, it has no effect on drawing raster graphics.
EDIT: It seems there is only so much SmoothPixmapTransform can do, and when the end result is so tiny, it isn't much:
QPainter p(this);
QPixmap img("e://img.png");
p.drawPixmap(QRect(50, 0, 50, 50), img);
p.setRenderHint(QPainter::SmoothPixmapTransform);
p.drawPixmap(QRect(0, 0, 50, 50), img);
img = img.scaled(50, 50, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
p.drawPixmap(100, 0, img);
This code produces the following result:
There is barely any difference between the second and third image, manually scaling the source image to the desired dimensions and drawing it produces the best result. This is certainly not right, it is expected from SmoothTransformation to produce the same result, but for some reason its scaling is inferior to the scale() method of QPixmap.

Use window/viewport to flip QPainter y-axis

I'm using Qt 4.7 QPainter to draw some polygons, etc into a widget. I am hoping to alter the coordinate system so that (0,0) is at the center of my widget, and the x/y axis behave in a standard "Cartesian" way (ie. y increases going "up" and decreases going "down"). In other words, I want the coordinates to be "math"-like not "computer graphics"-like, if you know what I mean. :-)
I'm trying to do this using setViewport() and setWindow() rather than do the math myself, as it would be nice to be able to just call the draw methods directly with my coordinates.
Here's what I've got so far:
// Setup coordinates
double screenWidth = width();
double screenHeight = height();
double windowWidth = 100.0;
double windowHeight = (screenHeight / screenWidth) * windowWidth;
painter.setViewport(0, 0, screenWidth, screenHeight);
painter.setWindow(-(windowWidth / 2.0), -(windowHeight / 2.0), windowWidth, windowHeight);
// Draw stuff
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::blue);
painter.drawRect(-10, -10, 20, 20);
Now this works just fine, in that it draws a nice blue square in the middle of the screen. The problem is, I have to say that the upper left corner is (-10, -10). I'd like to be able to make it (-10, 10), as that is what it would be in Cartesian coords.
I tried messing with setWindow/setViewport to get this "y-axis flip", but to no avail. This seems like a really easy/basic thing to do, but after scouring the Qt docs and the web, I can't figure it out!
Thanks,
Chris
Use class QMatrix. It specifies 2D transformations. QMatrix is set to QPainter.
But remember, in your case, if you convert your widget's coords to Cartesian coords, you will have to put first point at (-10,-10) (not at (-10,10) as you did mentioned) to draw a rect, which has center at (0,0), because Y-axis now grows up and X-Axis now grows right.
All you need is to transform your coord system this way:
translate origin from (0,0) to the middle of the widget:
scale Y-axis by -1 factor:
Here is the code, typed in paintEvent() function of a widget:
QPainter pn( this );
int w_2 = width() / 2;
int h_2 = height() / 2;
{ // X- and Y-Axis drawing
pn.setPen( Qt::blue );
pn.drawLine( 0, h_2, width(), h_2); // X-Axis
pn.drawLine( w_2, 0 , w_2, height() ); // Y-Axis
}
QMatrix m;
m.translate( w_2, h_2 );
m.scale( 1, -1 );
pn.setMatrix( m );
pn.setPen( Qt::NoPen );
pn.setBrush( QBrush( Qt::blue, Qt::Dense4Pattern ) );
pn.drawRect( -10, -10, 20, 20 );
result:
update apr 07, 2014
This question was asked a long time ago and many things have changed since. For those asking themselves the same question today (beginnings of 2014) then my personal answer is that since Qt 4.3 it is possible to solve problem with text flipping more easier.
You are right. Text also gets filpped because it is drawn with the same painter. You can draw text at the end, when all flipped drawings are done, if it is possible. This method is not convinient because of new calculations of texts position. Also you will need to drop settings for painter.
Now I would recommend you to use QGraphicsView, because of huge support of 2D painting. Also for each QGraphicsItem ItemIgnoresTransformations flag can be set, which allows it to ignore inherited transformations (i.e., its position is still anchored to its parent, but the parent or view rotation, zoom or shear transformations are ignored). This flag is useful for keeping text label items horizontal and unscaled, so they will still be readable if the graphics view is transformed
The above answer will also flip text, "p" will be "b". To avoid that you have to flip back the y-axis before text is drawn, and you have to change sign on y-coord for the text position when you draw it. This is a little bit ugly I think, or is there a better way?
As stated above, drawing text also appears flipped upside down. There is an easy solution to it, see below. We will temporary disable the world transform for the text drawing. Note that text is not scaled anymore.
in your painting code we want to draw text on coordinate QPointF P;
Painter pn( this );
// calculate the point with the transform
QPointF p = pm.transform().map(P);
// Disable Transform temporary
pn.setWorldMatrixEnabled(false);
// draw it ordinary, no scaling etc
pn.drawText(p, QString("HI FRIENDS!"));
// Enable the transform again
pn.setWorldMatrixEnabled(true);
I needed to flip the y-axis in order to paint lines and polygons using Qt from points defined in Java coordinates. I imagine others will need to do this in porting from Java to Qt coordinate systems. The discussion above was helpful. My solution was:
painter.translate(0,height());
painter.scale(1.0, -1.0);
and then proceed to draw the lines and polygons.

Resources