Files
Renderive/Core2/render/Blend2D_Cache.cpp
T
2026-08-10 21:05:36 +08:00

383 lines
13 KiB
C++

#include "Blend2D_Cache.h"
#include <algorithm>
#include <array>
#include <cstring>
#include <filesystem>
#include <initializer_list>
#include <limits>
#include <mutex>
#include <numbers>
#include <vector>
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<std::byte*>(data.pixel_data) +
static_cast<std::ptrdiff_t>(y) * data.stride;
std::memset(row, 0, static_cast<std::size_t>(data.size.w) * sizeof(Pixel));
}
}
void load_font_face(BLFontFace& face, std::initializer_list<const char*> 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<BLFontFace, 4> 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<std::uint8_t>((pixel >> shift) & 0xFFu);
}
std::vector<Pixel> bicubic_resample(std::span<const Pixel> source,
int source_width,
int source_height,
int destination_width,
int destination_height) {
std::vector<Pixel> destination(
static_cast<std::size_t>(destination_width) * destination_height);
for (int y = 0; y < destination_height; ++y) {
const double source_y = (static_cast<double>(y) + 0.5) * source_height /
destination_height -
0.5;
const int base_y = static_cast<int>(std::floor(source_y));
for (int x = 0; x < destination_width; ++x) {
const double source_x = (static_cast<double>(x) + 0.5) * source_width /
destination_width -
0.5;
const int base_x = static_cast<int>(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<std::size_t>(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::uint32_t>(std::clamp(std::lround(value), 0L, 255L));
};
destination[static_cast<std::size_t>(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<const Blend2D_Color_Cache&>(source);
if (typed.image_.is_empty())
return;
const Size source_size = typed.size();
if (image_.is_empty() || size() != source_size) {
ensure_size(source_size);
clear_image(image_);
}
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<double>(source_size.width),
static_cast<double>(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<const std::byte*>(data.pixel_data),
data.size.w,
data.size.h,
static_cast<int>(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<double> 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<const PointF> 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<const PointF> 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<float>(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<const Pixel> pixels,
Image_Interpolation_Mode interpolation) {
target = target.normalized();
if (!active_ || target.empty() || width <= 0 || height <= 0 ||
pixels.size() < static_cast<std::size_t>(width) * static_cast<std::size_t>(height))
return;
std::vector<Pixel> resampled;
if (interpolation == Image_Interpolation_Mode::Bicubic &&
target.width <= static_cast<double>(std::numeric_limits<int>::max()) &&
target.height <= static_cast<double>(std::numeric_limits<int>::max())) {
const int destination_width = std::max(1, static_cast<int>(std::lround(target.width)));
const int destination_height = std::max(1, static_cast<int>(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<Pixel*>(
static_cast<std::byte*>(data.pixel_data) + static_cast<std::ptrdiff_t>(y) * data.stride);
std::copy_n(pixels.data() + static_cast<std::size_t>(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