Path: blob/master/modules/masking.py
3055 views
from PIL import Image, ImageFilter, ImageOps123def get_crop_region_v2(mask, pad=0):4"""5Finds a rectangular region that contains all masked ares in a mask.6Returns None if mask is completely black mask (all 0)78Parameters:9mask: PIL.Image.Image L mode or numpy 1d array10pad: int number of pixels that the region will be extended on all sides11Returns: (x1, y1, x2, y2) | None1213Introduced post 1.9.014"""15mask = mask if isinstance(mask, Image.Image) else Image.fromarray(mask)16if box := mask.getbbox():17x1, y1, x2, y2 = box18return (max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask.size[0]), min(y2 + pad, mask.size[1])) if pad else box192021def get_crop_region(mask, pad=0):22"""23Same function as get_crop_region_v2 but handles completely black mask (all 0) differently24when mask all black still return coordinates but the coordinates may be invalid ie x2>x1 or y2>y125Notes: it is possible for the coordinates to be "valid" again if pad size is sufficiently large26(mask_size.x-pad, mask_size.y-pad, pad, pad)2728Extension developer should use get_crop_region_v2 instead unless for compatibility considerations.29"""30mask = mask if isinstance(mask, Image.Image) else Image.fromarray(mask)31if box := get_crop_region_v2(mask, pad):32return box33x1, y1 = mask.size34x2 = y2 = 035return max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask.size[0]), min(y2 + pad, mask.size[1])363738def expand_crop_region(crop_region, processing_width, processing_height, image_width, image_height):39"""expands crop region get_crop_region() to match the ratio of the image the region will processed in; returns expanded region40for example, if user drew mask in a 128x32 region, and the dimensions for processing are 512x512, the region will be expanded to 128x128."""4142x1, y1, x2, y2 = crop_region4344ratio_crop_region = (x2 - x1) / (y2 - y1)45ratio_processing = processing_width / processing_height4647if ratio_crop_region > ratio_processing:48desired_height = (x2 - x1) / ratio_processing49desired_height_diff = int(desired_height - (y2-y1))50y1 -= desired_height_diff//251y2 += desired_height_diff - desired_height_diff//252if y2 >= image_height:53diff = y2 - image_height54y2 -= diff55y1 -= diff56if y1 < 0:57y2 -= y158y1 -= y159if y2 >= image_height:60y2 = image_height61else:62desired_width = (y2 - y1) * ratio_processing63desired_width_diff = int(desired_width - (x2-x1))64x1 -= desired_width_diff//265x2 += desired_width_diff - desired_width_diff//266if x2 >= image_width:67diff = x2 - image_width68x2 -= diff69x1 -= diff70if x1 < 0:71x2 -= x172x1 -= x173if x2 >= image_width:74x2 = image_width7576return x1, y1, x2, y2777879def fill(image, mask):80"""fills masked regions with colors from image using blur. Not extremely effective."""8182image_mod = Image.new('RGBA', (image.width, image.height))8384image_masked = Image.new('RGBa', (image.width, image.height))85image_masked.paste(image.convert("RGBA").convert("RGBa"), mask=ImageOps.invert(mask.convert('L')))8687image_masked = image_masked.convert('RGBa')8889for radius, repeats in [(256, 1), (64, 1), (16, 2), (4, 4), (2, 2), (0, 1)]:90blurred = image_masked.filter(ImageFilter.GaussianBlur(radius)).convert('RGBA')91for _ in range(repeats):92image_mod.alpha_composite(blurred)9394return image_mod.convert("RGB")95969798