PDA

View Full Version : Problems working with 8bpp and 24bpp images



SkripT
13th February 2006, 10:42
Hi all, I am programming a simple photo editor program in Qt4.1 with winXp. Well, one of the utilities of the editor is to paint with a given color a selected rectangular area of the image. The code to paint this area is as follows:


void FotoEditorFotos::paintRect(const QColor &color, const QRect &rectAPintar)
{
int xMin = rectAPintar.x();
int xMax = xMin + rectAPintar.width() - 1;
int yMin = rectAPintar.y();
int yMax = yMin + rectAPintar.height() - 1;

if ( !imatge.valid(xMin, yMin) || !imatge.valid(xMax, yMax)) return;

QRgb *pixelsLiniaFoto;
QRgb valorColor = color.rgb();

for (int j = yMin; j <= yMax; j++)
{
pixelsLiniaFoto = ((QRgb *) imatge.scanLine(j)) + xMin;

for (int i = xMin; i <= xMax; i++)
{
*pixelsLiniaFoto = valorColor;
pixelsLiniaFoto++;
}
}

update();
}

The photo (imatge) is part of the class. Well the problem with the code above is that only works with images with 24 bits/pixel, but on the images with 8bpp it give me invalid results. The problem, I guess, is the cast to QRgb*. Anybody knows how to solve it? Thanks.

high_flyer
13th February 2006, 17:25
you will have to add an if statment and see if the painting is for 24 or 8bpp.
If the 24bpp then use the code you have, if its 8bpp, you have only 255 possible colors, so you will have to deal with that somewhere in your probgram, i.e limmiting the users to choose a color only from the 255 you use.

SkripT
13th February 2006, 17:44
... if its 8bpp, you have only 255 possible colors, so you will have to deal with that somewhere in your probgram, i.e limmiting the users to choose a color only from the 255 you use.

Exactly, the problem is that with the code that I have posted, although I pass it a valid color from the 255 (in my case a greyscale), it dosen't works :confused:

SkripT
13th February 2006, 23:31
Anybody knows how could I modify the code above to support 8bpp images?

high_flyer
14th February 2006, 09:44
Anybody knows how could I modify the code above to support 8bpp images?
I answered you, do you expect code?
You should think a bit your self...;)
In a case of gray scale you should use qGray().

SkripT
14th February 2006, 09:51
... if its 8bpp, you have only 255 possible colors, so you will have to deal with that somewhere in your probgram, i.e limmiting the users to choose a color only from the 255 you use.

Sorry high flyer I didn't undestand what you meant. Thanks.

Cesar
14th February 2006, 10:46
Take a look at $QTDIR/src/gui/painting/qrgb.h.

typedef unsigned int QRgb; // RGB triplet
That means sizeof(QRgb) == 4 on 32bit machines and 8 on 64bit ones. In case you have a 8bpp image, the sizeof(pixel) == 1 on any machine!!!
You may try this...

void FotoEditorFotos::paintRect(const QColor &color, const QRect &rectAPintar)
{
int xMin = rectAPintar.x();
int xMax = xMin + rectAPintar.width() - 1;
int yMin = rectAPintar.y();
int yMax = yMin + rectAPintar.height() - 1;

if ( !imatge.valid(xMin, yMin) || !imatge.valid(xMax, yMax)) return;

QRgb valorColor = color.rgb();

quint8 *pixelsLiniaFoto;
quint8 bpp8Color;

if (8 == imatge.bpp())
bpp8Color = (imatge.isGrayScale()) ? qGray(valorColor) : getColorIndex(valorColor);

for (int j = yMin; j <= yMax; j++)
{
pixelsLiniaFoto = static_cast<quit8 *>(imatge.scanLine(j)) + xMin * (imatge.bpp() / 8);
for (int i = xMin; i <= xMax; i++)
{
switch (imatge.bpp())
{
case 8:
*(pixelsLiniaFoto++) = colorToSet;
break;
// Other BPP formats
case 24:
*(pixelsLiniaFoto++) = qRed(valorColor);
*(pixelsLiniaFoto++) = qGreen(valorColor);
*(pixelsLiniaFoto++) = qBlue(valorColor);
break;
}
}
}

update();
}
Notes:
There're some functions with obvious names. I guess you understand what they mean. The 8bpp image is a special case. Each byte describes either grayscale value or index in the palette. qGrey(const QRgb &) is a native QT function, whilst getIndexColor(const QRgb &) should be implemented on your own.

SkripT
14th February 2006, 12:21
Thanks a lot Cesar, I think I will have to modify some lines but I think it will work :D

Cesar
14th February 2006, 12:34
You're always welcome, SkripT :) Feel free to ask anything. I've got some ideas on how to approximate any opaque QColor to index of a given palette.

SkripT
14th February 2006, 13:04
Hi. First, I post the code that finally I have implemented using the code by Cesar. After I comment a problem with this code:


void FotoEditorFotos::paintRect(const QColor &color, const QRect &rectAPintar)
{
int xMin = rectAPintar.x();
int xMax = xMin + rectAPintar.width() - 1;
int yMin = rectAPintar.y();
int yMax = yMin + rectAPintar.height() - 1;

if (!imatge.valid(xMin, yMin) || !imatge.valid(xMax, yMax)) return;

QRgb colorRgb = color.rgba();

quint8 *pixelsLiniaFoto;
quint8 color8, colorVermell, colorVerd, colorBlau, colorAlpha;

int bytesPixel = (imatge.depth()) >> 3;

if (bytesPixel == 1)
color8 = (imatge.isGrayScale()) ? qGray(colorRgb) : obtenirIndexColor(valorColor);
else
{
colorVermell = qRed(colorRgb);
colorVerd = qGreen(colorRgb);
colorBlau = qBlue(colorRgb);
colorAlpha = qAlpha(colorRgb);
}

for (int j = yMin; j <= yMax; j++)
{
pixelsLiniaFoto = static_cast<quint8 *>(imatge.scanLine(j)) + (xMin * bytesPixel);

for (int i = xMin; i <= xMax; i++)
{
switch (bytesPixel)
{
// Qt only manages 1, 8 or 32bpp
case 1:
*(pixelsLiniaFoto++) = color8;
break;

case 4:
*(pixelsLiniaFoto++) = colorVermell;
*(pixelsLiniaFoto++) = colorVerd;
*(pixelsLiniaFoto++) = colorBlau;
*(pixelsLiniaFoto++) = colorAlpha;
break;
}
}
}

update();
}

The problem is that with images with 32bpp it paints the area that I want but with an invalid color, different from the color that I pass to the function. Anoybody could explain where's the mistake?

Cesar
14th February 2006, 13:19
The only problem I could imagine is the following: your imatge object uses the different places for colours, rather then RGBA. I suggest you to temporarily the code to


//[SKIP]
case 4:
*(pixelsLiniaFoto++) = 0xff;
*(pixelsLiniaFoto++) = 0x00;
*(pixelsLiniaFoto++) = 0x00;
*(pixelsLiniaFoto++) = 0x00;
//[SKIP]

and watch for the results. This way you will fill the QRect with the pure color. Let's pretend you have got blue QRect. Then your first line should be changed to


*(pixelsLiniaFoto++) = colorBlau;

This way you can check, which colour is the first, which is the second and what is the place of alpha channel :)

SkripT
14th February 2006, 13:38
Thanks again Cesar, it's really strange if I try to put the value 255 in one posistion and the rest at 0, the result is always the same. It paints the selection with the same gray color. I attach an image of the result.

Cesar
14th February 2006, 15:45
Is imatge an instance of QImage class? Then why are you trying to invent your own bicycle??? :confused: This is the modified version of your initial code:

void FotoEditorFotos::paintRect(const QColor &color, const QRect &rectAPintar)
{
int xMin = rectAPintar.x();
int xMax = xMin + rectAPintar.width() - 1;
int yMin = rectAPintar.y();
int yMax = yMin + rectAPintar.height() - 1;

if ( !imatge.valid(xMin, yMin) || !imatge.valid(xMax, yMax)) return;

QRgb valorColor = (QImage::Format_Indexed8 == imatge.format()) ? obtenirIndexColor(color) : color.rgb();

for (int y = yMin; y <= yMax; ++y)
{
for (int x = xMin; x <= xMax; ++x)
{
imatge.setPixel(x, y, valorColor);
}
}

update();
}

SkripT
14th February 2006, 16:04
Hi, finally it works!!! :D . The order of the color of the pixels was the inverse. Here's the final code:


void FotoEditorFotos::paintRect(const QColor &color, const QRect &rectAPintar)
{
int xMin = rectAPintar.x();
int xMax = xMin + rectAPintar.width() - 1;
int yMin = rectAPintar.y();
int yMax = yMin + rectAPintar.height() - 1;

if (!imatge.valid(xMin, yMin) || !imatge.valid(xMax, yMax)) return;

QRgb colorRgb = color.rgba();

quint8 *pixelsLiniaFoto;
quint8 color8 = 0;
quint8 colorVermell = 0;
quint8 colorVerd = 0;
quint8 colorBlau = 0;
quint8 colorAlpha = 0;

int bytesPixel = (imatge.depth()) >> 3;

if (bytesPixel == 1)
color8 = (imatge.isGrayscale()) ? qGray(colorRgb) : obtenirIndexColor(colorRgb);
else
{
colorVermell = qRed(colorRgb);
colorVerd = qGreen(colorRgb);
colorBlau = qBlue(colorRgb);
colorAlpha = qAlpha(colorRgb);
}

for (int j = yMin; j <= yMax; j++)
{
pixelsLiniaFoto = static_cast<quint8 *>(imatge.scanLine(j)) + (xMin * bytesPixel);

for (int i = xMin; i <= xMax; i++)
{
switch (bytesPixel)
{
case 1:
*(pixelsLiniaFoto++) = color8;
break;

case 4:
*(pixelsLiniaFoto++) = colorBlau;
*(pixelsLiniaFoto++) = colorVerd;
*(pixelsLiniaFoto++) = colorVermell;
*(pixelsLiniaFoto++) = colorAlpha;
break;
}
}
}

update();
}


int FotoEditorFotos::obtenirIndexColor(const QRgb &colorRgb)
{
QVector<QRgb> taulaColor = imatge.colorTable();

int indexRgb = taulaColor.indexOf(colorRgb);

if (indexRgb < 0)
{
// Search for a similar color in the color table...
int n = taulaColor.count();

int verd = qGreen(colorRgb);
int vermell = qRed(colorRgb);
int blau = qBlue(colorRgb);
int alpha = qAlpha(colorRgb);
int diffMin = 1021; // 255*4 + 1
int diff;
QRgb rgb;

for (int i = 0; i < n; i++)
{
rgb = taulaColor[i];
diff = qAbs(qGreen(rgb) - verd) + qAbs(qRed(rgb) - vermell) + qAbs(qBlue(rgb) - blau) + qAbs(qAlpha(rgb) - alpha);

if (diff < diffMin)
{
diffMin = diff;
indexRgb = i;
}
}
}

return indexRgb;
}


Cesar I will try your last version. Thanks

Cesar
15th February 2006, 08:26
I'm glad you managed with that :). Anyway I suggest you to use QT api, where it is possible to avoid code duplication.
And here's my suggestion of the second function. I suppose it's a bit more precise ;)
First of all, let me explain, why is it so. Pretend QRgb is a point with coordinates (r, g, b). Then imatge.colorTable() is a series of points you have to choose from, when approximating random QRgb.
Let's pretend you have to approximate point QRgb(R', G', B'). The best point to choose from imatge.colorTable() is the one, which is the most close to (R', G', B'). As you know, the distance betwean two points is calculated this way:


double distance(const QRgb &x, const QRgb &y)
{
int dRed = qRed(x) - qRed(y);
int dGreen = qGreen(x) - qGreen(y);
int dBlue = qBlue(x) - qBlue(y);

return sqrt(dRed * dRed + dGreen * dGreen + dBlue * dBlue);
}
Now your task is to find such an i, for which distance(color, imatge.colorTable()[i]) is the least. You present algorythm fits well :).

int FotoEditorFotos::obtenirIndexColor(const QRgb &colorRgb)
{
QVector<QRgb> taulaColor = imatge.colorTable();
/*
int indexRgb = taulaColor.indexOf(colorRgb);

if (indexRgb < 0)
{
*/
int indexRgb;
// Search for a similar color in the color table...
int n = taulaColor.count();

int verd = qGreen(colorRgb);
int vermell = qRed(colorRgb);
int blau = qBlue(colorRgb);
int alpha = qAlpha(colorRgb);
int diffMin = 3 * 255 * 255 + 1;
int diff;
QRgb rgb;

for (int i = 0; i < n; ++i)
{
rgb = taulaColor[i];
int dR = vermell - qRed(rgb);
int dG = verd - qGreen(rgb);
int dB = blau - qBlue(rgb);
diff = dR * dR + dG * dG + dB * dB;

if (diff < diffMin)
{
diffMin = diff;
indexRgb = i;
if (0 == diffMin) break;
}
}
// }

return indexRgb;
}
Notes:

For performance reasons it's better to compare squares of distances rather then distances themselves.
There's no need to do indexRgb = taulaColor.indexOf(colorRgb) ;) Guess why.

Cesar
15th February 2006, 08:28
Oops, it seems I've forgotten to take alpha channel in account. I suppose you can do it youself.

SkripT
15th February 2006, 15:15
Another time thanks a lot Cesar, your suggestions are very helpfully :) It's a great idea to calculate a similar color in the table using the distance between two points, I didn't remember that function :D . I will try it and I comment the results.

PD: did you forget to calculate the distance using squares in the function 'obtenirIndexColor'?

diff = dR * dR + dG * dG + dB * dB;
The results of calculating the distance multiplying each term with itself are the same that calculating it as I did with the absolute value, no?

Cesar
16th February 2006, 07:54
Another time thanks a lot Cesar, your suggestions are very helpfully It's a great idea to calculate a similar color in the table using the distance between two points, I didn't remember that function . I will try it and I comment the results.
You are always welcome :)

The results of calculating the distance multiplying each term with itself are the same that calculating it as I did with the absolute value, no?
No I didn't. Consider the example:
The point to approximate: P(100, 100, 100).
Two points to choose betwean: A1(75, 125, 100) and A2(100, 51, 100)


diffSkript(P, A1) = |100 - 75| + |100 - 125| + |100 - 100| = 25 + 25 = 50
diffSkript(P, A2) = |100 - 100| + |100 - 51| + |100 - 100| = 49
diffCesar(P, A1) = (100 - 75)*(100 - 75) + (100 - 125)*(100 - 125) + (100 - 100)*(100 - 100) = 625 + 625 = 1250
diffCesar(P, A2) = (100 - 100)*(100 - 100) + (100 - 51)*(100 - 51) + (100 - 100)*(100 - 100) = 49*49 = 2401

As you can see, the results differ. Which one is the best? You choose :)

SkripT
16th February 2006, 16:36
You' re right Cesar, the results differ!! I will try some colors and see which one is more aproximated.

Cesar
16th February 2006, 23:08
Go ahead! :) And please, post the results, 'cos I'm too curious :)

SkripT
17th February 2006, 13:07
Hi, Cesar I have tested a little both versions. In many colors they give me the same results. But, in other cases, your version give me results that looks similar to the original color. Here are two examples:

Color to aproximate: r:85, g:0, b:255
Cesar: r:31, g:82, b:138
SkripT: r:79, g:110, b:140

Color to aproximate: r:255, g:85, b:255
Cesar: r:245, g:207, b:178
SkripT: r:255, g:255, b:255

In other cases where they gave me different results, was difficult to say which of one was closer to the original color because is a very subjective fact. Finally I maintain your version because looks more professional calculating the difference with the distance between points :D

Cheers.