Example #1
0
    def __init__(self):
        GObject.GObject.__init__(self)

        brush_file = open('../brushes/classic/charcoal.myb')
        brush_info = brush.BrushInfo(brush_file.read())
        brush_info.set_color_rgb((0.0, 0.0, 0.0))
        self.default_eraser = brush_info.get_base_value("eraser")
        self.default_radius = brush_info.get_base_value("radius_logarithmic")
        self.brush = brush.Brush(brush_info)

        self.button_pressed = False
        self.last_event = (0.0, 0.0, 0.0)  # (x, y, time)

        self.onionskin_on = True
        self.onionskin_by_cels = True
        self.onionskin_length = 3
        self.onionskin_falloff = 0.5

        self.eraser_on = False
        self.force_add_cel = True

        self.surface = None
        self.surface_node = None

        self.xsheet = XSheet(24 * 60)
        self.xsheet.connect('frame-changed', self.xsheet_changed_cb)
        self.xsheet.connect('layer-changed', self.xsheet_changed_cb)

        self.metronome = Metronome(self.xsheet)

        self.update_surface()

        self.nodes = {}
        self.create_graph()
        self.init_ui()
Example #2
0
    def setup(self):
        self._set_default_settings()

        self._xsheet = XSheet()
        self._xsheet.connect("cursor-changed", self._cursor_changed_cb)
        self._canvas_graph = CanvasGraph(self._xsheet)
        self._metronome = Metronome(self._xsheet)

        self._setup_icons()
        self._init_ui()

        if os.path.exists('test.zip'):
            self._xsheet.load('test.zip')
Example #3
0
def main():
    # Make sure we have enough stuff
    if len(sys.argv) < 3:
        print('Usage:')
        print('    ./gif2blit.py <gif> <output_dir>')
        sys.exit(0)

    # Need to make the QApp first
    app = QApplication(sys.argv)

    # Assume that all of the input is valid
    gifName = sys.argv[1]
    outputDirName = sys.argv[2]

    # Open of the animated GIF
    gif = QMovie(gifName)

    # Make the directory if it doesn't exist
    if not os.path.exists(outputDirName):
        os.makedirs(outputDirName)

    # Build the strucutres, get some data
    xsheet = XSheet(fps=50)
    anim = Animation(xsheet=xsheet,
                     name=os.path.basename(gifName),
                     created=time.ctime())
    frameCount = gif.frameCount()
    msDelayPerFrame = 1000 // xsheet.fps()
    timingOff = False
    filename = ''

    # Start the loop, but just look at the first frame so we can get the size
    gif.jumpToNextFrame()
    anim.setSize(gif.currentImage().size())

    # Add all of the frames together
    for i in range(0, frameCount):
        # Make the new frame and add it
        f = Frame()
        c = Cel(gif.currentImage().convertToFormat(
            QImage.Format_ARGB32_Premultiplied))
        f.setHold(gif.nextFrameDelay() // msDelayPerFrame)
        f.addCel(c)
        xsheet.addFrame(f)

        # Save images as we go along
        try:
            filename = os.path.join(outputDirName, '%s.png' % c.UUID())
            c.image().save(filename)
            print(filename)
        except:
            print('There was an error in trying to save a Cel to:\n  %s' %
                  filename)
            sys.exit(1)

        # Checking for not so good shifts in timing
        if (gif.nextFrameDelay() % msDelayPerFrame) != 0:
            timingOff = True

        # Dump it
        gif.jumpToNextFrame()

    # And save the JSON, then we're done
    try:
        filename = os.path.join(outputDirName, 'sequence.json')
        f = open(filename, 'w')
        f.write(structToJSON(animationToJSONStruct(anim)))
        f.close()
    except:
        print('There was an error in tyring to save the JSON file to:\n  %s' %
              filename)
        sys.exit(1)

    # Metrics:
    print('Converted a GIF with %i frames.' % xsheet.numFrames())
    if timingOff:
        print(
            'Because of the set FPS and the delay of the frames in the GIF, the timing might be off in the converted animation.'
        )
Example #4
0
class Application(GObject.GObject):
    def __init__(self):
        GObject.GObject.__init__(self)

        brush_file = open('../brushes/classic/charcoal.myb')
        brush_info = brush.BrushInfo(brush_file.read())
        brush_info.set_color_rgb((0.0, 0.0, 0.0))
        self.default_eraser = brush_info.get_base_value("eraser")
        self.default_radius = brush_info.get_base_value("radius_logarithmic")
        self.brush = brush.Brush(brush_info)

        self.button_pressed = False
        self.last_event = (0.0, 0.0, 0.0)  # (x, y, time)

        self.onionskin_on = True
        self.onionskin_by_cels = True
        self.onionskin_length = 3
        self.onionskin_falloff = 0.5

        self.eraser_on = False
        self.force_add_cel = True

        self.surface = None
        self.surface_node = None

        self.xsheet = XSheet(24 * 60)
        self.xsheet.connect('frame-changed', self.xsheet_changed_cb)
        self.xsheet.connect('layer-changed', self.xsheet_changed_cb)

        self.metronome = Metronome(self.xsheet)

        self.update_surface()

        self.nodes = {}
        self.create_graph()
        self.init_ui()

    def create_graph(self):
        self.graph = Gegl.Node()

        main_over = self.graph.create_child("gegl:over")
        self.nodes['main_over'] = main_over

        layer_overs = []
        for l in range(self.xsheet.layers_length):
            over = self.graph.create_child("gegl:over")
            layer_overs.append(over)

        self.nodes['layer_overs'] = layer_overs

        layer_overs[0].connect_to("output", main_over, "input")

        for over, next_over in zip(layer_overs, layer_overs[1:]):
            next_over.connect_to("output", over, "input")

        background_node = self.graph.create_child("gegl:rectangle")
        background_node.set_property('color', Gegl.Color.new("#fff"))
        background_node.connect_to("output", layer_overs[-1], "input")
        self.nodes['background'] = background_node

        layer_nodes = []
        for l in range(self.xsheet.layers_length):
            nodes = {}
            current_cel_over = self.graph.create_child("gegl:over")
            current_cel_over.connect_to("output", layer_overs[l], "aux")
            nodes['current_cel_over'] = current_cel_over

            onionskin_overs = []
            onionskin_opacities = []
            for i in range(self.onionskin_length):
                over = self.graph.create_child("gegl:over")
                onionskin_overs.append(over)

                opacity = self.graph.create_child("gegl:opacity")
                opacity.set_property('value', 1 - self.onionskin_falloff)
                onionskin_opacities.append(opacity)

                over.connect_to("output", opacity, "input")

                for over, next_opacity in zip(onionskin_overs,
                                              onionskin_opacities[1:]):
                    next_opacity.connect_to("output", over, "aux")

                onionskin_opacities[0].connect_to("output", current_cel_over,
                                                  "aux")

            nodes['onionskin'] = {}
            nodes['onionskin']['overs'] = onionskin_overs
            nodes['onionskin']['opacities'] = onionskin_opacities
            layer_nodes.append(nodes)

        self.nodes['layer_nodes'] = layer_nodes

        self.update_graph()

    def update_graph(self):
        get_cel = None
        if self.onionskin_by_cels:
            get_cel = self.xsheet.get_cel_relative_by_cels
        else:
            get_cel = self.xsheet.get_cel_relative

        for layer_idx in range(self.xsheet.layers_length):
            layer_nodes = self.nodes['layer_nodes'][layer_idx]
            cur_cel = self.xsheet.get_cel(layer_idx=layer_idx)

            if cur_cel is not None:
                cur_cel.surface_node.connect_to(
                    "output", layer_nodes['current_cel_over'], "input")
            else:
                layer_nodes['current_cel_over'].disconnect("input")

            if not self.onionskin_on:
                continue

            layer_diff = layer_idx - self.xsheet.layer_idx
            for i in range(self.onionskin_length):
                prev_cel = get_cel(-(i + 1), layer_diff=layer_diff)
                over = layer_nodes['onionskin']['overs'][i]
                opacity = layer_nodes['onionskin']['opacities'][i]

                if prev_cel is not None:
                    prev_cel.surface_node.connect_to("output", over, "input")
                else:
                    over.disconnect("input")

        # debug
        # print_connections(self.nodes['main_over'])

    def init_ui(self):
        window = Gtk.Window()
        window.props.title = "XSheet"
        window.connect("destroy", self.destroy_cb)
        window.connect("size-allocate", self.size_allocate_cb)
        window.connect("key-press-event", self.key_press_cb)
        window.connect("key-release-event", self.key_release_cb)
        window.show()

        top_box = Gtk.Grid()
        window.add(top_box)
        top_box.show()

        toolbar = Gtk.Toolbar()
        top_box.attach(toolbar, 0, 0, 2, 1)
        toolbar.show()

        factory = Gtk.IconFactory()
        icon_names = [
            'xsheet-onionskin', 'xsheet-play', 'xsheet-eraser',
            'xsheet-metronome', 'xsheet-settings'
        ]
        for name in icon_names:
            filename = os.path.join('data', 'icons', name + '.svg')
            pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
            iconset = Gtk.IconSet.new_from_pixbuf(pixbuf)
            factory.add(name, iconset)
            factory.add_default()

        play_button = Gtk.ToggleToolButton()
        play_button.set_stock_id("xsheet-play")
        play_button.connect("toggled", self.toggle_play_cb)
        toolbar.insert(play_button, -1)
        play_button.show()

        onionskin_button = Gtk.ToggleToolButton()
        onionskin_button.set_stock_id("xsheet-onionskin")
        onionskin_button.set_active(True)
        onionskin_button.connect("toggled", self.toggle_onionskin_cb)
        toolbar.insert(onionskin_button, -1)
        onionskin_button.show()

        eraser_button = Gtk.ToggleToolButton()
        eraser_button.set_stock_id("xsheet-eraser")
        eraser_button.connect("toggled", self.toggle_eraser_cb)
        toolbar.insert(eraser_button, -1)
        eraser_button.show()

        metronome_button = Gtk.ToggleToolButton()
        metronome_button.set_stock_id("xsheet-metronome")
        metronome_button.connect("toggled", self.toggle_metronome_cb)
        toolbar.insert(metronome_button, -1)
        metronome_button.show()

        settings_button = Gtk.ToolButton()
        settings_button.set_stock_id("xsheet-settings")
        settings_button.connect("clicked", self.settings_click_cb)
        toolbar.insert(settings_button, -1)
        settings_button.show()

        event_box = Gtk.EventBox()
        event_box.connect("motion-notify-event", self.motion_to_cb)
        event_box.connect("button-press-event", self.button_press_cb)
        event_box.connect("button-release-event", self.button_release_cb)
        top_box.attach(event_box, 0, 1, 1, 1)
        event_box.props.expand = True
        event_box.show()

        view_widget = GeglGtk.View()
        view_widget.set_node(self.nodes['main_over'])
        view_widget.set_autoscale_policy(GeglGtk.ViewAutoscale.DISABLED)
        view_widget.set_size_request(800, 400)
        event_box.add(view_widget)
        view_widget.show()

        xsheet_widget = XSheetWidget(self.xsheet)
        top_box.attach(xsheet_widget, 1, 1, 1, 1)
        xsheet_widget.show()

    def run(self):
        return Gtk.main()

    def destroy_cb(self, *ignored):
        Gtk.main_quit()

    def size_allocate_cb(self, widget, allocation):
        background_node = self.nodes['background']
        self.nodes['background'].set_property("width", allocation.width)
        self.nodes['background'].set_property("height", allocation.height)

    def motion_to_cb(self, widget, event):
        # FIXME, better disconnect
        if self.surface is None:
            return

        (x, y, time) = event.x, event.y, event.time

        pressure = event.get_axis(Gdk.AxisUse.PRESSURE)
        if pressure is None:
            pressure = 0.5

        xtilt = event.get_axis(Gdk.AxisUse.XTILT)
        ytilt = event.get_axis(Gdk.AxisUse.YTILT)
        if xtilt is None or ytilt is None:
            xtilt = 0
            ytilt = 0

        dtime = (time - self.last_event[2]) / 1000.0
        if self.button_pressed:
            self.surface.begin_atomic()
            self.brush.stroke_to(self.surface.backend, x, y, pressure, xtilt,
                                 ytilt, dtime)
            self.surface.end_atomic()

        self.last_event = (x, y, time)

    def button_press_cb(self, widget, event):
        if self.force_add_cel:
            self.xsheet.add_cel()

        self.button_pressed = True

    def button_release_cb(self, widget, event):
        self.button_pressed = False
        self.brush.reset()

    def xsheet_changed_cb(self, xsheet):
        self.update_surface()
        self.update_graph()

    def update_surface(self):
        cel = self.xsheet.get_cel()
        if cel is not None:
            self.surface = cel.surface
            self.surface_node = cel.surface_node
        else:
            self.surface = None
            self.surface_node = None

    def toggle_play_stop(self):
        if self.xsheet.is_playing:
            self.xsheet.stop()
        else:
            self.xsheet.play()

    def toggle_play_cb(self, widget):
        self.toggle_play_stop()

    def toggle_onionskin(self):
        self.onionskin_on = not self.onionskin_on

        for layer_idx in range(self.xsheet.layers_length):
            layer_nodes = self.nodes['layer_nodes'][layer_idx]
            onionskin_opacities = layer_nodes['onionskin']['opacities']
            current_cel_over = layer_nodes['current_cel_over']
            if self.onionskin_on:
                onionskin_opacities[0].connect_to("output", current_cel_over,
                                                  "aux")
            else:
                current_cel_over.disconnect("aux")

        self.update_graph()

    def toggle_onionskin_cb(self, widget):
        self.toggle_onionskin()

    def toggle_eraser(self):
        self.eraser_on = not self.eraser_on

        if self.eraser_on:
            self.brush.brushinfo.set_base_value("eraser", 1.0)
            self.brush.brushinfo.set_base_value("radius_logarithmic",
                                                self.default_radius * 3)
        else:
            self.brush.brushinfo.set_base_value("eraser", self.default_eraser)
            self.brush.brushinfo.set_base_value("radius_logarithmic",
                                                self.default_radius)

    def toggle_eraser_cb(self, widget):
        self.toggle_eraser()

    def toggle_metronome(self):
        if self.metronome.is_on():
            self.metronome.activate()
        else:
            self.metronome.deactivate()

    def toggle_metronome_cb(self, widget):
        self.toggle_metronome()

    def settings_click_cb(self, widget):
        dialog = SettingsDialog(widget.get_toplevel())
        dialog.show()

    def key_press_cb(self, widget, event):
        if event.keyval == Gdk.KEY_Up:
            self.xsheet.previous_frame()
        elif event.keyval == Gdk.KEY_Down:
            self.xsheet.next_frame()

    def key_release_cb(self, widget, event):
        if event.keyval == Gdk.KEY_c:
            self.xsheet.add_cel()
        elif event.keyval == Gdk.KEY_p:
            self.toggle_play_stop()
        elif event.keyval == Gdk.KEY_o:
            self.toggle_onionskin()
        elif event.keyval == Gdk.KEY_e:
            self.toggle_eraser()
        elif event.keyval == Gdk.KEY_BackSpace:
            # FIXME, needs to be done in gegl backend
            if self.surface is not None:
                self.surface.clear()
        elif event.keyval == Gdk.KEY_Left:
            self.xsheet.previous_layer()
        elif event.keyval == Gdk.KEY_Right:
            self.xsheet.next_layer()
Example #5
0
class Application(Gtk.Application):
    _INSTANCE = None

    def __init__(self):
        assert Application._INSTANCE is None
        Gtk.Application.__init__(self)
        Application._INSTANCE = self
        self.connect("activate", self._activate_cb)

    def setup(self):
        self._set_default_settings()

        self._xsheet = XSheet()
        self._xsheet.connect("cursor-changed", self._cursor_changed_cb)
        self._canvas_graph = CanvasGraph(self._xsheet)
        self._metronome = Metronome(self._xsheet)

        self._setup_icons()
        self._init_ui()

        if os.path.exists('test.zip'):
            self._xsheet.load('test.zip')

    def _activate_cb(self, app):
        self.setup()
        self._main_window.present()

    def get_metronome(self):
        return self._metronome

    def _about_cb(self, action, state):
        print("About")

    def _quit_cb(self, action, state):
        self._quit()

    def _new_cb(self, action, state):
        self._xsheet.new()

    def _cut_cb(self, action, state):
        self._xsheet.cut()

    def _copy_cb(self, action, state):
        self._xsheet.copy()

    def _paste_cb(self, action, state):
        self._xsheet.paste()

    def _remove_clear_cb(self, action, state):
        self._xsheet.remove_clear()

    def _next_frame_cb(self, action, state):
        self._xsheet.next_frame()

    def _previous_frame_cb(self, action, state):
        self._xsheet.previous_frame()

    def _next_layer_cb(self, action, state):
        self._xsheet.next_layer()

    def _previous_layer_cb(self, action, state):
        self._xsheet.previous_layer()

    def _pan_view_up_cb(self, action, state):
        self._main_window.get_canvas_widget().pan_view("up")

    def _pan_view_down_cb(self, action, state):
        self._main_window.get_canvas_widget().pan_view("down")

    def _pan_view_left_cb(self, action, state):
        self._main_window.get_canvas_widget().pan_view("left")

    def _pan_view_right_cb(self, action, state):
        self._main_window.get_canvas_widget().pan_view("right")

    def _zoom_view_in_cb(self, action, state):
        self._main_window.get_canvas_widget().zoom_view(1)

    def _zoom_view_out_cb(self, action, state):
        self._main_window.get_canvas_widget().zoom_view(-1)

    def _activate_toggle_cb(window, action, data=None):
        action.change_state(GLib.Variant('b', not action.get_state()))

    def _change_fullscreen_cb(self, action, state):
        if state.unpack():
            self._main_window.fullscreen()
        else:
            self._main_window.unfullscreen()
        action.set_state(state)

    def _change_timeline_cb(self, action, state):
        if state.unpack():
            self._main_window.get_xsheet_widget().show()
        else:
            self._main_window.get_xsheet_widget().hide()
        action.set_state(state)

    def _change_play_cb(self, action, state):
        if state.unpack():
            self._xsheet.play(_settings['play']['loop'])
        else:
            self._xsheet.stop()
        action.set_state(state)

    def _change_play_loop_cb(self, action, state):
        if state.unpack():
            _settings['play']['loop'] = True
        else:
            _settings['play']['loop'] = False
        action.set_state(state)

    def _change_onionskin_cb(self, action, state):
        if state.unpack():
            self._canvas_graph.set_onionskin_enabled(True)
        else:
            self._canvas_graph.set_onionskin_enabled(False)
        action.set_state(state)

    def _change_eraser_cb(self, action, state):
        if state.unpack():
            self._set_eraser_enabled(True)
        else:
            self._set_eraser_enabled(False)
        action.set_state(state)

    def _change_metronome_cb(self, action, state):
        if state.unpack():
            self._metronome.activate()
        else:
            self._metronome.deactivate()
        action.set_state(state)

    def _quit(self):
        self._xsheet.save('test.zip')
        Gtk.Application.quit(self)

    def _set_default_settings(self):
        brush = MyPaint.Brush()
        brush_def = open('../mypaint/brushes/classic/charcoal.myb').read()
        brush.from_string(brush_def)
        set_base_color(brush, (0.0, 0.0, 0.0))
        self._default_eraser = get_base_value(brush, "eraser")
        self._default_radius = get_base_value(brush, "radius_logarithmic")
        _settings['brush'] = brush

        _settings['onionskin'] = {}
        _settings['onionskin']['on'] = True
        _settings['onionskin']['by_cels'] = True
        _settings['onionskin']['length'] = 3
        _settings['onionskin']['falloff'] = 0.5

        _settings['eraser'] = {}
        _settings['eraser']['on'] = False

        _settings['play'] = {}
        _settings['play']['loop'] = False

    def _setup_icons(self):
        factory = Gtk.IconFactory()
        icon_names = ['xsheet-onionskin', 'xsheet-play', 'xsheet-eraser',
                      'xsheet-clear', 'xsheet-metronome', 'xsheet-settings',
                      'xsheet-prev-layer', 'xsheet-next-layer']
        for name in icon_names:
            filename = os.path.join('data', 'icons', name + '.svg')
            pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
            iconset = Gtk.IconSet.new_from_pixbuf(pixbuf)
            factory.add(name, iconset)
            factory.add_default()

    def _init_ui(self):
        self._main_window = ApplicationWindow(self, self._xsheet,
                                              self._canvas_graph)

        def add_simple_actions(obj, actions):
            for action_name, action_cb in actions:
                action = Gio.SimpleAction(name=action_name)
                action.connect("activate", action_cb)
                obj.add_action(action)

        def add_toggle_actions(obj, actions):
            for action_name, change_cb, enabled in actions:
                action = Gio.SimpleAction.new_stateful(action_name, None,
                                                       GLib.Variant('b', enabled))
                action.connect("activate", self._activate_toggle_cb)
                action.connect("change-state", change_cb)
                obj.add_action(action)

        app_actions = (
            ("about", self._about_cb),
            ("quit", self._quit_cb),
        )
        add_simple_actions(self, app_actions)

        win_actions = (
            ("new", self._new_cb),
            ("cut", self._cut_cb),
            ("copy", self._copy_cb),
            ("paste", self._paste_cb),
            ("remove_clear", self._remove_clear_cb),
            ("next_frame", self._next_frame_cb),
            ("previous_frame", self._previous_frame_cb),
            ("next_layer", self._next_layer_cb),
            ("previous_layer", self._previous_layer_cb),
            ("pan_view_up", self._pan_view_up_cb),
            ("pan_view_down", self._pan_view_down_cb),
            ("pan_view_left", self._pan_view_left_cb),
            ("pan_view_right", self._pan_view_right_cb),
            ("zoom_view_in", self._zoom_view_in_cb),
            ("zoom_view_out", self._zoom_view_out_cb),
        )
        add_simple_actions(self._main_window, win_actions)

        toggle_actions = (
            ("fullscreen", self._change_fullscreen_cb, False),
            ("timeline", self._change_timeline_cb, True),
            ("play", self._change_play_cb, False),
            ("play_loop", self._change_play_loop_cb, False),
            ("onionskin", self._change_onionskin_cb, True),
            ("eraser", self._change_eraser_cb, False),
            ("metronome", self._change_metronome_cb, False),
        )
        add_toggle_actions(self._main_window, toggle_actions)

        non_menu_accels = (
            ("o", "win.onionskin", None),
            ("e", "win.eraser", None),
            ("BackSpace", "win.remove_clear", None),
            ("<Control>Up", "win.previous_frame", None),
            ("<Control>Down", "win.next_frame", None),
            ("<Control>Left", "win.previous_layer", None),
            ("<Control>Right", "win.next_layer", None),
            ("<Control><Shift>Up", "win.pan_view_up", None),
            ("<Control><Shift>Down", "win.pan_view_down", None),
            ("<Control><Shift>Left", "win.pan_view_left", None),
            ("<Control><Shift>Right", "win.pan_view_right", None),
            ("comma", "win.zoom_view_out", None),
            ("period", "win.zoom_view_in", None),
        )
        for accel, action_name, parameter in non_menu_accels:
            self.add_accelerator(accel, action_name, parameter)

        builder = Gtk.Builder()
        builder.add_from_file("menu.ui")
        self.set_app_menu(builder.get_object("app-menu"))
        self.set_menubar(builder.get_object("menubar"))

        self._main_window.connect("destroy", self._destroy_cb)
        self._main_window.create_widgets()

    def _destroy_cb(self, *ignored):
        self._quit()

    def _set_eraser_enabled(self, enabled):
        _settings['eraser']['on'] = enabled

        brush = _settings['brush']
        if _settings['eraser']['on']:
            set_base_value(brush, "eraser", 1.0)
            set_base_value(brush, "radius_logarithmic",
                           self._default_radius * 3)
        else:
            set_base_value(brush, "eraser", self._default_eraser)
            set_base_value(brush, "radius_logarithmic",
                           self._default_radius)

    def _cursor_changed_cb(self, xsheet):
        cut_action = self._main_window.lookup_action("cut")
        copy_action = self._main_window.lookup_action("copy")

        if cut_action.props.enabled and self._xsheet.get_cel() is None:
            cut_action.props.enabled = False
            copy_action.props.enabled = False
        elif not cut_action.props.enabled and self._xsheet.get_cel() is not None:
            cut_action.props.enabled = True
            copy_action.props.enabled = True