3

I am trying to modify an image using Qt with the scanLine() method. This methods return a pointer to the data of a given row. I founded how to read rows here. Right now, I am able to read the value of all pixels like this:

QRgb ** pixels;

pixels = (QRgb **) (malloc(sizeof (QRgb*) * img->width() * img->height()));

#pragma omp parallel for
for (int y = 0; y < img->height(); ++y) {
    pixels[y] = (QRgb*) img->scanLine(y);
}

for (int x = 0; x < img->width(); ++x) {
    for (int y = 0; y < img->height(); ++y) {
        int color =  qRed(pixels[y][x]);
        std::cout << "Pixel at " << x << ", " << y << " is " << color << std::endl;
    }
}

Then, I process each pixel and try to add those in a new QRgb ** variable, but this is where the program fails while executing.

QRgb ** finalPixels;

finalPixels = (QRgb **) (malloc(sizeof (QRgb*) * img->width() * img->height()));

    for (int x = 0; x < img->width(); ++x) {
        for (int y = 0; y < img->height(); ++y) {

            // Process pixels[y][x]


            QColor final(/* some new value */);
            QRgb finalrgb = final.rgb();

            finalPixels[y][x] = finalrgb; // What I realy want to do - Make the program fail
            finalPixels[y][x] = &finalrgb; // Don't build : invalid conversion from ‘QRgb* {aka unsigned int*}’ to ‘QRgb {aka unsigned int}’
            finalPixels[y][x] = pixels[y][x]; // Make the program fail


        }
    }

I don't understand why I can't change the reference of finalPixels[y][x] to a new reference. Is the type of the variable incorrect ? Or this is not how to do ? I readed some stuff about 2 dimensional arrays and pointers but still I can't figure out what is the problem here.

EDIT

@rames answered to this question by proposing to use pixel() and setPixel() methods. These methods are much easier to use, but this is not what I'm looking for. My first implementation was using these methods but as the setPixel() method's documentation says:

Warning: This function is expensive due to the call of the internal detach() function called within; if performance is a concern, we recommend the use of scanLine() to access pixel data directly.

And since my goal is to apply filters to an image like blur, edge detection, I need performance, so this is why I'm trying to use scanLine().


I tried to change my variable type. And then simply change the pixels' color like this:

QRgb * pixels[img->height()];


#pragma omp parallel 
for (int y = 0; y < img->height(); ++y)
   pixels[y] = (QRgb*) img->scanLine();
}

for (int x = 0; x < img->width(); ++x) {
  for (int y = 0; y < img->height(); ++y) {
      QColor c(0,0,0);
      QRgb cr = c.rgb();
      pixels[y][x] = cr;
  }
}

But even this is failing when the program runs pixels[y][x] = cr; and I don't understand why. The output of QtCreator is he program has unexpectedly finished..


Ok so I know how to modify the pixels of an image with the scanLine() method thanks to @user3528438 and @Rames. But still I can't find a way to get all the pixels in a variable. My goal is to have a temp variable, this way, I can compute a modification on the image with original pixels. Here is the last thing I tried:

QRgb * pixelsCopy[img->height()][img->width()];
QRgb * pColor;

for (int y = 0; y < img->height(); ++y) {
    for (int x = 0; x < img->width(); ++x) {
        pColor = new QRgb( (QRgb)img->scanLine(y)[x] );

        pixelsCopy[y][x] = pColor;
    }
}

for (int x = 0; x < img->width(); ++x) {
    for (int y = 0; y < img->height(); ++y) {
        int color =  qRed(*pixelsCopy[y][x]); // Return 0
        std::cout << "Pixel at " << x << ", " << y << " is " << color << std::endl;
    }
} 

This compile and run well, but all values are 0.. If I compare to the original pixels, this is not the case. Can you explain to me why my values are not the original onces and are all set to 0 in my *pixelsCopy variable ? And also, isn't is too heavy to call scanLine() method for each pixel ? I also tried to change *pixelsCopy to pixelsCopy, but I still get 0 values..

EDIT 2

Thanks to @user3528438 and @Rames, I finally found a way to copy pixels into a new variable, but I am getting weird results. I wrote a code that copy the pixels and reapply those to the image and I am not getting the same image.

QRgb pixelsCopy[img->height()][img->width()];

for (int y = 0; y < img->height(); ++y) {
    QRgb * line = reinterpret_cast<QRgb *>(img->scanLine(y));
    for (int x = 0; x < img->width(); ++x) {
        pixelsCopy[y][x] = line[x];
    }
}

for (int x = 0; x < img->width(); ++x) {
    for (int y = 0; y < img->height(); ++y) {
        int r = qRed(pixelsCopy[y][x]);
        QColor final(r, r, r);
        img->scanLine(y)[x] = final.rgb();

    }
}

And this is the before and after: enter image description here

It looks like a coordinate mistake but I checked multiple times and saw nothing.. If you have a faster and/or cleaner way to copy the original pixels, It will be nice to advice me !

Community
  • 1
  • 1
oktomus
  • 566
  • 7
  • 28
  • `the program fails` is not much to work with. How can you tell that it has failed? – Dale Wilson Nov 14 '16 at 22:02
  • the way you allocate the memory is horribly wrong. and the decision to use `**` is questionable, too. i suggest not to use `**` in this case unless there are very compelling reasons. – user3528438 Nov 14 '16 at 22:21
  • @DaleWilson When I run the application with QtCreator, this is the only output that I get. I tested and noticed that it cames from here. – oktomus Nov 15 '16 at 09:57
  • @user3528438 Then what should I use ? `QRgb pixels[width][height];` ? But if I do this, do I have to allocate memory ? – oktomus Nov 15 '16 at 09:58
  • It depends on your usage, or what's the scope/lifetime you want it to have. – user3528438 Nov 15 '16 at 12:27
  • @user3528438 I don't know, what is the best for a use in a method ? In the future, I would set this variable as an attribute of a class which inherit from QImage, like this, I don't have to do the work each time I want to work on the pixels. – oktomus Nov 15 '16 at 12:30
  • You should use the pointer returned from `scanLine` or `bits` directly instead of creating a new array or allocating new memory. Still, manipulating pixels this way, and inheriting from QImage, are both questionable designs. Consider `QPainter`? Your second solution seems it should work, except that converting the pointer to `QRGB*` does not look right. Also the openmp does not seem necessary, nor safe. – user3528438 Nov 15 '16 at 13:09
  • @user3528438 If I use `scanLine` directly, wouldn't it be too heavy to call the method for each pixel ? (Like this `QRgb color = img->scanLine(y)[x]`). And why the conversion is does not look right in the second solution ? I though that cast was ok since I am able to read the colors. I also removed openmp. Thanks ! – oktomus Nov 16 '16 at 10:37
  • I tested `img->scanLine(y)[x] = final.rgb();` and it works perfectly, but I am afraid that it will be too slow. Moreover, I have to make some filter that need to create a temp variable of all pixels, so I will still be stuck with that problem.. – oktomus Nov 16 '16 at 10:41

2 Answers2

2

Do not use an array. The QImage class is designed for pixel manipulation and you really should use this one. Assuming that img variable is of type QImage *:

QImage finalImage(*img);
for (int y = 0; y < finalImage.height(); ++y) {
    QRgb *line = reinterpret_cast<QRgb*>(finalImage.scanLine(y));
    for (int x = 0; x < finalImage.width(); ++x) {
        QRgb pixelRgb = line[x];
        // Process pixelRgb

        QColor final(/* some new value */);
        line[x] = final.rgb();
    }
}
Rames
  • 918
  • 11
  • 27
  • no this is what I used before and it was working but this is too **heavy and slow**. As said in [`setPixel` documentation](http://doc.qt.io/qt-4.8/qimage.html#setPixel): `This function is expensive due to the call of the internal detach() function called within; if performance is a concern, we recommend the use of scanLine() to access pixel data directly.` – oktomus Nov 15 '16 at 10:01
  • And there is no `setPixelColor` method – oktomus Nov 15 '16 at 12:06
  • @KevinM `setPixelColor` was introduced in Qt 5.6. You haven't explicitly mentioned that you are using Qt 4.8. As far as performance is concerned I will try to show you the quicker version in next edit. – Rames Nov 15 '16 at 13:01
  • Thanks for the edit, I tried your code and it doesn't compile for me. I get this error : `error: invalid static_cast from type ‘uchar* {aka unsigned char*}’ to type ‘QRgb* {aka unsigned int*}’` Do you know why ? – oktomus Nov 16 '16 at 10:35
  • @KevinM Updated, changed type of cast. If this line still doesn't compile (though it really should), try to change it to `QRgb *line = (QRgb*)(finalImage.scanLine(y));` – Rames Nov 16 '16 at 14:15
  • This cast doesn't work neither. I'm looking for a way to store all scanLine in one variable, and if possible, copy the values to keep an original copy of the pixels – oktomus Nov 16 '16 at 20:30
  • @KevinM The cast is correct. What do you mean by doesn't work? And in my code snippet you keep original image under `img` variable. – Rames Nov 16 '16 at 20:39
2

Since my question was a bit too wide, I created a topic on the Qt Forum and Chris Kawa answered precisely to my problem.

Copy the pixels into another variable

int size = img->height() * img->width();
QRgb* data = new QRgb[size]; //don't forget to delete it somewhere
memmove(data, img.bits(), img.height() * img.width() * sizeof(QRgb));
// We don't need to copy each pixel, that's slow

Process each pixel

We can read each pixel by incrementing x and y.

for (int y = 0; y < img->height(); ++y) {
   for (int x = 0; x < img->width(); ++x) {

But this is going to be slow, it will be much faster to use pointers.

QRgb* ptr = data;
QRgb* end = ptr + img.width() * img.height();
for (; ptr < end; ++ptr)
    *ptr = qRgb(qRed(*ptr), qRed(*ptr), qRed(*ptr));

STD Style : Copy the pixels into another variable

//copy
std::vector<QRgb> pixels;
pixels.resize(img.height() * img.width());
memmove(pixels.data(), img.bits(), img.height() * img.width() * sizeof(QRgb));

STD Style : Process each pixel

std::for_each(pixels.begin(), pixels.end(), [](QRgb& c) { c = qRgb(qRed(c), qRed(c), qRed(c)); });

In c++17 you'll even be able to parallelize and vertorize it easily like this:

std::for_each(std::execution::parallel_unsequenced_policy, 
              pixels.begin(), pixels.end(), [](QRgb& c) { c = qRgb(qRed(c), qRed(c), qRed(c)); });

Thank Chris Kawa for the help, all the code above was given by him on the topic I created on the Qt Forum.

oktomus
  • 566
  • 7
  • 28