Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9305ca6a7a | ||
|
|
9a8f689fae | ||
|
|
b1cb565255 | ||
|
|
a4ed0d9f09 | ||
|
|
3ff6fbe5e7 | ||
|
|
8b66a51bc5 | ||
|
|
7ece136a60 |
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"git.showActionButton": {
|
||||
"sync": false,
|
||||
"commit": true,
|
||||
"publish": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
# AGENTS.md
|
||||
|
||||
Operational Rules for AI Coding Agents
|
||||
|
||||
- Scope control: Make minimal, localized changes. Do not rewrite entire files unless explicitly instructed.
|
||||
- Edit safety: Never leave files in a broken or incomplete state. Do not stop edits mid-file. Warn before any risky or large change.
|
||||
- Style consistency: Preserve existing coding style, structure, naming, and architecture. Do not reformat code unnecessarily.
|
||||
- No cosmetic refactors: Do not perform stylistic rewrites or "cleanup" changes disguised as refactors.
|
||||
- Imports/layout/formatting: Do not change imports, formatting, or code layout unless required for correctness.
|
||||
- File protection: Do not modify the following unless explicitly requested:
|
||||
- README.md
|
||||
- LICENSE
|
||||
- .git/
|
||||
- .claude/
|
||||
- nodes/
|
||||
- core/
|
||||
- No hallucination: Do not invent APIs, features, config options, or behaviors not present in the codebase.
|
||||
- Explicit intent: If a request is ambiguous or underspecified, ask for clarification before editing.
|
||||
- Refactors: Keep refactors incremental, justified, and tightly scoped. No sweeping refactors.
|
||||
- Testing & validation: When applicable, suggest how to validate changes. Do not claim tests were run unless you ran them.
|
||||
- Communication: Briefly explain what changed and why.
|
||||
- Failure handling: Do not abort silently. If you must continue later, say so clearly and indicate what remains.
|
||||
|
||||
## Version Tagging
|
||||
|
||||
- When a commit bumps the version in `pyproject.toml`, create and push the matching Git tag targeting that exact commit before doing anything else.
|
||||
- The version-bump commit must have a user-facing release message that summarizes the actual changes in that version. Do not use a generic subject such as `Bump version to 1.0.7` by itself. Use a descriptive subject such as `Prepare 1.0.7 release with user-defined style indicators`.
|
||||
- New version tags must include a concise description of the changes shipped in that version. Create release tags as annotated tags with a title like `Release 1.0.7` and bullet-point notes summarizing the user-visible changes and important fixes. Users should be able to understand what changed from the tag page alone.
|
||||
- The tag name must exactly match the version string in `pyproject.toml` (e.g., `1.0.0`, `1.0.1`, `1.2.3`). **No `v` prefix, no other format.** This repo uses plain numeric tags only (e.g., `1.2.0`, never `v1.2.0`).
|
||||
- Before creating a release tag, inspect the existing tags in the repo (`git tag --list`) to confirm the naming convention in use. For this repo the required format is always plain numeric (e.g., `1.2.0`).
|
||||
- The README version badge (`shields.io github/v/tag`) may render the tag with a `v` prefix (e.g., display `v1.2.0`) even when the actual Git tag is `1.2.0`. This is normal shields.io display behaviour — do **not** create a `v`-prefixed tag to match the badge display.
|
||||
- Do not create GitHub releases unless explicitly requested.
|
||||
- Never move, overwrite, recreate, or retag an existing version tag to point to a different commit.
|
||||
- Each version tag is immutable: it must permanently point to the commit where that version was published.
|
||||
- If a version is pushed without the matching tag, the README version badge will become outdated or inconsistent.
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 2.6 MiB |
+6
-1
@@ -124,9 +124,14 @@ Kombinieren Sie globale positive/negative Konditionierung mit der Bereichspipeli
|
||||
**Ausgaben:**
|
||||
- `positive_out` (`CONDITIONING`)
|
||||
- `negative_out` (`CONDITIONING`)
|
||||
- `areas_out` (`CONDITIONING_AREAS`)
|
||||
|
||||
**Details zu `areas_out`:**
|
||||
|
||||
`areas_out` gibt die Liste der konfigurierten Bereichsregionen als strukturierte Datenausgabe aus. Jeder Eintrag enthält die normalisierten Koordinaten (`x`, `y`, `width`, `height`) und `strength`, die über `Conditioning Pipeline (Set Area)` in der Pipeline definiert wurden. Verbinden Sie `areas_out` mit [ComfyUI-OpenPose-Studio](https://github.com/andreszs/comfyui-openpose-studio), um Ihre Konditionierungsbereiche automatisch in den OpenPose-Editor zu spiegeln — die Pose-Platzierung wird sich an den genau konditionierten Regionen ausrichten. Dieser Ausgang kann auch von anderen Erweiterungen oder Knoten verwendet werden, die Bereichsmetadaten für die Maskengenerierung oder bereichsbewusste nachgelagerte Verarbeitung akzeptieren.
|
||||
|
||||
**Hinweise zum Verhalten:**
|
||||
- Wenn die Pipeline leer/ungültig ist, greifen die Ausgaben auf die globalen Eingaben zurück.
|
||||
- Wenn die Pipeline leer/ungültig ist, greifen die Ausgaben auf die globalen Eingaben zurück, und `areas_out` ist eine leere Liste.
|
||||
- Wendet regionale Einträge an und anschließend einen Standard-Kombinationsdurchgang für nicht abgedeckte Regionen.
|
||||
- `global_strength` steuert, wie stark der globale Kontext mit lokalen Bereichen konkurriert.
|
||||
- Wenn Sie global_strength zu hoch einstellen, kann dies den Einfluss der bereichsspezifischen Konditionierung verringern und sich negativ auf die Bildqualität auswirken.
|
||||
|
||||
+6
-1
@@ -124,9 +124,14 @@ Combinar el condicionamiento global positivo/negativo con el pipeline del área
|
||||
**Salidas:**
|
||||
- `positive_out` (`CONDITIONING`)
|
||||
- `negative_out` (`CONDITIONING`)
|
||||
- `areas_out` (`CONDITIONING_AREAS`)
|
||||
|
||||
**Detalles de `areas_out`:**
|
||||
|
||||
`areas_out` expone la lista de regiones de área configuradas como una salida de datos estructurada. Cada entrada contiene las coordenadas normalizadas (`x`, `y`, `width`, `height`) y `strength` que se definieron en la canalización mediante `Conditioning Pipeline (Set Area)`. Conecte `areas_out` a [ComfyUI-OpenPose-Studio](https://github.com/andreszs/comfyui-openpose-studio) para reflejar automáticamente sus áreas de condicionamiento en el editor de OpenPose — la colocación de poses se alineará con las regiones exactas que condicionó. Esta salida también puede ser consumida por cualquier otra extensión o nodo que acepte metadatos de área para generación de máscaras o procesamiento posterior consciente de regiones.
|
||||
|
||||
**Notas de comportamiento:**
|
||||
- Si la canalización está vacía o no es válida, las salidas vuelven a las entradas globales.
|
||||
- Si la canalización está vacía o no es válida, las salidas vuelven a las entradas globales y `areas_out` es una lista vacía.
|
||||
- Aplica entradas regionales, luego un pase combinado predeterminado para regiones no cubiertas.
|
||||
- `global_strength` controla la fuerza con la que el contexto global compite con las áreas locales.
|
||||
- Presionar global_strength demasiado alto puede reducir la influencia del acondicionamiento por área y afectar negativamente la calidad de la imagen.
|
||||
|
||||
+7
-2
@@ -124,9 +124,14 @@ Combinez le conditionnement global positif/négatif avec le pipeline régional p
|
||||
**Sorties :**
|
||||
- `positive_out` (`CONDITIONING`)
|
||||
- `negative_out` (`CONDITIONING`)
|
||||
- `areas_out` (`CONDITIONING_AREAS`)
|
||||
|
||||
**Remarques sur le comportement :**
|
||||
- Si le pipeline est vide/invalide, les sorties reviennent aux entrées globales.
|
||||
**Détails sur `areas_out` :**
|
||||
|
||||
`areas_out` expose la liste des régions de zone configurées sous forme de sortie de données structurées. Chaque entrée contient les coordonnées normalisées (`x`, `y`, `width`, `height`) et `strength` définies dans le pipeline via `Conditioning Pipeline (Set Area)`. Connectez `areas_out` à [ComfyUI-OpenPose-Studio](https://github.com/andreszs/comfyui-openpose-studio) pour refléter automatiquement vos zones de conditionnement dans l'éditeur OpenPose — le placement des poses s'alignera sur les régions exactes que vous avez conditionnées. Cette sortie peut également être consommée par toute autre extension ou nœud acceptant des métadonnées de zone pour la génération de masques ou le traitement en aval sensible aux régions.
|
||||
|
||||
**Remarques sur le comportement :**
|
||||
- Si le pipeline est vide/invalide, les sorties reviennent aux entrées globales et `areas_out` est une liste vide.
|
||||
- Applique les entrées régionales, puis une passe de combinaison par défaut pour les régions non couvertes.
|
||||
- `global_strength` contrôle la mesure dans laquelle le contexte mondial entre en concurrence avec les zones locales.
|
||||
- Pousser global_strength trop haut peut réduire l'influence du conditionnement par zone et avoir un impact négatif sur la qualité de l'image.
|
||||
|
||||
+6
-1
@@ -124,9 +124,14 @@
|
||||
**出力:**
|
||||
- `positive_out` (`CONDITIONING`)
|
||||
- `negative_out` (`CONDITIONING`)
|
||||
- `areas_out` (`CONDITIONING_AREAS`)
|
||||
|
||||
**`areas_out` の詳細:**
|
||||
|
||||
`areas_out` は、設定されたエリア領域のリストを構造化データ出力として公開します。各エントリには、`Conditioning Pipeline (Set Area)` を通じてパイプラインで定義された正規化座標(`x`、`y`、`width`、`height`)と `strength` が含まれています。`areas_out` を [ComfyUI-OpenPose-Studio](https://github.com/andreszs/comfyui-openpose-studio) に接続すると、コンディショニングエリアを OpenPose エディターに自動的にミラーリングできます。ポーズの配置は、コンディショニングした正確な領域に合わせて整列されます。この出力は、マスク生成や領域認識型のダウンストリーム処理のためにエリアメタデータを受け入れる他の拡張機能やノードでも使用できます。
|
||||
|
||||
**行動メモ:**
|
||||
- パイプラインが空または無効な場合、出力はグローバル入力にフォールバックします。
|
||||
- パイプラインが空または無効な場合、出力はグローバル入力にフォールバックし、`areas_out` は空のリストになります。
|
||||
- 地域エントリを適用してから、カバーされていない地域のデフォルトの結合パスを適用します。
|
||||
- `global_strength` は、グローバル コンテキストがローカル エリアとどの程度競合するかを制御します。
|
||||
- global_strength を高くしすぎると、エリアごとのコンディショニングの影響が減少し、画質に悪影響を及ぼす可能性があります。
|
||||
|
||||
+6
-1
@@ -124,9 +124,14 @@ ComfyUI에 대한 영역 기반 LoRA 조절 래퍼 및 LoRA 예약 노드.
|
||||
**출력:**
|
||||
- `positive_out` (`CONDITIONING`)
|
||||
- `negative_out` (`CONDITIONING`)
|
||||
- `areas_out` (`CONDITIONING_AREAS`)
|
||||
|
||||
**`areas_out` 세부사항:**
|
||||
|
||||
`areas_out`은 구성된 영역 목록을 구조화된 데이터 출력으로 노출합니다. 각 항목에는 `Conditioning Pipeline (Set Area)`를 통해 파이프라인에서 정의된 정규화된 좌표(`x`, `y`, `width`, `height`)와 `strength`가 포함됩니다. `areas_out`을 [ComfyUI-OpenPose-Studio](https://github.com/andreszs/comfyui-openpose-studio)에 연결하면 조건화 영역을 OpenPose 편집기에 자동으로 미러링할 수 있습니다. 포즈 배치는 조건화한 정확한 영역에 맞게 정렬됩니다. 이 출력은 마스크 생성 또는 영역 인식 다운스트림 처리를 위한 영역 메타데이터를 허용하는 다른 확장 또는 노드에서도 사용할 수 있습니다.
|
||||
|
||||
**행동 참고사항:**
|
||||
- 파이프라인이 비어 있거나 유효하지 않은 경우 출력은 전역 입력으로 대체됩니다.
|
||||
- 파이프라인이 비어 있거나 유효하지 않은 경우 출력은 전역 입력으로 대체되고 `areas_out`은 빈 목록이 됩니다.
|
||||
- 지역 항목을 적용한 다음, 발견되지 않은 지역에 대한 기본 통합 패스를 적용합니다.
|
||||
- `global_strength`은 글로벌 컨텍스트가 로컬 영역과 얼마나 강력하게 경쟁하는지 제어합니다.
|
||||
- global_strength을(를) 너무 높이면 영역별 조정 영향이 줄어들고 이미지 품질에 부정적인 영향을 미칠 수 있습니다.
|
||||
|
||||
+6
-1
@@ -124,9 +124,14 @@ Combine global positive/negative conditioning with the area pipeline for region-
|
||||
**Outputs:**
|
||||
- `positive_out` (`CONDITIONING`)
|
||||
- `negative_out` (`CONDITIONING`)
|
||||
- `areas_out` (`CONDITIONING_AREAS`)
|
||||
|
||||
**`areas_out` details:**
|
||||
|
||||
`areas_out` exposes the list of configured area regions as a structured data output. Each entry contains the normalized coordinates (`x`, `y`, `width`, `height`) and `strength` that were defined in the pipeline via `Conditioning Pipeline (Set Area)`. Connect `areas_out` to [ComfyUI-OpenPose-Studio](https://github.com/andreszs/comfyui-openpose-studio) to automatically mirror your conditioning areas into the OpenPose editor — pose placement will align with the exact regions you conditioned. This output can also be consumed by any other extension or node that accepts area metadata for mask generation or region-aware downstream processing.
|
||||
|
||||
**Behavior notes:**
|
||||
- If the pipeline is empty/invalid, outputs fall back to the global inputs.
|
||||
- If the pipeline is empty/invalid, outputs fall back to the global inputs and `areas_out` is an empty list.
|
||||
- Applies regional entries, then a default combine pass for uncovered regions.
|
||||
- `global_strength` controls how strongly global context competes with local areas.
|
||||
- Pushing global_strength too high can reduce per-area conditioning influence and negatively impact image quality.
|
||||
|
||||
+6
-1
@@ -124,9 +124,14 @@ Combine o condicionamento positivo/negativo global com o pipeline de área para
|
||||
**Saídas:**
|
||||
- `positive_out` (`CONDITIONING`)
|
||||
- `negative_out` (`CONDITIONING`)
|
||||
- `areas_out` (`CONDITIONING_AREAS`)
|
||||
|
||||
**Detalhes sobre `areas_out`:**
|
||||
|
||||
`areas_out` expõe a lista de regiões de área configuradas como uma saída de dados estruturada. Cada entrada contém as coordenadas normalizadas (`x`, `y`, `width`, `height`) e `strength` definidas no pipeline via `Conditioning Pipeline (Set Area)`. Conecte `areas_out` ao [ComfyUI-OpenPose-Studio](https://github.com/andreszs/comfyui-openpose-studio) para espelhar automaticamente suas áreas de condicionamento no editor OpenPose — o posicionamento das poses se alinhará com as regiões exatas que você condicionou. Esta saída também pode ser consumida por qualquer outra extensão ou nó que aceite metadados de área para geração de máscaras ou processamento posterior com reconhecimento de região.
|
||||
|
||||
**Notas de comportamento:**
|
||||
- Se o pipeline estiver vazio/inválido, as saídas voltam para as entradas globais.
|
||||
- Se o pipeline estiver vazio/inválido, as saídas voltam para as entradas globais e `areas_out` é uma lista vazia.
|
||||
- Aplica entradas regionais e, em seguida, um passe combinado padrão para regiões descobertas.
|
||||
- `global_strength` controla a intensidade com que o contexto global compete com as áreas locais.
|
||||
- Pressionar global_strength muito alto pode reduzir a influência do condicionamento por área e impactar negativamente a qualidade da imagem.
|
||||
|
||||
+6
-1
@@ -124,9 +124,14 @@
|
||||
**Выходы:**
|
||||
- `positive_out` (`CONDITIONING`)
|
||||
- `negative_out` (`CONDITIONING`)
|
||||
- `areas_out` (`CONDITIONING_AREAS`)
|
||||
|
||||
**Подробности об `areas_out`:**
|
||||
|
||||
`areas_out` предоставляет список настроенных областных регионов в виде структурированных выходных данных. Каждая запись содержит нормализованные координаты (`x`, `y`, `width`, `height`) и `strength`, определённые в конвейере через `Conditioning Pipeline (Set Area)`. Подключите `areas_out` к [ComfyUI-OpenPose-Studio](https://github.com/andreszs/comfyui-openpose-studio), чтобы автоматически отобразить ваши области обусловливания в редакторе OpenPose — расположение поз будет совпадать с теми самыми регионами, которые вы обусловили. Этот выход также может быть использован любым другим расширением или узлом, принимающим метаданные областей для генерации масок или регионально-ориентированной нисходящей обработки.
|
||||
|
||||
**Примечания к поведению:**
|
||||
- Если конвейер пуст/недействителен, выходные данные возвращаются к глобальным входам.
|
||||
- Если конвейер пуст/недействителен, выходные данные возвращаются к глобальным входам, а `areas_out` является пустым списком.
|
||||
- Применяет региональные записи, а затем комбинированный проход по умолчанию для непокрытых регионов.
|
||||
- `global_strength` контролирует, насколько сильно глобальный контекст конкурирует с локальными областями.
|
||||
- Слишком высокое значение global_strength может уменьшить влияние обработки каждой области и отрицательно повлиять на качество изображения.
|
||||
|
||||
@@ -125,9 +125,14 @@
|
||||
**輸出:**
|
||||
- `positive_out` (`CONDITIONING`)
|
||||
- `negative_out` (`CONDITIONING`)
|
||||
- `areas_out` (`CONDITIONING_AREAS`)
|
||||
|
||||
**`areas_out` 詳情:**
|
||||
|
||||
`areas_out` 將已配置的區域清單作為結構化資料輸出公開。每個條目包含透過 `Conditioning Pipeline (Set Area)` 在管道中定義的歸一化座標(`x`、`y`、`width`、`height`)和 `strength`。將 `areas_out` 連接到 [ComfyUI-OpenPose-Studio](https://github.com/andreszs/comfyui-openpose-studio),即可將您的調節區域自動鏡像到 OpenPose 編輯器中——姿態放置將與您調節的確切區域對齊。此輸出也可被接受區域元資料的任何其他擴充功能或節點使用,用於遮罩生成或區域感知的下游處理。
|
||||
|
||||
**行為注意事項:**
|
||||
- 如果管道為空/無效,輸出將回落到全域輸入。
|
||||
- 如果管道為空/無效,輸出將回落到全域輸入,且 `areas_out` 為空清單。
|
||||
- 套用區域條目,然後對未覆蓋區域套用預設組合通道。
|
||||
- `global_strength` 控制全球背景與局部區域競爭的強度。
|
||||
- 將 global_strength 推得太高會減少每個區域的調節效果並對影像品質產生負面影響。
|
||||
|
||||
+6
-1
@@ -124,9 +124,14 @@
|
||||
**输出:**
|
||||
- `positive_out` (`CONDITIONING`)
|
||||
- `negative_out` (`CONDITIONING`)
|
||||
- `areas_out` (`CONDITIONING_AREAS`)
|
||||
|
||||
**`areas_out` 详情:**
|
||||
|
||||
`areas_out` 将已配置的区域列表作为结构化数据输出公开。每个条目包含通过 `Conditioning Pipeline (Set Area)` 在管道中定义的归一化坐标(`x`、`y`、`width`、`height`)和 `strength`。将 `areas_out` 连接到 [ComfyUI-OpenPose-Studio](https://github.com/andreszs/comfyui-openpose-studio),即可将您的调节区域自动镜像到 OpenPose 编辑器中——姿态放置将与您调节的确切区域对齐。此输出也可被接受区域元数据的任何其他扩展或节点使用,用于遮罩生成或区域感知的下游处理。
|
||||
|
||||
**行为注意事项:**
|
||||
- 如果管道为空/无效,输出将回落到全局输入。
|
||||
- 如果管道为空/无效,输出将回落到全局输入,且 `areas_out` 为空列表。
|
||||
- 应用区域条目,然后对未覆盖区域应用默认组合通道。
|
||||
- `global_strength` 控制全球背景与局部区域竞争的强度。
|
||||
- 将 global_strength 推得太高会减少每个区域的调节影响并对图像质量产生负面影响。
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -24,7 +24,7 @@ class ConditioningPipelineSetArea:
|
||||
|
||||
NODE_ID = "ConditioningPipelineSetArea"
|
||||
NODE_NAME = "Conditioning Pipeline (Set Area)"
|
||||
CATEGORY = "LoRA Pipeline/Conditioning"
|
||||
CATEGORY = "LoRA Pipeline/Conditioning"
|
||||
RETURN_TYPES = ("CONDITIONING_PIPELINE",)
|
||||
RETURN_NAMES = ("pipeline_out",)
|
||||
FUNCTION = "run"
|
||||
@@ -83,9 +83,9 @@ class ConditioningPipelineCombine:
|
||||
|
||||
NODE_ID = "ConditioningPipelineCombine"
|
||||
NODE_NAME = "Conditioning Pipeline (Combine)"
|
||||
CATEGORY = "LoRA Pipeline/Conditioning"
|
||||
RETURN_TYPES = ("CONDITIONING", "CONDITIONING")
|
||||
RETURN_NAMES = ("positive_out", "negative_out")
|
||||
CATEGORY = "LoRA Pipeline/Conditioning"
|
||||
RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "CONDITIONING_AREAS")
|
||||
RETURN_NAMES = ("positive_out", "negative_out", "areas_out")
|
||||
FUNCTION = "run"
|
||||
|
||||
BASE_RES = 64
|
||||
@@ -114,8 +114,19 @@ class ConditioningPipelineCombine:
|
||||
if cond is not None:
|
||||
valid.append(item)
|
||||
|
||||
areas_list = [
|
||||
{
|
||||
"x": float(item["x"]),
|
||||
"y": float(item["y"]),
|
||||
"width": float(item["width"]),
|
||||
"height": float(item["height"]),
|
||||
"strength": float(item.get("strength", 1.0)),
|
||||
}
|
||||
for item in valid
|
||||
]
|
||||
|
||||
if not valid:
|
||||
return (global_positive, global_negative)
|
||||
return (global_positive, global_negative, [])
|
||||
|
||||
graph = GraphBuilder()
|
||||
|
||||
@@ -179,6 +190,6 @@ class ConditioningPipelineCombine:
|
||||
)
|
||||
|
||||
return {
|
||||
"result": (final.out(0), final.out(1)),
|
||||
"result": (final.out(0), final.out(1), areas_list),
|
||||
"expand": graph.finalize(),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
# Palette: distinct colors readable on dark and light backgrounds
|
||||
_AREA_COLORS = [
|
||||
(41, 121, 255), # Blue
|
||||
(255, 140, 0), # Orange
|
||||
(0, 200, 80), # Green
|
||||
(220, 50, 220), # Magenta
|
||||
(0, 200, 230), # Cyan
|
||||
(210, 180, 0), # Gold
|
||||
]
|
||||
|
||||
# Grid visual constants (match the OpenPose Studio canvas style)
|
||||
_GRID_SPACING = 64
|
||||
_BG_COLOR = (26, 26, 26) # #1a1a1a
|
||||
_GRID_COLOR = (220, 220, 220) # light gray
|
||||
_GRID_ALPHA = 0.50
|
||||
_AXIS_X_COLOR = (0, 255, 0) # green
|
||||
_AXIS_Y_COLOR = (255, 0, 0) # red
|
||||
_AXIS_ALPHA = 0.30
|
||||
_BORDER_COLOR = (160, 160, 160)
|
||||
_BORDER_ALPHA = 0.70
|
||||
|
||||
_FILL_ALPHA = 0.18
|
||||
_BORDER_LINE_ALPHA = 0.85
|
||||
_LABEL_BG_ALPHA = 0.68
|
||||
_LABEL_TEXT_ALPHA = 0.92
|
||||
|
||||
_MIN_DIM_FOR_FULL_LABEL = 80
|
||||
_FONT_SIZE = 11
|
||||
_PAD_H = 4
|
||||
_PAD_V = 2
|
||||
_LABEL_INSET = 6
|
||||
_BORDER_RADIUS = 3
|
||||
|
||||
|
||||
def _load_font(size):
|
||||
from PIL import ImageFont
|
||||
try:
|
||||
return ImageFont.load_default(size=size)
|
||||
except TypeError:
|
||||
return ImageFont.load_default()
|
||||
|
||||
|
||||
def _alpha_blend(base_rgba, overlay_rgba):
|
||||
"""Blend overlay onto base; both (R,G,B,A) tuples with A in 0-255."""
|
||||
ba = base_rgba[3] / 255.0
|
||||
oa = overlay_rgba[3] / 255.0
|
||||
out_a = oa + ba * (1.0 - oa)
|
||||
if out_a < 1e-6:
|
||||
return (0, 0, 0, 0)
|
||||
out_rgb = tuple(
|
||||
int((overlay_rgba[i] * oa + base_rgba[i] * ba * (1.0 - oa)) / out_a)
|
||||
for i in range(3)
|
||||
)
|
||||
return out_rgb + (int(out_a * 255),)
|
||||
|
||||
|
||||
def _render_area_preview(areas, width, height):
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
w, h = int(width), int(height)
|
||||
if w < 1 or h < 1:
|
||||
w, h = 64, 64
|
||||
|
||||
# ---- background ----
|
||||
img = Image.new("RGBA", (w, h), _BG_COLOR + (255,))
|
||||
|
||||
# ---- grid ----
|
||||
grid_layer = Image.new("RGBA", (w, h), (0, 0, 0, 0))
|
||||
gd = ImageDraw.Draw(grid_layer)
|
||||
ga = int(_GRID_ALPHA * 255)
|
||||
cx, cy = w // 2, h // 2
|
||||
|
||||
for x in range(_GRID_SPACING, w, _GRID_SPACING):
|
||||
if abs(x - cx) < 1:
|
||||
continue
|
||||
gd.line([(x, 0), (x, h - 1)], fill=_GRID_COLOR + (ga,), width=1)
|
||||
for y in range(_GRID_SPACING, h, _GRID_SPACING):
|
||||
if abs(y - cy) < 1:
|
||||
continue
|
||||
gd.line([(0, y), (w - 1, y)], fill=_GRID_COLOR + (ga,), width=1)
|
||||
|
||||
aa = int(_AXIS_ALPHA * 255)
|
||||
gd.line([(cx, 0), (cx, h - 1)], fill=_AXIS_Y_COLOR + (aa,), width=1)
|
||||
gd.line([(0, cy), (w - 1, cy)], fill=_AXIS_X_COLOR + (aa,), width=1)
|
||||
|
||||
img = Image.alpha_composite(img, grid_layer)
|
||||
|
||||
# ---- fills ----
|
||||
fills_layer = Image.new("RGBA", (w, h), (0, 0, 0, 0))
|
||||
fd = ImageDraw.Draw(fills_layer)
|
||||
for i, area in enumerate(areas):
|
||||
r, g, b = _AREA_COLORS[i % len(_AREA_COLORS)]
|
||||
rx = int(area["x"] * w)
|
||||
ry = int(area["y"] * h)
|
||||
rw = max(1, int(area["width"] * w))
|
||||
rh = max(1, int(area["height"] * h))
|
||||
fill_a = int(_FILL_ALPHA * 255)
|
||||
try:
|
||||
fd.rounded_rectangle(
|
||||
[rx, ry, rx + rw, ry + rh],
|
||||
radius=_BORDER_RADIUS,
|
||||
fill=(r, g, b, fill_a),
|
||||
)
|
||||
except AttributeError:
|
||||
fd.rectangle([rx, ry, rx + rw, ry + rh], fill=(r, g, b, fill_a))
|
||||
|
||||
img = Image.alpha_composite(img, fills_layer)
|
||||
|
||||
# ---- borders ----
|
||||
borders_layer = Image.new("RGBA", (w, h), (0, 0, 0, 0))
|
||||
bd = ImageDraw.Draw(borders_layer)
|
||||
for i, area in enumerate(areas):
|
||||
r, g, b = _AREA_COLORS[i % len(_AREA_COLORS)]
|
||||
rx = int(area["x"] * w)
|
||||
ry = int(area["y"] * h)
|
||||
rw = max(1, int(area["width"] * w))
|
||||
rh = max(1, int(area["height"] * h))
|
||||
border_a = int(_BORDER_LINE_ALPHA * 255)
|
||||
try:
|
||||
bd.rounded_rectangle(
|
||||
[rx, ry, rx + rw, ry + rh],
|
||||
radius=_BORDER_RADIUS,
|
||||
outline=(r, g, b, border_a),
|
||||
width=2,
|
||||
)
|
||||
except AttributeError:
|
||||
bd.rectangle([rx, ry, rx + rw, ry + rh], outline=(r, g, b, border_a), width=2)
|
||||
|
||||
img = Image.alpha_composite(img, borders_layer)
|
||||
|
||||
# ---- labels ----
|
||||
labels_layer = Image.new("RGBA", (w, h), (0, 0, 0, 0))
|
||||
ld = ImageDraw.Draw(labels_layer)
|
||||
font = _load_font(_FONT_SIZE)
|
||||
|
||||
for i, area in enumerate(areas):
|
||||
rx = int(area["x"] * w)
|
||||
ry = int(area["y"] * h)
|
||||
rw = max(1, int(area["width"] * w))
|
||||
rh = max(1, int(area["height"] * h))
|
||||
|
||||
idx = i + 1
|
||||
short_label = f"Area {idx}"
|
||||
full_label = (
|
||||
f"Area {idx} · x={area['x']:.2f} y={area['y']:.2f}"
|
||||
f" w={area['width']:.2f} h={area['height']:.2f}"
|
||||
)
|
||||
label = full_label if (rw >= _MIN_DIM_FOR_FULL_LABEL and rh >= _MIN_DIM_FOR_FULL_LABEL) else short_label
|
||||
|
||||
# Measure text
|
||||
try:
|
||||
bbox = ld.textbbox((0, 0), label, font=font)
|
||||
text_w = bbox[2] - bbox[0]
|
||||
text_h = bbox[3] - bbox[1]
|
||||
except AttributeError:
|
||||
# Pillow < 8.0.0
|
||||
text_w, text_h = ld.textsize(label, font=font) # type: ignore[attr-defined]
|
||||
|
||||
badge_w = text_w + _PAD_H * 2
|
||||
badge_h = text_h + _PAD_V * 2
|
||||
bx = rx + _LABEL_INSET
|
||||
by = ry + _LABEL_INSET
|
||||
|
||||
# Ensure label fits within the rectangle
|
||||
if bx + badge_w > rx + rw or by + badge_h > ry + rh:
|
||||
label = short_label
|
||||
try:
|
||||
bbox = ld.textbbox((0, 0), label, font=font)
|
||||
text_w = bbox[2] - bbox[0]
|
||||
text_h = bbox[3] - bbox[1]
|
||||
except AttributeError:
|
||||
text_w, text_h = ld.textsize(label, font=font) # type: ignore[attr-defined]
|
||||
badge_w = text_w + _PAD_H * 2
|
||||
badge_h = text_h + _PAD_V * 2
|
||||
|
||||
bg_a = int(_LABEL_BG_ALPHA * 255)
|
||||
try:
|
||||
ld.rounded_rectangle(
|
||||
[bx, by, bx + badge_w, by + badge_h],
|
||||
radius=3,
|
||||
fill=(0, 0, 0, bg_a),
|
||||
)
|
||||
except AttributeError:
|
||||
ld.rectangle([bx, by, bx + badge_w, by + badge_h], fill=(0, 0, 0, bg_a))
|
||||
|
||||
txt_a = int(_LABEL_TEXT_ALPHA * 255)
|
||||
ld.text((bx + _PAD_H, by + _PAD_V), label, font=font, fill=(255, 255, 255, txt_a))
|
||||
|
||||
img = Image.alpha_composite(img, labels_layer)
|
||||
|
||||
# ---- outer border ----
|
||||
outer = ImageDraw.Draw(img)
|
||||
ob_a = int(_BORDER_ALPHA * 255)
|
||||
outer.rectangle([0, 0, w - 1, h - 1], outline=_BORDER_COLOR + (ob_a,), width=1)
|
||||
|
||||
# ---- convert to float32 RGB tensor [1, H, W, 3] ----
|
||||
arr = np.array(img.convert("RGB")).astype(np.float32) / 255.0
|
||||
return torch.from_numpy(arr).unsqueeze(0)
|
||||
|
||||
|
||||
class ConditioningAreaPreview:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"areas": ("CONDITIONING_AREAS",),
|
||||
"width": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 8}),
|
||||
"height": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 8}),
|
||||
}
|
||||
}
|
||||
|
||||
NODE_ID = "ConditioningAreaPreview"
|
||||
NODE_NAME = "Conditioning Area Preview"
|
||||
CATEGORY = "LoRA Pipeline/Conditioning"
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("preview",)
|
||||
FUNCTION = "run"
|
||||
|
||||
def run(self, areas, width, height):
|
||||
if not isinstance(areas, list) or len(areas) == 0:
|
||||
# Return an empty dark canvas
|
||||
empty = torch.zeros((1, int(height), int(width), 3), dtype=torch.float32)
|
||||
return (empty,)
|
||||
|
||||
tensor = _render_area_preview(areas, width, height)
|
||||
return (tensor,)
|
||||
+2
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "comfyui-lora-pipeline"
|
||||
version = "1.0.0"
|
||||
version = "1.1.2"
|
||||
description = "Area-based LoRA scheduling and area conditioning wrappers for ComfyUI."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
@@ -20,3 +20,4 @@ Documentation = "https://github.com/andreszs/comfyui-lora-pipeline#readme"
|
||||
PublisherId = "andreszs"
|
||||
DisplayName = "LoRA Pipeline"
|
||||
Icon = ""
|
||||
Banner = "https://raw.githubusercontent.com/andreszs/comfyui-lora-pipeline/refs/heads/main/assets/banner_112.gif"
|
||||
|
||||
Reference in New Issue
Block a user