diff --git a/README.MD b/README.MD index 146d76d..dc76d0a 100644 --- a/README.MD +++ b/README.MD @@ -13,6 +13,7 @@ Nodes are divided into 5 groups according to their functions: LayerStyle, LayerC [中文说明点这里](./README_CN.MD) ## Update +* Commit [TextImage](#TextImage) node, it generate text images and masks. * Add new types of [blend mode](#blend) between images. now supports up to 19 blend modes. add **color_burn, color_dodge, linear_burn, linear_dodge, overlay, soft_light, hard_light, vivid_light, pin_light, linear_light** and **hard_mix**. The newly added blend mode is applicable to all nodes that support blend mode. * Commit [ColorMap](#ColorMap) filter node to create a pseudo color heatmap effect. @@ -205,7 +206,7 @@ Node options: * LUT*: Here is a list of available. cube files in the LUT folder, and the selected LUT files will be applied to the image. *LUT folder is defined in resource_dir.ini, this file is located in the root directory of the plugin. -Open the text editing software and find the line starting with "LUT dir=", after "=", enter the custom folder path name. all .cube files in this folder will be collected and displayed in the node list during ComfyUI initialization. +Open the text editing software and find the line starting with "LUT_dir=", after "=", enter the custom folder path name. all .cube files in this folder will be collected and displayed in the node list during ComfyUI initialization. If the folder set in ini is invalid, the LUT folder that comes with the plugin will be enabled. ### ColorAdapter @@ -269,98 +270,6 @@ Node options: * V: V channel. -# LayerMask -![image](image/layermask_nodes.png) - -### MaskBoxDetect -Detect the area where the mask is located and output its position and size. -![image](image/mask_box_detect_example.png) - -Node options: -![image](image/mask_box_detect_node.png) -* detect: Detection method, min_bounding_rect is the minimum bounding rectangle, max_inscribed_rect is the maximum inscribed rectangle. -* x_adjust: Adjust of horizontal deviation after detection. -* y_adjust: Adjust of vertical offset after detection. -* scale_adjust: Adjust the scaling offset after detection. - -Output: -* box_preview: Preview image of detection results. Red represents the detected result, and green represents the adjust output result. -* x_percent: Horizontal position output in percentage. -* y_percent: Vertical position output in percentage. -* width: Width. -* height: Height. -* x: The x-coordinate of the top left corner position. -* y: The y-coordinate of the top left corner position. - - -### MaskGrow -Grow and shrink edges and blur the mask -![image](image/mask_grow_example.png) - -Node options: -![image](image/mask_grow_node.png) -* invert_mask: Whether to reverse the mask. -* grow: Positive values expand outward, while negative values contract inward. -* blur: Blur the edge. - -### MaskEdgeShrink -Smooth transition and shrink the mask edges while preserving edge details. -![image](image/mask_edge_shrink_example.png) - -Node options: -![image](image/mask_edge_shrink_node.png) -* invert_mask: Whether to reverse the mask. -* shrink_level: Shrink the smoothness level. -* soft: Smooth amplitude. -* edge_shrink: Edge shrinkage amplitude. -* edge_reserve: Preserve the amplitude of edge details, 100 represents complete preservation, and 0 represents no preservation at all. - -Comparison of MaskGrow and MaskEdgeShrink -![image](image/mask_edge_compare.png) - -### MaskMotionBlur -Create motion blur on the mask. -![image](image/mask_motion_blur_example.png) - -Node options: -![image](image/mask_motion_blur_node.png) -* invert_mask: Whether to reverse the mask. -* blur: The size of blur. -* angle: The angle of blur. - -### MaskGradient -Create a gradient for the mask from one side. -![image](image/mask_gradient_example.png) - -Node options: -![image](image/mask_gradient_node.png) -* invert_mask: Whether to reverse the mask. -* gradient_side: Generate gradient from which edge. There are four directions: top, bottom, left and right. -* gradient_scale: Gradient distance. The default value of 100 indicates that one side of the gradient is completely transparent and the other side is completely opaque. The smaller the value, the shorter the distance from transparent to opaque. -* gradient_offset: Gradient position offset. -* opacity: The opacity of the gradient. - -### MaskStroke -Generate mask contour strokes. -![image](image/mask_stroke_example.png) - -Node options: -![image](image/mask_stroke_node.png) -* invert_mask: Whether to reverse the mask. -* stroke_grow: Stroke expansion/contraction amplitude, positive values indicate expansion and negative values indicate contraction. -* stroke_width: Stroke width. -* blur: Blur of stroke. - -### MaskPreview -Preview the input mask -![image](image/mask_invert.png) - - -### MaskInvert -Invert the mask -![image](image/mask_invert_node.png) - - # LayerUtility ![image](image/layerutility_nodes.png) @@ -573,6 +482,33 @@ Node options: * start_color4: Color of the begging. * end_color4: Color of the ending. +### TextImage +![image](image/text_image_example.png) +Generate images and masks from text. support for adjusting the spacing between words and lines, horizontal and vertical adjustments, it can set random changes in each character, including size and position. + +![image](image/text_image_node.png) +Node options: +* size_as*: The input image or mask here will generate the output image and mask according to their size. +* font_file**: Here is a list of available font files in the font folder, and the selected font files will be used to generate images. +* spacing: Word spacing. +* leading: Row leading. +* horizontal_border: Side margin. If the text is horizontal, it is the left margin, and if it is vertical, it is the right margin. +* vertical_border: Top margin. +* scale: The overall size of the text. the initial size of text is automatically calculated based on the screen size and text content, with the longest row or column by default adapting to the image width or height. adjusting the value here will scale the text as a whole. +* variation_range: The range of random changes in characters. when this value is greater than 0, the character will undergo random changes in size and position, and the larger the value, the greater the magnitude of the change. +* variation_seed: The seed for randomly. fix this value to individual characters changes generated each time will not change. +* layout: Text layout. there are horizontal and vertical options to choose from. +* width; Width of the image. If there is a size_as input, this setting will be ignored. +* height; Height of the image. If there is a size_as input, this setting will be ignored. +* text_color: The color of text. +* background_color: The color of background. + +*Only limited to input image and mask. forcing the integration of other types of inputs will result in node errors. +**The font folder is defined in resource_dir.ini, this file is located in the root directory of the plugin. +Open the text editing software and find the line starting with "FONT_dir=", after "=", enter the custom folder path name. all font files in this folder will be collected and displayed in the node list during ComfyUI initialization. +If the folder set in ini is invalid, the font folder that comes with the plugin will be enabled. + + ### ImageChannelSplit ![image](image/image_channel_split_example.png) Split the image channel into individual images. @@ -601,6 +537,99 @@ Used to provide assistance for workflow debugging. When running, the properties This node allows any type of input. +# LayerMask +![image](image/layermask_nodes.png) + +### MaskBoxDetect +Detect the area where the mask is located and output its position and size. +![image](image/mask_box_detect_example.png) + +Node options: +![image](image/mask_box_detect_node.png) +* detect: Detection method, min_bounding_rect is the minimum bounding rectangle, max_inscribed_rect is the maximum inscribed rectangle. +* x_adjust: Adjust of horizontal deviation after detection. +* y_adjust: Adjust of vertical offset after detection. +* scale_adjust: Adjust the scaling offset after detection. + +Output: +* box_preview: Preview image of detection results. Red represents the detected result, and green represents the adjust output result. +* x_percent: Horizontal position output in percentage. +* y_percent: Vertical position output in percentage. +* width: Width. +* height: Height. +* x: The x-coordinate of the top left corner position. +* y: The y-coordinate of the top left corner position. + + +### MaskGrow +Grow and shrink edges and blur the mask +![image](image/mask_grow_example.png) + +Node options: +![image](image/mask_grow_node.png) +* invert_mask: Whether to reverse the mask. +* grow: Positive values expand outward, while negative values contract inward. +* blur: Blur the edge. + +### MaskEdgeShrink +Smooth transition and shrink the mask edges while preserving edge details. +![image](image/mask_edge_shrink_example.png) + +Node options: +![image](image/mask_edge_shrink_node.png) +* invert_mask: Whether to reverse the mask. +* shrink_level: Shrink the smoothness level. +* soft: Smooth amplitude. +* edge_shrink: Edge shrinkage amplitude. +* edge_reserve: Preserve the amplitude of edge details, 100 represents complete preservation, and 0 represents no preservation at all. + +Comparison of MaskGrow and MaskEdgeShrink +![image](image/mask_edge_compare.png) + +### MaskMotionBlur +Create motion blur on the mask. +![image](image/mask_motion_blur_example.png) + +Node options: +![image](image/mask_motion_blur_node.png) +* invert_mask: Whether to reverse the mask. +* blur: The size of blur. +* angle: The angle of blur. + +### MaskGradient +Create a gradient for the mask from one side. +![image](image/mask_gradient_example.png) + +Node options: +![image](image/mask_gradient_node.png) +* invert_mask: Whether to reverse the mask. +* gradient_side: Generate gradient from which edge. There are four directions: top, bottom, left and right. +* gradient_scale: Gradient distance. The default value of 100 indicates that one side of the gradient is completely transparent and the other side is completely opaque. The smaller the value, the shorter the distance from transparent to opaque. +* gradient_offset: Gradient position offset. +* opacity: The opacity of the gradient. + +### MaskStroke +Generate mask contour strokes. +![image](image/mask_stroke_example.png) + +Node options: +![image](image/mask_stroke_node.png) +* invert_mask: Whether to reverse the mask. +* stroke_grow: Stroke expansion/contraction amplitude, positive values indicate expansion and negative values indicate contraction. +* stroke_width: Stroke width. +* blur: Blur of stroke. + +### MaskPreview +Preview the input mask +![image](image/mask_invert.png) + + +### MaskInvert +Invert the mask +![image](image/mask_invert_node.png) + + + # LayerFilter ![image](image/layerfilter_nodes.png) diff --git a/README_CN.MD b/README_CN.MD index 6662c78..c673292 100644 --- a/README_CN.MD +++ b/README_CN.MD @@ -10,6 +10,7 @@ * [LayerFilter](#LayerFilter)½Úµã×éÌṩͼÏñЧ¹ûÂ˾µ¡£ ## ¸üÐÂ˵Ã÷ +* Ìí¼Ó[TextImage](#TextImage) ½Úµã£¬Éú³ÉÎÄ×ÖͼÏñºÍÕÚÕÖ¡£ * ͼÏñÖ®¼äµÄ[»ìºÏģʽ](#»ìºÏģʽ)Ôö¼ÓÐÂÀàÐÍ£¬ÏÖÔÚÖ§³Ö¶à´ï19ÖÖ»ìºÏģʽ¡£ÐÂÔöcolor_burnÑÕÉ«¼ÓÉî, color_dodgeÑÕÉ«¼õµ­, linear_burnÏßÐÔ¼ÓÉî, linear_dodgeÏßÐÔ¼õµ­, overlayµþ¼Ó, soft_lightÈá¹â, hard_lightÇ¿¹â, vivid_lightÁÁ¹â, pin_lightµã¹â, linear_lightÏßÐÔ¹â, hard_mixʵɫ»ìºÏ¡£ÐÂÔöµÄ»ìºÏģʽÊÊÓÃÓÚËùÓÐÖ§³Ö»ìºÏģʽµÄ½Úµã¡£ * Ìí¼Ó[ColorMap](#ColorMap) Â˾µ½Úµã£¬ÓÃÓÚÖÆ×÷α²ÊÉ«ÈÈÁ¦Í¼Ð§¹û¡£ * Ìí¼Ó[WaterColor](#WaterColor) ºÍ [SkinBeauty](#SkinBeauty) ½Úµã¡£ÕâÊÇÁ½¸öͼÏñÂ˾µ£¬Éú³ÉË®²Ê»­ºÍĥƤЧ¹û¡£ @@ -259,97 +260,6 @@ * S: ͼÏñµÄSͨµÀ¡£ * V: ͼÏñµÄVͨµÀ¡£ -# LayerMask -![image](image/layermask_nodes.png) - -### MaskBoxDetect -̽²âmaskËùÔÚÇøÓò£¬²¢Êä³öλÖúʹóС¡£ -![image](image/mask_box_detect_example.png) - -½ÚµãÑ¡Ïî˵Ã÷: -![image](image/mask_box_detect_node.png) -* detect: ̽²â·½·¨£¬min_bounding_rectÊÇ×îСÍâ½Ó¾ØÐÎ, max_inscribed_rectÊÇ×î´óÄÚ½Ó¾ØÐΡ£ -* x_adjust: ÐÞÕý̽²âÖ®ºóµÄË®Æ½Æ«ÒÆ¡£ -* y_adjust: ÐÞÕý̽²âÖ®ºóµÄ´¹Ö±Æ«ÒÆ¡£ -* scale_adjust: ÐÞÕý̽²âÖ®ºóµÄËõ·ÅÆ«ÒÆ¡£ - -Êä³ö£º -* box_preview: ̽²â½á¹ûÔ¤ÀÀͼ¡£ºìÉ«±íʾ̽²âµ½µÄ½á¹û£¬ÂÌÉ«±íʾ¼ÓÉÏÐÞÕýºóµÄÊä³ö½á¹û¡£ -* x_percent: ˮƽλÖÃÒÔ°Ù·Ö±ÈÊä³ö¡£ -* y_percent: ´¹Ö±Î»ÖÃÒÔ°Ù·Ö±ÈÊä³ö¡£ -* width: ¿í¶ÈÊä³ö¡£ -* height: ¸ß¶ÈÊä³ö¡£ -* x: ×óÉϽÇλÖÃx×ø±êÊä³ö¡£ -* y: ×óÉϽÇλÖÃy×ø±êÊä³ö¡£ - -### MaskGrow -¶Ômask½øÐÐÀ©ÕÅÊÕËõ±ßÔµºÍÄ£ºý´¦Àí -![image](image/mask_grow_example.png) - -½ÚµãÑ¡Ïî˵Ã÷: -![image](image/mask_grow_node.png) -* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ -* grow: À©ÕÅ·ù¶È¡£ÕýÖµÊÇÏòÍâÀ©ÕÅ£¬¸ºÖµÊÇÏòÄÚÊÕËõ¡£ -* blur: Ä£ºý¡£ - -### MaskEdgeShrink -ʹmask±ßԵƽ»¬µØ¹ý¶ÉÊÕËõ£¬²¢±£Áô±ßԵϸ½Ú¡£ -![image](image/mask_edge_shrink_example.png) - -½ÚµãÑ¡Ïî˵Ã÷: -![image](image/mask_edge_shrink_node.png) -* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ -* shrink_level: ÊÕËõƽ»¬¼¶±ð¡£ -* soft: ƽ»¬·ù¶È¡£ -* edge_shrink: ±ßÔµÊÕËõ·ù¶È¡£ -* edge_reserve: ±£Áô±ßԵϸ½Ú·ù¶È, 100ΪÍêÈ«±£Áô£¬0ΪÍêÈ«²»±£Áô¡£ - -MaskGrowÓëMaskEdgeShrinkЧ¹û¶Ô±È -![image](image/mask_edge_compare.png) - -### MaskMotionBlur -ʹmask²úÉúÔ˶¯Ä£ºý¡£ -![image](image/mask_motion_blur_example.png) - -½ÚµãÑ¡Ïî˵Ã÷: -![image](image/mask_motion_blur_node.png) -* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ -* blur: Ä£ºý´óС¡£ -* angle: Ä£ºý½Ç¶È¡£ - -### MaskGradient -ʹmask´ÓÒ»²à²úÉú½¥±ä¡£ -![image](image/mask_gradient_example.png) - -½ÚµãÑ¡Ïî˵Ã÷: -![image](image/mask_gradient_node.png) -* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ -* gradient_side: ´ÓÄĸö±ß²úÉú½¥±ä¡£ÓÐËĸö·½Ïò£º¶¥²àtop¡¢µ×²àbottom¡¢×ó²àleft¡¢ÓÒ²àright¡£ -* gradient_scale: ½¥±ä¾àÀ롣ĬÈÏÖµ100±íʾ½¥±ä²úÉúÒ»²àÍêȫ͸Ã÷£¬ÁíÒ»²àÍêÈ«²»Í¸Ã÷¡£ÊýֵԽС£¬´Ó͸Ã÷µ½²»Í¸Ã÷µÄ¾àÀëÔ½¶Ì¡£ -* gradient_offset: ½¥±äλÖÃÆ«ÒÆ¡£ -* opacity: ½¥±äµÄ²»Í¸Ã÷¶È¡£ - -### MaskStroke -²úÉúmaskÂÖÀªÃè±ß¡£ -![image](image/mask_stroke_example.png) - -½ÚµãÑ¡Ïî˵Ã÷: -![image](image/mask_stroke_node.png) -* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ -* stroke_grow: Ãè±ßÀ©ÕÅ/ÊÕËõ·ù¶È£¬ÕýÖµÊÇÀ©ÕÅ£¬¸ºÖµÊÇÊÕËõ¡£ -* stroke_width: Ãè±ß¿í¶È¡£ -* blur: Ãè±ßÄ£ºý¡£ - -### MaskPreview -Ô¤ÀÀmask -![image](image/mask_invert.png) - - -### MaskInvert -mask·´×ª -![image](image/mask_invert_node.png) - - # LayerUtility ![image](image/layerutility_nodes.png) @@ -560,6 +470,31 @@ mask * start_color4: ¿ªÊ¼¶ËÑÕÉ«¡£ * end_color4: ½áÊø¶ËÑÕÉ«¡£ +### TextImage +![image](image/text_image_example.png) +´ÓÎÄ×ÖÉú³ÉͼƬÒÔ¼°ÕÚÕÖ¡£Ö§³Ö×Ö¼ä¾àÐмä¾àµ÷Õû£¬ºáÅÅÊúÅŵ÷Õû£¬¿ÉÉèÖÃÎÄ×ÖµÄËæ»ú±ä»¯£¬°üÀ¨´óСºÍλÖõÄËæ»ú±ä»¯¡£ + +![image](image/text_image_node.png) +½ÚµãÑ¡Ïî˵Ã÷: +* size_as*: ´Ë´¦ÊäÈëͼÏñ»òÕÚÕÖ£¬½«°´ÕÕÆä³ß´çÉú³ÉÊä³öͼÏñºÍÕÚÕÖ¡£ +* font_file**: ÕâÀïÁгöÁËfontÎļþ¼ÐÖпÉÓõÄ×ÖÌåÎļþÁÐ±í£¬Ñ¡ÖеÄ×ÖÌåÎļþ½«±»ÓÃÀ´Éú³ÉͼÏñ¡£ +* spacing: ×Ö¼ä¾à¡£ +* leading: Ðмä¾à¡£ +* horizontal_border: ²à±ß±ß¾à¡£Èç¹ûÎÄ×ÖÊǺáÅÅ£¬ÊÇ×ó²à±ß¾à£¬ÊúÅÅÔòÊÇÓÒ²à±ß¾à¡£ +* vertical_border: ¶¥²¿±ß¾à¡£ +* scale: ÎÄ×Ö×ÜÌå´óС¡£ÎÄ×ֵijõʼ´óСÊǸù¾Ý»­Ãæ³ß´çºÍÎÄ×ÖÄÚÈÝ×Ô¶¯¼ÆË㣬ĬÈÏÒÔ×µÄÐлòÕßÁÐÊÊÅä»­Ãæ¿í»òÕ߸ߡ£µ÷Õû´Ë´¦ÊýÖµ½«ÕûÌå·Å´óºÍËõСÎÄ×Ö¡£ +* variation_range: ×Ö·ûËæ»ú±ä»¯·¶Î§¡£´ËÊýÖµ´óÓÚ0ʱ£¬×Ö·û½«²úÉú´óСºÍλÖõÄËæ»ú±ä»¯£¬ÊýÖµÔ½´ó£¬±ä»¯·ù¶ÈÔ½´ó¡£ +* variation_seed: Ëæ»ú±ä»¯µÄÖÖ×Ó¡£¹Ì¶¨´ËÊýÖµ£¬Ã¿´Î²úÉúµÄµ¥¸öÎÄ×ֵı仯²»»á¸Ä±ä¡£ +* layout: ÎÄ×ÖÅŰ档ÓкáÅźÍÊúÅÅ¿ÉÑ¡Ôñ¡£ +* width; »­ÃæµÄ¿í¶È¡£Èç¹ûÓÐsize_asÊäÈ룬´ËÉèÖý«±»ºöÂÔ¡£ +* height; »­ÃæµÄ¸ß¶È¡£Èç¹ûÓÐsize_asÊäÈ룬´ËÉèÖý«±»ºöÂÔ¡£ +* text_color: ÎÄ×ÖÑÕÉ«¡£ +* background_color: ±³¾°ÑÕÉ«¡£ + +*½öÏÞÊäÈëimageºÍmask, Èç¹ûÇ¿ÖÆ½ÓÈëÆäËûÀàÐÍÊäÈ룬½«µ¼Ö½ڵã´íÎó¡£ +**fontÎļþ¼ÐÔÚresource_dir.iniÖж¨Ò壬Õâ¸öÎļþλÓÚ²å¼þ¸ùĿ¼Ï¡£ÓÃÎı¾±à¼­Èí¼þ´ò¿ª£¬ÕÒµ½¡°FONT_dir=¡±¿ªÍ·µÄÕâÒ»ÐУ¬±à¼­¡°=¡±Ö®ºóΪ×Ô¶¨ÒåÎļþ¼Ð·¾¶Ãû¡£Õâ¸öÎļþ¼ÐÀïÃæËùÓеÄ.ttfºÍ.otfÎļþ½«ÔÚComfyUI³õʼ»¯Ê±±»ÊÕ¼¯²¢ÏÔʾÔÚ½ÚµãµÄÁбíÖС£Èç¹ûiniÖÐÉ趨µÄÎļþ¼ÐÎÞЧ£¬½«ÆôÓòå¼þ×Ô´øµÄfontÎļþ¼Ð¡£ + + ### ImageChannelSplit ![image](image/image_channel_split_example.png) ½«Í¼ÏñͨµÀ²ð·ÖΪµ¥¶ÀµÄͼƬ¡£ @@ -587,6 +522,99 @@ mask Õâ¸ö½ÚµãÔÊÐíÈÎÒâÀàÐ͵ÄÊäÈë¡£ + +# LayerMask +![image](image/layermask_nodes.png) + +### MaskBoxDetect +̽²âmaskËùÔÚÇøÓò£¬²¢Êä³öλÖúʹóС¡£ +![image](image/mask_box_detect_example.png) + +½ÚµãÑ¡Ïî˵Ã÷: +![image](image/mask_box_detect_node.png) +* detect: ̽²â·½·¨£¬min_bounding_rectÊÇ×îСÍâ½Ó¾ØÐÎ, max_inscribed_rectÊÇ×î´óÄÚ½Ó¾ØÐΡ£ +* x_adjust: ÐÞÕý̽²âÖ®ºóµÄË®Æ½Æ«ÒÆ¡£ +* y_adjust: ÐÞÕý̽²âÖ®ºóµÄ´¹Ö±Æ«ÒÆ¡£ +* scale_adjust: ÐÞÕý̽²âÖ®ºóµÄËõ·ÅÆ«ÒÆ¡£ + +Êä³ö£º +* box_preview: ̽²â½á¹ûÔ¤ÀÀͼ¡£ºìÉ«±íʾ̽²âµ½µÄ½á¹û£¬ÂÌÉ«±íʾ¼ÓÉÏÐÞÕýºóµÄÊä³ö½á¹û¡£ +* x_percent: ˮƽλÖÃÒÔ°Ù·Ö±ÈÊä³ö¡£ +* y_percent: ´¹Ö±Î»ÖÃÒÔ°Ù·Ö±ÈÊä³ö¡£ +* width: ¿í¶ÈÊä³ö¡£ +* height: ¸ß¶ÈÊä³ö¡£ +* x: ×óÉϽÇλÖÃx×ø±êÊä³ö¡£ +* y: ×óÉϽÇλÖÃy×ø±êÊä³ö¡£ + +### MaskGrow +¶Ômask½øÐÐÀ©ÕÅÊÕËõ±ßÔµºÍÄ£ºý´¦Àí +![image](image/mask_grow_example.png) + +½ÚµãÑ¡Ïî˵Ã÷: +![image](image/mask_grow_node.png) +* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ +* grow: À©ÕÅ·ù¶È¡£ÕýÖµÊÇÏòÍâÀ©ÕÅ£¬¸ºÖµÊÇÏòÄÚÊÕËõ¡£ +* blur: Ä£ºý¡£ + +### MaskEdgeShrink +ʹmask±ßԵƽ»¬µØ¹ý¶ÉÊÕËõ£¬²¢±£Áô±ßԵϸ½Ú¡£ +![image](image/mask_edge_shrink_example.png) + +½ÚµãÑ¡Ïî˵Ã÷: +![image](image/mask_edge_shrink_node.png) +* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ +* shrink_level: ÊÕËõƽ»¬¼¶±ð¡£ +* soft: ƽ»¬·ù¶È¡£ +* edge_shrink: ±ßÔµÊÕËõ·ù¶È¡£ +* edge_reserve: ±£Áô±ßԵϸ½Ú·ù¶È, 100ΪÍêÈ«±£Áô£¬0ΪÍêÈ«²»±£Áô¡£ + +MaskGrowÓëMaskEdgeShrinkЧ¹û¶Ô±È +![image](image/mask_edge_compare.png) + +### MaskMotionBlur +ʹmask²úÉúÔ˶¯Ä£ºý¡£ +![image](image/mask_motion_blur_example.png) + +½ÚµãÑ¡Ïî˵Ã÷: +![image](image/mask_motion_blur_node.png) +* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ +* blur: Ä£ºý´óС¡£ +* angle: Ä£ºý½Ç¶È¡£ + +### MaskGradient +ʹmask´ÓÒ»²à²úÉú½¥±ä¡£ +![image](image/mask_gradient_example.png) + +½ÚµãÑ¡Ïî˵Ã÷: +![image](image/mask_gradient_node.png) +* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ +* gradient_side: ´ÓÄĸö±ß²úÉú½¥±ä¡£ÓÐËĸö·½Ïò£º¶¥²àtop¡¢µ×²àbottom¡¢×ó²àleft¡¢ÓÒ²àright¡£ +* gradient_scale: ½¥±ä¾àÀ롣ĬÈÏÖµ100±íʾ½¥±ä²úÉúÒ»²àÍêȫ͸Ã÷£¬ÁíÒ»²àÍêÈ«²»Í¸Ã÷¡£ÊýֵԽС£¬´Ó͸Ã÷µ½²»Í¸Ã÷µÄ¾àÀëÔ½¶Ì¡£ +* gradient_offset: ½¥±äλÖÃÆ«ÒÆ¡£ +* opacity: ½¥±äµÄ²»Í¸Ã÷¶È¡£ + +### MaskStroke +²úÉúmaskÂÖÀªÃè±ß¡£ +![image](image/mask_stroke_example.png) + +½ÚµãÑ¡Ïî˵Ã÷: +![image](image/mask_stroke_node.png) +* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ +* stroke_grow: Ãè±ßÀ©ÕÅ/ÊÕËõ·ù¶È£¬ÕýÖµÊÇÀ©ÕÅ£¬¸ºÖµÊÇÊÕËõ¡£ +* stroke_width: Ãè±ß¿í¶È¡£ +* blur: Ãè±ßÄ£ºý¡£ + +### MaskPreview +Ô¤ÀÀmask +![image](image/mask_invert.png) + + +### MaskInvert +mask·´×ª +![image](image/mask_invert_node.png) + + + # LayerFilter ![image](image/layerfilter_nodes.png) diff --git a/font/Alibaba-PuHuiTi-Heavy.ttf b/font/Alibaba-PuHuiTi-Heavy.ttf new file mode 100644 index 0000000..7eb047f Binary files /dev/null and b/font/Alibaba-PuHuiTi-Heavy.ttf differ diff --git a/image/layerutility_nodes.png b/image/layerutility_nodes.png index 683f3e7..4aa5903 100644 Binary files a/image/layerutility_nodes.png and b/image/layerutility_nodes.png differ diff --git a/image/text_image_example.png b/image/text_image_example.png new file mode 100644 index 0000000..6f3726b Binary files /dev/null and b/image/text_image_example.png differ diff --git a/image/text_image_node.png b/image/text_image_node.png new file mode 100644 index 0000000..17cc2e2 Binary files /dev/null and b/image/text_image_node.png differ diff --git a/py/color_correct_LUTapply.py b/py/color_correct_LUTapply.py index a3ac006..b8dfd06 100644 --- a/py/color_correct_LUTapply.py +++ b/py/color_correct_LUTapply.py @@ -2,30 +2,30 @@ import os import glob from .imagefunc import * -lut_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.normpath(__file__))), 'lut') -ini_file = os.path.join(os.path.dirname(os.path.dirname(os.path.normpath(__file__))), "resource_dir.ini") - -try: - with open(ini_file, 'r') as f: - ini = f.readlines() - for line in ini: - if line.startswith('LUT_dir='): - d = line[line.find('=') + 1:].rstrip().lstrip() - break - if os.path.exists(d): - lut_dir = d - else: - log(f'ERROR: invalid LUT dir, default to be used. check {ini_file}') -except Exception as e: - log(f'ERROR: {ini_file} ' + repr(e)) - -file_list = glob.glob(lut_dir + '/*.cube') -lut_dict = {} -for i in range(len(file_list)): - _, filename = os.path.split(file_list[i]) - lut_dict[filename] = file_list[i] -lut_list = list(lut_dict.keys()) -log(f'find {len(lut_list)} LUTs in {lut_dir}') +# lut_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.normpath(__file__))), 'lut') +# ini_file = os.path.join(os.path.dirname(os.path.dirname(os.path.normpath(__file__))), "resource_dir.ini") +# +# try: +# with open(ini_file, 'r') as f: +# ini = f.readlines() +# for line in ini: +# if line.startswith('LUT_dir='): +# d = line[line.find('=') + 1:].rstrip().lstrip() +# break +# if os.path.exists(d): +# lut_dir = d +# else: +# log(f'ERROR: invalid LUT dir, default to be used. check {ini_file}') +# except Exception as e: +# log(f'ERROR: {ini_file} ' + repr(e)) +# +# file_list = glob.glob(lut_dir + '/*.cube') +# lut_dict = {} +# for i in range(len(file_list)): +# _, filename = os.path.split(file_list[i]) +# lut_dict[filename] = file_list[i] +# lut_list = list(lut_dict.keys()) +# log(f'find {len(lut_list)} LUTs in {lut_dir}') class ColorCorrectLUTapply: @@ -38,7 +38,7 @@ class ColorCorrectLUTapply: return { "required": { "image": ("IMAGE", ), # - "LUT": (lut_list,), # LUT文件 + "LUT": (LUT_LIST,), # LUT文件 }, "optional": { } @@ -53,7 +53,7 @@ class ColorCorrectLUTapply: def color_correct_LUTapply(self, image, LUT): _image = tensor2pil(image) - lut_file = lut_dict[LUT] + lut_file = LUT_DICT[LUT] ret_image = lut_apply(_image, lut_file) return (pil2tensor(ret_image),) diff --git a/py/imagefunc.py b/py/imagefunc.py index bd868ed..3b11af9 100644 --- a/py/imagefunc.py +++ b/py/imagefunc.py @@ -1,17 +1,20 @@ '''Image process functions for ComfyUI nodes by chflame https://github.com/chflame163 ''' - +import os import re +import glob import numpy as np import torch import scipy.ndimage import cv2 +import random from typing import Union, List from PIL import Image, ImageFilter, ImageChops, ImageDraw, ImageOps from skimage import img_as_float, img_as_ubyte import colorsys + def log(message): name = 'LayerStyle' print(f"# 😺dzNodes: {name} -> {message}") @@ -773,6 +776,15 @@ def has_letters(string:str) -> bool: else: return False +def random_numbers(total:int, random_range:int, seed:int=0, sum_of_numbers:int=0) -> list: + random.seed(seed) + numbers = [random.randint(-random_range//2, random_range//2) for _ in range(total - 1)] + avg = sum(numbers) // total + ret_list = [] + for i in numbers: + ret_list.append(i - avg) + ret_list.append(sum_of_numbers - sum(ret_list)) + return ret_list '''CLASS''' @@ -785,4 +797,46 @@ class AnyType(str): chop_mode = ['normal', 'multply', 'screen', 'add', 'subtract', 'difference', 'darker', 'lighter', 'color_burn', 'color_dodge', 'linear_burn', 'linear_dodge', 'overlay', - 'soft_light', 'hard_light', 'vivid_light', 'pin_light', 'linear_light', 'hard_mix'] \ No newline at end of file + 'soft_light', 'hard_light', 'vivid_light', 'pin_light', 'linear_light', 'hard_mix'] + +'''Load INI File''' + +default_lut_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.normpath(__file__))), 'lut') +default_font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.normpath(__file__))), 'font') +resource_dir_ini_file = os.path.join(os.path.dirname(os.path.dirname(os.path.normpath(__file__))), "resource_dir.ini") + +try: + with open(resource_dir_ini_file, 'r') as f: + ini = f.readlines() + for line in ini: + if line.startswith('LUT_dir='): + _ldir = line[line.find('=') + 1:].rstrip().lstrip() + if os.path.exists(_ldir): + default_lut_dir = _ldir + else: + log(f'ERROR: invalid LUT directory, default to be used. check {resource_dir_ini_file}') + elif line.startswith('FONT_dir='): + _fdir = line[line.find('=') + 1:].rstrip().lstrip() + if os.path.exists(_fdir): + default_font_dir = _fdir + else: + log(f'ERROR: invalid FONT directory, default to be used. check {resource_dir_ini_file}') +except Exception as e: + log(f'ERROR: {resource_dir_ini_file} ' + repr(e)) + +__lut_file_list = glob.glob(default_lut_dir + '/*.cube') +LUT_DICT = {} +for i in range(len(__lut_file_list)): + _, __filename = os.path.split(__lut_file_list[i]) + LUT_DICT[__filename] = __lut_file_list[i] +LUT_LIST = list(LUT_DICT.keys()) +log(f'find {len(LUT_LIST)} LUTs in {default_lut_dir}') + +__font_file_list = glob.glob(default_font_dir + '/*.ttf') +__font_file_list.extend(glob.glob(default_font_dir + '/*.otf')) +FONT_DICT = {} +for i in range(len(__font_file_list)): + _, __filename = os.path.split(__font_file_list[i]) + FONT_DICT[__filename] = __font_file_list[i] +FONT_LIST = list(FONT_DICT.keys()) +log(f'find {len(FONT_LIST)} Fonts in {default_font_dir}') diff --git a/py/text_image.py b/py/text_image.py new file mode 100644 index 0000000..25260d6 --- /dev/null +++ b/py/text_image.py @@ -0,0 +1,138 @@ +import time +import random +from PIL import ImageFont +from .imagefunc import * + +any = AnyType("*") + +class TextImage: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(self): + layout_list = ['horizontal', 'vertical'] + random_seed = int(time.time()) + return { + "required": { + "text": ("STRING", {"multiline": True, "default": "Text"}), + "font_file": (FONT_LIST,), + "spacing": ("INT", {"default": 0, "min": -9999, "max": 9999, "step": 1}), + "leading": ("INT", {"default": 0, "min": -9999, "max": 9999, "step": 1}), + "horizontal_border": ("FLOAT", {"default": 5, "min": -100, "max": 100, "step": 0.1}), # å·¦å�³è·�离百分比,横排为è·�左侧,竖排为è·�å�³ä¾§ + "vertical_border": ("FLOAT", {"default": 5, "min": -100, "max": 100, "step": 0.1}), # 上è·�离百分比 + "scale": ("FLOAT", {"default": 80, "min": 0.1, "max": 999, "step": 0.01}), # 整体大å°�与画é�¢é•¿å®½æ¯”,横排与宽比,竖排与高比 + "variation_range": ("INT", {"default": 0, "min": 0, "max": 100, "step": 1}), # éš�机大å°�å’Œä½�置范围 + "variation_seed": ("INT", {"default": random_seed, "min": 0, "max": 999999999999, "step": 1}), # éš�机ç§�å­� + "layout": (layout_list,), # 横排or竖排 + "width": ("INT", {"default": 512, "min": 4, "max": 999999, "step": 1}), + "height": ("INT", {"default": 512, "min": 4, "max": 999999, "step": 1}), + "text_color": ("STRING", {"default": "#FFA000"}), # 文字颜色 + "background_color": ("STRING", {"default": "#FFFFFF"}), # 背景颜色 + }, + "optional": { + "size_as": (any, {}), + } + } + + RETURN_TYPES = ("IMAGE", "MASK",) + RETURN_NAMES = ("image", "mask",) + FUNCTION = 'text_image' + CATEGORY = '😺dzNodes/LayerUtility' + OUTPUT_NODE = True + + def text_image(self, text, font_file, spacing, leading, horizontal_border, vertical_border, scale, + variation_range, variation_seed, layout, width, height, text_color, background_color, + size_as=None + ): + + spacing -= 20 + leading += 20 + scale *= 0.7 + if size_as is not None: + width, height = tensor2pil(size_as).size + text_table = [] + max_char_in_line = 0 + total_char = 0 + spacing = int(spacing * scale / 100) + leading = int(leading * scale / 100) + lines = [] + text_lines = text.split("\n") + for l in text_lines: + if len(l) > 0: + lines.append(l) + total_char += len(l) + if len(l) > max_char_in_line: + max_char_in_line = len(l) + else: + lines.append(" ") + if layout == 'vertical': + char_horizontal_size = width // len(lines) + char_vertical_size = height // max_char_in_line + char_size = min(char_horizontal_size, char_vertical_size) + if char_size < 1: + char_size = 1 + start_x = width - int(width * horizontal_border/100) - char_size + else: + char_horizontal_size = width // max_char_in_line + char_vertical_size = height // len(lines) + char_size = min(char_horizontal_size, char_vertical_size) + if char_size < 1: + char_size = 1 + start_x = int(width * horizontal_border/100) + start_y = int(height * vertical_border/100) + + # calculate every char position and size to a table list + for i in range(len(lines)): + _x = start_x + _y = start_y + line_table = [] + line_random = random_numbers(total=len(lines[i]), + random_range=int(char_size * variation_range / 25), + seed=variation_seed, sum_of_numbers=0) + for j in range(0, len(lines[i])): + offset = int((char_size + line_random[j]) * variation_range / 250) + offset = int(offset * scale / 100) + font_size = char_size - spacing + line_random[j] + font_size = int(font_size * scale / 100) + if font_size < 4: + font_size = 4 + axis_x = _x + offset if random.random() > 0.5 else _x - offset + axis_y = _y + offset if random.random() > 0.5 else _y - offset + char_dict = {'char':lines[i][j], + 'axis':(axis_x, axis_y), + 'size':font_size} + line_table.append(char_dict) + if layout == 'vertical': + _y += char_size + line_random[j] + spacing + else: + _x += char_size + line_random[j] + spacing + if layout == 'vertical': + start_x -= leading * (i+1) + char_size + else: + start_y += leading * (i+1) + char_size + text_table.append(line_table) + + # draw char + _mask = Image.new('RGB', size=(width, height), color='black') + draw = ImageDraw.Draw(_mask) + for l in range(len(lines)): + for c in range(len(lines[l])): + font_path = FONT_DICT.get(font_file) + font_size = text_table[l][c].get('size') + font = ImageFont.truetype(font_path, font_size) + draw.text(text_table[l][c].get('axis'), text_table[l][c].get('char'), font=font, fill='white') + _canvas = Image.new('RGB', size=(width, height), color=background_color) + _color = Image.new('RGB', size=(width, height), color=text_color) + _canvas.paste(_color, mask=_mask.convert('L')) + _canvas = RGB2RGBA(_canvas, _mask) + return (pil2tensor(_canvas), image2mask(_mask),) + +NODE_CLASS_MAPPINGS = { + "LayerUtility: TextImage": TextImage +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LayerUtility: TextImage": "LayerUtility: TextImage" +} \ No newline at end of file diff --git a/resource_dir.ini b/resource_dir.ini index f983904..87ca783 100644 --- a/resource_dir.ini +++ b/resource_dir.ini @@ -1,2 +1,2 @@ -font_dir=C:\Windows\fonts +FONT_dir=C:\font LUT_dir=C:\lut \ No newline at end of file diff --git a/workflow/text_image_example.json b/workflow/text_image_example.json new file mode 100644 index 0000000..0771d25 --- /dev/null +++ b/workflow/text_image_example.json @@ -0,0 +1,224 @@ +{ + "last_node_id": 28, + "last_link_id": 57, + "nodes": [ + { + "id": 11, + "type": "LayerStyle: DropShadow", + "pos": [ + 1250, + 300 + ], + "size": { + "0": 315, + "1": 266 + }, + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "background_image", + "type": "IMAGE", + "link": 52 + }, + { + "name": "layer_image", + "type": "IMAGE", + "link": 57 + }, + { + "name": "layer_mask", + "type": "MASK", + "link": null + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 46 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerStyle: DropShadow" + }, + "widgets_values": [ + false, + "normal", + 46, + 3, + 3, + 4, + 12, + "#000000" + ] + }, + { + "id": 12, + "type": "PreviewImage", + "pos": [ + 1620, + 310 + ], + "size": { + "0": 477.99102783203125, + "1": 481.061767578125 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 46 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 28, + "type": "LayerUtility: TextImage", + "pos": [ + 800, + 470 + ], + "size": { + "0": 400, + "1": 408 + }, + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "size_as", + "type": "*", + "link": 56 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 57 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "mask", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LayerUtility: TextImage" + }, + "widgets_values": [ + "鸿é›�长飞光ä¸�度,\n 鱼龙潜跃水æˆ�文。", + "演示秋鸿楷YanShiQiuHongKai.ttf", + -20, + 0, + 36, + 5, + 80, + 24, + 1707317113, + "vertical", + 512, + 512, + "#000020", + "#FFFFFF" + ] + }, + { + "id": 10, + "type": "LoadImage", + "pos": [ + 460, + 300 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 52, + 56 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": [], + "shape": 3, + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "1024x1024bkgd.png", + "image" + ] + } + ], + "links": [ + [ + 46, + 11, + 0, + 12, + 0, + "IMAGE" + ], + [ + 52, + 10, + 0, + 11, + 0, + "IMAGE" + ], + [ + 56, + 10, + 0, + 28, + 0, + "*" + ], + [ + 57, + 28, + 0, + 11, + 1, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file