def dynamic_ellipse(self, x, y, sx, sy): points_in_curve = 360 x1, y1 = difference(sx, sy, x, y) x1, y1, sin, cos = starting_point_for_ellipse(x1, y1, self.angle) rx, ry = point_in_ellipse(x1, y1, sin, cos, 0) self.brush_prep(sx + rx, sy + ry) entry_p, midpoint_p, prange1, prange2, h, t = self.line_settings() head = points_in_curve * h head_range = int(head) + 1 tail = points_in_curve * t tail_range = int(tail) + 1 tail_length = points_in_curve - tail # Beginning px, py = point_in_ellipse(x1, y1, sin, cos, 1) length, nx, ny = length_and_normal(rx, ry, px, py) mx, my = multiply_add(rx, ry, nx, ny, 0.25) self._stroke_to(sx + mx, sy + my, entry_p) pressure = abs(1 / head * prange1 + entry_p) self._stroke_to(sx + px, sy + py, pressure) for degree in xrange(2, head_range): px, py = point_in_ellipse(x1, y1, sin, cos, degree) pressure = abs(degree / head * prange1 + entry_p) self._stroke_to(sx + px, sy + py, pressure) # Middle for degree in xrange(head_range, tail_range): px, py = point_in_ellipse(x1, y1, sin, cos, degree) self._stroke_to(sx + px, sy + py, midpoint_p) # End for degree in xrange(tail_range, points_in_curve + 1): px, py = point_in_ellipse(x1, y1, sin, cos, degree) pressure = abs((degree - tail) / tail_length * prange2 + midpoint_p) self._stroke_to(sx + px, sy + py, pressure)
def draw_curve_1(self, cx, cy, sx, sy, ex, ey): points_in_curve = 100 entry_p, midpoint_p, prange1, prange2, h, t = self.line_settings() mx, my = midpoint(sx, sy, ex, ey) length, nx, ny = length_and_normal(mx, my, cx, cy) cx, cy = multiply_add(mx, my, nx, ny, length * 2) x1, y1 = difference(sx, sy, cx, cy) x2, y2 = difference(cx, cy, ex, ey) head = points_in_curve * h head_range = int(head) + 1 tail = points_in_curve * t tail_range = int(tail) + 1 tail_length = points_in_curve - tail # Beginning px, py = point_on_curve_1(1, cx, cy, sx, sy, x1, y1, x2, y2) length, nx, ny = length_and_normal(sx, sy, px, py) bx, by = multiply_add(sx, sy, nx, ny, 0.25) self._stroke_to(bx, by, entry_p) pressure = abs(1 / head * prange1 + entry_p) self._stroke_to(px, py, pressure) for i in xrange(2, head_range): px, py = point_on_curve_1(i, cx, cy, sx, sy, x1, y1, x2, y2) pressure = abs(i / head * prange1 + entry_p) self._stroke_to(px, py, pressure) # Middle for i in xrange(head_range, tail_range): px, py = point_on_curve_1(i, cx, cy, sx, sy, x1, y1, x2, y2) self._stroke_to(px, py, midpoint_p) # End for i in xrange(tail_range, points_in_curve + 1): px, py = point_on_curve_1(i, cx, cy, sx, sy, x1, y1, x2, y2) pressure = abs((i - tail) / tail_length * prange2 + midpoint_p) self._stroke_to(px, py, pressure)
def new_from_pixbuf_average(class_, pixbuf): """Returns the the average of all colors in a pixbuf.""" assert pixbuf.get_colorspace() == GdkPixbuf.Colorspace.RGB assert pixbuf.get_bits_per_sample() == 8 n_channels = pixbuf.get_n_channels() assert n_channels in (3, 4) if n_channels == 3: assert not pixbuf.get_has_alpha() else: assert pixbuf.get_has_alpha() data = pixbuf.get_pixels() w, h = pixbuf.get_width(), pixbuf.get_height() rowstride = pixbuf.get_rowstride() n_pixels = w * h r = g = b = 0 for y in xrange(h): for x in xrange(w): offs = y * rowstride + x * n_channels r += ord(data[offs]) g += ord(data[offs + 1]) b += ord(data[offs + 2]) r = r / n_pixels g = g / n_pixels b = b / n_pixels return RGBColor(r / 255, g / 255, b / 255)
def dynamic_ellipse(self, x, y, sx, sy): points_in_curve = 360 x1, y1 = difference(sx, sy, x, y) x1, y1, sin, cos = starting_point_for_ellipse(x1, y1, self.angle) rx, ry = point_in_ellipse(x1, y1, sin, cos, 0) self.brush_prep(sx+rx, sy+ry) entry_p, midpoint_p, prange1, prange2, h, t = self.line_settings() head = points_in_curve * h head_range = int(head)+1 tail = points_in_curve * t tail_range = int(tail)+1 tail_length = points_in_curve - tail # Beginning px, py = point_in_ellipse(x1, y1, sin, cos, 1) length, nx, ny = length_and_normal(rx, ry, px, py) mx, my = multiply_add(rx, ry, nx, ny, 0.25) self._stroke_to(sx+mx, sy+my, entry_p) pressure = abs(1/head * prange1 + entry_p) self._stroke_to(sx+px, sy+py, pressure) for degree in xrange(2, head_range): px, py = point_in_ellipse(x1, y1, sin, cos, degree) pressure = abs(degree/head * prange1 + entry_p) self._stroke_to(sx+px, sy+py, pressure) # Middle for degree in xrange(head_range, tail_range): px, py = point_in_ellipse(x1, y1, sin, cos, degree) self._stroke_to(sx+px, sy+py, midpoint_p) # End for degree in xrange(tail_range, points_in_curve+1): px, py = point_in_ellipse(x1, y1, sin, cos, degree) pressure = abs((degree-tail)/tail_length * prange2 + midpoint_p) self._stroke_to(sx+px, sy+py, pressure)
def draw_curve_1(self, cx, cy, sx, sy, ex, ey): points_in_curve = 100 entry_p, midpoint_p, prange1, prange2, h, t = self.line_settings() mx, my = midpoint(sx, sy, ex, ey) length, nx, ny = length_and_normal(mx, my, cx, cy) cx, cy = multiply_add(mx, my, nx, ny, length*2) x1, y1 = difference(sx, sy, cx, cy) x2, y2 = difference(cx, cy, ex, ey) head = points_in_curve * h head_range = int(head)+1 tail = points_in_curve * t tail_range = int(tail)+1 tail_length = points_in_curve - tail # Beginning px, py = point_on_curve_1(1, cx, cy, sx, sy, x1, y1, x2, y2) length, nx, ny = length_and_normal(sx, sy, px, py) bx, by = multiply_add(sx, sy, nx, ny, 0.25) self._stroke_to(bx, by, entry_p) pressure = abs(1/head * prange1 + entry_p) self._stroke_to(px, py, pressure) for i in xrange(2, head_range): px, py = point_on_curve_1(i, cx, cy, sx, sy, x1, y1, x2, y2) pressure = abs(i/head * prange1 + entry_p) self._stroke_to(px, py, pressure) # Middle for i in xrange(head_range, tail_range): px, py = point_on_curve_1(i, cx, cy, sx, sy, x1, y1, x2, y2) self._stroke_to(px, py, midpoint_p) # End for i in xrange(tail_range, points_in_curve+1): px, py = point_on_curve_1(i, cx, cy, sx, sy, x1, y1, x2, y2) pressure = abs((i-tail)/tail_length * prange2 + midpoint_p) self._stroke_to(px, py, pressure)
def test_all_modes_correctness(self): """Test that all modes work the way they advertise""" # Test each mode in turn for mode in xrange(mypaintlib.NumCombineModes): mode_info = mypaintlib.combine_mode_get_info(mode) mode_name = mode_info["name"] src = self.src dst = np.empty((N, N, 4), dtype='uint16') dst[...] = self.dst_orig[...] # Combine using the current mode mypaintlib.tile_combine(mode, src, dst, True, 1.0) # Tests zero_alpha_has_effect = False zero_alpha_clears_backdrop = True # means: "*always* clears b." can_decrease_alpha = False for i in xrange(len(self.sample_data)): for j in xrange(len(self.sample_data)): old = tuple(self.dst_orig[i, j]) new = tuple(dst[i, j]) if src[i][j][3] == 0: if new[3] != 0 and old[3] != 0: zero_alpha_clears_backdrop = False if old != new: zero_alpha_has_effect = True if (not can_decrease_alpha) and (new[3] < old[3]): can_decrease_alpha = True self.assertFalse( (new[0] > new[3] or new[1] > new[3] or new[2] > new[3]), msg="%s isn't writing premultiplied data properly" % (mode_name, ), ) self.assertFalse( (new[0] > FIX15_ONE or new[1] > FIX15_ONE or new[2] > FIX15_ONE or new[3] > FIX15_ONE), msg="%s isn't writing fix15 data properly" % (mode_name, ), ) flag_test_results = [ ("zero_alpha_has_effect", zero_alpha_has_effect), ("zero_alpha_clears_backdrop", zero_alpha_clears_backdrop), ("can_decrease_alpha", can_decrease_alpha), ] for info_str, tested_value in flag_test_results: current_value = bool(mode_info[info_str]) self.assertEqual( current_value, tested_value, msg="%s's %r is wrong: should be %r, not %r" % (mode_name, info_str, tested_value, current_value), ) self.assertTrue( mode in MODE_STRINGS, msg="%s needs localizable UI strings" % (mode_name, ), )
def _tile_pixbuf(pixbuf, repeats_x, repeats_y): """Make a repeated tiled image of a pixbuf""" w, h = pixbuf.get_width(), pixbuf.get_height() result = new_blank_pixbuf((0, 0, 0), repeats_x * w, repeats_y * h) for xi in xrange(repeats_x): for yi in xrange(repeats_y): pixbuf.copy_area(0, 0, w, h, result, w * xi, h * yi) return result
def draw_cb(self, widget, cr): # Paint the base color, and the list's pixbuf. state_flags = widget.get_state_flags() style_context = widget.get_style_context() style_context.save() bg_color_gdk = style_context.get_background_color(state_flags) bg_color = uicolor.from_gdk_rgba(bg_color_gdk) cr.set_source_rgb(*bg_color.get_rgb()) cr.paint() Gdk.cairo_set_source_pixbuf(cr, self.pixbuf, 0, 0) cr.paint() # border colors gdkrgba = style_context.get_background_color(state_flags | Gtk.StateFlags.SELECTED) selected_color = uicolor.from_gdk_rgba(gdkrgba) gdkrgba = style_context.get_background_color(state_flags | Gtk.StateFlags.NORMAL) insertion_color = uicolor.from_gdk_rgba(gdkrgba) style_context.restore() # Draw borders last_i = len(self.itemlist) - 1 for i, b in enumerate(self.itemlist): rect_color = None if b is self.selected: rect_color = selected_color elif self.drag_insertion_index is not None: if i == self.drag_insertion_index \ or (i == last_i and self.drag_insertion_index > i): rect_color = insertion_color if rect_color is None: continue x = (i % self.tiles_w) * self.total_w y = (i // self.tiles_w) * self.total_h w = self.total_w h = self.total_h def shrink(pixels, x, y, w, h): x += pixels y += pixels w -= 2 * pixels h -= 2 * pixels return (x, y, w, h) x, y, w, h = shrink(self.spacing_outside, x, y, w, h) for j in xrange(self.border_visible_outside_cell): x, y, w, h = shrink(-1, x, y, w, h) max_j = self.border_visible + self.border_visible_outside_cell for j in xrange(max_j): cr.set_source_rgb(*rect_color.get_rgb()) cr.rectangle(x, y, w - 1, h - 1) # FIXME: check pixel alignment cr.stroke() x, y, w, h = shrink(1, x, y, w, h) return True
def render_checks(cr, size, nchecks): """Render a checquerboard pattern to a cairo surface""" cr.set_source_rgb(*gui.style.ALPHA_CHECK_COLOR_1) cr.paint() cr.set_source_rgb(*gui.style.ALPHA_CHECK_COLOR_2) for i in xrange(0, nchecks): for j in xrange(0, nchecks): if (i + j) % 2 == 0: continue cr.rectangle(i * size, j * size, size, size) cr.fill()
def render_checks(cr, size, nchecks): """Render a checquerboard pattern to a cairo surface""" cr.set_source_rgb(*gui.style.ALPHA_CHECK_COLOR_1) cr.paint() cr.set_source_rgb(*gui.style.ALPHA_CHECK_COLOR_2) for i in xrange(0, nchecks): for j in xrange(0, nchecks): if (i+j) % 2 == 0: continue cr.rectangle(i*size, j*size, size, size) cr.fill()
def _init_groups(self): """Initialize brush groups, loading them from disk.""" self.contexts = [None for i in xrange(_NUM_BRUSHKEYS)] self.history = [None for i in xrange(_BRUSH_HISTORY_SIZE)] brush_cache = {} self._init_ordered_groups(brush_cache) self._init_unordered_groups(brush_cache) self._init_default_brushkeys_and_history() # clean up legacy stuff fn = os.path.join(self.user_brushpath, 'deleted.conf') if os.path.exists(fn): os.remove(fn)
def set_mask_from_palette(self, pal): """Sets the mask from a palette. Any `palette.Palette` can be loaded into the wheel widget, and color names are used for distinguishing mask shapes. If a color name matches the pattern "``mask #<decimal-int>``", it will be associated with the shape having the ID ``<decimal-int>``. """ if pal is None: return mask_id_re = re.compile(r'\bmask\s*#?\s*(\d+)\b') mask_shapes = {} for i in xrange(len(pal)): color = pal.get_color(i) if color is None: continue shape_id = 0 color_name = pal.get_color_name(i) if color_name is not None: mask_id_match = mask_id_re.search(color_name) if mask_id_match: shape_id = int(mask_id_match.group(1)) if shape_id not in mask_shapes: mask_shapes[shape_id] = [] mask_shapes[shape_id].append(color) mask_list = [] shape_ids = sorted(mask_shapes.keys()) for shape_id in shape_ids: mask_list.append(mask_shapes[shape_id]) self.set_mask(mask_list)
def _draw_curve_segment(self, model, p_1, p0, p1, p2, state): """Draw the curve segment between the middle two points""" last_t_abs = state["t_abs"] dtime_p0_p1_real = p1[-1] - p0[-1] steps_t = dtime_p0_p1_real / self.INTERPOLATION_MAX_SLICE_TIME dist_p1_p2 = math.hypot(p1[0] - p2[0], p1[1] - p2[1]) steps_d = dist_p1_p2 / self.INTERPOLATION_MAX_SLICE_DISTANCE steps = math.ceil(min(self.INTERPOLATION_MAX_SLICES, max(2, steps_t, steps_d))) for i in xrange(int(steps) + 1): t = i / steps point = gui.drawutils.spline_4p(t, p_1, p0, p1, p2) x, y, pressure, xtilt, ytilt, t_abs, viewzoom, viewrotation, barrel_rotation = point pressure = lib.helpers.clamp(pressure, 0.0, 1.0) xtilt = lib.helpers.clamp(xtilt, -1.0, 1.0) ytilt = lib.helpers.clamp(ytilt, -1.0, 1.0) t_abs = max(last_t_abs, t_abs) dtime = t_abs - last_t_abs viewzoom = self.doc.tdw.scale viewrotation = self.doc.tdw.rotation barrel_rotation = 0.0 self.stroke_to( model, dtime, x, y, pressure, xtilt, ytilt, viewzoom, viewrotation, barrel_rotation, auto_split=False, ) last_t_abs = t_abs state["t_abs"] = last_t_abs
def _draw_curve_segment(self, model, p_1, p0, p1, p2, state): """Draw the curve segment between the middle two points""" last_t_abs = state["t_abs"] dtime_p0_p1_real = p1[-1] - p0[-1] steps_t = dtime_p0_p1_real / self.INTERPOLATION_MAX_SLICE_TIME dist_p1_p2 = math.hypot(p1[0] - p2[0], p1[1] - p2[1]) steps_d = dist_p1_p2 / self.INTERPOLATION_MAX_SLICE_DISTANCE steps = math.ceil( min(self.INTERPOLATION_MAX_SLICES, max(2, steps_t, steps_d))) for i in xrange(int(steps) + 1): t = i / steps point = gui.drawutils.spline_4p(t, p_1, p0, p1, p2) x, y, pressure, xtilt, ytilt, t_abs, viewzoom, viewrotation = point pressure = lib.helpers.clamp(pressure, 0.0, 1.0) xtilt = lib.helpers.clamp(xtilt, -1.0, 1.0) ytilt = lib.helpers.clamp(ytilt, -1.0, 1.0) t_abs = max(last_t_abs, t_abs) dtime = t_abs - last_t_abs viewzoom = self.doc.tdw.scale viewrotation = self.doc.tdw.rotation self.stroke_to( model, dtime, x, y, pressure, xtilt, ytilt, viewzoom, viewrotation, auto_split=False, ) last_t_abs = t_abs state["t_abs"] = last_t_abs
def append(self, col, name=None, unique=False, match=False): """Appends a color, optionally setting a name for it. :param col: The color to append. :param name: Name of the color to insert. :param unique: If true, don't append if the color already exists in the palette. Only exact matches count. :param match: If true, set the match position to the appropriate palette entry. """ col = self._copy_color_in(col, name) if unique: # Find the final exact match, if one is present for i in xrange(len(self._colors)-1, -1, -1): if col == self._colors[i]: if match: self._match_position = i self._match_is_approx = False self.match_changed() return # Append new color, and select it if requested end_i = len(self._colors) self._colors.append(col) if match: self._match_position = end_i self._match_is_approx = False self.match_changed() self.sequence_changed()
def __add_void(self, x, y): # Adds a new shape into the empty space centred at `x`, `y`. self.queue_draw() # Pick a nice size for the new shape, taking care not to # overlap any other shapes, at least initially. alloc = self.get_allocation() cx, cy = self.get_center(alloc=alloc) radius = self.get_radius(alloc=alloc) dx, dy = x - cx, y - cy r = math.sqrt(dx**2 + dy**2) d = self.__dist_to_nearest_shape(x, y) if d is None: d = radius size = min((radius - r), d) * 0.95 minsize = radius * self.min_shape_size if size < minsize: return # Create a regular polygon with one of its edges facing the # middle of the wheel. shape = [] nsides = 3 + len(self.get_mask()) psi = math.atan2(dy, dx) + (math.pi / nsides) psi += math.pi for i in xrange(nsides): theta = 2.0 * math.pi * i / nsides theta += psi px = int(x + size * math.cos(theta)) py = int(y + size * math.sin(theta)) col = self.get_color_at_position(px, py, ignore_mask=True) shape.append(col) mask = self.get_mask() mask.append(shape) self.set_mask(mask)
def interpolate(self, other, steps): """YCbCr interpolation. >>> yellow = YCbCrColor(color=RGBColor(1,1,0)) >>> red = YCbCrColor(color=RGBColor(1,0,0)) >>> [c.to_hex_str() for c in yellow.interpolate(red, 3)] ['#feff00', '#ff7f00', '#ff0000'] This colorspace is a simple transformation of the RGB cube, so to within a small margin of error, the results of this interpolation are identical to an interpolation in RGB space. >>> y_rgb = RGBColor(1,1,0) >>> r_rgb = RGBColor(1,0,0) >>> [c.to_hex_str() for c in y_rgb.interpolate(r_rgb, 3)] ['#ffff00', '#ff7f00', '#ff0000'] """ assert steps >= 3 other = YCbCrColor(color=other) # Like HSV, interpolate using the shortest angular distance. for step in xrange(steps): p = step / (steps - 1) Y = self.Y + (other.Y - self.Y) * p Cb = self.Cb + (other.Cb - self.Cb) * p Cr = self.Cr + (other.Cr - self.Cr) * p yield YCbCrColor(Y=Y, Cb=Cb, Cr=Cr)
def __add_void(self, x, y): # Adds a new shape into the empty space centred at `x`, `y`. self.queue_draw() # Pick a nice size for the new shape, taking care not to # overlap any other shapes, at least initially. alloc = self.get_allocation() cx, cy = self.get_center(alloc=alloc) radius = self.get_radius(alloc=alloc) dx, dy = x-cx, y-cy r = math.sqrt(dx**2 + dy**2) d = self.__dist_to_nearest_shape(x, y) if d is None: d = radius size = min((radius - r), d) * 0.95 minsize = radius * self.min_shape_size if size < minsize: return # Create a regular polygon with one of its edges facing the # middle of the wheel. shape = [] nsides = 3 + len(self.get_mask()) psi = math.atan2(dy, dx) + (math.pi/nsides) psi += math.pi for i in xrange(nsides): theta = 2.0 * math.pi * i / nsides theta += psi px = int(x + size*math.cos(theta)) py = int(y + size*math.sin(theta)) col = self.get_color_at_position(px, py, ignore_mask=True) shape.append(col) mask = self.get_mask() mask.append(shape) self.set_mask(mask)
def setUp(self): if not mypaintlib.heavy_debug: self.skipTest("not compiled with HEAVY_DEBUG") # Unpremultiplied floating-point RGBA tuples self.sample_data = [] # Make some alpha/grey/color combinations for a in ALPHA_VALUES: for v in GREY_VALUES: self.sample_data.append((v, v, v, a)) for r in COLOR_COMPONENT_VALUES: for g in COLOR_COMPONENT_VALUES: for b in COLOR_COMPONENT_VALUES: self.sample_data.append((r, g, b, a)) assert len(self.sample_data) < N # What the hell, have some random junk too for i in xrange(len(self.sample_data), N): fuzz = (random(), random(), random(), random()) self.sample_data.append(fuzz) assert len(self.sample_data) == N # Prepare striped test data in a tile array self.src = np.empty((N, N, 4), dtype='uint16') self.dst_orig = np.empty((N, N, 4), dtype='uint16') for i, rgba1 in enumerate(self.sample_data): r1 = int(FIX15_ONE * rgba1[0] * rgba1[3]) g1 = int(FIX15_ONE * rgba1[1] * rgba1[3]) b1 = int(FIX15_ONE * rgba1[2] * rgba1[3]) a1 = int(FIX15_ONE * rgba1[3]) assert r1 <= FIX15_ONE assert r1 >= 0 assert g1 <= FIX15_ONE assert g1 >= 0 assert b1 <= FIX15_ONE assert b1 >= 0 assert a1 <= FIX15_ONE assert a1 >= 0 assert r1 <= a1 assert g1 <= a1 assert b1 <= a1 for j in xrange(len(self.sample_data)): self.src[i, j, :] = (r1, g1, b1, a1) self.dst_orig[j, i, :] = (r1, g1, b1, a1)
def new_from_pixbuf_average(class_, pixbuf): """Returns the the average of all colors in a pixbuf. >>> p = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 5, 5) >>> p.fill(0x880088ff) >>> UIColor.new_from_pixbuf_average(p).to_hex_str() '#880088' """ assert pixbuf.get_colorspace() == GdkPixbuf.Colorspace.RGB assert pixbuf.get_bits_per_sample() == 8 n_channels = pixbuf.get_n_channels() assert n_channels in (3, 4) if n_channels == 3: assert not pixbuf.get_has_alpha() else: assert pixbuf.get_has_alpha() data = pixbuf.get_pixels() assert isinstance(data, bytes) w, h = pixbuf.get_width(), pixbuf.get_height() rowstride = pixbuf.get_rowstride() n_pixels = w*h r = g = b = 0 for y in xrange(h): for x in xrange(w): offs = y*rowstride + x*n_channels if PY3: # bytes=bytes. Indexing produces ints. r += data[offs] g += data[offs+1] b += data[offs+2] else: # bytes=str. Indexing of produces a str of len 1. r += ord(data[offs]) g += ord(data[offs+1]) b += ord(data[offs+2]) r = r / n_pixels g = g / n_pixels b = b / n_pixels return RGBColor(r/255, g/255, b/255)
def new_from_pixbuf_average(class_, pixbuf): """Returns the the average of all colors in a pixbuf. >>> p = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 5, 5) >>> p.fill(0x880088ff) >>> UIColor.new_from_pixbuf_average(p).to_hex_str() '#880088' """ assert pixbuf.get_colorspace() == GdkPixbuf.Colorspace.RGB assert pixbuf.get_bits_per_sample() == 8 n_channels = pixbuf.get_n_channels() assert n_channels in (3, 4) if n_channels == 3: assert not pixbuf.get_has_alpha() else: assert pixbuf.get_has_alpha() data = pixbuf.get_pixels() assert isinstance(data, bytes) w, h = pixbuf.get_width(), pixbuf.get_height() rowstride = pixbuf.get_rowstride() n_pixels = w * h r = g = b = 0 for y in xrange(h): for x in xrange(w): offs = y * rowstride + x * n_channels if PY3: # bytes=bytes. Indexing produces ints. r += data[offs] g += data[offs + 1] b += data[offs + 2] else: # bytes=str. Indexing of produces a str of len 1. r += ord(data[offs]) g += ord(data[offs + 1]) b += ord(data[offs + 2]) r = r / n_pixels g = g / n_pixels b = b / n_pixels return RGBColor(r / 255, g / 255, b / 255)
def add_distance_fade_stops(gr, rgb, nstops=3, gamma=2, alpha=1.0): """Adds rgba stops to a Cairo gradient approximating a power law fade. The stops have even spacing between the 0 and 1 positions, and alpha values diminishing from 1 to 0. When `gamma` is greater than 1, the generated fades or glow diminishes faster than a linear gradient. This seems to reduce halo artifacts on some LCD backlit displays. """ red, green, blue = rgb nstops = int(nstops) + 2 for s in xrange(nstops+1): a = alpha * (((nstops - s) / nstops) ** gamma) stop = s / nstops gr.add_color_stop_rgba(stop, red, green, blue, a)
def add_distance_fade_stops(gr, rgb, nstops=3, gamma=2, alpha=1.0): """Adds rgba stops to a Cairo gradient approximating a power law fade. The stops have even spacing between the 0 and 1 positions, and alpha values diminishing from 1 to 0. When `gamma` is greater than 1, the generated fades or glow diminishes faster than a linear gradient. This seems to reduce halo artefacts on some LCD backlit displays. """ red, green, blue = rgb nstops = int(nstops) + 2 for s in xrange(nstops+1): a = alpha * (((nstops - s) / nstops) ** gamma) stop = s / nstops gr.add_color_stop_rgba(stop, red, green, blue, a)
def consume_buf(): ty = state['ty']-1 for i in xrange(state['buf'].shape[1] // N): tx = x // N + i src = state['buf'][:, i*N:(i+1)*N, :] if src[:, :, 3].any(): with self.tile_request(tx, ty, readonly=False) as dst: mypaintlib.tile_convert_rgba8_to_rgba16(src, dst) if state["progress"]: try: state["progress"].completed(ty - ty0) except Exception: logger.exception("Progress.completed() failed") state["progress"] = None
def render_background_cb(self, cr, wd, ht, icon_border=None): v = self._conf.get_value() col = self.__get_central_color() b = icon_border if b is None: b = self.BORDER_WIDTH eff_wd = int(wd - 2 * b) eff_ht = int(ht - 2 * b) f1, f2 = self.__get_faces() step = max(1, int(eff_wd // 128)) rect_x, rect_y = int(b) + 0.5, int(b) + 0.5 rect_w, rect_h = int(eff_wd) - 1, int(eff_ht) - 1 # Paint the central area offscreen cr.push_group() for x in xrange(0, eff_wd, step): # xmin <= amt <= xmax amt = x / eff_wd setattr(col, f1, map_to_range(v.xmin, v.xmax, amt)) # ymin <= f2 <= ymax setattr(col, f2, v.ymax) lg = cairo.LinearGradient(b + x, b, b + x, b + eff_ht) lg.add_color_stop_rgb(*([0.0] + list(col.get_rgb()))) setattr(col, f2, v.ymin) lg.add_color_stop_rgb(*([1.0] + list(col.get_rgb()))) cr.rectangle(b + x, b, step, eff_ht) cr.set_source(lg) cr.fill() slice_patt = cr.pop_group() # Tango-like outline cr.set_line_join(cairo.LINE_JOIN_ROUND) cr.rectangle(rect_x, rect_y, rect_w, rect_h) cr.set_line_width(self.OUTLINE_WIDTH) cr.set_source_rgba(*self.OUTLINE_RGBA) cr.stroke() # The main area cr.set_source(slice_patt) cr.paint() # Tango-like highlight over the top cr.rectangle(rect_x, rect_y, rect_w, rect_h) cr.set_line_width(self.EDGE_HIGHLIGHT_WIDTH) cr.set_source_rgba(*self.EDGE_HIGHLIGHT_RGBA) cr.stroke()
def _init_default_brushkeys_and_history(self): """Assign sensible defaults for brushkeys and history. Operates by filling in the gaps after `_init_unordered_groups()` has had a chance to populate the two lists. """ # Try the default startup group first. default_group = self.groups.get(_DEFAULT_STARTUP_GROUP, None) # Otherwise, use the biggest group to minimise the chance # of repetition. if default_group is None: groups_by_len = sorted( (len(g), n, g) for n, g in self.groups.items()) _len, _name, default_group = groups_by_len[-1] # Populate blank entries. for i in xrange(_NUM_BRUSHKEYS): if self.contexts[i] is None: idx = (i + 9) % 10 # keyboard order c_name = unicode('context%02d') % i c = ManagedBrush(self, name=c_name, persistent=False) group_idx = idx % len(default_group) b = default_group[group_idx] b.clone_into(c, c_name) self.contexts[i] = c for i in xrange(_BRUSH_HISTORY_SIZE): if self.history[i] is None: h_name = unicode('%s%d') % (_BRUSH_HISTORY_NAME_PREFIX, i) h = ManagedBrush(self, name=h_name, persistent=False) group_i = i % len(default_group) b = default_group[group_i] b.clone_into(h, h_name) self.history[i] = h
def remove_all_tool_widgets(self): """Remove all tool widgets in the stack, without cleanup This method is only intended to be used right before the parent of a ToolStack is destroyed. It does not remove any placeholders, only the individual tool widgets. """ for notebook in self._get_notebooks(): for index in xrange(notebook.get_n_pages()): page = notebook.get_nth_page(index) widget = page.get_child() widget.hide() page.remove(widget) if self.workspace: self.workspace.tool_widget_removed(widget)
def _regenerate_mipmap(self, t, tx, ty): t = _Tile() self.tiledict[(tx, ty)] = t empty = True for x in xrange(2): for y in xrange(2): src = self.parent.tiledict.get((tx*2 + x, ty*2 + y), transparent_tile) if src is mipmap_dirty_tile: src = self.parent._regenerate_mipmap( src, tx*2 + x, ty*2 + y, ) mypaintlib.tile_downscale_rgba16(src.rgba, t.rgba, x * N // 2, y * N // 2) if src.rgba is not transparent_tile.rgba: empty = False if empty: # rare case, no need to speed it up del self.tiledict[(tx, ty)] t = transparent_tile return t
def _outwards_from(n, i): """Search order within the palette, outwards from a given index. Defined for a sequence of len() `n`, outwards from index `i`. """ assert i < n and i >= 0 yield i for j in xrange(n): exhausted = True if i - j >= 0: yield i - j exhausted = False if i + j < n: yield i + j exhausted = False if exhausted: break
def render_background_cb(self, cr, wd, ht, icon_border=None): col = HSVColor(color=self.get_managed_color()) b = icon_border if b is None: b = self.BORDER_WIDTH eff_wd = int(wd - 2*b) eff_ht = int(ht - 2*b) f1, f2 = self.__get_faces() step = max(1, int(eff_wd // 128)) rect_x, rect_y = int(b)+0.5, int(b)+0.5 rect_w, rect_h = int(eff_wd)-1, int(eff_ht)-1 # Paint the central area offscreen cr.push_group() for x in xrange(0, eff_wd, step): amt = x / eff_wd setattr(col, f1, amt) setattr(col, f2, 1.0) lg = cairo.LinearGradient(b+x, b, b+x, b+eff_ht) lg.add_color_stop_rgb(*([0.0] + list(col.get_rgb()))) setattr(col, f2, 0.0) lg.add_color_stop_rgb(*([1.0] + list(col.get_rgb()))) cr.rectangle(b+x, b, step, eff_ht) cr.set_source(lg) cr.fill() slice_patt = cr.pop_group() # Tango-like outline cr.set_line_join(cairo.LINE_JOIN_ROUND) cr.rectangle(rect_x, rect_y, rect_w, rect_h) cr.set_line_width(self.OUTLINE_WIDTH) cr.set_source_rgba(*self.OUTLINE_RGBA) cr.stroke() # The main area cr.set_source(slice_patt) cr.paint() # Tango-like highlight over the top cr.rectangle(rect_x, rect_y, rect_w, rect_h) cr.set_line_width(self.EDGE_HIGHLIGHT_WIDTH) cr.set_source_rgba(*self.EDGE_HIGHLIGHT_RGBA) cr.stroke()
def interpolate(self, other, steps): """RGB interpolation. >>> white = RGBColor(r=1, g=1, b=1) >>> black = RGBColor(r=0, g=0, b=0) >>> [c.to_hex_str() for c in white.interpolate(black, 3)] ['#ffffff', '#7f7f7f', '#000000'] >>> [c.to_hex_str() for c in black.interpolate(white, 3)] ['#000000', '#7f7f7f', '#ffffff'] """ assert steps >= 3 other = RGBColor(color=other) for step in xrange(steps): p = step / (steps - 1) r = self.r + (other.r - self.r) * p g = self.g + (other.g - self.g) * p b = self.b + (other.b - self.b) * p yield RGBColor(r=r, g=g, b=b)
def processSound(self): while True: try: self.data_ready.wait() self.data_ready.clear() # horrible hack, the render_layer_as_pixbuf reads one pixel ahead, so we bring # the rendering window back before getting color data actual_row = self.active_row[:] actual_row[0] -= 1 pixbuf = self.app.doc.model._layers.render_layer_as_pixbuf( self.app.doc.model._layers, actual_row) n_channels = pixbuf.get_n_channels() assert n_channels in (3, 4) data = pixbuf.get_pixels() colors = [] rowstride = pixbuf.get_rowstride() for y in xrange(min(self.active_row[3], pixbuf.get_height())): if PY3: col = lib.color.RGBColor( data[y * rowstride + n_channels] / 255, data[y * rowstride + n_channels + 1] / 255, data[y * rowstride + n_channels + 2] / 255, ) else: col = lib.color.RGBColor( ord(data[y * rowstride + n_channels]) / 255, ord(data[y * rowstride + n_channels + 1]) / 255, ord(data[y * rowstride + n_channels + 2]) / 255, ) colors.append(col) # print("step {}, colors: {}".format(self.step, colors)) for c in self.consumers: c.data_ready(colors) except Exception as e: print("error getting color data: {}".format(e))
def render_background_cb(self, cr, wd, ht): b = self.BORDER_WIDTH bar_length = (self.vertical and ht or wd) - b - b b_x = b + 0.5 b_y = b + 0.5 b_w = wd - b - b - 1 b_h = ht - b - b - 1 # Build the gradient if self.vertical: bar_gradient = cairo.LinearGradient(0, b, 0, b + bar_length) else: bar_gradient = cairo.LinearGradient(b, 0, b + bar_length, 0) samples = self.samples + 2 for s in xrange(samples + 1): p = s / samples col = self.get_color_for_bar_amount(p) r, g, b = col.get_rgb() if self.vertical: p = 1 - p bar_gradient.add_color_stop_rgb(p, r, g, b) # Paint bar with Tango-like edges cr.set_line_join(cairo.LINE_JOIN_ROUND) cr.set_source_rgba(*self.OUTLINE_RGBA) cr.set_line_width(self.OUTLINE_WIDTH) cr.rectangle(b_x, b_y, b_w, b_h) cr.stroke() ## Paint bar cr.set_source(bar_gradient) cr.rectangle(b_x - 0.5, b_y - 0.5, b_w + 1, b_h + 1) cr.fill() ## Highlighted edge if b_w > 5 and b_h > 5: cr.set_line_width(self.EDGE_HIGHLIGHT_WIDTH) cr.set_source_rgba(*self.EDGE_HIGHLIGHT_RGBA) cr.rectangle(b_x, b_y, b_w, b_h) cr.stroke()
def interpolate(self, other, steps): """HCY interpolation. >>> red = HCYColor(0, 0.8, 0.5) >>> green = HCYColor(1./3, 0.8, 0.5) >>> [c.to_hex_str() for c in green.interpolate(red, 5)] ['#19c619', '#5ea319', '#8c8c19', '#c46f19', '#e55353'] >>> [c.to_hex_str() for c in red.interpolate(green, 5)] ['#e55353', '#c46f19', '#8c8c19', '#5ea319', '#19c619'] HCY is a cylindrical space, so interpolations between two endpoints of the same chroma will preserve that chroma. RGB interpoloation tends to diminish because the interpolation will pass near the diagonal of zero chroma. >>> [i.c for i in red.interpolate(green, 5)] [0.8, 0.8, 0.8, 0.8, 0.8] >>> red_rgb = RGBColor(color=red) >>> [round(HCYColor(color=i).c, 3) ... for i in red_rgb.interpolate(green, 5)] [0.8, 0.457, 0.571, 0.686, 0.8] """ assert steps >= 3 other = HCYColor(color=other) # Like HSV, interpolate using the shortest angular distance. ha = self.h % 1.0 hb = other.h % 1.0 hdelta = hb - ha for hdx0 in -(ha + 1 - hb), (hb + 1 - ha): if abs(hdx0) < abs(hdelta): hdelta = hdx0 for step in xrange(steps): p = step / (steps - 1) h = (self.h + hdelta * p) % 1.0 c = self.c + (other.c - self.c) * p y = self.y + (other.y - self.y) * p yield HCYColor(h=h, c=c, y=y)
def interpolate(self, other, steps): """HCY interpolation. >>> red = HCYColor(0, 0.8, 0.5) >>> green = HCYColor(1./3, 0.8, 0.5) >>> [c.to_hex_str() for c in green.interpolate(red, 5)] ['#19c619', '#5ea319', '#8c8c19', '#c46f19', '#e55353'] >>> [c.to_hex_str() for c in red.interpolate(green, 5)] ['#e55353', '#c46f19', '#8c8c19', '#5ea319', '#19c619'] HCY is a cylindrical space, so interpolations between two endpoints of the same chroma will preserve that chroma. RGB interpoloation tends to diminish because the interpolation will pass near the diagonal of zero chroma. >>> [i.c for i in red.interpolate(green, 5)] [0.8, 0.8, 0.8, 0.8, 0.8] >>> red_rgb = RGBColor(color=red) >>> [round(HCYColor(color=i).c, 3) ... for i in red_rgb.interpolate(green, 5)] [0.8, 0.457, 0.571, 0.686, 0.8] """ assert steps >= 3 other = HCYColor(color=other) # Like HSV, interpolate using the shortest angular distance. ha = self.h % 1.0 hb = other.h % 1.0 hdelta = hb - ha for hdx0 in -(ha+1-hb), (hb+1-ha): if abs(hdx0) < abs(hdelta): hdelta = hdx0 for step in xrange(steps): p = step / (steps - 1) h = (self.h + hdelta * p) % 1.0 c = self.c + (other.c - self.c) * p y = self.y + (other.y - self.y) * p yield HCYColor(h=h, c=c, y=y)
def interpolate(self, other, steps): """HSV interpolation, sometimes nicer looking than RGB. >>> red_hsv = HSVColor(h=0, s=1, v=1) >>> green_hsv = HSVColor(h=1./3, s=1, v=1) >>> [c.to_hex_str() for c in green_hsv.interpolate(red_hsv, 3)] ['#00ff00', '#ffff00', '#ff0000'] >>> [c.to_hex_str() for c in red_hsv.interpolate(green_hsv, 3)] ['#ff0000', '#ffff00', '#00ff00'] Note the pure yellow. Interpolations in RGB space are duller looking: >>> red_rgb = RGBColor(color=red_hsv) >>> [c.to_hex_str() for c in red_rgb.interpolate(green_hsv, 3)] ['#ff0000', '#7f7f00', '#00ff00'] """ assert steps >= 3 other = HSVColor(color=other) # Calculate the shortest angular distance # Normalize first ha = self.h % 1.0 hb = other.h % 1.0 # If the shortest distance doesn't pass through zero, then hdelta = hb - ha # But the shortest distance might pass through zero either antilockwise # or clockwise. Smallest magnitude wins. for hdx0 in -(ha+1-hb), (hb+1-ha): if abs(hdx0) < abs(hdelta): hdelta = hdx0 # Interpolate, using shortest angular dist for hue for step in xrange(steps): p = step / (steps - 1) h = (self.h + hdelta * p) % 1.0 s = self.s + (other.s - self.s) * p v = self.v + (other.v - self.v) * p yield HSVColor(h=h, s=s, v=v)
def interpolate(self, other, steps): """HSV interpolation, sometimes nicer looking than RGB. >>> red_hsv = HSVColor(h=0, s=1, v=1) >>> green_hsv = HSVColor(h=1./3, s=1, v=1) >>> [c.to_hex_str() for c in green_hsv.interpolate(red_hsv, 3)] ['#00ff00', '#ffff00', '#ff0000'] >>> [c.to_hex_str() for c in red_hsv.interpolate(green_hsv, 3)] ['#ff0000', '#ffff00', '#00ff00'] Note the pure yellow. Interpolations in RGB space are duller looking: >>> red_rgb = RGBColor(color=red_hsv) >>> [c.to_hex_str() for c in red_rgb.interpolate(green_hsv, 3)] ['#ff0000', '#7f7f00', '#00ff00'] """ assert steps >= 3 other = HSVColor(color=other) # Calculate the shortest angular distance # Normalize first ha = self.h % 1.0 hb = other.h % 1.0 # If the shortest distance doesn't pass through zero, then hdelta = hb - ha # But the shortest distance might pass through zero either antilockwise # or clockwise. Smallest magnitude wins. for hdx0 in -(ha + 1 - hb), (hb + 1 - ha): if abs(hdx0) < abs(hdelta): hdelta = hdx0 # Interpolate, using shortest angular dist for hue for step in xrange(steps): p = step / (steps - 1) h = (self.h + hdelta * p) % 1.0 s = self.s + (other.s - self.s) * p v = self.v + (other.v - self.v) * p yield HSVColor(h=h, s=s, v=v)
def remove_tool_widget(self, widget): """Removes a tool widget from the stack, hiding it :param widget: the GType name of the tab to be removed. :type widget: Gtk.Widget created by the Workspace's factory :rtype: bool :returns: whether the widget was removed """ target_notebook = None target_index = None target_page = None for notebook in self._get_notebooks(): for index in xrange(notebook.get_n_pages()): page = notebook.get_nth_page(index) if widget is page.get_child(): target_index = index target_notebook = notebook target_page = page break if target_notebook: break if target_notebook: assert target_page is not None assert target_index is not None logger.debug("Removing tool widget i=%d, p=%r, n=%r", target_index, target_page, target_notebook) target_page.hide() widget.hide() target_page.remove(widget) target_notebook.remove_page(target_index) target_page.destroy() if self.workspace: self.workspace.tool_widget_removed(widget) return True return False
if __name__ == '__main__': from random import randint win = Gtk.Window() win.set_title("cursor test") min_size = 5 max_size = 64 nsteps = 8 w = nsteps * max_size h = 4 * max_size surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) cr = cairo.Context(surf) cr.set_source_rgb(.7, .7, .7) cr.paint() for style in xrange(4): col = 0 for size in xrange(min_size, max_size + 1, (max_size - min_size) // nsteps): cr.save() y = (style * max_size) + ((max_size - size)/2) x = (col * max_size) + ((max_size - size)/2) cr.translate(x, y) draw_brush_cursor(cr, size, style) cr.restore() col += 1 pixbuf = _image_surface_to_pixbuf(surf) image = Gtk.Image() image.set_from_pixbuf(pixbuf) image.set_size_request(w, h)
def render_background_cb(self, cr, wd, ht, icon_border=None): """Renders the offscreen bg, for `ColorAdjusterWidget` impls. """ cr.save() ref_grey = self.color_at_normalized_polar_pos(0, 0) border = icon_border if border is None: border = self.BORDER_WIDTH radius = self.get_radius(wd, ht, border) steps = self.HUE_SLICES sat_slices = self.SAT_SLICES sat_gamma = self.SAT_GAMMA # Move to the centre cx, cy = self.get_center(wd, ht) cr.translate(cx, cy) # Clip, for a slight speedup cr.arc(0, 0, radius + border, 0, 2 * math.pi) cr.clip() # Tangoesque outer border cr.set_line_width(self.OUTLINE_WIDTH) cr.arc(0, 0, radius, 0, 2 * math.pi) cr.set_source_rgba(*self.OUTLINE_RGBA) cr.stroke() # Each slice in turn cr.save() cr.set_line_width(1.0) cr.set_line_join(cairo.LINE_JOIN_ROUND) step_angle = 2.0 * math.pi / steps mgr = self.get_color_manager() for ih in xrange(steps + 1): # overshoot by 1, no solid bit for final h = ih / steps if mgr: h = mgr.undistort_hue(h) edge_col = self.color_at_normalized_polar_pos(1.0, h) rgb = edge_col.get_rgb() if ih > 0: # Backwards gradient cr.arc_negative(0, 0, radius, 0, -step_angle) x, y = cr.get_current_point() cr.line_to(0, 0) cr.close_path() lg = cairo.LinearGradient(radius, 0, (x + radius) / 2, y) lg.add_color_stop_rgba(0, rgb[0], rgb[1], rgb[2], 1.0) lg.add_color_stop_rgba(1, rgb[0], rgb[1], rgb[2], 0.0) cr.set_source(lg) cr.fill() if ih < steps: # Forward solid cr.arc(0, 0, radius, 0, step_angle) x, y = cr.get_current_point() cr.line_to(0, 0) cr.close_path() cr.set_source_rgb(*rgb) cr.stroke_preserve() cr.fill() cr.rotate(step_angle) cr.restore() # Cheeky approximation of the right desaturation gradients rg = cairo.RadialGradient(0, 0, 0, 0, 0, radius) add_distance_fade_stops(rg, ref_grey.get_rgb(), nstops=sat_slices, gamma=1.0 / sat_gamma) cr.set_source(rg) cr.arc(0, 0, radius, 0, 2 * math.pi) cr.fill() # Tangoesque inner border cr.set_source_rgba(*self.EDGE_HIGHLIGHT_RGBA) cr.set_line_width(self.EDGE_HIGHLIGHT_WIDTH) cr.arc(0, 0, radius, 0, 2 * math.pi) cr.stroke() # Some small notches on the disc edge for pure colors if wd > 75 or ht > 75: cr.save() cr.arc(0, 0, radius + self.EDGE_HIGHLIGHT_WIDTH, 0, 2 * math.pi) cr.clip() pure_cols = [ RGBColor(1, 0, 0), RGBColor(1, 1, 0), RGBColor(0, 1, 0), RGBColor(0, 1, 1), RGBColor(0, 0, 1), RGBColor(1, 0, 1), ] for col in pure_cols: x, y = self.get_pos_for_color(col) x = int(x) - cx y = int(y) - cy cr.set_source_rgba(*self.EDGE_HIGHLIGHT_RGBA) cr.arc( x + 0.5, y + 0.5, 1.0 + self.EDGE_HIGHLIGHT_WIDTH, 0, 2 * math.pi, ) cr.fill() cr.set_source_rgba(*self.OUTLINE_RGBA) cr.arc( x + 0.5, y + 0.5, self.EDGE_HIGHLIGHT_WIDTH, 0, 2 * math.pi, ) cr.fill() cr.restore() cr.restore()
def scanline_strips_iter(surface, rect, alpha=False, single_tile_pattern=False, **kwargs): """Generate (render) scanline strips from a tile-blittable object :param lib.surface.TileBlittable surface: Surface to iterate over :param bool alpha: If true, write a PNG with alpha :param bool single_tile_pattern: True if surface is a one tile only. :param tuple \*\*kwargs: Passed to blit_tile_into. The `alpha` parameter is passed to the surface's `blit_tile_into()`. Rendering is skipped for all but the first line of single-tile patterns. The scanline strips yielded by this generator are suitable for feeding to a mypaintlib.ProgressivePNGWriter. """ # Sizes x, y, w, h = rect assert w > 0 assert h > 0 # calculate bounding box in full tiles render_tx = x // N render_ty = y // N render_tw = (x + w - 1) // N - render_tx + 1 render_th = (y + h - 1) // N - render_ty + 1 # buffer for rendering one tile row at a time arr = np.empty((N, render_tw * N, 4), 'uint8') # rgba or rgbu # view into arr without the horizontal padding arr_xcrop = arr[:, x-render_tx*N:x-render_tx*N+w, :] first_row = render_ty last_row = render_ty+render_th-1 for ty in range(render_ty, render_ty+render_th): skip_rendering = False if single_tile_pattern: # optimization for simple background patterns # e.g. solid color if ty != first_row: skip_rendering = True for tx_rel in xrange(render_tw): # render one tile dst = arr[:, tx_rel*N:(tx_rel+1)*N, :] if not skip_rendering: tx = render_tx + tx_rel try: surface.blit_tile_into(dst, alpha, tx, ty, **kwargs) except Exception: logger.exception("Failed to blit tile %r of %r", (tx, ty), surface) mypaintlib.tile_clear_rgba8(dst) # yield a numpy array of the scanline without padding res = arr_xcrop if ty == last_row: res = res[:y+h-ty*N, :, :] if ty == first_row: res = res[y-render_ty*N:, :, :] yield res
def render_background_cb(self, cr, wd, ht, icon_border=None): """Renders the offscreen bg, for `ColorAdjusterWidget` impls. """ cr.save() border = icon_border if border is None: border = self.BORDER_WIDTH radius = self.get_radius(wd, ht, border) steps = self.HUE_SLICES # Move to the centre cx, cy = self.get_center(wd, ht) cr.translate(cx, cy) # Clip, for a slight speedup cr.arc(0, 0, radius+border, 0, 2*math.pi) cr.clip() # Tangoesque outer border cr.set_line_width(self.OUTLINE_WIDTH) cr.arc(0, 0, radius, 0, 2*math.pi) cr.set_source_rgba(*self.OUTLINE_RGBA) cr.stroke() # Each slice in turn cr.save() cr.set_line_width(1.0) cr.set_line_join(cairo.LINE_JOIN_ROUND) step_angle = 2.0*math.pi/steps mgr = self.get_color_manager() for ih in xrange(steps+1): # overshoot by 1, no solid bit for final h = ih / steps if mgr: h = mgr.undistort_hue(h) edge_col = self.color_at_normalized_polar_pos(1.0, h) edge_col.s = 1.0 edge_col.v = 1.0 rgb = edge_col.get_rgb() if ih > 0: # Backwards gradient cr.arc_negative(0, 0, radius, 0, -step_angle) x, y = cr.get_current_point() cr.line_to(0, 0) cr.close_path() lg = cairo.LinearGradient(radius, 0, (x + radius) / 2, y) lg.add_color_stop_rgba(0, rgb[0], rgb[1], rgb[2], 1.0) lg.add_color_stop_rgba(1, rgb[0], rgb[1], rgb[2], 0.0) cr.set_source(lg) cr.fill() if ih < steps: # Forward solid cr.arc(0, 0, radius, 0, step_angle) x, y = cr.get_current_point() cr.line_to(0, 0) cr.close_path() cr.set_source_rgb(*rgb) cr.stroke_preserve() cr.fill() cr.rotate(step_angle) cr.restore() # Tangoesque inner border cr.set_source_rgba(*self.EDGE_HIGHLIGHT_RGBA) cr.set_line_width(self.EDGE_HIGHLIGHT_WIDTH) cr.arc(0, 0, radius, 0, 2*math.pi) cr.stroke() cr.set_line_width(self.OUTLINE_WIDTH) cr.arc(0, 0, radius*0.8, 0, 2*math.pi) cr.set_source_rgba(0, 0, 0, 1) cr.set_operator(cairo.OPERATOR_DEST_OUT) cr.fill() cr.set_operator(cairo.OPERATOR_OVER) cr.set_source_rgba(*self.OUTLINE_RGBA) cr.stroke() cr.set_source_rgba(*self.EDGE_HIGHLIGHT_RGBA) cr.set_line_width(self.EDGE_HIGHLIGHT_WIDTH) cr.arc(0, 0, radius*0.8, 0, 2*math.pi) cr.stroke()
def __update_active_objects(self, x, y): # Decides what a click or a drag at (x, y) would do, and updates the # mouse cursor and draw state to match. assert self.__drag_func is None self.__active_shape = None self.__active_ctrlpoint = None self.__tmp_new_ctrlpoint = None self.queue_draw() # yes, always # Possible mask void manipulations mask = self.get_mask() for mask_idx in xrange(len(mask)): colors = mask[mask_idx] if len(colors) < 3: continue # If the pointer is near an existing control point, clicking and # dragging will move it. void = [] for col_idx in xrange(len(colors)): col = colors[col_idx] px, py = self.get_pos_for_color(col) dp = math.sqrt((x-px)**2 + (y-py)**2) if dp <= self.__ctrlpoint_grab_radius: mask.remove(colors) mask.insert(0, colors) self.__active_shape = colors self.__active_ctrlpoint = col_idx self.__set_cursor(None) return void.append((px, py)) # If within a certain distance of an edge, dragging will create and # then move a new control point. void = geom.convex_hull(void) for p1, p2 in geom.pairwise(void): isect = geom.nearest_point_in_segment(p1, p2, (x, y)) if isect is not None: ix, iy = isect di = math.sqrt((ix-x)**2 + (iy-y)**2) if di <= self.__ctrlpoint_grab_radius: newcol = self.get_color_at_position(ix, iy) self.__tmp_new_ctrlpoint = newcol mask.remove(colors) mask.insert(0, colors) self.__active_shape = colors self.__set_cursor(None) return # If the mouse is within a mask void, then dragging would move that # shape around within the mask. if geom.point_in_convex_poly((x, y), void): mask.remove(colors) mask.insert(0, colors) self.__active_shape = colors self.__set_cursor(None) return # Away from shapes, clicks and drags manipulate the entire mask: adding # cutout voids to it, or rotating the whole mask around its central # axis. alloc = self.get_allocation() cx, cy = self.get_center(alloc=alloc) radius = self.get_radius(alloc=alloc) dx, dy = x-cx, y-cy r = math.sqrt(dx**2 + dy**2) if r < radius*(1.0-self.min_shape_size): if len(mask) < self.__max_num_shapes: d = self.__dist_to_nearest_shape(x, y) minsize = radius * self.min_shape_size if d is None or d > minsize: # Clicking will result in a new void self.__set_cursor(self.__add_cursor) else: # Click-drag to rotate the entire mask self.__set_cursor(self.__rotate_cursor)
def _scroll(tdw, model, width=1920, height=1080, zoom=1.0, mirrored=False, rotation=0.0, turns=8, turn_steps=8, turn_radius=0.3, save_pngs=False, set_modes=None, use_background=True): """Test scroll performance Scroll around in a circle centred on the virtual display, testing the same sort of render that's used for display - albeit to an in-memory surface. This tests rendering and cache performance quite well, though it discounts Cairo acceleration. """ num_undos_needed = 0 if set_modes: for path, mode in set_modes.items(): model.select_layer(path=path) num_undos_needed += 1 model.set_current_layer_mode(mode) num_undos_needed += 1 assert model.layer_stack.deepget(path, None).mode == mode model.layer_stack.background_visible = use_background model.layer_stack._render_cache.clear() radius = min(width, height) * turn_radius fakealloc = namedtuple("FakeAlloc", ["x", "y", "width", "height"]) alloc = fakealloc(0, 0, width, height) tdw.set_allocation(alloc) surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) tdw.set_rotation(rotation) tdw.set_zoom(zoom) tdw.set_mirrored(mirrored) tdw.recenter_document() start = time.clock() cx, cy = tdw.get_center() last_x = cx last_y = cy nframes = 0 for turn_i in xrange(turns): for step_i in xrange(turn_steps): t = 2 * math.pi * (step_i / turn_steps) x = cx + math.cos(t) * radius y = cy + math.sin(t) * radius dx = x - last_x dy = y - last_y cr = cairo.Context(surf) cr.rectangle(*alloc) cr.clip() tdw.scroll(dx, dy) tdw.renderer._draw_cb(tdw, cr) surf.flush() last_x = x last_y = y if save_pngs: filename = "/tmp/scroll-%03d-%03d.png" % (turn_i, step_i) surf.write_to_png(filename) nframes += 1 dt = time.clock() - start for i in range(num_undos_needed): model.undo() if set_modes: for path in set_modes.keys(): mode = model.layer_stack.deepget(path, None).mode assert mode == mypaintlib.CombineNormal return (nframes, dt)
def match_color(self, col, exact=False, order=None): """Moves the match position to the color closest to the argument. :param col: The color to match. :type col: lib.color.UIColor :param exact: Only consider exact matches, and not near-exact or approximate matches. :type exact: bool :param order: a search order to use. Default is outwards from the match position, or in order if the match is unset. :type order: sequence or iterator of integer color indices. :returns: Whether the match succeeded. :rtype: bool By default, the matching algorithm favours exact or near-exact matches which are close to the current position. If the current position is unset, this search starts at 0. If there are no exact or near-exact matches, a looser approximate match will be used, again favouring matches with nearby positions. >>> red2blue = RGBColor(1, 0, 0).interpolate(RGBColor(0, 1, 1), 5) >>> p = Palette(colors=red2blue) >>> p.match_color(RGBColor(0.45, 0.45, 0.45)) True >>> p.match_position 2 >>> p.match_is_approx True >>> p[p.match_position] <RGBColor r=0.5000, g=0.5000, b=0.5000> >>> p.match_color(RGBColor(0.5, 0.5, 0.5)) True >>> p.match_is_approx False >>> p.match_color(RGBColor(0.45, 0.45, 0.45), exact=True) False >>> p.match_color(RGBColor(0.5, 0.5, 0.5), exact=True) True Fires the ``match_changed()`` event when changes happen. """ if order is not None: search_order = order elif self.match_position is not None: search_order = _outwards_from(len(self), self.match_position) else: search_order = xrange(len(self)) bestmatch_i = None bestmatch_d = None is_approx = True for i in search_order: c = self._colors[i] if c is self._EMPTY_SLOT_ITEM: continue # Closest exact or near-exact match by index distance (according to # the search_order). Considering near-exact matches as equivalent # to exact matches improves the feel of PaletteNext and # PalettePrev. if exact: if c == col: bestmatch_i = i is_approx = False break else: d = _color_distance(col, c) if c == col or d < 0.06: bestmatch_i = i is_approx = False break if bestmatch_d is None or d < bestmatch_d: bestmatch_i = i bestmatch_d = d # Measuring over a blend into solid equiluminant 0-chroma # grey for the orange #DA5D2E with an opaque but feathered # brush made huge, and picking just inside the point where the # palette widget begins to call it approximate: # # 0.05 is a difference only discernible (to me) by tilting LCD # 0.066 to 0.075 appears slightly greyer for large areas # 0.1 and above is very clearly distinct # If there are no exact or near-exact matches, choose the most similar # color anywhere in the palette. if bestmatch_i is not None: self._match_position = bestmatch_i self._match_is_approx = is_approx self.match_changed() return True return False
def _queue_redraw_all_nodes(self): """Redraws all nodes on all known view TDWs""" for i in xrange(len(self.nodes)): self._queue_draw_node(i)
def update_button_positions(self): """Recalculates the positions of the mode's buttons.""" nodes = self._inkmode.nodes num_nodes = len(nodes) if num_nodes == 0: self.reject_button_pos = None self.accept_button_pos = None return button_radius = gui.style.FLOATING_BUTTON_RADIUS margin = 1.5 * button_radius alloc = self._tdw.get_allocation() view_x0, view_y0 = alloc.x, alloc.y view_x1, view_y1 = (view_x0 + alloc.width), (view_y0 + alloc.height) # Force-directed layout: "wandering nodes" for the buttons' # eventual positions, moving around a constellation of "fixed" # points corresponding to the nodes the user manipulates. fixed = [] for i, node in enumerate(nodes): x, y = self._tdw.model_to_display(node.x, node.y) fixed.append(_LayoutNode(x, y)) # The reject and accept buttons are connected to different nodes # in the stroke by virtual springs. stroke_end_i = len(fixed) - 1 stroke_start_i = 0 stroke_last_quarter_i = int(stroke_end_i * 3.0 // 4.0) assert stroke_last_quarter_i < stroke_end_i reject_anchor_i = stroke_start_i accept_anchor_i = stroke_end_i # Classify the stroke direction as a unit vector stroke_tail = ( fixed[stroke_end_i].x - fixed[stroke_last_quarter_i].x, fixed[stroke_end_i].y - fixed[stroke_last_quarter_i].y, ) stroke_tail_len = math.hypot(*stroke_tail) if stroke_tail_len <= 0: stroke_tail = (0., 1.) else: stroke_tail = tuple(c / stroke_tail_len for c in stroke_tail) # Initial positions. accept_button = _LayoutNode( fixed[accept_anchor_i].x + stroke_tail[0] * margin, fixed[accept_anchor_i].y + stroke_tail[1] * margin, ) reject_button = _LayoutNode( fixed[reject_anchor_i].x - stroke_tail[0] * margin, fixed[reject_anchor_i].y - stroke_tail[1] * margin, ) # Constraint boxes. They mustn't share corners. # Natural hand strokes are often downwards, # so let the reject button to go above the accept button. reject_button_bbox = ( view_x0 + margin, view_x1 - margin, view_y0 + margin, view_y1 - (2.666 * margin), ) accept_button_bbox = ( view_x0 + margin, view_x1 - margin, view_y0 + (2.666 * margin), view_y1 - margin, ) # Force-update constants k_repel = -25.0 k_attract = 0.05 # Let the buttons bounce around until they've settled. for iter_i in xrange(100): accept_button \ .add_forces_inverse_square(fixed, k=k_repel) \ .add_forces_inverse_square([reject_button], k=k_repel) \ .add_forces_linear([fixed[accept_anchor_i]], k=k_attract) reject_button \ .add_forces_inverse_square(fixed, k=k_repel) \ .add_forces_inverse_square([accept_button], k=k_repel) \ .add_forces_linear([fixed[reject_anchor_i]], k=k_attract) reject_button \ .update_position() \ .constrain_position(*reject_button_bbox) accept_button \ .update_position() \ .constrain_position(*accept_button_bbox) settled = [(p.speed < 0.5) for p in [accept_button, reject_button]] if all(settled): break self.accept_button_pos = accept_button.x, accept_button.y self.reject_button_pos = reject_button.x, reject_button.y