diff --git a/fury/ui/elements.py b/fury/ui/elements.py index 12d41afbe..0f82e5d17 100644 --- a/fury/ui/elements.py +++ b/fury/ui/elements.py @@ -3,7 +3,7 @@ __all__ = ["TextBox2D", "LineSlider2D", "LineDoubleSlider2D", "RingSlider2D", "RangeSlider", "Checkbox", "Option", "RadioButton", "ComboBox2D", "ListBox2D", "ListBoxItem2D", "FileMenu2D", - "DrawShape", "DrawPanel"] + "DrawShape", "DrawPanel", "ColorPicker"] import os from collections import OrderedDict @@ -20,6 +20,8 @@ from fury.ui.core import Button2D from fury.utils import (set_polydata_vertices, vertices_from_actor, update_actor) +# this line was changed +from fury.lib import ScalarBarActor, LookupTable, UnsignedCharArray, VTK_VERSION class TextBox2D(UI): @@ -874,7 +876,6 @@ def _setup(self): self.handles[1].on_left_mouse_button_released = \ self.handle_release_callback - def _get_actors(self): """Get the actors composing this UI component.""" return (self.track.actors + self.handles[0].actors + @@ -2839,6 +2840,7 @@ def left_button_clicked(self, i_ren, _obj, _list_box_item): def resize(self, size): self.background.resize(size) + class FileMenu2D(UI): """A menu to select files in the current folder. @@ -3484,3 +3486,236 @@ def left_button_dragged(self, i_ren, _obj, element): mouse_position = self.clamp_mouse_position(i_ren.event.position) self.handle_mouse_drag(mouse_position) i_ren.force_render() + + +class ColorPicker(UI): + """ A color picker that uses the HSV color model. + It consists of a vertical bar that selects the hue and + a square that selects the saturation and value for the selected hue. + """ + + def __init__(self, position=(0, 0)): + """ Initialize the color picker. + Parameters + ---------- + side : int + The side length of the square(and height of the hue bar) in pixels. + position : (float, float) + Coordinates (x, y) of the lower-left corner of the sqaure. + """ + super(ColorPicker, self).__init__(position) + self.side = 100 + self.pointer.position = self.ColorSelectionSquare.center + self.position = position + self.current_hue = 0 + self.current_saturation = 0.5 + self.current_value = 0.5 + # Offer some standard hooks to the user. + self.on_change = lambda color_picker, r, g, b: None + + def _setup(self): + """ Setup this UI component. + """ + # Setup the hue bar + # this line was changed + self.HueBar = ScalarBarActor() + self.HueBar.GetPositionCoordinate().SetCoordinateSystemToDisplay() + self.HueBar.GetPosition2Coordinate().SetCoordinateSystemToDisplay() + self.HueBar.SetNumberOfLabels(0) + hueLut = LookupTable() # this line was changed + hueLut.SetTableRange(0, 1) + hueLut.SetHueRange(0, 1) + hueLut.SetSaturationRange(1, 1) + hueLut.SetValueRange(1, 1) + hueLut.Build() + self.HueBar.SetLookupTable(hueLut) + self.add_callback(self.HueBar, "MouseMoveEvent", + self.hue_select_callback) + # Setup colors for the square + colors = UnsignedCharArray() # this line was changed + colors.SetNumberOfComponents(3) + colors.SetName("Colors") + colors.InsertNextTuple3(*self.hsv2rgb(0, 0, 0)) + colors.InsertNextTuple3(*self.hsv2rgb(0, 1, 0)) + colors.InsertNextTuple3(*self.hsv2rgb(0, 1, 1)) + colors.InsertNextTuple3(*self.hsv2rgb(0, 0, 1)) + # Setup the square + self.ColorSelectionSquare = Rectangle2D() + self.ColorSelectionSquare._polygonPolyData.GetPointData().\ + SetScalars(colors) + # if int(VTK_VERSION) <= 5: + # self.ColorSelectionSquare._polygonPolyData.Update() + self.ColorSelectionSquare.on_left_mouse_button_dragged = \ + self.color_select_callback + # Setup the circle pointer + self.pointer = Disk2D(outer_radius=1) + + def _get_actors(self): + """ Get the actors composing this UI component. + """ + return self.ColorSelectionSquare.actors + self.HueBar +\ + self.pointer.actors + + def _add_to_scene(self, scene): + """Add all subcomponents or VTK props that compose this UI component. + + Parameters + ---------- + scene : scene + """ + self.ColorSelectionSquare._add_to_scene(scene) # this line was changed + self.pointer._add_to_scene(scene) # this line was changed + scene.add(self.HueBar) + # def _add_to_renderer(self, ren): + # """ Add all subcomponents or VTK props that compose this UI component. + # Parameters + # ---------- + # ren : renderer + # """ + # self.ColorSelectionSquare.add_to_renderer(ren) + # self.pointer.add_to_renderer(ren) + # ren.add(self.HueBar) + + def _get_size(self): + return (6*self.side/5, self.side) + + @property + def side(self): + return self.HueBar.GetPosition2()[1] + + @side.setter + def side(self, side): + self.HueBar.SetPosition2(side / 5, side) + self.ColorSelectionSquare.resize((side, side)) + self.pointer.outer_radius = side/50 + self.pointer.inner_radius = side/50 - 1 + + @property + def current_hue(self): + color = self.ColorSelectionSquare._polygonPolyData.GetPointData().\ + GetScalars().GetTuple(2) + return self.rgb2hsv(*color)[0] + + @current_hue.setter + def current_hue(self, hue): + colors = UnsignedCharArray() # this line was changed + colors.SetNumberOfComponents(3) + colors.SetName("Colors") + colors.InsertNextTuple3(*self.hsv2rgb(hue, 0, 0)) + colors.InsertNextTuple3(*self.hsv2rgb(hue, 1, 0)) + colors.InsertNextTuple3(*self.hsv2rgb(hue, 1, 1)) + colors.InsertNextTuple3(*self.hsv2rgb(hue, 0, 1)) + self.ColorSelectionSquare._polygonPolyData.GetPointData().\ + SetScalars(colors) + # if int(VTK_VERSION) <= 5: # this line was changed + # self.ColorSelectionSquare._polygonPolyData.Update() + + def rgb2hsv(self, R, G, B): + """ Function to convert given RGB value to HSV + Parameters + ---------- + R : float[0-255] + Red value. + G : float[0-255] + Green value. + B : float[0-255] + Blue value. + """ + (r, g, b) = (R/255, G/255, B/255) + cmax = max(r, g, b) + cmin = min(r, g, b) + delta = cmax - cmin + if delta == 0: + H = 0 + elif cmax == r: + H = 60*(((g-b)/delta) % 6) + elif cmax == g: + H = 60*(((b-r)/delta)+2) + elif cmax == b: + H = 60*(((r-g)/delta)+4) + if cmax == 0: + S = 0 + else: + S = delta/cmax + V = cmax + return H, S, V + + def hsv2rgb(self, H, S, V): + """ Function to convert given HSV value to RGB + Parameters + ---------- + H : float[0-360] + Hue. + S : float[0-1] + Saturation. + V : float[0-1] + Value. + """ + C = V * S + X = C * (1 - abs((H / 60) % 2 - 1)) + m = V - C + if H < 60: + (r, g, b) = (C, X, 0) + elif H < 120: + (r, g, b) = (X, C, 0) + elif H < 180: + (r, g, b) = (0, C, X) + elif H < 240: + (r, g, b) = (0, X, C) + elif H < 300: + (r, g, b) = (X, 0, C) + else: + (r, g, b) = (C, 0, X) + (R, G, B) = ((r+m)*255, (g+m)*255, (b+m)*255) + return R, G, B + + def _set_position(self, coords): + """ Position the lower-left corner of this UI component. + Parameters + ---------- + coords: (float, float) + Absolute pixel coordinates (x, y). + """ + offset = coords - self.position + self.ColorSelectionSquare.position = coords + self.pointer.center += offset + self.HueBar.SetPosition(coords[0] + self.side, coords[1]) + + def hue_select_callback(self, i_ren, obj, color_picker): + """ A callback to select the hue from the hue bar. + Parameters + ---------- + i_ren: :class:`CustomInteractorStyle` + obj: :class:`vtkActor` + The picked actor + color_picker: :class:`ColorPicker` + """ + FractionalHue = (i_ren.event.position[1] - self.position[1])/self.side + self.current_hue = FractionalHue * 360 + self.on_change( + self, *self.hsv2rgb(self.current_hue, self.current_saturation, + self.current_value)) + i_ren.force_render() + i_ren.event.abort() + + def color_select_callback(self, i_ren, obj, rectangle2d): + """ A callback to select the hue and saturation from the sqaure. + Parameters + ---------- + i_ren: :class:`CustomInteractorStyle` + obj: :class:`vtkActor` + The picked actor + rectangle2d: :class:`Rectangle2D` + """ + coords = i_ren.event.position + # Ensure coords are within the square + coords = np.maximum(coords, self.position) + coords = np.minimum(coords, self.position + self.side) + self.pointer.center = coords + relative_coords = coords - self.position + self.current_saturation, self.current_value = relative_coords/self.side + self.on_change( + self, *self.hsv2rgb(self.current_hue, self.current_saturation, + self.current_value)) + i_ren.force_render() + i_ren.event.abort()