Skip to content

Commit 1746e42

Browse files
committed
add the VolcanoImageEditNode.
1 parent 62a5ce7 commit 1746e42

File tree

3 files changed

+128
-61
lines changed

3 files changed

+128
-61
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,14 @@ This node is designed to concatenate the input one batch images to a grid. It ca
4141

4242
## VolcanoOutpainting
4343
This node is designed to outpaint the input image using the Volcano engine.
44-
4544
use this node, must get your free API key from Volcano engine:
4645
- Visit [Volcano engine](https://console.volcengine.com/)
4746
- Log in with your Volcano engine account
4847
- Click on "访问控制" or go to [settings](https://console.volcengine.com/iam/keymanage)
4948
- Create a new API key
5049
- Copy the API key for use in the node's input
50+
## VolcanoImageEdit
51+
This node is designed to edit the input image using the Volcano engine.
5152

5253
## ModifyTextGender
5354
This node adjusts the text to describe the gender based on the input. If the gender input is 'M', the text will be adjusted to describe as male; if the gender input is 'F', it will be adjusted to describe as female.

py/node_volcano.py

Lines changed: 124 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -17,46 +17,37 @@
1717

1818
logger = logging.getLogger(__name__)
1919

20-
class VolcanoOutpaintingNode:
21-
@classmethod
22-
def INPUT_TYPES(s):
23-
return {
24-
"required": {
25-
"image": ("IMAGE",),
26-
"mask": ("MASK",),
27-
"ak": ("STRING", {"default": ""}),
28-
"sk": ("STRING", {"default": ""}),
29-
"seed": ("INT", {"default": 42, "min": 0, "max": 999999})
30-
}
31-
}
32-
33-
RETURN_TYPES = ("IMAGE",)
34-
RETURN_NAMES = ("result_image",)
35-
FUNCTION = "process_outpainting"
36-
CATEGORY = "image/volcano"
37-
20+
class VolcanoBaseNode:
21+
"""火山引擎视觉服务基类"""
22+
23+
def __init__(self):
24+
self.visual_service = VisualService()
25+
3826
def save_config(self, config):
27+
"""保存配置到文件"""
3928
config_path = os.path.join(config_dir, 'volcano_config.yml')
4029
with open(config_path, 'w', encoding='utf-8') as f:
4130
yaml.dump(config, f, indent=4)
4231

4332
def load_config(self):
33+
"""从文件加载配置"""
4434
config_path = os.path.join(config_dir, 'volcano_config.yml')
45-
with open(config_path, 'r', encoding='utf-8') as f:
46-
config = yaml.load(f, Loader=yaml.FullLoader)
47-
return config
35+
try:
36+
with open(config_path, 'r', encoding='utf-8') as f:
37+
config = yaml.load(f, Loader=yaml.FullLoader)
38+
return config
39+
except FileNotFoundError:
40+
return {}
4841

4942
def pil_to_base64(self, pil_image):
5043
"""将PIL图像转换为base64字符串"""
5144
buffer = io.BytesIO()
5245
pil_image.save(buffer, format='PNG')
5346
img_data = buffer.getvalue()
5447
return base64.b64encode(img_data).decode('utf-8')
55-
56-
def request_valcanic_outpainting(self, req_key, ak, sk, image_base64s, seed=42, top=0, left=0, bottom=0, right=0):
57-
"""调用火山引擎图像扩展API"""
58-
visual_service = VisualService()
59-
48+
49+
def setup_credentials(self, ak, sk):
50+
"""设置API凭证"""
6051
if ak and sk:
6152
self.save_config({"ak": ak, "sk": sk})
6253
else:
@@ -65,34 +56,49 @@ def request_valcanic_outpainting(self, req_key, ak, sk, image_base64s, seed=42,
6556
sk = config.get("sk")
6657
if not ak or not sk:
6758
raise Exception("volcano engine ak or sk not found")
68-
69-
visual_service.set_ak(ak)
70-
visual_service.set_sk(sk)
71-
72-
# 请求参数
73-
form = {
74-
"req_key": req_key,
75-
"binary_data_base64": image_base64s,
76-
"top": top,
77-
"left": left,
78-
"bottom": bottom,
79-
"right": right,
80-
"seed": seed
81-
}
82-
83-
resp = visual_service.cv_process(form)
59+
60+
self.visual_service.set_ak(ak)
61+
self.visual_service.set_sk(sk)
62+
return ak, sk
63+
64+
def request_volcano_api(self, ak, sk, form_data):
65+
"""通用的火山引擎API请求方法"""
66+
# 设置凭证
67+
self.setup_credentials(ak, sk)
68+
69+
# 发送请求
70+
resp = self.visual_service.cv_process(form_data)
8471

8572
# 解码返回的图像
8673
img_base64 = resp['data']['binary_data_base64'][0]
8774
img_data = base64.b64decode(img_base64)
88-
# 转换为PIL图像
8975
result_image = Image.open(io.BytesIO(img_data))
76+
77+
# 删除图片base64,方便print
78+
resp['data']['binary_data_base64'][0] = ""
79+
logger.debug(f"volcano api response: {resp}")
80+
81+
return result_image, resp
9082

91-
# 删除图片base64, 方便print
92-
resp['data']['binary_data_base64'][0] =""
83+
class VolcanoOutpaintingNode(VolcanoBaseNode):
84+
"""火山引擎图像扩展节点"""
85+
86+
@classmethod
87+
def INPUT_TYPES(s):
88+
return {
89+
"required": {
90+
"image": ("IMAGE",),
91+
"mask": ("MASK",),
92+
"ak": ("STRING", {"default": ""}),
93+
"sk": ("STRING", {"default": ""}),
94+
"seed": ("INT", {"default": 42, "min": 0, "max": 999999})
95+
}
96+
}
9397

94-
logger.debug(f"volcano outpainting response: {resp}")
95-
return result_image, resp
98+
RETURN_TYPES = ("IMAGE",)
99+
RETURN_NAMES = ("result_image",)
100+
FUNCTION = "process_outpainting"
101+
CATEGORY = "image/volcano"
96102

97103
def process_outpainting(self, image, mask, ak, sk, seed):
98104
try:
@@ -104,18 +110,23 @@ def process_outpainting(self, image, mask, ak, sk, seed):
104110
if pil_mask.mode != 'L':
105111
pil_mask = pil_mask.convert('L')
106112

107-
# 直接转换为base64
113+
# 转换为base64
108114
image_base64 = self.pil_to_base64(pil_image)
109115
mask_base64 = self.pil_to_base64(pil_mask)
110116

111-
# 调用API
112-
result_image, _ = self.request_valcanic_outpainting(
113-
req_key="i2i_outpainting",
114-
ak=ak,
115-
sk=sk,
116-
image_base64s=[image_base64, mask_base64],
117-
seed=seed
118-
)
117+
# 构建请求参数
118+
form_data = {
119+
"req_key": "i2i_outpainting",
120+
"binary_data_base64": [image_base64, mask_base64],
121+
"top": 0,
122+
"left": 0,
123+
"bottom": 0,
124+
"right": 0,
125+
"seed": seed
126+
}
127+
128+
# 调用基类的通用API请求方法
129+
result_image, _ = self.request_volcano_api(ak, sk, form_data)
119130

120131
# 使用节点库的转换函数转换结果为tensor
121132
result_tensor = pil2tensor(result_image)
@@ -127,11 +138,66 @@ def process_outpainting(self, image, mask, ak, sk, seed):
127138
# 返回原图作为fallback
128139
return (image,)
129140

141+
class VolcanoImageEditNode(VolcanoBaseNode):
142+
"""火山引擎图像编辑节点"""
143+
144+
@classmethod
145+
def INPUT_TYPES(s):
146+
return {
147+
"required": {
148+
"image": ("IMAGE",),
149+
"prompt": ("STRING", {"multiline": True, "default": ""}),
150+
"ak": ("STRING", {"default": ""}),
151+
"sk": ("STRING", {"default": ""}),
152+
"seed": ("INT", {"default": 42, "min": 0, "max": 0xffffffffffffffff}),
153+
"scale": ("FLOAT", {"default": 0.5, "min": 0.1, "max": 1.0, "step": 0.01})
154+
},
155+
"optional": {
156+
}
157+
}
158+
159+
RETURN_TYPES = ("IMAGE",)
160+
RETURN_NAMES = ("edited_image",)
161+
FUNCTION = "process_image_edit"
162+
CATEGORY = "image/volcano"
163+
164+
def process_image_edit(self, image, prompt, ak, sk, seed, scale=0.5):
165+
try:
166+
# 使用节点库的转换函数
167+
pil_image = tensor2pil(image)
168+
169+
# 转换为base64
170+
image_base64 = self.pil_to_base64(pil_image)
171+
binary_data = [image_base64]
172+
# 构建请求参数
173+
form_data = {
174+
"req_key": "byteedit_v2.0",
175+
"binary_data_base64": binary_data,
176+
"prompt": prompt,
177+
"seed": seed,
178+
"scale": scale
179+
}
180+
181+
# 调用基类的通用API请求方法
182+
result_image, _ = self.request_volcano_api(ak, sk, form_data)
183+
184+
# 使用节点库的转换函数转换结果为tensor
185+
result_tensor = pil2tensor(result_image)
186+
187+
return (result_tensor,)
188+
189+
except Exception as e:
190+
logger.exception(f"图像编辑处理失败: {e}")
191+
# 返回原图作为fallback
192+
return (image,)
193+
130194
# 节点映射
131195
NODE_CLASS_MAPPINGS = {
132-
"VolcanoOutpaintingNode": VolcanoOutpaintingNode
196+
"VolcanoOutpaintingNode": VolcanoOutpaintingNode,
197+
"VolcanoImageEditNode": VolcanoImageEditNode
133198
}
134199

135200
NODE_DISPLAY_NAME_MAPPINGS = {
136-
"VolcanoOutpaintingNode": "volcano outpainting"
201+
"VolcanoOutpaintingNode": "volcano outpainting",
202+
"VolcanoImageEditNode": "volcano image edit"
137203
}

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "comfyui-utils-nodes"
3-
description = "Nodes:LoadImageWithSwitch, ImageBatchOneOrMore, ModifyTextGender, GenderControlOutput, ImageCompositeMaskedWithSwitch, ImageCompositeMaskedOneByOne, ColorCorrectOfUtils, SplitMask, MaskFastGrow, CheckpointLoaderSimpleWithSwitch, ImageResizeTo8x, MatchImageRatioToPreset, UpscaleImageWithModelIfNeed, MaskFromFaceModel, MaskCoverFourCorners, DetectorForNSFW, DeepfaceAnalyzeFaceAttributes etc."
4-
version = "1.3.1"
3+
description = "Nodes:LoadImageWithSwitch, ImageBatchOneOrMore, ModifyTextGender, GenderControlOutput, ImageCompositeMaskedWithSwitch, ImageCompositeMaskedOneByOne, ColorCorrectOfUtils, SplitMask, MaskFastGrow, CheckpointLoaderSimpleWithSwitch, ImageResizeTo8x, MatchImageRatioToPreset, MaskFromFaceModel, MaskCoverFourCorners, DetectorForNSFW, DeepfaceAnalyzeFaceAttributes, VolcanoOutpainting, VolcanoImageEdit, etc."
4+
version = "1.3.2"
55
license = { file = "LICENSE" }
66
dependencies = []
77

0 commit comments

Comments
 (0)