#include "Blend2D_Cache.h" #include #include #include #include #include #include #include #include #include namespace renderive::detail { namespace { void clear_image(BLImage& image) { BLImageData data{}; if (image.get_data(&data) != BL_SUCCESS) return; for (int y = 0; y < data.size.h; ++y) { auto* row = static_cast(data.pixel_data) + static_cast(y) * data.stride; std::memset(row, 0, static_cast(data.size.w) * sizeof(Pixel)); } } void load_font_face(BLFontFace& face, std::initializer_list candidates) { for (const char* path : candidates) { std::error_code error; if (std::filesystem::exists(path, error) && face.create_from_file(path) == BL_SUCCESS) return; } } BLFontFace& default_font_face(const Font& font) { static std::array faces; static std::once_flag once; std::call_once(once, [] { load_font_face(faces[0], { "C:/Windows/Fonts/msyh.ttc", "C:/Windows/Fonts/simsun.ttc", "C:/Windows/Fonts/segoeui.ttf", "C:/Windows/Fonts/arial.ttf", "C:/Windows/Fonts/tahoma.ttf" }); load_font_face(faces[1], { "C:/Windows/Fonts/msyhbd.ttc", "C:/Windows/Fonts/segoeuib.ttf", "C:/Windows/Fonts/arialbd.ttf", "C:/Windows/Fonts/tahomabd.ttf" }); load_font_face(faces[2], { "C:/Windows/Fonts/segoeuii.ttf", "C:/Windows/Fonts/ariali.ttf", "C:/Windows/Fonts/tahomai.ttf" }); load_font_face(faces[3], { "C:/Windows/Fonts/segoeuiz.ttf", "C:/Windows/Fonts/arialbi.ttf", "C:/Windows/Fonts/tahomabi.ttf" }); }); const std::size_t index = (font.weight >= 600 ? 1u : 0u) | (font.italic ? 2u : 0u); return faces[index] ? faces[index] : faces[0]; } BLStrokeCap stroke_cap(Line_Cap cap) noexcept { switch (cap) { case Line_Cap::Square: return BL_STROKE_CAP_SQUARE; case Line_Cap::Round: return BL_STROKE_CAP_ROUND; case Line_Cap::Butt: default: return BL_STROKE_CAP_BUTT; } } BLStrokeJoin stroke_join(Line_Join join) noexcept { switch (join) { case Line_Join::Bevel: return BL_STROKE_JOIN_BEVEL; case Line_Join::Round: return BL_STROKE_JOIN_ROUND; case Line_Join::Miter: default: return BL_STROKE_JOIN_MITER_CLIP; } } double cubic_weight(double distance) noexcept { constexpr double a = -0.5; distance = std::abs(distance); if (distance < 1.0) return (a + 2.0) * distance * distance * distance - (a + 3.0) * distance * distance + 1.0; if (distance < 2.0) return a * distance * distance * distance - 5.0 * a * distance * distance + 8.0 * a * distance - 4.0 * a; return 0.0; } std::uint8_t pixel_channel(Pixel pixel, int shift) noexcept { return static_cast((pixel >> shift) & 0xFFu); } std::vector bicubic_resample(std::span source, int source_width, int source_height, int destination_width, int destination_height) { std::vector destination( static_cast(destination_width) * destination_height); for (int y = 0; y < destination_height; ++y) { const double source_y = (static_cast(y) + 0.5) * source_height / destination_height - 0.5; const int base_y = static_cast(std::floor(source_y)); for (int x = 0; x < destination_width; ++x) { const double source_x = (static_cast(x) + 0.5) * source_width / destination_width - 0.5; const int base_x = static_cast(std::floor(source_x)); double channels[4]{}; double total_weight{}; for (int offset_y = -1; offset_y <= 2; ++offset_y) { const int sample_y = std::clamp(base_y + offset_y, 0, source_height - 1); const double weight_y = cubic_weight(source_y - (base_y + offset_y)); for (int offset_x = -1; offset_x <= 2; ++offset_x) { const int sample_x = std::clamp(base_x + offset_x, 0, source_width - 1); const double weight = weight_y * cubic_weight(source_x - (base_x + offset_x)); const Pixel pixel = source[static_cast(sample_y) * source_width + sample_x]; channels[0] += pixel_channel(pixel, 0) * weight; channels[1] += pixel_channel(pixel, 8) * weight; channels[2] += pixel_channel(pixel, 16) * weight; channels[3] += pixel_channel(pixel, 24) * weight; total_weight += weight; } } const auto channel = [total_weight](double value) { if (total_weight != 0.0) value /= total_weight; return static_cast(std::clamp(std::lround(value), 0L, 255L)); }; destination[static_cast(y) * destination_width + x] = channel(channels[0]) | (channel(channels[1]) << 8) | (channel(channels[2]) << 16) | (channel(channels[3]) << 24); } } return destination; } } // namespace void Blend2D_Color_Cache::clear() { clear_image(image_); } void Blend2D_Color_Cache::composite(const ::Color_Cache& source) { const auto& typed = dynamic_cast(source); if (typed.image_.is_empty()) return; const Size source_size = typed.size(); if (image_.is_empty()) { ensure_size(source_size); clear_image(image_); } if (size() != source_size) return; BLContext context(image_); if (!context) return; context.set_comp_op(BL_COMP_OP_SRC_OVER); context.blit_image(BLRect(0.0, 0.0, static_cast(source_size.width), static_cast(source_size.height)), typed.image_, BLRectI(0, 0, source_size.width, source_size.height)); context.end(); } void Blend2D_Color_Cache::ensure_size(Size requested) { if (requested.empty()) { image_.reset(); return; } if (size() == requested) return; image_.create(requested.width, requested.height, BL_FORMAT_PRGB32); clear_image(image_); } Size Blend2D_Color_Cache::size() const noexcept { return {image_.width(), image_.height()}; } Image_View Blend2D_Color_Cache::view() const noexcept { BLImageData data{}; if (image_.get_data(&data) != BL_SUCCESS) return {}; return { static_cast(data.pixel_data), data.size.w, data.size.h, static_cast(data.stride), Pixel_Format::Premultiplied_32 }; } Painter::Painter(Blend2D_Color_Cache& cache, Size size) { cache.ensure_size(size); if (!cache.image_.is_empty() && context_.begin(cache.image_) == BL_SUCCESS) { context_.set_comp_op(BL_COMP_OP_SRC_OVER); active_ = true; } } Painter::~Painter() { if (active_) context_.end(); } BLRgba Painter::rgba(Color color) noexcept { constexpr double scale = 1.0 / 255.0; return BLRgba(color.r * scale, color.g * scale, color.b * scale, color.a * scale); } void Painter::apply_pen(const Pen& pen) { context_.set_stroke_style(rgba(pen.color)); context_.set_stroke_width(std::max(0.1, pen.width)); context_.set_stroke_caps(stroke_cap(pen.cap)); context_.set_stroke_join(stroke_join(pen.join)); BLArray pattern; if (pen.style == Line_Style::Dash) { pattern.append(std::max(1.0, pen.width * 4.0)); pattern.append(std::max(1.0, pen.width * 2.0)); } else if (pen.style == Line_Style::Dot) { pattern.append(std::max(1.0, pen.width)); pattern.append(std::max(1.0, pen.width * 2.0)); } context_.set_stroke_dash_array(pattern); } void Painter::line(PointF first, PointF second, const Pen& pen) { if (!active_ || !pen.enabled()) return; apply_pen(pen); context_.stroke_line(first.x, first.y, second.x, second.y); } void Painter::polyline(std::span points, const Pen& pen) { if (!active_ || !pen.enabled() || points.size() < 2) return; BLPath path; path.move_to(points.front().x, points.front().y); for (std::size_t index = 1; index < points.size(); ++index) path.line_to(points[index].x, points[index].y); apply_pen(pen); context_.stroke_path(path); } void Painter::polygon(std::span points, const Pen& pen, const Brush& brush) { if (!active_ || points.size() < 3) return; BLPath path; path.move_to(points.front().x, points.front().y); for (std::size_t index = 1; index < points.size(); ++index) path.line_to(points[index].x, points[index].y); path.close(); if (brush.enabled()) { context_.set_fill_style(rgba(brush.color)); context_.fill_path(path); } if (pen.enabled()) { apply_pen(pen); context_.stroke_path(path); } } void Painter::rect(RectF value, const Pen& pen, const Brush& brush) { if (!active_) return; value = value.normalized(); if (value.empty()) return; const BLRect rect(value.x, value.y, value.width, value.height); if (brush.enabled()) { context_.set_fill_style(rgba(brush.color)); context_.fill_rect(rect); } if (pen.enabled()) { apply_pen(pen); context_.stroke_rect(rect); } } void Painter::circle(PointF center, double radius, const Pen& pen, const Brush& brush) { if (!active_ || radius <= 0.0) return; BLPath path; path.add_ellipse(BLEllipse(center.x, center.y, radius, radius)); if (brush.enabled()) { context_.set_fill_style(rgba(brush.color)); context_.fill_path(path); } if (pen.enabled()) { apply_pen(pen); context_.stroke_path(path); } } BLFont Painter::make_font(const Font& font) { BLFont result; auto& face = default_font_face(font); if (face) result.create_from_face(face, static_cast(std::max(1.0, font.size))); return result; } void Painter::text(PointF position, std::string_view value, const Font& font, const Pen& pen, double rotation_degrees) { if (!active_ || value.empty() || !pen.enabled()) return; BLFont bl_font = make_font(font); if (!bl_font) return; context_.set_fill_style(rgba(pen.color)); const auto& metrics = bl_font.metrics(); const double baseline = position.y + metrics.ascent; const bool rotated = std::isfinite(rotation_degrees) && rotation_degrees != 0.0; if (rotated) { context_.save(); context_.rotate(rotation_degrees * std::numbers::pi / 180.0, position.x, baseline); } context_.fill_utf8_text(BLPoint(position.x, baseline), bl_font, value.data(), value.size()); if (rotated) context_.restore(); } void Painter::heatmap(RectF target, int width, int height, std::span pixels, Image_Interpolation_Mode interpolation) { target = target.normalized(); if (!active_ || target.empty() || width <= 0 || height <= 0 || pixels.size() < static_cast(width) * static_cast(height)) return; std::vector resampled; if (interpolation == Image_Interpolation_Mode::Bicubic && target.width <= static_cast(std::numeric_limits::max()) && target.height <= static_cast(std::numeric_limits::max())) { const int destination_width = std::max(1, static_cast(std::lround(target.width))); const int destination_height = std::max(1, static_cast(std::lround(target.height))); resampled = bicubic_resample(pixels, width, height, destination_width, destination_height); pixels = resampled; width = destination_width; height = destination_height; } BLImage image(width, height, BL_FORMAT_PRGB32); BLImageData data{}; if (image.get_data(&data) != BL_SUCCESS) return; for (int y = 0; y < height; ++y) { auto* destination = reinterpret_cast( static_cast(data.pixel_data) + static_cast(y) * data.stride); std::copy_n(pixels.data() + static_cast(y) * width, width, destination); } context_.set_pattern_quality(interpolation == Image_Interpolation_Mode::Bilinear ? BL_PATTERN_QUALITY_BILINEAR : BL_PATTERN_QUALITY_NEAREST); context_.blit_image(BLRect(target.x, target.y, target.width, target.height), image, BLRectI(0, 0, width, height)); } } // namespace renderive::detail