Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
automatic1111
GitHub Repository: automatic1111/stable-diffusion-webui
Path: blob/master/scripts/poor_mans_outpainting.py
3055 views
1
import math
2
3
import modules.scripts as scripts
4
import gradio as gr
5
from PIL import Image, ImageDraw
6
7
from modules import images, devices
8
from modules.processing import Processed, process_images
9
from modules.shared import opts, state
10
11
12
class Script(scripts.Script):
13
def title(self):
14
return "Poor man's outpainting"
15
16
def show(self, is_img2img):
17
return is_img2img
18
19
def ui(self, is_img2img):
20
if not is_img2img:
21
return None
22
23
pixels = gr.Slider(label="Pixels to expand", minimum=8, maximum=256, step=8, value=128, elem_id=self.elem_id("pixels"))
24
mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=4, elem_id=self.elem_id("mask_blur"))
25
inpainting_fill = gr.Radio(label='Masked content', choices=['fill', 'original', 'latent noise', 'latent nothing'], value='fill', type="index", elem_id=self.elem_id("inpainting_fill"))
26
direction = gr.CheckboxGroup(label="Outpainting direction", choices=['left', 'right', 'up', 'down'], value=['left', 'right', 'up', 'down'], elem_id=self.elem_id("direction"))
27
28
return [pixels, mask_blur, inpainting_fill, direction]
29
30
def run(self, p, pixels, mask_blur, inpainting_fill, direction):
31
initial_seed = None
32
initial_info = None
33
34
p.mask_blur = mask_blur * 2
35
p.inpainting_fill = inpainting_fill
36
p.inpaint_full_res = False
37
38
left = pixels if "left" in direction else 0
39
right = pixels if "right" in direction else 0
40
up = pixels if "up" in direction else 0
41
down = pixels if "down" in direction else 0
42
43
init_img = p.init_images[0]
44
target_w = math.ceil((init_img.width + left + right) / 64) * 64
45
target_h = math.ceil((init_img.height + up + down) / 64) * 64
46
47
if left > 0:
48
left = left * (target_w - init_img.width) // (left + right)
49
if right > 0:
50
right = target_w - init_img.width - left
51
52
if up > 0:
53
up = up * (target_h - init_img.height) // (up + down)
54
55
if down > 0:
56
down = target_h - init_img.height - up
57
58
img = Image.new("RGB", (target_w, target_h))
59
img.paste(init_img, (left, up))
60
61
mask = Image.new("L", (img.width, img.height), "white")
62
draw = ImageDraw.Draw(mask)
63
draw.rectangle((
64
left + (mask_blur * 2 if left > 0 else 0),
65
up + (mask_blur * 2 if up > 0 else 0),
66
mask.width - right - (mask_blur * 2 if right > 0 else 0),
67
mask.height - down - (mask_blur * 2 if down > 0 else 0)
68
), fill="black")
69
70
latent_mask = Image.new("L", (img.width, img.height), "white")
71
latent_draw = ImageDraw.Draw(latent_mask)
72
latent_draw.rectangle((
73
left + (mask_blur//2 if left > 0 else 0),
74
up + (mask_blur//2 if up > 0 else 0),
75
mask.width - right - (mask_blur//2 if right > 0 else 0),
76
mask.height - down - (mask_blur//2 if down > 0 else 0)
77
), fill="black")
78
79
devices.torch_gc()
80
81
grid = images.split_grid(img, tile_w=p.width, tile_h=p.height, overlap=pixels)
82
grid_mask = images.split_grid(mask, tile_w=p.width, tile_h=p.height, overlap=pixels)
83
grid_latent_mask = images.split_grid(latent_mask, tile_w=p.width, tile_h=p.height, overlap=pixels)
84
85
p.n_iter = 1
86
p.batch_size = 1
87
p.do_not_save_grid = True
88
p.do_not_save_samples = True
89
90
work = []
91
work_mask = []
92
work_latent_mask = []
93
work_results = []
94
95
for (y, h, row), (_, _, row_mask), (_, _, row_latent_mask) in zip(grid.tiles, grid_mask.tiles, grid_latent_mask.tiles):
96
for tiledata, tiledata_mask, tiledata_latent_mask in zip(row, row_mask, row_latent_mask):
97
x, w = tiledata[0:2]
98
99
if x >= left and x+w <= img.width - right and y >= up and y+h <= img.height - down:
100
continue
101
102
work.append(tiledata[2])
103
work_mask.append(tiledata_mask[2])
104
work_latent_mask.append(tiledata_latent_mask[2])
105
106
batch_count = len(work)
107
print(f"Poor man's outpainting will process a total of {len(work)} images tiled as {len(grid.tiles[0][2])}x{len(grid.tiles)}.")
108
109
state.job_count = batch_count
110
111
for i in range(batch_count):
112
p.init_images = [work[i]]
113
p.image_mask = work_mask[i]
114
p.latent_mask = work_latent_mask[i]
115
116
state.job = f"Batch {i + 1} out of {batch_count}"
117
processed = process_images(p)
118
119
if initial_seed is None:
120
initial_seed = processed.seed
121
initial_info = processed.info
122
123
p.seed = processed.seed + 1
124
work_results += processed.images
125
126
127
image_index = 0
128
for y, h, row in grid.tiles:
129
for tiledata in row:
130
x, w = tiledata[0:2]
131
132
if x >= left and x+w <= img.width - right and y >= up and y+h <= img.height - down:
133
continue
134
135
tiledata[2] = work_results[image_index] if image_index < len(work_results) else Image.new("RGB", (p.width, p.height))
136
image_index += 1
137
138
combined_image = images.combine_grid(grid)
139
140
if opts.samples_save:
141
images.save_image(combined_image, p.outpath_samples, "", initial_seed, p.prompt, opts.samples_format, info=initial_info, p=p)
142
143
processed = Processed(p, [combined_image], initial_seed, initial_info)
144
145
return processed
146
147
148