127 lines
4.0 KiB
C++
127 lines
4.0 KiB
C++
#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();
|
||
}
|
||
|
||
} // namespace entity_cutout
|