Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/colors.py
diff options
context:
space:
mode:
Diffstat (limited to 'colors.py')
-rwxr-xr-xcolors.py199
1 files changed, 103 insertions, 96 deletions
diff --git a/colors.py b/colors.py
index 06899d1..1ee966d 100755
--- a/colors.py
+++ b/colors.py
@@ -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)