Mecanik
Mecanik

Reputation: 1049

QRawFont/QPainterPath reproducing icon

I`m trying to reproduce an icon from FontAwesome.

First I load the font and get the points:

int fa = QFontDatabase::addApplicationFont(":/fonts/fa-solid-900.ttf");
auto fas = QFontDatabase::applicationFontFamilies(fa).at(0);

QRawFont rfont = QRawFont::fromFont(fas);

qDebug() << rfont.pathForGlyph(0);

Result being:

QPainterPath: Element count=45
 -> MoveTo(x=10.5, y=-14)
 -> LineTo(x=1.5, y=-14)
 -> CurveTo(x=1.08333, y=-13.9792)
 -> CurveToData(x=0.729167, y=-13.8333)
 -> CurveToData(x=0.4375, y=-13.5625)
 -> CurveTo(x=0.166667, y=-13.2708)
 -> CurveToData(x=0.0208333, y=-12.9167)
 -> CurveToData(x=0, y=-12.5)
 -> LineTo(x=0, y=0.5)
 -> CurveTo(x=0.0208333, y=0.916667)
 -> CurveToData(x=0.166667, y=1.27083)
 -> CurveToData(x=0.4375, y=1.5625)
 -> CurveTo(x=0.729167, y=1.83333)
 -> CurveToData(x=1.08333, y=1.97917)
 -> CurveToData(x=1.5, y=2)
 -> LineTo(x=10.5, y=2)
 -> CurveTo(x=10.9167, y=1.97917)
 -> CurveToData(x=11.2708, y=1.83333)
 -> CurveToData(x=11.5625, y=1.5625)
 -> CurveTo(x=11.8333, y=1.27083)
 -> CurveToData(x=11.9792, y=0.916667)
 -> CurveToData(x=12, y=0.5)
 -> LineTo(x=12, y=-12.5)
 -> CurveTo(x=11.9792, y=-12.9167)
 -> CurveToData(x=11.8333, y=-13.2708)
 -> CurveToData(x=11.5625, y=-13.5625)
 -> CurveTo(x=11.2708, y=-13.8333)
 -> CurveToData(x=10.9167, y=-13.9792)
 -> CurveToData(x=10.5, y=-14)
 -> MoveTo(x=8.8125, y=-12)
 -> LineTo(x=6, y=-7.8125)
 -> LineTo(x=3.21875, y=-12)
 -> LineTo(x=8.8125, y=-12)
 -> MoveTo(x=2, y=-10.1875)
 -> LineTo(x=4.8125, y=-6)
 -> LineTo(x=2, y=-1.8125)
 -> LineTo(x=2, y=-10.1875)
 -> MoveTo(x=3.21875, y=0)
 -> LineTo(x=6, y=-4.1875)
 -> LineTo(x=8.8125, y=0)
 -> LineTo(x=3.21875, y=0)
 -> MoveTo(x=10, y=-1.8125)
 -> LineTo(x=7.21875, y=-6)
 -> LineTo(x=10, y=-10.1875)
 -> LineTo(x=10, y=-1.8125)

Then I am trying to reproduce it:

QPainterPath* polygonPath = new QPainterPath();

polygonPath->moveTo(10.5, -14);
polygonPath->lineTo(1.5, -14);
polygonPath->CurveTo(1.08333, -13.9792);
polygonPath->CurveToData(0.729167, -13.8333);
polygonPath->CurveToData(0.4375, -13.5625);
polygonPath->CurveTo(0.166667, -13.2708);
polygonPath->CurveToData(0.0208333, -12.9167);
polygonPath->CurveToData(0, -12.5);
polygonPath->lineTo(0, 0.5);
polygonPath->CurveTo(0.0208333, 0.916667);
polygonPath->CurveToData(0.166667, 1.27083);
polygonPath->CurveToData(0.4375, 1.5625);
polygonPath->CurveTo(0.729167, 1.83333);
polygonPath->CurveToData(1.08333, 1.97917);
polygonPath->CurveToData(1.5, 2);
polygonPath->lineTo(10.5, 2);
polygonPath->CurveTo(10.9167, 1.97917);
polygonPath->CurveToData(11.2708, 1.83333);
polygonPath->CurveToData(11.5625, 1.5625);
polygonPath->CurveTo(11.8333, 1.27083);
polygonPath->CurveToData(11.9792, 0.916667);
polygonPath->CurveToData(12, 0.5);
polygonPath->lineTo(12, -12.5);
polygonPath->CurveTo(11.9792, -12.9167);
polygonPath->CurveToData(11.8333, -13.2708);
polygonPath->CurveToData(11.5625, -13.5625);
polygonPath->CurveTo(11.2708, -13.8333);
polygonPath->CurveToData(10.9167, -13.9792);
polygonPath->CurveToData(10.5, -14);
polygonPath->moveTo(8.8125, -12);
polygonPath->lineTo(6, -7.8125);
polygonPath->lineTo(3.21875, -12);
polygonPath->lineTo(8.8125, -12);
polygonPath->moveTo(2, -10.1875);
polygonPath->lineTo(4.8125, -6);
polygonPath->lineTo(2, -1.8125);
polygonPath->lineTo(2, -10.1875);
polygonPath->moveTo(3.21875, 0);
polygonPath->lineTo(6, -4.1875);
polygonPath->lineTo(8.8125, 0);
polygonPath->lineTo(3.21875, 0);
polygonPath->moveTo(10, -1.8125);
polygonPath->lineTo(7.21875, -6);
polygonPath->lineTo(10, -10.1875);
polygonPath->lineTo(10, -1.8125);
polygonPath->closeSubpath();

Unfortunately I have no idea what to use instead of CurveTo and CurveToData. I have been reading the docs but just cannot understand it.

What I want to achieve is to obtain the icon points so I can draw it as a polygon. Similar to this:

QPolygon symbol;

symbol << QPoint(0.65, -9.75)
    << QPoint(5.85, -9.75)
    << QPoint(5.85, 0)
    << QPoint(0.65, 0);

painter.drawPolygon(symbol);

If someone would be so kind to provide a solution that would be amazing.

Upvotes: 1

Views: 254

Answers (2)

Scheff&#39;s Cat
Scheff&#39;s Cat

Reputation: 20141

For the other answer about QPainterPath, I got the feedback that the actual issue of the OP seems to be to retrieve the painter path for the intended glyph.

Thus, I modified the MCVE to draw the painter path of a selected character.

First, I used the MS Character Table to chose a nice character:

MS Character Table - Skull

The relevant info is the Unicode code point shown in the bottom status line: U+2620.

Then I modified the makeSample() to use that character for retrieval of the corresponding painter path:

QPainterPath makeSample()
{
  // get a font which should be able to provide most of the Unicode code points
  QFont qFont("Arial Unicode MS");
  // transform font into a raw font
  QRawFont qRawFont = QRawFont::fromFont(qFont);
  // build a Qt string with the intended character
  QString qText(1, QChar(0x2620)); // 0x2620 is the Unicode code point for Skull
  // retrieve the glyph index for the character
  QVector<quint32> qGlyphIndices = qRawFont.glyphIndexesForString(qText);
  // build the Qt painter path
  QPainterPath qPainterPath;
  for (quint32 qGlyphIndex : qGlyphIndices) {
    qPainterPath += qRawFont.pathForGlyph(qGlyphIndex);
  }
  // done
  return qPainterPath;
}

The first result was a Chinese glyph until I realized that the glyph index has nothing to do with the Unicode code point. (I had used 0x2620 (the Unicode code point) as glyph index.) Thus, I introduced the conversion with QRawFont::glyphIndexesForString().

Similar to what OP described, the next thing I got were some lines in the upper left corner. With a short glance onto the dumped lists, I realized that there are y coordinates in the dump with negative numbers i.e. rendered beyond the top border of my Canvas widget.

Hence, I tweaked the rendering a bit until I found it acceptable somehow.

The whole MCVE:

#include <QtWidgets>

class Canvas: public QWidget {

  private:
    QPainterPath *_pQPainterPath;

  public:
    explicit Canvas(QPainterPath* pQPainterPath, QWidget* pQParent = nullptr):
      QWidget(pQParent),
      _pQPainterPath(pQPainterPath)
    { }

  protected:
    virtual void paintEvent(QPaintEvent* pQEvent) override;
};

void Canvas::paintEvent(QPaintEvent* pQEvent)
{
  if (!_pQPainterPath) return;
  const QRectF qRect = _pQPainterPath->boundingRect();
  const qreal scale
    = std::min(width() / qRect.width(), height() / qRect.height());
  const QPointF center(0.5 * width(), 0.5 * height());
  QPainter qPainter(this);
  QPen qPen(Qt::blue);
  qPen.setCosmetic(true);
  qPainter.setPen(qPen);
  qPainter.translate(center);
  qPainter.scale(scale, scale);
  qPainter.translate(-qRect.center());
  qPainter.drawPath(*_pQPainterPath);
}

const char* toText(QPainterPath::ElementType type)
{
  static const char* const texts[] = {
    "MoveTo", "LineTo", "CurveTo", "CurveToData"
  };
  const size_t n = std::size(texts);
  return type < n ? texts[type] : "???";
}

QString list(const QPainterPath& qPainterPath)
{
  QString qText;
  QTextStream qOut(&qText);
  for (int i = 0, n = qPainterPath.elementCount(); i < n; ++i) {
    const QPainterPath::Element& qPPElem = qPainterPath.elementAt(i);
    qOut << toText(qPPElem.type) << "(x=" << qPPElem.x << ", y=" << qPPElem.y << ")\n";
  }
  return qText;
}

QString listSrc(const QPainterPath& qPainterPath)
{
  QString qText;
  QTextStream qOut(&qText);
  qOut << "QPainterPath qPainterPath;\n";
  for (int i = 0, n = qPainterPath.elementCount(); i < n; ++i) {
    const QPainterPath::Element& qPPElem = qPainterPath.elementAt(i);
    switch (qPPElem.type) {
      case QPainterPath::MoveToElement:
        qOut << "qPainterPath.moveTo(" << qPPElem.x << ", " << qPPElem.y << ");\n";
        break;
      case QPainterPath::LineToElement:
        qOut << "qPainterPath.lineTo(" << qPPElem.x << ", " << qPPElem.y << ");\n";
        break;
      case QPainterPath::CurveToElement: {
        if (i + 2 >= n
          || qPainterPath.elementAt(i + 1).type != QPainterPath::CurveToDataElement
          || qPainterPath.elementAt(i + 2).type != QPainterPath::CurveToDataElement) {
          qOut << "// BROKEN qPainterPath.cubicTo();\n";
          break; // ERROR!
        }
        const QPainterPath::Element& qPPElem1 = qPainterPath.elementAt(++i);
        const QPainterPath::Element& qPPElem2 = qPainterPath.elementAt(++i);
        qOut << "qPainterPath.cubicTo(" << qPPElem.x << ", " << qPPElem.y << ", "
           << qPPElem1.x << ", " << qPPElem1.y << ", "
           << qPPElem2.x << ", " << qPPElem2.y << ");\n";
      } break;
    }
  }
  return qText;
}

QPainterPath makeSample()
{
  QFont qFont("Arial Unicode MS");
  QRawFont qRawFont = QRawFont::fromFont(qFont);
  QString qText(1, QChar(0x2620)); // 0x2620 is the Unicode code point for Skull
  QVector<quint32> qGlyphIndices = qRawFont.glyphIndexesForString(qText);
  QPainterPath qPainterPath;
  for (quint32 qGlyphIndex : qGlyphIndices) {
    qPainterPath += qRawFont.pathForGlyph(qGlyphIndex);
  }
  return qPainterPath;
}

int main(int argc, char** argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // setup data
  QPainterPath qPainterPath = makeSample();
  qDebug() << qPainterPath;
  // setup GUI
  QMainWindow qWinMain;
  qWinMain.setWindowTitle("Test QRawFont - QPainterPath");
  qWinMain.resize(480, 280);
  QSplitter qVSplit(Qt::Vertical);
  Canvas canvas(&qPainterPath);
  qVSplit.addWidget(&canvas);
  QSplitter qHSplit(Qt::Horizontal);
  QPlainTextEdit qEditCode;
  qEditCode.setPlainText(listSrc(qPainterPath));
  qHSplit.addWidget(&qEditCode);
  QPlainTextEdit qEditPath;
  qEditPath.setPlainText(list(qPainterPath));
  qHSplit.addWidget(&qEditPath);
  qVSplit.addWidget(&qHSplit);
  qVSplit.setSizes({ 100, 180 });
  qWinMain.setCentralWidget(&qVSplit);
  qWinMain.show();
  // runtime loop
  return app.exec();
}

Output:

Snapshot of testQRawFontQPainterPath

There is for sure a similarity though the rendering might not be perfect.

Snapshot of testQRawFontQPainterPath with Yellow Brush

This is how it looks if a yellow brush is used with the blue pen. (I had removed the brush while fiddling as the result didn't match my expectation until I added the qPen.setCosmetic(true);.)

Upvotes: 1

Scheff&#39;s Cat
Scheff&#39;s Cat

Reputation: 20141

I must admit that all the guessing in my comments could have been prevented as the doc. about QPainterPath mentions explicitly how it works:

enum QPainterPath::ElementType

This enum describes the types of elements used to connect vertices in subpaths.

Note that elements added as closed subpaths using the addEllipse(), addPath(), addPolygon(), addRect(), addRegion() and addText() convenience functions, is actually added to the path as a collection of separate elements using the moveTo(), lineTo() and cubicTo() functions.

Constant Value Description
QPainterPath::MoveToElement 0 A new subpath. See also moveTo().
QPainterPath::LineToElement 1 A line. See also lineTo().
QPainterPath::CurveToElement 2 A curve. See also cubicTo() and quadTo().
QPainterPath::CurveToDataElement 3 The extra data required to describe a curve in a CurveToElement element.

I made my own MCVE to illustrate this:

#include <QtWidgets>

class Canvas: public QWidget {

  private:
    QPainterPath *_pQPainterPath;

  public:
    explicit Canvas(QPainterPath* pQPainterPath, QWidget* pQParent = nullptr):
      QWidget(pQParent),
      _pQPainterPath(pQPainterPath)
    { }

  protected:
    virtual void paintEvent(QPaintEvent* pQEvent) override;
};

void Canvas::paintEvent(QPaintEvent* pQEvent)
{
  QPainter qPainter(this);
  qPainter.setPen(
    QPen(
      QColor(79, 106, 25), 1,
      Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
  qPainter.setBrush(QColor(122, 163, 39));
  if (_pQPainterPath) qPainter.drawPath(*_pQPainterPath);
}

const char* toText(QPainterPath::ElementType type)
{
  static const char* const texts[] = {
    "MoveTo", "LineTo", "CurveTo", "CurveToData"
  };
  const size_t n = std::size(texts);
  return type < n ? texts[type] : "???";
}

QString list(const QPainterPath& qPainterPath)
{
  QString qText;
  QTextStream qOut(&qText);
  for (int i = 0, n = qPainterPath.elementCount(); i < n; ++i) {
    const QPainterPath::Element& qPPElem = qPainterPath.elementAt(i);
    qOut << toText(qPPElem.type) << "(x=" << qPPElem.x << ", y=" << qPPElem.y << ")\n";
  }
  return qText;
}

QString listSrc(const QPainterPath& qPainterPath)
{
  QString qText;
  QTextStream qOut(&qText);
  qOut << "QPainterPath qPainterPath;\n";
  for (int i = 0, n = qPainterPath.elementCount(); i < n; ++i) {
    const QPainterPath::Element& qPPElem = qPainterPath.elementAt(i);
    switch (qPPElem.type) {
      case QPainterPath::MoveToElement:
        qOut << "qPainterPath.moveTo(" << qPPElem.x << ", " << qPPElem.y << ");\n";
        break;
      case QPainterPath::LineToElement:
        qOut << "qPainterPath.lineTo(" << qPPElem.x << ", " << qPPElem.y << ");\n";
        break;
      case QPainterPath::CurveToElement: {

        if (i + 2 >= n
          || qPainterPath.elementAt(i + 1).type != QPainterPath::CurveToDataElement
          || qPainterPath.elementAt(i + 2).type != QPainterPath::CurveToDataElement) {
          qOut << "// BROKEN qPainterPath.cubicTo();\n";
          break; // ERROR!
        }
        const QPainterPath::Element& qPPElem1 = qPainterPath.elementAt(++i);
        const QPainterPath::Element& qPPElem2 = qPainterPath.elementAt(++i);
        qOut << "qPainterPath.cubicTo(" << qPPElem.x << ", " << qPPElem.y << ", "
           << qPPElem1.x << ", " << qPPElem1.y << ", "
           << qPPElem2.x << ", " << qPPElem2.y << ");\n";
      } break;
    }
  }
  return qText;
}

QPainterPath makeSample()
{
#if 0 // ORIGINAL:
  // taken from https://doc.qt.io/qt-5/qpainterpath.html#details
  QPainterPath qPainterPath;
  qPainterPath.addRect(20, 20, 60, 60);
  qPainterPath.moveTo(0, 0);
  qPainterPath.cubicTo(99, 0,  50, 50,  99, 99);
  qPainterPath.cubicTo(0, 99,  50, 50,  0, 0);
#else // DUMPED in listSrc:
  QPainterPath qPainterPath;
  qPainterPath.moveTo(20, 20);
  qPainterPath.lineTo(80, 20);
  qPainterPath.lineTo(80, 80);
  qPainterPath.lineTo(20, 80);
  qPainterPath.lineTo(20, 20);
  qPainterPath.moveTo(0, 0);
  qPainterPath.cubicTo(99, 0, 50, 50, 99, 99);
  qPainterPath.cubicTo(0, 99, 50, 50, 0, 0);
#endif // 0
  return qPainterPath;
}

int main(int argc, char** argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // setup data
  QPainterPath qPainterPath = makeSample();
  // setup GUI
  QMainWindow qWinMain;
  qWinMain.setWindowTitle("Test QPainterPath");
  qWinMain.resize(480, 280);
  QSplitter qVSplit(Qt::Vertical);
  Canvas canvas(&qPainterPath);
  qVSplit.addWidget(&canvas);
  QSplitter qHSplit(Qt::Horizontal);
  QPlainTextEdit qEditCode;
  qEditCode.setPlainText(listSrc(qPainterPath));
  qHSplit.addWidget(&qEditCode);
  QPlainTextEdit qEditPath;
  qEditPath.setPlainText(list(qPainterPath));
  qHSplit.addWidget(&qEditPath);
  qVSplit.addWidget(&qHSplit);
  qVSplit.setSizes({ 100, 180 });
  qWinMain.setCentralWidget(&qVSplit);
  qWinMain.show();
  // runtime loop
  return app.exec();
}

Output:

Qt Version: 5.15.1
QPainterPath: Element count=12
 -> MoveTo(x=20, y=20)
 -> LineTo(x=80, y=20)
 -> LineTo(x=80, y=80)
 -> LineTo(x=20, y=80)
 -> LineTo(x=20, y=20)
 -> MoveTo(x=0, y=0)
 -> CurveTo(x=99, y=0)
 -> CurveToData(x=50, y=50)
 -> CurveToData(x=99, y=99)
 -> CurveTo(x=0, y=99)
 -> CurveToData(x=50, y=50)
 -> CurveToData(x=0, y=0)

Snapshot of testQPainterPath (Original)

This is the output for the original sample (taken from Qt doc.).

Snapshot of testQPainterPath (Dumped)

This is the output for the source code dumped in the lower left text view and copied into the source.

(Some kind of bootstrap? ;-))

Upvotes: 1

Related Questions