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"