Warm tip: This article is reproduced from serverfault.com, please click

Setting pixel color of 8-bit grayscale image using pointer

发布于 2020-11-29 16:18:30

I have this code:

QImage grayImage = image.convertToFormat(QImage::Format_Grayscale8);
int size = grayImage.width() * grayImage.height();
QRgb *data = new QRgb[size];
memmove(data, grayImage.constBits(), size * sizeof(QRgb));

QRgb *ptr = data;
QRgb *end = ptr + size;
for (; ptr < end; ++ptr) {
    int gray = qGray(*ptr);
}

delete[] data;

It is based on this: https://stackoverflow.com/a/40740985/8257882

How can I set the color of a pixel using that pointer?

In addition, using qGray() and loading a "bigger" image seem to crash this.

This works:

int width = image.width();
int height = image.height();
for (int y = 0; y < height; ++y) {
    for (int x = 0; x < width; ++x) {
        image.setPixel(x, y, qRgba(0, 0, 0, 255));
    }
}

But it is slow when compared to explicitly manipulating the image data.

Edit

Ok, I have this code now:

for (int y = 0; y < height; ++y) {
    uchar *line = grayImage.scanLine(y);
    for (int x = 0; x < width; ++x) {
        int gray = qGray(line[x]);
        *(line + x) = uchar(gray);
        qInfo() << gray;
    }
}

And it seems to work. However, when I use an image that has only black and white colors and print the gray value, black color gives me 0 and white gives 39. How can I get the gray value in a range of 0-255?

Questioner
Deeds
Viewed
0
Marko Mahnič 2020-12-01 15:56:37

First of all you are copying too much data in this line:

memmove(data, grayImage.constBits(), size * sizeof(QRgb));

The size ob Qrgb is 4 bytes, but according to the documentation, the size of a Format_Grayscale8 pixel is only 8 bits or 1 byte. If you remove sizeof(QRgb) you should be copying the correct amount of bytes, assuming all the lines in the bitmap are consecutive (which, according to the documentation, they are not -- they are aligned to at minimum 32-bits, so you would have to account for that in size). The array data should not be of type Qrgb[size] but ucahr[size]. You can then modify data as you like. Finally, you will probably have to create a new QImage with one of the constructors that accept image bits as uchar and assign the new image to the old image:

auto newImage = QImage( data, image.width(), image.height(), QImage::Format_Grayscale8, ...);
grayImage = std::move( newImage );

But instead of copying image data, you could probably just modify grayImage directly by accessing its data through bits(), or even better, through scanLine(), maybe something like this:

int line, column;
auto pLine = grayImage.scanLine(line);
*(pLine + column) = uchar(grayValue);

EDIT:

According to scanLine documentation, the image is at least 32-bit aligned. So if your 8-bit grayScale image is 3 pixels wide, a new scan line will start every 4 bytes. If you have a 3x3 image, the total size of the memory required to hold the image pixels will be 12. The following code shows the required memory size:

int main() {
   auto image = QImage(3, 3, QImage::Format_Grayscale8);
   std::cout << image.bytesPerLine() * image.height() << "\n";
   return 0;
}

The fill method (setting all gray values to 0xC0) could be implemented like this:

   auto image = QImage(3, 3, QImage::Format_Grayscale8);
   uchar gray = 0xc0;
   for ( int i = 0; i < image.height(); ++i ) {
      auto pLine = image.scanLine( i );
      for ( int j = 0; j < image.width(); ++j )
         *pLine++ = gray;
   }