diff options
Diffstat (limited to 'colors.py')
-rwxr-xr-x | colors.py | 199 |
1 files changed, 103 insertions, 96 deletions
@@ -32,6 +32,12 @@ import logging, os, sys, math, time, copy, json, tempfile from gettext import gettext as _ +try: + import json + json.dumps +except (ImportError, AttributeError): + import simplejson as json + # Import the C++ component of the activity. from colorsc import * @@ -40,12 +46,8 @@ import gobject, pygtk, gtk, pango # Needed to avoid thread crashes with GStreamer gobject.threads_init() -sys.path.insert(0,"") -# Import PyGame. Used for a few realtime aspects. +# Import PyGame. Used for camera and sound. import pygame -from pygame import camera -from pygame import transform -from pygame import mask # Import DBUS and mesh networking modules. import dbus, telepathy, telepathy.client @@ -54,12 +56,14 @@ from dbus.service import method, signal from dbus.gobject_service import ExportedGObject from sugar.presence.tubeconn import TubeConnection from sugar.presence import presenceservice +from sugar.datastore import datastore # Import Sugar UI modules. from sugar import graphics from sugar.activity import activity from sugar.graphics import * from sugar.graphics import toggletoolbutton +from sugar.graphics.menuitem import MenuItem # Import GStreamer (for camera access). #import pygst, gst @@ -84,14 +88,14 @@ DBUS_SERVICE = DBUS_IFACE # The color wheel and triangle are rendered into GdkImage objects by C++ code in palette.h / palette.cpp which # is compiled into the colorsc module. class BrushControlsPanel(gtk.HBox): - PALETTE_SIZE = 500 - PREVIEW_SIZE = 250 - BRUSHTYPE_SIZE = 100 + PALETTE_SIZE = int(7.0 * style.GRID_CELL_SIZE) & ~1 + PREVIEW_SIZE = int(2.5 * style.GRID_CELL_SIZE) & ~1 + BRUSHTYPE_SIZE = int(1.0 * style.GRID_CELL_SIZE) & ~1 def __init__ (self): gtk.HBox.__init__(self) - self.set_property("spacing", 10) - self.set_border_width(50) + self.set_property("spacing", 5) + self.set_border_width(30) # Locally managed Brush object. self.brush = Brush() @@ -164,8 +168,8 @@ class BrushControlsPanel(gtk.HBox): brushbox.pack_start(brushtbl, False) - self.pack_start(sizebox, True, False) - self.pack_start(opacitybox, True, False) + self.pack_start(sizebox, False, False) + self.pack_start(opacitybox, False, False) self.pack_start(palbox, True, False) self.pack_start(brushbox, False) @@ -388,6 +392,11 @@ class Colors(activity.Activity, ExportedGObject): self.update_timer = None self.update() + # store event.get_axis() of last event to ignore fake pressure + # when system doesnt support gtk.gdk.AXIS_PRESSURE but + # event.get_axis(gtk.gdk.AXIS_PRESSURE) returns 0.0 value + self._prev_AXIS_PRESSURE = None + #----------------------------------------------------------------------------------------------------------------- # User interface construction @@ -588,7 +597,7 @@ class Colors(activity.Activity, ExportedGObject): samples = [] fd = open(activity.get_bundle_path() + '/data/INDEX', 'r') try: - samples = json.read(fd.read()) + samples = json.loads(fd.read()) finally: fd.close() @@ -611,41 +620,37 @@ class Colors(activity.Activity, ExportedGObject): toolbar.add_toolbar(_("Learn"),samplebox) toolbar.show_all() self.set_toolbox(toolbar) + + # Add Keep As button to activity toolbar. + activity_toolbar = toolbar.get_activity_toolbar() + keep_palette = activity_toolbar.keep.get_palette() + menu_item = MenuItem(_('Keep to PNG')) + menu_item.connect('activate', self.on_export_png) + keep_palette.menu.append(menu_item) + menu_item.show() #----------------------------------------------------------------------------------------------------------------- # Camera access # - # todo- Consider going straight to video4linux2's C API, we aren't really using any features of GStreamer. + # The new camera module from Pygame, by Nirav Patel, is used for camera access. + # It was only recently added, so we have to handle the case where the module doesn't exist. def init_camera (self): - self.cam = camera.Camera("/dev/video0",(320,240),"RGB") - self.camcapture = pygame.surface.Surface((320,240),0,16,(63488,2016,31,0)) - self.camsmall = pygame.surface.Surface((160,120),0,self.camcapture) - self.camhsv = pygame.surface.Surface((160,120),0,self.camcapture) - - # self.gstpipe = gst.parse_launch("v4l2src ! fakesink name=fakesink signal-handoffs=true") - # self.gstfakesink = self.gstpipe.get_by_name('fakesink') - # self.gstfakesink.connect("handoff", self.on_gst_buffer) - # self.easelarea.connect("destroy", self.on_gst_destroy) - # - # self.gstpipe.set_state(gst.STATE_PLAYING) - # - #def on_gst_buffer(self, element, buffer, pad): - # if self.take_reference: - # self.easel.set_reference_buffer(buffer, 640, 480) - # # Bring up the reference screen and update. - # self.showrefbtn.set_active(True) - # self.easel.render_reference_overlay() - # self.flush_entire_canvas() - # self.take_reference = False - # - # if self.videopaint_enabled: - # self.easel.videopaint_motion(buffer, 640, 480) - - # todo- This isn't working right, for some reason the GST pipe keeps playing after the window is destroyed, leading - # to Python exceptions in on_gst_buffer. - #def on_gst_destroy (self, widget): - # self.gstpipe.set_state(gst.STATE_NULL) + self.camera_enabled = False + + try: + camera_list = pygame.camera.list_cameras() + if len(camera_list): + self.cam = pygame.camera.Camera(camera_list[0],(320,240),"RGB") + self.camcapture = pygame.surface.Surface((320,240),0,16,(63488,2016,31,0)) + self.camsmall = pygame.surface.Surface((160,120),0,self.camcapture) + self.camhsv = pygame.surface.Surface((160,120),0,self.camcapture) + self.camera_enabled = True + else: + log.debug('No cameras found, videopaint disabled.') + + except AttributeError: + pass #----------------------------------------------------------------------------------------------------------------- # Mesh networking @@ -990,7 +995,9 @@ class Colors(activity.Activity, ExportedGObject): # Read pressure information if available. pressure = event.get_axis(gtk.gdk.AXIS_PRESSURE) - if pressure != None: + if pressure or self._prev_AXIS_PRESSURE: + pressure = min(pressure, 1.0) + self._prev_AXIS_PRESSURE = pressure self.pressure = int(pressure * 255) else: self.pressure = 255 @@ -1359,7 +1366,7 @@ class Colors(activity.Activity, ExportedGObject): # Load and play intro movie. It was created on a DS at 60hz, so we need to speed it up drastically to # make it watchable. self.easel.clear() - self.easel.load(activity.get_bundle_path() + "/data/intro.drw") + self.easel.load(str(activity.get_bundle_path() + "/data/intro.drw")) self.easel.set_playback_speed(8) self.easel.start_playback() self.start_update_timer() @@ -1672,57 +1679,20 @@ class Colors(activity.Activity, ExportedGObject): # self.set_mode(Colors.MODE_CANVAS) def on_videopaint (self, button): - self.videopaint_enabled = button.get_active() - if button.get_active(): - self.cam.start() - # flips the image to start with - self.cam.set_controls(hflip = 1) - gobject.timeout_add(33, self.on_videopaint_tick) - else: - self.cam.stop() - - - #def on_videopaint (self, button): - # self.videopaint_enabled = button.get_active() - # if button.get_active(): - # gobject.timeout_add(33, self.on_videopaint_tick) - - #def on_videopaintpreview_expose (self, widget, event): - # self.easel.blit_videopaint(self.videopaintimage) - # size = self.videopaintpreview.window.get_size() - # x = (size[0]-self.easel.VIDEO_WIDTH)/2 - # y = (size[1]-self.easel.VIDEO_HEIGHT)/2 - # self.videopaintpreview.window.draw_image( - # self.videopaintpreview.get_style().fg_gc[gtk.STATE_NORMAL], - # self.videopaintimage, - # 0, 0, x, y, -1, -1) - - #def on_videopaint_tick (self): - # if not self.videopaint_enabled: - # return False - # - # # Update "pressure" from image (uses blob size). - # self.pressure = self.easel.videopaint_pressure - # - # # Move the mouse to follow the videopaint cursor. - # # Note that we do not allow it to reach the corners, to avoid bringing up the Sugar overlay. - # size = self.window.get_size() - # self.mx = int(25+max(0.0, min(1.0, self.easel.videopaint_pos.x))*(size[0]-50)) - # self.my = int(25+max(0.0, min(1.0, self.easel.videopaint_pos.y))*(size[1]-50)) - # #gtk.gdk.display_get_default().warp_pointer(self.get_screen(), self.mx, self.my) - # self.flush_cursor() - # - # # Update the preview window. - # self.videopaintpreview.queue_draw() - # - # # Process drawing. - # self.update() - # - # return True + if self.camera_enabled: + self.videopaint_enabled = button.get_active() + if button.get_active(): + self.cam.start() + # flips the image to start with + self.cam.set_controls(hflip = 1) + gobject.timeout_add(33, self.on_videopaint_tick) + else: + self.cam.stop() def on_videopaint_tick (self): - if not self.videopaint_enabled: + if not self.camera_enabled or not self.videopaint_enabled: return False + # by using query_image, we avoid tieing the framerate to the camera if self.cam.query_image(): # get the new frame @@ -1750,6 +1720,7 @@ class Colors(activity.Activity, ExportedGObject): gtk.gdk.display_get_default().warp_pointer(self.get_screen(), self.mx, self.my) self.flush_cursor() self.update() + return True @@ -1800,7 +1771,7 @@ class Colors(activity.Activity, ExportedGObject): def on_sample (self, button): self.set_mode(Colors.MODE_PLAYBACK) self.easel.clear() - self.easel.load(button.filename) + self.easel.load(str(button.filename)) self.easel.set_playback_speed(8) self.flush_entire_canvas() self.toolbox.set_current_toolbar(2) # Switch to 'watch' toolbar. @@ -1820,7 +1791,7 @@ class Colors(activity.Activity, ExportedGObject): log.debug("Loading from journal %s", file_path) self.set_mode(Colors.MODE_CANVAS) self.easel.clear() - self.easel.load(file_path.encode()) + self.easel.load(str(file_path.encode())) self.easel.start_playback() self.easel.finish_playback() self.playbackpos.set_value(100) @@ -1861,6 +1832,42 @@ class Colors(activity.Activity, ExportedGObject): def on_paste(self, button): pass + + #----------------------------------------------------------------------------------------------------------------- + # PNG Export to Journal + + def on_export_png(self, event): + # Create pixbuf. + w = self.easel.width*2 + h = self.easel.height*2 + + image = gtk.gdk.Image(gtk.gdk.IMAGE_FASTEST, gtk.gdk.visual_get_system(), w, h) + self.easel.blit_2x(image, 0, 0, 0, 0, w, h, False) + + pbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, w, h) + pbuf = pbuf.get_from_image(image, self.easelarea.get_colormap(), 0, 0, 0, 0, w, h) + + # Create a new journal item. + ds = datastore.create() + act_meta = self.metadata + ds.metadata['title'] = act_meta['title'] + ' (PNG)' + ds.metadata['title_set_by_user'] = act_meta['title_set_by_user'] + ds.metadata['mime_type'] = 'image/png' + ds.metadata['icon-color'] = act_meta['icon-color'] + + preview = self.get_preview() + if preview is not None: + ds.metadata['preview'] = dbus.ByteArray(preview) + + # Save the picture to a temporary file. + ds.file_path = os.path.join(self.get_activity_root(), + 'instance', '%i' % time.time()) + pbuf.save(ds.file_path, "png") + + # Store the journal item. + datastore.write(ds, transfer_ownership=True) + ds.destroy() + del ds #----------------------------------------------------------------------------------------------------------------- # Benchmarking @@ -1888,12 +1895,12 @@ class Colors(activity.Activity, ExportedGObject): canvasimage = gtk.gdk.Image(gtk.gdk.IMAGE_FASTEST, gtk.gdk.visual_get_system(), 1200, 800) start = time.time() for i in range(0,100): - canvas.blit_2x(canvasimage, 0, 0, 600, 400) + canvas.blit_2x(canvasimage, 0, 0, 0, 0, 600, 400, False) log.debug("Canvas 2.0x blit benchmark: %f sec", time.time()-start) # Benchmark a Palette object. palette = Palette(500) - paletteimage = gtk.gdk.Image(gtk.gdk.IMAGE_FASTEST, gtk.gdk.visual_get_system(), 500, 500) + paletteimage = gtk.gdk.Image(gtk.gdk.IMAGE_FASTEST, gtk.gdk.visual_get_system(), BrushControlsPanel.PALETTE_SIZE, BrushControlsPanel.PALETTE_SIZE) start = time.time() for i in range(0,100): palette.render_wheel(paletteimage) |