import os
import time
import random
import logging
from gradio.blocks import postprocess_update_dict
import numpy as np
from typing import Any, Dict, List, Optional, Union
import torch
from PIL import Image
import gradio as gr
from tempfile import NamedTemporaryFile
from diffusers import (
DiffusionPipeline,
AutoencoderTiny,
AutoencoderKL,
AutoPipelineForImage2Image,
FluxPipeline,
FlowMatchEulerDiscreteScheduler,
DPMSolverMultistepScheduler)
from huggingface_hub import (
hf_hub_download,
HfFileSystem,
ModelCard,
snapshot_download)
from diffusers.utils import load_image
from modules.version_info import (
versions_html,
#initialize_cuda,
#release_torch_resources,
#get_torch_info
)
from modules.image_utils import (
change_color,
open_image,
build_prerendered_images_by_quality,
upscale_image,
# lerp_imagemath,
# shrink_and_paste_on_blank,
show_lut,
apply_lut_to_image_path,
multiply_and_blend_images,
alpha_composite_with_control,
resize_and_crop_image,
convert_to_rgba_png,
get_image_from_dict
)
from modules.constants import (
LORA_DETAILS, LORAS as loras, MODELS,
default_lut_example_img,
lut_files,
MAX_SEED,
IS_SHARED_SPACE,
# lut_folder,cards,
# cards_alternating,
# card_colors,
# card_colors_alternating,
pre_rendered_maps_paths,
PROMPTS,
NEGATIVE_PROMPTS,
TARGET_SIZE,
temp_files,
load_env_vars,
dotenv_path
)
# from modules.excluded_colors import (
# add_color,
# delete_color,
# build_dataframe,
# on_input,
# excluded_color_list,
# on_color_display_select
# )
from modules.misc import (
get_filename,
convert_ratio_to_dimensions,
update_dimensions_on_ratio,
wait5
)
from modules.lora_details import (
approximate_token_count,
split_prompt_precisely,
upd_prompt_notes_by_index,
get_trigger_words_by_index
)
import spaces
input_image_palette = []
current_prerendered_image = gr.State("./images/Beeuty-1.png")
user_info = {
"username": "guest",
"session_hash": None,
"headers": None,
"client": None,
"query_params": None,
"path_params": None,
"level" : 0
}
user_profile = gr.State(None)
def get_profile() -> gr.OAuthProfile | None:
global user_profile
return user_profile
# Define a function to handle the login button click and retrieve user information.
def handle_login(profile: gr.OAuthProfile | None, request: gr.Request):
# Extract user information from the request
if profile is None:
profile = gr.State(None)
profile = get_profile()
user_info = {
"username": request.username,
"session_hash": request.session_hash,
"headers": dict(request.headers),
"client": request.client,
"query_params": dict(request.query_params),
"path_params": dict(request.path_params),
"level" : (0 if request.username == "guest" else 2)
}
return user_info, gr.update(logout_value=f"Logout {user_info['username']} ({user_info['level']})", value=f"Login {user_info['username']} ({user_info['level']})"), profile
#---if workspace = local or colab---
# Authenticate with Hugging Face
# from huggingface_hub import login
# Log in to Hugging Face using the provided token
# hf_token = 'hf-token-authentication'
# login(hf_token)
def calculate_shift(
image_seq_len,
base_seq_len: int = 256,
max_seq_len: int = 4096,
base_shift: float = 0.5,
max_shift: float = 1.16,
):
m = (max_shift - base_shift) / (max_seq_len - base_seq_len)
b = base_shift - m * base_seq_len
mu = image_seq_len * m + b
return mu
def retrieve_timesteps(
scheduler,
num_inference_steps: Optional[int] = None,
device: Optional[Union[str, torch.device]] = None,
timesteps: Optional[List[int]] = None,
sigmas: Optional[List[float]] = None,
**kwargs,
):
if timesteps is not None and sigmas is not None:
raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values")
if timesteps is not None:
scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
timesteps = scheduler.timesteps
num_inference_steps = len(timesteps)
elif sigmas is not None:
scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
timesteps = scheduler.timesteps
num_inference_steps = len(timesteps)
else:
scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
timesteps = scheduler.timesteps
return timesteps, num_inference_steps
@torch.inference_mode()
def flux_pipe_call_that_returns_an_iterable_of_images(
self,
prompt: Union[str, List[str]] = None,
prompt_2: Optional[Union[str, List[str]]] = None,
negative_prompt: Optional[Union[str, List[str]]] = None,
height: Optional[int] = None,
width: Optional[int] = None,
num_inference_steps: int = 28,
timesteps: List[int] = None,
guidance_scale: float = 3.5,
num_images_per_prompt: Optional[int] = 1,
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
latents: Optional[torch.FloatTensor] = None,
prompt_embeds: Optional[torch.FloatTensor] = None,
pooled_prompt_embeds: Optional[torch.FloatTensor] = None,
output_type: Optional[str] = "pil",
return_dict: bool = True,
joint_attention_kwargs: Optional[Dict[str, Any]] = None,
max_sequence_length: int = 512,
good_vae: Optional[Any] = None,
):
# Set default height and width
height = height or self.default_sample_size * self.vae_scale_factor
width = width or self.default_sample_size * self.vae_scale_factor
# Validate inputs
self.check_inputs(
prompt,
prompt_2,
height,
width,
prompt_embeds=prompt_embeds,
pooled_prompt_embeds=pooled_prompt_embeds,
max_sequence_length=max_sequence_length,
)
self._guidance_scale = guidance_scale
self._joint_attention_kwargs = joint_attention_kwargs
self._interrupt = False
batch_size = 1 if isinstance(prompt, str) else len(prompt)
device = self._execution_device
lora_scale = joint_attention_kwargs.get("scale", None) if joint_attention_kwargs is not None else None
# Encode the positive prompt
prompt_embeds_pos, pooled_prompt_embeds_pos, text_ids_pos = self.encode_prompt(
prompt=prompt,
prompt_2=prompt_2,
prompt_embeds=prompt_embeds,
pooled_prompt_embeds=pooled_prompt_embeds,
device=device,
num_images_per_prompt=num_images_per_prompt,
max_sequence_length=max_sequence_length,
lora_scale=lora_scale,
)
# Encode the negative prompt if provided
if negative_prompt is not None:
prompt_embeds_neg, pooled_prompt_embeds_neg, text_ids_neg = self.encode_prompt(
prompt=negative_prompt,
prompt_2=None, # Assuming no secondary prompt for negative
prompt_embeds=None,
pooled_prompt_embeds=None,
device=device,
num_images_per_prompt=num_images_per_prompt,
max_sequence_length=max_sequence_length,
lora_scale=lora_scale,
)
else:
# Fallback to positive embeddings if no negative prompt is provided
prompt_embeds_neg = prompt_embeds_pos
pooled_prompt_embeds_neg = pooled_prompt_embeds_pos
text_ids_neg = text_ids_pos
# Prepare latents
num_channels_latents = self.transformer.config.in_channels // 4
latents, latent_image_ids = self.prepare_latents(
batch_size * num_images_per_prompt,
num_channels_latents,
height,
width,
prompt_embeds_pos.dtype,
device,
generator,
latents,
)
# Set up timesteps
sigmas = np.linspace(1.0, 1 / num_inference_steps, num_inference_steps)
image_seq_len = latents.shape[1]
mu = calculate_shift(
image_seq_len,
self.scheduler.config.base_image_seq_len,
self.scheduler.config.max_image_seq_len,
self.scheduler.config.base_shift,
self.scheduler.config.max_shift,
)
timesteps, num_inference_steps = retrieve_timesteps(
self.scheduler,
num_inference_steps,
device,
timesteps,
sigmas,
mu=mu,
)
self._num_timesteps = len(timesteps)
guidance = (
torch.full([1], guidance_scale, device=device, dtype=torch.float32).expand(latents.shape[0])
if self.transformer.config.guidance_embeds
else None
)
# Denoising loop
for i, t in enumerate(timesteps):
if self._interrupt:
continue
timestep = t.expand(latents.shape[0]).to(latents.dtype)
print(f"Step {i + 1}/{num_inference_steps} - Timestep: {timestep.item()}\n")
# Compute noise prediction for positive prompt
noise_pred_pos = self.transformer(
hidden_states=latents,
timestep=timestep / 1000,
guidance=guidance,
pooled_projections=pooled_prompt_embeds_pos,
encoder_hidden_states=prompt_embeds_pos,
txt_ids=text_ids_pos,
img_ids=latent_image_ids,
joint_attention_kwargs=self.joint_attention_kwargs,
return_dict=False,
)[0]
# Compute noise prediction for negative prompt
noise_pred_neg = self.transformer(
hidden_states=latents,
timestep=timestep / 1000,
guidance=guidance,
pooled_projections=pooled_prompt_embeds_neg,
encoder_hidden_states=prompt_embeds_neg,
txt_ids=text_ids_neg,
img_ids=latent_image_ids,
joint_attention_kwargs=self.joint_attention_kwargs,
return_dict=False,
)[0]
# Combine noise predictions using guidance scale
noise_pred = noise_pred_neg + guidance_scale * (noise_pred_pos - noise_pred_neg)
# Generate intermediate image
latents_for_image = self._unpack_latents(latents, height, width, self.vae_scale_factor)
latents_for_image = (latents_for_image / self.vae.config.scaling_factor) + self.vae.config.shift_factor
image = self.vae.decode(latents_for_image, return_dict=False)[0]
yield self.image_processor.postprocess(image, output_type=output_type)[0]
# Update latents with combined noise prediction
latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0]
torch.cuda.empty_cache()
# Final image generation
latents = self._unpack_latents(latents, height, width, self.vae_scale_factor)
latents = (latents / good_vae.config.scaling_factor) + good_vae.config.shift_factor
image = good_vae.decode(latents, return_dict=False)[0]
self.maybe_free_model_hooks()
torch.cuda.empty_cache()
yield self.image_processor.postprocess(image, output_type=output_type)[0]
#--------------------------------------------------Model Initialization-----------------------------------------------------------------------------------------#
dtype = torch.bfloat16
device = "cuda" if torch.cuda.is_available() else "cpu"
base_model = "black-forest-labs/FLUX.1-dev"
#TAEF1 is very tiny autoencoder which uses the same "latent API" as FLUX.1's VAE. FLUX.1 is useful for real-time previewing of the FLUX.1 generation process.#
taef1 = AutoencoderTiny.from_pretrained("madebyollin/taef1", torch_dtype=dtype).to(device)
good_vae = AutoencoderKL.from_pretrained(base_model, subfolder="vae", torch_dtype=dtype).to(device)
pipe = DiffusionPipeline.from_pretrained(base_model, torch_dtype=dtype, vae=taef1).to(device)
pipe_i2i = AutoPipelineForImage2Image.from_pretrained(base_model,
vae=good_vae,
transformer=pipe.transformer,
text_encoder=pipe.text_encoder,
tokenizer=pipe.tokenizer,
text_encoder_2=pipe.text_encoder_2,
tokenizer_2=pipe.tokenizer_2,
torch_dtype=dtype
)
pipe.flux_pipe_call_that_returns_an_iterable_of_images = flux_pipe_call_that_returns_an_iterable_of_images.__get__(pipe)
class calculateDuration:
def __init__(self, activity_name=""):
self.activity_name = activity_name
def __enter__(self):
self.start_time = time.time()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.end_time = time.time()
self.elapsed_time = self.end_time - self.start_time
if self.activity_name:
print(f"Elapsed time for {self.activity_name}: {self.elapsed_time:.6f} seconds")
else:
print(f"Elapsed time: {self.elapsed_time:.6f} seconds")
def update_selection(evt: gr.SelectData, width, height, aspect_ratio):
selected_lora = loras[evt.index]
new_placeholder = f"Type a prompt for {selected_lora['title']}"
new_aspect_ratio = aspect_ratio
lora_repo = selected_lora["repo"]
updated_text = f"### Selected: [{lora_repo}](https://huggingface.co/{lora_repo}) ✅"
# aspect will now use ratios if implemented, like 16:9, 4:3, 1:1, etc.
if "aspect" in selected_lora:
try:
new_aspect_ratio = selected_lora["aspect"]
width, height = update_dimensions_on_ratio(new_aspect_ratio, height)
except Exception as e:
print(f"\nError in update selection aspect ratios:{e}\nSkipping")
new_aspect_ratio = aspect_ratio
width = width
height = height
return (
gr.update(placeholder=new_placeholder),
updated_text,
evt.index,
width,
height,
new_aspect_ratio,
upd_prompt_notes_by_index(evt.index)
)
@spaces.GPU(duration=120,progress=gr.Progress(track_tqdm=True))
def generate_image(prompt_mash, negative_prompt, steps, seed, cfg_scale, width, height, lora_scale, progress):
pipe.to("cuda")
generator = torch.Generator(device="cuda").manual_seed(seed)
flash_attention_enabled = torch.backends.cuda.flash_sdp_enabled()
if flash_attention_enabled:
pipe.attn_implementation="flash_attention_2"
if IS_SHARED_SPACE:
pipe.vae.enable_tiling() # For larger resolutions if needed
else:
# Compile UNet
#pipe.transformer = torch.compile(pipe.transformer, mode="reduce-overhead")
#pipe.enable_model_cpu_offload() #for smaller GPUs
pipe.vae.enable_slicing()
# Disable unnecessary features
pipe.safety_checker = None
print(f"\nGenerating image with prompt: {prompt_mash}\n")
approx_tokens= approximate_token_count(prompt_mash)
if approx_tokens > 76:
print(f"\nSplitting prompt due to length: {approx_tokens}\n")
prompt, prompt2 = split_prompt_precisely(prompt_mash)
else:
prompt = prompt_mash
prompt2 = None
with calculateDuration("Generating image"):
# Generate image
for img in pipe.flux_pipe_call_that_returns_an_iterable_of_images(
prompt=prompt,
prompt_2=prompt2,
num_inference_steps=steps,
guidance_scale=cfg_scale,
width=width,
height=height,
generator=generator,
joint_attention_kwargs={"scale": lora_scale},
output_type="pil",
good_vae=good_vae,
):
yield img
def generate_image_to_image(prompt_mash, negative_prompt, image_input_path, image_strength, steps, cfg_scale, width, height, lora_scale, seed, progress):
generator = torch.Generator(device="cuda").manual_seed(seed)
pipe_i2i.to("cuda")
flash_attention_enabled = torch.backends.cuda.flash_sdp_enabled()
if flash_attention_enabled:
pipe_i2i.attn_implementation="flash_attention_2"
if IS_SHARED_SPACE:
pipe_i2i.vae.enable_tiling() # For larger resolutions if needed
else:
# Compile UNet
# pipe.transformer = torch.compile(pipe.transformer, mode="reduce-overhead") # uses the other pipe's transformer
# pipe_i2i.enable_model_cpu_offload() #for smaller GPUs
pipe_i2i.vae.enable_slicing()
# Disable unnecessary features
pipe_i2i.safety_checker = None
image_input = open_image(image_input_path)
print(f"\nGenerating image with prompt: {prompt_mash} and {image_input_path}\n")
approx_tokens= approximate_token_count(prompt_mash)
if approx_tokens > 76:
print(f"\nSplitting prompt due to length: {approx_tokens}\n")
prompt, prompt2 = split_prompt_precisely(prompt_mash)
else:
prompt = prompt_mash
prompt2 = None
with calculateDuration("Generating image"):
# Generate image
final_image = pipe_i2i(
prompt=prompt,
prompt_2=prompt2,
image=image_input,
strength=image_strength,
num_inference_steps=steps,
guidance_scale=cfg_scale,
width=width,
height=height,
generator=generator,
joint_attention_kwargs={"scale": lora_scale},
output_type="pil",
).images[0]
return final_image
@spaces.GPU(duration=140,progress=gr.Progress(track_tqdm=True))
def run_lora(prompt, map_option, image_input, image_strength, cfg_scale, steps, selected_index, randomize_seed, seed, width, height, lora_scale, enlarge, use_conditioned_image=False, progress=gr.Progress(track_tqdm=True)):
if selected_index is None:
raise gr.Error("You must select a LoRA before proceeding.🧨")
print(f"input Image: {image_input}\n")
# handle selecting a conditioned image from the gallery
global current_prerendered_image
conditioned_image=None
formatted_map_option = map_option.lower().replace(' ', '_')
if use_conditioned_image:
print(f"Conditioned path: {current_prerendered_image.value}.. converting to RGB\n")
# ensure the conditioned image is an image and not a string, cannot use RGBA
if isinstance(current_prerendered_image.value, str):
conditioned_image = open_image(current_prerendered_image.value).convert("RGB")
image_input = resize_and_crop_image(conditioned_image, width, height)
print(f"Conditioned Image: {image_input.size}.. converted to RGB and resized\n")
if map_option != "Prompt":
prompt = PROMPTS[map_option]
negative_prompt = NEGATIVE_PROMPTS.get(map_option, "")
selected_lora = loras[selected_index]
lora_path = selected_lora["repo"]
trigger_word = selected_lora["trigger_word"]
if(trigger_word):
if "trigger_position" in selected_lora:
if selected_lora["trigger_position"] == "prepend":
prompt_mash = f"{trigger_word} {prompt}"
else:
prompt_mash = f"{prompt} {trigger_word}"
else:
prompt_mash = f"{trigger_word} {prompt}"
else:
prompt_mash = prompt
with calculateDuration("Unloading LoRA"):
pipe.unload_lora_weights()
pipe_i2i.unload_lora_weights()
#LoRA weights flow
with calculateDuration(f"Loading LoRA weights for {selected_lora['title']}"):
pipe_to_use = pipe_i2i if image_input is not None else pipe
weight_name = selected_lora.get("weights", None)
pipe_to_use.load_lora_weights(
lora_path,
weight_name=weight_name,
low_cpu_mem_usage=True
)
with calculateDuration("Randomizing seed"):
if randomize_seed:
seed = random.randint(0, MAX_SEED)
if(image_input is not None):
print(f"\nGenerating image to image with seed: {seed}\n")
generated_image = generate_image_to_image(prompt_mash, negative_prompt, image_input, image_strength, steps, cfg_scale, width, height, lora_scale, seed, progress)
if enlarge:
upscaled_image = upscale_image(generated_image, max(1.0,min((TARGET_SIZE[0]/width),(TARGET_SIZE[1]/height))))
else:
upscaled_image = generated_image
# Save the upscaled image to a temporary file
with NamedTemporaryFile(delete=False, suffix=".png", prefix=f"{formatted_map_option}_") as tmp_upscaled:
upscaled_image.save(tmp_upscaled.name, format="PNG")
temp_files.append(tmp_upscaled.name)
print(f"Upscaled image saved to {tmp_upscaled.name}")
final_image = tmp_upscaled.name
yield final_image, seed, gr.update(visible=False)
else:
image_generator = generate_image(prompt_mash, negative_prompt, steps, seed, cfg_scale, width, height, lora_scale, progress)
final_image = None
step_counter = 0
for image in image_generator:
step_counter+=1
generated_image = image
progress_bar = f'
'
yield image, seed, gr.update(value=progress_bar, visible=True)
if enlarge:
upscaled_image = upscale_image(generated_image, max(1.0,min((TARGET_SIZE[0]/width),(TARGET_SIZE[1]/height))))
else:
upscaled_image = generated_image
# Save the upscaled image to a temporary file
with NamedTemporaryFile(delete=False, suffix=".png", prefix=f"{formatted_map_option}_") as tmp_upscaled:
upscaled_image.save(tmp_upscaled.name, format="PNG")
temp_files.append(tmp_upscaled.name)
print(f"Upscaled image saved to {tmp_upscaled.name}")
final_image = tmp_upscaled.name
yield final_image, seed, gr.update(value=progress_bar, visible=False)
def get_huggingface_safetensors(link):
split_link = link.split("/")
if(len(split_link) == 2):
model_card = ModelCard.load(link)
base_model = model_card.data.get("base_model")
print(base_model)
#Allows Both
if base_model not in MODELS:
#if((base_model != "black-forest-labs/FLUX.1-dev") and (base_model != "black-forest-labs/FLUX.1-schnell")):
raise Exception("Flux LoRA Not Found!")
# Only allow "black-forest-labs/FLUX.1-dev"
#if base_model != "black-forest-labs/FLUX.1-dev":
#raise Exception("Only FLUX.1-dev is supported, other LoRA models are not allowed!")
image_path = model_card.data.get("widget", [{}])[0].get("output", {}).get("url", None)
trigger_word = model_card.data.get("instance_prompt", "")
image_url = f"https://huggingface.co/{link}/resolve/main/{image_path}" if image_path else None
fs = HfFileSystem()
try:
list_of_files = fs.ls(link, detail=False)
for file in list_of_files:
if(file.endswith(".safetensors")):
safetensors_name = file.split("/")[-1]
if (not image_url and file.lower().endswith((".jpg", ".jpeg", ".png", ".webp"))):
image_elements = file.split("/")
image_url = f"https://huggingface.co/{link}/resolve/main/{image_elements[-1]}"
except Exception as e:
print(e)
gr.Warning(f"You didn't include a link neither a valid Hugging Face repository with a *.safetensors LoRA")
raise Exception(f"You didn't include a link neither a valid Hugging Face repository with a *.safetensors LoRA")
return split_link[1], link, safetensors_name, trigger_word, image_url
def check_custom_model(link):
if(link.startswith("https://")):
if(link.startswith("https://huggingface.co") or link.startswith("https://www.huggingface.co")):
link_split = link.split("huggingface.co/")
return get_huggingface_safetensors(link_split[1])
else:
return get_huggingface_safetensors(link)
def add_custom_lora(custom_lora):
global loras
if(custom_lora):
try:
title, repo, path, trigger_word, image = check_custom_model(custom_lora)
print(f"Loaded custom LoRA: {repo}")
card = f'''
Loaded custom LoRA:
{title}
{"Using: "+trigger_word+"
as the trigger word" if trigger_word else "No trigger word found. If there's a trigger word, include it in your prompt"}
'''
existing_item_index = next((index for (index, item) in enumerate(loras) if item['repo'] == repo), None)
if(not existing_item_index):
new_item = {
"image": image,
"title": title,
"repo": repo,
"weights": path,
"trigger_word": trigger_word
}
print(new_item)
existing_item_index = len(loras)
loras.append(new_item)
return gr.update(visible=True, value=card), gr.update(visible=True), gr.Gallery(selected_index=None), f"Custom: {path}", existing_item_index, trigger_word
except Exception as e:
gr.Warning(f"Invalid LoRA: either you entered an invalid link, or a non-FLUX LoRA")
return gr.update(visible=True, value=f"Invalid LoRA: either you entered an invalid link, a non-FLUX LoRA"), gr.update(visible=False), gr.update(), "", None, ""
else:
return gr.update(visible=False), gr.update(visible=False), gr.update(), "", None, ""
def remove_custom_lora():
return gr.update(visible=False), gr.update(visible=False), gr.update(), "", None, ""
def on_prerendered_gallery_selection(event_data: gr.SelectData):
global current_prerendered_image
selected_index = event_data.index
selected_image = pre_rendered_maps_paths[selected_index]
print(f"Gallery Image Selected: {selected_image}\n")
current_prerendered_image.value = selected_image
return current_prerendered_image
def update_prompt_visibility(map_option):
is_visible = (map_option == "Prompt")
return (
gr.update(visible=is_visible),
gr.update(visible=is_visible),
gr.update(visible=is_visible)
)
def composite_with_control_sync(input_image, sketch_image, slider_value):
# Load the images using open_image() if they are provided as file paths.
in_img = open_image(input_image) if isinstance(input_image, str) else input_image
sk_img_path, _ = get_image_from_dict(sketch_image)
sk_img = open_image(sk_img_path)
# Resize sketch image if dimensions don't match input image.
if in_img.size != sk_img.size:
sk_img = sk_img.resize(in_img.size, Image.LANCZOS)
# Now composite using the original alpha_composite_with_control function.
result_img = alpha_composite_with_control(in_img, sk_img, slider_value)
return result_img
def replace_input_with_sketch_image(sketch_image):
print(f"Sketch Image: {sketch_image}\n")
sketch, is_dict = get_image_from_dict(sketch_image)
return sketch
def on_input_image_change(image_path):
if image_path is None:
gr.Warning("Please upload an Input Image to get started.")
return None, gr.update()
img, img_path = convert_to_rgba_png(image_path)
with Image.open(img_path) as pil_img:
width, height = pil_img.size
return [img_path, gr.update(width=width, height=height)]
def update_sketch_dimensions(input_image, sketch_image):
# Load the images using open_image() if they are provided as file paths.
in_img = open_image(input_image) if isinstance(input_image, str) else input_image
sk_img_path, _ = get_image_from_dict(sketch_image)
sk_img = open_image(sk_img_path)
# Resize sketch image if dimensions don't match input image.
if (in_img) and (in_img.size != sk_img.size):
sk_img = sk_img.resize(in_img.size, Image.LANCZOS)
return [sk_img, gr.update(width=in_img.width, height=in_img.height)]
else:
return [sk_img, gr.update()]
@spaces.GPU()
def getVersions():
return versions_html()
run_lora.zerogpu = True
gr.set_static_paths(paths=["images/","images/images","images/prerendered","LUT/","fonts/", "assets/"])
title = "MapLore Magic (Development Features for HexaGrid)"
with gr.Blocks(css_paths="style_20250314.css", title=title, theme='Surn/beeuty@==0.5.12', delete_cache=(43200, 43200), head_paths="head.htm") as app:
with gr.Row():
gr.Markdown("""
# MapLore Magic (Development Features for HexaGrid)
## This project includes features that did not make it into the main project! ⬢""", elem_classes="intro")
with gr.Row():
with gr.Accordion("Welcome to MapLore Magic, the ultimate tool for transforming your images into mesmerizing artworks. Whether you're a tabletop game enthusiast, a digital artist, or someone who loves unique patterns, LandLore Magic has something for you.", open=False, elem_classes="intro"):
gr.Markdown ("""
## Drop an image into the Input Image and get started!
## What is MapLore Magic?
MapLore Magic is a web-based application that allows you to transform your images into stunning artworks. You can customize various aspects of your images, apply filters, and use advanced AI models to generate unique visuals.
### What Can You Do?
- **AI-Powered Image Generation:** Use advanced AI models to generate images based on your prompts.
- **Sketch, Upload, or Select:** Guide your AI image generation with sketches, uploaded images, or pre-rendered templates.
- **Image Filter [Look-Up Table (LUT)] Application:** Apply filters (LUTs) to your images for color grading and enhancement.
- **Pre-rendered Maps:** Access a library of pre-rendered maps for quick and easy customization.
- **Composite Images:** Composite images with control over opacity and color.
- **Sketch Pad:** Draw or upload sketches to guide image generation.
### Why You'll Love It
- **Fun and Easy to Use:** With an intuitive interface and real-time previews, creating unique images has never been this fun!
- **Endless Creativity:** Unleash your creativity with endless customization options and see your images transform in unique ways.
- **Advanced AI Models:** Leverage advanced AI models and LoRA weights for high-quality image generation and customization.
### Get Started
1. **Upload or Generate an Image:** Start by uploading your own image or generate one using our AI-powered tool.
2. **Customize Your Image:** Play around with the settings to create the perfect image.
3. **Download and Share:** Once you're happy with your creation, download it and share it with the world!
### Advanced Features
- **Generative AI Integration:** Utilize models like `black-forest-labs/FLUX.1-dev` and various LoRA weights for generating unique images.
- **Pre-rendered Maps:** Access a library of pre-rendered maps for quick and easy customization.
- **Image Filter [Look-Up Table (LUT)] Application:** Apply filters (LUTs) to your images for color grading and enhancement.
- **Composite Images:** Composite images with control over opacity and color.
- **Sketch Pad:** Draw or upload sketches to guide image generation.
Join the creative journey with MapLore Magic today!
""", elem_classes="intro")
selected_index = gr.State(None)
with gr.Row():
with gr.Column(scale=2):
progress_bar = gr.Markdown(elem_id="progress",visible=False)
input_image = gr.Image(
label="Input Image",
type="filepath",
interactive=True,
elem_classes="centered solid imgcontainer",
key="imgInput",
image_mode="RGB",
format="PNG"
)
with gr.Column(scale=1):
with gr.Accordion("Image Filters", open = False):
with gr.Row():
with gr.Column():
lut_filename = gr.Textbox(
value="",
label="Look Up Table (LUT) File Name",
elem_id="lutFileName")
with gr.Column():
lut_file = gr.File(
value=None,
file_count="single",
file_types=[".cube"],
type="filepath",
label="LUT cube File",
height=120)
with gr.Row():
lut_intensity = gr.Slider(label="Filter Intensity", minimum=-200, maximum=200, value=100, info="0 none, negative inverts the filter", interactive=True)
apply_lut_button = gr.Button("Apply Filter (LUT)", elem_classes="solid", elem_id="apply_lut_button")
with gr.Row():
lut_example_image = gr.Image(type="pil", label="Filter (LUT) Example Image", value=default_lut_example_img, format="png")
with gr.Row():
with gr.Column():
gr.Markdown("""
### Included Filters (LUTs)
Try on Example Image then APPLY FILTER to Input Image.
*-none.cube files are placebo controls
""", elem_id="lut_markdown")
with gr.Column():
gr.Examples(elem_id="lut_examples",
examples=[[f] for f in lut_files],
inputs=[lut_filename],
outputs=[lut_filename],
label="Select a Filter (LUT) file to populate the LUT File Name field",
examples_per_page = 25,
)
lut_file.change(get_filename, inputs=[lut_file], outputs=[lut_filename])
lut_filename.change(show_lut, inputs=[lut_filename, input_image, lut_intensity], outputs=[lut_example_image], scroll_to_output=True)
lut_intensity.change(show_lut, inputs=[lut_filename, input_image, lut_intensity], outputs=[lut_example_image])
apply_lut_button.click(
lambda lut_filename, input_image, lut_intensity: gr.Warning("Please upload an Input Image to get started.") if input_image is None else apply_lut_to_image_path(lut_filename, input_image, lut_intensity)[1],
inputs=[lut_filename, input_image, lut_intensity],
outputs=[input_image],
scroll_to_output=True
)
with gr.Accordion("Color Composite", open = False):
with gr.Row():
composite_color = gr.ColorPicker(label="Color", value="#ede9ac44")
composite_opacity = gr.Slider(label="Opacity %", minimum=0, maximum=100, value=50, interactive=True)
with gr.Row():
composite_button = gr.Button("Composite", elem_classes="solid")
with gr.Accordion("Sketch Pad", open = False):
with gr.Row():
sketch_image = gr.Sketchpad(
label="Sketch Image",
type="filepath",
#invert_colors=True,
#sources=['upload','canvas'],
#tool=['editor','select','color-sketch'],
placeholder="Draw a sketch or upload an image. Currently broken in gradio 5.17.1",
interactive=True,
elem_classes="centered solid imgcontainer",
key="imgSketch",
image_mode="RGBA",
format="PNG",
brush=gr.Brush(),
height=300
)
with gr.Row():
with gr.Column(scale=1):
sketch_replace_input_image_button = gr.Button(
"Replace Input Image with sketch",
elem_id="sketch_replace_input_image_button",
elem_classes="solid"
)
with gr.Column(scale=2):
alpha_composite_slider = gr.Slider(0,100,50,0.5, label="Alpha Composite Sketch to Input Image", elem_id="alpha_composite_slider")
with gr.Row():
with gr.Accordion("Generative AI", open = True ):
with gr.Column():
map_options = gr.Dropdown(
label="Map Options*",
choices=list(PROMPTS.keys()),
value="Alien Landscape",
elem_classes="solid",
scale=0
)
prompt = gr.Textbox(
label="Prompt",
visible=False,
elem_classes="solid",
value="Planetary overhead view, directly from above, centered on the planet’s surface, (rectangular tabletop_map) alien planet map, Battletech_boardgame scifi world with forests, lakes, oceans, continents and snow at the top and bottom, (middle is dark, no_reflections, no_shadows), looking straight down.",
lines=4
)
negative_prompt_textbox = gr.Textbox(
label="Negative Prompt",
visible=False,
elem_classes="solid",
value="Earth, low quality, bad anatomy, blurry, cropped, worst quality, shadows, people, humans, reflections, shadows, realistic map of the Earth, isometric, text"
)
prompt_notes_label = gr.Label(
"Choose a LoRa style or add an image. YOU MUST CLEAR THE IMAGE TO START OVER ",
elem_classes="solid centered small",
show_label=False,
visible=False
)
# Keep the change event to maintain functionality
map_options.change(
fn=update_prompt_visibility,
inputs=[map_options],
outputs=[prompt, negative_prompt_textbox, prompt_notes_label]
)
with gr.Row():
with gr.Column(scale=1):
generate_button = gr.Button("Generate From Map Options, Input Image and LoRa Style", variant="primary", elem_id="gen_btn")
with gr.Accordion("Image Styles*", open=False):
selected_info = gr.Markdown("")
lora_gallery = gr.Gallery(
[(item["image"], item["title"]) for item in loras],
label="LoRA Styles",
allow_preview=False,
columns=3,
elem_id="lora_gallery",
show_share_button=False
)
with gr.Accordion("Custom LoRA", open=False):
with gr.Group():
custom_lora = gr.Textbox(label="Enter Custom LoRA. **NOT TESTED**", placeholder="prithivMLmods/Canopus-LoRA-Flux-Anime")
gr.Markdown("[Check the list of FLUX LoRA's](https://huggingface.co/models?other=base_model:adapter:black-forest-labs/FLUX.1-dev)", elem_id="lora_list")
custom_lora_info = gr.HTML(visible=False)
custom_lora_button = gr.Button("Remove custom LoRA", visible=False)
with gr.Column(scale=2):
generate_input_image_from_gallery = gr.Button(
"Generate AI Image from Template Image",
elem_id="generate_input_image_from_gallery",
elem_classes="solid",
variant="primary"
)
with gr.Accordion("Template Images", open = False):
with gr.Row():
with gr.Column(scale=1):
# Gallery from PRE_RENDERED_IMAGES GOES HERE
prerendered_image_gallery = gr.Gallery(label="Template Gallery", show_label=True, value=build_prerendered_images_by_quality(3,'thumbnail'), elem_id="gallery", elem_classes="solid", type="filepath", columns=[3], rows=[3], preview=False ,object_fit="contain", height="auto", format="png",allow_preview=False)
with gr.Column(scale=1):
# def handle_login(request: gr.Request):
# # Extract user information from the request
# user_info = {
# "username": request.username,
# "session_hash": request.session_hash,
# "headers": dict(request.headers),
# "client": request.client,
# "query_params": dict(request.query_params),
# "path_params": dict(request.path_params)
# }
# print(f"\n{user_info}\n")
# return user_info
replace_input_image_button = gr.Button(
"Replace Input Image",
elem_id="prerendered_replace_input_image_button",
elem_classes="solid"
)
# login_button = gr.LoginButton()
# user_info_output = gr.JSON(label="User Information")
# login_button.click(fn=handle_login, inputs=[], outputs=user_info_output)
with gr.Row():
with gr.Accordion("Advanced Settings", open=False):
with gr.Row():
image_strength = gr.Slider(label="Image Guidance Strength (prompt percentage)", info="Lower means more image influence", minimum=0.1, maximum=1.0, step=0.01, value=0.85)
with gr.Column():
with gr.Row():
cfg_scale = gr.Slider(label="CFG Scale", minimum=1, maximum=20, step=0.5, value=5.0)
steps = gr.Slider(label="Steps", minimum=1, maximum=50, step=1, value=30)
with gr.Row():
negative_prompt_textbox = gr.Textbox(
label="Negative Prompt",
visible=False,
elem_classes="solid",
value="Earth, low quality, bad anatomy, blurry, cropped, worst quality, shadows, people, humans, reflections, shadows, realistic map of the Earth, isometric, text"
)
# Add Dropdown for sizing of Images, height and width based on selection. Options are 16x9, 16x10, 4x5, 1x1
# The values of height and width are based on common resolutions for each aspect ratio
# Default to 16x9, 1024x576
image_size_ratio = gr.Dropdown(label="Image Aspect Ratio", choices=["16:9", "16:10", "4:5", "4:3", "2:1","3:2","1:1", "9:16", "10:16", "5:4", "3:4","1:2", "2:3"], value="16:9", elem_classes="solid", type="value", scale=0, interactive=True)
width = gr.Slider(label="Width", minimum=256, maximum=2560, step=16, value=1024, interactive=False)
height = gr.Slider(label="Height", minimum=256, maximum=1536, step=64, value=512)
enlarge_to_default = gr.Checkbox(label="Auto Enlarge to Default Size", value=False)
image_size_ratio.change(
fn=update_dimensions_on_ratio,
inputs=[image_size_ratio, height],
outputs=[width, height]
)
height.change(
fn=lambda *args: update_dimensions_on_ratio(*args)[0],
inputs=[image_size_ratio, height],
outputs=[width]
)
with gr.Row():
randomize_seed = gr.Checkbox(False, label="Randomize seed",elem_id="rnd_seed_chk")
seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0, randomize=True, elem_id="rnd_seed")
lora_scale = gr.Slider(label="LoRA Scale", minimum=0, maximum=3, step=0.01, value=1.01)
with gr.Row():
login_button = gr.LoginButton(logout_value=f"Logout({user_info['username']} ({user_info['level']}))", size="md", elem_classes="solid centered", elem_id="hf_login_btn", icon="./assets/favicon.ico")
# Create a JSON component to display the user information
user_info_output = gr.JSON(label="User Information:")
# Set up the event listener for the login button click
login_button.click(fn=wait5, inputs=[gr.State("Login/logout"), gr.State(10)], outputs=[]
).then(
fn=handle_login,
inputs=[],
outputs=[user_info_output, login_button, user_profile]
)
with gr.Row():
gr.HTML(value=getVersions(), visible=True, elem_id="versions")
# Event Handlers
composite_button.click(
fn=lambda input_image, composite_color, composite_opacity: gr.Warning("Please upload an Input Image to get started.") if input_image is None else change_color(input_image, composite_color, composite_opacity),
inputs=[input_image, composite_color, composite_opacity],
outputs=[input_image]
)
input_image.input(
fn=on_input_image_change,
inputs=[input_image],
outputs=[input_image,sketch_image], scroll_to_output=True,
)
#use conditioned_image as the input_image for generate_input_image_click
generate_input_image_from_gallery.click(
fn=run_lora,
inputs=[prompt, map_options, input_image, image_strength, cfg_scale, steps, selected_index, randomize_seed, seed, width, height, lora_scale, enlarge_to_default, gr.State(True)],
outputs=[input_image, seed, progress_bar], scroll_to_output=True
).then(
fn=update_sketch_dimensions,
inputs=[input_image, sketch_image],
outputs=[sketch_image, sketch_image]
)
prerendered_image_gallery.select(
fn=on_prerendered_gallery_selection,
inputs=None,
outputs=gr.State(current_prerendered_image), # Update the state with the selected image
show_api=False, scroll_to_output=True
)
alpha_composite_slider.change(
fn=composite_with_control_sync,
inputs=[input_image, sketch_image, alpha_composite_slider],
outputs=[input_image],
scroll_to_output=True
)
sketch_replace_input_image_button.click(
lambda sketch_image: replace_input_with_sketch_image(sketch_image),
inputs=[sketch_image],
outputs=[input_image], scroll_to_output=True
)
# replace input image with selected prerendered image gallery selection
replace_input_image_button.click(
lambda: current_prerendered_image.value,
inputs=None,
outputs=[input_image], scroll_to_output=True
).then(
fn=update_sketch_dimensions,
inputs=[input_image, sketch_image],
outputs=[sketch_image, sketch_image]
)
lora_gallery.select(
update_selection,
inputs=[width, height, image_size_ratio],
outputs=[prompt, selected_info, selected_index, width, height, image_size_ratio, prompt_notes_label]
)
custom_lora.input(
add_custom_lora,
inputs=[custom_lora],
outputs=[custom_lora_info, custom_lora_button, lora_gallery, selected_info, selected_index, prompt]
)
custom_lora_button.click(
remove_custom_lora,
outputs=[custom_lora_info, custom_lora_button, lora_gallery, selected_info, selected_index, custom_lora]
)
gr.on(
triggers=[generate_button.click, prompt.submit],
fn=run_lora,
inputs=[prompt, map_options, input_image, image_strength, cfg_scale, steps, selected_index, randomize_seed, seed, width, height, lora_scale, enlarge_to_default, gr.State(False)],
outputs=[input_image, seed, progress_bar]
).then(
fn=update_sketch_dimensions,
inputs=[input_image, sketch_image],
outputs=[sketch_image, sketch_image]
)
load_env_vars(dotenv_path)
logging.basicConfig(
format="[%(levelname)s] %(asctime)s %(message)s", level=logging.INFO
)
logging.info("Environment Variables: %s" % os.environ)
app.queue()
app.launch(allowed_paths=["assets","/","./assets","images","./images", "./images/prerendered"], favicon_path="./assets/favicon.ico", max_file_size="10mb")