@@ -294,7 +294,10 @@ def execute(self, mask_opacity, mask_color, filename_prefix="ComfyUI", image=Non
294294 mask_image [:, :, :, 1 ] = g
295295 mask_image [:, :, :, 2 ] = b
296296
297- preview , = ImageCompositeMasked .composite (self , image , mask_image , 0 , 0 , True , mask_adjusted )
297+ if hasattr (ImageCompositeMasked , "execute" ):
298+ preview , = ImageCompositeMasked .execute (image , mask_image , 0 , 0 , True , mask_adjusted )
299+ else :
300+ preview , = ImageCompositeMasked .composite (image , mask_image , 0 , 0 , True , mask_adjusted )
298301
299302 if preview is None :
300303 preview = empty_image (64 , 64 )
@@ -1794,105 +1797,194 @@ class AILab_ImageCompare:
17941797 def __init__ (self ):
17951798 self .font_size = 20
17961799 self .padding = 10
1797- self .bg_color = "white"
1798- self .font_color = "black"
1799- self .text_align = "center"
1800+ #self.text_align = "center"
18001801
18011802 @classmethod
18021803 def INPUT_TYPES (cls ):
18031804 return {
18041805 "required" : {
1806+ "text1" : ("STRING" , {"default" : "Image 1" }),
1807+ "text2" : ("STRING" , {"default" : "Image 2" }),
1808+ "text3" : ("STRING" , {"default" : "Image 3" }),
1809+ "size_base" : (["largest" , "smallest" , "image1" , "image2" , "image3" ], {"default" : "largest" }),
1810+ "text_color" : ("COLORCODE" , {"default" : "#000000" }),
1811+ "bg_color" : ("COLORCODE" , {"default" : "#FFFFFF" }),
1812+ },
1813+ "optional" : {
18051814 "image1" : ("IMAGE" ,),
18061815 "image2" : ("IMAGE" ,),
1807- "text1" : ("STRING" , {"default" : "image 1" }),
1808- "text2" : ("STRING" , {"default" : "image 2" }),
1809- }
1816+ "image3" : ("IMAGE" ,),
1817+ },
18101818 }
18111819
18121820 RETURN_TYPES = ("IMAGE" ,)
18131821 FUNCTION = "generate"
18141822 CATEGORY = "🧪AILab/🖼️IMAGE"
18151823
1816- def get_font (self ) -> ImageFont . FreeTypeFont :
1824+ def get_font (self ):
18171825 try :
1818- if os .name == 'nt' :
1826+ if os .name == "nt" :
18191827 return ImageFont .truetype ("arial.ttf" , self .font_size )
1828+ elif os .path .exists ("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" ):
1829+ return ImageFont .truetype (
1830+ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" ,
1831+ self .font_size ,
1832+ )
18201833 else :
1821- return ImageFont .truetype ("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" , self .font_size )
1822- except :
1823- base_font = ImageFont .load_default ()
1824- scale_factor = self .font_size / 10
1825- return ImageFont .TransposedFont (base_font , scale = scale_factor )
1834+ return ImageFont .load_default ()
1835+ except Exception :
1836+ return ImageFont .load_default ()
18261837
1827- def create_text_panel (self , width : int , text : str ) -> Image . Image :
1838+ def create_text_panel (self , width , text ) :
18281839 font = self .get_font ()
1829-
1830- temp_img = Image .new ('RGB' , (width , self .font_size * 4 ), self .bg_color )
1840+ temp_img = Image .new ("RGB" , (width , self .font_size * 2 ), self .bg_color )
18311841 temp_draw = ImageDraw .Draw (temp_img )
1832-
1833- text_bbox = temp_draw .textbbox ((0 , self .font_size ), text , font = font )
1842+ text_bbox = temp_draw .textbbox ((0 , 0 ), text , font = font )
18341843 text_width = text_bbox [2 ] - text_bbox [0 ]
18351844 text_height = text_bbox [3 ] - text_bbox [1 ]
1836-
1837- final_height = int (text_height * 1.5 )
1838- panel = Image .new ('RGB' , (width , final_height ), self .bg_color )
1845+ final_height = max (int (text_height * 1.5 ), self .font_size * 2 )
1846+ panel = Image .new ("RGB" , (width , final_height ), self .bg_color )
18391847 draw = ImageDraw .Draw (panel )
1840-
18411848 x = (width - text_width ) // 2
18421849 y = (final_height - text_height ) // 2
1843-
1844- draw .text ((x , y ), text , font = font , fill = self .font_color )
1850+ draw .text ((x , y ), text , font = font , fill = self .text_color )
18451851 return panel
18461852
1847- def process_image (self , img : Image .Image , target_size : tuple ) -> Image .Image :
1848- target_width , target_height = target_size
1849- img_width , img_height = img .size
1850-
1851- scale_width = target_width / img_width
1852- scale_height = target_height / img_height
1853- scale = max (scale_width , scale_height )
1854-
1855- new_width = int (img_width * scale )
1856- new_height = int (img_height * scale )
1857-
1858- resized = resize_image (img , new_width , new_height )
1859- left = (new_width - target_width ) // 2
1860- top = (new_height - target_height ) // 2
1861- right = left + target_width
1862- bottom = top + target_height
1863-
1864- return resized .crop ((left , top , right , bottom ))
1853+ def _select_base_image (self , pil_map , size_base ):
1854+ if size_base in ("image1" , "image2" , "image3" ) and size_base in pil_map :
1855+ return size_base
1856+ if size_base == "smallest" :
1857+ best_label = None
1858+ best_area = float ('inf' )
1859+ for label , img in pil_map .items ():
1860+ area = img .width * img .height
1861+ if area < best_area :
1862+ best_area = area
1863+ best_label = label
1864+ return best_label
1865+ if size_base != "largest" :
1866+ print (
1867+ f"Warning: size_base '{ size_base } ' is not available, fallback to 'largest'."
1868+ )
1869+ best_label = None
1870+ best_area = - 1
1871+ for label , img in pil_map .items ():
1872+ area = img .width * img .height
1873+ if area > best_area :
1874+ best_area = area
1875+ best_label = label
1876+ return best_label
1877+
1878+ def generate (self , text1 , text2 , text3 , size_base = "largest" , text_color = "#000000" , bg_color = "#FFFFFF" , image1 = None , image2 = None , image3 = None ,):
1879+ self .bg_color = bg_color
1880+ self .text_color = text_color
1881+
1882+ tensors = []
1883+ texts = []
1884+ labels = []
1885+
1886+ if image1 is not None and hasattr (image1 , "shape" ) and image1 .shape [0 ] > 0 :
1887+ tensors .append (image1 )
1888+ texts .append (text1 )
1889+ labels .append ("image1" )
1890+ if image2 is not None and hasattr (image2 , "shape" ) and image2 .shape [0 ] > 0 :
1891+ tensors .append (image2 )
1892+ texts .append (text2 )
1893+ labels .append ("image2" )
1894+ if image3 is not None and hasattr (image3 , "shape" ) and image3 .shape [0 ] > 0 :
1895+ tensors .append (image3 )
1896+ texts .append (text3 )
1897+ labels .append ("image3" )
1898+
1899+ if len (tensors ) < 2 :
1900+ print ("Warning: At least two images are required." )
1901+ return (torch .zeros ((1 , 64 , 64 , 3 )),)
18651902
1866- def generate (self , image1 , image2 , text1 , text2 ):
1867- img1 = tensor2pil (image1 )
1868- img2 = tensor2pil (image2 )
1869-
1870- if img2 .size != img1 .size :
1871- img2 = resize_image (img2 , img1 .size [0 ], img1 .size [1 ])
1872-
1873- panel1 = None if not text1 .strip () else self .create_text_panel (img1 .width , text1 )
1874- panel2 = None if not text2 .strip () else self .create_text_panel (img2 .width , text2 )
1875-
1876- total_width = img1 .width + img2 .width + self .padding * 3
1877- img_height = img1 .height
1878- panel_height = (panel1 .height if panel1 else 0 ) if (panel1 or panel2 ) else 0
1879- total_height = img_height + (panel_height + self .padding if panel_height > 0 else 0 ) + self .padding * 2
1880-
1881- result = Image .new ('RGB' , (total_width , total_height ), self .bg_color )
1903+ batch_sizes = [t .shape [0 ] for t in tensors ]
1904+ batch_size = min (batch_sizes )
1905+ if len (set (batch_sizes )) > 1 :
1906+ print (
1907+ f"Warning: Input batches have different sizes { batch_sizes } . "
1908+ f"Only processing the minimum size of { batch_size } ."
1909+ )
18821910
1883- x1 = self .padding
1884- x2 = x1 + img1 .width + self .padding
1885- y = self .padding
1886-
1887- result .paste (img1 , (x1 , y ))
1888- result .paste (img2 , (x2 , y ))
1889-
1890- if panel1 :
1891- result .paste (panel1 , (x1 , y + img_height + self .padding ))
1892- if panel2 :
1893- result .paste (panel2 , (x2 , y + img_height + self .padding ))
1894-
1895- return (pil2tensor (result ),)
1911+ output_images = []
1912+
1913+ for i in range (batch_size ):
1914+ pil_map = {}
1915+ for t , label in zip (tensors , labels ):
1916+ frame = t [i ].unsqueeze (0 )
1917+ img_pil = tensor2pil (frame )
1918+ pil_map [label ] = img_pil
1919+
1920+ base_label = self ._select_base_image (pil_map , size_base )
1921+ base_img = pil_map [base_label ]
1922+ base_h = base_img .height
1923+
1924+ resized_pils = []
1925+ for label in labels :
1926+ img = pil_map [label ]
1927+ w , h = img .size
1928+
1929+ if w == 0 or h == 0 :
1930+ resized_pils .append (Image .new ("RGB" , (1 , 1 ), self .bg_color ))
1931+ continue
1932+
1933+ if label == base_label :
1934+ resized_pils .append (img )
1935+ continue
1936+
1937+ scale = base_h / h
1938+ new_h = base_h
1939+ new_w = max (1 , int (round (w * scale )))
1940+
1941+ if img .size != (new_w , new_h ):
1942+ img = resize_image (img , new_w , new_h )
1943+ resized_pils .append (img )
1944+
1945+ panels = []
1946+ for img , text in zip (resized_pils , texts ):
1947+ if text .strip ():
1948+ panels .append (self .create_text_panel (img .width , text ))
1949+ else :
1950+ panels .append (None )
1951+
1952+ total_width = (
1953+ sum (img .width for img in resized_pils )
1954+ + self .padding * (len (resized_pils ) + 1 )
1955+ )
1956+ img_height = max (img .height for img in resized_pils )
1957+
1958+ panel_heights = [p .height for p in panels if p is not None ]
1959+ panel_height = max (panel_heights ) if panel_heights else 0
1960+ panel_area_height = (panel_height + self .padding ) if panel_height > 0 else 0
1961+
1962+ total_height = img_height + panel_area_height + self .padding * 2
1963+
1964+ result_pil = Image .new ("RGB" , (total_width , total_height ), self .bg_color )
1965+
1966+ y_img = self .padding
1967+ y_panel = y_img + img_height + self .padding
1968+
1969+ x = self .padding
1970+ for img , panel in zip (resized_pils , panels ):
1971+ result_pil .paste (img , (x , y_img ))
1972+ if panel is not None and panel_height > 0 :
1973+ y_offset = (
1974+ y_panel + (panel_height - panel .height )
1975+ if panel .height < panel_height
1976+ else y_panel
1977+ )
1978+ result_pil .paste (panel , (x , y_offset ))
1979+ x += img .width + self .padding
1980+
1981+ output_images .append (pil2tensor (result_pil ))
1982+
1983+ if not output_images :
1984+ return (torch .zeros ((1 , 64 , 64 , 3 )),)
1985+
1986+ final_batch_tensor = torch .cat (output_images , dim = 0 )
1987+ return (final_batch_tensor ,)
18961988
18971989# Color Input node
18981990class AILab_ColorInput :
@@ -2177,3 +2269,4 @@ def resize(self, image, width, height, scale_by, upscale_method, resize_mode, pa
21772269 "AILab_ColorInput" : "Color Input (RMBG) 🎨" ,
21782270 "AILab_ImageMaskResize" : "Image Mask Resize (RMBG) 🖼️🎭"
21792271}
2272+
0 commit comments