update
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -209,7 +209,13 @@ private:
|
||||
void beginPresentationZoomRestore();
|
||||
void presentationComputeZoomTarget(int entityIndex, QPointF* outPan, qreal* outScale) const;
|
||||
|
||||
private:
|
||||
void syncFullBackgroundImageFromDiskIfDirty();
|
||||
void rebuildFullBackgroundCutoutIfDirty();
|
||||
QVector<QVector<QPointF>> visibleBlackholeCutoutPolysWorld() const;
|
||||
void applyBlackHolesWorldToCrop(QImage& cropPremul, const QRect& cropWorld) const;
|
||||
/// 与画布黑洞一致:优先整图 cutout 缓存,否则区域解码后再填黑。
|
||||
bool loadRgbCropForEntitySegmentStroke(const QRectF& polyBrWorld, QRect& outCropWorld, QImage& outCropRgb);
|
||||
|
||||
struct Entity {
|
||||
QString id;
|
||||
QRectF rect; // world 坐标(用于拖拽与约束)
|
||||
|
||||
@@ -123,4 +123,29 @@ void applyBlackFillToBackground(QImage& bgCutout, const QVector<QPointF>& polyWo
|
||||
p.end();
|
||||
}
|
||||
|
||||
void applyBlackHolesToImageRegion(QImage& regionPremul, const QRect& cropWorldRect,
|
||||
const QVector<QVector<QPointF>>& 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<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
|
||||
|
||||
@@ -14,5 +14,8 @@ QPointF polygonCentroid(const QVector<QPointF>& poly);
|
||||
QRect clampRectToImage(const QRect& r, const QSize& size);
|
||||
QImage extractEntityImage(const QImage& bg, const QVector<QPointF>& polyWorld, QPointF& outTopLeftWorld);
|
||||
void applyBlackFillToBackground(QImage& bgCutout, const QVector<QPointF>& polyWorld);
|
||||
/// 子图左上角与 cropWorldRect.topLeft() 对齐(世界坐标);将 holePolysWorld 内像素置黑,供分割/吸附与画布一致。
|
||||
void applyBlackHolesToImageRegion(QImage& regionPremul, const QRect& cropWorldRect,
|
||||
const QVector<QVector<QPointF>>& holePolysWorld);
|
||||
|
||||
} // namespace entity_cutout
|
||||
|
||||
Reference in New Issue
Block a user