Exemple #1
0
    def test_brush_paint(self):
        """30s of painting at 4x with a charcoal brush"""
        s = tiledsurface.Surface()
        myb_path = join(paths.TESTS_DIR, 'brushes/v2/charcoal.myb')
        with open(myb_path, "r") as fp:
            bi = brush.BrushInfo(fp.read())
        b = brush.Brush(bi)

        events = np.loadtxt(join(paths.TESTS_DIR, 'painting30sec.dat'))

        bi.set_color_rgb((0.0, 0.9, 1.0))

        t0 = time()
        for i in range(10):
            t_old = events[0][0]
            for t, x, y, pressure in events:
                dtime = t - t_old
                t_old = t
                s.begin_atomic()
                b.stroke_to(
                    s.backend,
                    x * 4,
                    y * 4,
                    pressure,
                    0.0, 0.0,
                    dtime,
                    1.0,  # view zoom
                    0.0,  # view rotation
                )
                s.end_atomic()
        print('%0.4fs, ' % (time() - t0,), end="", file=sys.stderr)
        # FIXME: why is this time so different each run?
        # print(s.get_bbox(), b.get_total_stroke_painting_time())

        s.save_as_png('test_brushPaint.png')
Exemple #2
0
def brushPaint():

    s = tiledsurface.Surface()
    with open('brushes/charcoal.myb') as fp:
        bi = brush.BrushInfo(fp.read())
    b = brush.Brush(bi)

    events = np.loadtxt('painting30sec.dat')

    bi.set_color_rgb((0.0, 0.9, 1.0))

    t0 = time()
    for i in range(10):
        t_old = events[0][0]
        for t, x, y, pressure in events:
            dtime = t - t_old
            t_old = t
            s.begin_atomic()
            b.stroke_to(s.backend, x * 4, y * 4, pressure, 0.0, 0.0, dtime)
            s.end_atomic()
    print('Brushpaint time:', time() - t0)
    # FIXME: why is this time so different each run?
    print(s.get_bbox(), b.get_total_stroke_painting_time())

    s.save_as_png('test_brushPaint.png')
Exemple #3
0
    def reset(self):
        self.entry_pressure = np.min(self.pressures)

        if self.conditional:
            self.random_target = self.get_random_target(num=1, squeeze=True)
        else:
            self.random_target = None

        self.s = tiledsurface.Surface()
        self.s.flood_fill(0, 0, (255, 255, 255), (0, 0, 64, 64), 0, self.s)
        self.s.begin_atomic()

        with open(self.args.brush_path) as fp:
            self.bi = brush.BrushInfo(fp.read())
        self.b = brush.Brush(self.bi)

        self._step = 0
        self.s_x, self.s_y = None, None

        if self.args.conditional:
            self.z = None
        else:
            self.z = np.random.uniform(-0.5, 0.5, size=self.args.z_dim)

        return self.state, self.random_target, self.z
Exemple #4
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()
Exemple #5
0
def brushengine_paint_hires():
    from lib import tiledsurface, brush
    s = tiledsurface.Surface()
    with open('brushes/watercolor.myb') as fp:
        bi = brush.BrushInfo(fp.read())
    b = brush.Brush(bi)

    events = np.loadtxt('painting30sec.dat')
    t_old = events[0][0]
    yield start_measurement
    s.begin_atomic()
    trans_time = 0.0
    for t, x, y, pressure in events:
        dtime = t - t_old
        t_old = t
        b.stroke_to(s.backend, x * 5, y * 5, pressure, 0.0, 0.0, dtime)

        trans_time += dtime
        if trans_time > 0.05:
            trans_time = 0.0
            s.end_atomic()
            s.begin_atomic()

    s.end_atomic()
    yield stop_measurement
Exemple #6
0
def migrate_brushes_to_json(dirpath):

    files = os.listdir(dirpath)
    files = [
        os.path.join(dirpath, fn) for fn in files
        if os.path.splitext(fn)[1] == '.myb'
    ]

    for fpath in files:
        with open(fpath, 'r') as fp:
            b = brush.BrushInfo(fp.read())
        with open(fpath, 'w') as fp:
            fp.write(b.to_json())
Exemple #7
0
    def __init__(self):
        self.brush_info = brush.BrushInfo(
            open('tests/brushes/charcoal.myb').read())
        self.brush_info.set_color_rgb((0.0, 0.0, 0.0))

        self.brush = brush.Brush(self.brush_info)
        self.surface = tiledsurface.GeglSurface()
        self.display_node = self.surface.get_node()
        self.graph = Gegl.Node()

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

        self.init_ui()
Exemple #8
0
    def __init__(self, args):
        super(MNIST, self).__init__(args)
        self.mnist_nums = args.mnist_nums
        self.colorize = not args.train

        self.prepare_mnist()

        with open(args.brush_path) as fp:
            self.bi = brush.BrushInfo(fp.read())
        self.b = brush.Brush(self.bi)

        # jump
        self.jumps = [0, 1]

        # size
        self.sizes = np.arange(1, 0, -0.1)
        self.sizes = self.sizes * 1
        if 'size' in self.action_sizes:
            self.sizes = \
                    self.sizes[:self.action_sizes['size'][0]]

        # pressure
        self.pressures = np.arange(1.0, 0, -0.2)
        if 'pressure' in self.action_sizes:
            self.pressures = \
                    self.pressures[:self.action_sizes['pressure'][0]]

        self.entry_pressure = 0
        self.exit_pressure = self.pressures[-1]

        self.colors = [
            (0., 0., 0.),  # black
            (102., 217., 232.),  # cyan 3
            (173., 181., 189.),  # gray 5
            (255., 224., 102.),  # yellow 3
            (229., 153., 247.),  # grape 3
            (99., 230., 190.),  # teal 3
            (255., 192., 120.),  # orange 3
            (255., 168., 168.),  # red 3
        ]
        self.colors = np.array(self.colors) / 255.

        if 'color' in self.action_sizes:
            self.colors = self.colors[:self.action_sizes['color'][0]]

        self.controls = utils.uniform_locations(self.screen_size,
                                                self.location_size, 0)

        self.ends = utils.uniform_locations(self.screen_size,
                                            self.location_size, 0)
Exemple #9
0
    def reset(self):
        self.intermediate_images = []
        self.prev_x, self.prev_y, self.prev_pressure = None, None, None

        self.s = tiledsurface.Surface()
        self.s.flood_fill(0, 0, (255, 255, 255), (0, 0, 64, 64), 0, self.s)
        self.s.begin_atomic()

        with open(self.brush_path) as fp:
            self.bi = brush.BrushInfo(fp.read())
        self.b = brush.Brush(self.bi)

        # Two extra brushstrokes for MyPaint 2
        self.b.stroke_to(self.s.backend, 20, 20, 0.0, 0.0, 0.0, 0.1, 0, 0, 0)
        self.b.stroke_to(self.s.backend, 20, 20, 0.0, 0.0, 0.0, 0.1, 0, 0, 0)
        self.s.end_atomic()
        self.s.begin_atomic()
Exemple #10
0
    def __init__(self, args):
        super(MNIST, self).__init__(args)

        with open('assets/brushes/dry_brush.myb') as fp:
            self.bi = brush.BrushInfo(fp.read())
        self.b = brush.Brush(self.bi)

        self.background_color = (255, 255, 255)

        # jump
        self.jumps = [0, 1]

        # size
        self.sizes = np.arange(1, 0, -0.1)
        self.sizes = self.sizes * 1
        if 'size' in self.action_sizes:
            self.sizes = \
                    self.sizes[:self.action_sizes['size'][0]]

        # pressure
        self.pressures = np.arange(1.0, 0.9, -0.01)
        if 'pressure' in self.action_sizes:
            self.pressures = \
                    self.pressures[:self.action_sizes['pressure'][0]]

        self.colors = [
            (0., 0., 0.),  # black
            (102., 217., 232.),  # cyan 3
            (173., 181., 189.),  # gray 5
            (255., 224., 102.),  # yellow 3
        ]
        if 'color' in self.action_sizes:
            self.colors = self.colors[:self.action_sizes['color'][0]]
            self.colors = np.array(self.colors) / 255.

        self.controls = utils.uniform_locations(self.screen_size,
                                                self.location_size,
                                                0,
                                                normalize=True)
        self.controls /= self.location_size

        self.ends = utils.uniform_locations(self.screen_size,
                                            self.location_size, 0)
Exemple #11
0
    def __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.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.surface = None
        self.surface_node = None

        self.play_hid = None

        self.timeline = Timeline(10)
        self.update_surface()

        self.create_graph()
        self.init_ui()
Exemple #12
0
    def __init__(self, filenames, state_dirs, version, fullscreen=False):
        """Construct, but do not run.

        :param list filenames: The list of files to load (unicode required)
        :param StateDirs state_dirs: static special paths.
        :param unicode version: Version string for the about dialog.
        :param bool fullscreen: Go fullscreen after starting.

        Only the first filename listed will be loaded. If no files are
        listed, the autosave recovery dialog may be shown when the
        application starts up.

        """
        assert Application._INSTANCE is None
        super(Application, self).__init__()
        Application._INSTANCE = self

        self.state_dirs = state_dirs  #: Static special paths: see StateDirs

        self.version = version  #: version string for the app.

        # Create the user's config directory and any needed R/W data
        # storage areas.
        for basedir in [state_dirs.user_config, state_dirs.user_data]:
            if not os.path.isdir(basedir):
                os.makedirs(basedir)
                logger.info('Created basedir %r', basedir)
        for datasubdir in [u'backgrounds', u'brushes', u'scratchpads']:
            datadir = os.path.join(state_dirs.user_data, datasubdir)
            if not os.path.isdir(datadir):
                os.mkdir(datadir)
                logger.info('Created data subdir %r', datadir)

        _init_icons(state_dirs.app_icons)

        # Core actions and menu structure
        ui_dir = os.path.dirname(os.path.abspath(__file__))
        resources_xml = join(ui_dir, "resources.xml")
        self.builder = Gtk.Builder()
        self.builder.set_translation_domain("mypaint")
        self.builder.add_from_file(resources_xml)

        self.ui_manager = self.builder.get_object("app_ui_manager")
        signal_callback_objs = [self]

        Gdk.set_program_class('MyPaint')

        self.pixmaps = PixbufDirectory(join(state_dirs.app_data, u'pixmaps'))
        self.cursor_color_picker = Gdk.Cursor.new_from_pixbuf(
            Gdk.Display.get_default(),
            self.pixmaps.cursor_color_picker,
            3,
            15,
        )
        self.cursor_color_picker_h = Gdk.Cursor.new_from_pixbuf(
            Gdk.Display.get_default(),
            self.pixmaps.cursor_color_picker_h,
            3,
            15,
        )
        self.cursor_color_picker_c = Gdk.Cursor.new_from_pixbuf(
            Gdk.Display.get_default(),
            self.pixmaps.cursor_color_picker_c,
            3,
            15,
        )
        self.cursor_color_picker_y = Gdk.Cursor.new_from_pixbuf(
            Gdk.Display.get_default(),
            self.pixmaps.cursor_color_picker_y,
            3,
            15,
        )
        self.cursors = gui.cursor.CustomCursorMaker(self)

        # App-level settings
        self._preferences = lib.observable.ObservableDict()
        self.load_settings()

        # Unmanaged main brush.
        # Always the same instance (we can attach settings_observers).
        # This brush is where temporary changes (color, size...) happen.
        self.brush = brush.BrushInfo()
        self.brush.load_defaults()

        # Global pressure mapping function, ignored unless set
        self.pressure_mapping = None

        # Fake inputs to send when using a mouse.  Adjustable
        # via slider and/or hotkeys
        self.fakepressure = 0.5
        self.fakerotation = 0.5

        # Keyboard manager
        self.kbm = keyboard.KeyboardManager(self)

        # File I/O
        self.filehandler = filehandling.FileHandler(self)

        # Picking grabs
        self.context_grab = gui.picker.ContextPickingGrabPresenter()
        self.context_grab.app = self
        self.color_grab = gui.picker.ColorPickingGrabPresenter()
        self.color_grab.app = self

        # Load the main interface
        mypaint_main_xml = join(ui_dir, "mypaint.glade")
        self.builder.add_from_file(mypaint_main_xml)

        # Main drawing window
        self.drawWindow = self.builder.get_object("drawwindow")
        signal_callback_objs.append(self.drawWindow)

        # Workspace widget. Manages layout of toolwindows, and autohide in
        # fullscreen.
        wkspace = self.builder.get_object("app_workspace")
        wkspace.build_from_layout(self.preferences["workspace.layout"])
        wkspace.floating_window_created += self._floating_window_created_cb
        fs_autohide_action = self.builder.get_object("FullscreenAutohide")
        fs_autohide_action.set_active(wkspace.autohide_enabled)
        self.workspace = wkspace

        # Working document: viewer widget
        app_canvas = self.builder.get_object("app_canvas")

        # Working document: model and controller
        model = lib.document.Document(self.brush)
        self.doc = document.Document(self, app_canvas, model)
        app_canvas.set_model(model)

        signal_callback_objs.append(self.doc)
        signal_callback_objs.append(self.doc.modes)

        self.scratchpad_filename = ""
        scratchpad_model = lib.document.Document(self.brush,
                                                 painting_only=True)
        scratchpad_tdw = tileddrawwidget.TiledDrawWidget()
        scratchpad_tdw.scroll_on_allocate = False
        scratchpad_tdw.set_model(scratchpad_model)
        self.scratchpad_doc = document.Document(self, scratchpad_tdw,
                                                scratchpad_model)
        self.brushmanager = brushmanager.BrushManager(
            lib.config.mypaint_brushdir,
            join(self.state_dirs.user_data, 'brushes'),
            self,
        )
        signal_callback_objs.append(self.filehandler)
        self.brushmodifier = brushmodifier.BrushModifier(self)
        signal_callback_objs.append(self.brushmodifier)
        self.blendmodemanager = blendmodehandler.BlendModeManager(self)
        signal_callback_objs.append(self.blendmodemanager)
        self.blendmodemanager.register(self.brushmodifier.bm)

        self.line_mode_settings = linemode.LineModeSettings(self)

        # Button press mapping
        self.button_mapping = ButtonMapping()

        # Monitors pluggings and uses of input device, configures them,
        # and switches between device-specific brushes.
        self.device_monitor = gui.device.Monitor(self)

        if not self.preferences.get("scratchpad.last_opened_scratchpad", None):
            self.preferences["scratchpad.last_opened_scratchpad"] \
                = self.filehandler.get_scratchpad_autosave()
        self.scratchpad_filename \
            = self.preferences["scratchpad.last_opened_scratchpad"]

        self.brush_color_manager = BrushColorManager(self)
        self.brush_color_manager.set_picker_cursor(self.cursor_color_picker)
        self.brush_color_manager.set_data_path(self.datapath)

        #: Mapping of setting cname to a GtkAdjustment which controls the base
        #: value of that setting for the app's current brush.
        self.brush_adjustment = {}
        self.init_brush_adjustments()
        # Extend with some fake inputs that act kind of like brush settings
        self.fake_adjustment = {}

        # Connect signals defined in resources.xml
        callback_finder = CallbackFinder(signal_callback_objs)
        self.builder.connect_signals(callback_finder)

        self.kbm.start_listening()
        self.filehandler.doc = self.doc
        self.filehandler.filename = None
        Gtk.AccelMap.load(join(self.user_confpath, 'accelmap.conf'))

        # Load the default background image
        self.doc.reset_background()

        # Non-dockable subwindows
        # Loading is deferred as late as possible
        self._subwindow_classes = {
            # action-name: action-class
            "BackgroundWindow": backgroundwindow.BackgroundWindow,
            "BrushEditorWindow": brusheditor.BrushEditorWindow,
            "PreferencesWindow": preferenceswindow.PreferencesWindow,
            "InputTestWindow": inputtestwindow.InputTestWindow,
            "BrushIconEditorWindow": brushiconeditor.BrushIconEditorWindow,
        }
        self._subwindows = {}

        # Statusbar init
        statusbar = self.builder.get_object("app_statusbar")
        self.statusbar = statusbar
        context_id = statusbar.get_context_id("transient-message")
        self._transient_msg_context_id = context_id
        self._transient_msg_remove_timeout_id = None

        # Profiling & debug stuff
        self.profiler = gui.profiling.Profiler()

        # Show main UI.
        self.drawWindow.show_all()
        GLib.idle_add(self._at_application_start, filenames, fullscreen)
Exemple #13
0
    def test_docpaint(self):
        """Saved and reloaded documents look identical"""

        # TODO: brushes should be re-saved in the new JSON format
        with open(join(paths.TESTS_DIR, 'brushes/s008.myb')) as fp:
            b1 = brush.BrushInfo(fp.read())
        with open(join(paths.TESTS_DIR, 'brushes/redbrush.myb')) as fp:
            b2 = brush.BrushInfo(fp.read())
        with open(join(paths.TESTS_DIR, 'brushes/watercolor.myb')) as fp:
            b3 = brush.BrushInfo(fp.read())

        b = brush.BrushInfo()
        b.load_defaults()

        # test some actions
        doc = document.Document(b, painting_only=True)
        events = np.loadtxt(join(paths.TESTS_DIR, 'painting30sec.dat'))
        # events = events[:len(events) // 8]
        t_old = events[0][0]
        n = len(events)
        for i, (t, x, y, pressure) in enumerate(events):
            dtime = t - t_old
            t_old = t

            layer = doc.layer_stack.current
            layer.stroke_to(
                doc.brush,
                x / 4,
                y / 4,
                pressure,
                0.0,
                0.0,
                dtime,
                1.0,  # view zoom
                0.0,  # view rotation
            )

            # Vary the colour so we know roughly where we are

            # Transition from one brush to another and occasionally make
            # some new layers.
            if i == 0:
                b.load_from_brushinfo(b1)
                hsv = (i / n, 1.0, 1.0)
                b.set_color_hsv(hsv)
            if i == int(n * 1 / 6):
                b.load_from_brushinfo(b2)
                hsv = (i / n, 1.0, 1.0)
                b.set_color_hsv(hsv)
            if i == int(n * 2 / 6):
                b.load_from_brushinfo(b3)
                hsv = (i / n, 1.0, 1.0)
                b.set_color_hsv(hsv)

            if i == int(n * 3 / 6):
                doc.add_layer([-1])
                b.load_from_brushinfo(b1)
                hsv = (i / n, 1.0, 1.0)
                b.set_color_hsv(hsv)
            if i == int(n * 4 / 6):
                b.load_from_brushinfo(b2)
                hsv = (i / n, 1.0, 1.0)
                b.set_color_hsv(hsv)
            if i == int(n * 5 / 6):
                b.load_from_brushinfo(b3)
                hsv = (i / n, 1.0, 1.0)
                b.set_color_hsv(hsv)

        # If there is an eraser (or smudging) at work, we might be
        # erasing tiles that are empty. Those tile get memory allocated
        # and affect the bounding box of the layer. This shouldn't be a
        # big issue, but they get dropped when loading a document, which
        # makes a comparision of the PNG files fail. The hack below is
        # to avoid that.
        for i, (path, layer) in enumerate(doc.layer_stack.walk()):
            layer._surface.remove_empty_tiles()

            png1a = 'test_doc1_layer%da.png' % (i, )
            png1b = 'test_doc1_layer%db.png' % (i, )
            layer.save_as_png(png1a)
            layer.save_as_png(png1b)

            # the resulting images will look slightly different because of
            # dithering
            self.assert_pngs_equal(png1a, png1b)

        # Whole doc save and load
        doc.save('test_doc1.ora')

        doc2 = document.Document()
        doc2.load('test_doc1.ora')

        # (We don't preserve the absolute position of the image, only
        # the size.)
        # assert doc.get_bbox() == doc2.get_bbox()

        # print('doc / doc2 bbox:', doc.get_bbox(), doc2.get_bbox())

        for i, (path, layer) in enumerate(doc2.layer_stack.walk()):
            png1a = 'test_doc1_layer%da.png' % (i, )
            png2a = 'test_doc2_layer%da.png' % (i, )
            png2b = 'test_doc2_layer%db.png' % (i, )
            layer.save_as_png(png2a)
            layer.save_as_png(png2b)
            self.assert_pngs_equal(png2a, png2b)
            self.assert_pngs_equal(png2a, png1a)

        doc2.save('test_doc2.ora')

        # check not possible, because PNGs not exactly equal:-
        # assert files_equal('test_f1.ora', 'test_f2.ora')

        # less strict test than above (just require load-save-load-save
        # not to alter the file)
        doc3 = document.Document()
        doc3.load('test_doc2.ora')
        self.assertTrue(doc2.get_bbox() == doc3.get_bbox())

        # check not possible, because PNGs not exactly equal:-
        # assert files_equal('test_f2.ora', 'test_f3.ora')

        # note: this is not supposed to be strictly reproducible because
        # of different random seeds [huh? what does that mean?]
        # bbox = doc.get_bbox()
        # print('document bbox is', bbox)

        # test for appearance changes (make sure they are intended)
        doc.save('test_docPaint_flat.png', alpha=False)
        doc.save('test_docPaint_alpha.png', alpha=True)
        self.assert_pngs_equal(
            'test_docPaint_flat.png',
            join(paths.TESTS_DIR, 'correct_docPaint_flat.png'),
        )
        self.assert_pngs_equal(
            'test_docPaint_alpha.png',
            join(paths.TESTS_DIR, 'correct_docPaint_alpha.png'),
        )
    def __init__(self,
                 logger=None,
                 imsize=64,
                 bg_color=None,
                 max_episode_steps=10,
                 pos_resolution=32,
                 brush_info_file=None,
                 start_x=0):
        super().__init__()

        # TODO: use EnvSpec?

        # length of a sequence
        self.tags = {'max_episode_steps': max_episode_steps}

        self.logger = logger or logging.getLogger(__name__)

        # starting positing of the pen for each episode
        self.tile_offset = mypaintlib.TILE_SIZE
        self.start_x = start_x
        self.start_color = (0, 0, 0)  # initial color of the brush
        self.imsize = imsize
        self.pos_resolution = pos_resolution

        # action space
        self.action_space = spaces.Dict({
            'position':
            spaces.Discrete(self.pos_resolution**2),
            'pressure':
            spaces.Box(low=0, high=1.0, shape=(), dtype=float),
            'color':
            spaces.Box(low=0, high=1.0, shape=(3, ), dtype=float),
            'prob':
            spaces.Discrete(2)
        })

        # observation space
        self.observation_space = spaces.Dict({
            'image':
            spaces.Box(low=0,
                       high=255,
                       shape=(self.imsize, self.imsize, 3),
                       dtype=np.uint8),
            'position':
            spaces.Discrete(self.pos_resolution**2),
            'pressure':
            spaces.Box(low=0, high=1.0, shape=(), dtype=float),
            'color':
            spaces.Box(low=0, high=1.0, shape=(3, ), dtype=float),
            'prob':
            spaces.Discrete(1)
        })

        # color of the background
        if bg_color is None:
            self.bg_color = (1, 1, 1)
        else:
            self.bg_color = bg_color

        # open brush
        if brush_info_file is None:
            brush_info_file = os.getenv('BRUSHINFO')
            if brush_info_file is None:
                raise ValueError('You need to specify brush file')

        self.logger.debug('Open brush info from %s', brush_info_file)

        with open(brush_info_file, 'r') as f:
            self.brush_info = brush.BrushInfo(f.read())
        self.brush = brush.Brush(self.brush_info)

        # initialize canvas (surface)
        self.surface = tiledsurface.Surface()

        # reset canvas and set current position of the pen
        self.reset()
Exemple #15
0
 def get_brush(brush_path):
     with open(brush_path) as fp:
         return mypaint_brush.Brush(mypaint_brush.BrushInfo(fp.read()))
Exemple #16
0
    def __init__(self,
                 filenames,
                 app_datapath,
                 app_extradatapath,
                 user_datapath,
                 user_confpath,
                 version,
                 fullscreen=False):
        """Construct, but do not run.

        :params filenames: The list of files to load.
          Note: only the first is used.
        :param app_datapath: App-specific read-only data area.
          Path used for UI definition XML, and the default sets of backgrounds,
          palettes, and brush defintions. Often $PREFIX/share/.
        :param app_extradatapath: Extra search path for themeable UI icons.
          This will be used in addition to $XDG_DATA_DIRS for the purposes of
          icon lookup. Normally it's $PREFIX/share, to support unusual
          installations outside the usual locations. It should contain an
          icons/ subdirectory.
        :param user_datapath: Location of the user's app-specific data.
          For MyPaint, this means the user's brushes, backgrounds, and
          scratchpads. Commonly $XDG_DATA_HOME/mypaint, i.e.
          ~/.local/share/mypaint
        :param user_confpath: Location of the user's app-specific config area.
          This is where MyPaint will save user preferences data and the
          keyboard accelerator map. Commonly $XDG_CONFIG_HOME/mypaint, i.e.
          ~/.config/mypaint
        :param version: Version string for the about dialog.
        :param fullscreen: Go fullscreen after starting.

        """
        assert Application._INSTANCE is None
        super(Application, self).__init__()
        Application._INSTANCE = self

        self.user_confpath = user_confpath  #: User configs (see __init__)
        self.user_datapath = user_datapath  #: User data (see __init__)

        self.datapath = app_datapath

        self.version = version  #: version string for the app.

        # create config directory, and subdirs where the user might drop files
        for basedir in [self.user_confpath, self.user_datapath]:
            if not os.path.isdir(basedir):
                os.mkdir(basedir)
                logger.info('Created basedir %r', basedir)
        for datasubdir in ['backgrounds', 'brushes', 'scratchpads']:
            datadir = os.path.join(self.user_datapath, datasubdir)
            if not os.path.isdir(datadir):
                os.mkdir(datadir)
                logger.info('Created data subdir %r', datadir)

        _init_icons(join(app_extradatapath, "icons"))

        # Core actions and menu structure
        resources_xml = join(self.datapath, "gui", "resources.xml")
        self.builder = Gtk.Builder()
        self.builder.set_translation_domain("mypaint")
        self.builder.add_from_file(resources_xml)

        self.ui_manager = self.builder.get_object("app_ui_manager")
        signal_callback_objs = [self]

        Gdk.set_program_class('MyPaint')

        self.pixmaps = PixbufDirectory(join(self.datapath, 'pixmaps'))
        self.cursor_color_picker = Gdk.Cursor.new_from_pixbuf(
            Gdk.Display.get_default(),
            self.pixmaps.cursor_color_picker,
            3,
            15,
        )
        self.cursors = gui.cursor.CustomCursorMaker(self)

        # unmanaged main brush; always the same instance (we can attach settings_observers)
        # this brush is where temporary changes (color, size...) happen
        self.brush = brush.BrushInfo()
        self.brush.load_defaults()

        # Global pressure mapping function, ignored unless set
        self.pressure_mapping = None

        self.preferences = {}
        self.load_settings()

        # Keyboard manager
        self.kbm = keyboard.KeyboardManager(self)

        # File I/O
        self.filehandler = filehandling.FileHandler(self)

        # Picking grabs
        self.context_grab = gui.picker.ContextPickingGrabPresenter()
        self.context_grab.app = self
        self.color_grab = gui.picker.ColorPickingGrabPresenter()
        self.color_grab.app = self

        # Load the main interface
        mypaint_main_xml = join(self.datapath, "gui", "mypaint.glade")
        self.builder.add_from_file(mypaint_main_xml)

        # Main drawing window
        self.drawWindow = self.builder.get_object("drawwindow")
        signal_callback_objs.append(self.drawWindow)

        # Workspace widget. Manages layout of toolwindows, and autohide in
        # fullscreen.
        workspace = self.builder.get_object("app_workspace")
        workspace.build_from_layout(self.preferences["workspace.layout"])
        workspace.floating_window_created += self._floating_window_created_cb
        fs_autohide_action = self.builder.get_object("FullscreenAutohide")
        fs_autohide_action.set_active(workspace.autohide_enabled)
        self.workspace = workspace

        # Working document: viewer widget
        app_canvas = self.builder.get_object("app_canvas")

        # Working document: model and controller
        model = lib.document.Document(self.brush)
        self.doc = document.Document(self, app_canvas, model)
        app_canvas.set_model(model)

        signal_callback_objs.append(self.doc)
        signal_callback_objs.append(self.doc.modes)

        self.scratchpad_filename = ""
        scratchpad_model = lib.document.Document(self.brush,
                                                 painting_only=True)
        scratchpad_tdw = tileddrawwidget.TiledDrawWidget()
        scratchpad_tdw.set_model(scratchpad_model)
        self.scratchpad_doc = document.Document(self, scratchpad_tdw,
                                                scratchpad_model)
        self.brushmanager = brushmanager.BrushManager(
            join(app_datapath, 'brushes'), join(user_datapath, 'brushes'),
            self)
        signal_callback_objs.append(self.filehandler)
        self.brushmodifier = brushmodifier.BrushModifier(self)
        signal_callback_objs.append(self.brushmodifier)
        self.line_mode_settings = linemode.LineModeSettings(self)

        # Button press mapping
        self.button_mapping = ButtonMapping()

        # Monitors pluggings and uses of input device, configures them,
        # and switches between device-specific brushes.
        self.device_monitor = gui.device.Monitor(self)

        if not self.preferences.get("scratchpad.last_opened_scratchpad", None):
            self.preferences[
                "scratchpad.last_opened_scratchpad"] = self.filehandler.get_scratchpad_autosave(
                )
        self.scratchpad_filename = self.preferences[
            "scratchpad.last_opened_scratchpad"]

        self.brush_color_manager = BrushColorManager(self)
        self.brush_color_manager.set_picker_cursor(self.cursor_color_picker)
        self.brush_color_manager.set_data_path(self.datapath)

        #: Mapping of setting cname to a GtkAdjustment which controls the base
        #: value of that setting for the app's current brush.
        self.brush_adjustment = {}
        self.init_brush_adjustments()

        # Connect signals defined in mypaint.xml
        callback_finder = CallbackFinder(signal_callback_objs)
        self.builder.connect_signals(callback_finder)

        self.kbm.start_listening()
        self.filehandler.doc = self.doc
        self.filehandler.filename = None
        Gtk.AccelMap.load(join(self.user_confpath, 'accelmap.conf'))

        # Load the default background image if one exists
        layer_stack = self.doc.model.layer_stack
        inited_background = False
        for datapath in [self.user_datapath, self.datapath]:
            bg_path = join(datapath, backgroundwindow.BACKGROUNDS_SUBDIR,
                           backgroundwindow.DEFAULT_BACKGROUND)
            if not os.path.exists(bg_path):
                continue
            bg, errors = backgroundwindow.load_background(bg_path)
            if bg:
                layer_stack.set_background(bg, make_default=True)
                inited_background = True
                logger.info("Initialized background from %r", bg_path)
                break
            else:
                logger.warning(
                    "Failed to load user's default background image %r",
                    bg_path,
                )
                if errors:
                    for error in errors:
                        logger.warning("warning: %r", error)

        # Otherwise, try to use a sensible fallback background image.
        if not inited_background:
            bg_path = join(self.datapath, backgroundwindow.BACKGROUNDS_SUBDIR,
                           backgroundwindow.FALLBACK_BACKGROUND)
            bg, errors = backgroundwindow.load_background(bg_path)
            if bg:
                layer_stack.set_background(bg, make_default=True)
                inited_background = True
                logger.info("Initialized background from %r", bg_path)
            else:
                logger.warning(
                    "Failed to load fallback background image %r",
                    bg_path,
                )
                if errors:
                    for error in errors:
                        logger.warning("warning: %r", error)

        # Double fallback. Just use a color.
        if not inited_background:
            bg_color = (0xa8, 0xa4, 0x98)
            layer_stack.set_background(bg_color, make_default=True)
            logger.info("Initialized background to %r", bg_color)
            inited_background = True

        # Non-dockable subwindows
        # Loading is deferred as late as possible
        self._subwindow_classes = {
            # action-name: action-class
            "BackgroundWindow": backgroundwindow.BackgroundWindow,
            "BrushEditorWindow": brusheditor.BrushEditorWindow,
            "PreferencesWindow": preferenceswindow.PreferencesWindow,
            "InputTestWindow": inputtestwindow.InputTestWindow,
            "BrushIconEditorWindow": brushiconeditor.BrushIconEditorWindow,
        }
        self._subwindows = {}

        # Statusbar init
        statusbar = self.builder.get_object("app_statusbar")
        self.statusbar = statusbar
        context_id = statusbar.get_context_id("transient-message")
        self._transient_msg_context_id = context_id
        self._transient_msg_remove_timeout_id = None

        # Show main UI.
        self.drawWindow.show_all()
        GObject.idle_add(self._at_application_start, filenames, fullscreen)
Exemple #17
0
def docPaint():
    b1 = brush.BrushInfo(open('brushes/s008.myb').read())
    b2 = brush.BrushInfo(open('brushes/redbrush.myb').read())
    b2.set_color_hsv((0.3, 0.4, 0.35))
    b3 = brush.BrushInfo(open('brushes/watercolor.myb').read())
    b3.set_color_hsv((0.9, 0.2, 0.2))

    b = brush.BrushInfo()
    b.load_defaults()

    # test some actions
    doc = document.Document(b)
    doc.undo()  # nop
    events = np.loadtxt('painting30sec.dat')
    events = events[:len(events) / 8]
    t_old = events[0][0]
    n = len(events)
    layer = doc.layer_stack.current
    for i, (t, x, y, pressure) in enumerate(events):
        dtime = t - t_old
        t_old = t
        #print dtime
        layer.stroke_to(doc.brush, x, y, pressure, 0.0, 0.0, dtime)
        if i == n * 1 / 8:
            b.load_from_brushinfo(b2)
        if i == n * 2 / 8:
            doc.clear_layer()
            doc.undo()
            assert not doc.get_bbox().empty()
            doc.redo()
            assert doc.get_bbox().empty()
        if i == n * 3 / 8:
            doc.undo()
            b.load_from_brushinfo(b3)
        if i == n * 4 / 8:
            b.load_from_brushinfo(b2)
        if i == n * 5 / 8:
            doc.undo()
            doc.redo()
        if i == n * 6 / 8:
            b.load_from_brushinfo(b2)
        if i == n * 7 / 8:
            doc.add_layer(1)

    # If there is an eraser (or smudging) at work, we might be erasing
    # tiles that are empty. Those tile get memory allocated and affect
    # the bounding box of the layer. This shouldn't be a big issue, but
    # they get dropped when loading a document, which makes a
    # comparision of the PNG files fail. The hack below is to avoid that.
    for l in doc.layer_stack:
        l._surface.remove_empty_tiles()

    doc.layer_stack[0].save_as_png('test_docPaint_a.png')
    doc.layer_stack[0].save_as_png('test_docPaint_a1.png')
    # the resulting images will look slightly different because of dithering
    assert pngs_equal('test_docPaint_a.png', 'test_docPaint_a1.png')

    # test save/load
    doc.save('test_f1.ora')
    doc2 = document.Document()
    doc2.load('test_f1.ora')

    # (We don't preserve the absolute position of the image, only the size.)
    #assert doc.get_bbox() == doc2.get_bbox()
    print('doc / doc2 bbox:', doc.get_bbox(), doc2.get_bbox())

    doc2.layer_stack[0].save_as_png('test_docPaint_b.png')
    assert pngs_equal('test_docPaint_a.png', 'test_docPaint_b.png')
    doc2.save('test_f2.ora')
    #check not possible, because PNGs not exactly equal:
    #assert files_equal('test_f1.ora', 'test_f2.ora')

    # less strict test than above (just require load-save-load-save not to alter the file)
    doc3 = document.Document()
    doc3.load('test_f2.ora')
    assert doc2.get_bbox() == doc3.get_bbox()
    doc3.layer_stack[0].save_as_png('test_docPaint_c.png')
    assert pngs_equal('test_docPaint_b.png', 'test_docPaint_c.png')
    doc2.save('test_f3.ora')
    #check not possible, because PNGs not exactly equal:
    #assert files_equal('test_f2.ora', 'test_f3.ora')

    # note: this is not supposed to be strictly reproducible because
    # of different random seeds [huh? what does that mean?]
    bbox = doc.get_bbox()
    print('document bbox is', bbox)

    # test for appearance changes (make sure they are intended)
    doc.save('test_docPaint_flat.png', alpha=False)
    doc.save('test_docPaint_alpha.png', alpha=True)
    assert pngs_equal('test_docPaint_flat.png', 'correct_docPaint_flat.png')
    assert pngs_equal('test_docPaint_alpha.png', 'correct_docPaint_alpha.png')
Exemple #18
0
    def __init__(self,
                 filenames,
                 app_datapath,
                 app_extradatapath,
                 user_datapath,
                 user_confpath,
                 version,
                 fullscreen=False):
        """Construct, but do not run.

        :params filenames: The list of files to load.
          Note: only the first is used.
        :param app_datapath: App-specific read-only data area.
          Path used for UI definition XML, and the default sets of backgrounds,
          palettes, and brush defintions. Often $PREFIX/share/.
        :param app_extradatapath: Extra search path for themeable UI icons.
          This will be used in addition to $XDG_DATA_DIRS for the purposes of
          icon lookup. Normally it's $PREFIX/share, to support unusual
          installations outside the usual locations. It should contain an
          icons/ subdirectory.
        :param user_datapath: Location of the user's app-specific data.
          For MyPaint, this means the user's brushes, backgrounds, and
          scratchpads. Commonly $XDG_DATA_HOME/mypaint, i.e.
          ~/.local/share/mypaint
        :param user_confpath: Location of the user's app-specific config area.
          This is where MyPaint will save user preferences data and the
          keyboard accelerator map. Commonly $XDG_CONFIG_HOME/mypaint, i.e.
          ~/.config/mypaint
        :param version: Version string for the about dialog.
        :param fullscreen: Go fullscreen after starting.

        """
        assert Application._INSTANCE is None
        super(Application, self).__init__()
        Application._INSTANCE = self

        self.user_confpath = user_confpath  #: User configs (see __init__)
        self.user_datapath = user_datapath  #: User data (see __init__)

        self.datapath = app_datapath

        self.version = version  #: version string for the app.

        # create config directory, and subdirs where the user might drop files
        for basedir in [self.user_confpath, self.user_datapath]:
            if not os.path.isdir(basedir):
                os.mkdir(basedir)
                logger.info('Created basedir %r', basedir)
        for datasubdir in ['backgrounds', 'brushes', 'scratchpads']:
            datadir = os.path.join(self.user_datapath, datasubdir)
            if not os.path.isdir(datadir):
                os.mkdir(datadir)
                logger.info('Created data subdir %r', datadir)

        # Default location for our icons. The user's theme can override these.
        icon_theme = gtk.icon_theme_get_default()
        icon_theme.append_search_path(join(app_extradatapath, "icons"))

        # Icon sanity check
        if not icon_theme.has_icon('mypaint') \
                or not icon_theme.has_icon('mypaint-tool-brush'):
            logger.error('Error: Where have my icons gone?')
            logger.error('Icon search path: %r', icon_theme.get_search_path())
            logger.error("Mypaint can't run sensibly without its icons; "
                         "please check your installation. See "
                         "https://gna.org/bugs/?18460 for possible solutions")
            sys.exit(1)

        gtk.Window.set_default_icon_name('mypaint')

        # Stock items, core actions, and menu structure
        resources_xml = join(self.datapath, "gui", "resources.xml")
        self.builder = gtk.Builder()
        self.builder.set_translation_domain("mypaint")
        self.builder.add_from_file(resources_xml)
        factory = self.builder.get_object("stock_icon_factory")
        factory.add_default()

        self.ui_manager = self.builder.get_object("app_ui_manager")
        signal_callback_objs = []

        gdk.set_program_class('MyPaint')

        self.pixmaps = PixbufDirectory(join(self.datapath, 'pixmaps'))
        self.cursor_color_picker = gdk.Cursor(
            gtk2compat.gdk.display_get_default(),
            self.pixmaps.cursor_color_picker, 1, 30)
        self.cursors = CursorCache(self)

        # unmanaged main brush; always the same instance (we can attach settings_observers)
        # this brush is where temporary changes (color, size...) happen
        self.brush = brush.BrushInfo()
        self.brush.load_defaults()

        # Global pressure mapping function, ignored unless set
        self.pressure_mapping = None

        self.preferences = {}
        self.load_settings()

        # Keyboard manager
        self.kbm = keyboard.KeyboardManager(self)

        # File I/O
        self.filehandler = filehandling.FileHandler(self)

        # Load the main interface
        mypaint_main_xml = join(self.datapath, "gui", "mypaint.glade")
        self.builder.add_from_file(mypaint_main_xml)

        # Main drawing window
        self.drawWindow = self.builder.get_object("drawwindow")
        signal_callback_objs.append(self.drawWindow)

        # Workspace widget. Manages layout of toolwindows, and autohide in
        # fullscreen.
        workspace = self.builder.get_object("app_workspace")
        workspace.build_from_layout(self.preferences["workspace.layout"])
        workspace.floating_window_created += self._floating_window_created_cb
        fs_autohide_action = self.builder.get_object("FullscreenAutohide")
        fs_autohide_action.set_active(workspace.autohide_enabled)
        self.workspace = workspace

        # Working document: viewer widget
        app_canvas = self.builder.get_object("app_canvas")

        # Working document: model and controller
        model = lib.document.Document(self.brush)
        self.doc = document.Document(self, app_canvas, model)
        app_canvas.set_model(model)

        signal_callback_objs.append(self.doc)
        signal_callback_objs.append(self.doc.modes)

        self.scratchpad_filename = ""
        scratchpad_model = lib.document.Document(self.brush)
        scratchpad_tdw = tileddrawwidget.TiledDrawWidget()
        scratchpad_tdw.set_model(scratchpad_model)
        self.scratchpad_doc = document.Document(self,
                                                scratchpad_tdw,
                                                scratchpad_model,
                                                leader=self.doc)
        self.brushmanager = brushmanager.BrushManager(
            join(app_datapath, 'brushes'), join(user_datapath, 'brushes'),
            self)
        signal_callback_objs.append(self.filehandler)
        self.brushmodifier = brushmodifier.BrushModifier(self)
        self.line_mode_settings = linemode.LineModeSettings(self)

        # Button press mapping
        self.button_mapping = ButtonMapping()

        # Monitors changes of input device & saves device-specific brushes
        self.device_monitor = DeviceUseMonitor(self)

        if not self.preferences.get("scratchpad.last_opened_scratchpad", None):
            self.preferences[
                "scratchpad.last_opened_scratchpad"] = self.filehandler.get_scratchpad_autosave(
                )
        self.scratchpad_filename = self.preferences[
            "scratchpad.last_opened_scratchpad"]

        self.brush_color_manager = BrushColorManager(self)
        self.brush_color_manager.set_picker_cursor(self.cursor_color_picker)
        self.brush_color_manager.set_data_path(self.datapath)

        #: Mapping of setting cname to a GtkAdjustment which controls the base
        #: value of that setting for the app's current brush.
        self.brush_adjustment = {}
        self.init_brush_adjustments()

        # Connect signals defined in mypaint.xml
        callback_finder = CallbackFinder(signal_callback_objs)
        self.builder.connect_signals(callback_finder)

        self.kbm.start_listening()
        self.filehandler.doc = self.doc
        self.filehandler.filename = None
        gtk2compat.gtk.accel_map_load(join(self.user_confpath,
                                           'accelmap.conf'))

        # Load the default background
        for datapath in [self.user_datapath, self.datapath]:
            bg_path = join(datapath, backgroundwindow.BACKGROUNDS_SUBDIR,
                           backgroundwindow.DEFAULT_BACKGROUND)
            if not os.path.exists(bg_path):
                continue
            bg, errors = backgroundwindow.load_background(bg_path)
            if bg:
                self.doc.model.set_background(bg, make_default=True)
                break
            else:
                logger.warning("Failed to load default background image %r",
                               bg_path)
                if errors:
                    for error in errors:
                        logger.warning("warning: %r", error)

        # Non-dockable subwindows
        # Loading is deferred as late as possible
        self._subwindow_classes = {
            # action-name: action-class
            "BackgroundWindow": backgroundwindow.BackgroundWindow,
            "BrushEditorWindow": brusheditor.BrushEditorWindow,
            "PreferencesWindow": preferenceswindow.PreferencesWindow,
            "InputTestWindow": inputtestwindow.InputTestWindow,
            "BrushIconEditorWindow": brushiconeditor.BrushIconEditorWindow,
        }
        self._subwindows = {}

        # Show main UI.
        self.drawWindow.show_all()
        gobject.idle_add(self._at_application_start, filenames, fullscreen)