From f820e4c7f3d76fd3892a73f5543e76b4795bafb3 Mon Sep 17 00:00:00 2001 From: Victor Doval <10011941+vicdoval@users.noreply.github.com> Date: Sat, 7 Nov 2020 21:14:06 +0100 Subject: [PATCH 1/3] Color Mix Node --- index.md | 1 + nodes/vector/color_mix.py | 289 +++++++++++++++++++++++++++++++ ui/icons/sv_color_mix.png | Bin 0 -> 1680 bytes ui/icons/svg/sv_color_mix.svg | 317 ++++++++++++++++++++++++++++++++++ 4 files changed, 607 insertions(+) create mode 100644 nodes/vector/color_mix.py create mode 100644 ui/icons/sv_color_mix.png create mode 100644 ui/icons/svg/sv_color_mix.svg diff --git a/index.md b/index.md index 6678be09b..6ad0c6f1b 100644 --- a/index.md +++ b/index.md @@ -679,6 +679,7 @@ SvFormulaColorNode SvTextureEvaluateNodeMk2 SvColorRampNode + SvColorMixNode --- SvSculptMaskNode SvSelectMeshVerts diff --git a/nodes/vector/color_mix.py b/nodes/vector/color_mix.py new file mode 100644 index 000000000..2f6402198 --- /dev/null +++ b/nodes/vector/color_mix.py @@ -0,0 +1,289 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy +from bpy.props import EnumProperty, FloatProperty, BoolProperty, FloatVectorProperty + +from sverchok.ui.sv_icons import custom_icon +from sverchok.node_tree import SverchCustomTreeNode +from sverchok.data_structure import (updateNode, list_match_func, numpy_list_match_modes, numpy_list_match_func) +from sverchok.utils.modules.color_utils import hsv_to_rgb, rgb_to_hsv +from sverchok.utils.sv_itertools import recurse_f_level_control +import numpy as np + +def overlay_func(fac, color_a, color_b): + end_color = np.array(color_a) + for i in range(color_a.shape[1]): + mask = color_a[:, i] < 0.5 + mask2 = color_a[:, i] > 0.5 + end_color[mask, i] = 2 * color_a[mask, i] * color_b[mask, i] + end_color[mask2, i] = 1.0 - 2.0 * (1.0 - color_a[mask2, i]) * (1.0 - color_b[mask2, i]) + return color_a + (end_color - color_a) * fac[:, np.newaxis] + + +def soft_light_func(fac, color_a, color_b): + end_color = np.array(color_a) + for i in range(color_a.shape[1]): + mask = color_b[:, i] < 0.5 + mask2 = color_b[:, i] > 0.5 + end_color[mask, i] = 2.0 * color_a[mask, i] * color_b[mask, i] + color_a[mask, i] * color_a[mask, i] * (1.0 - 2.0 * color_b[mask, i]) + end_color[mask2, i] = np.sqrt(color_a[mask2, i]) * (2.0 * color_b[mask2, i] - 1.0) + 2.0 * color_a[mask2, i] * (1.0 - color_b[mask2, i]) + return color_a + (end_color - color_a) * fac[:, np.newaxis] + +def color_dodge_func(fac, color_a, color_b): + end_color = np.array(color_a) + for i in range(color_a.shape[1]): + mask = color_b[:, i] >= 1 + mask2 = np.invert(mask) + end_color[mask, i] = color_b[mask, i] + end_color[mask2, i] = np.minimum(color_a[mask2, i] / (1.0 - color_b[mask2, i]), 1.0) + return color_a + (end_color - color_a) * fac[:, np.newaxis] + + +def color_burn_func(fac, color_a, color_b): + end_color = np.array(color_a) + for i in range(color_a.shape[1]): + mask = color_b[:, i] <= 0 + mask2 = np.invert(mask) + end_color[mask, i] = color_b[mask, i] + end_color[mask2, i] = np.maximum((1.0 - ((1.0 - color_a[mask2, i]) / color_b[mask2, i])), 0.0) + return color_a + (end_color - color_a) * fac[:, np.newaxis] + + +def vivid_light_func(fac, color_a, color_b): + end_color = np.array(color_a) + for i in range(color_a.shape[1]): + mask = color_b[:, i] < 0.5 + mask2 = np.invert(mask) + mask1b = color_b[:, i] <= 0 + mask2b = color_b[:, i] >= 1 + end_color[mask, i] = np.maximum((1.0 - ((1.0 - color_a[mask, i]) / color_b[mask, i])), 0.0) + end_color[mask1b, i] = color_b[mask1b, i] + end_color[mask2, i] = np.minimum(color_a[mask2, i] / (1.0 - color_b[mask2, i]), 1.0) + end_color[mask2b, i] = color_b[mask2b, i] + return color_a + (end_color - color_a) * fac[:, np.newaxis] + + +def pin_light_func(fac, color_a, color_b): + end_color = np.array(color_a) + for i in range(color_a.shape[1]): + mask = color_b[:, i] < 0.5 + mask2 = np.invert(mask) + end_color[mask, i] = np.minimum(color_a[mask, i], 2 * color_b[mask, i]) + end_color[mask2, i] = np.maximum(color_a[mask2, i], 2*(color_b[mask2, i]-0.5)) + + return color_a + (end_color - color_a) * fac[:, np.newaxis] + + +def hard_mix_func(fac, color_a, color_b): + end_color = np.array(color_a) + for i in range(color_a.shape[1]): + mask = color_b[:, i] < 0.5 + mask2 = np.invert(mask) + mask1b = color_b[:, i] <= 0 + mask2b = color_b[:, i] >= 1 + end_color[mask, i] = np.maximum((1.0 - ((1.0 - color_a[mask, i]) / color_b[mask, i])), 0.0) + end_color[mask1b, i] = color_b[mask1b, i] + end_color[mask2, i] = np.minimum(color_a[mask2, i] / (1.0 - color_b[mask2, i]), 1.0) + end_color[mask2b, i] = color_b[mask2b, i] + mask_f = end_color[:, i] < 0.5 + end_color[mask_f, i] = 0 + end_color[np.invert(mask_f), i] = 1 + return color_a + (end_color - color_a) * fac[:, np.newaxis] + + +def reflect_func(fac, color_a, color_b): + end_color = np.array(color_a) + for i in range(color_a.shape[1]): + mask = color_b[:, i] >= 1 + mask2 = np.invert(mask) + + end_color[mask, i] = color_b[mask, i] + end_color[mask2, i] = np.minimum(color_a[mask2, i] * color_a[mask2, i] / (1.0 - color_b[mask2, i]), 1.0) + + return color_a + (end_color - color_a) * fac[:, np.newaxis] + +def hue_mix(fac, color_a, color_b): + return hsv_mix(fac, color_a, color_b, 0) + +def saturation_mix(fac, color_a, color_b): + return hsv_mix(fac, color_a, color_b, 1) + +def value_mix(fac, color_a, color_b): + return hsv_mix(fac, color_a, color_b, 2) + +def hsv_mix(fac, color_a, color_b, channel): + hsv_color_a = rgb_to_hsv(color_a) + hsv_color_b = rgb_to_hsv(color_b) + hsv_end_color = np.array(hsv_color_a) + hsv_end_color[:, channel] = hsv_color_b[:, channel] + rgb_end_color = np.zeros(color_a.shape) + rgb_end_color[:, :3] = hsv_to_rgb(hsv_end_color[:, :3]) + rgb_end_color[:, 3] = color_b[:, 3] + return color_a + (rgb_end_color - color_a) * fac[:, np.newaxis] + + + +COLOR_MODES_DICT = { + "Mix": (lambda fac, color_a, color_b: color_a + (color_b-color_a) * fac[:, np.newaxis], 1), + "Darken": (lambda fac, color_a, color_b: color_a + (np.minimum(color_a, color_b) - color_a) * fac[:, np.newaxis], 10), + "Multiply": (lambda fac, color_a, color_b: color_a + ((color_a * color_b) - color_a) * fac[:, np.newaxis], 11), + "Burn": (color_burn_func, 12), + + "Lighten": (lambda fac, color_a, color_b: color_a + (np.maximum(color_a, color_b) -color_a) * fac[:, np.newaxis], 20), + "Screen": (lambda fac, color_a, color_b: color_a + ((1 - (1 - color_a) * (1 - color_b)) - color_a) * fac[:, np.newaxis], 21), + "Dodge": (color_dodge_func, 22), + "Add": (lambda fac, color_a, color_b: color_a + color_b * fac[:, np.newaxis], 23), + + "Overlay": (overlay_func, 30), + "Soft Light": (soft_light_func, 31), + "Vivd Light": (vivid_light_func, 32), + "Pin Light": (pin_light_func, 33), + "Hard Mix": (hard_mix_func, 34), + + "Subtract": (lambda fac, color_a, color_b: color_a - color_b * fac[:, np.newaxis], 40), + "Difference": (lambda fac, color_a, color_b: color_a + ((color_a-color_b)-color_a) * fac[:, np.newaxis], 41), + "Divide": (lambda fac, color_a, color_b: color_a + ((color_a/color_b)-color_a) * fac[:, np.newaxis], 42), + + "Reflect": (reflect_func, 50), + + "Hue": (hue_mix, 60), + "Saturation": (saturation_mix, 61), + "Value": (value_mix, 62), + + + +} + +COLOR_MODE_ITEMS = [(k.replace(" ", "_"), k, k, "", COLOR_MODES_DICT[k][1]) for k in COLOR_MODES_DICT] + + +def color_mix(params, constant, matching_f): + result = [] + mode, match_mode, clamp, out_numpy = constant + params = matching_f(params) + numpy_match = numpy_list_match_func[match_mode] + color_func = COLOR_MODES_DICT[mode.replace("_", " ")][0] + + for props in zip(*params): + + np_props = [np.array(prop) for prop in props] + fac, color_a, color_b = numpy_match(np_props) + + res = color_func(fac, color_a, color_b) + if clamp: + res = np.clip(res, 0, 1) + result.append(res if out_numpy else res.tolist()) + + return result + +class SvColorMixNode(bpy.types.Node, SverchCustomTreeNode): + """ + Triggers: Colors Math + Tooltip: Mix colors with various methods. + """ + bl_idname = 'SvColorMixNode' + bl_label = 'Color Mix' + sv_icon = 'SV_COLOR_MIX' + + current_op: EnumProperty( + name="Function", description="Function choice", default="Mix", + items=COLOR_MODE_ITEMS, update=updateNode) + + factor: FloatProperty(default=1.0, name='Fac', soft_min=0.0, soft_max=1.0, update=updateNode) + + list_match: EnumProperty( + name="List Match", + description="Behavior on different list lengths", + items=numpy_list_match_modes, default="REPEAT", + update=updateNode) + + output_numpy: BoolProperty( + name='Output NumPy', + description='Output NumPy arrays', + default=False, update=updateNode) + clamp_output: BoolProperty( + name='Clamp', + description='Clamp result of the node to 0..1 range.', + default=False, update=updateNode) + + color_A = FloatVectorProperty( + name="Color A", + subtype='COLOR', + size=4, + default=(0.0, 0.0, 0.0, 1.0), + min=0.0, max=1.0, + description="color picker", + update=updateNode + ) + color_B = FloatVectorProperty( + name="Color B", + subtype='COLOR', + size=4, + default=(1.0, 1.0, 1.0, 1.0), + min=0.0, max=1.0, + description="color picker", + update=updateNode + ) + + def draw_label(self): + if self.hide: + label = ["Color", self.current_op,] + return " ".join(label) + return "Color Mix" + + def draw_buttons(self, ctx, layout): + + layout.prop(self, 'current_op', text="", icon_value=custom_icon("SV_FUNCTION")) + layout.prop(self, 'clamp_output') + + def draw_buttons_ext(self, ctx, layout): + layout.prop(self, 'current_op', text="", icon_value=custom_icon("SV_FUNCTION")) + layout.prop(self, 'list_match', expand=False) + layout.prop(self, 'clamp_output') + layout.prop(self, 'output_numpy', expand=False) + + def rclick_menu(self, context, layout): + layout.prop_menu_enum(self, 'current_op', text="Function") + layout.prop(self, 'clamp_output') + layout.prop(self, "output_numpy", expand=False) + + + def sv_init(self, context): + self.inputs.new('SvStringsSocket', "Fac").prop_name = 'factor' + self.inputs.new('SvColorSocket', "Color A").prop_name = 'color_A' + self.inputs.new('SvColorSocket', "Color B").prop_name = 'color_B' + self.outputs.new('SvColorSocket', "Color") + + + def process(self): + + if self.outputs[0].is_linked: + + params = [si.sv_get(default=[[]], deepcopy=False) for si in self.inputs] + matching_f = list_match_func[self.list_match] + + desired_levels = [2, 3, 3] + ops = [self.current_op, self.list_match, self.clamp_output, self.output_numpy] + result = recurse_f_level_control(params, ops, color_mix, matching_f, desired_levels) + + self.outputs[0].sv_set(result) + + +classes = [SvColorMixNode] +register, unregister = bpy.utils.register_classes_factory(classes) diff --git a/ui/icons/sv_color_mix.png b/ui/icons/sv_color_mix.png new file mode 100644 index 0000000000000000000000000000000000000000..d7ee3b17b7df084339cc7327c6d2381304ce6634 GIT binary patch literal 1680 zcmV;B25NWZ1L8szPf?0qrI3xq$j^;|#h-Z^0c8F9^)fs>ELNzjtQ0_$ z;uqlFTZzxnir(-U7!|Jv7Vc|C-g(Is-?3wdrSK%i#>PY`C1GJ0TG0*64kSJUs8?K-Lr?N+Al40pXLPVK)S@9ay`u7@m1L_%iLP0H-U0 z>gsADBO?RyMn^|kw{9JM=UjaFHq5ZWsngsIi~&yVYLI&r(*{Cqi_`wOnJG#mE33Q@EC{pRPw>*n$Kn+6uoiNTnE zQ+f6J;O`qCciDOd2M2Mx-BTk!Ki|)3YHBKOx0|uCF*-Ur7?muZ+iJZ63*sRr8Ut9Y zwIVTb9)3sfPz^*yC*g27u-om{o&q2tA%Xq-_tVg*>Fl-UB+f%?MWPmnoMmy9`+tFy zloa%>u0;UMojVu1-A?}nYkSlz5QqxsJvk9ho;-=q=lfj&w6wG^G~^~}esG>v;PrU@ z4f(n7hnN9YtXM%!O%0u$opg3~GCn@;$MyMqQ+B7*$$G$yO*?Nn0Hrb;?>{553MNRBVc0k`E0H5E{MlC`8hK44kSH2UFz!U#Ow72 zY&K4uI3a-S-VwZknH;^1NYZ=^tr!Mk;Ce$cA`s>t;-S+&!GQaA8XFr)OG_g>JpAS) z0JwJT8bw7#FwxIj+rbuQNt=dUAHb1%lt39!4NSXwZvyo#KSE|^CUJ4Wo7kN@chc0< z#G6~;HtQIt;m|iw@jp;-9C&x~`HcaL15YX#>F@7HL;^Z1D=VX{tc*tD<~d+9 zFc;7<(UARsDSl6a^(0pp9R1eZ?;qPhE4+sA2JjB%7F%X}E6fJAX~h~thz7HnSP+k% ze2sgCp}iZ!Jv8-kdP#)6z#G8-gE literal 0 HcmV?d00001 diff --git a/ui/icons/svg/sv_color_mix.svg b/ui/icons/svg/sv_color_mix.svg new file mode 100644 index 000000000..ba8e28349 --- /dev/null +++ b/ui/icons/svg/sv_color_mix.svg @@ -0,0 +1,317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + -- GitLab From ab6a654b7afdbe90c01eb9bbc6e28555cf73fee4 Mon Sep 17 00:00:00 2001 From: Victor Doval <10011941+vicdoval@users.noreply.github.com> Date: Sat, 7 Nov 2020 21:24:04 +0100 Subject: [PATCH 2/3] fix to #3700 --- utils/modules/color_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/modules/color_utils.py b/utils/modules/color_utils.py index 8c0e2b7fe..9369a672f 100644 --- a/utils/modules/color_utils.py +++ b/utils/modules/color_utils.py @@ -107,7 +107,8 @@ def rgb_to_hsv(rgb_col): mask_s = max_comp == 0 mask_other = np.invert(mask_s) - hsv_col[mask_s,1] = 0 + hsv_col[mask_s, 0] = 0 + hsv_col[mask_s, 1] = 0 hsv_col[mask_other, 1] = 1 - min_comp[mask_other] / max_comp[mask_other] hsv_col[:, 2] = max_comp if rgb_col.shape[1] == 4: -- GitLab From c9ebcafba648f1f28300ed8c8b42702b55b7340a Mon Sep 17 00:00:00 2001 From: Victor Doval <10011941+vicdoval@users.noreply.github.com> Date: Sat, 7 Nov 2020 22:48:53 +0100 Subject: [PATCH 3/3] Color Mix documentation --- docs/nodes/vector/color_mix.rst | 40 ++++++++++++++++++++++++++++++ docs/nodes/vector/vector_index.rst | 1 + nodes/vector/color_mix.py | 2 +- ui/icons/svg/sv_color_mix.svg | 8 +++--- 4 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 docs/nodes/vector/color_mix.rst diff --git a/docs/nodes/vector/color_mix.rst b/docs/nodes/vector/color_mix.rst new file mode 100644 index 000000000..d00791421 --- /dev/null +++ b/docs/nodes/vector/color_mix.rst @@ -0,0 +1,40 @@ +Color Mix +========= + +Functionality +------------- + +This node allows you mix colors using many standard methods. + +Inputs +------ + +**Fac**: Amount of mixture to be applied. + +**Color A**: Base color. + +**Color B**: Top color. + + +Parameters +---------- + +This node has the following parameters: + +* **Function**. Offers Mix, Darken, Multiply, Burn, Lighten, Screen, Dodge, Add, Overlay, Soft Light, Vivid Light, Pin Light, Hard Mix, Subtract, Difference, Divide, Reflect, Hue, Saturation, Value. + +* **Clamp**. Clamp result of the node to 0..1 range. + +Outputs +------- + +This node has only one output: **Color**. It is RGB or RGBA vector, depending on the inputted colors. + +Examples +-------- + +.. image:: https://user-images.githubusercontent.com/10011941/98450826-530e4600-2140-11eb-8206-b3e4e40c8565.png + +.. image:: https://user-images.githubusercontent.com/10011941/98450861-9b2d6880-2140-11eb-9c46-728aeb3ced01.png + +.. image:: https://user-images.githubusercontent.com/10011941/98450881-da5bb980-2140-11eb-861c-e45c1f483a84.png diff --git a/docs/nodes/vector/vector_index.rst b/docs/nodes/vector/vector_index.rst index c98f955d0..257fb33b5 100644 --- a/docs/nodes/vector/vector_index.rst +++ b/docs/nodes/vector/vector_index.rst @@ -31,5 +31,6 @@ Vector color_in_mk1 color_out_mk1 color_ramp + color_mix formula_color texture_evaluate_mk2 diff --git a/nodes/vector/color_mix.py b/nodes/vector/color_mix.py index 2f6402198..4a7333dde 100644 --- a/nodes/vector/color_mix.py +++ b/nodes/vector/color_mix.py @@ -152,7 +152,7 @@ COLOR_MODES_DICT = { "Overlay": (overlay_func, 30), "Soft Light": (soft_light_func, 31), - "Vivd Light": (vivid_light_func, 32), + "Vivid Light": (vivid_light_func, 32), "Pin Light": (pin_light_func, 33), "Hard Mix": (hard_mix_func, 34), diff --git a/ui/icons/svg/sv_color_mix.svg b/ui/icons/svg/sv_color_mix.svg index ba8e28349..acc76a698 100644 --- a/ui/icons/svg/sv_color_mix.svg +++ b/ui/icons/svg/sv_color_mix.svg @@ -7,9 +7,9 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - inkscape:export-ydpi="96" - inkscape:export-xdpi="96" - inkscape:export-filename="C:\Users\PcCom\AppData\Roaming\Blender Foundation\Blender\2.81\scripts\addons\sverchok\ui\icons\sv_pulga_fit_force.png" + inkscape:export-ydpi="48.000004" + inkscape:export-xdpi="48.000004" + inkscape:export-filename="C:\Users\PcCom\AppData\Roaming\Blender Foundation\Blender\2.81\scripts\addons\sverchok\ui\icons\sv_color_mix.png" sodipodi:docname="sv_color_mix.svg" inkscape:version="1.0 (4035a4fb49, 2020-05-01)" id="svg8" @@ -198,7 +198,7 @@ inkscape:current-layer="layer1" inkscape:document-units="mm" inkscape:cy="34.402707" - inkscape:cx="44.417072" + inkscape:cx="35.8313" inkscape:zoom="4.7753425" inkscape:pageshadow="2" inkscape:pageopacity="0" -- GitLab