bl_bench_backend_cairo.cpp (17642B)
1 // This file is part of Blend2D project <https://blend2d.com> 2 // 3 // See LICENSE.md for license and copyright information 4 // SPDX-License-Identifier: Zlib 5 6 #ifdef BL_BENCH_ENABLE_CAIRO 7 8 #include "bl_bench_app.h" 9 #include "bl_bench_backend.h" 10 11 #include <algorithm> 12 #include <cairo.h> 13 14 namespace blbench { 15 16 static inline double u8_to_unit(int x) { 17 constexpr double kDiv255 = 1.0 / 255.0; 18 return double(x) * kDiv255; 19 } 20 21 static uint32_t to_cairo_format(uint32_t format) { 22 switch (format) { 23 case BL_FORMAT_PRGB32: return CAIRO_FORMAT_ARGB32; 24 case BL_FORMAT_XRGB32: return CAIRO_FORMAT_RGB24; 25 case BL_FORMAT_A8 : return CAIRO_FORMAT_A8; 26 default: 27 return 0xFFFFFFFFu; 28 } 29 } 30 31 static uint32_t to_cairo_operator(uint32_t comp_op) { 32 switch (comp_op) { 33 case BL_COMP_OP_SRC_OVER : return CAIRO_OPERATOR_OVER; 34 case BL_COMP_OP_SRC_COPY : return CAIRO_OPERATOR_SOURCE; 35 case BL_COMP_OP_SRC_IN : return CAIRO_OPERATOR_IN; 36 case BL_COMP_OP_SRC_OUT : return CAIRO_OPERATOR_OUT; 37 case BL_COMP_OP_SRC_ATOP : return CAIRO_OPERATOR_ATOP; 38 case BL_COMP_OP_DST_OVER : return CAIRO_OPERATOR_DEST_OVER; 39 case BL_COMP_OP_DST_COPY : return CAIRO_OPERATOR_DEST; 40 case BL_COMP_OP_DST_IN : return CAIRO_OPERATOR_DEST_IN; 41 case BL_COMP_OP_DST_OUT : return CAIRO_OPERATOR_DEST_OUT; 42 case BL_COMP_OP_DST_ATOP : return CAIRO_OPERATOR_DEST_ATOP; 43 case BL_COMP_OP_XOR : return CAIRO_OPERATOR_XOR; 44 case BL_COMP_OP_CLEAR : return CAIRO_OPERATOR_CLEAR; 45 case BL_COMP_OP_PLUS : return CAIRO_OPERATOR_ADD; 46 case BL_COMP_OP_MULTIPLY : return CAIRO_OPERATOR_MULTIPLY; 47 case BL_COMP_OP_SCREEN : return CAIRO_OPERATOR_SCREEN; 48 case BL_COMP_OP_OVERLAY : return CAIRO_OPERATOR_OVERLAY; 49 case BL_COMP_OP_DARKEN : return CAIRO_OPERATOR_DARKEN; 50 case BL_COMP_OP_LIGHTEN : return CAIRO_OPERATOR_LIGHTEN; 51 case BL_COMP_OP_COLOR_DODGE: return CAIRO_OPERATOR_COLOR_DODGE; 52 case BL_COMP_OP_COLOR_BURN : return CAIRO_OPERATOR_COLOR_BURN; 53 case BL_COMP_OP_HARD_LIGHT : return CAIRO_OPERATOR_HARD_LIGHT; 54 case BL_COMP_OP_SOFT_LIGHT : return CAIRO_OPERATOR_SOFT_LIGHT; 55 case BL_COMP_OP_DIFFERENCE : return CAIRO_OPERATOR_DIFFERENCE; 56 case BL_COMP_OP_EXCLUSION : return CAIRO_OPERATOR_EXCLUSION; 57 58 default: 59 return 0xFFFFFFFFu; 60 } 61 } 62 63 static void round_rect(cairo_t* ctx, const BLRect& rect, double radius) { 64 double rw2 = rect.w * 0.5; 65 double rh2 = rect.h * 0.5; 66 67 double rx = std::min(bl_abs(radius), rw2); 68 double ry = std::min(bl_abs(radius), rh2); 69 70 double kappa_inv = 1 - 0.551915024494; 71 double kx = rx * kappa_inv; 72 double ky = ry * kappa_inv; 73 74 double x0 = rect.x; 75 double y0 = rect.y; 76 double x1 = rect.x + rect.w; 77 double y1 = rect.y + rect.h; 78 79 cairo_move_to(ctx, x0 + rx, y0); 80 cairo_line_to(ctx, x1 - rx, y0); 81 cairo_curve_to(ctx, x1 - kx, y0, x1, y0 + ky, x1, y0 + ry); 82 83 cairo_line_to(ctx, x1, y1 - ry); 84 cairo_curve_to(ctx, x1, y1 - ky, x1 - kx, y1, x1 - rx, y1); 85 86 cairo_line_to(ctx, x0 + rx, y1); 87 cairo_curve_to(ctx, x0 + kx, y1, x0, y1 - ky, x0, y1 - ry); 88 89 cairo_line_to(ctx, x0, y0 + ry); 90 cairo_curve_to(ctx, x0, y0 + ky, x0 + kx, y0, x0 + rx, y0); 91 92 cairo_close_path(ctx); 93 } 94 95 struct CairoModule : public Backend { 96 cairo_surface_t* _cairo_surface {}; 97 cairo_surface_t* _cairo_sprites[kBenchNumSprites] {}; 98 cairo_t* _cairo_ctx {}; 99 100 // Initialized by before_run(). 101 uint32_t _pattern_extend {}; 102 uint32_t _pattern_filter {}; 103 104 CairoModule(); 105 ~CairoModule() override; 106 107 void serialize_info(JSONBuilder& json) const override; 108 109 template<typename RectT> 110 void setup_style(StyleKind style, const RectT& rect); 111 112 bool supports_comp_op(BLCompOp comp_op) const override; 113 bool supports_style(StyleKind style) const override; 114 115 void before_run() override; 116 void flush() override; 117 void after_run() override; 118 119 void render_rect_a(RenderOp op) override; 120 void render_rect_f(RenderOp op) override; 121 void render_rect_rotated(RenderOp op) override; 122 void render_round_f(RenderOp op) override; 123 void render_round_rotated(RenderOp op) override; 124 void render_polygon(RenderOp op, uint32_t complexity) override; 125 void render_shape(RenderOp op, ShapeData shape) override; 126 }; 127 128 CairoModule::CairoModule() { 129 strcpy(_name, "Cairo"); 130 } 131 CairoModule::~CairoModule() {} 132 133 void CairoModule::serialize_info(JSONBuilder& json) const { 134 json.before_record() 135 .add_key("version") 136 .add_string(cairo_version_string()); 137 } 138 139 template<typename RectT> 140 void CairoModule::setup_style(StyleKind style, const RectT& rect) { 141 switch (style) { 142 case StyleKind::kSolid: { 143 BLRgba32 c(_rnd_color.next_rgba32()); 144 cairo_set_source_rgba(_cairo_ctx, u8_to_unit(c.r()), u8_to_unit(c.g()), u8_to_unit(c.b()), u8_to_unit(c.a())); 145 return; 146 } 147 148 case StyleKind::kLinearPad: 149 case StyleKind::kLinearRepeat: 150 case StyleKind::kLinearReflect: 151 case StyleKind::kRadialPad: 152 case StyleKind::kRadialRepeat: 153 case StyleKind::kRadialReflect: { 154 double x = rect.x; 155 double y = rect.y; 156 157 BLRgba32 c0(_rnd_color.next_rgba32()); 158 BLRgba32 c1(_rnd_color.next_rgba32()); 159 BLRgba32 c2(_rnd_color.next_rgba32()); 160 161 cairo_pattern_t* pattern {}; 162 if (style < StyleKind::kRadialPad) { 163 // Linear gradient. 164 double x0 = rect.x + rect.w * 0.2; 165 double y0 = rect.y + rect.h * 0.2; 166 double x1 = rect.x + rect.w * 0.8; 167 double y1 = rect.y + rect.h * 0.8; 168 pattern = cairo_pattern_create_linear(x0, y0, x1, y1); 169 170 cairo_pattern_add_color_stop_rgba(pattern, 0.0, u8_to_unit(c0.r()), u8_to_unit(c0.g()), u8_to_unit(c0.b()), u8_to_unit(c0.a())); 171 cairo_pattern_add_color_stop_rgba(pattern, 0.5, u8_to_unit(c1.r()), u8_to_unit(c1.g()), u8_to_unit(c1.b()), u8_to_unit(c1.a())); 172 cairo_pattern_add_color_stop_rgba(pattern, 1.0, u8_to_unit(c2.r()), u8_to_unit(c2.g()), u8_to_unit(c2.b()), u8_to_unit(c2.a())); 173 } 174 else { 175 // Radial gradient. 176 x += double(rect.w) / 2.0; 177 y += double(rect.h) / 2.0; 178 179 double r = double(rect.w + rect.h) / 4.0; 180 pattern = cairo_pattern_create_radial(x, y, r, x - r / 2, y - r / 2, 0.0); 181 182 // Color stops in Cairo's radial gradient are reverse to Blend/Qt. 183 cairo_pattern_add_color_stop_rgba(pattern, 0.0, u8_to_unit(c2.r()), u8_to_unit(c2.g()), u8_to_unit(c2.b()), u8_to_unit(c2.a())); 184 cairo_pattern_add_color_stop_rgba(pattern, 0.5, u8_to_unit(c1.r()), u8_to_unit(c1.g()), u8_to_unit(c1.b()), u8_to_unit(c1.a())); 185 cairo_pattern_add_color_stop_rgba(pattern, 1.0, u8_to_unit(c0.r()), u8_to_unit(c0.g()), u8_to_unit(c0.b()), u8_to_unit(c0.a())); 186 } 187 188 cairo_pattern_set_extend(pattern, cairo_extend_t(_pattern_extend)); 189 cairo_set_source(_cairo_ctx, pattern); 190 cairo_pattern_destroy(pattern); 191 return; 192 } 193 194 case StyleKind::kPatternNN: 195 case StyleKind::kPatternBI: { 196 // Matrix associated with cairo_pattern_t is inverse to Blend/Qt. 197 cairo_matrix_t matrix; 198 cairo_matrix_init_translate(&matrix, -rect.x, -rect.y); 199 200 cairo_pattern_t* pattern = cairo_pattern_create_for_surface(_cairo_sprites[nextSpriteId()]); 201 cairo_pattern_set_matrix(pattern, &matrix); 202 cairo_pattern_set_extend(pattern, cairo_extend_t(_pattern_extend)); 203 cairo_pattern_set_filter(pattern, cairo_filter_t(_pattern_filter)); 204 205 cairo_set_source(_cairo_ctx, pattern); 206 cairo_pattern_destroy(pattern); 207 return; 208 } 209 210 default: { 211 return; 212 } 213 } 214 } 215 216 bool CairoModule::supports_comp_op(BLCompOp comp_op) const { 217 return to_cairo_operator(comp_op) != 0xFFFFFFFFu; 218 } 219 220 bool CairoModule::supports_style(StyleKind style) const { 221 return style == StyleKind::kSolid || 222 style == StyleKind::kLinearPad || 223 style == StyleKind::kLinearRepeat || 224 style == StyleKind::kLinearReflect || 225 style == StyleKind::kRadialPad || 226 style == StyleKind::kRadialRepeat || 227 style == StyleKind::kRadialReflect || 228 style == StyleKind::kPatternNN || 229 style == StyleKind::kPatternBI ; 230 } 231 232 void CairoModule::before_run() { 233 int w = int(_params.screen_w); 234 int h = int(_params.screen_h); 235 StyleKind style = _params.style; 236 237 // Initialize the sprites. 238 for (uint32_t i = 0; i < kBenchNumSprites; i++) { 239 const BLImage& sprite = _sprites[i]; 240 241 BLImageData sprite_data; 242 sprite.get_data(&sprite_data); 243 244 int stride = int(sprite_data.stride); 245 int format = to_cairo_format(sprite_data.format); 246 unsigned char* pixels = static_cast<unsigned char*>(sprite_data.pixel_data); 247 248 cairo_surface_t* cairo_sprite = cairo_image_surface_create_for_data( 249 pixels, cairo_format_t(format), sprite_data.size.w, sprite_data.size.h, stride); 250 251 _cairo_sprites[i] = cairo_sprite; 252 } 253 254 // Initialize the surface and the context. 255 { 256 BLImageData surface_data; 257 _surface.create(w, h, _params.format); 258 _surface.make_mutable(&surface_data); 259 260 int stride = int(surface_data.stride); 261 int format = to_cairo_format(surface_data.format); 262 unsigned char* pixels = (unsigned char*)surface_data.pixel_data; 263 264 _cairo_surface = cairo_image_surface_create_for_data( 265 pixels, cairo_format_t(format), w, h, stride); 266 267 if (_cairo_surface == nullptr) { 268 return; 269 } 270 271 _cairo_ctx = cairo_create(_cairo_surface); 272 if (_cairo_ctx == nullptr) { 273 return; 274 } 275 } 276 277 // Setup the context. 278 cairo_set_operator(_cairo_ctx, CAIRO_OPERATOR_CLEAR); 279 cairo_rectangle(_cairo_ctx, 0, 0, w, h); 280 cairo_fill(_cairo_ctx); 281 282 cairo_set_operator(_cairo_ctx, cairo_operator_t(to_cairo_operator(_params.comp_op))); 283 cairo_set_line_width(_cairo_ctx, _params.stroke_width); 284 285 // Setup globals. 286 _pattern_extend = CAIRO_EXTEND_REPEAT; 287 _pattern_filter = CAIRO_FILTER_NEAREST; 288 289 switch (style) { 290 case StyleKind::kSolid : break; 291 case StyleKind::kLinearPad : _pattern_extend = CAIRO_EXTEND_PAD ; break; 292 case StyleKind::kLinearRepeat : _pattern_extend = CAIRO_EXTEND_REPEAT ; break; 293 case StyleKind::kLinearReflect : _pattern_extend = CAIRO_EXTEND_REFLECT ; break; 294 case StyleKind::kRadialPad : _pattern_extend = CAIRO_EXTEND_PAD ; break; 295 case StyleKind::kRadialRepeat : _pattern_extend = CAIRO_EXTEND_REPEAT ; break; 296 case StyleKind::kRadialReflect : _pattern_extend = CAIRO_EXTEND_REFLECT ; break; 297 case StyleKind::kPatternNN : _pattern_filter = CAIRO_FILTER_NEAREST ; break; 298 case StyleKind::kPatternBI : _pattern_filter = CAIRO_FILTER_BILINEAR; break; 299 300 // These are not supported. 301 case StyleKind::kConic: 302 break; 303 } 304 } 305 306 void CairoModule::flush() { 307 // Nothing... 308 } 309 310 void CairoModule::after_run() { 311 // Free the surface & the context. 312 cairo_destroy(_cairo_ctx); 313 cairo_surface_destroy(_cairo_surface); 314 315 _cairo_ctx = nullptr; 316 _cairo_surface = nullptr; 317 318 // Free the sprites. 319 for (uint32_t i = 0; i < kBenchNumSprites; i++) { 320 cairo_surface_destroy(_cairo_sprites[i]); 321 _cairo_sprites[i] = nullptr; 322 } 323 } 324 325 void CairoModule::render_rect_a(RenderOp op) { 326 BLSizeI bounds(_params.screen_w, _params.screen_h); 327 StyleKind style = _params.style; 328 329 int wh = _params.shape_size; 330 331 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) { 332 BLRectI rect(_rnd_coord.next_rect_i(bounds, wh, wh)); 333 setup_style<BLRectI>(style, rect); 334 335 if (op == RenderOp::kStroke) { 336 cairo_rectangle(_cairo_ctx, rect.x, rect.y, rect.w, rect.h); 337 cairo_stroke(_cairo_ctx); 338 } 339 else { 340 cairo_rectangle(_cairo_ctx, rect.x, rect.y, rect.w, rect.h); 341 cairo_fill(_cairo_ctx); 342 } 343 } 344 } 345 346 void CairoModule::render_rect_f(RenderOp op) { 347 BLSize bounds(_params.screen_w, _params.screen_h); 348 StyleKind style = _params.style; 349 350 double wh = _params.shape_size; 351 352 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) { 353 BLRect rect(_rnd_coord.next_rect(bounds, wh, wh)); 354 355 setup_style<BLRect>(style, rect); 356 cairo_rectangle(_cairo_ctx, rect.x, rect.y, rect.w, rect.h); 357 358 if (op == RenderOp::kStroke) { 359 cairo_stroke(_cairo_ctx); 360 } 361 else { 362 cairo_fill(_cairo_ctx); 363 } 364 } 365 } 366 367 void CairoModule::render_rect_rotated(RenderOp op) { 368 BLSize bounds(_params.screen_w, _params.screen_h); 369 StyleKind style = _params.style; 370 371 double cx = double(_params.screen_w) * 0.5; 372 double cy = double(_params.screen_h) * 0.5; 373 double wh = _params.shape_size; 374 double angle = 0.0; 375 376 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++, angle += 0.01) { 377 BLRect rect(_rnd_coord.next_rect(bounds, wh, wh)); 378 379 cairo_translate(_cairo_ctx, cx, cy); 380 cairo_rotate(_cairo_ctx, angle); 381 cairo_translate(_cairo_ctx, -cx, -cy); 382 383 setup_style<BLRect>(style, rect); 384 cairo_rectangle(_cairo_ctx, rect.x, rect.y, rect.w, rect.h); 385 386 if (op == RenderOp::kStroke) { 387 cairo_stroke(_cairo_ctx); 388 } 389 else { 390 cairo_fill(_cairo_ctx); 391 } 392 393 cairo_identity_matrix(_cairo_ctx); 394 } 395 } 396 397 void CairoModule::render_round_f(RenderOp op) { 398 BLSize bounds(_params.screen_w, _params.screen_h); 399 StyleKind style = _params.style; 400 401 double wh = _params.shape_size; 402 403 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) { 404 BLRect rect(_rnd_coord.next_rect(bounds, wh, wh)); 405 double radius = _rnd_extra.next_double(4.0, 40.0); 406 407 setup_style<BLRect>(style, rect); 408 round_rect(_cairo_ctx, rect, radius); 409 410 if (op == RenderOp::kStroke) { 411 cairo_stroke(_cairo_ctx); 412 } 413 else { 414 cairo_fill(_cairo_ctx); 415 } 416 } 417 } 418 419 void CairoModule::render_round_rotated(RenderOp op) { 420 BLSize bounds(_params.screen_w, _params.screen_h); 421 StyleKind style = _params.style; 422 423 double cx = double(_params.screen_w) * 0.5; 424 double cy = double(_params.screen_h) * 0.5; 425 double wh = _params.shape_size; 426 double angle = 0.0; 427 428 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++, angle += 0.01) { 429 BLRect rect(_rnd_coord.next_rect(bounds, wh, wh)); 430 double radius = _rnd_extra.next_double(4.0, 40.0); 431 432 cairo_translate(_cairo_ctx, cx, cy); 433 cairo_rotate(_cairo_ctx, angle); 434 cairo_translate(_cairo_ctx, -cx, -cy); 435 436 setup_style<BLRect>(style, rect); 437 round_rect(_cairo_ctx, rect, radius); 438 439 if (op == RenderOp::kStroke) { 440 cairo_stroke(_cairo_ctx); 441 } 442 else { 443 cairo_fill(_cairo_ctx); 444 } 445 446 cairo_identity_matrix(_cairo_ctx); 447 } 448 } 449 450 void CairoModule::render_polygon(RenderOp op, uint32_t complexity) { 451 BLSizeI bounds(_params.screen_w - _params.shape_size, 452 _params.screen_h - _params.shape_size); 453 StyleKind style = _params.style; 454 double wh = double(_params.shape_size); 455 456 cairo_set_fill_rule(_cairo_ctx, op == RenderOp::kFillEvenOdd ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING); 457 458 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) { 459 BLPoint base(_rnd_coord.nextPoint(bounds)); 460 461 double x = _rnd_coord.next_double(base.x, base.x + wh); 462 double y = _rnd_coord.next_double(base.y, base.y + wh); 463 464 cairo_move_to(_cairo_ctx, x, y); 465 for (uint32_t p = 1; p < complexity; p++) { 466 x = _rnd_coord.next_double(base.x, base.x + wh); 467 y = _rnd_coord.next_double(base.y, base.y + wh); 468 cairo_line_to(_cairo_ctx, x, y); 469 } 470 setup_style<BLRect>(style, BLRect(base.x, base.y, wh, wh)); 471 472 if (op == RenderOp::kStroke) { 473 cairo_stroke(_cairo_ctx); 474 } 475 else { 476 cairo_fill(_cairo_ctx); 477 } 478 } 479 } 480 481 void CairoModule::render_shape(RenderOp op, ShapeData shape) { 482 BLSizeI bounds(_params.screen_w - _params.shape_size, 483 _params.screen_h - _params.shape_size); 484 StyleKind style = _params.style; 485 double wh = double(_params.shape_size); 486 487 ShapeIterator it(shape); 488 while (it.has_command()) { 489 if (it.is_move_to()) { 490 cairo_move_to(_cairo_ctx, it.x(0) * wh, it.y(0) * wh); 491 } 492 else if (it.is_line_to()) { 493 cairo_line_to(_cairo_ctx, it.x(0) * wh, it.y(0) * wh); 494 } 495 else if (it.is_quad_to()) { 496 double x0 = it.x(-1) * wh; 497 double y0 = it.y(-1) * wh; 498 double x1 = it.x(0) * wh; 499 double y1 = it.y(0) * wh; 500 double x2 = it.x(1) * wh; 501 double y2 = it.y(1) * wh; 502 503 cairo_curve_to(_cairo_ctx, 504 (2.0 / 3.0) * x1 + (1.0 / 3.0) * x0, (2.0 / 3.0) * y1 + (1.0 / 3.0) * y0, 505 (2.0 / 3.0) * x1 + (1.0 / 3.0) * x2, (2.0 / 3.0) * y1 + (1.0 / 3.0) * y2, 506 y1, y2); 507 } 508 else if (it.is_cubic_to()) { 509 cairo_curve_to(_cairo_ctx, 510 it.x(0) * wh, it.y(0) * wh, 511 it.x(1) * wh, it.y(1) * wh, 512 it.x(2) * wh, it.y(2) * wh); 513 } 514 else { 515 cairo_close_path(_cairo_ctx); 516 } 517 it.next(); 518 } 519 520 // No idea who invented this, but you need a `cairo_t` to create a `cairo_path_t`. 521 cairo_path_t* path = cairo_copy_path(_cairo_ctx); 522 cairo_new_path(_cairo_ctx); 523 524 cairo_set_fill_rule(_cairo_ctx, op == RenderOp::kFillEvenOdd ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING); 525 526 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) { 527 cairo_save(_cairo_ctx); 528 529 BLPoint base(_rnd_coord.nextPoint(bounds)); 530 setup_style<BLRect>(style, BLRect(base.x, base.y, wh, wh)); 531 532 cairo_translate(_cairo_ctx, base.x, base.y); 533 cairo_append_path(_cairo_ctx, path); 534 535 if (op == RenderOp::kStroke) { 536 cairo_stroke(_cairo_ctx); 537 } 538 else { 539 cairo_fill(_cairo_ctx); 540 } 541 542 cairo_restore(_cairo_ctx); 543 } 544 545 cairo_path_destroy(path); 546 } 547 548 Backend* create_cairo_backend() { 549 return new CairoModule(); 550 } 551 } // {blbench} 552 553 #endif // BL_BENCH_ENABLE_CAIRO