bl_demo_tiger.cpp (12082B)
1 #include "bl_qt_headers.h" 2 #include "bl_qt_canvas.h" 3 #include "bl_demo_tiger.h" 4 5 #include <stdlib.h> 6 7 #define ARRAY_SIZE(X) uint32_t(sizeof(X) / sizeof(X[0])) 8 9 static const double PI = 3.14159265359; 10 11 struct TigerPath { 12 inline TigerPath() 13 : fill_color(0), 14 stroke_color(0), 15 qt_pen(Qt::NoPen), 16 fill_rule(BL_FILL_RULE_NON_ZERO), 17 fill(false), 18 stroke(false) {} 19 inline ~TigerPath() {} 20 21 BLPath bl_path; 22 BLPath bl_stroked_path; 23 BLStrokeOptions bl_stroke_options; 24 25 BLRgba32 fill_color; 26 BLRgba32 stroke_color; 27 28 QPainterPath qt_path; 29 QPainterPath qt_stroked_path; 30 QPen qt_pen; 31 QBrush qt_brush; 32 33 BLFillRule fill_rule; 34 bool fill; 35 bool stroke; 36 }; 37 38 struct Tiger { 39 Tiger() { 40 init( 41 TigerData::commands, ARRAY_SIZE(TigerData::commands), 42 TigerData::points, ARRAY_SIZE(TigerData::points)); 43 } 44 45 ~Tiger() { 46 destroy(); 47 } 48 49 void init(const char* commands, int commandCount, const float* points, uint32_t pointCount) { 50 size_t c = 0; 51 size_t p = 0; 52 53 float h = float(TigerData::height); 54 55 while (c < commandCount) { 56 TigerPath* tp = new TigerPath(); 57 58 // Fill params. 59 switch (commands[c++]) { 60 case 'N': tp->fill = false; break; 61 case 'F': tp->fill = true; tp->fill_rule = BL_FILL_RULE_NON_ZERO; break; 62 case 'E': tp->fill = true; tp->fill_rule = BL_FILL_RULE_EVEN_ODD; break; 63 } 64 65 // Stroke params. 66 switch (commands[c++]) { 67 case 'N': tp->stroke = false; break; 68 case 'S': tp->stroke = true; break; 69 } 70 71 switch (commands[c++]) { 72 case 'B': tp->bl_stroke_options.set_caps(BL_STROKE_CAP_BUTT); break; 73 case 'R': tp->bl_stroke_options.set_caps(BL_STROKE_CAP_ROUND); break; 74 case 'S': tp->bl_stroke_options.set_caps(BL_STROKE_CAP_SQUARE); break; 75 } 76 77 switch (commands[c++]) { 78 case 'M': tp->bl_stroke_options.join = BL_STROKE_JOIN_MITER_BEVEL; break; 79 case 'R': tp->bl_stroke_options.join = BL_STROKE_JOIN_ROUND; break; 80 case 'B': tp->bl_stroke_options.join = BL_STROKE_JOIN_BEVEL; break; 81 } 82 83 tp->bl_stroke_options.miter_limit = points[p++]; 84 tp->bl_stroke_options.width = points[p++]; 85 86 // Stroke & Fill style. 87 tp->stroke_color = BLRgba32(uint32_t(points[p + 0] * 255.0f), uint32_t(points[p + 1] * 255.0f), uint32_t(points[p + 2] * 255.0f), 255); 88 tp->fill_color = BLRgba32(uint32_t(points[p + 3] * 255.0f), uint32_t(points[p + 4] * 255.0f), uint32_t(points[p + 5] * 255.0f), 255); 89 p += 6; 90 91 // Path. 92 int i, count = int(points[p++]); 93 for (i = 0 ; i < count; i++) { 94 switch (commands[c++]) { 95 case 'M': 96 tp->bl_path.move_to(points[p], h - points[p + 1]); 97 tp->qt_path.moveTo(points[p], h - points[p + 1]); 98 p += 2; 99 break; 100 case 'L': 101 tp->bl_path.line_to(points[p], h - points[p + 1]); 102 tp->qt_path.lineTo(points[p], h - points[p + 1]); 103 p += 2; 104 break; 105 case 'C': 106 tp->bl_path.cubic_to(points[p], h - points[p + 1], points[p + 2], h - points[p + 3], points[p + 4], h - points[p + 5]); 107 tp->qt_path.cubicTo(points[p], h - points[p + 1], points[p + 2], h - points[p + 3], points[p + 4], h - points[p + 5]); 108 p += 6; 109 break; 110 case 'E': 111 tp->bl_path.close(); 112 tp->qt_path.closeSubpath(); 113 break; 114 } 115 } 116 117 tp->bl_path.shrink(); 118 tp->qt_path.setFillRule(tp->fill_rule == BL_FILL_RULE_NON_ZERO ? Qt::WindingFill : Qt::OddEvenFill); 119 120 if (tp->fill) { 121 tp->qt_brush = QBrush(bl_rgba_to_qcolor(tp->fill_color)); 122 } 123 124 if (tp->stroke) { 125 tp->bl_stroked_path.add_stroked_path(tp->bl_path, tp->bl_stroke_options, bl_default_approximation_options); 126 tp->bl_stroked_path.shrink(); 127 128 tp->qt_pen = QPen(bl_rgba_to_qcolor(tp->stroke_color)); 129 tp->qt_pen.setWidthF(tp->bl_stroke_options.width); 130 tp->qt_pen.setMiterLimit(tp->bl_stroke_options.miter_limit); 131 132 Qt::PenCapStyle qtCapStyle = 133 tp->bl_stroke_options.start_cap == BL_STROKE_CAP_BUTT ? Qt::FlatCap : 134 tp->bl_stroke_options.start_cap == BL_STROKE_CAP_ROUND ? Qt::RoundCap : Qt::SquareCap; 135 136 Qt::PenJoinStyle qtJoinStyle = 137 tp->bl_stroke_options.join == BL_STROKE_JOIN_ROUND ? Qt::RoundJoin : 138 tp->bl_stroke_options.join == BL_STROKE_JOIN_BEVEL ? Qt::BevelJoin : Qt::MiterJoin; 139 140 tp->qt_pen.setCapStyle(qtCapStyle); 141 tp->qt_pen.setJoinStyle(qtJoinStyle); 142 143 QPainterPathStroker stroker; 144 stroker.setWidth(tp->bl_stroke_options.width); 145 stroker.setMiterLimit(tp->bl_stroke_options.miter_limit); 146 stroker.setJoinStyle(qtJoinStyle); 147 stroker.setCapStyle(qtCapStyle); 148 149 tp->qt_stroked_path = stroker.createStroke(tp->qt_path); 150 } 151 152 paths.append(tp); 153 } 154 } 155 156 void destroy() { 157 for (size_t i = 0, count = paths.size(); i < count; i++) 158 delete paths[i]; 159 paths.reset(); 160 } 161 162 BLArray<TigerPath*> paths; 163 }; 164 165 class MainWindow : public QWidget { 166 Q_OBJECT 167 168 public: 169 QTimer _timer; 170 QBLCanvas _canvas; 171 QComboBox _renderer_select; 172 QCheckBox _limit_fps_check; 173 QComboBox _caching_select; 174 QSlider _slider; 175 Tiger _tiger; 176 177 bool _animate = true; 178 bool _cache_stroke = false; 179 bool _render_stroke = true; 180 double _rot = 0.0; 181 double _scale = 1.0; 182 183 MainWindow() { 184 QVBoxLayout* vBox = new QVBoxLayout(); 185 vBox->setContentsMargins(0, 0, 0, 0); 186 vBox->setSpacing(0); 187 188 QGridLayout* grid = new QGridLayout(); 189 grid->setContentsMargins(5, 5, 5, 5); 190 grid->setSpacing(5); 191 192 QBLCanvas::init_renderer_select_box(&_renderer_select); 193 _limit_fps_check.setText(QLatin1String("Limit FPS")); 194 195 _caching_select.addItem("None", QVariant(int(0))); 196 _caching_select.addItem("Strokes", QVariant(int(1))); 197 198 _slider.setOrientation(Qt::Horizontal); 199 _slider.setMinimum(50); 200 _slider.setMaximum(20000); 201 _slider.setSliderPosition(1000); 202 203 connect(&_renderer_select, SIGNAL(currentIndexChanged(int)), SLOT(onRendererChanged(int))); 204 connect(&_limit_fps_check, SIGNAL(stateChanged(int)), SLOT(onLimitFpsChanged(int))); 205 connect(&_caching_select, SIGNAL(currentIndexChanged(int)), SLOT(onCachingChanged(int))); 206 connect(&_slider, SIGNAL(valueChanged(int)), SLOT(onZoomChanged(int))); 207 208 _canvas.on_render_blend2d = std::bind(&MainWindow::on_render_blend2d, this, std::placeholders::_1); 209 _canvas.on_render_qt = std::bind(&MainWindow::on_render_qt, this, std::placeholders::_1); 210 211 grid->addWidget(new QLabel("Renderer:"), 0, 0, Qt::AlignRight); 212 grid->addWidget(&_renderer_select, 0, 1); 213 214 grid->addWidget(new QLabel("Caching:"), 0, 2, Qt::AlignRight); 215 grid->addWidget(&_caching_select, 0, 3); 216 217 grid->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding), 0, 4); 218 grid->addWidget(&_limit_fps_check, 0, 5); 219 220 grid->addWidget(new QLabel("Zoom:"), 1, 0, Qt::AlignRight); 221 grid->addWidget(&_slider, 1, 1, 1, 5); 222 223 _canvas.on_render_blend2d = std::bind(&MainWindow::on_render_blend2d, this, std::placeholders::_1); 224 _canvas.on_render_qt = std::bind(&MainWindow::on_render_qt, this, std::placeholders::_1); 225 226 vBox->addLayout(grid); 227 vBox->addWidget(&_canvas); 228 setLayout(vBox); 229 230 connect(&_timer, SIGNAL(timeout()), this, SLOT(onTimer())); 231 connect(new QShortcut(QKeySequence(Qt::Key_P), this), SIGNAL(activated()), SLOT(onToggleAnimate())); 232 connect(new QShortcut(QKeySequence(Qt::Key_R), this), SIGNAL(activated()), SLOT(onToggleRenderer())); 233 connect(new QShortcut(QKeySequence(Qt::Key_S), this), SIGNAL(activated()), SLOT(onToggleStroke())); 234 connect(new QShortcut(QKeySequence(Qt::Key_Q), this), SIGNAL(activated()), SLOT(onRotatePrev())); 235 connect(new QShortcut(QKeySequence(Qt::Key_W), this), SIGNAL(activated()), SLOT(onRotateNext())); 236 237 onInit(); 238 } 239 240 void showEvent(QShowEvent* event) override { _timer.start(); } 241 void hideEvent(QHideEvent* event) override { _timer.stop(); } 242 243 void onInit() { 244 _updateTitle(); 245 _limit_fps_check.setChecked(true); 246 } 247 248 Q_SLOT void onRendererChanged(int index) { _canvas.set_renderer_type(_renderer_select.itemData(index).toInt()); } 249 Q_SLOT void onLimitFpsChanged(int value) { _timer.setInterval(value ? 1000 / 120 : 0); } 250 251 Q_SLOT void onCachingChanged(int index) { _cache_stroke = index != 0; } 252 Q_SLOT void onZoomChanged(int value) { _scale = (double(value) / 1000.0); } 253 254 Q_SLOT void onToggleAnimate() { _animate = !_animate; } 255 Q_SLOT void onToggleRenderer() { _renderer_select.setCurrentIndex(_renderer_select.currentIndex() ^ 1); } 256 Q_SLOT void onToggleStroke() { _render_stroke = !_render_stroke; } 257 Q_SLOT void onRotatePrev() { _rot -= 0.25; } 258 Q_SLOT void onRotateNext() { _rot += 0.25; } 259 260 Q_SLOT void onTimer() { 261 if (_animate) { 262 _rot += 0.25; 263 if (_rot >= 360) _rot -= 360.0; 264 } 265 266 _canvas.update_canvas(true); 267 _updateTitle(); 268 } 269 270 void on_render_blend2d(BLContext& ctx) noexcept { 271 ctx.fill_all(BLRgba32(0xFF00007Fu)); 272 273 bool renderStroke = _render_stroke; 274 double min_x = 17; 275 double min_y = 53; 276 double max_x = 562.0; 277 double max_y = 613.0; 278 double s = bl_min(_canvas.image_width() / (max_x - min_x), _canvas.image_height() / (max_y - min_y)) * _scale; 279 280 BLMatrix2D transform; 281 transform.reset(); 282 transform.rotate((_rot / 180.0) * PI, min_x + max_x / 2.0, min_y + max_y / 2.0); 283 transform.post_translate(-max_x / 2, -max_y / 2); 284 285 ctx.save(); 286 ctx.translate(_canvas.image_width() / 2, _canvas.image_height() / 2); 287 ctx.scale(s); 288 ctx.apply_transform(transform); 289 290 for (size_t i = 0, count = _tiger.paths.size(); i < count; i++) { 291 const TigerPath* tp = _tiger.paths[i]; 292 293 if (tp->fill) { 294 ctx.set_fill_rule(tp->fill_rule); 295 ctx.fill_path(tp->bl_path, tp->fill_color); 296 } 297 298 if (tp->stroke && renderStroke) { 299 if (_cache_stroke) { 300 ctx.fill_path(tp->bl_stroked_path, tp->stroke_color); 301 } 302 else { 303 ctx.set_stroke_options(tp->bl_stroke_options); 304 ctx.stroke_path(tp->bl_path, tp->stroke_color); 305 } 306 } 307 } 308 309 ctx.restore(); 310 } 311 312 void on_render_qt(QPainter& ctx) noexcept { 313 bool renderStroke = _render_stroke; 314 315 ctx.fillRect(0, 0, _canvas.image_width(), _canvas.image_height(), QColor(0, 0, 0x7F)); 316 ctx.setRenderHint(QPainter::Antialiasing, true); 317 318 double min_x = 17; 319 double min_y = 53; 320 double max_x = 562.0; 321 double max_y = 613.0; 322 double s = bl_min(_canvas.image_width() / (max_x - min_x), _canvas.image_height() / (max_y - min_y)) * _scale; 323 324 BLMatrix2D m; 325 m.reset(); 326 m.rotate((_rot / 180.0) * PI, min_x + max_x / 2.0, min_y + max_y / 2.0); 327 m.post_translate(-max_x / 2, -max_y / 2); 328 329 ctx.save(); 330 ctx.translate(_canvas.image_width() / 2, _canvas.image_height() / 2); 331 ctx.scale(s, s); 332 ctx.setTransform(QTransform(m.m00, m.m01, m.m10, m.m11, m.m20, m.m21), true); 333 334 for (size_t i = 0, count = _tiger.paths.size(); i < count; i++) { 335 const TigerPath* tp = _tiger.paths[i]; 336 337 if (tp->fill) { 338 ctx.fillPath(tp->qt_path, tp->qt_brush); 339 } 340 341 if (tp->stroke && renderStroke) { 342 if (_cache_stroke) 343 ctx.fillPath(tp->qt_stroked_path, tp->qt_pen.brush()); 344 else 345 ctx.strokePath(tp->qt_path, tp->qt_pen); 346 } 347 } 348 349 ctx.restore(); 350 } 351 352 void _updateTitle() { 353 char buf[256]; 354 snprintf(buf, 256, "Tiger [%dx%d] [RenderTime=%.2fms FPS=%.1f]", 355 _canvas.image_width(), 356 _canvas.image_height(), 357 _canvas.average_render_time(), 358 _canvas.fps()); 359 360 QString title = QString::fromUtf8(buf); 361 if (title != windowTitle()) 362 setWindowTitle(title); 363 } 364 }; 365 366 int main(int argc, char *argv[]) { 367 QApplication app(argc, argv); 368 MainWindow win; 369 370 win.setMinimumSize(QSize(400, 320)); 371 win.resize(QSize(580, 520)); 372 win.show(); 373 374 return app.exec(); 375 } 376 377 #include "bl_demo_tiger.moc"