@@ -62,30 +62,30 @@ def pil2tensor_mask(pil):
6262toTensor = transforms .ToTensor ()
6363
6464
65- def pil2tensor (pil ):
65+ def pil_to_tensor (pil ):
6666 # [C, H, W] to [H, W, C]
6767 return toTensor (pil ).permute (1 , 2 , 0 )
6868
6969
70- def tensor2pil (tensor ):
70+ def tensor_to_pil (tensor ):
7171 # [H, W, C] to [C, H, W]
7272 return toPIL (tensor .permute (2 , 0 , 1 ))
7373
7474
75- def tensor2batch (tensor , h , w , c ):
75+ def tensor_to_batch (tensor , h , w , c ):
7676 tensor = torch .cat (tensor )
7777 tensor = tensor .reshape (- 1 , h , w , c )
7878 return tensor
7979
8080
81- def batch2list (tensor_batch ):
81+ def batch_to_list (tensor_batch ):
8282 tensors = []
8383 for i in range (tensor_batch .shape [0 ]):
8484 tensors .append (tensor_batch [i ])
8585 return tensors
8686
8787
88- def list2batch (tensor_list ):
88+ def list_to_batch (tensor_list ):
8989 return torch .cat (tensor_list )
9090
9191
@@ -102,6 +102,14 @@ def load_pil_from_url(url):
102102 return pil , name
103103
104104
105+ def pil_to_np (pilimage ):
106+ return np .array (pilimage ) / 255
107+
108+
109+ def np_to_pil (image ):
110+ return Image .fromarray ((image * 255 ).astype ("uint8" ))
111+
112+
105113class DownloadImageNode :
106114 def __init__ (self ):
107115 self .output_dir = folder_paths .get_temp_directory ()
@@ -180,7 +188,7 @@ def INPUT_TYPES(cls):
180188
181189 def function (self , image , directory , name , type , compress_level ):
182190 for i in image :
183- pil = tensor2pil (i )
191+ pil = tensor_to_pil (i )
184192 name = f"{ name } .{ type } "
185193 pil .save (os .path .join (directory , name ), compress_level = compress_level )
186194
@@ -371,7 +379,7 @@ def INPUT_TYPES(cls):
371379 def node_function (self , url , channels ):
372380 pil , name = load_pil_from_url (url )
373381 pil .convert (channels )
374- img_out = pil2tensor (pil )
382+ img_out = pil_to_tensor (pil )
375383 return (img_out , img_out , name )
376384
377385
@@ -403,7 +411,7 @@ def load_image_to_tensor(directory, recursive, channels):
403411 elif pil .mode == "RGB" and channels == "RGBA" :
404412 pil = pil .convert ("RGBA" )
405413
406- image_tensor = pil2tensor (pil )
414+ image_tensor = pil_to_tensor (pil )
407415 image_tensor = image_tensor .unsqueeze (0 ) # Add batch dimension
408416 image_tensors .append (image_tensor )
409417
@@ -472,7 +480,7 @@ def node_function(self, directory, recursive, channels):
472480 raise Exception ("folder_path is not valid: " + directory )
473481
474482 (out_image , out_dir , out_name ) = load_image_to_tensor (directory , recursive , channels )
475- out_image = list2batch (out_image )
483+ out_image = list_to_batch (out_image )
476484
477485 return (out_image , out_dir , out_name )
478486
@@ -500,7 +508,7 @@ def INPUT_TYPES(cls):
500508
501509 def fill_alpha (self , tensor , alpha_threshold , fill_color ):
502510
503- pil = tensor2pil (tensor )
511+ pil = tensor_to_pil (tensor )
504512 if pil .mode != "RGBA" :
505513 raise Exception ("Image mode is not RGBA" )
506514
@@ -516,7 +524,7 @@ def fill_alpha(self, tensor, alpha_threshold, fill_color):
516524
517525 new_pil = Image .new ("RGB" , pil .size )
518526 new_pil .putdata (new_pixels )
519- image_tensor = pil2tensor (new_pil )
527+ image_tensor = pil_to_tensor (new_pil )
520528 return image_tensor
521529
522530 def node_function (self , image , alpha_threshold , r , g , b ):
@@ -558,7 +566,7 @@ def INPUT_TYPES(cls):
558566 CATEGORY = "Fair/image"
559567
560568 def function (self , image ):
561- pil = tensor2pil (image )
569+ pil = tensor_to_pil (image )
562570 encoded_base64 = pil_to_base64 (pil )
563571 return (encoded_base64 ,)
564572
@@ -602,7 +610,7 @@ def INPUT_TYPES(cls):
602610
603611 def function (self , string ):
604612 pil = base64_to_pil (string )
605- image = pil2tensor (pil )
613+ image = pil_to_tensor (pil )
606614 return (image ,)
607615
608616
@@ -800,3 +808,77 @@ def INPUT_TYPES(cls):
800808 def node_function (self , images ):
801809 width , height , length = (images .shape [2 ], images .shape [1 ], images .shape [0 ])
802810 return (width , height , length )
811+
812+
813+ class DitherNode :
814+ def __init__ (self ):
815+ pass
816+
817+ @classmethod
818+ def INPUT_TYPES (cls ):
819+ return {
820+ "required" : {
821+ "images" : (IO .IMAGE , {"defaultInput" : True }),
822+ "dither" : (["Modulation" , "Floyd–Steinberg" , "Halftone" ], {"default" : "Modulation" }),
823+ }
824+ }
825+
826+ FUNCTION = "node_function"
827+ CATEGORY = "Fair/image"
828+ RETURN_TYPES = (IO .IMAGE ,)
829+ RETURN_NAMES = ("images" ,)
830+
831+ def floyd_steinberg (self , image ):
832+ # image: np.array of shape (height, width), dtype=float, 0.0-1.0
833+ # works in-place!
834+ h , w = image .shape
835+ for y in range (h ):
836+ for x in range (w ):
837+ old = image [y , x ]
838+ new = np .round (old )
839+ image [y , x ] = new
840+ error = old - new
841+ # precomputing the constants helps
842+ if x + 1 < w :
843+ image [y , x + 1 ] += error * 0.4375 # right, 7 / 16
844+ if (y + 1 < h ) and (x + 1 < w ):
845+ image [y + 1 , x + 1 ] += error * 0.0625 # right, down, 1 / 16
846+ if y + 1 < h :
847+ image [y + 1 , x ] += error * 0.3125 # down, 5 / 16
848+ if (x - 1 >= 0 ) and (y + 1 < h ):
849+ image [y + 1 , x - 1 ] += error * 0.1875 # left, down, 3 / 16
850+ return image
851+
852+ def modulation (self , image ):
853+ # image: np.array of shape (height, width), dtype=float, 0.0-1.0
854+ # works in-place!
855+ h , w = image .shape
856+ for y in range (h ):
857+ for x in range (w ):
858+ old = image [y , x ]
859+ new = np .round (old )
860+ image [y , x ] = new
861+ error = old - new
862+ # precomputing the constants helps
863+ if x + 1 < w :
864+ image [y , x + 1 ] += error * 1 # right
865+ return image
866+
867+ def ErrorDiffusionDithering (self , pil ):
868+ # 转换为黑白255
869+ paletted = pil .convert ("1" , matrix = Image .FLOYDSTEINBERG )
870+ paletted = paletted .convert ("RGB" )
871+ return paletted
872+
873+ def node_function (self , images , dither ):
874+ out_images = []
875+
876+ for image in images :
877+ pil = tensor_to_pil (image ).convert ("L" )
878+ img_np = pil_to_np (pil )
879+ img_np = self .modulation (img_np )
880+ image = torch .from_numpy (img_np )
881+ out_images .append (image )
882+
883+ out_images = torch .stack (out_images )
884+ return (out_images ,)
0 commit comments