Search code examples
c++imageqtmonochrome

Printing/saving QGraphicsScene to monochrome image very poor quality


I would like to print/save the QGraphicsScene in Mono format. I made the scene contain only a single color object (in one of the windows, it actually is black and white).

OutputView::OutputView(QWidget *parent)
    : QGraphicsView(parent) {...}


void OutputView::saveToImage() 
{
    QImage image(scene()->sceneRect().size().toSize(), QImage::Format_Mono);
    image.fill(Qt::transparent);
    QPainter painter(&image);
    painter.setRenderHint(QPainter::Antialiasing); // seems to do nothing
    render(&painter);
    image.save("output.png");
    painter.end();

    // to test rgb, i am adding that - but I really need only mono
    // I don't know why the background is black, but since I don't intend to use the color version, it is not relevant
    QImage image1(scene()->sceneRect().size().toSize(), QImage::Format_RGB32);
    image1.fill(Qt::transparent);
    QPainter painter1(&image1);
    render(&painter1);
    image1.save("output1.png");
    painter1.end();
}

The quality of the black-and-white image is very poor !

I expected red pixels to become black, but only some of them did. For the 4th pane, I expected all the black pixels to be placed as black in the mono output, but again, only some of them did.

enter image description here

Is there any way to make a black-and-white image with black pixels where the object has color, and white elsewhere ?

I can easily change the code that generates the color sections, to output black instead of the original color, but that doesn't seem to solve the problem (the 4th panel has black color, but the mono image doesn't contain all the pixels).

I need mono because of the memory cost...

How can I make the scenes output correctly into QImage ?

Update1: I even tried to dither the color image - the result was scary (too light in areas that should be darker, weird artifacts, plus I am adding an extra step that is time consuming, and worst of all, adding a very large rgb image... which is exactly what i am trying to avoid, I need monochrome image because of very limited resources)

    QImage image(scene()->sceneRect().size().toSize(), QImage::Format_RGB32);
    image.fill(Qt::transparent);
    QPainter painter(&image);
    painter.setRenderHint(QPainter::Antialiasing); // seems to do nothing
    render(&painter);
    painter.end();
    QImage image11 = image.convertToFormat(QImage::Format_Mono,
              Qt::MonoOnly | Qt::DiffuseDither);
    image1.save("output.png");

enter image description here

Playing with the built-in qt dither methods, I see that the rendering to mono image uses Qt::ThresholdDither.

If at least I could apply a different dither to the rendering, in a single step, without having to first build a color image and then dither to a new monochrome image, would be a huge improvement. Memory and time are both very important.

Though perhaps what I really need is a fast way to go through every pixel and set it to 1 if its intensity is above .5 and to 0 if below ? That would still mean a separate step after creating the rgb32 image though... and for some reason doing it "by hand" is always slower than the built-in methods.

Update2: the item painted in this example is a rectangle with a gradient brush. I also separated colors into the 4 QGraphicsScenes: if color == color1 then color2,3,4 = Qt::white and so on.

void Item::paint(QPainter *painter, const QStyleOptionGraphicsItem */*option*/, QWidget */*widget*/)
{
    QRadialGradient radialGradient(0, 0, 300, 100, 100);
    radialGradient.setColorAt(0.0, color1);
    radialGradient.setColorAt(0.27, color2);
    radialGradient.setColorAt(0.44, Qt::white);
    radialGradient.setColorAt(0.76, color3);
    radialGradient.setColorAt(1.0, color4);
    QBrush brush = QBrush(radialGradient);
    painter->setBrush(brush);
    painter->drawRect(boundingRect());
}

Solution

  • For now my solution is:

    • for each item on the scene, if it requires to be converted to an image, place it on scene along with all items that collide and are above it

    • save the item rectangle clip of the scene to a color image

    • convert the image to monochrome using Qt::OrderedDither, which creates fewer artifacts

    • place the image in place of the item on the scene

    • convert the scene no monochrome using the default (Qt::ThresholdDither)

    This is likely to use less memory (depending on how large he items are) - but has a high chance of applying the same process repeatedly, and storing in image format, of areas that are on top of each other.