Esempio n. 1
0
class Paint(StencilView):
    scale = NumericProperty()

    def __init__(self, app, **kwargs):
        StencilView.__init__(self, **kwargs)
        self.app = app
        self.xx = 0
        self.scale = 1.0
        self.size_hint = (None, None)
        self.cursor_pos = (0, 0)
        self.fbo_size = DEFAULT_IMAGE_SIZE
        self.fbo = None
        self.fbo_rect = None
        self.tool_fbo = None
        self.tool_fbo_rect = None
        self.bg_rect = None
        self.rect = None
        self.fbo_clear_color = (1, 1, 1, 1)
        self.layer_undo_stack = []
        self.undo_layer_index = 0
        self.layer_rect = []
        self.fbo_create_on_canvas()
        self.touches = []
        self.move_image = False
        self.active_layer = None
        self.tool_buffer_enabled = True
        self.px = None
        self.py = None
        self.grid_texture = None
        self.active_layer_last_texture = None

    def grid_create(self, size):
        self.grid_texture = Texture.create(size=size, colorfmt='rgba', bufferfmt='ubyte')
        tex = improc.texture_draw_grid(self.grid_texture, 255, 4)
        return tex

    def fbo_create(self, size, tex=None):
        if tex is None:
            tex = Texture.create(size=size, colorfmt='rgba', bufferfmt='ubyte')
            tex.mag_filter = 'nearest'
            tex.min_filter = 'nearest'
        if self.fbo is None:
            self.fbo = Fbo(size=size, texture=tex)
            self.fbo.texture.mag_filter = 'nearest'
            self.fbo.texture.min_filter = 'nearest'
        else:
            self.fbo = Fbo(size=size, texture=tex)
            self.fbo.texture.mag_filter = 'nearest'
            self.fbo.texture.min_filter = 'nearest'
        tool_tex = Texture.create(size=size, colorfmt='rgba', bufferfmt='ubyte')
        tool_tex.mag_filter = 'nearest'
        tool_tex.min_filter = 'nearest'
        if self.tool_fbo is None:
            self.tool_fbo = Fbo(size=size, texture=tool_tex)
            self.tool_fbo.texture.mag_filter = 'nearest'
            self.tool_fbo.texture.min_filter = 'nearest'
        else:
            self.tool_fbo = Fbo(size=size, texture=tool_tex)
            self.tool_fbo.texture.mag_filter = 'nearest'
            self.tool_fbo.texture.min_filter = 'nearest'
        return self.fbo

    def refbo(self):
        tex = Texture.create(size=self.fbo.texture.size, colorfmt='rgba', bufferfmt='ubyte')
        self.fbo = Fbo(size=tex.size, texture=tex)
        self.fbo.texture.mag_filter = 'nearest'
        self.fbo.texture.min_filter = 'nearest'
        self.fbo.draw()
        self.canvas.ask_update()

    def canvas_put_drawarea(self, texture=None):
        self.canvas.clear()
        if texture:
            self.fbo_size = texture.size
            self.fbo_create(texture.size, tex=texture)
        self.bg_rect = layer.Layer.layer_bg_rect
        with self.canvas:
            self.canvas.add(self.bg_rect)
            Color(1, 1, 1, 1)
            for laybox in layer.LayerBox.boxlist:
                if laybox.layer:
                    if laybox.layer.texture and laybox.layer.visible:
                        self.canvas.add(laybox.layer.rect)
                        laybox.layer.rect.pos = self.fbo_rect.pos

                if self.tool_buffer_enabled:
                    self.tool_fbo_rect = Rectangle(pos=self.fbo_rect.pos, size=self.tool_fbo.texture.size,
                                                   texture=self.tool_fbo.texture)
        self.fbo_update_pos()
        self.fbo.draw()
        self.canvas.ask_update()

    def fbo_create_on_canvas(self, size=None, pos=(0, 0)):
        self.canvas.clear()
        if size is None:
            size = self.fbo_size
        else:
            self.fbo_size = size
        self.fbo_create(size)
        with self.canvas:
            Color(1, 1, 1, 1)
            self.fbo_rect = Rectangle(texture=self.fbo.texture, pos=pos, size=self.fbo.texture.size)
            self.tool_fbo_rect = Rectangle(texture=self.tool_fbo.texture, pos=pos, size=self.tool_fbo.texture.size)
        self.fbo.draw()
        self.canvas.ask_update()


    def fbo_clear(self):
        self.fbo.bind()
        self.fbo.clear()
        self.fbo.clear_color = self.fbo_clear_color
        self.fbo.clear_buffer()
        self.fbo.release()
        self.fbo.draw()

    def fbo_update_pos(self):
        x = self.app.aPaintLayout.size[0] / self.scale / 2 - self.fbo.size[0] / 2
        y = self.app.aPaintLayout.size[1] / self.scale / 2 - self.fbo.size[1] / 2
        self.fbo_rect.pos = (x, y)
        self.tool_fbo_rect.pos = (x, y)
        if self.bg_rect:
            self.bg_rect.pos = (x, y)
        if self.app.layer_ribbon:
            for lb in layer.LayerBox.boxlist:
                if lb.layer.rect:
                    lb.layer.rect.pos = self.fbo_rect.pos

    def fbo_move_by_offset(self, offset):
        if self.bg_rect:
            self.bg_rect.pos = self.bg_rect.pos[0] + offset[0], self.bg_rect.pos[1] + offset[1]
            self.fbo_rect.pos = self.bg_rect.pos
            self.tool_fbo_rect.pos = self.bg_rect.pos
        if self.app.layer_ribbon:
            for lb in layer.LayerBox.boxlist:
                if lb.layer.rect:
                    lb.layer.rect.pos = self.fbo_rect.pos
        # self.ox, self.oy = self.bg_rect.pos
    #
    def fbo_render(self, touch, width):
        self.fbo.bind()
        if touch and touch.ud.has_key('line'):
            with self.fbo:
                self.fbo.add(self.app.aColor)
                d = width
                self.cursor_pos = ((touch.x - d / 2) / self.scale - self.fbo_rect.pos[0],
                                   (touch.y - d / 2) / self.scale - self.fbo_rect.pos[1])
                touch.ud['line'].points += self.cursor_pos
        self.fbo.release()
        self.fbo.draw()
        self.canvas.ask_update()

    def fbo_get_texture(self):
        return self.fbo.texture

    def fbo_get_pixel_color(self, touch, format='1'):
        self.fbo.bind()
        x = touch.x / self.scale - self.fbo_rect.pos[0]
        y = touch.y / self.scale - self.fbo_rect.pos[1]
        data = glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE)
        self.fbo.release()
        if format == '1':
            c = [ord(a) / 255.0 for a in data]
        else:
            c = [ord(a) for a in data]
        return c

    def fbo_fill_region(self, touch):
        x = int(touch.x / self.scale - self.fbo_rect.pos[0])
        y = int(touch.y / self.scale - self.fbo_rect.pos[1])
        improc.fillimage(self.fbo, (x, y), [int(255 * c) for c in self.app.aColor.rgba])
        self.canvas.ask_update()

    def fbo_replace_color(self, touch, width):
        cx = int(touch.x / self.scale - self.fbo_rect.pos[0])
        cy = int(touch.y / self.scale - self.fbo_rect.pos[1])
        if self.px is None:
            self.px = cx
        if self.py is None:
            self.py = cy
        if (cx - self.px) > 0:
            tg_alpha = float((cy - self.py)) / (cx - self.px)
            step = int(math.copysign(1, tg_alpha))
            if abs(cx - self.px) > abs(cy - self.py):
                coords = [(x, self.py + (x - self.px) * tg_alpha) for x in xrange(int(self.px), int(cx))]
            else:
                coords = [(self.px + (y - self.py) / tg_alpha, y) for y in xrange(int(self.py), int(cy), step)]
        elif (cx - self.px) < 0:
            tg_alpha = float((cy - self.py)) / (cx - self.px)
            step = int(math.copysign(1, tg_alpha))
            if abs(cx - self.px) > abs(cy - self.py):
                coords = [(x, self.py + (x - self.px) * tg_alpha) for x in xrange(int(cx), int(self.px))]
            else:
                coords = [(self.px + (y - self.py) / tg_alpha, y) for y in xrange(int(cy), int(self.py), step)]
        elif (cx - self.px) == 0:
            if (cy - self.py) > 0:
                coords = [(cx, y) for y in xrange(int(self.py), int(cy))]
            elif (cy - self.py) < 0:
                coords = [(cx, y) for y in xrange(int(cy), int(self.py))]
            else:
                coords = [(cx, cy), ]
        self.fbo.bind()
        for (x, y) in coords:
            improc.texture_replace_color(tex=self.fbo.texture, pos=(x, y), color=0, size=(width, width))
        self.fbo.release()
        self.fbo.clear()
        self.fbo.draw()
        self.canvas.ask_update()
        self.px = cx
        self.py = cy

    def pos_to_widget(self, touch):
        return self.to_widget(touch.x, touch.y)

    def scale_canvas(self, zoom):
        with self.canvas.before:
            PushMatrix()
            self.scale *= zoom
            context_instructions.Scale(zoom)
        with self.canvas.after:
            PopMatrix()
        self.fbo_update_pos()

    def on_scale(self, instance, scale):
        ToolBehavior.scale()

    def scale_pos(self, pos):
        return [pos[0] / self.scale - self.fbo_rect.pos[0],
                pos[1] / self.scale - self.fbo_rect.pos[1]]

    def scaled_pos(self):
        return [self.fbo_rect.pos[0] * self.scale,
                self.fbo_rect.pos[1] * self.scale]

    def scaled_size(self):
        return [self.fbo_rect.texture.size[0] * self.scale,
                self.fbo_rect.texture.size[1] * self.scale]

    def add_touch(self, touch):
        if touch and len(self.touches) < 2:
            self.touches.append(touch)
        if len(self.touches) >= 2:
            self.touch_dist = self.touches[0].distance(self.touches[1])
            self.touch_dist_const = self.touch_dist
            self.first_touch = self.touches[0]

    def double_touch(self):
        if len(self.touches) > 1:
            return True

    # def double_touch_distance(self):
    #     return Vector(self.touches[0].pos).distance(self.touches[1].pos)

    def touch_responce(self):
        result = False
        if len(self.touches) >= 2:
            pos1 = self.pos_to_widget(self.touches[0])
            pos2 = self.touches[1].pos
            dist = distance(pos1, pos2)
            if dist > self.touch_dist + 10:
                self.touch_dist = dist
                result = 2
            elif dist <= self.touch_dist - 10:
                self.touch_dist = dist
                result = 1
            if abs(self.touch_dist_const - dist) < 10:
                result = 3
            return result
        return False

    def on_touch_down(self, touch):
        self.tool_buffer.on_touch_down(touch)

        if self.collide_point(*touch.pos):
            if self.app.config.getint('toolbars', 'toolbar_autohide'):
                self.app.toolbar.animate_hide()
            if self.app.config.getint('toolbars', 'palette_autohide'):
                self.app.palette.animate_hide()
            if self.app.config.getint('toolbars', 'layer_ribbon_autohide'):
                self.app.layer_ribbon.animate_hide()

        if not self.collide_point(*touch.pos):
            if self.app.active_tool == TOOL_LINE:
                self.tool_line._render_and_reset_state(self.fbo, self.tool_fbo)
                self.fbo.draw()
                self.canvas.ask_update()

            return False

        touch.grab(self)

        self.add_touch(touch)

        if len(self.touches) == 1:
            self.active_layer_backup_texture()

        if self.double_touch():
            self.active_layer_set_last_backup_texture()
            return

        if not self.app.layer_ribbon.get_active_layer().visible:
            dialog.PopupMessage("Notification", "The current layer is hidden, make it visible for edit")
            return
        if self.app.layer_ribbon.get_active_layer().texture_locked:
            dialog.PopupMessage("Notification", "The current layer is locked, make it unlock for edit")
            return

        if self.app.active_tool == TOOL_PENCIL1:
            self.fbo.bind()
            if touch:
                with self.fbo:
                    self.fbo.add(self.app.aColor)
                    d = 1.
                    self.cursor_pos = ((touch.x - d / 2) / self.scale - self.fbo_rect.pos[0],
                                       (touch.y - d / 2) / self.scale - self.fbo_rect.pos[1])
                    Ellipse(pos=self.cursor_pos, size=(d, d))
                    touch.ud['line'] = Line(points=self.cursor_pos, width=d)
            self.fbo.release()
            self.fbo.draw()
            self.canvas.ask_update()
        elif self.app.active_tool == TOOL_PENCIL2:
            self.fbo.bind()
            if touch:
                with self.fbo:
                    self.fbo.add(self.app.aColor)
                    d = 2.1
                    self.cursor_pos = ((touch.x - d / 2) / self.scale - self.fbo_rect.pos[0],
                                       (touch.y - d / 2) / self.scale - self.fbo_rect.pos[1])
                    Ellipse(pos=self.cursor_pos, size=(d, d))
                    touch.ud['line'] = Line(points=self.cursor_pos, width=d / 2.0)
            self.fbo.release()
            self.fbo.draw()
            self.canvas.ask_update()
        elif self.app.active_tool == TOOL_PENCIL3:
            self.fbo.bind()
            if touch:
                with self.fbo:
                    self.fbo.add(self.app.aColor)
                    d = 3.
                    self.cursor_pos = ((touch.x - d / 2) / self.scale - self.fbo_rect.pos[0],
                                       (touch.y - d / 2) / self.scale - self.fbo_rect.pos[1])
                    Ellipse(pos=self.cursor_pos, size=(d, d))
                    touch.ud['line'] = Line(points=self.cursor_pos, width=d / 2)
            self.fbo.release()
            self.fbo.draw()
            self.canvas.ask_update()
        elif self.app.active_tool == TOOL_PICKER:
            if touch:
                if self.collide_point(touch.x, touch.y):
                    c = self.fbo_get_pixel_color(touch)
                    self.app.aColor = Color(c[0], c[1], c[2], c[3])
                    self.app.toolbar.tools_select(self.app.prev_tool)
        elif self.app.active_tool == TOOL_FILL:
            if touch:
                if self.collide_point(touch.x, touch.y):
                    self.fbo_fill_region(touch)
        elif self.app.active_tool == TOOL_ERASE1:
            if touch:
                if self.collide_point(touch.x, touch.y):
                    self.fbo_replace_color(touch, width=1)
        elif self.app.active_tool == TOOL_ERASE2:
            if touch:
                if self.collide_point(touch.x, touch.y):
                    self.fbo_replace_color(touch, width=2)
        elif self.app.active_tool == TOOL_ERASE3:
            if touch:
                if self.collide_point(touch.x, touch.y):
                    self.fbo_replace_color(touch, width=3)
        elif self.app.active_tool == TOOL_SELECT:
            if touch:
                if not self.tool_buffer.enabled:
                    self.tool_select.on_touch_down(touch, self.fbo, self.tool_fbo)
                else:
                    pass
        elif self.app.active_tool == TOOL_LINE:
            if touch:
                self.tool_line.on_touch_down(touch, self.fbo, self.tool_fbo)
            self.fbo.draw()
            self.canvas.ask_update()
        elif self.app.active_tool == TOOL_RECT:
            if touch:
                self.tool_rect.on_touch_down(touch, self.fbo, self.tool_fbo)
            self.fbo.draw()
            self.canvas.ask_update()
        elif self.app.active_tool == TOOL_ELLIPSE:
            if touch:
                self.tool_ellipse.on_touch_down(touch, self.fbo, self.tool_fbo)
            self.fbo.draw()
            self.canvas.ask_update()
        elif self.app.active_tool == TOOL_MOVE:
            if touch:
                if self.collide_point(touch.x, touch.y):
                    self.move_image = True
                    self.ox, self.oy = touch.pos
        return True

    def on_touch_move(self, touch):
        result = self.touch_responce()
        if result == 2:
            self.scale_canvas(1.02)
            return
        elif result == 1:
            self.scale_canvas(0.98)
            return
        elif result == 3:
            dist = self.touches[0].distance(self.first_touch)
            self.fbo_move_by_offset((dist / self.scale, dist / self.scale))

        if self.app.active_tool == TOOL_PENCIL1:
            self.fbo_render(touch, width=1.)
        elif self.app.active_tool == TOOL_PENCIL2:
            self.fbo_render(touch, width=2.)
        elif self.app.active_tool == TOOL_PENCIL3:
            self.fbo_render(touch, width=3.)
        elif self.app.active_tool == TOOL_PICKER:
            c = self.fbo_get_pixel_color(touch)
            self.app.aColor = Color(c[0], c[1], c[2])
        elif self.app.active_tool == TOOL_ERASE1:
            if touch:
                if self.collide_point(touch.x, touch.y):
                    self.fbo_replace_color(touch, width=1)
        elif self.app.active_tool == TOOL_ERASE2:
            if touch:
                if self.collide_point(touch.x, touch.y):
                    self.fbo_replace_color(touch, width=2)
        elif self.app.active_tool == TOOL_ERASE3:
            if touch:
                if self.collide_point(touch.x, touch.y):
                    self.fbo_replace_color(touch, width=3)
        elif self.app.active_tool == TOOL_SELECT:
            if touch:
                if not self.tool_buffer.enabled:
                    self.tool_select.on_touch_move(touch, self.tool_fbo)
                    self.fbo.draw()
                    self.canvas.ask_update()
                else:
                    pass

        elif self.app.active_tool == TOOL_LINE:
            if touch:
                self.tool_line.on_touch_move(touch, self.tool_fbo)
            self.fbo.draw()
            self.canvas.ask_update()
        elif self.app.active_tool == TOOL_RECT:
            if touch:
                self.tool_rect.on_touch_move(touch, self.tool_fbo)
            self.fbo.draw()
            self.canvas.ask_update()
        elif self.app.active_tool == TOOL_ELLIPSE:
            if touch:
                self.tool_ellipse.on_touch_move(touch, self.tool_fbo)
            self.fbo.draw()
            self.canvas.ask_update()
        elif self.app.active_tool == TOOL_MOVE:
            if self.move_image:
                self.fbo_move_by_offset((touch.dx / self.scale, touch.dy / self.scale))
        self.tool_buffer.on_touch_move(touch, self.fbo)

    def on_touch_up(self, touch):
        self.tool_buffer.on_touch_up(touch)
        if touch.grab_current is not self:
            return False
        if not self.collide_point(*touch.pos):
            return False
        touch.ungrab(self)

        if self.double_touch():
            _not_add_too_undo = True
        else:
            _not_add_too_undo = False

        if touch:
            try:
                self.touches.remove(touch)
            except:
                pass
            if self.app.active_tool == TOOL_SELECT:
                if touch:
                    self.tool_select.on_touch_up(touch)
                self.fbo.draw()
                self.canvas.ask_update()
            elif self.app.active_tool == TOOL_LINE:
                if touch:
                    self.tool_line.on_touch_up(touch)
                self.fbo.draw()
                self.canvas.ask_update()
            elif self.app.active_tool == TOOL_RECT:
                if touch:
                    self.tool_rect.on_touch_up(touch)
                self.fbo.draw()
                self.canvas.ask_update()
            elif self.app.active_tool == TOOL_ELLIPSE:
                if touch:
                    self.tool_ellipse.on_touch_up(touch)
                self.fbo.draw()
                self.canvas.ask_update()
            elif self.app.active_tool == TOOL_MOVE:
                if touch:
                    self.move_image = False
            elif self.app.active_tool == TOOL_ERASE1 or self.app.active_tool == TOOL_ERASE2 or self.app.active_tool == TOOL_ERASE3:
                if touch:
                    self.px = None
                    self.py = None
            if self.app.active_tool == TOOL_PENCIL1 or self.app.active_tool == TOOL_ERASE1 \
                    or self.app.active_tool == TOOL_PENCIL2 or self.app.active_tool == TOOL_ERASE2 \
                    or self.app.active_tool == TOOL_PENCIL3 or self.app.active_tool == TOOL_ERASE3 \
                    or self.app.active_tool == TOOL_FILL:
                rect = self.app.layer_ribbon.get_active_layer().rect
                pos = rect.pos[0] * self.scale, rect.pos[1] * self.scale
                size = rect.size[0] * self.scale, rect.size[1] * self.scale
                if not _not_add_too_undo:
                    if pos[0] <= touch.x <= pos[0] + size[0] and pos[1] <= touch.y <= pos[1] + size[1]:
                        self.add_undo_stack()
        return True

    def add_redo_stack(self):
        pass

    def add_undo_stack(self):
        _active_layer = self.app.layer_ribbon.get_active_layer()
        _active_layer.backup_texture()
        self.layer_undo_stack = self.layer_undo_stack[:self.undo_layer_index + 1]
        self.layer_undo_stack.append(_active_layer)
        self.undo_layer_index = len(self.layer_undo_stack) - 1

    def do_undo(self, *args):
        if self.layer_undo_stack:
            if self.undo_layer_index > 0:
                _layer = self.layer_undo_stack[self.undo_layer_index]
                _layer.texture_from_backup(direction=-1)
                _active_layer = self.app.layer_ribbon.get_active_layer()
                if _layer is _active_layer:
                    self.fbo_create(_active_layer.texture.size, _active_layer.texture)
                self.undo_layer_index -= 1
                self.canvas_put_drawarea()
            self.fbo_update_pos()

    def active_layer_backup_texture(self):
        active_layer = self.app.layer_ribbon.get_active_layer()
        self.active_layer_last_texture = improc.texture_copy(active_layer.texture)

    def active_layer_set_last_backup_texture(self):
        active_layer = self.app.layer_ribbon.get_active_layer()
        # texture = active_layer.textures_array[-1]
        active_layer.replace_texture(self.active_layer_last_texture)

        self.fbo_create(active_layer.texture.size, active_layer.texture)

        self.canvas_put_drawarea()
        self.fbo_update_pos()
        self.canvas.ask_update()

    def do_redo(self, *args):
        if self.layer_undo_stack:
            if self.undo_layer_index < len(self.layer_undo_stack) - 1:
                self.undo_layer_index += 1
                _layer = self.layer_undo_stack[self.undo_layer_index]
                _layer.texture_from_backup(direction=1)
                _active_layer = self.app.layer_ribbon.get_active_layer()
                if _layer is _active_layer:
                    self.fbo_create(_active_layer.texture.size, _active_layer.texture)
                self.canvas_put_drawarea()
            self.fbo_update_pos()