odin-blend2d

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

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"