Shachar Shemesh
Shachar Shemesh

Reputation: 8583

"Drawing" with a totally transparent pen in QT

I am writing a whiteboard application in QT. I am using a double layer approach, so I have a QPixmap that contains the drawing, and another that contains the background. The drawing pixmap is, unsurprisingly, with an alpha channel.

Now I wish to implement an eraser tool. This tool should revert, wherever it paints, the pixmap's color to QColor(255, 255, 255, 0) (i.e. - totally transparent). My method of painting, however, does not lend well to that.

This is my draw routine:

void WhiteBoardWidget::draw(QPointF pos, bool erase) {
    QPainter painter( &underlyingImage );
    QPen pen = painter.pen();

    if( ! erase ) {
        pen.setColor(penColor);
        pen.setWidth(penWidth);
    } else {
        pen.setColor( QColor(255, 255, 255, 0) );
        pen.setWidth(penWidth * EraserSizeFactor);
    }

    painter.setPen(pen);
    painter.drawLine( lastPoint, pos );
    lastPoint = pos;
    update();
}

I understand why it doesn't work (the transparent pen doesn't change the pixmap because it is, wait for it... transparent). I'm just not sure what would be a good way to do what I want.

Upvotes: 3

Views: 1659

Answers (2)

Aconcagua
Aconcagua

Reputation: 25536

This is a matter of composition mode of the QPainter in use. Default is QPainter::CompositionMode_SourceOver, which, as current pen is transparent, just leaves the underground as is. By setting to QPainter::CompositionMode_Clear you enforce the painter to erase anything. You shouldn't even have to change the current pen's colour then.

Upvotes: 2

eyllanesc
eyllanesc

Reputation: 244162

Based on my previous answer I will just translate the code to

#ifndef DRAWER_H
#define DRAWER_H

#include <QWidget>

enum STATES{
    DRAW_STATE,
    CLEAR_STATE
};

class Drawer : public QWidget
{
    Q_OBJECT
    Q_PROPERTY(STATES currentState READ currentState WRITE setCurrentState NOTIFY currentStateChanged)
public:
    explicit Drawer(QWidget *parent = nullptr);
    STATES currentState() const;
    void setCurrentState(STATES newCurrentState);
Q_SIGNALS:
    void currentStateChanged();

protected:
    void paintEvent(QPaintEvent *);
    void resizeEvent(QResizeEvent *);
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
private:
    QImage background;
    QImage foreground;
    QPoint lastPoint;
    QBrush brushColor;
    int brushSize;
    bool drawing;
    int clearSize;

    STATES m_currentState;
};

#endif // DRAWER_H
#include "drawer.h"

#include <QApplication>
#include <QImage>
#include <QMouseEvent>
#include <QPainter>

Drawer::Drawer(QWidget *parent) :
    QWidget(parent), brushColor(Qt::black), brushSize(2), drawing(false), clearSize(10), m_currentState(DRAW_STATE)
{
    background = QImage(100, 100, QImage::Format_ARGB32);
    background.fill(Qt::white);
    foreground = QImage(100, 100, QImage::Format_ARGB32);
    foreground.fill(Qt::transparent);
}
STATES Drawer::currentState() const
{
    return m_currentState;
}

void Drawer::setCurrentState(STATES newCurrentState)
{
    if (m_currentState == newCurrentState)
        return;
    m_currentState = newCurrentState;
    Q_EMIT currentStateChanged();
    if(m_currentState == CLEAR_STATE){
        QPixmap pixmap(QSize(1, 1)* clearSize);
        pixmap.fill(Qt::transparent);
        QPainter painter(&pixmap);
        painter.setPen(QPen(Qt::black, 2));
        painter.drawRect(pixmap.rect());
        painter.end();
        QCursor cursor(pixmap);
        QApplication::setOverrideCursor(cursor);
    }
    else if (m_currentState == DRAW_STATE) {
        QApplication::restoreOverrideCursor();
    }
}

void Drawer::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.drawImage(QPoint(0, 0), background);
    painter.drawImage(QPoint(0, 0), foreground);
}

void Drawer::resizeEvent(QResizeEvent *)
{
    bool changeSize = (width() > foreground.width()) || (height() > foreground.height());
    if(changeSize){
        QSize s(std::max(width(), background.width()), std::max(height(), background.height()));
        background = QImage(s, background.format());
        background.fill(Qt::white);
        QImage newForeground = QImage(s, foreground.format());
        newForeground.fill(Qt::transparent);
        QPainter painter(&newForeground);
        painter.drawImage(QPoint(0, 0), foreground);
        painter.end();
        foreground = newForeground;
    }
}

void Drawer::mousePressEvent(QMouseEvent *event)
{
    if(event->buttons() & Qt::LeftButton){
        drawing = true;
        lastPoint = event->pos();
        update();
    }
}
void Drawer::mouseMoveEvent(QMouseEvent *event)
{
    if(event->buttons() & Qt::LeftButton && drawing){
        QPainter painter(&foreground);
        painter.setPen(QPen(brushColor, brushSize, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
        if(m_currentState == CLEAR_STATE){
            QRect r(QPoint(), clearSize*QSize());
            r.moveCenter(event->pos());
            painter.save();
            painter.setCompositionMode(QPainter::CompositionMode_Clear);
            painter.eraseRect(r);
            painter.restore();
        }
        else if(m_currentState == DRAW_STATE){
            painter.drawLine(lastPoint, event->pos());
        }
        painter.end();
        lastPoint = event->pos();
        update();
    }
}
void Drawer::mouseReleaseEvent(QMouseEvent *event)
{
    if(event->buttons() & Qt::LeftButton){
        drawing = false;
        update();
    }
}
#include "drawer.h"

#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QPushButton *button = new QPushButton("Clear");
    button->setCheckable(true);
    Drawer *drawer = new Drawer;
    QObject::connect(button, &QPushButton::toggled, [drawer, button](bool checked){
        if(checked){
            drawer->setCurrentState(STATES::CLEAR_STATE);
            button->setText("Draw");
        }
        else{
            drawer->setCurrentState(STATES::DRAW_STATE);
            button->setText("Clear");
        }
    });
    QWidget widget;
    QVBoxLayout *lay = new QVBoxLayout(&widget);
    lay->addWidget(button);
    lay->addWidget(drawer);
    widget.resize(640, 480);
    widget.show();
    return a.exec();
}

Upvotes: 0

Related Questions