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
-
-
-### MaskBoxDetect
-Detect the area where the mask is located and output its position and size.
-
-
-Node options:
-
-* 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
-
-
-Node options:
-
-* 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.
-
-
-Node options:
-
-* 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
-
-
-### MaskMotionBlur
-Create motion blur on the mask.
-
-
-Node options:
-
-* 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.
-
-
-Node options:
-
-* 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.
-
-
-Node options:
-
-* 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
-
-
-
-### MaskInvert
-Invert the mask
-
-
-
# LayerUtility

@@ -573,6 +482,33 @@ Node options:
* start_color4: Color of the begging.
* end_color4: Color of the ending.
+### TextImage
+
+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.
+
+
+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

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
+
+
+### MaskBoxDetect
+Detect the area where the mask is located and output its position and size.
+
+
+Node options:
+
+* 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
+
+
+Node options:
+
+* 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.
+
+
+Node options:
+
+* 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
+
+
+### MaskMotionBlur
+Create motion blur on the mask.
+
+
+Node options:
+
+* 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.
+
+
+Node options:
+
+* 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.
+
+
+Node options:
+
+* 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
+
+
+
+### MaskInvert
+Invert the mask
+
+
+
+
# LayerFilter

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
-
-
-### MaskBoxDetect
-̽²âmaskËùÔÚÇøÓò£¬²¢Êä³öλÖúʹóС¡£
-
-
-½ÚµãÑ¡Ïî˵Ã÷:
-
-* 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½øÐÐÀ©ÕÅÊÕËõ±ßÔµºÍÄ£ºý´¦Àí
-
-
-½ÚµãÑ¡Ïî˵Ã÷:
-
-* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
-* grow: À©ÕÅ·ù¶È¡£ÕýÖµÊÇÏòÍâÀ©ÕÅ£¬¸ºÖµÊÇÏòÄÚÊÕËõ¡£
-* blur: Ä£ºý¡£
-
-### MaskEdgeShrink
-ʹmask±ßԵƽ»¬µØ¹ý¶ÉÊÕËõ£¬²¢±£Áô±ßԵϸ½Ú¡£
-
-
-½ÚµãÑ¡Ïî˵Ã÷:
-
-* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
-* shrink_level: ÊÕËõƽ»¬¼¶±ð¡£
-* soft: ƽ»¬·ù¶È¡£
-* edge_shrink: ±ßÔµÊÕËõ·ù¶È¡£
-* edge_reserve: ±£Áô±ßԵϸ½Ú·ù¶È, 100ΪÍêÈ«±£Áô£¬0ΪÍêÈ«²»±£Áô¡£
-
-MaskGrowÓëMaskEdgeShrinkЧ¹û¶Ô±È
-
-
-### MaskMotionBlur
-ʹmask²úÉúÔ˶¯Ä£ºý¡£
-
-
-½ÚµãÑ¡Ïî˵Ã÷:
-
-* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
-* blur: Ä£ºý´óС¡£
-* angle: Ä£ºý½Ç¶È¡£
-
-### MaskGradient
-ʹmask´ÓÒ»²à²úÉú½¥±ä¡£
-
-
-½ÚµãÑ¡Ïî˵Ã÷:
-
-* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
-* gradient_side: ´ÓÄĸö±ß²úÉú½¥±ä¡£ÓÐËĸö·½Ïò£º¶¥²àtop¡¢µ×²àbottom¡¢×ó²àleft¡¢ÓÒ²àright¡£
-* gradient_scale: ½¥±ä¾àÀ롣ĬÈÏÖµ100±íʾ½¥±ä²úÉúÒ»²àÍêȫ͸Ã÷£¬ÁíÒ»²àÍêÈ«²»Í¸Ã÷¡£ÊýֵԽС£¬´Ó͸Ã÷µ½²»Í¸Ã÷µÄ¾àÀëÔ½¶Ì¡£
-* gradient_offset: ½¥±äλÖÃÆ«ÒÆ¡£
-* opacity: ½¥±äµÄ²»Í¸Ã÷¶È¡£
-
-### MaskStroke
-²úÉúmaskÂÖÀªÃè±ß¡£
-
-
-½ÚµãÑ¡Ïî˵Ã÷:
-
-* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
-* stroke_grow: Ãè±ßÀ©ÕÅ/ÊÕËõ·ù¶È£¬ÕýÖµÊÇÀ©ÕÅ£¬¸ºÖµÊÇÊÕËõ¡£
-* stroke_width: Ãè±ß¿í¶È¡£
-* blur: Ãè±ßÄ£ºý¡£
-
-### MaskPreview
-Ô¤ÀÀmask
-
-
-
-### MaskInvert
-mask·´×ª
-
-
-
# LayerUtility

@@ -560,6 +470,31 @@ mask
* start_color4: ¿ªÊ¼¶ËÑÕÉ«¡£
* end_color4: ½áÊø¶ËÑÕÉ«¡£
+### TextImage
+
+´ÓÎÄ×ÖÉú³ÉͼƬÒÔ¼°ÕÚÕÖ¡£Ö§³Ö×Ö¼ä¾àÐмä¾àµ÷Õû£¬ºáÅÅÊúÅŵ÷Õû£¬¿ÉÉèÖÃÎÄ×ÖµÄËæ»ú±ä»¯£¬°üÀ¨´óСºÍλÖõÄËæ»ú±ä»¯¡£
+
+
+½ÚµãÑ¡Ïî˵Ã÷:
+* 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

½«Í¼ÏñͨµÀ²ð·ÖΪµ¥¶ÀµÄͼƬ¡£
@@ -587,6 +522,99 @@ mask
Õâ¸ö½ÚµãÔÊÐíÈÎÒâÀàÐ͵ÄÊäÈë¡£
+
+# LayerMask
+
+
+### MaskBoxDetect
+̽²âmaskËùÔÚÇøÓò£¬²¢Êä³öλÖúʹóС¡£
+
+
+½ÚµãÑ¡Ïî˵Ã÷:
+
+* 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½øÐÐÀ©ÕÅÊÕËõ±ßÔµºÍÄ£ºý´¦Àí
+
+
+½ÚµãÑ¡Ïî˵Ã÷:
+
+* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
+* grow: À©ÕÅ·ù¶È¡£ÕýÖµÊÇÏòÍâÀ©ÕÅ£¬¸ºÖµÊÇÏòÄÚÊÕËõ¡£
+* blur: Ä£ºý¡£
+
+### MaskEdgeShrink
+ʹmask±ßԵƽ»¬µØ¹ý¶ÉÊÕËõ£¬²¢±£Áô±ßԵϸ½Ú¡£
+
+
+½ÚµãÑ¡Ïî˵Ã÷:
+
+* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
+* shrink_level: ÊÕËõƽ»¬¼¶±ð¡£
+* soft: ƽ»¬·ù¶È¡£
+* edge_shrink: ±ßÔµÊÕËõ·ù¶È¡£
+* edge_reserve: ±£Áô±ßԵϸ½Ú·ù¶È, 100ΪÍêÈ«±£Áô£¬0ΪÍêÈ«²»±£Áô¡£
+
+MaskGrowÓëMaskEdgeShrinkЧ¹û¶Ô±È
+
+
+### MaskMotionBlur
+ʹmask²úÉúÔ˶¯Ä£ºý¡£
+
+
+½ÚµãÑ¡Ïî˵Ã÷:
+
+* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
+* blur: Ä£ºý´óС¡£
+* angle: Ä£ºý½Ç¶È¡£
+
+### MaskGradient
+ʹmask´ÓÒ»²à²úÉú½¥±ä¡£
+
+
+½ÚµãÑ¡Ïî˵Ã÷:
+
+* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
+* gradient_side: ´ÓÄĸö±ß²úÉú½¥±ä¡£ÓÐËĸö·½Ïò£º¶¥²àtop¡¢µ×²àbottom¡¢×ó²àleft¡¢ÓÒ²àright¡£
+* gradient_scale: ½¥±ä¾àÀ롣ĬÈÏÖµ100±íʾ½¥±ä²úÉúÒ»²àÍêȫ͸Ã÷£¬ÁíÒ»²àÍêÈ«²»Í¸Ã÷¡£ÊýֵԽС£¬´Ó͸Ã÷µ½²»Í¸Ã÷µÄ¾àÀëÔ½¶Ì¡£
+* gradient_offset: ½¥±äλÖÃÆ«ÒÆ¡£
+* opacity: ½¥±äµÄ²»Í¸Ã÷¶È¡£
+
+### MaskStroke
+²úÉúmaskÂÖÀªÃè±ß¡£
+
+
+½ÚµãÑ¡Ïî˵Ã÷:
+
+* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
+* stroke_grow: Ãè±ßÀ©ÕÅ/ÊÕËõ·ù¶È£¬ÕýÖµÊÇÀ©ÕÅ£¬¸ºÖµÊÇÊÕËõ¡£
+* stroke_width: Ãè±ß¿í¶È¡£
+* blur: Ãè±ßÄ£ºý¡£
+
+### MaskPreview
+Ô¤ÀÀmask
+
+
+
+### MaskInvert
+mask·´×ª
+
+
+
+
# LayerFilter

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