This commit is contained in:
2026-05-15 00:46:37 +08:00
parent b4c7697e8a
commit f64e624a39
8 changed files with 190 additions and 100 deletions
+38 -39
View File
@@ -2241,8 +2241,8 @@ bool ProjectWorkspace::resolveBlackholeByUseOriginalBackground(const QString& id
return true;
}
bool ProjectWorkspace::resolveBlackholeByCopyBackground(const QString& id, const QPoint& sourceOffsetPx,
bool hideBlackholeAfterFill) {
bool ProjectWorkspace::resolveBlackholeByCopyBackground(const QString& id, const QRect& targetWorldInBackground,
const QRect& srcWorldInBackground, bool hideBlackholeAfterFill) {
if (m_projectDir.isEmpty() || id.isEmpty()) {
return false;
}
@@ -2267,68 +2267,67 @@ bool ProjectWorkspace::resolveBlackholeByCopyBackground(const QString& id, const
return false;
}
QImage bg = image_file::loadImageLimited(bgAbs, image_decode::kWorkspaceMaxPixels);
if (bg.isNull()) {
const QRect targetWorld = targetWorldInBackground.normalized();
const QRect srcWorld = srcWorldInBackground.normalized();
if (!targetWorld.isValid() || !srcWorld.isValid() || targetWorld.width() <= 0 || targetWorld.height() <= 0) {
return false;
}
if (bg.format() != QImage::Format_ARGB32_Premultiplied) {
bg = bg.convertToFormat(QImage::Format_ARGB32_Premultiplied);
if (targetWorld.width() != srcWorld.width() || targetWorld.height() != srcWorld.height()) {
return false;
}
const QImage srcSnapshot = bg;
QSize logicalSz;
if (!image_file::probeImagePixelSize(bgAbs, &logicalSz) || !logicalSz.isValid() || logicalSz.width() < 1 ||
logicalSz.height() < 1) {
return false;
}
QPainterPath holePathWorld;
holePathWorld.addPolygon(QPolygonF(ent.cutoutPolygonWorld));
holePathWorld.closeSubpath();
const QRect targetWorld =
holePathWorld.boundingRect().toAlignedRect().intersected(QRect(0, 0, logicalSz.width(), logicalSz.height()));
if (!targetWorld.isValid() || targetWorld.width() <= 0 || targetWorld.height() <= 0) {
const QRect bounds(0, 0, logicalSz.width(), logicalSz.height());
if (targetWorld.intersected(bounds) != targetWorld || srcWorld.intersected(bounds) != srcWorld) {
return false;
}
QRect srcWorld(targetWorld.topLeft() + sourceOffsetPx, targetWorld.size());
if (srcWorld.left() < 0) srcWorld.moveLeft(0);
if (srcWorld.top() < 0) srcWorld.moveTop(0);
if (srcWorld.right() >= logicalSz.width()) srcWorld.moveRight(logicalSz.width() - 1);
if (srcWorld.bottom() >= logicalSz.height()) srcWorld.moveBottom(logicalSz.height() - 1);
srcWorld = srcWorld.intersected(QRect(0, 0, logicalSz.width(), logicalSz.height()));
if (srcWorld.width() != targetWorld.width() || srcWorld.height() != targetWorld.height()) {
QImage targetPatch;
QImage srcPatch;
if (!image_file::loadRegionToQImageExact(bgAbs, targetWorld, &targetPatch) ||
!image_file::loadRegionToQImageExact(bgAbs, srcWorld, &srcPatch) || targetPatch.isNull() ||
srcPatch.isNull()) {
return false;
}
const qreal sx = bg.width() / qreal(logicalSz.width());
const qreal sy = bg.height() / qreal(logicalSz.height());
QTransform worldToBg;
worldToBg.scale(sx, sy);
const QRect targetImg =
worldToBg.mapRect(QRectF(targetWorld)).toAlignedRect().intersected(QRect(0, 0, bg.width(), bg.height()));
const QRect srcImg =
worldToBg.mapRect(QRectF(srcWorld)).toAlignedRect().intersected(QRect(0, 0, bg.width(), bg.height()));
if (!targetImg.isValid() || !srcImg.isValid() || targetImg.width() != srcImg.width() ||
targetImg.height() != srcImg.height()) {
if (targetPatch.size() != srcPatch.size() || targetPatch.width() != targetWorld.width() ||
targetPatch.height() != targetWorld.height()) {
return false;
}
if (targetPatch.format() != QImage::Format_ARGB32_Premultiplied) {
targetPatch = targetPatch.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
if (srcPatch.format() != QImage::Format_ARGB32_Premultiplied) {
srcPatch = srcPatch.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
const QPainterPath holePathImg = worldToBg.map(holePathWorld);
QPolygonF polyLocal;
polyLocal.reserve(ent.cutoutPolygonWorld.size());
for (const QPointF& pt : ent.cutoutPolygonWorld) {
polyLocal.append(pt - targetWorld.topLeft());
}
QPainterPath holePathLocal;
if (polyLocal.size() >= 3) {
holePathLocal.addPolygon(polyLocal);
holePathLocal.closeSubpath();
}
{
QPainter p(&bg);
QPainter p(&targetPatch);
p.setRenderHint(QPainter::Antialiasing, true);
p.setClipPath(holePathImg);
p.drawImage(targetImg.topLeft(), srcSnapshot, srcImg);
if (!holePathLocal.isEmpty()) {
p.setClipPath(holePathLocal);
}
p.drawImage(0, 0, srcPatch);
p.end();
}
const QImage patch = bg.copy(targetImg);
const auto before = m_project.entities();
QString overlayRel;
if (!writeBlackholeOverlayPng(m_projectDir, id, ents[hit].blackholeOverlayPath, patch, targetWorld,
if (!writeBlackholeOverlayPng(m_projectDir, id, ents[hit].blackholeOverlayPath, targetPatch, targetWorld,
&overlayRel)) {
return false;
}
+3 -3
View File
@@ -117,9 +117,9 @@ public:
bool setEntityVisible(const QString& id, bool on);
bool setEntityBlackholeVisible(const QString& id, bool on);
bool resolveBlackholeByUseOriginalBackground(const QString& id);
// 复制背景其他区域填充黑洞(sourceOffsetPx 以黑洞包围盒左上角为基准偏移
bool resolveBlackholeByCopyBackground(const QString& id, const QPoint& sourceOffsetPx,
bool hideBlackholeAfterFill);
// 复制背景其他区域填充黑洞(矩形均为背景逻辑像素坐标,须与画布一致且 src 与 target 同尺寸
bool resolveBlackholeByCopyBackground(const QString& id, const QRect& targetWorldInBackground,
const QRect& srcWorldInBackground, bool hideBlackholeAfterFill);
// 将模型补全的裁剪块保存为覆盖层并叠加显示,不修改 background 原图
bool resolveBlackholeByModelInpaint(const QString& id, const QImage& overlayPatch,
const QRect& overlayRectInBackground, bool hideBlackholeAfterFill);