1

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.

Shun
  • 107
  • 10
  • I scrolled through the doc. of [`QBrush`](http://doc.qt.io/qt-5/qbrush.html) but there was nothing mentioned like a color matrix. There is [`QBrush::setMatrix()`](http://doc.qt.io/qt-5/qbrush.html#setMatrix) which is about local brush transformation (i.e. concerning coordinates but not colors). However, it's a [`QPixmap`](http://doc.qt.io/qt-5/qpixmap.html) which is used in [`QBrush::setTexture()`](http://doc.qt.io/qt-5/qbrush.html#setTexture). So, you may make a copy and modify it according to your needs, can't you. – Scheff's Cat Aug 21 '18 at 07:04
  • In case that I missed something (I'm still a learner as well), you may ask the author of [here](https://stackoverflow.com/a/49298313/7117846) to elaborate this color matrix idea. Even if he provides the sample in C#, you may notify me and I would be glad to help (out of curiosity). ;-) – Scheff's Cat Aug 21 '18 at 07:06
  • @Scheff I have not enough reputation to add a comment in his answer...so I ask this question. – Shun Aug 21 '18 at 07:15
  • Sorry, I didn't consider. Have it done by myself: [Comment for Creating different brush patterns in c#](https://stackoverflow.com/questions/49290951/creating-different-brush-patterns-in-c-sharp/49298313#comment90839954_49298313) – Scheff's Cat Aug 21 '18 at 09:12
  • @Scheff thanks, i see his newest comment, and scrolled through the doc. of Qt graphics , it seems that have not such `ColorMatrix` and `ImageAttributes` class in QT. – Shun Aug 21 '18 at 09:47
  • This approach with color matrix is awesome. I will have a look into Qt doc. again. May be, something is available what I missed until now. If not - with the description, I might be able to add something which can change the contents of `QImage` or `QPixmap`. Unfortunately, I've to do some work I'm paid for. ;-) So, it may take some hours until I can provide an answer... – Scheff's Cat Aug 21 '18 at 09:55
  • Thinking twice, my first comment was not that bad. I believe it will become something like: Convert original `QPixmap` with color matrix to a modified `QPixmap`. Use modified `QPixmap` for `QBrush`. (Instead of `QPixmap`, a `QImage` can be used as well.) – Scheff's Cat Aug 21 '18 at 09:59
  • @Scheff I think we can get `QImage` pixel of each point, and then for each multiply with custom 5x5 matrix, put it back to image. – Shun Aug 21 '18 at 10:34
  • This is actually what I intend to do. – Scheff's Cat Aug 21 '18 at 10:34

1 Answers1

1

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

Scheff's Cat
  • 16,517
  • 5
  • 25
  • 45
  • @Shun I've extended the sample a bit and stored it on github. The link is added to the end of my answer (in case you're interested). – Scheff's Cat Aug 22 '18 at 14:19