#include "editor/EntityCutoutUtils.h" #include #include #include #include #include namespace entity_cutout { QPainterPath pathFromWorldPolygon(const QVector& poly) { QPainterPath path; if (poly.size() < 3) { return path; } path.addPolygon(QPolygonF(poly)); path.closeSubpath(); return path; } QPointF polygonCentroid(const QVector& 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(p0.x()) * static_cast(p1.y()) - static_cast(p1.x()) * static_cast(p0.y()); a2 += cross; cx6a += (static_cast(p0.x()) + static_cast(p1.x())) * cross; cy6a += (static_cast(p0.y()) + static_cast(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& 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& 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>& holePolysWorld) { if (regionPremul.isNull() || !cropWorldRect.isValid()) { return; } QPainter painter(®ionPremul); painter.setRenderHint(QPainter::Antialiasing, true); painter.setPen(Qt::NoPen); painter.setBrush(QColor(0, 0, 0, 255)); const double ox = static_cast(cropWorldRect.left()); const double oy = static_cast(cropWorldRect.top()); for (const QVector& 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