397 lines
13 KiB
C++
397 lines
13 KiB
C++
#include "Frame_Raster_Context.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
namespace renderive {
|
|
|
|
namespace {
|
|
template <typename Path>
|
|
void fill_glyph_path(BLContext& context, const Path& path, double dx, double dy, const QColor& color) {
|
|
if (path.isEmpty())
|
|
return;
|
|
BLPath converted;
|
|
bool open = false;
|
|
for (int i = 0; i < path.elementCount(); ++i) {
|
|
const auto element = path.elementAt(i);
|
|
const int type = static_cast<int>(element.type);
|
|
if (type == 0) {
|
|
if (open)
|
|
converted.close();
|
|
converted.move_to(element.x + dx, element.y + dy);
|
|
open = true;
|
|
}
|
|
else if (type == 1) {
|
|
converted.line_to(element.x + dx, element.y + dy);
|
|
}
|
|
else if (type == 2 && i + 2 < path.elementCount()) {
|
|
const auto c1 = path.elementAt(i);
|
|
const auto c2 = path.elementAt(i + 1);
|
|
const auto end = path.elementAt(i + 2);
|
|
converted.cubic_to(c1.x + dx, c1.y + dy, c2.x + dx, c2.y + dy, end.x + dx, end.y + dy);
|
|
i += 2;
|
|
}
|
|
}
|
|
if (open)
|
|
converted.close();
|
|
context.set_fill_style(Raster_Canvas::color_of(color));
|
|
context.fill_path(converted);
|
|
}
|
|
} // namespace
|
|
|
|
BLRgba32 Raster_Canvas::color_of(const QColor& color) {
|
|
return BLRgba32(color.red(), color.green(), color.blue(), color.alpha());
|
|
}
|
|
|
|
BLStrokeCap Raster_Canvas::stroke_cap(Qt::PenCapStyle style) {
|
|
switch (style) {
|
|
case Qt::FlatCap: return BL_STROKE_CAP_BUTT;
|
|
case Qt::SquareCap: return BL_STROKE_CAP_SQUARE;
|
|
case Qt::RoundCap: return BL_STROKE_CAP_ROUND;
|
|
default: return BL_STROKE_CAP_BUTT;
|
|
}
|
|
}
|
|
|
|
BLStrokeJoin Raster_Canvas::stroke_join(Qt::PenJoinStyle style) {
|
|
switch (style) {
|
|
case Qt::MiterJoin: return BL_STROKE_JOIN_MITER_CLIP;
|
|
case Qt::BevelJoin: return BL_STROKE_JOIN_BEVEL;
|
|
case Qt::RoundJoin: return BL_STROKE_JOIN_ROUND;
|
|
case Qt::SvgMiterJoin: return BL_STROKE_JOIN_MITER_BEVEL;
|
|
default: return BL_STROKE_JOIN_MITER_CLIP;
|
|
}
|
|
}
|
|
|
|
bool Raster_Canvas::configure_stroke() {
|
|
if (!active() || pen_.style() == Qt::NoPen)
|
|
return false;
|
|
const double width = pen_.widthF() == 0.0 ? 1.0 : pen_.widthF();
|
|
context_->set_stroke_style(color_of(pen_.color()));
|
|
context_->set_stroke_width(width);
|
|
context_->set_stroke_caps(stroke_cap(pen_.capStyle()));
|
|
context_->set_stroke_join(stroke_join(pen_.joinStyle()));
|
|
context_->set_stroke_miter_limit(pen_.miterLimit());
|
|
context_->set_stroke_dash_offset(pen_.dashOffset() * width);
|
|
BLArray<double> dashes;
|
|
const QVector<qreal> pattern = pen_.dashPattern();
|
|
for (qreal dash : pattern)
|
|
dashes.append(static_cast<double>(dash) * width);
|
|
context_->set_stroke_dash_array(dashes);
|
|
return true;
|
|
}
|
|
|
|
bool Raster_Canvas::configure_fill() {
|
|
if (!active() || brush_.style() == Qt::NoBrush)
|
|
return false;
|
|
context_->set_fill_style(color_of(brush_.color()));
|
|
return true;
|
|
}
|
|
|
|
void Raster_Canvas::save() {
|
|
if (!active())
|
|
return;
|
|
context_->save();
|
|
states_.push_back({pen_, brush_, font_});
|
|
}
|
|
|
|
void Raster_Canvas::restore() {
|
|
if (!active())
|
|
return;
|
|
context_->restore();
|
|
if (states_.empty())
|
|
return;
|
|
pen_ = states_.back().pen;
|
|
brush_ = states_.back().brush;
|
|
font_ = states_.back().font;
|
|
states_.pop_back();
|
|
}
|
|
|
|
void Raster_Canvas::setPen(const QPen& pen) {
|
|
pen_ = pen;
|
|
}
|
|
|
|
void Raster_Canvas::setBrush(const QBrush& brush) {
|
|
brush_ = brush;
|
|
}
|
|
|
|
void Raster_Canvas::drawLine(const QPointF& p0, const QPointF& p1) {
|
|
if (configure_stroke())
|
|
context_->stroke_line(p0.x(), p0.y(), p1.x(), p1.y());
|
|
}
|
|
|
|
void Raster_Canvas::drawRect(const QRectF& rect) {
|
|
if (!active())
|
|
return;
|
|
if (configure_fill())
|
|
context_->fill_rect(rect.left(), rect.top(), rect.width(), rect.height());
|
|
if (configure_stroke())
|
|
context_->stroke_rect(rect.left(), rect.top(), rect.width(), rect.height());
|
|
}
|
|
|
|
void Raster_Canvas::fillRect(const QRectF& rect, const QBrush& brush) {
|
|
if (!active() || brush.style() == Qt::NoBrush)
|
|
return;
|
|
context_->set_fill_style(color_of(brush.color()));
|
|
context_->fill_rect(rect.left(), rect.top(), rect.width(), rect.height());
|
|
}
|
|
|
|
void Raster_Canvas::drawEllipse(const QPointF& center, double rx, double ry) {
|
|
if (!active())
|
|
return;
|
|
if (configure_fill())
|
|
context_->fill_ellipse(center.x(), center.y(), rx, ry);
|
|
if (configure_stroke())
|
|
context_->stroke_ellipse(center.x(), center.y(), rx, ry);
|
|
}
|
|
|
|
void Raster_Canvas::drawPolygon(const QPointF* points, int count) {
|
|
if (!active() || !points || count < 3)
|
|
return;
|
|
std::vector<BLPoint> polygon;
|
|
polygon.reserve(static_cast<std::size_t>(count));
|
|
for (int i = 0; i < count; ++i)
|
|
polygon.emplace_back(points[i].x(), points[i].y());
|
|
if (configure_fill())
|
|
context_->fill_polygon(polygon.data(), polygon.size());
|
|
if (configure_stroke())
|
|
context_->stroke_polygon(polygon.data(), polygon.size());
|
|
}
|
|
|
|
void Raster_Canvas::drawPoint(const QPointF& point) {
|
|
if (!active() || pen_.style() == Qt::NoPen)
|
|
return;
|
|
const double radius = std::max(0.5, (pen_.widthF() == 0.0 ? 1.0 : pen_.widthF()) * 0.5);
|
|
context_->set_fill_style(color_of(pen_.color()));
|
|
context_->fill_ellipse(point.x(), point.y(), radius, radius);
|
|
}
|
|
|
|
void Raster_Canvas::drawPoints(const QPointF* points, int count) {
|
|
if (!points)
|
|
return;
|
|
for (int i = 0; i < count; ++i)
|
|
drawPoint(points[i]);
|
|
}
|
|
|
|
bool Raster_Canvas::bind_image(const QImage& source, BLImage& image, QImage& converted) {
|
|
if (source.isNull())
|
|
return false;
|
|
converted = source.format() == QImage::Format_ARGB32_Premultiplied
|
|
? source
|
|
: source.convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
|
return image.create_from_data(converted.width(), converted.height(), BL_FORMAT_PRGB32, converted.bits(),
|
|
static_cast<intptr_t>(converted.bytesPerLine()), BL_DATA_ACCESS_RW) == BL_SUCCESS;
|
|
}
|
|
|
|
void Raster_Canvas::drawImage(const QPoint& top_left, const QImage& image) {
|
|
drawImage(QPointF(top_left), image);
|
|
}
|
|
|
|
void Raster_Canvas::drawImage(const QPointF& top_left, const QImage& image) {
|
|
if (!active() || image.isNull())
|
|
return;
|
|
BLImage source_image;
|
|
QImage converted;
|
|
if (!bind_image(image, source_image, converted))
|
|
return;
|
|
context_->blit_image(BLPoint(top_left.x(), top_left.y()), source_image);
|
|
}
|
|
|
|
void Raster_Canvas::drawImage(const QRectF& target, const QImage& image) {
|
|
drawImage(target, image, QRectF(0.0, 0.0, image.width(), image.height()));
|
|
}
|
|
|
|
void Raster_Canvas::drawImage(const QRectF& target, const QImage& image, const QRectF& source) {
|
|
if (!active() || image.isNull() || target.isEmpty() || source.isEmpty())
|
|
return;
|
|
BLImage source_image;
|
|
QImage converted;
|
|
if (!bind_image(image, source_image, converted))
|
|
return;
|
|
const BLRect source_rect(source.left(), source.top(), source.width(), source.height());
|
|
const BLRectI source_area(static_cast<int>(std::floor(source_rect.x)), static_cast<int>(std::floor(source_rect.y)),
|
|
static_cast<int>(std::ceil(source_rect.w)), static_cast<int>(std::ceil(source_rect.h)));
|
|
context_->blit_image(BLRect(target.left(), target.top(), target.width(), target.height()), source_image, source_area);
|
|
}
|
|
|
|
void Raster_Canvas::drawText(const QPointF& baseline, const QString& text) {
|
|
if (!active() || text.isEmpty() || pen_.style() == Qt::NoPen)
|
|
return;
|
|
const QRawFont raw_font = QRawFont::fromFont(font_);
|
|
if (!raw_font.isValid())
|
|
return;
|
|
const QVector<quint32> glyphs = raw_font.glyphIndexesForString(text);
|
|
const QVector<QPointF> advances = raw_font.advancesForGlyphIndexes(glyphs);
|
|
double x = baseline.x();
|
|
for (int i = 0; i < glyphs.size(); ++i) {
|
|
fill_glyph_path(*context_, raw_font.pathForGlyph(glyphs.at(i)), x, baseline.y(), pen_.color());
|
|
if (i < advances.size())
|
|
x += advances.at(i).x();
|
|
}
|
|
}
|
|
|
|
void Raster_Canvas::drawText(const QRectF& rect, Qt::Alignment alignment, const QString& text) {
|
|
if (text.isEmpty())
|
|
return;
|
|
const QFontMetricsF metrics(font_);
|
|
const double width = metrics.horizontalAdvance(text);
|
|
const double height = metrics.height();
|
|
double x = rect.left();
|
|
if (alignment & Qt::AlignHCenter)
|
|
x = rect.left() + (rect.width() - width) * 0.5;
|
|
else if (alignment & Qt::AlignRight)
|
|
x = rect.right() - width;
|
|
double baseline = rect.top() + metrics.ascent();
|
|
if (alignment & Qt::AlignVCenter)
|
|
baseline = rect.top() + (rect.height() - height) * 0.5 + metrics.ascent();
|
|
else if (alignment & Qt::AlignBottom)
|
|
baseline = rect.bottom() - metrics.descent();
|
|
drawText(QPointF(x, baseline), text);
|
|
}
|
|
|
|
void Raster_Canvas::translate(const QPoint& point) {
|
|
if (active())
|
|
context_->translate(point.x(), point.y());
|
|
}
|
|
|
|
void Raster_Canvas::translate(const QPointF& point) {
|
|
if (active())
|
|
context_->translate(point.x(), point.y());
|
|
}
|
|
|
|
void Raster_Canvas::rotate(double degrees) {
|
|
if (active())
|
|
context_->rotate(degrees * 3.14159265358979323846 / 180.0);
|
|
}
|
|
|
|
void Raster_Canvas::scale(double x, double y) {
|
|
if (active())
|
|
context_->scale(x, y);
|
|
}
|
|
|
|
void Raster_Canvas::resetTransform() {
|
|
if (active())
|
|
context_->reset_transform();
|
|
}
|
|
|
|
void Raster_Canvas::setClipRect(const QRectF& rect) {
|
|
if (active())
|
|
context_->clip_to_rect(rect.left(), rect.top(), rect.width(), rect.height());
|
|
}
|
|
|
|
void Raster_Canvas::setClipping(bool enabled) {
|
|
if (active() && !enabled)
|
|
context_->restore_clipping();
|
|
}
|
|
|
|
Frame_Raster_Context::Frame_Raster_Context(QImage& target, QPointF base_translation)
|
|
: target_(target), base_translation_(base_translation), canvas_(&bl_context_) {
|
|
if (target_.format() != QImage::Format_ARGB32_Premultiplied || target_.isNull())
|
|
return;
|
|
blend_image_ready_ = bl_image_.create_from_data(
|
|
target_.width(), target_.height(), BL_FORMAT_PRGB32, target_.bits(),
|
|
static_cast<intptr_t>(target_.bytesPerLine()), BL_DATA_ACCESS_RW) == BL_SUCCESS;
|
|
}
|
|
|
|
Frame_Raster_Context::~Frame_Raster_Context() {
|
|
finish();
|
|
}
|
|
|
|
BLContext* Frame_Raster_Context::blend() {
|
|
if (!blend_image_ready_)
|
|
return nullptr;
|
|
if (!active_) {
|
|
BLContextCreateInfo create_info{};
|
|
create_info.thread_count = 0;
|
|
if (bl_context_.begin(bl_image_, create_info) != BL_SUCCESS)
|
|
return nullptr;
|
|
bl_context_.translate(base_translation_.x(), base_translation_.y());
|
|
active_ = true;
|
|
}
|
|
return &bl_context_;
|
|
}
|
|
|
|
Raster_Canvas& Frame_Raster_Context::canvas() {
|
|
canvas_.bind(blend());
|
|
return canvas_;
|
|
}
|
|
|
|
void Frame_Raster_Context::end_blend() {
|
|
if (!active_)
|
|
return;
|
|
bl_context_.end();
|
|
active_ = false;
|
|
}
|
|
|
|
void Frame_Raster_Context::finish() {
|
|
end_blend();
|
|
}
|
|
|
|
BLStrokeCap Frame_Raster_Context::stroke_cap(Qt::PenCapStyle style) {
|
|
return Raster_Canvas::stroke_cap(style);
|
|
}
|
|
|
|
BLStrokeJoin Frame_Raster_Context::stroke_join(Qt::PenJoinStyle style) {
|
|
return Raster_Canvas::stroke_join(style);
|
|
}
|
|
|
|
bool Frame_Raster_Context::configure_stroke(const QPen& pen) {
|
|
if (pen.style() == Qt::NoPen)
|
|
return false;
|
|
BLContext* context = blend();
|
|
if (!context)
|
|
return false;
|
|
const double width = pen.widthF() == 0.0 ? 1.0 : pen.widthF();
|
|
context->set_stroke_style(Raster_Canvas::color_of(pen.color()));
|
|
context->set_stroke_width(width);
|
|
context->set_stroke_caps(stroke_cap(pen.capStyle()));
|
|
context->set_stroke_join(stroke_join(pen.joinStyle()));
|
|
context->set_stroke_miter_limit(pen.miterLimit());
|
|
context->set_stroke_dash_offset(pen.dashOffset() * width);
|
|
BLArray<double> dashes;
|
|
const QVector<qreal> pattern = pen.dashPattern();
|
|
for (qreal dash : pattern)
|
|
dashes.append(static_cast<double>(dash) * width);
|
|
context->set_stroke_dash_array(dashes);
|
|
return true;
|
|
}
|
|
|
|
bool Frame_Raster_Context::stroke_polyline(std::span<const QPointF> points, const QPen& pen) {
|
|
if (points.size() < 2 || pen.style() == Qt::NoPen)
|
|
return true;
|
|
if (!configure_stroke(pen))
|
|
return false;
|
|
BLPath path;
|
|
bool active = false;
|
|
for (const QPointF& point : points) {
|
|
if (!std::isfinite(point.x()) || !std::isfinite(point.y())) {
|
|
active = false;
|
|
continue;
|
|
}
|
|
if (active)
|
|
path.line_to(point.x(), point.y());
|
|
else {
|
|
path.move_to(point.x(), point.y());
|
|
active = true;
|
|
}
|
|
}
|
|
if (!path.is_empty())
|
|
bl_context_.stroke_path(path);
|
|
return true;
|
|
}
|
|
|
|
bool Frame_Raster_Context::fill_polygon(std::span<const QPointF> points, const QBrush& brush) {
|
|
if (points.size() < 3 || brush.style() == Qt::NoBrush)
|
|
return true;
|
|
BLContext* context = blend();
|
|
if (!context)
|
|
return false;
|
|
std::vector<BLPoint> polygon;
|
|
polygon.reserve(points.size());
|
|
for (const QPointF& point : points)
|
|
polygon.emplace_back(point.x(), point.y());
|
|
context->set_fill_style(Raster_Canvas::color_of(brush.color()));
|
|
context->fill_polygon(polygon.data(), polygon.size());
|
|
return true;
|
|
}
|
|
} // namespace renderive
|