Files
hfut-bishe/client/gui/editor/EntityCutoutUtils.cpp
T
2026-05-14 22:12:40 +08:00

152 lines
4.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "editor/EntityCutoutUtils.h"
#include <algorithm>
#include <cmath>
#include <QPainter>
#include <QPolygonF>
#include <QTransform>
namespace entity_cutout {
QPainterPath pathFromWorldPolygon(const QVector<QPointF>& poly) {
QPainterPath path;
if (poly.size() < 3) {
return path;
}
path.addPolygon(QPolygonF(poly));
path.closeSubpath();
return path;
}
QPointF polygonCentroid(const QVector<QPointF>& poly) {
if (poly.size() < 3) {
return {};
}
double a2 = 0.0;
double cx6a = 0.0;
double cy6a = 0.0;
for (int i = 0; i < poly.size(); ++i) {
const QPointF p0 = poly[i];
const QPointF p1 = poly[(i + 1) % poly.size()];
const double cross = static_cast<double>(p0.x()) * static_cast<double>(p1.y()) -
static_cast<double>(p1.x()) * static_cast<double>(p0.y());
a2 += cross;
cx6a += (static_cast<double>(p0.x()) + static_cast<double>(p1.x())) * cross;
cy6a += (static_cast<double>(p0.y()) + static_cast<double>(p1.y())) * cross;
}
if (std::abs(a2) < 1e-6) {
const QRectF bb = pathFromWorldPolygon(poly).boundingRect();
return bb.center();
}
const double inv6a = 1.0 / (3.0 * a2);
return QPointF(cx6a * inv6a, cy6a * inv6a);
}
QRect clampRectToImage(const QRect& r, const QSize& size) {
QRect out = r.normalized();
if (out.isNull()) {
return {};
}
out.setLeft(std::max(0, out.left()));
out.setTop(std::max(0, out.top()));
out.setRight(std::min(size.width() - 1, out.right()));
out.setBottom(std::min(size.height() - 1, out.bottom()));
if (out.width() <= 0 || out.height() <= 0) {
return {};
}
return out;
}
QImage extractEntityImage(const QImage& bg, const QVector<QPointF>& polyWorld, QPointF& outTopLeftWorld) {
if (bg.isNull() || polyWorld.size() < 3) {
outTopLeftWorld = {};
return {};
}
const QPainterPath path = pathFromWorldPolygon(polyWorld);
if (path.isEmpty()) {
outTopLeftWorld = {};
return {};
}
// bbox 必须只由顶点 min/max 推导,不能依赖 QPainterPath::boundingRect()
// 在坐标含小数时,boundingRect 的浮点误差会造成稳定的“右下”偏移。
double minX = polyWorld.front().x();
double minY = polyWorld.front().y();
double maxX = polyWorld.front().x();
double maxY = polyWorld.front().y();
for (const auto& p : polyWorld) {
minX = std::min(minX, double(p.x()));
minY = std::min(minY, double(p.y()));
maxX = std::max(maxX, double(p.x()));
maxY = std::max(maxY, double(p.y()));
}
const int left = std::clamp(int(std::floor(minX)), 0, bg.width());
const int top = std::clamp(int(std::floor(minY)), 0, bg.height());
const int right = std::clamp(int(std::ceil(maxX)), 0, bg.width() - 1);
const int bottom = std::clamp(int(std::ceil(maxY)), 0, bg.height() - 1);
const QRect bbox = clampRectToImage(QRect(QPoint(left, top), QPoint(right, bottom)), bg.size());
if (bbox.isNull()) {
outTopLeftWorld = {};
return {};
}
outTopLeftWorld = bbox.topLeft();
QImage out(bbox.size(), QImage::Format_ARGB32_Premultiplied);
out.fill(Qt::transparent);
QPainter p(&out);
p.setRenderHint(QPainter::Antialiasing, true);
QTransform tr;
tr.translate(-bbox.left(), -bbox.top());
const QPainterPath localPath = tr.map(path);
p.setClipPath(localPath);
p.drawImage(QPoint(0, 0), bg, bbox);
p.end();
return out;
}
void applyBlackFillToBackground(QImage& bgCutout, const QVector<QPointF>& polyWorld) {
if (bgCutout.isNull() || polyWorld.size() < 3) {
return;
}
QPainterPath path = pathFromWorldPolygon(polyWorld);
if (path.isEmpty()) {
return;
}
QPainter p(&bgCutout);
p.setRenderHint(QPainter::Antialiasing, true);
p.setPen(Qt::NoPen);
p.setBrush(QColor(0, 0, 0, 255));
p.drawPath(path);
p.end();
}
void applyBlackHolesToImageRegion(QImage& regionPremul, const QRect& cropWorldRect,
const QVector<QVector<QPointF>>& holePolysWorld) {
if (regionPremul.isNull() || !cropWorldRect.isValid()) {
return;
}
QPainter painter(&regionPremul);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(0, 0, 0, 255));
const double ox = static_cast<double>(cropWorldRect.left());
const double oy = static_cast<double>(cropWorldRect.top());
for (const QVector<QPointF>& polyWorld : holePolysWorld) {
if (polyWorld.size() < 3) {
continue;
}
QPolygonF local;
local.reserve(polyWorld.size());
for (const QPointF& w : polyWorld) {
local.append(QPointF(w.x() - ox, w.y() - oy));
}
painter.drawPolygon(local);
}
painter.end();
}
} // namespace entity_cutout