bl_bench_backend_juce.cpp (18413B)
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_JUCE 7 8 #include "bl_bench_app.h" 9 #include "bl_bench_backend.h" 10 11 #include <juce_graphics/juce_graphics.h> 12 13 namespace blbench { 14 15 static inline juce::Image::PixelFormat to_juce_format(BLFormat format) noexcept { 16 switch (format) { 17 case BL_FORMAT_PRGB32: 18 return juce::Image::ARGB; 19 20 case BL_FORMAT_XRGB32: 21 return juce::Image::RGB; 22 23 case BL_FORMAT_A8: 24 return juce::Image::SingleChannel; 25 26 default: 27 return juce::Image::UnknownFormat; 28 } 29 } 30 31 static inline BLFormat to_blend2d_format(juce::Image::PixelFormat format) noexcept { 32 switch (format) { 33 case juce::Image::ARGB: 34 return BL_FORMAT_PRGB32; 35 36 case juce::Image::RGB: 37 return BL_FORMAT_XRGB32; 38 39 case juce::Image::SingleChannel: 40 return BL_FORMAT_A8; 41 42 default: 43 return BL_FORMAT_NONE; 44 } 45 } 46 47 static void convert_blend2d_image_to_juce_image(juce::Image& dst, BLImage& src, const juce::ImageType& imageType) { 48 BLImageData src_data; 49 src.get_data(&src_data); 50 51 BLFormat format = BLFormat(src_data.format); 52 uint32_t w = uint32_t(src_data.size.w); 53 uint32_t h = uint32_t(src_data.size.h); 54 size_t row_size = size_t(w) * (bl_format_info[format].depth / 8); 55 56 if (dst.getWidth() != int(w) && dst.getHeight() != h || dst.getFormat() != to_juce_format(format)) { 57 dst = juce::Image(to_juce_format(format), int(w), int(h), false, imageType); 58 } 59 60 juce::Image::BitmapData dst_data(dst, juce::Image::BitmapData::readWrite); 61 62 if (format == BL_FORMAT_XRGB32) { 63 for (uint32_t y = 0; y < h; y++) { 64 uint8_t* dst_line = dst_data.data + intptr_t(y) * dst_data.lineStride; 65 const uint8_t* src_line = static_cast<const uint8_t*>(src_data.pixel_data) + intptr_t(y) * src_data.stride; 66 67 for (uint32_t x = 0; x < w; x++) { 68 dst_line[x * 3 + 0] = src_line[x * 4 + 2]; 69 dst_line[x * 3 + 1] = src_line[x * 4 + 1]; 70 dst_line[x * 3 + 2] = src_line[x * 4 + 0]; 71 } 72 } 73 } 74 else { 75 for (uint32_t y = 0; y < h; y++) { 76 memcpy( 77 dst_data.data + intptr_t(y) * dst_data.lineStride, 78 static_cast<const uint8_t*>(src_data.pixel_data) + intptr_t(y) * src_data.stride, 79 row_size); 80 } 81 } 82 } 83 84 static void convert_juce_image_to_blend2d_image(BLImage& dst, juce::Image& src) { 85 juce::Image::BitmapData src_data(src, juce::Image::BitmapData::readOnly); 86 BLFormat format = to_blend2d_format(src_data.pixelFormat); 87 uint32_t w = uint32_t(src_data.width); 88 uint32_t h = uint32_t(src_data.height); 89 size_t row_size = size_t(w) * (bl_format_info[format].depth / 8); 90 91 BLImageData dst_data; 92 dst.create(int(w), int(h), format); 93 dst.make_mutable(&dst_data); 94 95 if (format == BL_FORMAT_XRGB32) { 96 for (uint32_t y = 0; y < h; y++) { 97 uint8_t* dst_line = static_cast<uint8_t*>(dst_data.pixel_data) + intptr_t(y) * dst_data.stride; 98 const uint8_t* src_line = src_data.data + intptr_t(y) * src_data.lineStride; 99 100 for (uint32_t x = 0; x < w; x++) { 101 dst_line[x * 4 + 0] = src_line[x * 3 + 2]; 102 dst_line[x * 4 + 1] = src_line[x * 3 + 1]; 103 dst_line[x * 4 + 2] = src_line[x * 3 + 0]; 104 dst_line[x * 4 + 3] = uint8_t(0xFF); 105 } 106 } 107 } 108 else { 109 for (uint32_t y = 0; y < h; y++) { 110 memcpy( 111 static_cast<uint8_t*>(dst_data.pixel_data) + intptr_t(y) * dst_data.stride, 112 src_data.data + intptr_t(y) * src_data.lineStride, 113 row_size); 114 } 115 } 116 } 117 118 static inline juce::Colour toJuceColor(BLRgba32 rgba) noexcept { 119 return juce::Colour( 120 uint8_t(rgba.r()), 121 uint8_t(rgba.g()), 122 uint8_t(rgba.b()), 123 uint8_t(rgba.a())); 124 } 125 126 struct JuceModule : public Backend { 127 juce::SoftwareImageType _juce_image_type; 128 juce::PathStrokeType _juce_stroke_type; 129 float _line_thickness {}; 130 uint32_t _opaque_bits {}; 131 132 juce::Image _juce_surface; 133 juce::Image _juce_sprites[kBenchNumSprites]; 134 juce::Image _juce_sprites_opaque[kBenchNumSprites]; 135 juce::Graphics* _juce_context {}; 136 137 JuceModule(); 138 ~JuceModule() override; 139 140 void serialize_info(JSONBuilder& json) const override; 141 142 bool supports_comp_op(BLCompOp comp_op) const override; 143 bool supports_style(StyleKind style) const override; 144 145 void before_run() override; 146 void flush() override; 147 void after_run() override; 148 149 template<typename RectT> 150 inline void setup_style(const RectT& rect, StyleKind style); 151 152 void render_rect_a(RenderOp op) override; 153 void render_rect_f(RenderOp op) override; 154 void render_rect_rotated(RenderOp op) override; 155 void render_round_f(RenderOp op) override; 156 void render_round_rotated(RenderOp op) override; 157 void render_polygon(RenderOp op, uint32_t complexity) override; 158 void render_shape(RenderOp op, ShapeData shape) override; 159 }; 160 161 JuceModule::JuceModule() 162 : _juce_stroke_type(1.0f) { 163 strcpy(_name, "JUCE"); 164 } 165 166 JuceModule::~JuceModule() {} 167 168 void JuceModule::serialize_info(JSONBuilder& json) const { 169 #if defined(JUCE_VERSION) 170 uint32_t major_version = (JUCE_VERSION >> 16); 171 uint32_t minor_version = (JUCE_VERSION >> 8) & 0xFF; 172 uint32_t patch_version = (JUCE_VERSION >> 0) & 0xFF; 173 174 json.before_record() 175 .add_key("version") 176 .add_stringf("%u.%u.%u", major_version, minor_version, patch_version); 177 #endif // JUCE_VERSION 178 } 179 180 bool JuceModule::supports_comp_op(BLCompOp comp_op) const { 181 return comp_op == BL_COMP_OP_SRC_OVER || 182 comp_op == BL_COMP_OP_SRC_COPY ; 183 } 184 185 bool JuceModule::supports_style(StyleKind style) const { 186 return style == StyleKind::kSolid || 187 style == StyleKind::kLinearPad || 188 style == StyleKind::kRadialPad || 189 style == StyleKind::kPatternNN || 190 style == StyleKind::kPatternBI ; 191 } 192 193 void JuceModule::before_run() { 194 int w = int(_params.screen_w); 195 int h = int(_params.screen_h); 196 StyleKind style = _params.style; 197 198 _opaque_bits = _params.comp_op == BL_COMP_OP_SRC_COPY ? 0xFF000000u : 0x00000000u; 199 _line_thickness = float(_params.stroke_width); 200 _juce_stroke_type.setEndStyle(juce::PathStrokeType::butt); 201 _juce_stroke_type.setJointStyle(juce::PathStrokeType::mitered); 202 _juce_stroke_type.setStrokeThickness(_line_thickness); 203 204 for (uint32_t i = 0; i < kBenchNumSprites; i++) { 205 BLImage opaque(_sprites[i]); 206 opaque.convert(BL_FORMAT_XRGB32); 207 208 convert_blend2d_image_to_juce_image(_juce_sprites[i], _sprites[i], _juce_image_type); 209 convert_blend2d_image_to_juce_image(_juce_sprites_opaque[i], opaque, _juce_image_type); 210 } 211 212 // NOTE: There seems to be no way we can use a user provided pixel buffer in JUCE, so let's create two 213 // separate buffers so we can copy to our main `_surface` in `after_run()`. The `after_run()` function 214 // is excluded from rendering time so there is no penalty for JUCE. 215 _juce_surface = juce::Image(to_juce_format(_params.format), w, h, false, _juce_image_type); 216 _juce_surface.clear(juce::Rectangle<int>(0, 0, w, h)); 217 _juce_context = new juce::Graphics(_juce_surface); 218 219 if (_params.style == StyleKind::kPatternBI) { 220 _juce_context->setImageResamplingQuality(juce::Graphics::mediumResamplingQuality); 221 } 222 else { 223 _juce_context->setImageResamplingQuality(juce::Graphics::lowResamplingQuality); 224 } 225 } 226 227 void JuceModule::flush() { 228 } 229 230 void JuceModule::after_run() { 231 if (_juce_context) { 232 delete _juce_context; 233 _juce_context = nullptr; 234 } 235 236 convert_juce_image_to_blend2d_image(_surface, _juce_surface); 237 } 238 239 template<typename RectT> 240 inline void JuceModule::setup_style(const RectT& rect, StyleKind style) { 241 switch (style) { 242 case StyleKind::kLinearPad: 243 case StyleKind::kLinearRepeat: 244 case StyleKind::kLinearReflect: { 245 BLRgba32 c0 = _rnd_color.next_rgba32(_opaque_bits); 246 BLRgba32 c1 = _rnd_color.next_rgba32(_opaque_bits); 247 BLRgba32 c2 = _rnd_color.next_rgba32(_opaque_bits); 248 249 float x0 = float(rect.x) + rect.w * float(0.2); 250 float y0 = float(rect.y) + rect.h * float(0.2); 251 float x1 = float(rect.x) + rect.w * float(0.8); 252 float y1 = float(rect.y) + rect.h * float(0.8); 253 254 juce::ColourGradient gradient(toJuceColor(c0), juce::Point<float>(x0, y0), toJuceColor(c2), juce::Point<float>(x1, y1), false); 255 gradient.addColour(0.5, toJuceColor(c1)); 256 _juce_context->setGradientFill(gradient); 257 break; 258 } 259 260 case StyleKind::kRadialPad: 261 case StyleKind::kRadialRepeat: 262 case StyleKind::kRadialReflect: { 263 BLRgba32 c0 = _rnd_color.next_rgba32(_opaque_bits); 264 BLRgba32 c1 = _rnd_color.next_rgba32(_opaque_bits); 265 BLRgba32 c2 = _rnd_color.next_rgba32(_opaque_bits); 266 267 float cx = float(rect.x + rect.w / 2); 268 float cy = float(rect.y + rect.h / 2); 269 float cr = float((rect.w + rect.h) / 4); 270 float fx = float(cx - cr / 2); 271 float fy = float(cy - cr / 2); 272 273 juce::ColourGradient gradient(toJuceColor(c0), juce::Point<float>(cx, cy), toJuceColor(c2), juce::Point<float>(cx - cr, cy - cr), true); 274 gradient.addColour(0.5, toJuceColor(c1)); 275 _juce_context->setGradientFill(gradient); 276 break; 277 } 278 279 case StyleKind::kPatternNN: 280 case StyleKind::kPatternBI: { 281 juce::AffineTransform transform = juce::AffineTransform::translation(float(rect.x), float(rect.y)); 282 if (_params.comp_op == BL_COMP_OP_SRC_OVER) 283 _juce_context->setFillType(juce::FillType(_juce_sprites[nextSpriteId()], transform)); 284 else 285 _juce_context->setFillType(juce::FillType(_juce_sprites_opaque[nextSpriteId()], transform)); 286 break; 287 } 288 289 default: 290 return; 291 } 292 } 293 294 void JuceModule::render_rect_a(RenderOp op) { 295 BLSizeI bounds(_params.screen_w, _params.screen_h); 296 StyleKind style = _params.style; 297 int wh = _params.shape_size; 298 299 if (style == StyleKind::kSolid) { 300 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) { 301 BLRectI r = _rnd_coord.next_rect_i(bounds, wh, wh); 302 _juce_context->setColour(toJuceColor(_rnd_color.next_rgba32(_opaque_bits))); 303 304 if (op == RenderOp::kStroke) 305 _juce_context->drawRect(juce::Rectangle<int>(r.x, r.y, r.w, r.h), _line_thickness); 306 else 307 _juce_context->fillRect(juce::Rectangle<int>(r.x, r.y, r.w, r.h)); 308 } 309 } 310 else if ((style == StyleKind::kPatternNN || style == StyleKind::kPatternBI) && op != RenderOp::kStroke) { 311 if (_params.comp_op == BL_COMP_OP_SRC_OVER) { 312 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) { 313 BLRectI rect(_rnd_coord.next_rect_i(bounds, wh, wh)); 314 _juce_context->drawImageAt(_juce_sprites[nextSpriteId()], rect.x, rect.y); 315 } 316 } 317 else { 318 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) { 319 BLRectI rect(_rnd_coord.next_rect_i(bounds, wh, wh)); 320 _juce_context->drawImageAt(_juce_sprites_opaque[nextSpriteId()], rect.x, rect.y); 321 } 322 } 323 } 324 else { 325 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) { 326 BLRectI r = _rnd_coord.next_rect_i(bounds, wh, wh); 327 setup_style(r, style); 328 329 if (op == RenderOp::kStroke) 330 _juce_context->drawRect(juce::Rectangle<int>(r.x, r.y, r.w, r.h), _line_thickness); 331 else 332 _juce_context->fillRect(juce::Rectangle<int>(r.x, r.y, r.w, r.h)); 333 } 334 } 335 } 336 337 void JuceModule::render_rect_f(RenderOp op) { 338 BLSize bounds(_params.screen_w, _params.screen_h); 339 StyleKind style = _params.style; 340 double wh = _params.shape_size; 341 342 if (style == StyleKind::kSolid) { 343 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) { 344 BLRect r = _rnd_coord.next_rect(bounds, wh, wh); 345 _juce_context->setColour(toJuceColor(_rnd_color.next_rgba32(_opaque_bits))); 346 347 if (op == RenderOp::kStroke) 348 _juce_context->drawRect(juce::Rectangle<float>(float(r.x), float(r.y), float(r.w), float(r.h)), _line_thickness); 349 else 350 _juce_context->fillRect(juce::Rectangle<float>(float(r.x), float(r.y), float(r.w), float(r.h))); 351 } 352 } 353 else { 354 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) { 355 BLRect r = _rnd_coord.next_rect(bounds, wh, wh); 356 setup_style(r, style); 357 358 if (op == RenderOp::kStroke) 359 _juce_context->drawRect(juce::Rectangle<float>(float(r.x), float(r.y), float(r.w), float(r.h)), _line_thickness); 360 else 361 _juce_context->fillRect(juce::Rectangle<float>(float(r.x), float(r.y), float(r.w), float(r.h))); 362 } 363 } 364 } 365 366 void JuceModule::render_rect_rotated(RenderOp op) { 367 BLSize bounds(_params.screen_w, _params.screen_h); 368 StyleKind style = _params.style; 369 370 double cx = double(_params.screen_w) * 0.5; 371 double cy = double(_params.screen_h) * 0.5; 372 double wh = _params.shape_size; 373 double angle = 0.0; 374 375 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++, angle += 0.01) { 376 BLRect r(_rnd_coord.next_rect(bounds, wh, wh)); 377 juce::AffineTransform tr = juce::AffineTransform::rotation(float(angle), float(cx), float(cy)); 378 379 _juce_context->saveState(); 380 _juce_context->addTransform(tr); 381 382 if (style == StyleKind::kSolid) { 383 _juce_context->setColour(toJuceColor(_rnd_color.next_rgba32(_opaque_bits))); 384 } 385 else { 386 setup_style(r, style); 387 } 388 389 if (op == RenderOp::kStroke) 390 _juce_context->drawRect(juce::Rectangle<float>(float(r.x), float(r.y), float(r.w), float(r.h)), _line_thickness); 391 else 392 _juce_context->fillRect(juce::Rectangle<float>(float(r.x), float(r.y), float(r.w), float(r.h))); 393 394 _juce_context->restoreState(); 395 } 396 } 397 398 void JuceModule::render_round_f(RenderOp op) { 399 BLSize bounds(_params.screen_w, _params.screen_h); 400 StyleKind style = _params.style; 401 double wh = _params.shape_size; 402 403 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) { 404 BLRect r(_rnd_coord.next_rect(bounds, wh, wh)); 405 float radius = float(_rnd_extra.next_double(4.0, 40.0)); 406 407 if (style == StyleKind::kSolid) { 408 _juce_context->setColour(toJuceColor(_rnd_color.next_rgba32(_opaque_bits))); 409 } 410 else { 411 setup_style(r, style); 412 } 413 414 if (op == RenderOp::kStroke) 415 _juce_context->drawRoundedRectangle(float(r.x), float(r.y), float(r.w), float(r.h), radius, _line_thickness); 416 else 417 _juce_context->fillRoundedRectangle(float(r.x), float(r.y), float(r.w), float(r.h), radius); 418 } 419 } 420 421 void JuceModule::render_round_rotated(RenderOp op) { 422 BLSize bounds(_params.screen_w, _params.screen_h); 423 StyleKind style = _params.style; 424 425 double cx = double(_params.screen_w) * 0.5; 426 double cy = double(_params.screen_h) * 0.5; 427 double wh = _params.shape_size; 428 double angle = 0.0; 429 430 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++, angle += 0.01) { 431 BLRect r(_rnd_coord.next_rect(bounds, wh, wh)); 432 float radius = float(_rnd_extra.next_double(4.0, 40.0)); 433 juce::AffineTransform tr = juce::AffineTransform::rotation(float(angle), float(cx), float(cy)); 434 435 _juce_context->saveState(); 436 _juce_context->addTransform(tr); 437 438 if (style == StyleKind::kSolid) { 439 _juce_context->setColour(toJuceColor(_rnd_color.next_rgba32(_opaque_bits))); 440 } 441 else { 442 setup_style(r, style); 443 } 444 445 if (op == RenderOp::kStroke) 446 _juce_context->drawRoundedRectangle(float(r.x), float(r.y), float(r.w), float(r.h), radius, _line_thickness); 447 else 448 _juce_context->fillRoundedRectangle(float(r.x), float(r.y), float(r.w), float(r.h), radius); 449 450 _juce_context->restoreState(); 451 } 452 } 453 454 void JuceModule::render_polygon(RenderOp op, uint32_t complexity) { 455 BLSizeI bounds(_params.screen_w - _params.shape_size, 456 _params.screen_h - _params.shape_size); 457 StyleKind style = _params.style; 458 double wh = double(_params.shape_size); 459 460 juce::Path path; 461 path.setUsingNonZeroWinding(op != RenderOp::kFillEvenOdd); 462 463 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) { 464 BLPoint base(_rnd_coord.nextPoint(bounds)); 465 466 double x = _rnd_coord.next_double(base.x, base.x + wh); 467 double y = _rnd_coord.next_double(base.y, base.y + wh); 468 469 path.clear(); 470 path.startNewSubPath(float(x), float(y)); 471 472 for (uint32_t p = 1; p < complexity; p++) { 473 x = _rnd_coord.next_double(base.x, base.x + wh); 474 y = _rnd_coord.next_double(base.y, base.y + wh); 475 path.lineTo(float(x), float(y)); 476 } 477 478 path.closeSubPath(); 479 480 if (style == StyleKind::kSolid) { 481 _juce_context->setColour(toJuceColor(_rnd_color.next_rgba32(_opaque_bits))); 482 } 483 else { 484 setup_style(BLRect(x, y, wh, wh), style); 485 } 486 487 if (op == RenderOp::kStroke) 488 _juce_context->strokePath(path, _juce_stroke_type); 489 else 490 _juce_context->fillPath(path); 491 } 492 } 493 494 void JuceModule::render_shape(RenderOp op, ShapeData shape) { 495 BLSizeI bounds(_params.screen_w - _params.shape_size, 496 _params.screen_h - _params.shape_size); 497 StyleKind style = _params.style; 498 double wh = double(_params.shape_size); 499 500 juce::Path path; 501 path.setUsingNonZeroWinding(op != RenderOp::kFillEvenOdd); 502 503 ShapeIterator it(shape); 504 while (it.has_command()) { 505 if (it.is_move_to()) { 506 path.startNewSubPath( 507 float(it.x(0) * wh), float(it.y(0) * wh)); 508 } 509 else if (it.is_line_to()) { 510 path.lineTo( 511 float(it.x(0) * wh), float(it.y(0) * wh)); 512 } 513 else if (it.is_quad_to()) { 514 path.quadraticTo( 515 float(it.x(0) * wh), float(it.y(0) * wh), 516 float(it.x(1) * wh), float(it.y(1) * wh)); 517 } 518 else if (it.is_cubic_to()) { 519 path.cubicTo( 520 float(it.x(0) * wh), float(it.y(0) * wh), 521 float(it.x(1) * wh), float(it.y(1) * wh), 522 float(it.x(2) * wh), float(it.y(2) * wh)); 523 } 524 else { 525 path.closeSubPath(); 526 } 527 it.next(); 528 } 529 530 for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) { 531 BLPoint base(_rnd_coord.nextPoint(bounds)); 532 juce::AffineTransform transform = juce::AffineTransform::translation(float(base.x), float(base.y)); 533 534 if (style == StyleKind::kSolid) { 535 _juce_context->setColour(toJuceColor(_rnd_color.next_rgba32(_opaque_bits))); 536 } 537 else { 538 setup_style(BLRect(base.x, base.y, wh, wh), style); 539 } 540 541 if (op == RenderOp::kStroke) 542 _juce_context->strokePath(path, _juce_stroke_type, transform); 543 else 544 _juce_context->fillPath(path, transform); 545 } 546 } 547 548 Backend* create_juce_backend() { 549 return new JuceModule(); 550 } 551 552 } // {blbench} 553 554 #endif // BL_BENCH_ENABLE_JUCE