This commit is contained in:
2026-05-14 22:12:40 +08:00
parent 334845d732
commit 3ba8ba80b3
6 changed files with 252 additions and 69 deletions
+117 -63
View File
@@ -1493,6 +1493,117 @@ void EditorCanvas::invalidateViewportLod() {
++m_vpToken;
}
void EditorCanvas::syncFullBackgroundImageFromDiskIfDirty() {
if (m_bgAbsPath.isEmpty() || m_useViewportLod) {
return;
}
if (!m_bgImageDirty) {
return;
}
m_bgImageDirty = false;
m_bgImage = readImageTolerant(m_bgAbsPath);
if (!m_bgImage.isNull() && m_bgImage.format() != QImage::Format_ARGB32_Premultiplied) {
m_bgImage = m_bgImage.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
m_bgCutoutDirty = true;
}
void EditorCanvas::rebuildFullBackgroundCutoutIfDirty() {
if (m_useViewportLod || m_bgAbsPath.isEmpty()) {
return;
}
if (!m_bgCutoutDirty) {
return;
}
if (m_bgImage.isNull()) {
syncFullBackgroundImageFromDiskIfDirty();
}
if (m_bgImage.isNull()) {
return;
}
m_bgCutoutDirty = false;
m_bgImageCutout = m_bgImage;
for (const auto& ent : m_entities) {
if (ent.blackholeVisible && !ent.cutoutPolygonWorld.isEmpty()) {
entity_cutout::applyBlackFillToBackground(m_bgImageCutout, ent.cutoutPolygonWorld);
}
}
}
QVector<QVector<QPointF>> EditorCanvas::visibleBlackholeCutoutPolysWorld() const {
QVector<QVector<QPointF>> out;
for (const auto& ent : m_entities) {
if (ent.blackholeVisible && ent.cutoutPolygonWorld.size() >= 3) {
out.push_back(ent.cutoutPolygonWorld);
}
}
return out;
}
void EditorCanvas::applyBlackHolesWorldToCrop(QImage& cropPremul, const QRect& cropWorld) const {
entity_cutout::applyBlackHolesToImageRegion(cropPremul, cropWorld, visibleBlackholeCutoutPolysWorld());
}
bool EditorCanvas::loadRgbCropForEntitySegmentStroke(const QRectF& polyBrWorld, QRect& outCropWorld,
QImage& outCropRgb) {
outCropRgb = QImage();
if (m_bgAbsPath.isEmpty()) {
return false;
}
ensurePixmapLoaded();
QRect cropWorld = entity_cutout::clampRectToImage(
polyBrWorld.adjusted(-kSamCropMargin, -kSamCropMargin, kSamCropMargin, kSamCropMargin).toAlignedRect(),
QSize(backgroundLogicalWidth(), backgroundLogicalHeight()));
if (cropWorld.isEmpty()) {
return false;
}
outCropWorld = cropWorld;
if (!m_useViewportLod) {
syncFullBackgroundImageFromDiskIfDirty();
rebuildFullBackgroundCutoutIfDirty();
if (!m_bgImageCutout.isNull()) {
outCropRgb = m_bgImageCutout.copy(cropWorld);
}
}
if (outCropRgb.isNull()) {
(void)core::image_file::loadRegionToQImage(m_bgAbsPath, cropWorld, 4096, 4096, &outCropRgb);
if (!outCropRgb.isNull() && outCropRgb.format() != QImage::Format_ARGB32_Premultiplied) {
outCropRgb = outCropRgb.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
}
if (outCropRgb.isNull() && !m_bgImage.isNull()) {
const QRect cw = entity_cutout::clampRectToImage(
polyBrWorld.adjusted(-kSamCropMargin, -kSamCropMargin, kSamCropMargin, kSamCropMargin).toAlignedRect(),
m_bgImage.size());
if (!cw.isEmpty()) {
outCropRgb = m_bgImage.copy(cw);
outCropWorld = cw;
}
}
if (outCropRgb.isNull()) {
syncFullBackgroundImageFromDiskIfDirty();
rebuildFullBackgroundCutoutIfDirty();
if (!m_bgImage.isNull()) {
const QRect cw2 = entity_cutout::clampRectToImage(
polyBrWorld.adjusted(-kSamCropMargin, -kSamCropMargin, kSamCropMargin, kSamCropMargin).toAlignedRect(),
m_bgImage.size());
if (!cw2.isEmpty()) {
outCropRgb = m_bgImage.copy(cw2);
outCropWorld = cw2;
}
}
}
if (outCropRgb.isNull()) {
return false;
}
if (outCropRgb.format() != QImage::Format_ARGB32_Premultiplied) {
outCropRgb = outCropRgb.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
applyBlackHolesWorldToCrop(outCropRgb, outCropWorld);
return true;
}
void EditorCanvas::ensurePixmapLoaded() const {
if (!m_pixmapDirty) {
return;
@@ -1827,23 +1938,8 @@ void EditorCanvas::paintEvent(QPaintEvent* e) {
const bool showBg = m_presentationPreviewMode || m_backgroundVisible;
if (showBg && !m_useViewportLod) {
if (m_bgImageDirty) {
m_bgImageDirty = false;
m_bgImage = readImageTolerant(m_bgAbsPath);
if (m_bgImage.format() != QImage::Format_ARGB32_Premultiplied && !m_bgImage.isNull()) {
m_bgImage = m_bgImage.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
m_bgCutoutDirty = true;
}
if (m_bgCutoutDirty) {
m_bgCutoutDirty = false;
m_bgImageCutout = m_bgImage;
for (const auto& ent : m_entities) {
if (ent.blackholeVisible && !ent.cutoutPolygonWorld.isEmpty() && !m_bgImageCutout.isNull()) {
entity_cutout::applyBlackFillToBackground(m_bgImageCutout, ent.cutoutPolygonWorld);
}
}
}
syncFullBackgroundImageFromDiskIfDirty();
rebuildFullBackgroundCutoutIfDirty();
}
// 以“世界坐标”绘制:支持缩放/平移
@@ -3136,69 +3232,27 @@ void EditorCanvas::mouseReleaseEvent(QMouseEvent* e) {
}
} else if (m_entityCreateSegmentMode == EntityCreateSegmentMode::Snap) {
if (m_strokeWorld.size() >= kMinStrokePointsManual) {
ensurePixmapLoaded();
const QRectF polyBr = QPolygonF(m_strokeWorld).boundingRect();
const QRect cropWorld = entity_cutout::clampRectToImage(
polyBr.adjusted(-kSamCropMargin, -kSamCropMargin, kSamCropMargin, kSamCropMargin)
.toAlignedRect(),
QSize(backgroundLogicalWidth(), backgroundLogicalHeight()));
QRect cropWorld;
QImage crop;
if (!cropWorld.isEmpty() &&
core::image_file::loadRegionToQImage(m_bgAbsPath, cropWorld, 4096, 4096, &crop) &&
!crop.isNull()) {
if (loadRgbCropForEntitySegmentStroke(polyBr, cropWorld, crop)) {
const QImage gray = crop.convertToFormat(QImage::Format_Grayscale8);
const QVector<QPointF> snapped =
snapStrokeToEdgesWorld(m_strokeWorld, gray, cropWorld.topLeft(), cropWorld.size(), 6.0);
setPendingEntityPolygonWorld(snapped);
} else if (!m_bgImage.isNull()) {
const QRect cw = entity_cutout::clampRectToImage(
polyBr.adjusted(-kSamCropMargin, -kSamCropMargin, kSamCropMargin, kSamCropMargin)
.toAlignedRect(),
m_bgImage.size());
const QImage gray = m_bgImage.copy(cw).convertToFormat(QImage::Format_Grayscale8);
const QVector<QPointF> snapped =
snapStrokeToEdgesWorld(m_strokeWorld, gray, cw.topLeft(), cw.size(), 6.0);
setPendingEntityPolygonWorld(snapped);
}
}
} else if (m_strokeWorld.size() >= kMinStrokePointsSam) {
ensurePixmapLoaded();
const QRectF polyBr = QPolygonF(m_strokeWorld).boundingRect();
QRect cropWorld = entity_cutout::clampRectToImage(
polyBr.adjusted(-kSamCropMargin, -kSamCropMargin, kSamCropMargin, kSamCropMargin)
.toAlignedRect(),
QSize(backgroundLogicalWidth(), backgroundLogicalHeight()));
QRect cropWorld;
QImage crop;
if (!cropWorld.isEmpty()) {
(void)core::image_file::loadRegionToQImage(m_bgAbsPath, cropWorld, 4096, 4096, &crop);
}
if (crop.isNull() && !m_bgImage.isNull()) {
cropWorld = entity_cutout::clampRectToImage(
polyBr.adjusted(-kSamCropMargin, -kSamCropMargin, kSamCropMargin, kSamCropMargin)
.toAlignedRect(),
m_bgImage.size());
crop = m_bgImage.copy(cropWorld);
}
if (crop.isNull()) {
m_bgImageDirty = false;
m_bgImage = readImageTolerant(m_bgAbsPath);
if (!m_bgImage.isNull() &&
m_bgImage.format() != QImage::Format_ARGB32_Premultiplied) {
m_bgImage = m_bgImage.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
cropWorld = entity_cutout::clampRectToImage(
polyBr.adjusted(-kSamCropMargin, -kSamCropMargin, kSamCropMargin, kSamCropMargin)
.toAlignedRect(),
m_bgImage.size());
crop = m_bgImage.copy(cropWorld);
}
QByteArray cropPng;
QByteArray ovPng;
QPointF cropOrigin;
QJsonArray pts;
QJsonArray labs;
QJsonArray box;
if (!crop.isNull() &&
if (loadRgbCropForEntitySegmentStroke(polyBr, cropWorld, crop) &&
buildSamSegmentPayloadFromStrokeMapped(m_strokeWorld, crop, cropWorld, cropPng, ovPng, cropOrigin,
pts, labs, box)) {
emit requestSamSegment(cropPng, ovPng, cropOrigin, pts, labs, box);