|
|
|
import math |
|
|
|
import numpy |
|
import torch |
|
from PIL import Image, ImageDraw |
|
from PIL.Image import Resampling |
|
|
|
from .categories import * |
|
from .shared import ALWAYS_CHANGED_FLAG, convertTensorImageToPIL, DreamImageProcessor, \ |
|
DreamImage, DreamMask |
|
from .dreamtypes import SharedTypes, FrameCounter |
|
|
|
|
|
class DreamImageMotion: |
|
NODE_NAME = "Image Motion" |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
return { |
|
"required": { |
|
"image": ("IMAGE",), |
|
"zoom": ("FLOAT", {"default": 0.0, "min": -10, "max": 10, "step": 0.01}), |
|
"mask_1_feather": ("INT", {"default": 0, "min": 0}), |
|
"mask_1_overlap": ("INT", {"default": 0, "min": 0}), |
|
"mask_2_feather": ("INT", {"default": 10, "min": 0}), |
|
"mask_2_overlap": ("INT", {"default": 5, "min": 0}), |
|
"mask_3_feather": ("INT", {"default": 15, "min": 0}), |
|
"mask_3_overlap": ("INT", {"default": 5, "min": 0}), |
|
"x_translation": ("FLOAT", {"default": 0.0, "min": -10, "max": 10, "step": 0.01}), |
|
"y_translation": ("FLOAT", {"default": 0.0, "min": -10, "max": 10, "step": 0.01}), |
|
} | SharedTypes.frame_counter, |
|
"optional": { |
|
"noise": ("IMAGE",), |
|
"output_resize_width": ("INT", {"default": 0, "min": 0}), |
|
"output_resize_height": ("INT", {"default": 0, "min": 0}) |
|
} |
|
} |
|
|
|
CATEGORY = NodeCategories.ANIMATION_TRANSFORMS |
|
RETURN_TYPES = ("IMAGE", "MASK", "MASK", "MASK") |
|
RETURN_NAMES = ("image", "mask1", "mask2", "mask3") |
|
FUNCTION = "result" |
|
|
|
@classmethod |
|
def IS_CHANGED(cls, *values): |
|
return ALWAYS_CHANGED_FLAG |
|
|
|
def _mk_PIL_image(self, size, color=None, mode="RGB") -> Image: |
|
im = Image.new(mode=mode, size=size) |
|
if color: |
|
im.paste(color, (0, 0, size[0], size[1])) |
|
return im |
|
|
|
def _convertPILToMask(self, image): |
|
return torch.from_numpy(numpy.array(image.convert("L")).astype(numpy.float32) / 255.0) |
|
|
|
def _apply_feather(self, pil_image, area, feather): |
|
feather = min((area[2] - area[0]) // 2 - 1, feather) |
|
draw = ImageDraw.Draw(pil_image) |
|
for i in range(1, feather + 1): |
|
rect = [(area[0] + i - 1, area[1] + i - 1), (area[2] - i + 1, area[3] - i + 1)] |
|
c = 255 - int(round(255.0 * (i / (feather + 1)))) |
|
draw.rectangle(rect, fill=None, outline=(c, c, c)) |
|
return pil_image |
|
|
|
def _make_mask(self, width, height, selection_area, feather, overlap): |
|
complete_area = self._mk_PIL_image((width, height), "white") |
|
draw = ImageDraw.Draw(complete_area) |
|
(left, top, right, bottom) = selection_area |
|
area = (left + overlap, top + overlap, right - overlap - 1, bottom - overlap - 1) |
|
draw.rectangle(area, fill="black", width=0) |
|
return self._apply_feather(complete_area, area, feather) |
|
|
|
def _make_resizer(self, output_resize_width, output_resize_height): |
|
def bound(i): |
|
return min(max(i, 1), 32767) |
|
|
|
if output_resize_height and output_resize_width: |
|
return lambda img: img.resize((bound(output_resize_width), bound(output_resize_height)), Resampling.NEAREST) |
|
else: |
|
return lambda img: img |
|
|
|
def result(self, image: torch.Tensor, zoom, x_translation, y_translation, mask_1_feather, mask_1_overlap, |
|
mask_2_feather, mask_2_overlap, mask_3_feather, mask_3_overlap, frame_counter: FrameCounter, |
|
**other): |
|
def _limit_range(f): |
|
return max(-1.0, min(1.0, f)) |
|
|
|
def _motion(image: DreamImage, batch_counter, zoom, x_translation, y_translation, mask_1_overlap, |
|
mask_2_overlap, |
|
mask_3_overlap): |
|
zoom = _limit_range(zoom / frame_counter.frames_per_second) |
|
x_translation = _limit_range(x_translation / frame_counter.frames_per_second) |
|
y_translation = _limit_range(y_translation / frame_counter.frames_per_second) |
|
pil_image = image.pil_image |
|
sz = self._make_resizer(other.get("output_resize_width", None), other.get("output_resize_height", None)) |
|
noise = other.get("noise", None) |
|
multiplier = math.pow(2, zoom) |
|
resized_image = pil_image.resize((round(pil_image.width * multiplier), |
|
round(pil_image.height * multiplier)), Resampling.BILINEAR) |
|
|
|
if noise is None: |
|
base_image = self._mk_PIL_image(pil_image.size, "black") |
|
else: |
|
base_image = convertTensorImageToPIL(noise).resize(pil_image.size, Resampling.BILINEAR) |
|
|
|
selection_offset = (round(x_translation * pil_image.width), round(y_translation * pil_image.height)) |
|
selection = ((pil_image.width - resized_image.width) // 2 + selection_offset[0], |
|
(pil_image.height - resized_image.height) // 2 + selection_offset[1], |
|
(pil_image.width - resized_image.width) // 2 + selection_offset[0] + resized_image.width, |
|
(pil_image.height - resized_image.height) // 2 + selection_offset[1] + resized_image.height) |
|
base_image.paste(resized_image, selection) |
|
|
|
mask_1_overlap = min(pil_image.width // 3, min(mask_1_overlap, pil_image.height // 3)) |
|
mask_2_overlap = min(pil_image.width // 3, min(mask_2_overlap, pil_image.height // 3)) |
|
mask_3_overlap = min(pil_image.width // 3, min(mask_3_overlap, pil_image.height // 3)) |
|
mask1 = self._make_mask(pil_image.width, pil_image.height, selection, mask_1_feather, mask_1_overlap) |
|
mask2 = self._make_mask(pil_image.width, pil_image.height, selection, mask_2_feather, mask_2_overlap) |
|
mask3 = self._make_mask(pil_image.width, pil_image.height, selection, mask_3_feather, mask_3_overlap) |
|
|
|
return (DreamImage(pil_image=sz(base_image)), |
|
DreamMask(pil_image=sz(mask1)), |
|
DreamMask(pil_image=sz(mask2)), |
|
DreamMask(pil_image=sz(mask3))) |
|
|
|
proc = DreamImageProcessor(image, |
|
zoom=zoom, |
|
x_translation=x_translation, |
|
y_translation=y_translation, |
|
mask_1_overlap=mask_1_overlap, |
|
mask_2_overlap=mask_2_overlap, |
|
mask_3_overlap=mask_3_overlap) |
|
return proc.process(_motion) |
|
|