TestUpdate, major luma wipe speed update,with a few bugs left

exit_image
Charles Fettinger 2023-05-11 13:16:46 -07:00
parent b3e642d57d
commit c4b44e1a28
1 changed files with 222 additions and 30 deletions

View File

@ -1,10 +1,12 @@
from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageDraw, ImageFont, ImageOps, ImageMath
from decimal import ROUND_CEILING
from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageDraw, ImageChops, ImageOps, ImageMath
import requests
import base64
import numpy as np
import math
from io import BytesIO
from modules.processing import apply_overlay, slerp
from timeit import default_timer as timer
def shrink_and_paste_on_blank(current_image, mask_width, mask_height):
@ -51,8 +53,7 @@ def open_image(image_path):
img = Image.open(BytesIO(decoded_data))
else:
# Assume that the image path is a file path
img = Image.open(image_path)
img = Image.open(image_path)
return img
def apply_alpha_mask(image, mask_image, invert = False):
@ -69,33 +70,42 @@ def apply_alpha_mask(image, mask_image, invert = False):
# Resize the mask to match the current image size
mask_image = resize_and_crop_image(mask_image, image.width, image.height).convert('L') # convert to grayscale
if invert:
ImageEnhance.Contrast(mask_image).enhance(-1.0)
mask_image = ImageOps.invert(mask_image)
# Apply the mask as the alpha layer of the current image
result_image = image.copy()
result_image.putalpha(mask_image)
return result_image
def convert_to_rgba(images):
start = timer()
rgba_images = []
for img in images:
if img.mode == 'RGB':
rgba_img = img.convert('RGBA')
rgba_images.append(rgba_img)
else:
rgba_images.append(img)
rgba_images.append(img)
end = timer()
print(f"rgb convert:{end - start}")
return rgba_images
def lerp(value1, value2, factor):
"""
Linearly interpolate between value1 and value2 by factor.
"""
return np.interp(factor, [0, 1], [value1, value2])
result = np.interp(factor, [0, 1], [value1, value2])
return result
def lerp(a, b, t):
#start = timer()
t = np.clip(t, 0, 1) # clip t to the range [0, 1]
return ((1 - t) * np.array(a) + t * np.array(b))
result = ((1 - t) * np.array(a) + t * np.array(b))
#end = timer()
#print(end - start)
return result
def lerpy(img1, img2, alpha):
start = timer()
vector = np.vectorize(np.int_)
if type(img1) is PIL.Image.Image:
img1 = np.array(img1)[:, :, 3]
@ -109,13 +119,12 @@ def lerpy(img1, img2, alpha):
img1[j][i]=vector(((img1[j][i]*alpha)+(img2[j][i]*beta))+gamma)
#cv2.imshow('linear interpolation',img1[:, :, :, None])
#cv2.waitKey(0) & 0xFF
return Image.fromarray(img1[:, :], mode='L')
#return Image.fromarray(img1[:, :], 'RGBA')
#result = Image.fromarray(img1[:, :], mode='L')
return Image.fromarray(img1[:, :], 'RGBA')
#return img1[:, :]
def lerp_imagemath(img1, img2, alpha:int = 50):
# must use ImageMath.eval to avoid overflow and alpha must be an int from 0 to 100
return ImageMath.eval("((im * a) / 100) + (im2 * (100 - a)) / 100", im=img1.convert('L'), im2= img2.convert('L'), a=alpha)
end = timer()
print(end - start)
return result
def lerp_color(color1, color2, t):
"""
@ -135,6 +144,97 @@ def lerp_color(color1, color2, t):
a = (1 - t) * color1[3] + t * color2[3]
return (r, g, b, a)
def lerp_imagemath(img1, img2, alpha:int = 50):
start = timer()
# must use ImageMath.eval to avoid overflow and alpha must be an int from 0 to 100
result = ImageMath.eval("((im * (100 - a)) / 100) + (im2 * a) / 100", im=img1.convert('L'), im2= img2.convert('L'), a=alpha)
end = timer()
print(end - start)
return result
def lerp_imagemath_RGBA(img1, img2, alphaimg, factor:int = 50):
"""
Performs a linear interpolation (lerp) between two images at a given factor value.
Args:
img1 (PIL.Image): The first image to be interpolated.
img2 (PIL.Image): The second image to be interpolated.
factor (float): A value between 0.0 and 1.0 representing the progress of the interpolation.
Returns:
A PIL.Image object representing the resulting interpolated image.
"""
# create alpha and alpha inverst from luma wipe image
# multiply the time factor
# Split the input images into color bands
r1, g1, b1 = img1.split()
r2, g2, b2 = img2.split()
aa, ba, aa = alphaimg.split()
ai = ImageMath.eval("(255-a) * t / 100",a = aa,t=5).convert('L')
yellow_end_image = ImageChops.multiply(r2.convert("RGB"),aa.convert("RGB"))
rebuilt_image = Image.merge("RGBA", (r1,g1,b1,aa))
# Multiply the red channel of the first image by the factor
r1 = ImageMath.eval("convert(int(a*b), 'L')", a=r1, b=factor)
# Merge the color bands back into an RGBA image
result = Image.merge("RGBA", (r1, g2, b1, aa))
return result
#r = (1 - t) * color1[0] + t * color2[0]
#g = (1 - t) * color1[1] + t * color2[1]
#b = (1 - t) * color1[2] + t * color2[2]
#a = (1 - t) * color1[3] + t * color2[3]
def CMYKInvert(img) :
return Image.merge(img.mode, [ImageOps.invert(b.convert('L')) for b in img.split()])
def clip_gradient_image(gradient_image, min_value:int = 50, max_value:int =75, invert= False):
"""
Return only the values of a gradient grayscale image between a minimum and maximum value.
Args:
gradient_image (PIL.Image): The gradient grayscale image to adjust.
min_value (int): The minimum brightness value (0-255) to map to in the output image.
max_value (int): The maximum brightness value (0-255) to map to in the output image.
Returns:
A PIL.Image object representing the adjusted gradient image.
"""
start = timer()
# Convert the image to grayscale if needed
if gradient_image.mode != "L":
gradient_image = gradient_image.convert("L")
# Normalize the input range to 0-1 NOTUSED
#normalized_image = ImageMath.eval("im/255", im=gradient_image)
normalized_image = gradient_image
# Adjust the brightness using ImageEnhance
#enhancer = ImageEnhance.Brightness(normalized_image)
#adjusted_image = enhancer.enhance(1.0 + max_value / 255)
adjusted_image = normalized_image
# Clip the values outside the desired range
adjusted_image_mask = ImageMath.eval("im <= 0 ", im=adjusted_image)
adjusted_image = ImageMath.eval("mask * (255 - im)", im=adjusted_image, mask=adjusted_image_mask).convert("L")
# Map the brightness to the desired range
#mapped_image = ImageMath.eval("im * (max_value - min_value) + min_value", im=adjusted_image, min_value=min_value, max_value=max_value)
#mapped_image = ImageMath.eval("float(im) * ((max_value + min_value)/(max_value - min_value)) - min_value", im=adjusted_image, min_value=min_value, max_value=max_value)
if min_value <= 0 and max_value >= 0:
#mapped_image_mask = ImageMath.eval("im < max_value ", im=adjusted_image, min_value=min_value, max_value=max_value)
#mapped_image = ImageMath.eval("mask * im", im=adjusted_image, mask=mapped_image_mask, max_value=max_value).convert("L")
mapped_image = ImageMath.eval("im*(im<max_value)%256", im=adjusted_image, min_value=min_value, max_value=max_value)
elif min_value > 0 and max_value >= 255:
mapped_image = ImageMath.eval("im*(im>min_value)%256", im=adjusted_image, min_value=min_value, max_value=max_value)
else:
#mapped_image = ImageMath.eval("min(min(255 - float(im),255 - max_value) , (max(float(im), min_value) - min_value))", im=adjusted_image, min_value=min_value, max_value=max_value)
mapped_image = ImageMath.eval("min(im*(im<max_value)%256 , im*(im>min_value)%256)", im=adjusted_image, min_value=min_value, max_value=max_value)
# Convert the image back to 8-bit grayscale
final_image = ImageOps.grayscale(mapped_image)
if invert:
final_image = ImageOps.invert(final_image)
end = timer()
print(end - start)
return final_image
def resize_image_with_aspect_ratio(image: Image, basewidth: int = 512, baseheight: int = 512) -> Image:
"""
Resizes an image while maintaining its aspect ratio. This may not fill the entire image height.
@ -196,11 +296,9 @@ def resize_and_crop_image(image: Image, new_width: int = 512, new_height: int =
"""
# Get the dimensions of the original image
orig_width, orig_height = image.size
# Calculate the aspect ratios of the original and new images
orig_aspect_ratio = orig_width / float(orig_height)
new_aspect_ratio = new_width / float(new_height)
# Calculate the new size of the image while maintaining aspect ratio
if orig_aspect_ratio > new_aspect_ratio:
# The original image is wider than the new image, so we need to crop the sides
@ -214,16 +312,13 @@ def resize_and_crop_image(image: Image, new_width: int = 512, new_height: int =
resized_height = int(new_width / orig_aspect_ratio)
left_offset = 0
top_offset = (resized_height - new_height) // 2
# Resize the image with Lanczos resampling filter
resized_image = image.resize((resized_width, resized_height), resample=Image.Resampling.LANCZOS)
# Crop the image to fill the entire height and width of the new image
cropped_image = resized_image.crop((left_offset, top_offset, left_offset + new_width, top_offset + new_height))
return cropped_image
def grayscale_to_gradient(image, gradient_colors):
def grayscale_to_gradient(image, gradient_colors = list([(255,255,255),(255,0,0)])):
"""
Converts a grayscale PIL Image into a two color image using the specified gradient colors.
@ -236,19 +331,18 @@ def grayscale_to_gradient(image, gradient_colors):
"""
# Create a new image with a palette
result = Image.new("P", image.size)
result.putpalette([c for color in gradient_colors for c in color])
result.putpalette([c for color in gradient_colors for c in color])
# Convert the input image to a list of pixel values
pixel_values = list(image.getdata())
pixel_values = list(image.getdata())
# Convert the pixel values to indices in the palette and assign them to the output image
result.putdata([gradient_colors[int(p * (len(gradient_colors) - 1))] for p in pixel_values])
#result.putdata([np.dot(p, gradient_colors[(len(gradient_colors) - 1)]) for p in pixel_values])
result.putdata([int(p * gradient_colors[(len(gradient_colors) - 1)]) for p in pixel_values])
return result
def rgb2gray(rgb):
return np.dot(rgb[...,:3], [0.2989, 0.5870, 0.1140])
# _, (h, k) = ellipse_bbox(0,0,768,512,math.radians(0.0)) # Ellipse center
def ellipse_bbox(h, k, a, b, theta):
"""
@ -509,6 +603,8 @@ def multiply_alpha_ImageMath(image, factor):
Multiply the alpha layer of a PIL RGBA image by a given factor and clip it between 0 and 255.
Returns a modified image.
"""
start = timer()
# Split the image into separate bands
r, g, b, a = image.split()
# Multiply the alpha band by the factor using ImageMath
@ -516,7 +612,10 @@ def multiply_alpha_ImageMath(image, factor):
# Clip the alpha band between 0 and 255
a = ImageMath.eval("convert(min(max(a, 0), 255), 'L')", a=a)
# Merge the bands back into an RGBA image
return Image.merge("RGBA", (r, g, b, a))
result_image = Image.merge("RGBA", (r, g, b, a))
end = timer()
print(f"multiply_alpha_ImageMath:{end - start}")
return result_image
def multiply_alpha(image, factor):
"""
@ -529,6 +628,7 @@ def multiply_alpha(image, factor):
Returns:
PIL.Image.Image: The modified image.
"""
start = timer()
# Convert the image to a numpy array
np_image = np.array(image)
# Extract the alpha channel from the image
@ -541,6 +641,8 @@ def multiply_alpha(image, factor):
np_image[:, :, 3] = alpha.astype(np.uint8)
# Convert the numpy array back to a PIL image
result_image = Image.fromarray(np_image)
end = timer()
print(f"multiply_alpha:{end - start}")
return result_image
def blend_images(start_image: Image, stop_image: Image, gray_image: Image, num_frames: int) -> list:
@ -563,6 +665,7 @@ def blend_images(start_image: Image, stop_image: Image, gray_image: Image, num_f
# Generate each frame of the blending animation
for i in range(num_frames):
start = timer()
# Calculate the alpha amount for this frame
alpha = i / float(num_frames - 1)
@ -571,6 +674,8 @@ def blend_images(start_image: Image, stop_image: Image, gray_image: Image, num_f
# Append the blended frame to the list
blended_frames.append(blended_image)
end = timer()
print(f"blend:{end - start}")
# Return the list of blended frames
return blended_frames
@ -585,7 +690,7 @@ def alpha_composite_images(start_image: Image, stop_image: Image, gray_image: Im
- num_frames: the number of frames to generate in the blending animation
The function returns a list of PIL images representing the blending animation.
"""
"""
# Initialize the list of blended frames
ac_frames = []
@ -595,6 +700,7 @@ def alpha_composite_images(start_image: Image, stop_image: Image, gray_image: Im
# Generate each frame of the blending animation
for i in range(num_frames):
start = timer()
# Calculate the alpha amount for this frame
alpha = i / float(num_frames - 1)
start_adj_image = multiply_alpha(start_image.copy(), 1 - alpha)
@ -605,14 +711,17 @@ def alpha_composite_images(start_image: Image, stop_image: Image, gray_image: Im
# Append the blended frame to the list
ac_frames.append(ac_image)
end = timer()
print(f"alpha_composited:{end - start}")
# Return the list of blended frames
return ac_frames
def luma_wipe_images(start_image: Image, stop_image: Image, alpha: Image, num_frames: int) -> list:
#progress(0, status='Generating luma wipe...')
#progress(0, status='Generating luma wipe...')
lw_frames = []
for i in range(num_frames):
start = timer()
# Compute the luma value for this frame
luma_progress = i / (num_frames - 1)
# Create a new image for the transition
@ -638,6 +747,8 @@ def luma_wipe_images(start_image: Image, stop_image: Image, alpha: Image, num_fr
# Append the transition image to the list
lw_frames.append(transition)
#progress((x + 1) / num_frames)
end = timer()
print(f"luma_wipe:{end - start}")
return lw_frames
def srgb_nonlinear_to_linear_channel(u):
@ -648,7 +759,7 @@ def srgb_nonlinear_to_linear(v):
#result_img = eval("convert('RGBA')", lambda x, y: PSLumaWipe(img_a.getpixel((x,y)), img_b.getpixel((x,y)), test_g_image.getpixel((x,y))[0]/255,(1,0,0,.5), 0.25, False, 0.1, 0.01, 0.01))
#list(np.divide((255,255,245,225),255))
def PSLumaWipe(a_color, b_color, luma, l_color=(255, 255, 255, 255), progress=0.0, invert=False, softness=0.01, start_adjust = 0.01, stop_adjust = 0.0):
def PSLumaWipe(a_color, b_color, luma, l_color=(255, 255, 255, 255), progress=0.0, invert=False, softness=0.01, start_adjust = 0.01, stop_adjust = 0.0):
# - adjust for min and max. Do not process if luma value is outside min or max
if ((luma >= (start_adjust)) and (luma <= (1 - stop_adjust))):
if (invert):
@ -667,7 +778,7 @@ def PSLumaWipe(a_color, b_color, luma, l_color=(255, 255, 255, 255), progress=0.
alpha = (time - luma) / softness
out_color = lerp(a_color, b_color + out_color, alpha)
#print(f"alpha: {str(alpha)} out_color: {str(out_color)} time: {str(time)} luma: {str(luma)}")
out_color = srgb_nonlinear_to_linear(out_color)
out_color = srgb_nonlinear_to_linear(out_color)
return tuple(np.round(out_color).astype(int))
else:
# return original pixel color
@ -677,6 +788,7 @@ def PSLumaWipe_images(start_image: Image, stop_image: Image, luma_wipe_image: Im
#progress(0, status='Generating luma wipe...')
# fix transition_color to relative 0.0 - 1.0
#luma_color = list(np.divide(transition_color,255))
softness = 0.03
lw_frames = []
lw_frames.append(start_image)
@ -686,6 +798,7 @@ def PSLumaWipe_images(start_image: Image, stop_image: Image, luma_wipe_image: Im
luma_wipe_image = resize_and_crop_image(luma_wipe_image,width,height)
# call PSLumaWipe for each pixel
for i in range(num_frames):
start = timer()
# Compute the luma value for this frame
luma_progress = i / (num_frames - 1)
transition = Image.new(start_image.mode, (width, height))
@ -698,5 +811,84 @@ def PSLumaWipe_images(start_image: Image, stop_image: Image, luma_wipe_image: Im
lw_frames.append(transition)
print(f"Luma Wipe frame:{len(lw_frames)}")
#lw_frames[-1].show()
end = timer()
print(f"PSLumaWipe:{end - start}")
lw_frames.append(stop_image)
return lw_frames
#result_img = , 0.25, False, 0.1, 0.01, 0.01))
#list(np.divide((255,255,245,225),255))
def PSLumaWipe2(a_color, b_color, luma, l_color=(255, 255, 0, 255), progress=0.0, invert=False, softness=1.0, start_adjust = 0.1, stop_adjust = 0.1):
# luma is now an image file
# color is now a color image file that is merged with approaching b_color image
# the entire frame is built based upon progress
# image alpha layers are the luma image grayscale image
#0. Handle edge cases
#1. invert luma if invert is true
#2. build the colorized out_color image, b_color is the rgb value, alpha channel from clip_gradient_image
#3. build the colorized luma image
#4. Use a_color image as the base image
#5. merge or composite images together
#6. return the merged image
# - adjust for min and max. Do not process if luma value is outside min or max
start = timer()
if (progress <= start_adjust):
return a_color
if (progress >= (1 - stop_adjust)):
return b_color
# invert luma if invert is true
if (invert):
luma = ImageOps.invert(luma)
# build time values to create the image at the correct progress point
max_time = int(np.ceil(np.clip(lerp(0.0, 1.0 + softness, progress) * 255, 0, 255)))
time = int(np.ceil(np.clip(lerp(0.0, 1.0, progress) * 255, 0, 255)))
# build the colorized out_color image
# b_color is the rgb value
out_color = Image.new("RGBA", a_color.size, (l_color[0], l_color[1], l_color[2], l_color[3]))
out_color_alpha = clip_gradient_image(luma, time, max_time, False)
#build colorized luma image
out_color.putalpha(out_color_alpha)
# add out_color to a_color within alpha limits
a_out_color = a_color.copy()
a_out_color.putalpha(out_color_alpha)
a_out_color = Image.alpha_composite(a_out_color, out_color)
# build the colorized out_color image, b_color is the rgb value
# out_color_alpha should provide transparency to see b_color
# we need the alpha channel to be reversed so that the color is transparent
b_color_alpha = clip_gradient_image(ImageOps.invert(luma.convert("L")), 255 - max_time, 255, False)
b_out_color = b_color.copy()
b_out_color.putalpha(b_color_alpha)
b_out_color = Image.alpha_composite(a_out_color, b_out_color)
final_image = Image.alpha_composite(a_color.convert("RGBA"), b_out_color)
end = timer()
print(f"PSLumaWipe2:{end - start}")
return final_image
def PSLumaWipe_images2(start_image: Image, stop_image: Image, luma_wipe_image: Image, num_frames: int, transition_color: tuple[int, int, int, int] = (255,255,255,255)) -> list:
#progress(0, status='Generating luma wipe...')
# fix transition_color to relative 0.0 - 1.0
#luma_color = list(np.divide(transition_color,255))
softness = 0.03
lw_frames = []
lw_frames.append(start_image)
width, height = start_image.size
#compensate for different image sizes for LumaWipe
if (start_image.size != luma_wipe_image.size):
luma_wipe_image = resize_and_crop_image(luma_wipe_image,width,height)
# call PSLumaWipe for each pixel
for i in range(num_frames):
start = timer()
# Compute the luma value for this frame
luma_progress = i / (num_frames - 1)
transition = Image.new(start_image.mode, (width, height))
# call PSLumaWipe for frame
transition = PSLumaWipe2(start_image, stop_image, luma_wipe_image, transition_color, luma_progress, False, softness, 0.01, 0.00)
lw_frames.append(transition)
print(f"Luma Wipe frame:{len(lw_frames)}")
#lw_frames[-1].show()
lw_frames.append(stop_image)
return lw_frames