Ejemplo n.º 1
0
 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)
Ejemplo n.º 2
0
 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)
Ejemplo n.º 3
0
 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)
Ejemplo n.º 4
0
 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)
Ejemplo n.º 5
0
 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)
Ejemplo n.º 6
0
    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, ),
            )
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
    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
Ejemplo n.º 10
0
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()
Ejemplo n.º 11
0
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()
Ejemplo n.º 12
0
    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)
Ejemplo n.º 13
0
    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)
Ejemplo n.º 14
0
 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
Ejemplo n.º 15
0
 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
Ejemplo n.º 16
0
    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()
Ejemplo n.º 17
0
    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)
Ejemplo n.º 18
0
 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)
Ejemplo n.º 19
0
    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)
Ejemplo n.º 20
0
    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()
Ejemplo n.º 21
0
    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)
Ejemplo n.º 22
0
 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)
Ejemplo n.º 23
0
    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)
Ejemplo n.º 24
0
    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)
Ejemplo n.º 25
0
    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)
Ejemplo n.º 26
0
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)
Ejemplo n.º 27
0
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)
Ejemplo n.º 28
0
 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()
Ejemplo n.º 30
0
    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
Ejemplo n.º 31
0
    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)
Ejemplo n.º 32
0
    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
Ejemplo n.º 33
0
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
Ejemplo n.º 34
0
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
Ejemplo n.º 35
0
    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()
Ejemplo n.º 36
0
    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)
Ejemplo n.º 37
0
    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)
Ejemplo n.º 38
0
    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))
Ejemplo n.º 39
0
    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()
Ejemplo n.º 40
0
    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()
Ejemplo n.º 41
0
    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)
Ejemplo n.º 42
0
    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)
Ejemplo n.º 43
0
    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)
Ejemplo n.º 44
0
    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)
Ejemplo n.º 45
0
    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
Ejemplo n.º 46
0
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)
Ejemplo n.º 47
0
    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()
Ejemplo n.º 48
0
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
Ejemplo n.º 49
0
    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()
Ejemplo n.º 50
0
    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)
Ejemplo n.º 51
0
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)
Ejemplo n.º 52
0
    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
Ejemplo n.º 53
0
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)
Ejemplo n.º 54
0
    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
Ejemplo n.º 55
0
 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)
Ejemplo n.º 56
0
    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