I'm trying to understand why the code below changes the QImage in Qt. It's not meant to do anything (yet), it's just for testing. When I run the code on an image with alpha, the alpha channel is lost and replaced by a black background.
QImage image;
image.load("image.png");
for (int y = 0; y < image.height(); y++) {
for (int x = 0; x < image.height(); x++) {
QColor c = QColor::fromRgba(image.pixel(x, y));
c.setHsv(c.hue(), c.saturation(), c.value());
image.setPixel(x, y, c.rgba());
}
}
Here is the result when I comment out the line image.setPixel(...):
And here is the result with the image.setPixel(...) line:
I would expect my code to do no change on the image. Any idea why it's doing this?
If you look at the documentation of setHsv(), you will see that alpha is set by default to 255 (or 1.0 for the float version) if you don't explicitly specify it.
Perhaps using the line c.setHsv(c.hue(), c.saturation(), c.value(), c.alpha()); will resolve your problem.
Related
I'm new to graphics programming (pixels, images, etc..)
I'm trying to convert Raw data to QImage and display it on QLabel. The problem is that, the raw data can be any data (it's not actually image raw data, it's binary file.)
The reason if this is that, to understand deeply how pixels and things like that work, I know I'll get random image with weird results, but it will work.
I'm doing something like this, but I think I'm doing it wrong!
QImage *img = new QImage(640, 480, QImage::Format_RGB16); //640,480 size picture.
//here I'm trying to fill newly created QImage with random pixels and display it.
for(int i = 0; i < 640; i++)
{
for(int u = 0; u < 480; u++)
{
img->setPixel(i, u, rawData[i]);
}
}
ui->label->setPixmap(QPixmap::fromImage(*img));
am I doing it correctly? By the way, can you point me where should I learn these things? Thank you!
Overall it's correct. QImage is a class that allows to manipulate its own data directly, but you should use correct pixel format.
A bit more efficient example:
QImage* img = new QImage(640, 480, QImage::Format_RGB16);
for (int y = 0; y < img->height(); y++)
{
memcpy(img->scanLine(y), rawData[y], img->bytesPerLine());
}
Where rawData is a two-dimension array.
This is how I saved a raw BGRA frame to the disk:
QImage image((const unsigned char*)pixels, width, height, QImage::Format_RGB32);
image.save("out.jpg");
Syntactically, your code appears to be correct.
Reading the class signature, you may want to call setPixel in the following manner:
img->setPixel(i, u, QRbg(##FFRRGGBB));
Where ##FFRRGGBB is a color quadruplet, unless, of course, you want monochrome 8 bit support.
Additionally, declaring a naked pointer is dangerous. The following code is equivalent:
QImage image(640, 480, QImage::Format_something);
QPixmap::fromImage(image);
And will deallocate appropriately upon function completion.
Qt Examples directory is a great place to search for functionality. Also, peruse the class documentation because they're littered with examples.
I have been looking for such answers.
I found this method in QPixmap transformed() though I think it does not help me in my pursuit of searching a method to convert QPixmap Images (grayscale) into a Matrix...
Thanks for the help =)
The QMatrix in the QPixmap::transformed() function is specifically for warping images.
I think what you want to do is read the values from a QPixmap into some matrix.
You don't specify what grayscale means but I presume that qGray(QRgb) is sufficient if the image is not already grayscale.
I think basically something like this is what you need:
QImage myimage = mypixmap.toImage(); // convert your QPixmap to QImage
int width = myimage.width();
int height = myimage.height();
int *matrix = new int [width*height]; // store 2-D data in 1-D vector
for(int j = 0; j < height; j++)
{
for(int i = 0; i < width; i++)
{
matrix[j*width+i] = qGray(myimage.pixel(i,j));
}
}
// ... do stuff ...
delete [] matrix;
You can easily change the matrix variable into some other layout in memory if you like.
I'm using .NET 4.0. I don't know if this is a framework bug or if it's a GDI+ thing. I just discovered it while writing an app to swap color channels.
Let me try to explain the problem. I'm reading pixels from one bitmap, swapping the channels, and writing them out to another bitmap. (Specifically, I'm setting the output image's RGB values equal to the input image's alpha, and output's alpha equal to the input's green channel… or, to put it succinctly, A => RGB and G => A.) The code is as follows:
for (int y = 0; y < input.Height; y++)
{
for (int x = 0; x < input.Width; x++)
{
Color srcPixel = input.GetPixel(x, y);
int alpha = srcPixel.A;
int green = srcPixel.G;
Color destPixel = Color.FromArgb(green, alpha, alpha, alpha);
output.SetPixel(x, y, destPixel);
}
}
Similarly, I've tried this:
int color = green << 24 | alpha << 16 | alpha << 8 | alpha;
Color destPixel = Color.FromArgb(color);
output.SetPixel(x, y, destPixel);
For the most part, it works.
The problem: regardless of what the RGB values are, when alpha is zero, the resultant RGB value is always pure black (R:0, G:0, B:0). I don't know if this is some sort of FromArgb() "optimization" — using .NET Reflector, I don't see FromArgb() doing anything strange — or if Bitmap.SetPixel is the culprit — more likely since it defers to native code and I can't look at it. Either way, when alpha is zero, the pixel is black. This is not the behavior I expected. I need to keep RGB channels intact.
At first I thought it was a pre-multiplied alpha issue, because I'm loading DDS files using my home-brewed DDS loader (which I built to spec and which has never given me any issues), but when I specify an explicit alpha of 255, like this:
Color destPixel = Color.FromArgb(255, alpha, alpha, alpha);
...the RGB channels show up correctly — i.e., none of them turns out black — so it's definitely something within GDI+ that erroneously assumes RGB values can be safely ignored if the alpha is zero… which, to me, seems like a pretty stupid assumption, but, whatever.
Further exacerbating the problem is that the Color type is immutable, which makes sense for a structure, but it means I can't create a color and then assign the alpha… which, if SetPixel() is the culprit, wouldn't matter anyway. (I've tested this by geting the pixel again immediately after setting it and seeing the same results: zero alpha = zero RGB).
So, my question: has anyone dealt with this issue and come up with a relatively simple workaround? In an effort to keep my dependencies down, I am loathe to import a third-party image library, but since GDI+ is making buggy assumptions about my color channels, I may not have a choice.
Thanks for your help.
EDIT: I solved this, but I can't post the answer for another seven hours. Awesome.
Sorry for the delay. Anyway, I should have worked on this a bit longer before posting, because I found a solution about five or ten minutes later. To be clear, I didn't find a solution to the stated GDI+ issue, but I found a suitable workaround. I thought about how, in other API's, I would lock a surface and transfer bytes directly to another surface, so I took that approach. After a little help from MSDN, here's my code (sans error handling):
Bitmap input = Bitmap.FromFile(filename) as Bitmap;
int byteCount = input.Width * input.Height * 4;
var inBytes = new byte[byteCount];
var outBytes = new byte[byteCount];
var inBmpData = input.LockBits(new Rectangle(0, 0, input.Width, input.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(inBmpData.Scan0, inBytes, 0, byteCount);
for (int y = 0; y < input.Height; y++)
{
for (int x = 0; x < input.Width; x++)
{
int offset = (input.Width * y + x) * 4;
// byte blue = inBytes[offset];
byte green = inBytes[offset + 1];
// byte red = inBytes[offset + 2];
byte alpha = inBytes[offset + 3];
outBytes[offset] = alpha;
outBytes[offset + 1] = alpha;
outBytes[offset + 2] = alpha;
outBytes[offset + 3] = green;
}
}
input.UnlockBits(inBmpData);
Bitmap output = new Bitmap(input.Width, input.Height, PixelFormat.Format32bppArgb);
var outBmpData = output.LockBits(new Rectangle(0, 0, output.Width, output.Height), ImageLockMode.WriteOnly, output.PixelFormat);
Marshal.Copy(outBytes, 0, outBmpData.Scan0, outBytes.Length);
output.UnlockBits(outBmpData);
Notes: Marshal is under System.Runtime.InteropServices; BitmapData (inBmpData, outBmpData), ImageLockMode, and PixelFormat are under System.Drawing.Imaging.
Not only does this work perfectly, but it is phenomenally faster. I'll be using this technique from now on for all my channel swapping needs. (I've already used it in another, similar app.)
Sorry for the needless post. I at least hope this solution helps someone else.
Given a .png image with a transparent background, I want to find the bounding box of the non-transparent data. Using nested for loops with QImage.pixel() is painfully slow. Is there a built-in method of doing this in Qt?
There is one option that involves using a QGraphicsPixmapItem and querying for the bounding box of the opaque area (QGraphicsPixmapItem::opaqueArea().boundingRect()). Not sure if it is the best way but it works :) It might be worth digging into Qt's source code to see what code is at the heart of it.
The following code will print out the width and height of the image followed by the width and height of the opaque portions of the image:
QPixmap p("image.png");
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(p);
std::cout << item->boundingRect().width() << "," << item->boundingRect().height() << std::endl;
std::cout << item->opaqueArea().boundingRect().width() << "," << item->opaqueArea().boundingRect().height() << std::endl;
If pixel() is too slow for you, consider more efficient row-wise data adressing, given a QImage p:
int l =p.width(), r = 0, t = p.height(), b = 0;
for (int y = 0; y < p.height(); ++y) {
QRgb *row = (QRgb*)p.scanLine(y);
bool rowFilled = false;
for (int x = 0; x < p.width(); ++x) {
if (qAlpha(row[x])) {
rowFilled = true;
r = std::max(r, x);
if (l > x) {
l = x;
x = r; // shortcut to only search for new right bound from here
}
}
}
if (rowFilled) {
t = std::min(t, y);
b = y;
}
}
I doubt it will get any faster than this.
The easiest and also relatively fast solution is to do as follows:
QRegion(QBitmap::fromImage(image.createMaskFromColor(0x00000000))).boundingRect()
If you have a QPixmap rather than QImage, then you can use:
QRegion(pixmap.createMaskFromColor(Qt::transparent)).boundingRect()
QPixmap::createMaskFromColor internally will convert the pixmap to an image and do the same as above. An even shorter solution for QPixmap is:
QRegion(pixmap.mask()).boundingRect()
In this case, a QPixmap without alpha channel will result in an empty region, so you may need to check for that explicitly. Incidentally, this is also what QGraphicsPixmapItem::opaqueArea mentioned by #Arnold Spence is based on.
You may also want to try QImage::createAlphaMask, though the cutoff point will not be at 0 alpha but rather somewhere at half opacity.
I am trying to write something in my Flex 3 application with actionscript that will take an image and when a user clicks a button, it will strip out all the white(ish) pixels and convert them to transparent, I say white(ish) because I have tried exactly white, but I get a lot of artifacts around the edges. I have gotten somewhat close using the following code:
targetBitmapData.threshold(sourceBitmapData, sourceBitmapData.rect, new Point(0,0), ">=", 0xFFf7f0f2, 0x00FFFFFF, 0xFFFFFFFF, true);
However, it also makes red or yellows disappear. Why is it doing this? I'm not exactly sure how to make this work. Is there another function that is better suited for my needs?
A friend and I were trying to do this a while back for a project, and found writing an inline method that does this in ActionScript to be incredibly slow. You have to scan each pixel and do a computation against it, but doing it with PixelBender proved to be lightning fast (if you can use Flash 10, otherwise your stuck with slow AS).
The pixel bender code looks like:
input image4 src;
output float4 dst;
// How close of a match you want
parameter float threshold
<
minValue: 0.0;
maxValue: 1.0;
defaultValue: 0.4;
>;
// Color you are matching against.
parameter float3 color
<
defaultValue: float3(1.0, 1.0, 1.0);
>;
void evaluatePixel()
{
float4 current = sampleNearest(src, outCoord());
dst = float4((distance(current.rgb, color) < threshold) ? 0.0 : current);
}
If you need to do it in AS you can use something like:
function threshold(source:BitmapData, dest:BitmapData, color:uint, threshold:Number) {
dest.lock();
var x:uint, y:uint;
for (y = 0; y < source.height; y++) {
for (x = 0; x < source.width; x++) {
var c1:uint = source.getPixel(x, y);
var c2:uint = color;
var rx:uint = Math.abs(((c1 & 0xff0000) >> 16) - ((c2 & 0xff0000) >> 16));
var gx:uint = Math.abs(((c1 & 0xff00) >> 8) - ((c2 & 0xff00) >> 8));
var bx:uint = Math.abs((c1 & 0xff) - (c2 & 0xff));
var dist = Math.sqrt(rx*rx + gx*gx + bx*bx);
if (dist <= threshold)
dest.setPixel(x, y, 0x00ffffff);
else
dest.setPixel(x, y, c1);
}
}
dest.unlock();
}
You can actually do it without pixelbender and real-time thanks to the inbuilt threshold function :
// Creates a new transparent BitmapData (in case the source is opaque)
var dest:BitmapData = new BitmapData(source.width,source.height,true,0x00000000);
// Copies the source pixels onto it
dest.draw(source);
// Replaces all the pixels greater than 0xf1f1f1 by transparent pixels
dest.threshold(source, source.rect, new Point(), ">", 0xfff1f1f1,0x00000000);
// And here you go ...
addChild(new Bitmap(dest));
it looks like the above code would make a range of colors transparent.
pseudo-code:
for each pixel in targetBitmapData
if pixel's color is >= #FFF7F0F2
change color to #00FFFFFF
something like this will never be perfect, because you will lose any light colors
i would find an online color picker that you can use to see exactly what colors will be altered
The answer in 1 of pixel bender code:
dst = float4((distance(current.rgb, color) < threshold) ? 0.0 : current);
should be:
dst = (distance(current.rgb, color) < threshold) ? float4(0.0) : current;
or
if (distance(current.rgb, color) < threshold)
dst = float4(0.0);
else
dst = float4(current);