3 Commits
Author SHA1 Message Date
Morgan Johansson 96e5fa3ae8 Contrast analysis/adjustment 2023-09-16 06:24:56 +02:00
Morgan Johansson 4418ba9454 version 2023-09-13 18:54:10 +02:00
Morgan Johansson fefe43ebe3 removed junk text 2023-09-13 18:53:08 +02:00
8 changed files with 225 additions and 25 deletions
+3 -2
View File
@@ -26,12 +26,13 @@ _NODE_CLASSES: List[Type] = [DreamSineWave, DreamLinear, DreamCSVCurve, DreamBea
DreamFrameCountCalculator, DreamBigImageSwitch, DreamBigTextSwitch, DreamBigFloatSwitch,
DreamBigIntSwitch, DreamBigPaletteSwitch, DreamWeightedPromptBuilder, DreamPromptFinalizer,
DreamFrameCounterInfo, DreamBoolToFloat, DreamBoolToInt, DreamSawWave, DreamTriangleWave,
DreamTriangleEvent, DreamSmoothEvent, DreamCalculation]
DreamTriangleEvent, DreamSmoothEvent, DreamCalculation, DreamImageColorShift,
DreamComparePalette, DreamImageContrast, DreamImageBrightness]
_SIGNATURE_SUFFIX = " [Dream]"
MANIFEST = {
"name": "Dream Project Animation",
"version": (3, 1, 0),
"version": (3, 2, 0),
"author": "Dream Project",
"project": "https://github.com/alt-key-project/comfyui-dream-project",
"description": "Various utility nodes for creating animations with ComfyUI",
+2 -1
View File
@@ -10,6 +10,7 @@ from .shared import hashed_as_strings
class DreamCalculation:
NODE_NAME = "Calculation"
ICON = "🖩"
@classmethod
def INPUT_TYPES(cls):
@@ -27,7 +28,7 @@ class DreamCalculation:
}
}
CATEGORY = NodeCategories.ANIMATION_CURVES
CATEGORY = NodeCategories.UTILS
RETURN_TYPES = ("FLOAT", "INT")
RETURN_NAMES = ("FLOAT", "INT")
FUNCTION = "result"
+152 -9
View File
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from .categories import NodeCategories
from .shared import *
from .types import *
@@ -128,7 +129,7 @@ class DreamColorAlign:
def INPUT_TYPES(cls):
return {
"required": SharedTypes.palette | {
"target_align": (RGBPalette.ID, ),
"target_align": (RGBPalette.ID,),
"alignment_factor": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 10.0, "step": 0.1}),
}
}
@@ -151,8 +152,8 @@ class DreamColorAlign:
for i in range(len(palette)):
p = palette[i]
t = target_align[i]
(_, r1, g1, b1) = p.analyze()
(_, r2, g2, b2) = t.analyze()
(_, _, r1, g1, b1) = p.analyze()
(_, _, r2, g2, b2) = t.analyze()
dr = (r2 - r1) * alignment_factor
dg = (g2 - g1) * alignment_factor
@@ -214,9 +215,150 @@ class DreamColorShift:
return (tuple(results),)
class DreamImageColorShift:
NODE_NAME = "Image Color Shift"
ICON = "🖼"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {"image": ("IMAGE",),
"red_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0}),
"green_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0}),
"blue_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0}),
},
}
CATEGORY = NodeCategories.IMAGE_COLORS
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def result(self, image, red_multiplier, green_multiplier, blue_multiplier):
proc = DreamImageProcessor(inputs=image)
def recolor(im: DreamImage, *a, **args):
return (im.adjust_colors(red_multiplier, green_multiplier, blue_multiplier),)
return proc.process(recolor)
class DreamImageBrightness:
NODE_NAME = "Image Brightness Adjustment"
ICON = "☼"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {"image": ("IMAGE",),
"factor": ("FLOAT", {"default": 1.0, "min": 0.0}),
},
}
CATEGORY = NodeCategories.IMAGE_COLORS
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def result(self, image, factor):
proc = DreamImageProcessor(inputs=image)
def change(im: DreamImage, *a, **args):
return (im.change_brightness(factor),)
return proc.process(change)
class DreamImageContrast:
NODE_NAME = "Image Contrast Adjustment"
ICON = "◐"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {"image": ("IMAGE",),
"factor": ("FLOAT", {"default": 1.0, "min": 0.0}),
},
}
CATEGORY = NodeCategories.IMAGE_COLORS
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def result(self, image, factor):
proc = DreamImageProcessor(inputs=image)
def change(im: DreamImage, *a, **args):
return (im.change_contrast(factor),)
return proc.process(change)
class DreamComparePalette:
NODE_NAME = "Compare Palettes"
ICON = "📊"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"a": (RGBPalette.ID,),
"b": (RGBPalette.ID,),
},
}
CATEGORY = NodeCategories.IMAGE_COLORS
RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT", "FLOAT")
RETURN_NAMES = (
"brightness_multiplier", "contrast_multiplier", "red_multiplier", "green_multiplier", "blue_multiplier")
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def result(self, a, b):
MIN_VALUE = 1 / 255.0
brightness = list()
contrasts = list()
reds = list()
greens = list()
blues = list()
for i in range(min(len(a), len(b))):
(bright, ctr, red, green, blue) = a[i].analyze()
(bright2, ctr2, red2, green2, blue2) = b[i].analyze()
brightness.append(bright2 / max(MIN_VALUE, bright))
contrasts.append(ctr2 / max(MIN_VALUE, ctr))
reds.append(red2 / max(MIN_VALUE, red))
greens.append(green2 / max(MIN_VALUE, green))
blues.append(blue2 / max(MIN_VALUE, blue))
n = len(brightness)
return (sum(brightness) / n, sum(contrasts) / n, sum(reds) / n,
sum(greens) / n, sum(blues) / n)
class DreamAnalyzePalette:
NODE_NAME = "Analyze Palette"
NODE = "📊"
ICON = "📊"
@classmethod
def INPUT_TYPES(cls):
@@ -226,8 +368,8 @@ class DreamAnalyzePalette:
}
CATEGORY = NodeCategories.IMAGE_COLORS
RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT", "FLOAT")
RETURN_NAMES = ("brightness", "redness", "greenness", "blueness")
RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT", "FLOAT", "FLOAT")
RETURN_NAMES = ("brightness", "contrast", "redness", "greenness", "blueness")
FUNCTION = "result"
@classmethod
@@ -236,12 +378,13 @@ class DreamAnalyzePalette:
def result(self, palette):
f = 1.0 / len(palette)
(w, r, g, b) = (0, 0, 0, 0)
(w, c, r, g, b) = (0, 0, 0, 0, 0)
for p in palette:
(brightness, red, green, blue) = p.analyze()
(brightness, contrast, red, green, blue) = p.analyze()
w += brightness
c += contrast
r += red
g += green
b += blue
return w * f, r * f, g * f, b * f
return w * f, c * f, r * f, g * f, b * f
+6 -2
View File
@@ -1,5 +1,5 @@
{
"Analyze Palette [Dream]": "Output brightness, red, green and blue averages of a palette",
"Analyze Palette [Dream]": "Output brightness, contrast, red, green and blue averages of a palette",
"Beat Curve [Dream]": "Beat pattern curve with impulses at specified beats of a measure",
"Big Float Switch [Dream]": "Switch for up to 10 inputs",
"Big Image Switch [Dream]": "Switch for up to 10 inputs",
@@ -14,6 +14,7 @@
"CSV Generator [Dream]": "CSV output, mainly for debugging purposes",
"Calculation [Dream]": "Mathematical calculation node",
"Common Frame Dimensions [Dream]": "Utility for calculating good width/height based on common video dimensions",
"Compare Palettes [Dream]": "Analyses two palettes producing the factor for each color channel",
"FFMPEG Video Encoder [Dream]": "Post processing for animation sequences calling FFMPEG to generate video file",
"File Count [Dream]": "Finds the number of files in a directory matching specified patterns",
"Finalize Prompt [Dream]": "Used in conjunction with 'Build Prompt'",
@@ -23,6 +24,9 @@
"Frame Counter (Simple) [Dream]": "Integer value used as frame counter",
"Frame Counter Info [Dream]": "Extracts information from the frame counter",
"Frame Counter Offset [Dream]": "Adds an offset to a frame counter",
"Image Brightness Adjustment [Dream]": "Adjusts the brightness of an image by a factor",
"Image Color Shift [Dream]": "Adjust the colors (or brightness) of an image",
"Image Contrast Adjustment [Dream]": "Adjusts the contrast of an image by a factor",
"Image Motion [Dream]": "Node supporting zooming in/out and translating an image",
"Image Sequence Blend [Dream]": "Post processing for animation sequences blending frame for a smoother blurred effect",
"Image Sequence Loader [Dream]": "Loads a frame from a directory of images",
@@ -44,4 +48,4 @@
"Triangle Curve [Dream]": "Triangle wave curve",
"Triangle Event Curve [Dream]": "Single event/peak curve with triangular shape",
"Video Encoder (mpegCoder) [Dream]": "Post processing for animation sequences using mpegCoder module to generate video file"
} and switches.
}
+12
View File
@@ -147,6 +147,9 @@ Mathematical calculation node. Exposes most of the mathematical functions in the
[math module](https://docs.python.org/3/library/math.html), mathematical operators as well as round, abs, int,
float, max and min.
### Compare Palettes [Dream]
Analyses two palettes and produces the quotient for each individual channel (b/a) and brightness.
### CSV Curve [Dream]
CSV input curve where first column is frame or second and second column is value.
@@ -182,6 +185,15 @@ Extracts information from the frame counter.
### Frame Counter Offset [Dream]
Adds an offset to a frame counter.
### Image Brightness Adjustment [Dream]
Adjusts the brightness of an image by a factor.
### Image Color Shift [Dream]
Allows changing the colors of an image with a multiplier for each channel (RGB).
### Image Contrast Adjustment [Dream]
Adjusts the contrast of an image by a factor.
### Image Motion [Dream]
Node supporting zooming in/out and translating an image.
+3 -5
View File
@@ -4,9 +4,7 @@ import shutil
import subprocess
import tempfile
from functools import lru_cache
from PIL import Image
from PIL import Image as PilImage
from .categories import NodeCategories
from .err import on_error
from .shared import DreamConfig, MpegEncoderUtility
@@ -16,8 +14,8 @@ CONFIG = DreamConfig()
@lru_cache(5)
def _load_image_cached(filename) -> Image:
return Image.open(filename)
def _load_image_cached(filename):
return PilImage.open(filename)
class TempFileSet:
+22 -4
View File
@@ -10,13 +10,13 @@ import folder_paths as comfy_paths
import glob
import numpy
import torch
from PIL import Image, ImageFilter
from PIL import Image, ImageFilter, ImageEnhance
from PIL.ImageDraw import ImageDraw
from PIL.PngImagePlugin import PngInfo
from .embedded_config import EMBEDDED_CONFIGURATION
from typing import Dict, Tuple, List
from .dreamlogger import DreamLog
from .embedded_config import EMBEDDED_CONFIGURATION
NODE_FILE = os.path.abspath(__file__)
DREAM_NODES_SOURCE_ROOT = os.path.dirname(NODE_FILE)
@@ -151,6 +151,14 @@ class DreamImage:
self.size = self.pil_image.size
self._draw = ImageDraw(self.pil_image)
def change_brightness(self, factor):
enhancer = ImageEnhance.Brightness(self.pil_image)
return DreamImage(pil_image=enhancer.enhance(factor))
def change_contrast(self, factor):
enhancer = ImageEnhance.Contrast(self.pil_image)
return DreamImage(pil_image=enhancer.enhance(factor))
def numpy_array(self):
return numpy.array(self.pil_image)
@@ -195,6 +203,15 @@ class DreamImage:
def blur(self, amount):
return DreamImage(pil_image=self.pil_image.filter(ImageFilter.GaussianBlur(amount)))
def adjust_colors(self, red_factor=1.0, green_factor=1.0, blue_factor=1.0):
# newRed = 1.1*oldRed + 0*oldGreen + 0*oldBlue + constant
# newGreen = 0*oldRed + 0.9*OldGreen + 0*OldBlue + constant
# newBlue = 0*oldRed + 0*OldGreen + 1*OldBlue + constant
matrix = (red_factor, 0, 0, 0,
0, green_factor, 0, 0,
0, 0, blue_factor, 0)
return DreamImage(pil_image=self.pil_image.convert("RGB", matrix))
def get_pixel(self, x, y):
p = self.pil_image.getpixel((x, y))
if len(p) == 4:
@@ -360,7 +377,7 @@ class MpegEncoderUtility:
self._logger = get_logger()
self._enc = mpegCoder.MpegEncoder()
bit_rate = self._calculate_bit_rate(width, height, fps, bit_rate_factor)
self._logger.info("Bitrate "+str(bit_rate))
self._logger.info("Bitrate " + str(bit_rate))
self._enc.setParameter(
videoPath=video_path, codecName=codec_name,
nthread=encoding_threads, bitRate=bit_rate, width=width, height=height, widthSrc=width,
@@ -368,7 +385,7 @@ class MpegEncoderUtility:
GOPSize=len(files), maxBframe=max_b_frame, frameRate=self._fps_to_tuple(fps))
def _calculate_bit_rate(self, width: int, height: int, fps: float, bit_rate_factor: float):
bits_per_pixel_base = 0.075
bits_per_pixel_base = 0.125
return round(max(10, float(width * height * fps * bits_per_pixel_base * bit_rate_factor * 0.001)))
def encode(self):
@@ -390,6 +407,7 @@ class MpegEncoderUtility:
def _fps_to_tuple(self, fps: float):
def _is_almost_int(f: float):
return abs(f - int(f)) < 0.001
a = fps
b = 1
while not _is_almost_int(a) and b < 100:
+25 -2
View File
@@ -26,6 +26,24 @@ class RGBPalette:
for c in colors:
self._colors.append(_fix_tuple(c))
def _calculate_channel_contrast(self, c):
hist = list(map(lambda _: 0, range(16)))
for pixel in self._colors:
hist[pixel[c] // 16] += 1
s = 0
max_possible = (15 - 0) * (len(self) // 2) * (len(self) // 2)
for i in range(16):
for j in range(i):
if i != j:
s += abs(i - j) * hist[i] * hist[j]
return s / max_possible
def _calculate_combined_contrast(self):
s = 0
for c in range(3):
s += self._calculate_channel_contrast(c)
return s / 3
def analyze(self):
total_red = 0
total_blue = 0
@@ -38,7 +56,7 @@ class RGBPalette:
r = float(total_red) / (255 * n)
g = float(total_green) / (255 * n)
b = float(total_blue) / (255 * n)
return ((r + g + b) / 3.0, r, g, b)
return ((r + g + b) / 3.0, self._calculate_combined_contrast(), r, g, b)
def __len__(self):
return len(self._colors)
@@ -73,7 +91,12 @@ class PartialPrompt:
def add(self, text: str, weight: float):
output = PartialPrompt()
output._data = dict(self._data)
output._data[text.strip()] = weight
for parts in text.split(","):
parts = parts.strip()
if " " in parts:
output._data["(" + parts + ")"] = weight
else:
output._data[parts] = weight
return output
def is_empty(self):