|
import gradio as gr |
|
import numpy as np |
|
import requests |
|
from PIL import Image |
|
from io import BytesIO |
|
import time |
|
|
|
MAX_SEED = np.iinfo(np.int32).max |
|
MAX_IMAGE_SIZE = 1344 |
|
|
|
title = """ |
|
# Stability AI - Developer Platform WebUI |
|
### UI for using the stable image api |
|
|
|
API Key is required to use this service. |
|
https://platform.stability.ai/account/keys |
|
|
|
Contact: D̷ELL@Stability AI - Advocate (https://x.com/xqdior) / Author: umise (https://x.com/UiE029) |
|
""" |
|
|
|
title_jp = """ |
|
# Stability AI - Developer Platform WebUI |
|
### このSpaceは、Stable Image APIを使用するためのWEB-UIです。 |
|
|
|
このサービスを利用するにはAPIキーが必要です。以下のリンクから取得してください。 |
|
https://platform.stability.ai/account/keys |
|
|
|
お問い合わせ先: D̷ELL@Stability AI - Advocate (https://x.com/xqdior) / Author: umise (https://x.com/UiE029) |
|
""" |
|
|
|
overview = """ |
|
|
|
**Overview** |
|
Stability AI’s Stable Image services offer a growing set of APIs for developers to build the best in class image applications. |
|
|
|
- **Disrupting Content Creation:** Stability’s Image APIs are the foundation for applications disrupting publishing, media, gaming, marketing, advertising, design, and more. |
|
- **For Developers:** Application developers can build advanced features for designers, photographers, content creators, and a variety of B2C customers. |
|
- **Simple APIs:** Stability AI is focused on delivering simple APIs for easy integration into applications with a high bar for quality, alignment, speed, and safety. |
|
|
|
Get Started Now: https://platform.stability.ai/docs/getting-started/stable-image |
|
""" |
|
|
|
overview_jp = """ |
|
各種モードについて |
|
- テキストから生成: |
|
プロンプトを基に画像を生成します。 |
|
- アップスケール: |
|
- 通常のアップスケール: |
|
できるだけ絵を変更せずにアップスケールします |
|
- クリエィティブなアップスケール: |
|
絵全体をいい感じになるようにアップスケールします |
|
- 画像の編集: |
|
- 消去: |
|
マスクした要素を削除します |
|
- インペイント: |
|
マスクされた部分とプロンプトを基に再生成します |
|
- アウトペイント: |
|
指定した範囲分、画像を拡張します |
|
- 検索と置き換え: |
|
検索プロンプトに入力されたオブジェクトを探し、プロンプトをもとにオブジェクトを再生成します |
|
- コントロール: |
|
- 構図: |
|
入力画像の構図を基に新しい画像を生成します |
|
- スケッチ: |
|
ラフなスケッチとプロンプトを基に生成します |
|
""" |
|
|
|
model_url = { |
|
"ImageUltra": "https://api.stability.ai/v2beta/stable-image/generate/ultra", |
|
"ImageCore": "https://api.stability.ai/v2beta/stable-image/generate/core", |
|
"StableDiffusion3": "https://api.stability.ai/v2beta/stable-image/generate/sd3", |
|
} |
|
|
|
service_url = { |
|
"Conservative_Upscale": "https://api.stability.ai/v2beta/stable-image/upscale/conservative", |
|
"Creative_Upscale": "https://api.stability.ai/v2beta/stable-image/upscale/creative", |
|
"Erase": "https://api.stability.ai/v2beta/stable-image/edit/erase", |
|
"Inpaint": "https://api.stability.ai/v2beta/stable-image/edit/inpaint", |
|
"Outpaint": "https://api.stability.ai/v2beta/stable-image/edit/outpaint", |
|
"SR": "https://api.stability.ai/v2beta/stable-image/edit/search-and-replace", |
|
"RMBG": "https://api.stability.ai/v2beta/stable-image/edit/remove-background", |
|
"Sketch": "https://api.stability.ai/v2beta/stable-image/control/sketch", |
|
"Structure": "https://api.stability.ai/v2beta/stable-image/control/structure", |
|
} |
|
|
|
|
|
translations = { |
|
"en": { |
|
"api_key": "API Key", |
|
"api_key_placeholder": "Enter your API key", |
|
"model_label": "Model", |
|
"mode_label": "Mode", |
|
"prompt_placeholder": "Enter your prompt", |
|
"negative_prompt_placeholder": "Enter a negative prompt", |
|
"seed_label": "Seed", |
|
"randomize_seed_label": "Randomize seed", |
|
"aspect_label": "Aspect ratio", |
|
"run_button": "Run", |
|
"result_label": "Result", |
|
"copy_field_placeholder": "Copy the field", |
|
"Negative_prompt": "Negative prompt", |
|
"Advanced_Settings": "Advanced Settings", |
|
"Example": "Example", |
|
"Generate": "Generate", |
|
"Upscale": "Upscale", |
|
"Edit": "Edit", |
|
"Control": "Control", |
|
"Submode": "Submode", |
|
"Conservative": "Conservative", |
|
"Creative": "Creative", |
|
"Erase": "Erase", |
|
"Inpaint": "Inpaint", |
|
"Outpaint": "Outpaint", |
|
"Structure": "Structure", |
|
"Sketch": "Sketch", |
|
"Search_and_Replace": "Search and Replace", |
|
"Remove_Background": "Remove Background", |
|
"input_image": "Input Image", |
|
"style_preset": "Style preset", |
|
"preset_description": "This parameter is only available for ImageCore model.", |
|
"Search_prompt_placeholder": "Enter a search prompt", |
|
"Control_Strength": "Control Strength", |
|
"overview": overview, |
|
"overview_label": "How to use", |
|
"title": title, |
|
}, |
|
"ja": { |
|
"api_key": "APIキー", |
|
"api_key_placeholder": "APIキーを入力してください", |
|
"model_label": "モデル", |
|
"mode_label": "モード", |
|
"prompt_placeholder": "プロンプトを入力してください", |
|
"negative_prompt_placeholder": "ネガティブプロンプトを入力してください", |
|
"seed_label": "シード", |
|
"randomize_seed_label": "シードをランダム化", |
|
"aspect_label": "アスペクト比", |
|
"run_button": "実行", |
|
"result_label": "結果", |
|
"copy_field_placeholder": "ここに貼り付け用の情報が出てきます", |
|
"Negative_prompt": "ネガティブプロンプト", |
|
"Advanced_Settings": "追加設定", |
|
"Example": "例", |
|
"Generate": "テキストから生成", |
|
"Upscale": "アップスケール", |
|
"Edit": "画像の編集", |
|
"Control": "コントロールモード", |
|
"Submode": "サブモード", |
|
"Conservative": "通常のアップスケール", |
|
"Creative": "クリエィティブなアップスケール", |
|
"Erase": "消去", |
|
"Inpaint": "インペイント", |
|
"Outpaint": "アウトペイント(拡張)", |
|
"Structure": "構図", |
|
"Sketch": "スケッチ", |
|
"Search_and_Replace": "検索と置き換え", |
|
"Remove_Background": "背景削除", |
|
"input_image": "入力画像", |
|
"style_preset": "スタイルのプリセット", |
|
"preset_description": "このパラメータはimage coreのときにだけ有効になります", |
|
"Search_prompt_placeholder": "探したい要素を入力してください", |
|
"Control_Strength": "コントロールネットの適用強度", |
|
"overview": overview_jp, |
|
"overview_label": "使い方", |
|
"title": title_jp, |
|
}, |
|
} |
|
|
|
lang = "ja" |
|
|
|
|
|
def bytes_to_image(image): |
|
image = BytesIO(image) |
|
image = Image.open(image).convert("RGB") |
|
return image |
|
|
|
|
|
def image_to_bytes(image): |
|
byte_io = BytesIO() |
|
image.save(byte_io, format="PNG") |
|
byte_data = byte_io.getvalue() |
|
return byte_data |
|
|
|
|
|
def send_request(url, api_key, file, data): |
|
response = requests.post( |
|
url, |
|
headers={"Authorization": f"Bearer {api_key}", "Accept": "image/*"}, |
|
files=file, |
|
data=data, |
|
) |
|
return response |
|
|
|
|
|
def generate( |
|
prompt, |
|
negative_prompt, |
|
seed, |
|
mode, |
|
submode, |
|
input_image, |
|
mask, |
|
CNstrength, |
|
search_prompt, |
|
op_left, |
|
op_right, |
|
op_up, |
|
op_down, |
|
randomize_seed, |
|
aspect, |
|
model, |
|
preset, |
|
api_key, |
|
): |
|
if randomize_seed: |
|
seed = 0 |
|
|
|
file = {} |
|
data = { |
|
"prompt": prompt, |
|
"negative_prompt": negative_prompt, |
|
"output_format": "png", |
|
"seed": seed, |
|
"aspect_ratio": aspect, |
|
} |
|
data_rmbg = { |
|
"output_format": "png", |
|
} |
|
if input_image is not None: |
|
file["image"] = image_to_bytes(input_image) |
|
if mask is not None: |
|
file["mask"] = image_to_bytes(mask) |
|
|
|
if mode == translations[lang]["Generate"]: |
|
file["none"] = "" |
|
if model == "Stable Image Ultra (8B + workflow)": |
|
url = model_url["ImageUltra"] |
|
elif model == "Stable Image Core (2B + workflow)": |
|
url = model_url["ImageCore"] |
|
data["style_preset"] = preset |
|
elif model == "Stable Diffusion 3 Medium (2B)": |
|
url = model_url["StableDiffusion3"] |
|
data["model"] = "sd3-medium" |
|
elif model == "Stable Diffusion 3 Large (8B)": |
|
url = model_url["StableDiffusion3"] |
|
data["model"] = "sd3-large" |
|
elif model == "Stable Diffusion 3 Large Turbo (8B Turbo)": |
|
url = model_url["StableDiffusion3"] |
|
data["model"] = "sd3-large-turbo" |
|
else: |
|
raise ValueError("Invalid model type") |
|
|
|
elif mode == translations[lang]["Upscale"]: |
|
if submode == translations[lang]["Conservative"]: |
|
url = service_url["Conservative_Upscale"] |
|
elif submode == translations[lang]["Creative"]: |
|
url = service_url["Creative_Upscale"] |
|
|
|
elif mode == translations[lang]["Edit"]: |
|
if submode == translations[lang]["Erase"]: |
|
url = service_url["Erase"] |
|
elif submode == translations[lang]["Inpaint"]: |
|
url = service_url["Inpaint"] |
|
elif submode == translations[lang]["Outpaint"]: |
|
url = service_url["Outpaint"] |
|
data["left"] = op_left |
|
data["right"] = op_right |
|
data["up"] = op_up |
|
data["down"] = op_down |
|
elif submode == translations[lang]["Search_and_Replace"]: |
|
url = service_url["SR"] |
|
data["search_prompt"] = search_prompt |
|
elif submode == translations[lang]["Remove_Background"]: |
|
data = data_rmbg |
|
url = service_url["RMBG"] |
|
|
|
elif mode == translations[lang]["Control"]: |
|
data["control_strength"] = CNstrength |
|
if submode == translations[lang]["Sketch"]: |
|
url = service_url["Sketch"] |
|
elif submode == translations[lang]["Structure"]: |
|
url = service_url["Structure"] |
|
response = send_request(url, api_key, file, data) |
|
|
|
if response.status_code == 200: |
|
if ( |
|
mode == translations[lang]["Upscale"] |
|
and submode == translations[lang]["Creative"] |
|
): |
|
generation_id = response.json().get("id") |
|
if not generation_id: |
|
raise Exception("No generation ID returned for creative upscale") |
|
|
|
|
|
result_url = f"https://api.stability.ai/v2beta/stable-image/upscale/creative/result/{generation_id}" |
|
while True: |
|
result_response = requests.get( |
|
result_url, |
|
headers={"accept": "image/*", "authorization": f"Bearer {api_key}"}, |
|
) |
|
if result_response.status_code == 202: |
|
print("Generation in-progress, try again in 10 seconds.") |
|
time.sleep(10) |
|
elif result_response.status_code == 200: |
|
print("Generation complete!") |
|
image = result_response.content |
|
image = bytes_to_image(image) |
|
copy_filed_value = f"prompt:{prompt}, negative:{negative_prompt}, mode:{mode}, submode:{submode}" |
|
return image, seed, copy_filed_value |
|
else: |
|
raise Exception(str(result_response.json())) |
|
else: |
|
image = response.content |
|
image = bytes_to_image(image) |
|
copy_filed_value = f"prompt:{prompt}, negative:{negative_prompt}, mode:{mode}, submode:{submode}" |
|
return image, seed, copy_filed_value |
|
else: |
|
raise Exception(str(response.json())) |
|
|
|
|
|
examples = [ |
|
"Astronaut in a jungle, cold color palette, muted colors, detailed, 8k", |
|
"An astronaut riding a green horse", |
|
"A delicious ceviche cheesecake slice", |
|
] |
|
|
|
css = """ |
|
#col-container { |
|
margin: 0 auto; |
|
max-width: 50vw; |
|
} |
|
""" |
|
|
|
|
|
def update_style_visibility(model): |
|
if model == "Stable Image Core (2B + workflow)": |
|
return gr.update(visible=True) |
|
else: |
|
return gr.update(visible=False) |
|
|
|
|
|
def update_mode(mode): |
|
submode_update = gr.update(choices=["None"], visible=False) |
|
image_label_update = gr.update(visible=False) |
|
img_input_update = gr.update(visible=False) |
|
mask_update = gr.update(visible=False) |
|
|
|
if mode == translations[lang]["Generate"]: |
|
submode_update = gr.update(visible=False) |
|
elif mode == translations[lang]["Upscale"]: |
|
submode_update = gr.update( |
|
choices=[ |
|
translations[lang]["Conservative"], |
|
translations[lang]["Creative"], |
|
], |
|
value=translations[lang]["Conservative"], |
|
visible=True, |
|
) |
|
img_input_update = gr.update(visible=True) |
|
image_label_update = gr.update(visible=True) |
|
elif mode == translations[lang]["Edit"]: |
|
submode_update = gr.update( |
|
choices=[ |
|
translations[lang]["Erase"], |
|
translations[lang]["Inpaint"], |
|
translations[lang]["Outpaint"], |
|
translations[lang]["Search_and_Replace"], |
|
translations[lang]["Remove_Background"], |
|
], |
|
value=translations[lang]["Erase"], |
|
visible=True, |
|
) |
|
img_input_update = gr.update(visible=True) |
|
image_label_update = gr.update(visible=True) |
|
elif mode == translations[lang]["Control"]: |
|
submode_update = gr.update( |
|
choices=[ |
|
translations[lang]["Structure"], |
|
translations[lang]["Sketch"], |
|
], |
|
value=translations[lang]["Structure"], |
|
visible=True, |
|
) |
|
img_input_update = gr.update(visible=True) |
|
image_label_update = gr.update(visible=True) |
|
|
|
return submode_update, img_input_update, mask_update, image_label_update |
|
|
|
|
|
def update_submode(submode): |
|
mask = gr.update(visible=False) |
|
outpaint = gr.update(visible=False) |
|
cn = gr.update(visible=False) |
|
search_prompt = gr.update(visible=False) |
|
|
|
if submode in [translations[lang]["Erase"], translations[lang]["Inpaint"]]: |
|
mask = gr.update(visible=True) |
|
|
|
else: |
|
if submode == translations[lang]["Outpaint"]: |
|
outpaint = gr.update(visible=True) |
|
|
|
elif submode in [translations[lang]["Structure"], translations[lang]["Sketch"]]: |
|
cn = gr.update(visible=True) |
|
|
|
elif submode == translations[lang]["Search_and_Replace"]: |
|
search_prompt = gr.update(visible=True) |
|
|
|
return mask, outpaint, cn, search_prompt |
|
|
|
|
|
with gr.Blocks(css=css, theme="NoCrypt/miku") as demo: |
|
with gr.Column(elem_id="col-container"): |
|
gr.Markdown( |
|
translations[lang]["title"], |
|
) |
|
with gr.Accordion(translations[lang]["overview_label"], open=False): |
|
gr.Markdown( |
|
translations[lang]["overview"], |
|
) |
|
|
|
with gr.Row(): |
|
api_key = gr.Text( |
|
label=translations[lang]["api_key"], |
|
type="password", |
|
placeholder=translations[lang]["api_key_placeholder"], |
|
max_lines=1, |
|
container=False, |
|
) |
|
|
|
with gr.Row(): |
|
model = gr.Dropdown( |
|
label=translations[lang]["model_label"], |
|
choices=[ |
|
"Stable Image Ultra (8B + workflow)", |
|
"Stable Image Core (2B + workflow)", |
|
"Stable Diffusion 3 Large Turbo (8B Turbo)", |
|
"Stable Diffusion 3 Large (8B)", |
|
"Stable Diffusion 3 Medium (2B)", |
|
], |
|
value="Stable Image Ultra (8B + workflow)", |
|
) |
|
mode = gr.Dropdown( |
|
label=translations[lang]["mode_label"], |
|
choices=[ |
|
translations[lang]["Generate"], |
|
translations[lang]["Upscale"], |
|
translations[lang]["Edit"], |
|
translations[lang]["Control"], |
|
], |
|
value=translations[lang]["Generate"], |
|
) |
|
|
|
submode = gr.Dropdown( |
|
label=translations[lang]["Submode"], |
|
choices=["None"], |
|
visible=False, |
|
value="None", |
|
) |
|
|
|
with gr.Row(): |
|
with gr.Column(): |
|
prompt = gr.Text( |
|
label="Prompt", |
|
show_label=False, |
|
max_lines=1, |
|
placeholder=translations[lang]["prompt_placeholder"], |
|
container=False, |
|
) |
|
search_prompt = gr.Text( |
|
label="search prompt", |
|
visible=False, |
|
show_label=False, |
|
max_lines=1, |
|
placeholder=translations[lang]["Search_prompt_placeholder"], |
|
) |
|
run_button = gr.Button(translations[lang]["run_button"], scale=0) |
|
with gr.Row(): |
|
gr.Examples( |
|
label=translations[lang]["Example"], examples=examples, inputs=[prompt] |
|
) |
|
with gr.Row(): |
|
with gr.Column(): |
|
image_label = gr.Markdown( |
|
value=translations[lang]["input_image"], visible=False |
|
) |
|
image = gr.Image( |
|
type="pil", |
|
label="img input", |
|
width="20vw", |
|
height="20vw", |
|
show_label=True, |
|
visible=False, |
|
interactive=True, |
|
container=False, |
|
) |
|
with gr.Column(visible=False) as mask: |
|
mask_label = gr.Markdown(value="input mask") |
|
mask_input = gr.Image( |
|
type="pil", |
|
label="mask", |
|
width="20vw", |
|
height="20vw", |
|
show_label=True, |
|
interactive=True, |
|
container=False, |
|
) |
|
|
|
with gr.Row(): |
|
result = gr.Image( |
|
label=translations[lang]["result_label"], width="20vw", height="20%" |
|
) |
|
|
|
with gr.Accordion(translations[lang]["Advanced_Settings"], open=False): |
|
negative_prompt = gr.Text( |
|
label=translations[lang]["Negative_prompt"], |
|
max_lines=1, |
|
placeholder=translations[lang]["negative_prompt_placeholder"], |
|
) |
|
seed = gr.Slider( |
|
label=translations[lang]["seed_label"], |
|
minimum=0, |
|
maximum=MAX_SEED, |
|
step=1, |
|
value=0, |
|
) |
|
CN_strength = gr.Slider( |
|
label=translations[lang]["Control_Strength"], |
|
minimum=0, |
|
maximum=1, |
|
step=0.01, |
|
value=0.5, |
|
visible=False, |
|
) |
|
randomize_seed = gr.Checkbox( |
|
label=translations[lang]["randomize_seed_label"], value=True |
|
) |
|
aspect = gr.Radio( |
|
choices=[ |
|
"1:1", |
|
"16:9", |
|
"21:9", |
|
"2:3", |
|
"3:2", |
|
"4:5", |
|
"5:4", |
|
"9:16", |
|
"9:21", |
|
], |
|
label=translations[lang]["aspect_label"], |
|
value="1:1", |
|
) |
|
with gr.Row(visible=False) as style: |
|
style_preset = gr.Radio( |
|
choices=[ |
|
"3d-model", |
|
"analog-film", |
|
"anime", |
|
"cinematic", |
|
"comic-book", |
|
"digital-art", |
|
"enhance", |
|
"fantasy-art", |
|
"isometric", |
|
"line-art", |
|
"low-poly", |
|
"modeling-compound", |
|
"neon-punk", |
|
"origami", |
|
"photographic", |
|
"pixel-art", |
|
"tile-texture", |
|
], |
|
label=translations[lang]["style_preset"], |
|
value="anime", |
|
info=translations[lang]["preset_description"], |
|
) |
|
with gr.Row(visible=False) as outpaint_scale: |
|
paint = gr.Markdown(value="Outpain Scale") |
|
op_left = gr.Slider( |
|
label="left", minimum=0, maximum=2000, step=4, value=200 |
|
) |
|
op_right = gr.Slider( |
|
label="right", minimum=0, maximum=2000, step=4, value=200 |
|
) |
|
op_up = gr.Slider( |
|
label="up", minimum=0, maximum=2000, step=4, value=200 |
|
) |
|
op_down = gr.Slider( |
|
label="down", minimum=0, maximum=2000, step=4, value=200 |
|
) |
|
|
|
copy_filed = gr.TextArea( |
|
value="", |
|
label="Copy Field", |
|
max_lines=1, |
|
placeholder=translations[lang]["copy_field_placeholder"], |
|
show_copy_button=True, |
|
container=False, |
|
) |
|
gr.Markdown( |
|
f""" |
|
## License |
|
This work is licensed under a |
|
[Creative Commons Attribution-NonCommercial 4.0 International License][cc-by-nc]. |
|
|
|
[![CC BY-NC 4.0][cc-by-nc-image]][cc-by-nc] |
|
|
|
[cc-by-nc]: https://creativecommons.org/licenses/by-nc/4.0/ |
|
[cc-by-nc-image]: https://licensebuttons.net/l/by-nc/4.0/88x31.png |
|
[cc-by-nc-shield]: https://img.shields.io/badge/License-CC%20BY--NC%204.0-lightgrey.svg |
|
|
|
**MIT Licensed Source Code** |
|
Portions of this work are licensed under the MIT License. For more details, please refer to the original source at: [stabilityai/stable-diffusion-3-medium](https://huggingface.co/spaces/stabilityai/stable-diffusion-3-medium) |
|
""" |
|
) |
|
gr.on( |
|
triggers=[run_button.click, prompt.submit, negative_prompt.submit], |
|
fn=generate, |
|
inputs=[ |
|
prompt, |
|
negative_prompt, |
|
seed, |
|
mode, |
|
submode, |
|
image, |
|
mask_input, |
|
CN_strength, |
|
search_prompt, |
|
op_left, |
|
op_right, |
|
op_up, |
|
op_down, |
|
randomize_seed, |
|
aspect, |
|
model, |
|
style_preset, |
|
api_key, |
|
], |
|
outputs=[result, seed, copy_filed], |
|
) |
|
|
|
mode.change( |
|
fn=update_mode, inputs=mode, outputs=[submode, image, mask, image_label] |
|
) |
|
submode.change( |
|
fn=update_submode, |
|
inputs=submode, |
|
outputs=[mask, outpaint_scale, CN_strength, search_prompt], |
|
) |
|
model.change(fn=update_style_visibility, inputs=model, outputs=style) |
|
demo.launch() |
|
|