Search code examples
qtdrawingtextures

QPainter how draw texture with special color


I have some patterns which are black with alpha and have some points that I want to draw line with patterns.

I find QBrush can be constructed by texture, but I don't know how to draw it with difference colors.

This answer show a way here in C# code, but I don't know how to change patterns color with ColorMatrix.


Solution

  • The modification of RGBA values of an image using a 5×5 color matrix reminds me to the transformation of homogeneous coordinates how it is often used in computer graphics. If you imagine the RGBA values as 4-dimensional color/alpha space the transformation of colors using transformation matrices doesn't sound that revolutionary. (Not that you got me wrong – this impressed me much, and I couldn't resist to try this out immediately.) Hence, I didn't wonder why a 5×5 matrix is needed though there are only 4 color components. (E.g. if a translation of color values is intended the 5th dimension cames into play.)

    I must admit that I first applied my knowledge from Computer Animation to this problem and compared my approach to the one described on MSDN Using a Color Matrix to Transform a Single Color afterwards. Then I realized that the original paper uses transposed vectors and matrices compared to mine. This is just mathematics as

    (vT MT)T = v' = M v

    if I remember right.

    Practically, it means I have to use matrix rows as columns when I try to reproduce the samples of e.g. the ColorMatrix Guide. (This feels somehow right to me as it is exactly as we describe transformations in 3d space i.e. translation is the last column of the transformation matrix.)

    Snapshot of colorMatrix program

    The sample code:

    colorMatrix.h:

    #ifndef COLOR_MATRIX_H
    #define COLOR_MATRIX_H
    
    #include <algorithm>
    
    struct ColorMatrix {
      float values[5][5];
      ColorMatrix() { }
      ColorMatrix(const float(&values)[25])
      {
        std::copy(std::begin(values), std::end(values), (float*)this->values);
      }
      float (&operator[](unsigned i))[5] { return values[i]; }
      const float(&operator[](unsigned i) const)[5] { return values[i]; }
    };
    
    struct ColorVector {
      float values[5];
      ColorVector(const float(&values)[5])
      {
        std::copy(std::begin(values), std::end(values), (float*)this->values);
      }
      float& operator[](size_t i) { return values[i]; }
      const float& operator[](size_t i) const { return values[i]; }
    };
    
    #endif // COLOR_MATRIX_H
    

    colorMatrix.cc:

    #include <algorithm>
    
    #include <QtWidgets>
    
    #include "colorMatrix.h"
    #include "QColorMatrixView.h"
    
    ColorVector operator*(const ColorMatrix &m, const ColorVector &v)
    {
      return ColorVector({
        m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2] + m[0][3] * v[3] + m[0][4] * v[4],
        m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2] + m[1][3] * v[3] + m[1][4] * v[4],
        m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2] + m[2][3] * v[3] + m[2][4] * v[4],
        m[3][0] * v[0] + m[3][1] * v[1] + m[3][2] * v[2] + m[3][3] * v[3] + m[3][4] * v[4],
        m[4][0] * v[0] + m[4][1] * v[1] + m[4][2] * v[2] + m[4][3] * v[3] + m[4][4] * v[4]
      });
    }
    
    const ColorMatrix Identity({
      1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
      0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
      0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
      0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
      0.0f, 0.0f, 0.0f, 0.0f, 1.0f
    });
    
    template <typename T>
    T clamp(T value, T min, T max)
    {
      return value < min ? min
        : value > max ? max
        : value;
    }
    
    QRgb transform(const ColorMatrix &mat, const QRgb &color)
    {
      ColorVector vec({
        qRed(color) / 255.0f, qGreen(color) / 255.0f, qBlue(color) / 255.0f, qAlpha(color) / 255.0f, 1.0f });
      vec = mat * vec;
      if (vec[4] != 0.0f) {
        vec[0] /= vec[4]; vec[1] /= vec[4]; vec[2] /= vec[4]; vec[3] /= vec[4]; // vec[4] = 1.0f;
      }
      return qRgba(
        clamp<int>(255 * vec[0], 0, 255),
        clamp<int>(255 * vec[1], 0, 255),
        clamp<int>(255 * vec[2], 0, 255),
        clamp<int>(255 * vec[3], 0, 255));
    }
    
    QImage transform(const ColorMatrix &mat, const QImage &qImg)
    {
      const int w = qImg.width(), h = qImg.height();
      QImage qImgDst(w, h, qImg.format());
      for (int y = 0; y < h; ++y) for (int x = 0; x < w; ++x) {
        qImgDst.setPixel(x, y, transform(mat, qImg.pixel(x, y)));
      }
      return qImgDst;
    }
    
    QImage open(QWidget *pQParent)
    {
      return QImage(
        QFileDialog::getOpenFileName(pQParent,
          QString::fromUtf8("Open Image File"),
          QString()));
    }
    
    void update(
      QLabel &qLblViewResult,
      const QColorMatrixView &qEditColMat, const QLabel &qLblViewOrig)
    {
      ColorMatrix colMat = qEditColMat.values();
      const QPixmap *pQPixmap = qLblViewOrig.pixmap();
      const QImage qImg = pQPixmap ? pQPixmap->toImage() : QImage();
      qLblViewResult.setPixmap(
        QPixmap::fromImage(transform(colMat, qImg)));
    }
    
    int main(int argc, char **argv)
    {
      QApplication app(argc, argv);
      // setup GUI
      QWidget qWin;
      qWin.setWindowTitle(QString::fromUtf8("Qt Color Matrix Demo"));
      QGridLayout qGrid;
      QVBoxLayout qVBoxColMat;
      QLabel qLblColMat(QString::fromUtf8("Color Matrix:"));
      qVBoxColMat.addWidget(&qLblColMat, 0);
      QColorMatrixView qEditColMat;
      qEditColMat.setValues(Identity);
      qVBoxColMat.addWidget(&qEditColMat);
      QPushButton qBtnReset(QString::fromUtf8("Identity"));
      qVBoxColMat.addWidget(&qBtnReset);
      QPushButton qBtnGray(QString::fromUtf8("Grayscale"));
      qVBoxColMat.addWidget(&qBtnGray);
      qVBoxColMat.addStretch(1);
      qGrid.addLayout(&qVBoxColMat, 0, 0, 2, 1);
      QLabel qLblX(QString::fromUtf8(" \xc3\x97 "));
      qGrid.addWidget(&qLblX, 0, 1);
      QLabel qLblViewOrig;
      qGrid.addWidget(&qLblViewOrig, 0, 2);
      QPushButton qBtnLoad(QString::fromUtf8("Open..."));
      qGrid.addWidget(&qBtnLoad, 1, 2);
      QLabel qLblEq(QString::fromUtf8(" = "));
      qGrid.addWidget(&qLblEq, 0, 3);
      QLabel qLblViewResult;
      qGrid.addWidget(&qLblViewResult, 0, 4);
      qWin.setLayout(&qGrid);
      qWin.show();
      // install signal handlers
      QObject::connect(&qEditColMat, &QColorMatrixView::editingFinished,
        [&]() { update(qLblViewResult, qEditColMat, qLblViewOrig); });
      QObject::connect(&qBtnReset, &QPushButton::clicked,
        [&]() {
          qEditColMat.setValues(Identity);
          update(qLblViewResult, qEditColMat, qLblViewOrig);
        });
      QObject::connect(&qBtnGray, &QPushButton::clicked,
        [&]() {
          qEditColMat.setValues(ColorMatrix({
            0.33f, 0.59f, 0.11f, 0.0f, 0.0f,
            0.33f, 0.59f, 0.11f, 0.0f, 0.0f,
            0.33f, 0.59f, 0.11f, 0.0f, 0.0f,
            0.00f, 0.00f, 0.00f, 1.0f, 0.0f,
            0.00f, 0.00f, 0.00f, 0.0f, 1.0f
          }));
          update(qLblViewResult, qEditColMat, qLblViewOrig);
        });
      QObject::connect(&qBtnLoad, &QPushButton::clicked,
        [&]() {
          qLblViewOrig.setPixmap(QPixmap::fromImage(open(&qBtnLoad)));
          update(qLblViewResult, qEditColMat, qLblViewOrig);
        });
      // initial contents
      {
        QImage qImg("colorMatrixDefault.jpg");
        qLblViewOrig.setPixmap(QPixmap::fromImage(qImg));
        update(qLblViewResult, qEditColMat, qLblViewOrig);
      }
      // runtime loop
      return app.exec();
    }
    

    QColorMatrixView.h:

    #ifndef Q_COLOR_MATRIX_VIEW_H
    #define Q_COLOR_MATRIX_VIEW_H
    
    #include <QLineEdit>
    #include <QGridLayout>
    #include <QWidget>
    
    #include "colorMatrix.h"
    
    class QColorMatrixView: public QWidget {
      Q_OBJECT
      private:
        QGridLayout _qGrid;
        QLineEdit _qEdit[5][5];
      signals:
        void editingFinished();
      public:
        QColorMatrixView(QWidget *pQParent = nullptr);
        virtual ~QColorMatrixView() = default;
        QColorMatrixView(const QColorMatrixView&) = delete;
        QColorMatrixView& operator=(const QColorMatrixView&) = delete;
        ColorMatrix values() const;
        void setValues(const ColorMatrix &mat);
    };
    
    #endif // Q_COLOR_MATRIX_VIEW_H
    

    QColorMatrixView.cc:

    #include "QColorMatrixView.h"
    
    QColorMatrixView::QColorMatrixView(QWidget *pQParent):
      QWidget(pQParent)
    {
      QFontMetrics qFontMetrics(font());
      const int w = qFontMetrics.boundingRect(QString("-000.000")).width() + 10;
      for (int r = 0; r < 5; ++r) {
        for (int c = 0; c < 5; ++c) {
          QLineEdit &qEdit = _qEdit[r][c];
          _qGrid.addWidget(&qEdit, r, c);
          qEdit.setFixedWidth(w);
          QObject::connect(&qEdit, &QLineEdit::editingFinished,
            [this, r, c]() {
            _qEdit[r][c].setText(
              QString::number(_qEdit[r][c].text().toFloat(), 'f', 3));
            editingFinished();
          });
        }
      }
      setLayout(&_qGrid);
    }
    
    ColorMatrix QColorMatrixView::values() const
    {
      ColorMatrix mat;
      for (int r = 0; r < 5; ++r) for (int c = 0; c < 5; ++c) {
        mat[r][c] = _qEdit[r][c].text().toFloat();
      }
      return mat;
    }
    
    void QColorMatrixView::setValues(const ColorMatrix &mat)
    {
      for (int r = 0; r < 5; ++r) for (int c = 0; c < 5; ++c) {
        _qEdit[r][c].setText(QString::number(mat[r][c], 'f', 3));
      }
    }
    

    moc_colorMatrix.cc (to consider moc generated sources):

    #include "moc_QColorMatrixView.cpp"
    

    colorMatrix.pro (the qmake project file):

    SOURCES = colorMatrix.cc QColorMatrixView.cc
    HEADERS = colorMatrix.h QColorMatrixView.h
    SOURCES += moc_colorMatrix.cc
    MOC_DIR = .
    
    QT += widgets
    

    and the default sample image colorMatrixDefault.jpg if no (cat) photo file is at hand:

    Moritz the cat

    Although, I've developed and tested the application in VS2013, I built and tested also on cygwin to ensure that the qmake project is complete and self-standing:

    $ qmake-qt5 colorMatrix.pro
    
    $ make
    
    $ ./colorMatrix
    

    An enhanced version of this sample code can be found on github Qt Color Matrix Demo.

    Snapshot of Enhanced Demo on github