def draw_square(self,width,height,radius): img = cairo.ImageSurface(cairo.FORMAT_ARGB32, width,height) cr = cairo.Context(img) m = radius/sqrt(2.0) # Slightly darker border for the colour area sw = max(0.75*self.stroke_width, 3.0) cr.set_source_rgb(*NEUTRAL_DARK_GREY) cr.rectangle(self.x0-m-sw, self.y0-m-sw, 2*(m+sw), 2*(m+sw)) cr.fill() h,s,v = self.hsv ds = 2*m*CSTEP v = 0.0 x1 = self.x0-m x2 = self.x0+m y = self.y0-m while v < 1.0: g = cairo.LinearGradient(x1,y,x2,y) g.add_color_stop_rgb(0.0, *hsv_to_rgb(h,0.0,1.0-v)) g.add_color_stop_rgb(1.0, *hsv_to_rgb(h,1.0,1.0-v)) cr.set_source(g) cr.rectangle(x1,y, 2*m, ds) cr.fill_preserve() cr.stroke() y += ds v += CSTEP h,s,v = self.hsv x = self.x0-m + s*2*m y = self.y0-m + (1-v)*2*m cr.set_source_rgb(*hsv_to_rgb(1-h,1-s,1-v)) cr.arc(x,y, 3.0, 0.0, 2*pi) cr.stroke() return img
def brush_selected_cb(self, bm, managed_brush, brushinfo): """Responds to the user changing their brush. This observer callback is responsible for allocating the current brush settings to the current brush singleton in `self.app`. The Brush Selector, the Pick Context action, and the Brushkeys and Device-specific brush associations all cause this to be invoked. """ self._in_brush_selected_cb = True b = self.app.brush prev_lock_alpha = b.is_alpha_locked() # Changing the effective brush b.begin_atomic() color = b.get_color_hsv() mix_old = b.get_base_value('restore_color') b.load_from_brushinfo(brushinfo) self.unmodified_brushinfo = b.clone() # Preserve color mix = b.get_base_value('restore_color') if mix: c1 = hsv_to_rgb(*color) c2 = hsv_to_rgb(*b.get_color_hsv()) c3 = [(1.0 - mix) * v1 + mix * v2 for v1, v2 in zip(c1, c2)] color = rgb_to_hsv(*c3) elif mix_old and self._last_selected_color: # switching from a brush with fixed color back to a normal one color = self._last_selected_color b.set_color_hsv(color) b.set_string_property("parent_brush_name", managed_brush.name) if b.is_eraser(): # User picked a dedicated eraser brush # Unset any lock_alpha state (necessary?) self.set_override_setting("lock_alpha", False) else: # Preserve the old lock_alpha state self.set_override_setting("lock_alpha", prev_lock_alpha) b.end_atomic() # Updates the blend mode buttons to match the new settings. # First decide which blend mode is active and which aren't. active_blend_mode = self.bm.normal_mode for mode in self.bm.modes: setting_name = mode.setting_name if setting_name is not None: if b.has_large_base_value(setting_name): active_blend_mode = mode active_blend_mode.active = True self._in_brush_selected_cb = False
def draw_gradient(self,cr, start,end, hsv=True): if hsv: clr1 = hsv_to_rgb(*start) clr2 = hsv_to_rgb(*end) else: clr1 = start clr2 = end g = cairo.LinearGradient(0,0,self.w,self.h) g.add_color_stop_rgb(0.0, *clr1) g.add_color_stop_rgb(1.0, *clr2) cr.set_source(g) cr.rectangle(0,0,self.w,self.h) cr.fill()
def expose_cb(self, widget, event): cr = self.window.cairo_create() cr.set_source_rgb(0.9, 0.9, 0.9) cr.paint() cr.set_line_join(cairo.LINE_JOIN_ROUND) cr.translate(0.0, popup_height/2.0) for i, c in enumerate(self.app.ch.colors): if i != self.selection: cr.scale(0.5, 0.5) line_width = 3.0 distance = 2*line_width rect = [0, -popup_height/2.0, popup_height, popup_height] rect[0] += distance/2.0 rect[1] += distance/2.0 rect[2] -= distance rect[3] -= distance cr.rectangle(*rect) cr.set_source_rgb(*helpers.hsv_to_rgb(*c)) cr.fill_preserve() cr.set_line_width(line_width) cr.set_source_rgb(0, 0, 0) cr.stroke() cr.translate(popup_height, 0) if i != self.selection: cr.scale(2.0, 2.0) return True
def set_color(self, hsv, exclude=None): for w in self.widgets: if w is not exclude: w.set_color(hsv) self.color = hsv_to_rgb(*hsv) self.hsv = hsv self.on_select(hsv)
def get_color_hsv(self): h = self.get_base_value('color_h') s = self.get_base_value('color_s') v = self.get_base_value('color_v') rgb = helpers.hsv_to_rgb(h, s, v) rgb = rgb[0]**(1 / 2.4), rgb[1]**(1 / 2.4), rgb[2]**(1 / 2.4) hsv = helpers.rgb_to_hsv(*rgb) h, s, v = hsv assert not math.isnan(h) return (h, s, v)
def set_color(self, hsv): self.atomic = True self.color = r,g,b = hsv_to_rgb(*hsv) self.rspin.set_value(r*255) self.gspin.set_value(g*255) self.bspin.set_value(b*255) self.rsel.set_color(hsv) self.gsel.set_color(hsv) self.bsel.set_color(hsv) self.atomic = False self.hsv = hsv
def set_color(self, hsv): self.atomic = True self.hsv = h,s,v = hsv self.hspin.set_value(h*359) self.sspin.set_value(s*100) self.vspin.set_value(v*100) self.hsel.set_color(hsv) self.ssel.set_color(hsv) self.vsel.set_color(hsv) self.atomic = False self.color = hsv_to_rgb(*hsv)
def get_color_hsv(self): tf = self.EOTF h = self.get_base_value('color_h') s = self.get_base_value('color_s') v = self.get_base_value('color_v') rgb = helpers.hsv_to_rgb(h, s, v) rgb = rgb[0]**(1 / tf), rgb[1]**(1 / tf), rgb[2]**(1 / tf) hsv = helpers.rgb_to_hsv(*rgb) h, s, v = hsv assert not math.isnan(h) return (h, s, v)
def push_color(self, color): if self.atomic: return for c in self.colors: if self.hsv_equal(c, color): self.colors.remove(c) break self.colors = (self.colors + [color])[-self.num_colors:] self.last_color = helpers.hsv_to_rgb(*color) self.app.preferences['colorhistory.colors'] = [tuple(hsv) for hsv in self.colors] for func in self.color_pushed_observers: func(color)
def set_color_hsv(self, hsv): if not hsv: return self.begin_atomic() try: rgb = helpers.hsv_to_rgb(*hsv) rgb = rgb[0]**2.4, rgb[1]**2.4, rgb[2]**2.4 hsv = helpers.rgb_to_hsv(*rgb) h, s, v = hsv self.set_base_value('color_h', h) self.set_base_value('color_s', s) self.set_base_value('color_v', v) finally: self.end_atomic()
def set_color_hsv(self, hsv): tf = self.EOTF if not hsv: return self.begin_atomic() try: rgb = helpers.hsv_to_rgb(*hsv) rgb = rgb[0]**tf, rgb[1]**tf, rgb[2]**tf hsv = helpers.rgb_to_hsv(*rgb) h, s, v = hsv self.set_base_value('color_h', h) self.set_base_value('color_s', s) self.set_base_value('color_v', v) finally: self.end_atomic()
def draw(self,w, event): if not self.window: return cr = self.window.cairo_create() h,s,v = self.hsv dx = self.w*CSTEP x = 0 h1 = 0. while h1 < 1: cr.set_source_rgb(*hsv_to_rgb(h1,s,v)) cr.rectangle(x,0,dx,self.h) cr.fill_preserve() cr.stroke() h1 += CSTEP x += dx x1 = h*self.w self.draw_line_at(cr, x1)
def draw_circle(self, w, h): if self.circle_img: return self.circle_img img = cairo.ImageSurface(cairo.FORMAT_ARGB32, w,h) cr = cairo.Context(img) cr.set_line_width(0.75*self.stroke_width) a1 = 0.0 while a1 < 2*pi: clr = hsv_to_rgb(a1/(2*pi), 1.0, 1.0) x1,y1,x2,y2 = self.calc_line(a1) a1 += CSTEP cr.set_source_rgb(*clr) cr.move_to(x1,y1) cr.line_to(x2,y2) cr.stroke() self.circle_img = img return img
def select_color_at(self, x,y): color, is_hsv = self.get_color_at(x,y) if color is None: return if is_hsv: self.hsv = color self.color = hsv_to_rgb(*color) else: self.color = color self.hsv = rgb_to_hsv(*color) if self.device_pressed: selected_color = self.color # Potential device change, therefore brush & colour change... self.app.doc.tdw.device_used(self.device_pressed) self.color = selected_color #... but *this* is what the user wants self.redraw_on_select() self.on_select(self.hsv)
def set_color(self, hsv): self.hsv = hsv self.color = hsv_to_rgb(*hsv) self.queue_draw()
def get_color_rgb(self): hsv = self.get_color_hsv() return helpers.hsv_to_rgb(*hsv)
def set_color(self, hsv, redraw=True): self.hsv = hsv self.color = hsv_to_rgb(*hsv) if redraw: self.queue_draw()
def brush_selected_cb(self, bm, managed_brush, brushinfo): """Responds to the user changing their brush. This observer callback is responsible for allocating the current brush settings to the current brush singleton in `self.app`. The Brush Selector, the Pick Context action, and the Brushkeys and Device-specific brush associations all cause this to be invoked. """ self._in_brush_selected_cb = True b = self.app.brush prev_lock_alpha = b.is_alpha_locked() # Changing the effective brush # Preserve colour b.begin_atomic() color = b.get_color_hsv() mix_old = b.get_base_value('restore_color') b.load_from_brushinfo(brushinfo) self.unmodified_brushinfo = b.clone() mix = b.get_base_value('restore_color') if mix: c1 = hsv_to_rgb(*color) c2 = hsv_to_rgb(*b.get_color_hsv()) c3 = [(1.0-mix)*v1 + mix*v2 for v1, v2 in zip(c1, c2)] color = rgb_to_hsv(*c3) elif mix_old: # switching from a brush with fixed color back to a normal one color = self._last_selected_color b.set_color_hsv(color) b.set_string_property("parent_brush_name", managed_brush.name) if b.is_eraser(): # User picked a dedicated eraser brush # Unset any lock_alpha state (necessary?) self.set_override_setting("lock_alpha", False) else: # Preserve the old lock_alpha state self.set_override_setting("lock_alpha", prev_lock_alpha) b.end_atomic() # Updates the blend mode buttons to match the new settings. # First decide which blend mode is active and which aren't. active_blend_mode = self.normal_mode blend_modes = [] for mode_action in self.action_group.list_actions(): setting_name = mode_action.setting_name if setting_name is not None: if b.has_large_base_value(setting_name): active_blend_mode = mode_action blend_modes.append(mode_action) blend_modes.remove(active_blend_mode) # Twiddle the UI to match without emitting "activate" signals. active_blend_mode.block_activate() active_blend_mode.set_active(True) active_blend_mode.unblock_activate() for other in blend_modes: other.block_activate() other.set_active(False) other.unblock_activate() self._in_brush_selected_cb = False
def draw_harmony_ring(self,width,height): """Draws the harmony ring if any colour harmonies are visible.""" if not self.window: return points_size = min(width,height)/27. img = cairo.ImageSurface(cairo.FORMAT_ARGB32, width,height) cr = cairo.Context(img) if not self.has_harmonies_visible(): self.draw_central_fill(cr, self.r2) return img h,s,v = self.hsv a = h*2*pi self.angles = [] self.simple_colors = [] cr.set_line_width(0.75*self.stroke_width) self.samples = [self.hsv] for i in range(int(CIRCLE_N)): c1 = c = h + i/CIRCLE_N if c1 > 1: c1 -= 1 clr = hsv_to_rgb(c1,s,v) hsv = c1,s,v self.simple_colors.append(clr) delta = c1 - h an = -c1*2*pi self.angles.append(-an+pi/CIRCLE_N) a1 = an-pi/CIRCLE_N a2 = an+pi/CIRCLE_N cr.new_path() cr.set_source_rgb(*clr) cr.move_to(self.x0,self.y0) cr.arc(self.x0, self.y0, self.r2, a1, a2) cr.line_to(self.x0,self.y0) cr.fill_preserve() cr.set_source_rgb(*NEUTRAL_DARK_GREY) # "lines" between samples cr.stroke() # Indicate harmonic colors if self.app.preferences.get("colorsampler.triadic", False) and i%(CIRCLE_N/3)==0: self.small_triangle(cr, points_size, self.inv(clr), an, (self.r2+self.rd)/2) try_put(self.samples, hsv) if self.app.preferences.get("colorsampler.complementary", False) and i%(CIRCLE_N/2)==0: self.small_circle(cr, points_size, self.inv(clr), an, (self.r2+self.rd)/2) try_put(self.samples, hsv) if self.app.preferences.get("colorsampler.square", False) and i%(CIRCLE_N/4)==0: self.small_square(cr, points_size, self.inv(clr), an, (self.r2+self.rd)/2) try_put(self.samples, hsv) # FIXME: should this harmonies be expressed in terms of CIRCLE_N? if self.app.preferences.get("colorsampler.double_comp", False) and i in [0,2,6,8]: self.small_rect_vert(cr, points_size, self.inv(clr), an, (self.r2+self.rd)/2) try_put(self.samples, hsv) if self.app.preferences.get("colorsampler.split_comp", False) and i in [0,5,7]: self.small_triangle_down(cr, points_size, self.inv(clr), an, (self.r2+self.rd)/2) try_put(self.samples, hsv) if self.app.preferences.get("colorsampler.analogous", False) and i in [0,1,CIRCLE_N-1]: self.small_rect(cr, points_size, self.inv(clr), an, (self.r2+self.rd)/2) try_put(self.samples, hsv) # Fill the centre self.draw_central_fill(cr, self.rd) # And an inner thin line cr.set_source_rgb(*NEUTRAL_DARK_GREY) cr.arc(self.x0, self.y0, self.rd, 0, 2*pi) cr.stroke() return img
def set_color_hsv(self, h, s, v): self.rgb = helpers.hsv_to_rgb(h, s, v) self.queue_draw()
def hsv_equal(a, b): # hack required because we somewhere have an rgb<-->hsv conversion roundtrip a_ = numpy.array(helpers.hsv_to_rgb(*a)) b_ = numpy.array(helpers.hsv_to_rgb(*b)) return ((a_ - b_)**2).sum() < (3*1.0/256)**2