Weird QImage pixel manipulation issue.



  • Hello,

    I've encountered a weird bug while inverting an image, editing it afterwards, converting it to a QPixmap and finally drawing it to screen.

    This is the code for manipulating the image:

    QPixmap modify(const QImage &source) {
         QImage inv = source.copy();
         inv.invertPixels();
         
         uint *ptr = (uint *)inv.bits();
         uint *end = inv.bits() + (inv.byteCount() / 4);
         while (ptr < end) {
              *ptr++ += 0x00333333;
         }
    
         return QPixmap::fromImage(inv);
    }
    

    and this one draws the QPixmap to screen

    void XYZ::paintEvent(QPaintEvent *event) {
        QPainter painter(this);
        painter.drawPixmap(m_ImagePos, m_MyPixmap);
    }
    

    results in

    alt text

    The background is completely messed up, it seems to render random pixels there and whenever I hover it with the mouse, it seems to execute the "leaveEvent" of the underlying button. If I click into it, it focuses the QtCreator instance displayed below my application.

    Has anyone an idea what the issue could be?
    Thanks in advance.

    PS: It results in the same issue if I manipulate the image using scanlines and qRgba(...) for manipulating the pixel.


  • Moderators

    @Nicolas-Kogler said in Weird QImage pixel manipulation issue.:

    uint *end = (uint *)(inv.bits() + inv.byteCount());

    You're trying to modify pixel data, but you use byteCount() - I think this is wrong. An RGBA pixmap with 100 pixels will consume 400 bytes. You're most probably writing outside of your pixmap which is undefined behaviour.
    You can use size() to calculate number of pixels.



  • Hello,

    sorry, my actual code was different. The image is assured to be 32bpp, therefore I actually used

    uint *end = ptr + (inv.byteCount() / 4);
    

    but the issue remains. I tried to save my QPixmap to file before rendering it and it looks just fine, that means my manipulation can not be the issue here. :(


  • Moderators

    @Nicolas-Kogler Does your pixmap have a transparent background?



  • @jsulm said in Weird QImage pixel manipulation issue.:

    @Nicolas-Kogler Does your pixmap have a transparent background?

    Indeed. It needs to have a transparent background for the algorithm to work dynamically and in every situation possible. Is that an issue?

    I believe that semi-transparency is the issue by now, but forcing the background to be white (QPainter::setBackground & QPainter::setBackgroundMode) in the paintEvent didn't help either.

    Of course, I could blit the QPixmap on a plain white image and draw that in the end, but this is kinda hacky and also quite costly when done multiple times or with larger images.


  • Qt Champions 2016

    @Nicolas-Kogler
    Just iterate the image through the API instead of the internal data. E.g.:

    QPixmap modify(const QImage &source)
    {
         qint32 width = source.width(), height = source.height();
         QImage result(width, height, source.format());
         for (qint32 y = 0, y < height; y++)  {
              for (qint32 x = 0; x < width; x++)  {
                  QRgb pixel = source.pixel(x, y);
    
                  Q_ASSERT(0x132 - qRed(pixel) <= 0xFF && 0x132 - qGreen(pixel) <= 0xFF && 0x132 - qBlue(pixel) <= 0xFF);
                  pixel = qRgb(0x132 - qRed(pixel), 0x132 - qGreen(pixel), 0x132 - qBlue(pixel));
                  result.setPixel(x, y, pixel);
              }
         }
    
         return QPixmap::fromImage(result);
    }
    

    And I don't understand what this is supposed to do exactly:

    *ptr++ += 0x00333333;
    

    why add 0x00333333, what's the significance? You do understand that there will be shifting if the value overflows ...?



  • Hello kshegunov,

    isn't setPixel costly? To answer your question: Only pure black images are passed to this function. The loop continuously fetches the RGBA data (i.e. 0xFF000000) and adds 0x00333333 to it -> 0xFF333333, results in a slightly brighter picture.

    I will try your method nevertheless and set this topic as "solved" if I don't find any better one. Thanks!



  • If that is the only case then it's much more efficient to use the colour table

    inv.convertToFormat(QImage::Format_Indexed8);
    const int colCount=inv.colorCount();
    for(int i=0;i<colCount;++i){
    if(inv.color(i)==qRgba(0,0,0,0xFF)){
    inv.setColor(i,qRgba(0x33,0x33,0x33,0xFF));
    break;
    }
    }
    


  • That actually is a great idea VRonin, as flat-styled images will never really contain more than 256 colors!

    Solved :).


  • Qt Champions 2016

    @Nicolas-Kogler said in Weird QImage pixel manipulation issue.:

    isn't setPixel costly?

    Depends on your definition of costly. A function call, couple of ifs and a switch I don't consider to be much on the costly side.



  • We are not necessarily talking about small images here. I believe setPixel is costly when big images are involved. But thanks for that solution, too. May come in handy for small images later! :)


  • Qt Champions 2016

    As one Donald Knuth once notably remarked:
    "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil."

    Which I happen to agree with. So before you know that the call to setPixel() is a bottleneck I advise you just forget this (really) tiny inefficiency.



  • @kshegunov said in Weird QImage pixel manipulation issue.:

    As one Donald Knuth once notably remarked:
    "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil."

    Which I happen to agree with. So before you know that the call to setPixel() is a bottleneck I advise you just forget this (really) tiny inefficiency.

    I agree, but as Niklaus Wirth once stated:
    'Software is getting slower more rapidly than hardware is getting faster.'
    I tested it now, both algorithms work and the method with 'scanline' and 'bits' is faster, so why wouldn't I take the faster one?

    btt:
    I finally found the issue (I felt like this could be it for a while) for the weird rendering, but I cannot really explain why it happened, given the fact that 'c' is copied in invertImage():

    void setImage(const QPixmap &img) {
        QImage c = img.toImage();
        // ... do some pixel fetching (but not manipulating) ...
        m_pm = QPixmap::fromImage(invertImage(c));
    }
    

    now with

    void setImage(const QImage &c) {
        // ... do some pixel fetching ...
        m_pm = QPixmap::fromImage(invertImage(c));
    }
    

    it just works fine.


  • Qt Champions 2016

    @Nicolas-Kogler said in Weird QImage pixel manipulation issue.:

    I tested it now, both algorithms work and the method with 'scanline' and 'bits' is faster, so why wouldn't I take the faster one?

    Few reasons (whether they're good is up to you to decide):

    1. It depends on the internal representation of the data inside QImage which isn't guaranteed to be compatible between versions, while the Qt API is binary compatible between minor versions (that translates to years).
    2. It's a (tragic) fact of life that code is read much more than it's written, so one'd be wise to opt for more readable (and type-safe) code as every opportunity presents itself.
    3. Faster is a relative term - faster compared to what? Knuth's whole point is that you can spend months making a piece of code to run 10% faster, but if that piece of code carries 10% of the total execution time, in reality you've optimized only to remove a meager 1% of execution time. Ultimately, it boils down to profiling, finding the bottlenecks and finally removing them.

    PS.
    Talking about micro optimizations I'd suggest changing:

    inv.byteCount() / 4
    

    to:

    inv.byteCount() >> 2
    

    it isn't that clear and pretty though, is it?



  • Bitshifting is just plain beautiful <3

    Jokes aside, of course you are totally correct. I implemented the same algorithm twice because I thought that my initial issue was caused by that. Anyways, now I am just using pixel and setPixel to interact with the data, because I agree on the readability reason with you.

    @kshegunov said in Weird QImage pixel manipulation issue.:

    Faster is a relative term - faster compared to what? Knuth's whole point is that you can spend months making a piece of code to run 10% faster, but if that piece of code carries 10% of the total execution time, in reality you've optimized only to remove a meager 1% of execution time. Ultimately, it boils down to profiling, finding the bottlenecks and finally removing them.

    This is also a good point, but I guess it only applies to companies which actually only have spare development time. This is a hobby project, therefore I have plenty of time to try different stuff and play around with the features of Qt. Not to mention that I am 18 years old only and need to gain experience working with the Qt framework.

    We shouldn't abuse this thread for these kinds of discussions anymore. You can always send me a private message, if you want. :)


  • Qt Champions 2016

    @Nicolas-Kogler said in Weird QImage pixel manipulation issue.:

    We shouldn't abuse this thread for these kinds of discussions anymore.

    I tend to wander off, so that happens to me a lot ... sorry.



  • Sorry to put this back on top, but for anyone who has the same problem as I had:

    The reason for the messed-up background was that after copying the image, Qt somehow converted it to a premultiplied-alpha format. Hence attempting to edit the pixels was disastrous. A call to 'convertToFormat(QImage::Format_ARGB32)' after 'copy()' should do it :).


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.