Пример #1
0
    def make_screen_fbo(self, screen, mode=None):
        assert(mode is not None)
        attr = 'fbo_'  + mode
        fbo = getattr(self, attr)

        w, h = screen.size
        w = (w - w % TILE) + TILE
        h = (h - h % TILE) + TILE
        size = w, h

        if not fbo:
            fbo = Fbo(size=size)
            setattr(self, attr, fbo)

        fbo.clear()
        with fbo:
            ClearColor(0, 0, 0, 1)
            ClearBuffers()

        fbo.add(screen.canvas)
        fbo.draw()
        return fbo
Пример #2
0
class Paintbrush(Widget):
    def __init__(self, **kwargs):
        super(Paintbrush, self).__init__(**kwargs)
        self.fbo = Fbo(size=(10, 10))
        self.mesh = Mesh()
        self.points = []
        self.vertices = []
        self.indices = []
        self.line_widths = []
        self.cap_vertices_index = 0
        self.cap_indices_index = 0
        self.mask_lines = []
        self.mask_alphas = []
        self.canvas = RenderContext()
        self.canvas.shader.fs = mask_shader
        self.buffer_container = None
        self.rgb = (0, 1, 1)
        # We'll update our glsl variables in a clock
        Clock.schedule_interval(self.update_glsl, 0)

        # Maintain a window of history for autocorrelations
        self.ac_window = []
        self.ac_position = 0
        self.periodicity_factor = 1.0

    def update_glsl(self, *largs):
        # This is needed for the default vertex shader.
        self.canvas['projection_mat'] = Window.render_context['projection_mat']
        self.canvas['modelview_mat'] = Window.render_context['modelview_mat']

    def on_size(self, instance, value):
        self.canvas.clear()
        with self.canvas:
            self.fbo = Fbo(size=value)
            self.mask_fbo = Fbo(size=(value[0] // 5, value[1] // 5), clear_color=(1, 1, 1, 1))
            Color(*self.rgb, 0.9)
            BindTexture(texture=self.mask_fbo.texture, index=1)
            self.buffer_container = Rectangle(pos=self.pos, size=value, texture=self.fbo.texture)
            #Rectangle(pos=self.pos, size=value, texture=self.mask_fbo.texture)

        self.canvas['texture1'] = 1

        with self.fbo:
            Color(1, 1, 1)
            self.mesh = Mesh(mode='triangle_strip')

    def on_pos(self, instance, value):
        if not self.buffer_container: return
        self.buffer_container.pos = value

    def build_line_segment(self, start, end, future, start_width=8.0, end_width=8.0):
        """Builds a line segment knowing the start and end, as well as one point in the future."""
        start = np.array([start[0], start[1]])
        end = np.array([end[0], end[1]])
        future = np.array([future[0], future[1]])
        length = np.linalg.norm(end - start)
        num_interpolants = max(int(length / DISTANCE_PER_POINT), 3)

        normal = (end - start) / length * start_width / 2.0
        normal = np.array([-normal[1], normal[0]])
        end_normal = (future - end) / max(np.linalg.norm(future - end), 0.1) * end_width / 2.0
        end_normal = np.array([-end_normal[1], end_normal[0]])
        delta_sign = None

        # if (self.last_normal is not None and
        #     np.linalg.norm(normal - self.last_normal) > np.linalg.norm(normal + self.last_normal)):
        #     self.last_normal *= -1

        # Add points deviating in alternating directions around the actual path
        for i in range(num_interpolants):
            path_point = start + (i / num_interpolants) * (end - start)
            delta = normal + (i / num_interpolants) * (end_normal - normal)
            if delta_sign is None:
                delta_sign = 1
                if len(self.points) > 3:
                    second_last_vertex = np.array(self.vertices[-8:-6])
                    option_1 = path_point + delta
                    option_2 = path_point - delta
                    if (np.linalg.norm(option_2 - second_last_vertex) <
                        np.linalg.norm(option_1 - second_last_vertex)):
                        delta_sign *= -1
            self.vertices.extend([*(path_point + delta * delta_sign), 0, 0])
            self.indices.append(len(self.indices))
            delta_sign *= -1

    def add_cap(self, width):
        """Adds a round line cap to the end of the vertex/index list."""
        self.cap_vertices_index = len(self.vertices)
        self.cap_indices_index = len(self.indices)
        if len(self.points) < 3:
            return

        # Extend the current line segment using a circular interpolation of line widths
        start = np.array([self.points[-1][0], self.points[-1][1]])
        prev = np.array([self.points[-2][0], self.points[-2][1]])
        end = start + (start - prev) / max(np.linalg.norm(start - prev), 0.001) * width / 2.0
        length = np.linalg.norm(end - start)
        num_interpolants = max(int(length / DISTANCE_PER_POINT) * 2, 3)

        normal = (end - start) / length * width / 2.0
        normal = np.array([-normal[1], normal[0]])
        end_normal = np.zeros(2)
        delta_sign = None

        # Add points deviating in alternating directions around the actual path
        for i in range(num_interpolants):
            path_point = start + (i / (num_interpolants - 1)) * (end - start)
            circ_progress = 1 - np.sqrt(1 - (i / (num_interpolants - 1)) ** 2)
            delta = normal + circ_progress * (end_normal - normal)
            if delta_sign is None:
                delta_sign = 1
                if len(self.points) > 3:
                    second_last_vertex = np.array(self.vertices[-8:-6])
                    option_1 = path_point + delta
                    option_2 = path_point - delta
                    if (np.linalg.norm(option_2 - second_last_vertex) <
                        np.linalg.norm(option_1 - second_last_vertex)):
                        delta_sign *= -1
            self.vertices.extend([*(path_point + delta * delta_sign), 0, 0])
            self.indices.append(len(self.indices))
            delta_sign *= -1

    def remove_cap(self):
        """Removes a cap on the line."""
        if self.cap_vertices_index > 0 and self.cap_vertices_index <= len(self.vertices):
            del self.vertices[self.cap_vertices_index:]
            del self.indices[self.cap_indices_index:]
        self.cap_vertices_index = 0
        self.cap_indices_index = 0

    def current_line_width(self, depth, window=5):
        """Computes the current line width of the previous `window` points."""
        max_width = 120.0
        min_width = 5.0
        min_dist = 40.0
        max_dist = 140.0
        last_point = self.points[-1]
        old_point = self.points[max(0, len(self.points) - window)]
        if PAINTBRUSH_MODE == 0:
            dist = np.linalg.norm(np.array([last_point[0], last_point[1]]) -
                                  np.array([old_point[0], old_point[1]]))
        else:
            dist = 120.0
        width = (max_dist - dist) * (max_width * 0.8 - min_width) / (max_dist - min_dist)
        if PAINTBRUSH_MODE != 0:
            depth_factor = 1 / (1 + np.exp(-(depth - 0.5) * 4))
            width *= depth_factor
            if PAINTBRUSH_MODE == 2:
                width *= self.periodicity_factor
        return np.clip(width, min_width, max_width)

    def update_periodicity(self, point):
        """Computes a new autocorrelation magnitude by adding the given point."""
        self.ac_window.append(point)
        if len(self.ac_window) > AUTOCORRELATION_WINDOW:
            del self.ac_window[0]
        self.ac_position += 1
        if self.ac_position % 8 == 0 and len(self.ac_window) == AUTOCORRELATION_WINDOW:
            ac_window = np.array(self.ac_window)
            x_fft = np.abs(np.fft.rfft(ac_window[:,0] * np.hanning(AUTOCORRELATION_WINDOW)))
            y_fft = np.abs(np.fft.rfft(ac_window[:,1] * np.hanning(AUTOCORRELATION_WINDOW)))
            x_fft = x_fft[4:20] / np.mean(x_fft[4:20])
            y_fft = y_fft[4:20] / np.mean(y_fft[4:20])
            # if self.ac_position > 200:
            #     plt.figure()
            #     plt.subplot(121)
            #     plt.plot(ac_window[:,0], ac_window[:,1])
            #     plt.subplot(122)
            #     plt.plot(x_fft, label='x')
            #     plt.plot(y_fft, label='y')
            #     plt.show()
            self.periodicity_factor = ((max(1.0, np.max(x_fft) / 4.0) *
                                        max(1.0, np.max(y_fft) / 4.0)) - 1) ** 2 + 1


    def add_point(self, point, depth=None, width=None, alpha=None):
        """
        point: a point in window space to add to the paintbrush trajectory (x, y).
        depth: a 0-1 value indicating the depth into the screen of the current point.
        alpha: a manual 0-1 alpha level for this point.

        Returns the current line width.
        """
        point = (point[0] - self.pos[0], point[1] - self.pos[1])
        self.points.append(point)

        # Build a segment of line
        line_width = 0
        if len(self.points) > 2:
            self.update_periodicity(point)
            line_width = self.current_line_width(depth) if depth is not None else width
            old_line_width = (sum(self.line_widths) / len(self.line_widths)
                              if self.line_widths else line_width)
            self.line_widths.append(line_width)
            if len(self.line_widths) > LINE_WIDTH_WINDOW:
                self.line_widths.pop(0)
            if width is None:
                line_width = sum(self.line_widths) / len(self.line_widths)
            # Clamp the amount by which the line width can change - results in
            # smoother lines
            # line_width = old_line_width + np.clip(line_width - old_line_width, -2.0, 2.0)
            self.remove_cap()
            self.build_line_segment(*self.points[-3:], old_line_width, line_width)
            self.add_cap(line_width)

            # Update mask
            if len(self.points) % MASK_INTERVAL == 0 and len(self.points) > MASK_INTERVAL:
                self.mask_lines.append(Line(points=(self.points[-MASK_INTERVAL - 1][0] / 5,
                                                    self.points[-MASK_INTERVAL - 1][1] /5 ,
                                                    self.points[-1][0] / 5,
                                                    self.points[-1][1] / 5),
                                           width=(line_width + 8.0) / 10))
                if alpha is not None:
                    self.mask_alphas.append(alpha)
                with self.mask_fbo:
                    self.mask_fbo.clear()
                    self.mask_fbo.clear_buffer()
                    if len(self.mask_alphas) == len(self.mask_lines):
                        white_values = self.mask_alphas
                    else:
                        white_values = 1 / (1 + np.exp(-((np.arange(len(self.mask_lines)) -
                                                         len(self.mask_lines)) / FADE_FACTOR + 3)))
                        white_values = white_values * (1 - BASE_FADE) + BASE_FADE
                    for i, (white, line) in enumerate(zip(white_values, self.mask_lines)):
                        Color(white, white, white, 1)
                        self.mask_fbo.add(line)


        # if len(self.points) % 100 == 20:
        #     plt.figure()
        #     plt.plot(self.vertices[::4], self.vertices[1::4])
        #     plt.plot(self.vertices[::4], self.vertices[1::4], 'b.')
        #     plt.plot([x[0] for x in self.points], [x[1] for x in self.points], 'ro')
        #     plt.plot([x[0] for x in self.points], [x[1] for x in self.points], 'r-')
        #     plt.show()

        # self.vertices.extend([point[0], point[1], 0, 0])
        # if len(self.points) > 1:
        #     self.indices.extend([len(self.points) - 2, len(self.points) - 1])
        self.mesh.vertices = self.vertices
        self.mesh.indices = self.indices
        return line_width

    def clear(self):
        self.points = []
        self.vertices = []
        self.indices = []
        self.mesh.vertices = []
        self.mesh.indices = []
        self.periodicity_factor = 1.0
        self.ac_window = []
        self.ac_position = 0
        with self.fbo:
            self.fbo.clear_buffer()
        self.mask_lines = []
        self.mask_colors = []
        with self.mask_fbo:
            self.mask_fbo.clear()
            self.mask_fbo.clear_buffer()
        self.on_size(self, self.size)
Пример #3
0
class FragmentCompute:
    def __init__(self, fs, length1, length2 = 1):
        size = (length1, length2)

        # it doesn't look like we can use float textures on mobile kivy, but people sometimes interconvert floats with 32bit rgba in shaders.
        # we would then have 3 shaders or texture rows or such, for x coord, y coord, angle, etc

        #Logger.info('float: ' + str(gl.getExtension('OES_texture_float')))
        texture = Texture.create(
            size = size,
            #bufferfmt = 'float'
        )
        self._fbo = Fbo(
            size = size,
            texture = texture,
            vs = default_vs,
            fs = header_fs + fs, 
        )

        # these matrices are to transform
        # window coordinates into data
        # coordinates
        centermat = Matrix()
        centermat.translate(-.5,-.5,-.5)
        idxscale = 1.0 / 255.0;
        idxmat = Matrix()
        idxmat.scale(idxscale, idxscale, idxscale)
        self._fbo['frag_coord2idx'] = idxmat.multiply(centermat)
        ratiomat = Matrix()
        ratiomat.scale(1.0 / length1, 1.0 / length2, 1.0)
        self._fbo['frag_coord2ratio'] = ratiomat

        self._texture_bindings = {}

        self._fbo.add_reload_observer(self._populate_fbo)
        self._populate_fbo(self._fbo)
    def texture(self):
        return self._fbo.texture
    def download(self):
        ## cgl requires cython,
        ## but glReadPixels is used in
        ## fbo.py, and could be used to
        ## get RGB instead of RGBA, since
        ## A is presently drawn blended,
        ## and maybe save a memory copy.
        #width, height = self._fbo.size
        #data = array('B', [1] * width * height * 4)
        #self._fbo.bind()
        #cgl.cgl.glPixelStorei(GL_PACK_ALIGNMENT, 1)
        #cgl.cgl.glReadPixels(0, 0, width, height, cgl.GL_RGB, cgl.GL_UNSIGNED_BYTE, data)
        #self._fbo.release()
        #return data
        return bytearray(self._fbo.pixels)
    def compute(self):
        self._rectangle.texture = self._fbo.texture
        self._fbo.draw()
        return self
    def __setitem__(self, name, value):
        if isinstance(value, Texture):
            if name in self._texture_bindings:
                index, oldvalue = self._texture_bindings[name]
            else:
                index = len(self._texture_bindings) + 1
            self._texture_bindings[name] = (index, value)
            self._fbo[name] = index 
            self._fbo.clear()
            self._populate_fbo(self._fbo)
        else:
            self._fbo[name] = value 

    def _populate_fbo(self, fbo):
        with fbo:
            for index, texture in self._texture_bindings.values():
                BindTexture(index = index, texture = texture)
            Callback(self._set_blend_mode)
            self._rectangle = Rectangle(size = self._fbo.size)
            Callback(self._unset_blend_mode)
    # opaque blend mode provides for use of the alpha channel for data
    def _set_blend_mode(self, instruction = None):
        gl.glBlendFunc(gl.GL_ONE, gl.GL_ZERO);
        gl.glBlendFuncSeparate(gl.GL_ONE, gl.GL_ZERO, gl.GL_ONE, gl.GL_ZERO);
    def _unset_blend_mode(self, instruction = None):
        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA);
        gl.glBlendFuncSeparate(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA, gl.GL_ONE, gl.GL_ONE);