odin-blend2d

Odin bindings to Blend2D
Log | Files | Refs | README | LICENSE

bl_demo_stroke.cpp (11976B)


      1 #include <blend2d.h>
      2 
      3 #include "bl_qt_headers.h"
      4 #include "bl_qt_canvas.h"
      5 
      6 class MainWindow : public QWidget {
      7   Q_OBJECT
      8 public:
      9   // Widgets.
     10   QComboBox _cap_type_select;
     11   QComboBox _join_type_select;
     12   QSlider _width_slider;
     13   QSlider _miter_limit_slider;
     14   QBLCanvas _canvas;
     15 
     16   // Canvas data.
     17   BLRandom _prng;
     18   BLPath _path;
     19   bool _show_control = true;
     20   size_t _closest_vertex = SIZE_MAX;
     21   size_t _grabbed_vertex = SIZE_MAX;
     22   int _grabbed_x = 0;
     23   int _grabbed_y = 0;
     24   BLStrokeOptions _stroke_options;
     25 
     26   MainWindow() {
     27     int max_pb_width = 30;
     28 
     29     setWindowTitle(QLatin1String("Stroke Sample"));
     30 
     31     QVBoxLayout* vBox = new QVBoxLayout();
     32     vBox->setContentsMargins(0, 0, 0, 0);
     33     vBox->setSpacing(0);
     34 
     35     QGridLayout* grid = new QGridLayout();
     36     grid->setContentsMargins(5, 5, 5, 5);
     37     grid->setSpacing(5);
     38 
     39     QPushButton* pb_a = new QPushButton("A");
     40     QPushButton* pb_b = new QPushButton("B");
     41     QPushButton* pb_c = new QPushButton("C");
     42     QPushButton* pb_d = new QPushButton("D");
     43     QPushButton* pb_e = new QPushButton("E");
     44     QPushButton* pb_f = new QPushButton("F");
     45     QPushButton* pb_x = new QPushButton("X");
     46     QPushButton* pb_y = new QPushButton("Y");
     47     QPushButton* pb_z = new QPushButton("Z");
     48     QPushButton* pb_random = new QPushButton("Random");
     49     QPushButton* pb_dump = new QPushButton(QString::fromLatin1("Dump"));
     50 
     51     pb_a->setMaximumWidth(max_pb_width);
     52     pb_b->setMaximumWidth(max_pb_width);
     53     pb_c->setMaximumWidth(max_pb_width);
     54     pb_d->setMaximumWidth(max_pb_width);
     55     pb_e->setMaximumWidth(max_pb_width);
     56     pb_f->setMaximumWidth(max_pb_width);
     57     pb_x->setMaximumWidth(max_pb_width);
     58     pb_y->setMaximumWidth(max_pb_width);
     59     pb_z->setMaximumWidth(max_pb_width);
     60 
     61     _cap_type_select.addItem("Butt", QVariant(int(BL_STROKE_CAP_BUTT)));
     62     _cap_type_select.addItem("Square", QVariant(int(BL_STROKE_CAP_SQUARE)));
     63     _cap_type_select.addItem("Round", QVariant(int(BL_STROKE_CAP_ROUND)));
     64     _cap_type_select.addItem("Round-Rev", QVariant(int(BL_STROKE_CAP_ROUND_REV)));
     65     _cap_type_select.addItem("Triangle", QVariant(int(BL_STROKE_CAP_TRIANGLE)));
     66     _cap_type_select.addItem("Triangle-Rev", QVariant(int(BL_STROKE_CAP_TRIANGLE_REV)));
     67 
     68     _join_type_select.addItem("Miter-Clip", QVariant(int(BL_STROKE_JOIN_MITER_CLIP)));
     69     _join_type_select.addItem("Miter-Bevel", QVariant(int(BL_STROKE_JOIN_MITER_BEVEL)));
     70     _join_type_select.addItem("Miter-Round", QVariant(int(BL_STROKE_JOIN_MITER_ROUND)));
     71     _join_type_select.addItem("Bevel", QVariant(int(BL_STROKE_JOIN_BEVEL)));
     72     _join_type_select.addItem("Round", QVariant(int(BL_STROKE_JOIN_ROUND)));
     73 
     74     connect(pb_a, SIGNAL(clicked()), SLOT(onSetDataA()));
     75     connect(pb_b, SIGNAL(clicked()), SLOT(onSetDataB()));
     76     connect(pb_c, SIGNAL(clicked()), SLOT(onSetDataC()));
     77     connect(pb_d, SIGNAL(clicked()), SLOT(onSetDataD()));
     78     connect(pb_e, SIGNAL(clicked()), SLOT(onSetDataE()));
     79     connect(pb_f, SIGNAL(clicked()), SLOT(onSetDataF()));
     80     connect(pb_x, SIGNAL(clicked()), SLOT(onSetDataX()));
     81     connect(pb_y, SIGNAL(clicked()), SLOT(onSetDataY()));
     82     connect(pb_z, SIGNAL(clicked()), SLOT(onSetDataZ()));
     83     connect(pb_dump, SIGNAL(clicked()), SLOT(onDumpPath()));
     84     connect(pb_random, SIGNAL(clicked()), SLOT(onSetRandom()));
     85     connect(&_cap_type_select, SIGNAL(activated(int)), SLOT(onCapTypeUpdate(int)));
     86     connect(&_join_type_select, SIGNAL(activated(int)), SLOT(onJoinTypeUpdate(int)));
     87 
     88     _width_slider.setOrientation(Qt::Horizontal);
     89     _width_slider.setMinimum(1);
     90     _width_slider.setMaximum(400);
     91     _width_slider.setSliderPosition(40);
     92     connect(&_width_slider, SIGNAL(valueChanged(int)), SLOT(onWidthChanged(int)));
     93 
     94     _miter_limit_slider.setOrientation(Qt::Horizontal);
     95     _miter_limit_slider.setMinimum(0);
     96     _miter_limit_slider.setMaximum(1000);
     97     _miter_limit_slider.setSliderPosition(400);
     98     connect(&_miter_limit_slider, SIGNAL(valueChanged(int)), SLOT(onMiterLimitChanged(int)));
     99 
    100     _canvas.on_render_blend2d = std::bind(&MainWindow::onRender, this, std::placeholders::_1);
    101     _canvas.on_mouse_event = std::bind(&MainWindow::on_mouse_event, this, std::placeholders::_1);
    102 
    103     grid->addWidget(new QLabel("Stroke Caps:"), 0, 0, Qt::AlignRight);
    104     grid->addWidget(&_cap_type_select, 0, 1);
    105     grid->addWidget(pb_random, 0, 2);
    106     grid->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding), 0, 3);
    107     grid->addWidget(pb_a, 0, 4);
    108     grid->addWidget(pb_b, 0, 5);
    109     grid->addWidget(pb_c, 0, 6);
    110     grid->addWidget(pb_d, 0, 7);
    111     grid->addWidget(pb_e, 0, 8);
    112     grid->addWidget(pb_f, 0, 9);
    113 
    114     grid->addWidget(new QLabel("Stroke Join:"), 1, 0, Qt::AlignRight);
    115     grid->addWidget(&_join_type_select, 1, 1);
    116     grid->addWidget(pb_dump, 1, 2);
    117     grid->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding), 1, 3, 1, 4);
    118     grid->addWidget(pb_x, 1, 7);
    119     grid->addWidget(pb_y, 1, 8);
    120     grid->addWidget(pb_z, 1, 9);
    121 
    122     grid->addWidget(new QLabel("Width:"), 2, 0, Qt::AlignRight);
    123     grid->addWidget(&_width_slider, 2, 1, 1, 10);
    124 
    125     grid->addWidget(new QLabel("Miter Limit:"), 3, 0, Qt::AlignRight);
    126     grid->addWidget(&_miter_limit_slider, 3, 1, 1, 10);
    127 
    128     vBox->addLayout(grid);
    129     vBox->addWidget(&_canvas);
    130     setLayout(vBox);
    131     onInit();
    132   }
    133 
    134   void keyPressEvent(QKeyEvent *event) override {
    135     if (event->key() == Qt::Key_Z) {
    136       _show_control = !_show_control;
    137       _canvas.update_canvas();
    138     }
    139   }
    140 
    141   void onInit() {
    142     _prng.reset(QCoreApplication::applicationPid());
    143     _stroke_options.width = _width_slider.sliderPosition();
    144     _stroke_options.miter_limit = 5;
    145 
    146     onSetDataA();
    147   }
    148 
    149   void on_mouse_event(QMouseEvent* event) {
    150     QPointF position(event->position().x() * devicePixelRatio(),
    151                      event->position().y() * devicePixelRatio());
    152 
    153     if (event->type() == QEvent::MouseButtonPress) {
    154       if (event->button() == Qt::LeftButton) {
    155         if (_closest_vertex != SIZE_MAX) {
    156           _grabbed_vertex = _closest_vertex;
    157           _grabbed_x = position.x();
    158           _grabbed_y = position.y();
    159           _canvas.update_canvas();
    160         }
    161       }
    162     }
    163 
    164     if (event->type() == QEvent::MouseButtonRelease) {
    165       if (event->button() == Qt::LeftButton) {
    166         if (_grabbed_vertex != SIZE_MAX) {
    167           _grabbed_vertex = SIZE_MAX;
    168           _canvas.update_canvas();
    169         }
    170       }
    171     }
    172 
    173     if (event->type() == QEvent::MouseMove) {
    174       if (_grabbed_vertex == SIZE_MAX) {
    175         _path.get_closest_vertex(BLPoint(double(position.x()), double(position.y())), 5, &_closest_vertex);
    176         _canvas.update_canvas();
    177       }
    178       else {
    179         double x = position.x();
    180         double y = position.y();
    181         _path.set_vertex_at(_grabbed_vertex, BL_PATH_CMD_PRESERVE, BLPoint(x, y));
    182         _canvas.update_canvas();
    183       }
    184     }
    185   }
    186 
    187   Q_SLOT void onSetRandom() {
    188     double min_x = 25;
    189     double min_y = 25;
    190     double max_x = double(_canvas.image_width()) - min_x;
    191     double max_y = double(_canvas.image_height()) - min_y;
    192 
    193     auto rx = [&]() { return _prng.next_double() * (max_x - min_x) + min_x; };
    194     auto ry = [&]() { return _prng.next_double() * (max_y - min_y) + min_y; };
    195 
    196     _path.clear();
    197     _path.move_to(rx(), ry());
    198 
    199     double cmd = _prng.next_double();
    200     if (cmd < 0.33) {
    201       _path.line_to(rx(), ry());
    202       _path.line_to(rx(), ry());
    203       _path.line_to(rx(), ry());
    204     }
    205     else if (cmd < 0.66) {
    206       _path.quad_to(rx(), ry(), rx(), ry());
    207       _path.quad_to(rx(), ry(), rx(), ry());
    208     }
    209     else {
    210       _path.cubic_to(rx(), ry(), rx(), ry(), rx(), ry());
    211     }
    212 
    213     if (_prng.next_double() < 0.5)
    214       _path.close();
    215 
    216     _canvas.update_canvas();
    217   }
    218 
    219   Q_SLOT void onSetDataA() {
    220     _path.clear();
    221     _path.move_to(345, 333);
    222     _path.cubic_to(308, 3, 33, 352, 512, 244);
    223     _canvas.update_canvas();
    224   }
    225 
    226   Q_SLOT void onSetDataB() {
    227     _path.clear();
    228     _path.move_to(60, 177);
    229     _path.quad_to(144, 354, 396, 116);
    230     _path.quad_to(106, 184, 43.4567, 43.3091);
    231     _canvas.update_canvas();
    232   }
    233 
    234   Q_SLOT void onSetDataC() {
    235     _path.clear();
    236     _path.move_to(488, 45);
    237     _path.cubic_to(22, 331, 26, 27, 493, 338);
    238     _canvas.update_canvas();
    239   }
    240 
    241   Q_SLOT void onSetDataD() {
    242     _path.clear();
    243     _path.move_to(276, 152);
    244     _path.line_to(194.576, 54.1927);
    245     _path.line_to(114, 239);
    246     _path.line_to(526.311, 134.453);
    247     _canvas.update_canvas();
    248   }
    249 
    250   Q_SLOT void onSetDataE() {
    251     _path.clear();
    252     _path.move_to(161, 308);
    253     _path.cubic_to(237.333, 152.509, 146.849, 108.62, 467.225, 59.9782);
    254     _path.close();
    255     _canvas.update_canvas();
    256   }
    257 
    258   Q_SLOT void onSetDataF() {
    259     _path.clear();
    260     _path.add_circle(BLCircle(280, 190, 140));
    261     _canvas.update_canvas();
    262   }
    263 
    264   Q_SLOT void onSetDataX() {
    265     _path.clear();
    266     _path.move_to(300, 200);
    267     _path.quad_to(50, 200, 500, 200);
    268     _canvas.update_canvas();
    269   }
    270 
    271   Q_SLOT void onSetDataY() {
    272     _path.clear();
    273     _path.move_to(300, 200);
    274     _path.cubic_to(50, 200, 500, 200, 350, 200);
    275     _canvas.update_canvas();
    276   }
    277 
    278   Q_SLOT void onSetDataZ() {
    279     _path.clear();
    280     _path.move_to(300, 200);
    281     _path.line_to(50, 200);
    282     _path.line_to(500, 200);
    283     _path.line_to(350, 200);
    284     _canvas.update_canvas();
    285   }
    286 
    287   Q_SLOT void onDumpPath() {
    288     size_t count = _path.size();
    289     const BLPoint* vtx = _path.vertex_data();
    290     const uint8_t* cmd = _path.command_data();
    291 
    292     size_t i = 0;
    293     while (i < count) {
    294       switch (cmd[i]) {
    295         case BL_PATH_CMD_MOVE:
    296           printf("p.moveTo(%g, %g);\n", vtx[i].x, vtx[i].y);
    297           i++;
    298           break;
    299         case BL_PATH_CMD_ON:
    300           printf("p.lineTo(%g, %g);\n", vtx[i].x, vtx[i].y);
    301           i++;
    302           break;
    303         case BL_PATH_CMD_QUAD:
    304           printf("p.quadTo(%g, %g, %g, %g);\n", vtx[i].x, vtx[i].y, vtx[i+1].x, vtx[i+1].y);
    305           i += 2;
    306           break;
    307         case BL_PATH_CMD_CUBIC:
    308           printf("p.cubicTo(%g, %g, %g, %g, %g, %g);\n", vtx[i].x, vtx[i].y, vtx[i+1].x, vtx[i+1].y, vtx[i+2].x, vtx[i+2].y);
    309           i += 3;
    310           break;
    311         case BL_PATH_CMD_CLOSE:
    312           printf("p.close();\n");
    313           i++;
    314           break;
    315       }
    316     }
    317   }
    318 
    319   Q_SLOT void onCapTypeUpdate(int index) {
    320     _stroke_options.start_cap = uint8_t(_cap_type_select.itemData(index).toInt());
    321     _stroke_options.end_cap = _stroke_options.start_cap;
    322     _canvas.update_canvas();
    323   }
    324 
    325   Q_SLOT void onJoinTypeUpdate(int index) {
    326     _stroke_options.join = uint8_t(_join_type_select.itemData(index).toInt());
    327     _canvas.update_canvas();
    328   }
    329 
    330   Q_SLOT void onWidthChanged(int value) {
    331     _stroke_options.width = double(value);
    332     _canvas.update_canvas();
    333   }
    334 
    335   Q_SLOT void onMiterLimitChanged(int value) {
    336     _stroke_options.miter_limit = double(value) / 100.0;
    337     _canvas.update_canvas();
    338   }
    339 
    340   void onRender(BLContext& ctx) {
    341     ctx.fill_all(BLRgba32(0xFF000000u));
    342 
    343     BLPath s;
    344     s.add_stroked_path(_path, _stroke_options, bl_default_approximation_options);
    345     ctx.fill_path(s, BLRgba32(0x8F003FAAu));
    346 
    347     if (_show_control) {
    348       ctx.stroke_path(s, BLRgba32(0xFF0066AAu));
    349       render_path_points(ctx, s, SIZE_MAX, BLRgba32(0x7F007FFFu), BLRgba32(0xFFFFFFFFu));
    350     }
    351 
    352     ctx.stroke_path(_path, BLRgba32(0xFFFFFFFFu));
    353     render_path_points(ctx, _path, _closest_vertex, BLRgba32(0xFFFFFFFF), BLRgba32(0xFF00FFFFu));
    354   }
    355 
    356   void render_path_points(BLContext& ctx, const BLPath& path, size_t highlight, BLRgba32 normalColor, BLRgba32 highlightColor) noexcept {
    357     size_t count = path.size();
    358     const BLPoint* vtx = path.vertex_data();
    359 
    360     for (size_t i = 0; i < count; i++) {
    361       if (!std::isfinite(vtx[i].x))
    362         continue;
    363       ctx.fill_circle(vtx[i].x, vtx[i].y, 2.5, i == highlight ? highlightColor : normalColor);
    364     }
    365   }
    366 };
    367 
    368 int main(int argc, char *argv[]) {
    369   QApplication app(argc, argv);
    370   MainWindow win;
    371 
    372   win.resize(QSize(580, 520));
    373   win.show();
    374 
    375   return app.exec();
    376 }
    377 
    378 #include "bl_demo_stroke.moc"