diff --git a/client/core/workspace/ProjectWorkspace.cpp b/client/core/workspace/ProjectWorkspace.cpp index 0dc986a..c3d0a8e 100644 --- a/client/core/workspace/ProjectWorkspace.cpp +++ b/client/core/workspace/ProjectWorkspace.cpp @@ -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; } diff --git a/client/core/workspace/ProjectWorkspace.h b/client/core/workspace/ProjectWorkspace.h index 701b845..6e1bb1f 100644 --- a/client/core/workspace/ProjectWorkspace.h +++ b/client/core/workspace/ProjectWorkspace.h @@ -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); diff --git a/client/gui/editor/EditorCanvas.cpp b/client/gui/editor/EditorCanvas.cpp index 2fdacb2..4c95988 100644 --- a/client/gui/editor/EditorCanvas.cpp +++ b/client/gui/editor/EditorCanvas.cpp @@ -1214,21 +1214,6 @@ bool EditorCanvas::startBlackholeCopyResolve(const QString& entityId) { srcRect.translate(shift, 0.0); srcRect = clampRectTopLeftToBounds(srcRect, bg); - m_bhCopyPatchValid = false; - m_bhCopyPatch = QImage(); - if (m_useViewportLod && !m_bgAbsPath.isEmpty()) { - QRect uni = - holeRect.united(srcRect).toAlignedRect().intersected(bg.toAlignedRect()); - if (uni.isValid() && uni.width() > 0 && uni.height() > 0 && - core::image_file::loadRegionToQImage(m_bgAbsPath, uni, 8192, 8192, &m_bhCopyPatch) && - !m_bhCopyPatch.isNull()) { - if (m_bhCopyPatch.format() != QImage::Format_ARGB32_Premultiplied) { - m_bhCopyPatch = m_bhCopyPatch.convertToFormat(QImage::Format_ARGB32_Premultiplied); - } - m_bhCopyPatchOrigin = uni.topLeft(); - m_bhCopyPatchValid = true; - } - } if (!m_bhCopyPatchValid && m_bgImageDirty) { m_bgImageDirty = false; m_bgImage = readImageTolerant(m_bgAbsPath); @@ -1244,17 +1229,59 @@ bool EditorCanvas::startBlackholeCopyResolve(const QString& entityId) { m_blackholeCopySourceRect = srcRect; m_blackholeCopyDragging = false; m_blackholeCopyDragOffset = QPointF(); + reloadBlackholeCopyVipsPatchUnionIfNeeded(); updateCursor(); update(); return true; } +void EditorCanvas::reloadBlackholeCopyVipsPatchUnionIfNeeded() { + if (!m_blackholeCopyResolveActive || !m_useViewportLod || m_bgAbsPath.isEmpty() || !m_bgLogicalSize.isValid() || + m_bgLogicalSize.width() < 1 || m_bgLogicalSize.height() < 1) { + m_bhCopyPatchValid = false; + m_bhCopyPatch = QImage(); + m_bhCopyPatchLogicalSize = QSize(); + return; + } + const QRect bgBounds(0, 0, m_bgLogicalSize.width(), m_bgLogicalSize.height()); + const QRect uni = m_blackholeCopyHoleRect.united(m_blackholeCopySourceRect) + .toAlignedRect() + .intersected(bgBounds); + if (!uni.isValid() || uni.width() < 1 || uni.height() < 1) { + m_bhCopyPatchValid = false; + m_bhCopyPatch = QImage(); + m_bhCopyPatchLogicalSize = QSize(); + return; + } + if (m_bhCopyPatchValid && m_bhCopyPatchLogicalSize.isValid() && !m_bhCopyPatch.isNull()) { + const QRect cached(m_bhCopyPatchOrigin, m_bhCopyPatchLogicalSize); + if (cached.contains(uni)) { + return; + } + } + QImage patch; + if (!core::image_file::loadRegionToQImageExact(m_bgAbsPath, uni, &patch) || patch.isNull()) { + m_bhCopyPatchValid = false; + m_bhCopyPatch = QImage(); + m_bhCopyPatchLogicalSize = QSize(); + return; + } + if (patch.format() != QImage::Format_ARGB32_Premultiplied) { + patch = patch.convertToFormat(QImage::Format_ARGB32_Premultiplied); + } + m_bhCopyPatch = std::move(patch); + m_bhCopyPatchOrigin = uni.topLeft(); + m_bhCopyPatchLogicalSize = uni.size(); + m_bhCopyPatchValid = true; +} + void EditorCanvas::cancelBlackholeCopyResolve() { if (!m_blackholeCopyResolveActive) { return; } m_bhCopyPatchValid = false; m_bhCopyPatch = QImage(); + m_bhCopyPatchLogicalSize = QSize(); m_blackholeCopyResolveActive = false; m_blackholeCopyEntityId.clear(); m_blackholeCopyHoleRect = QRectF(); @@ -1567,7 +1594,7 @@ bool EditorCanvas::loadRgbCropForEntitySegmentStroke(const QRectF& polyBrWorld, } } if (outCropRgb.isNull()) { - (void)core::image_file::loadRegionToQImage(m_bgAbsPath, cropWorld, 4096, 4096, &outCropRgb); + (void)core::image_file::loadRegionToQImageExact(m_bgAbsPath, cropWorld, &outCropRgb); if (!outCropRgb.isNull() && outCropRgb.format() != QImage::Format_ARGB32_Premultiplied) { outCropRgb = outCropRgb.convertToFormat(QImage::Format_ARGB32_Premultiplied); } @@ -2027,11 +2054,11 @@ void EditorCanvas::paintEvent(QPaintEvent* e) { if (!m_presentationPreviewMode && m_blackholeCopyResolveActive && !m_blackholeCopyHoleRect.isNull() && !m_blackholeCopySourceRect.isNull()) { - const QRect srcRect = m_blackholeCopySourceRect.toAlignedRect(); - const QRect dstRect = m_blackholeCopyHoleRect.toAlignedRect(); + const QRectF holeF = m_blackholeCopyHoleRect; + const QRectF srcF(m_blackholeCopySourceRect.topLeft(), QSizeF(holeF.width(), holeF.height())); const bool havePreview = (m_bhCopyPatchValid && !m_bhCopyPatch.isNull()) || !m_bgImage.isNull(); - if (havePreview && srcRect.isValid() && dstRect.isValid()) { + if (havePreview && holeF.width() >= 1.0 && holeF.height() >= 1.0) { QPainterPath holePath; for (const auto& ent : m_entities) { if (ent.id == m_blackholeCopyEntityId && !ent.cutoutPolygonWorld.isEmpty()) { @@ -2044,11 +2071,31 @@ void EditorCanvas::paintEvent(QPaintEvent* e) { p.setClipPath(holePath); } p.setOpacity(0.75); - if (m_bhCopyPatchValid && !m_bhCopyPatch.isNull()) { - const QRect srcLocal = srcRect.translated(-m_bhCopyPatchOrigin); - p.drawImage(dstRect.topLeft(), m_bhCopyPatch, srcLocal); - } else { - p.drawImage(dstRect.topLeft(), m_bgImage, srcRect); + if (m_bhCopyPatchValid && !m_bhCopyPatch.isNull() && m_bhCopyPatchLogicalSize.isValid() && + m_bhCopyPatchLogicalSize.width() > 0 && m_bhCopyPatchLogicalSize.height() > 0) { + const QRectF srcWorldInPatch = srcF.translated(-m_bhCopyPatchOrigin); + const QRectF bound(0, 0, m_bhCopyPatchLogicalSize.width(), m_bhCopyPatchLogicalSize.height()); + const QRectF srcVis = srcWorldInPatch.intersected(bound); + if (!srcVis.isEmpty()) { + const double sx = double(m_bhCopyPatch.width()) / double(m_bhCopyPatchLogicalSize.width()); + const double sy = double(m_bhCopyPatch.height()) / double(m_bhCopyPatchLogicalSize.height()); + const QRectF srcPixels(srcVis.x() * sx, srcVis.y() * sy, srcVis.width() * sx, srcVis.height() * sy); + const QPointF dVis = srcVis.topLeft() - srcWorldInPatch.topLeft(); + const QRectF dstFrag(holeF.topLeft() + dVis, srcVis.size()); + p.drawImage(dstFrag, m_bhCopyPatch, srcPixels); + } + } else if (!m_bgImage.isNull() && m_bgLogicalSize.isValid() && m_bgLogicalSize.width() > 0 && + m_bgLogicalSize.height() > 0) { + const QRectF bgB(0, 0, m_bgLogicalSize.width(), m_bgLogicalSize.height()); + const double bx = double(m_bgImage.width()) / double(m_bgLogicalSize.width()); + const double by = double(m_bgImage.height()) / double(m_bgLogicalSize.height()); + const QRectF srcFW = srcF.intersected(bgB); + if (!srcFW.isEmpty()) { + const QPointF dVis = srcFW.topLeft() - srcF.topLeft(); + const QRectF dstFrag(holeF.topLeft() + dVis, srcFW.size()); + const QRectF srcPixels(srcFW.x() * bx, srcFW.y() * by, srcFW.width() * bx, srcFW.height() * by); + p.drawImage(dstFrag, m_bgImage, srcPixels); + } } p.setOpacity(1.0); p.restore(); @@ -3002,6 +3049,7 @@ void EditorCanvas::mouseMoveEvent(QMouseEvent* e) { src.moveTopLeft(wp - m_blackholeCopyDragOffset); src = clampRectTopLeftToBounds(src, worldRectOfBackground()); m_blackholeCopySourceRect = src; + reloadBlackholeCopyVipsPatchUnionIfNeeded(); update(); } e->accept(); @@ -3204,10 +3252,38 @@ void EditorCanvas::mouseReleaseEvent(QMouseEvent* e) { if (m_blackholeCopyResolveActive && e->button() == Qt::LeftButton) { if (m_blackholeCopyDragging) { m_blackholeCopyDragging = false; - const QPointF delta = m_blackholeCopySourceRect.topLeft() - m_blackholeCopyHoleRect.topLeft(); - emit requestResolveBlackholeCopy( - m_blackholeCopyEntityId, - QPoint(static_cast(std::round(delta.x())), static_cast(std::round(delta.y())))); + QRect targetW; + QRect srcW; + if (m_bgLogicalSize.isValid() && m_bgLogicalSize.width() > 0 && m_bgLogicalSize.height() > 0) { + const QRect bounds(0, 0, m_bgLogicalSize.width(), m_bgLogicalSize.height()); + // 洞与源不得各自 toAlignedRect:浮点外接框相同宽高时取整常会差 1px,导致 emit 空矩形。 + // 以洞的对齐矩形为准,源矩形与之同尺寸,仅平移量取 hole/source 左上角差值的四舍五入。 + targetW = m_blackholeCopyHoleRect.toAlignedRect().intersected(bounds); + if (targetW.isValid() && targetW.width() > 0 && targetW.height() > 0) { + const QPointF deltaF = + m_blackholeCopySourceRect.topLeft() - m_blackholeCopyHoleRect.topLeft(); + const QPoint d(int(std::lround(deltaF.x())), int(std::lround(deltaF.y()))); + srcW = QRect(targetW.topLeft() + d, targetW.size()); + if (srcW.left() < bounds.left()) { + srcW.moveLeft(bounds.left()); + } + if (srcW.top() < bounds.top()) { + srcW.moveTop(bounds.top()); + } + if (srcW.right() > bounds.right()) { + srcW.moveRight(bounds.right()); + } + if (srcW.bottom() > bounds.bottom()) { + srcW.moveBottom(bounds.bottom()); + } + } + } + if (targetW.isValid() && srcW.isValid() && targetW.width() > 0 && targetW.height() > 0 && + targetW.width() == srcW.width() && targetW.height() == srcW.height()) { + emit requestResolveBlackholeCopy(m_blackholeCopyEntityId, targetW, srcW); + } else { + emit requestResolveBlackholeCopy(m_blackholeCopyEntityId, QRect(), QRect()); + } } cancelBlackholeCopyResolve(); e->accept(); diff --git a/client/gui/editor/EditorCanvas.h b/client/gui/editor/EditorCanvas.h index 3e568f2..2b1892a 100644 --- a/client/gui/editor/EditorCanvas.h +++ b/client/gui/editor/EditorCanvas.h @@ -153,7 +153,8 @@ signals: void requestMoveCamera(const QString& id, const QPointF& delta); void requestCameraViewScaleAdjust(const QString& id, double factor); void selectedCameraChanged(bool hasSelection, const QString& id, const QPointF& centerWorld, double viewScale); - void requestResolveBlackholeCopy(const QString& entityId, const QPoint& sourceOffsetPx); + void requestResolveBlackholeCopy(const QString& entityId, const QRect& targetWorldInBackground, + const QRect& srcWorldInBackground); void entityDragActiveChanged(bool on); void selectedEntityPreviewChanged(const QString& id, int depth, const QPointF& originWorld); /// 预览模式下点击实体:anchorView 为实体质心在视图中的位置,用于摆放介绍浮层 @@ -269,6 +270,8 @@ private: bool m_initialBgLoadFinishedEmitted = false; QImage m_bhCopyPatch; // 黑洞「复制背景」预览:原图子区域 QPoint m_bhCopyPatchOrigin; + /// 与 m_bhCopyPatch 对应的逻辑像素范围(vips 路径可能将 patch 缩小到 maxOutput,与逻辑尺寸不同) + QSize m_bhCopyPatchLogicalSize; bool m_bhCopyPatchValid = false; mutable QPixmap m_bgPixmap; mutable bool m_pixmapDirty = true; @@ -337,6 +340,9 @@ private: DragMode m_dragMode = DragMode::None; QPointF m_dragStartMouseWorld; + /// 视口 LOD 下:黑洞「复制背景」预览纹理须覆盖 hole∪source;拖动时按需从磁盘重载。 + void reloadBlackholeCopyVipsPatchUnionIfNeeded(); + QVector m_entities; QVector m_tools; QVector m_cameraOverlays; diff --git a/client/gui/main_window/MainWindow.cpp b/client/gui/main_window/MainWindow.cpp index b4a5318..f942cef 100644 --- a/client/gui/main_window/MainWindow.cpp +++ b/client/gui/main_window/MainWindow.cpp @@ -4159,8 +4159,10 @@ void MainWindow::rebuildCentralPages() { } }); connect(m_editorCanvas, &EditorCanvas::requestResolveBlackholeCopy, this, - [this](const QString& entityId, const QPoint& sourceOffsetPx) { - if (!m_workspace.resolveBlackholeByCopyBackground(entityId, sourceOffsetPx, true)) { + [this](const QString& entityId, const QRect& targetWorldInBackground, + const QRect& srcWorldInBackground) { + if (!m_workspace.resolveBlackholeByCopyBackground(entityId, targetWorldInBackground, + srcWorldInBackground, true)) { QMessageBox::warning( this, QStringLiteral("黑洞修复"), @@ -4804,9 +4806,9 @@ void MainWindow::showBlackholeContextMenu(const QPoint& globalPos, const QString return; } - const int maxDec = std::clamp(std::max(cropRect.width(), cropRect.height()), 512, 4096); + // 与黑洞/覆盖层逻辑像素严格对齐:必须按 crop 原始分辨率解码,避免 vips maxOutput 缩放导致预览与写盘发糊。 QImage bg; - if (!core::image_file::loadRegionToQImage(bgAbs, cropRect, maxDec, maxDec, &bg) || bg.isNull()) { + if (!core::image_file::loadRegionToQImageExact(bgAbs, cropRect, &bg) || bg.isNull()) { QMessageBox::warning(this, QStringLiteral("黑洞修复"), QStringLiteral("读取背景失败。")); return; } @@ -5008,6 +5010,9 @@ void MainWindow::showBlackholeContextMenu(const QPoint& globalPos, const QString QString immediateErr; const QString inpaintModel = dlg.inpaintModelKey(); const QString prompt = dlg.promptText(); + // 后端在不超过 max_side 时保持输入分辨率推理,再缩回原尺寸;此处用裁剪长边避免无谓降采样(上限与 API 一致)。 + const int cropMaxSide = std::max(cropRect.width(), cropRect.height()); + const int inpaintMaxSide = std::clamp(cropMaxSide, 256, 8192); QNetworkReply* reply = client->inpaintAsync( cropPng, maskPng, @@ -5015,7 +5020,7 @@ void MainWindow::showBlackholeContextMenu(const QPoint& globalPos, const QString prompt, QString(), 0.72, - 1024, + inpaintMaxSide, &immediateErr); if (!reply) { QMessageBox::warning(this, diff --git a/python_server/app/schemas.py b/python_server/app/schemas.py index e318062..7239f69 100644 --- a/python_server/app/schemas.py +++ b/python_server/app/schemas.py @@ -51,7 +51,7 @@ class InpaintRequest(ImageInput): strength: float = Field(0.8, ge=0.0, le=1.0) negative_prompt: Optional[str] = Field("", description="负向 prompt") mask_b64: Optional[str] = Field(None, description="mask PNG base64(可选)") - max_side: int = Field(1024, ge=128, le=2048) + max_side: int = Field(1024, ge=128, le=8192, description="推理时长边上限;不超过则按输入全分辨率推理后再还原") guidance_scale: Optional[float] = Field(None, ge=0.0, le=200.0, description="扩散 CFG;不传则各后端默认") num_inference_steps: Optional[int] = Field(None, ge=1, le=150, description="采样步数;不传则各后端默认") diff --git a/python_server/model/Inpaint/flux_fill/loader.py b/python_server/model/Inpaint/flux_fill/loader.py index e38354f..cef841e 100644 --- a/python_server/model/Inpaint/flux_fill/loader.py +++ b/python_server/model/Inpaint/flux_fill/loader.py @@ -63,13 +63,14 @@ def make_flux_fill_predictor( if device == "cuda": try: - pipe.enable_model_cpu_offload() + pipe.enable_vae_tiling() except Exception: - try: - pipe.enable_vae_tiling() - except Exception: - pass - pipe.to("cuda") + pass + try: + pipe.enable_attention_slicing() + except Exception: + pass + pipe.to("cuda") else: pipe.to(device) diff --git a/python_server/model/Inpaint/inpaint_loader.py b/python_server/model/Inpaint/inpaint_loader.py index f6457aa..f1e0de4 100644 --- a/python_server/model/Inpaint/inpaint_loader.py +++ b/python_server/model/Inpaint/inpaint_loader.py @@ -78,10 +78,8 @@ def _enable_memory_opts(pipe, device: str) -> None: pipe.enable_vae_tiling() except Exception: pass - try: - pipe.enable_model_cpu_offload() - except Exception: - pass + # 不使用 enable_model_cpu_offload:与 transformers 5.x 下 CLIPTextModel 等子模块的 accelerate hook + # 组合易触发 `'_hf_hook'` AttributeError;整管已 .to(cuda) 时以 slicing/tiling 省显存即可。 def _align_size(orig_w: int, orig_h: int, max_side: int) -> tuple[int, int]: @@ -123,8 +121,7 @@ def _make_sdxl_inpaint_predictor( use_safetensors=True, ).to(device) - # 省显存设置(尽量不改变输出语义) - # 注意:CPU offload 会明显变慢,但能显著降低显存占用。 + # 省显存:attention slicing / VAE slicing tiling(不再 enable_model_cpu_offload,见 _enable_memory_opts 注释) if device == "cuda": try: pipe.enable_attention_slicing() @@ -138,10 +135,6 @@ def _make_sdxl_inpaint_predictor( pipe.enable_vae_tiling() except Exception: pass - try: - pipe.enable_model_cpu_offload() - except Exception: - pass def _predict( image: Image.Image, @@ -244,10 +237,7 @@ def _make_controlnet_predictor(_: UnifiedInpaintConfig): pipe.enable_vae_tiling() except Exception: pass - try: - pipe.enable_model_cpu_offload() - except Exception: - pipe.to(device) + pipe.to(device) else: pipe.to(device) @@ -361,9 +351,22 @@ def _make_lama_predictor(cfg: UnifiedInpaintConfig): max_side: int = 1024, **_kwargs, ) -> Image.Image: - # big-lama:仅 (RGB, mask);无文本条件。 - _ = (strength, guidance_scale, num_inference_steps, max_side) - return lama(image, mask) + # big-lama:仅 (RGB, mask);无文本条件。与 SDXL/FLUX 一致:可按 max_side 降采样推理后再还原到输入分辨率。 + _ = (strength, guidance_scale, num_inference_steps) + image = image.convert("RGB") + mask = mask.convert("L") + orig_w, orig_h = image.size + run_w, run_h = _align_size(orig_w, orig_h, max_side=max_side) + if (run_w, run_h) != (orig_w, orig_h): + image_run = image.resize((run_w, run_h), Image.BICUBIC) + mask_run = mask.resize((run_w, run_h), Image.NEAREST) + else: + image_run = image + mask_run = mask + out = lama(image_run, mask_run) + if out.size != (orig_w, orig_h): + out = out.resize((orig_w, orig_h), resample=Image.BICUBIC) + return out return _predict