mistelix r7 - in trunk: . extensions extensions/Effects extensions/Effects/RotateImage po src src/core src/datamodel src/dialogs src/widgets



Author: jmas
Date: Wed Mar 25 20:26:34 2009
New Revision: 7
URL: http://svn.gnome.org/viewvc/mistelix?rev=7&view=rev

Log:
Better preview & initial effect support

Added:
   trunk/extensions/Effects/
   trunk/extensions/Effects/Makefile.am
   trunk/extensions/Effects/RotateImage/
   trunk/extensions/Effects/RotateImage/Makefile.am
   trunk/extensions/Effects/RotateImage/RotateImage.addin.xml
   trunk/extensions/Effects/RotateImage/RotateImage.cs
   trunk/src/core/EffectManager.cs
   trunk/src/core/NoneEffect.cs
   trunk/src/datamodel/IEffect.cs
   trunk/src/widgets/DataImageSurface.cs
Modified:
   trunk/ChangeLog
   trunk/configure.in
   trunk/extensions/Makefile.am
   trunk/po/POTFILES.in
   trunk/src/Makefile.am
   trunk/src/core/DvdMenu.cs
   trunk/src/core/SlideImage.cs
   trunk/src/core/SlideShow.cs
   trunk/src/datamodel/SlideShowProjectElement.cs
   trunk/src/dialogs/AddSlideDialog.cs
   trunk/src/mistelix.addin.xml
   trunk/src/mistelix.cs
   trunk/src/widgets/FileView.cs
   trunk/src/widgets/GtkMenu.cs
   trunk/src/widgets/ImagesFileView.cs
   trunk/src/widgets/PixbufImageSurface.cs
   trunk/src/widgets/SlideShowImageView.cs

Modified: trunk/configure.in
==============================================================================
--- trunk/configure.in	(original)
+++ trunk/configure.in	Wed Mar 25 20:26:34 2009
@@ -133,4 +133,6 @@
 extensions/SlideTransitions/Makefile
 extensions/SlideTransitions/Fade/Makefile
 extensions/SlideTransitions/OpaqueLines/Makefile
+extensions/Effects/Makefile
+extensions/Effects/RotateImage/Makefile
 ])

Added: trunk/extensions/Effects/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/extensions/Effects/Makefile.am	Wed Mar 25 20:26:34 2009
@@ -0,0 +1,2 @@
+SUBDIRS = 			\
+	RotateImage

Added: trunk/extensions/Effects/RotateImage/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/extensions/Effects/RotateImage/Makefile.am	Wed Mar 25 20:26:34 2009
@@ -0,0 +1,39 @@
+PLUGIN_NAME = RotateImage
+
+PLUGIN_MANIFEST = $(PLUGIN_NAME).addin.xml
+
+PLUGIN_ASSEMBLY = $(PLUGIN_NAME).dll
+
+PLUGIN_SOURCES =			\
+	$(srcdir)/RotateImage.cs
+
+REFS =					\
+	-r:$(top_builddir)/src/mistelix.exe
+
+PKGS =					\
+	-pkg:gtk-sharp-2.0
+
+RESOURCES =				\
+	-resource:$(srcdir)/$(PLUGIN_MANIFEST)
+
+all: $(PLUGIN_ASSEMBLY)
+
+mpack: $(PLUGIN_ASSEMBLY)
+	mautil p $(PLUGIN_ASSEMBLY)
+
+$(PLUGIN_ASSEMBLY): $(PLUGIN_SOURCES) $(PLUGIN_MANIFEST)
+	$(CSC) -target:library -out:$@ $(CSC_DEFINES) $(PLUGIN_SOURCES) $(REFS) $(PKGS) $(ASSEMBLIES) $(RESOURCES)
+
+plugindir = $(pkglibdir)/extensions
+
+plugin_DATA =			\
+	$(PLUGIN_ASSEMBLY)
+
+EXTRA_DIST = 			\
+	$(PLUGIN_SOURCES)	\
+	$(PLUGIN_MANIFEST)
+
+CLEANFILES =			\
+	$(PLUGIN_ASSEMBLY)	\
+	$(PLUGIN_ASSEMBLY).mdb	\
+	*.mpack

Added: trunk/extensions/Effects/RotateImage/RotateImage.addin.xml
==============================================================================
--- (empty file)
+++ trunk/extensions/Effects/RotateImage/RotateImage.addin.xml	Wed Mar 25 20:26:34 2009
@@ -0,0 +1,17 @@
+<Addin namespace="Mistelix"
+	version="0.10"
+	name="Fade"
+	description="Rotate images"
+	author="Jordi Mas"
+	url=""
+	defaultEnabled="true"
+	category="Effects">
+
+	<Dependencies>
+		<Addin id="Mistelix" version="0.10"/>
+	</Dependencies>
+
+	<Extension path="/Mistelix/Effects">
+		<Effects type="Mistelix.Effects.RotateImage" />
+	</Extension>
+</Addin>

Added: trunk/extensions/Effects/RotateImage/RotateImage.cs
==============================================================================
--- (empty file)
+++ trunk/extensions/Effects/RotateImage/RotateImage.cs	Wed Mar 25 20:26:34 2009
@@ -0,0 +1,55 @@
+//
+// Copyright (C) 2008 Jordi Mas i Hernandez, jmas softcatala org
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+using Mistelix.DataModel;
+using Mistelix.Effects;
+
+namespace Mistelix.Effects
+{
+	public class RotateImage: IEffect
+	{
+		public string DisplayName {
+			get { return ("Rotate image"); }
+		}
+
+		public string Name {
+			get { return ("rotate image"); }
+		}
+
+		public SlideImage ApplyEffect (SlideImage org)
+		{
+			SlideImage image;
+
+			image = new SlideImage ();
+			image.CopyProperties (org);
+			image.Pixels = new byte [org.stride * org.height];
+
+			for (int i = 0; i < org.stride * org.height; i++)
+				image.Pixels[i] = (byte) ((double) org.Pixels[i] * 0.5);
+	
+			return image;
+		}
+	}
+}

Modified: trunk/extensions/Makefile.am
==============================================================================
--- trunk/extensions/Makefile.am	(original)
+++ trunk/extensions/Makefile.am	Wed Mar 25 20:26:34 2009
@@ -1,5 +1,6 @@
 SUBDIRS = 			\
-	SlideTransitions
+	SlideTransitions	\
+	Effects
 
 addinsdir = $(pkglibdir)
 addins_DATA = mistelix.global.addins

Modified: trunk/po/POTFILES.in
==============================================================================
--- trunk/po/POTFILES.in	(original)
+++ trunk/po/POTFILES.in	Wed Mar 25 20:26:34 2009
@@ -3,6 +3,7 @@
 src/core/Dependencies.cs
 src/core/DvdProjectBuilder.cs
 src/core/MistelixLib.cs
+src/core/NoneEffect.cs
 src/core/NoneTransition.cs
 src/core/TheoraProjectBuilder.cs
 src/core/ThumbnailSizeManager.cs

Modified: trunk/src/Makefile.am
==============================================================================
--- trunk/src/Makefile.am	(original)
+++ trunk/src/Makefile.am	Wed Mar 25 20:26:34 2009
@@ -63,7 +63,11 @@
 	$(srcdir)/dialogs/AboutDialog.cs \
 	$(srcdir)/core/Dependencies.cs \
 	$(srcdir)/dialogs/CheckDependenciesDialog.cs \
-	$(srcdir)/datamodel/ObservableList.cs
+	$(srcdir)/datamodel/ObservableList.cs \
+	$(srcdir)/datamodel/IEffect.cs \
+ 	$(srcdir)/core/EffectManager.cs \
+	$(srcdir)/core/NoneEffect.cs \
+	$(srcdir)/widgets/DataImageSurface.cs
 
 ASSEMBLIES = \
 	 $(MISTELIX_LIBS)    		\

Modified: trunk/src/core/DvdMenu.cs
==============================================================================
--- trunk/src/core/DvdMenu.cs	(original)
+++ trunk/src/core/DvdMenu.cs	Wed Mar 25 20:26:34 2009
@@ -66,6 +66,8 @@
 		public string GenerateMPEG (/*, ProgressEventHandler progress*/)
 		{
 			SlideShow sw;
+
+			Logger.Debug ("DvdMenu.GenerateMPEG");
 	
 			Save (project.FileToFullPath (Defines.MAIN_MENU_FILE_PNG));
 

Added: trunk/src/core/EffectManager.cs
==============================================================================
--- (empty file)
+++ trunk/src/core/EffectManager.cs	Wed Mar 25 20:26:34 2009
@@ -0,0 +1,76 @@
+//
+// Copyright (C) 2009 Jordi Mas i Hernandez, jmas softcatala org
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using Mono.Addins;
+using Mistelix.Effects;
+
+namespace Mistelix.Core
+{
+	// Manages all the available effects
+	public static class EffectManager
+	{
+		static None none;
+		static EffectManager ()
+		{
+		}
+
+		static public IEffect None {
+			get {
+				if (none == null)
+					none = new None ();
+
+				return none;
+			}
+		}
+
+		// Get default none transition + the ones provides by pluggins
+		static public IEffect[] List {
+			get {
+				IEffect[] effects;
+				int pos = 0;
+				ExtensionNodeList addins = AddinManager.GetExtensionNodes ("/Mistelix/Effects");
+				effects = new IEffect [addins.Count + 1];
+	
+				effects[pos++] = new None ();
+
+				foreach (TypeExtensionNode node in addins) {
+					effects[pos++] =  (IEffect) node.CreateInstance ();
+				}
+
+				return effects;
+			}
+		}
+
+		static public IEffect FromName (string val) 
+		{
+			foreach (IEffect effect in List)
+			{
+				if (effect.Name.Equals (val))
+					return effect;
+			}
+			Logger.Debug (String.Format ("EffectManager.FromName -> effect {0} not found", val));
+			return None;
+		}
+	}
+}

Added: trunk/src/core/NoneEffect.cs
==============================================================================
--- (empty file)
+++ trunk/src/core/NoneEffect.cs	Wed Mar 25 20:26:34 2009
@@ -0,0 +1,48 @@
+//
+// Copyright (C) 2009 Jordi Mas i Hernandez, jmas softcatala org
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using Mistelix.DataModel;
+using Mono.Unix;
+
+// Default built-in transition
+namespace Mistelix.Effects
+{
+	// This transition does nothing
+	public class None: IEffect
+	{
+		public string DisplayName {
+			get { return (Catalog.GetString ("<None>")); }
+		}
+
+		public string Name {
+			get { return ("none"); }
+		}
+
+		public SlideImage ApplyEffect (SlideImage slideimage) 
+		{
+			return null;
+		}
+	}
+}
+

Modified: trunk/src/core/SlideImage.cs
==============================================================================
--- trunk/src/core/SlideImage.cs	(original)
+++ trunk/src/core/SlideImage.cs	Wed Mar 25 20:26:34 2009
@@ -29,9 +29,11 @@
 using System.Xml.Serialization;
 using Gdk;
 using Pango;
+using Cairo;
 
 using Mistelix.Core;
 using Mistelix.Widgets;
+using Mistelix.Effects;
 
 namespace Mistelix.DataModel
 {
@@ -40,33 +42,40 @@
 	//
 	public class SlideImage : SlideShowProjectElement.Image
     	{
-		[XmlIgnoreAttribute] public int width, height, stride, channels = 3;
+		[XmlIgnoreAttribute] public int width, height, stride, channels = 3, requested_channels = 3;
 		[XmlIgnoreAttribute] public bool alpha = false;
 		[XmlIgnoreAttribute] byte[] pixels = null;
 		[XmlIgnoreAttribute] Project project;
+		[XmlIgnoreAttribute] int offset_x, offset_y, w, h;
 
 		public SlideImage ()
 		{
-			transition = 2; // Transition from slide to slide (effect duration)
-			time = Mistelix.Preferences.GetIntValue (Preferences.DefaultDurationKey);
-			effect = Mistelix.Preferences.GetStringValue (Preferences.DefaultTransitionKey);
-			position = (TextPosition) Mistelix.Preferences.GetIntValue (Preferences.DefaultTextPositionKey);
+			TransitionTime = 2; // Transition from slide to slide (effect duration). Hardcoded default
+			ShowTime = Mistelix.Preferences.GetIntValue (Preferences.DefaultDurationKey);
+			Transition = Mistelix.Preferences.GetStringValue (Preferences.DefaultTransitionKey);
+			Position = (TextPosition) Mistelix.Preferences.GetIntValue (Preferences.DefaultTextPositionKey);
 		}
 
 		public SlideImage (string image) : this ()
 		{
+			if (name == null)
+				throw new ArgumentNullException ("image name cannot be null");
+
 			this.image = image;
 		}
 
-		public SlideImage (string image, string title, int time, string effect) : this ()
+		public SlideImage (string image, string title, int time, string transition) : this ()
 		{
-			if (effect == null)
+			if (transition == null)
 				throw new ArgumentNullException ("effect cannot be null");
+
+			if (image == null)
+				throw new ArgumentNullException ("image name cannot be null");
 		
 			this.image = image;
-			this.title = title;
-			this.time = time;
-			this.effect = effect;
+			Title = title;
+			ShowTime = time;
+			Transition = transition;
 		}
 		
 		// Used only for previewing the transition effect
@@ -82,41 +91,12 @@
 			alpha = true;
 			pixels = img.Data;
 		}
-		
-		// mistelixvideosrc expects images in 24 bits (3 channels)
-		public void FromImage (PixbufImageSurface img)
-		{
-			if (img.Format != Cairo.Format.Argb32 && img.Format != Cairo.Format.Rgb24)
-				throw new InvalidOperationException (String.Format ("SlideImage.FromCairo: unsupported format {0}", img.Format));
-
-			width = img.Width;
-			height = img.Height;
-			stride = img.Width * 3;
-			channels = 3;
-			alpha = false;
-			pixels = img.Get24bitsPixBuf ();
-		}
-		
-		void LoadPixBuffer (Gdk.Pixbuf buf)
-		{
-			int len;
-
-			width = buf.Width;
-			height = buf.Height;
-			stride = buf.Rowstride;
-			channels = buf.NChannels;
-			alpha = buf.HasAlpha;
-			len = stride * height;
-
-			pixels = new byte [len];
-			Marshal.Copy (buf.Pixels, pixels, 0, len);
-		}
 
 		[System.Xml.Serialization.XmlIgnoreAttribute]
 		public byte[] Pixels {
 			get {
 				if (pixels == null)
-					LoadImageFromFile ();
+					LoadAndScaleImage ();
 
 				return pixels;
 			}
@@ -130,6 +110,19 @@
 			set { project = value; }
 		}
 
+		public void CopyProperties (SlideImage src)
+		{
+			// TODO: Missing SlideImage properties
+			width = src.width;
+			height = src.height;
+			stride = src.stride;
+			alpha = src.alpha;
+			channels = src.channels;
+			Effects = src.Effects;
+
+			image = src.image;
+		}
+
 		// The list of images for a slideshow is kept on a list
 		// When generating the slideshow the pixels are allocated, however they are only 
 		// needed during the slideshow generation. Releasing the pixels helps to GC 
@@ -139,6 +132,34 @@
 			pixels = null;
 		}
 
+
+		public DataImageSurface GetThumbnail (int width, int height)
+		{
+			SlideImage slide = new SlideImage ();
+			slide.CopyProperties (this);
+			slide.requested_channels = 4;
+			slide.LoadAndScaleImage (width, height);
+			//slide.ProcessImage ();
+			slide.ProcessEffects ();
+
+			return new DataImageSurface (DataImageSurface.Allocate (slide.Pixels),
+				Cairo.Format.ARGB32, slide.width, slide.height, slide.stride);
+		}
+
+		// mistelixvideosrc expects images in 24 bits (3 channels)
+		void FromImage (DataImageSurface img)
+		{
+			if (img.Format != Cairo.Format.Argb32 && img.Format != Cairo.Format.Rgb24)
+				throw new InvalidOperationException (String.Format ("SlideImage.FromCairo: unsupported format {0}", img.Format));
+
+			width = img.Width;
+			height = img.Height;
+			stride = img.Width * 3;
+			channels = 3;
+			alpha = false;
+			pixels = img.Get24bitsPixBuf ();
+		}
+		
 		void DrawImageLegend (Cairo.Context cr, string title, int x, int y, int width, int height)
 		{
 			const int marginx = 50; // Distance of the coloured box from the x margins (in safe area)
@@ -167,7 +188,7 @@
 				box_w = w;
 				box_h = y + h + textoffset_y * 2;
 
-				switch (position) {
+				switch (Position) {
 				case TextPosition.Top:
 					box_y = marginy;
 					break;
@@ -189,23 +210,29 @@
 			}
 		}
 
-		void LoadImageFromFile ()
+		void LoadAndScaleImage ()
 		{
-			if (time == 0)
-				time = 3;
-
 			if (project == null)
 				throw new InvalidOperationException (String.Format ("SlideImage.CreateImage: need project defined (image {0})", image));
 
+			LoadAndScaleImage (project.Details.Width, project.Details.Height);
+			ProcessEffects ();
+		}
+
+		//
+		// Loads the image from disk and scales it to certain size
+		// It is used to generate the final images and thumbnails
+		//
+		void LoadAndScaleImage (int width, int height)
+		{
 			if (image == null)
 				throw new InvalidOperationException (String.Format ("SlideImage.CreateImage: no filename defined for image"));
 
-			Logger.Debug ("SlideImage.CreateImage {0}", image);
+			Logger.Debug ("SlideImage.CreateImage {0}", image, width, height);
 
-			int max_w = project.Details.Width;    // max target width 
-			int max_h = project.Details.Height;   // max target height
+			int max_w = width;    // max target width 
+			int max_h = height;   // max target height
 			double target_ratio = (double) max_w / (double) max_h;  // aspect ratio target 
-			int offset_x, offset_y, w, h;
 			double scale, original_ratio, corrected_ratio;
 			Gdk.Pixbuf raw_image, processed_image;
 
@@ -251,30 +278,101 @@
 			else
 				offset_y = 0;
 
-			raw_image.Scale (processed_image, offset_x, offset_y, w, h, offset_x, offset_y, (double) w / (double) raw_image.Width, (double)h /(double) raw_image.Height, InterpType.Hyper);
+			raw_image.Scale (processed_image, offset_x, offset_y, w, h, offset_x, offset_y, 
+				(double) w / (double) raw_image.Width, (double)h /(double) raw_image.Height, InterpType.Hyper);
 
-			if (title != string.Empty) {
-				PixbufImageSurface pix = new PixbufImageSurface (processed_image);
-				Cairo.Context gr = new Cairo.Context (pix);
-				DrawImageLegend (gr, title, offset_x, offset_y,  w, h);
-				FromImage (pix);
-				((IDisposable)gr).Dispose ();
-				((IDisposable)pix).Dispose ();
-			} else
-				LoadPixBuffer (processed_image);
+			LoadPixBuffer (processed_image);
 
 			raw_image.Dispose ();
 			processed_image.Dispose ();
 		}
 
-		public void CopyProperties (SlideImage src)
+		void ProcessImage ()
 		{
-			// TODO: Missing SlideImage properties
-			width = src.width;
-			height = src.height;
-			stride = src.stride;
-			alpha = src.alpha;
-			channels = src.channels;
+			Logger.Debug ("SlideImage.ProcessImage {0}", image);
+
+			if (Title == null)
+				return;
+
+			if (Title == string.Empty)
+				throw new Exception ("Not expecting an empty.string here");
+
+			Logger.Debug ("SlideImage.ProcessImage {0}", image);
+
+			DataImageSurface pix = new DataImageSurface (DataImageSurface.Allocate (Pixels), Cairo.Format.Argb32, width, height, stride);
+			Cairo.Context gr = new Cairo.Context (pix);
+			DrawImageLegend (gr, Title, offset_x, offset_y,  w, h);
+			FromImage (pix);
+			((IDisposable)gr).Dispose ();
+			((IDisposable)pix).Dispose ();
+		}
+
+		public void ProcessEffects ()
+		{
+			IEffect effect;
+			SlideImage processed, previous;
+
+			if (Effects == null)
+				return;
+
+			previous = processed = this;
+
+			foreach (string name in Effects)
+			{
+				Logger.Debug ("SlideImage.ProcessEffects -> effect {0}", name);
+				effect = EffectManager.FromName (name);
+				previous = processed;
+				processed = effect.ApplyEffect (processed);
+				previous.ReleasePixels ();
+			}
+
+			CopyProperties (processed);
+			Pixels = processed.Pixels;
+		}
+		
+		void LoadPixBuffer (Gdk.Pixbuf buf)
+		{
+			int len;
+
+			width = buf.Width;
+			height = buf.Height;
+			stride = buf.Rowstride;
+			alpha = buf.HasAlpha;
+			
+			if (requested_channels == buf.NChannels) {
+				channels = buf.NChannels;
+				len = stride * height;
+				pixels = new byte [len];
+				Marshal.Copy (buf.Pixels, pixels, 0, len);
+				return;
+			}
+
+			if (requested_channels == 4 && buf.NChannels == 3) {
+				int src;
+				byte [] source;
+
+				src = 0;
+				channels = buf.NChannels;
+				stride = 4 * width;
+				len = stride * height;
+				source = new byte [channels * height * width];
+				Marshal.Copy (buf.Pixels, source, 0, channels * height * width);
+
+				pixels = new byte [len];
+							
+				for (int trg = 0; trg < len; trg = trg + 4) {
+					pixels [trg]      = source [src + 2];
+					pixels [trg + 1]  = source [src + 1];
+					pixels [trg + 2]  = source [src + 0];
+					pixels [trg + 3]  = 0xff;
+					src += 3;
+				}
+				return;
+			}
+
+			throw new InvalidOperationException (
+					String.Format ("Could not process SlideImage.LoadPixBuffer requested channels {0} image {1}", 
+					requested_channels, buf.NChannels));
 		}
 	}
 }

Modified: trunk/src/core/SlideShow.cs
==============================================================================
--- trunk/src/core/SlideShow.cs	(original)
+++ trunk/src/core/SlideShow.cs	Wed Mar 25 20:26:34 2009
@@ -49,12 +49,12 @@
 	
 			for (int i = 0; i < images.Count - 1; i++) 
 			{
-				total_frames += (frames_sec * images[i].time) + (images[i].transition * frames_sec);
+				total_frames += (frames_sec * images[i].ShowTime) + (images[i].TransitionTime * frames_sec);
 				((SlideImage) images[i]).Project = project;
 			}
 
 			((SlideImage) images[images.Count - 1]).Project = project;
-			total_frames += frames_sec * images[images.Count - 1].time;
+			total_frames += frames_sec * images[images.Count - 1].ShowTime;
 			args.Total = images.Count;
 
 			Logger.Debug ("SlideShow.GenerateMPEG -> Generating MPEG for " + images.Count + " images " + "total frames " + total_frames + " at " + filename);
@@ -85,14 +85,13 @@
 					progress (this, args);
 				}
 
-				Logger.Debug ("SlideShow.GenerateMPEG ->Send Fixed image {0}, time {1} (frames)", i, (uint) (frames_sec * images[i].time));
-
-				transition = TransitionManager.FromName (images[i].effect);
-				lib.SlideShowAddImageFixed (((SlideImage)images[i]), (uint) (frames_sec * images[i].time));
+				Logger.Debug ("SlideShow.GenerateMPEG ->Send Fixed image {0}, time {1} (frames)", i, (uint) (frames_sec * images[i].ShowTime));
+				transition = TransitionManager.FromName (images[i].Transition);
+				lib.SlideShowAddImageFixed (((SlideImage)images[i]), (uint) (frames_sec * images[i].ShowTime));
 		
 				// Transition between two images
-				Logger.Debug ("SlideShow.GenerateMPEG ->Generate transition for frames_sec {0} and time {1}", frames_sec, images[i].transition);
-				images_transition = transition.Effect (((SlideImage)images[i]), ((SlideImage)images[i + 1]), frames_sec, images[i].transition);
+				Logger.Debug ("SlideShow.GenerateMPEG ->Generate transition for frames_sec {0} and time {1}", frames_sec, images[i].TransitionTime);
+				images_transition = transition.Effect (((SlideImage)images[i]), ((SlideImage)images[i + 1]), frames_sec, images[i].TransitionTime);
 				for (int j = 0; j < images_transition.Length; j++) {
 					Logger.Debug ("   Sending subimage {0}", j);
 					lib.SlideShowAddImage (images_transition [j]);
@@ -100,8 +99,8 @@
 				((SlideImage) images[i]).ReleasePixels ();
 			}
 
-			Logger.Debug ("SlideShow.GenerateMPEG ->Send Fixed image time {0} (frames)", (uint) (frames_sec * images[images.Count -1].time));
-			lib.SlideShowAddImageFixed (((SlideImage)images[images.Count - 1]), (uint) (frames_sec * images[images.Count -1].time));
+			Logger.Debug ("SlideShow.GenerateMPEG ->Send Fixed image time {0} (frames)", (uint) (frames_sec * images[images.Count -1].ShowTime));
+			lib.SlideShowAddImageFixed (((SlideImage)images[images.Count - 1]), (uint) (frames_sec * images[images.Count -1].ShowTime));
 	
 			if (progress != null) {
 				args.Progress = args.Progress + 1;

Added: trunk/src/datamodel/IEffect.cs
==============================================================================
--- (empty file)
+++ trunk/src/datamodel/IEffect.cs	Wed Mar 25 20:26:34 2009
@@ -0,0 +1,45 @@
+//
+// Copyright (C) 2009 Jordi Mas i Hernandez, jmas softcatala org
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using Mistelix.DataModel;
+
+namespace Mistelix.Effects
+{
+	//
+	// Interface that defines an effect to apply to an image
+	//
+	public interface IEffect
+	{
+		SlideImage ApplyEffect (SlideImage slideimage);
+
+		string DisplayName {
+			get;
+		}
+
+		string Name {
+			get;
+		}
+	}
+}
+

Modified: trunk/src/datamodel/SlideShowProjectElement.cs
==============================================================================
--- trunk/src/datamodel/SlideShowProjectElement.cs	(original)
+++ trunk/src/datamodel/SlideShowProjectElement.cs	Wed Mar 25 20:26:34 2009
@@ -30,6 +30,7 @@
 using Gdk;
 
 using Mistelix.Core;
+using Mistelix.Effects;
 
 namespace Mistelix.DataModel
 {	
@@ -84,15 +85,63 @@
 		public abstract class Image : ProjectElement
 		{
 			public string image;
-			public string title;
-			public int transition ; // Transition from slide to slide (effect duration)
-			public int time; // Time that the image is shown
-			public string effect; // Transition effect
+			string title;
+			int transition_time;
+			int shown_time; 
+			string transition;
+			TextPosition position;
+			List <string> effects;
+
+			// Text description
+			//[XmlElementAttribute ("effects")]
+			public List <string> Effects {
+				get { return effects;}
+				set { effects = value;}
+			}
+
+			// Text description
+			[XmlElementAttribute ("title")]
+			public string Title {
+				get { return title;}
+				set { title = value;}
+			}
+
+			// Transition type
+			[XmlElementAttribute ("transition")]
+			public string Transition {
+				get { return transition;}
+				set { transition = value;}
+			}
+
+			// Time that the image is statically shown
+			[XmlElementAttribute ("show_time")]
+			public int ShowTime {
+				get { return shown_time;}
+				set { shown_time = value;}
+			}
+
+			// Transition from slide to slide (effect duration)
+			[XmlIgnoreAttribute] 
+			public int TransitionTime {
+				get { return transition_time;}
+				set { transition_time = value;}
+			}
 			
-			[XmlElementAttribute (DataType="int")]
-			public TextPosition position;
+			[XmlElementAttribute ("position", DataType="int")]
+			public TextPosition Position {
+				get { return position;}
+				set { position = value;}
+			}
 						
 			protected Image () {}
+
+			public void AddEffect (string effect)
+			{
+				if (effects == null)
+					effects = new List <string> ();
+				
+				effects.Add (effect);
+			}
 		}
 	}
 }

Modified: trunk/src/dialogs/AddSlideDialog.cs
==============================================================================
--- trunk/src/dialogs/AddSlideDialog.cs	(original)
+++ trunk/src/dialogs/AddSlideDialog.cs	Wed Mar 25 20:26:34 2009
@@ -53,7 +53,6 @@
 		ListStore transitions_combo, textposition_store;
 		SlideImage selected_image;
 		SlideShow slide;
-		Dictionary <string, Gdk.Pixbuf> thumbnail_cache;
 		TransitionPreview drawing_area;
 		bool edit_mode;
 
@@ -77,7 +76,6 @@
 			image_view.ChangeEvent = new ShowImageSelectionEventHandler (OnChangeImage);
 			image_view.UpdatedElements = new ShowImageUpdatedElementsEventHandler (OnUpdatedImages);
 			scrolled_images.Add (image_view);
-			thumbnail_cache = new Dictionary <string, Gdk.Pixbuf> ();
 
 			up_button.Clicked += new EventHandler (OnButtonUp);
 			down_button.Clicked += new EventHandler (OnButtonDown);
@@ -86,8 +84,6 @@
 			new DirectoryView (vbox_dir, new ChangeDirectoryEventHandler (OnDirectoryChanged),
 				Mistelix.Preferences.GetStringValue (Preferences.ImagesDirectoryKey));
 
-			file_view.Cache = thumbnail_cache;
-			image_view.Cache = thumbnail_cache;
 			scrolledwin_files.Add (file_view);
 
 			drawing_area = new TransitionPreview ();
@@ -201,7 +197,7 @@
 			{
 				model.GetIter (out iter, path);
 				image = (SlideImage) model.GetValue (iter, SlideShowImageView.COL_OBJECT);
-				image.time = spinner.ValueAsInt;
+				image.ShowTime = spinner.ValueAsInt;
 			}
 		}
 
@@ -225,15 +221,15 @@
 			TreeIter iter;
 			TreeModel model;
 			SlideImage image;
-			string effect;
+			string transition;
 			TreeSelection selection;
 			TreePath[] paths;
 
 			if (!combo.GetActiveIter (out iter))
 				return;
 			
-			// Establish new effect on the selected items
-			effect = (string) combo.Model.GetValue (iter, (int) ColumnsCombo.Column_Data);
+			// Establish new transition on the selected items
+			transition = (string) combo.Model.GetValue (iter, (int) ColumnsCombo.Column_Data);
 			selection = (image_view as TreeView).Selection;
 
 			paths = selection.GetSelectedRows (out model);
@@ -241,10 +237,10 @@
 			{
 				model.GetIter (out iter, path);
 				image = (SlideImage) model.GetValue (iter, SlideShowImageView.COL_OBJECT);
-				image.effect = effect;
+				image.Transition = transition;
 			}
 
-			drawing_area.UpdateTransitionType (effect);
+			drawing_area.UpdateTransitionType (transition);
 		}
 
 		void OnTextPositionComboChanged (object sender, EventArgs args)
@@ -271,7 +267,7 @@
 			{
 				model.GetIter (out iter, path);
 				image = (SlideImage) model.GetValue (iter, SlideShowImageView.COL_OBJECT);
-				image.position = position;
+				image.Position = position;
 			}
 		}
 
@@ -280,7 +276,7 @@
 		{
 			bool more;
 			TreeIter iter;
-			string effect;
+			string transition;
 
 			Logger.Debug ("AddSlideDialog.OnChangeImage. Images {0}", args.images.Length);
 
@@ -290,15 +286,15 @@
 			selected_image = args.images [0]; // Take the first since all of them will be the same
 			ImageControlsSensitive (true);
 
-			duration_spin.Value = selected_image.time;
+			duration_spin.Value = selected_image.ShowTime;
 			drawing_area.UpdateTransitionType (string.Empty);
 	
-			// Transition effect for this image
+			// Transition for this image
 			more = transitions_combo.GetIterFirst (out iter);
 			while (more)
 			{
-				effect = (string) transitions_combo.GetValue (iter, (int) ColumnsCombo.Column_Data);
-				if (selected_image.effect.Equals (effect)) {
+				transition = (string) transitions_combo.GetValue (iter, (int) ColumnsCombo.Column_Data);
+				if (selected_image.Transition.Equals (transition)) {
 					transition_combo.SetActiveIter (iter);
 					break;
 				}
@@ -308,13 +304,13 @@
 			if (more == false)
 				SetTransisitionToNone ();
 
-			// Text position effect for this image
+			// Text position for this image
 			TextPosition position;
 			more = textposition_store.GetIterFirst (out iter);
 			while (more)
 			{
 				position = (TextPosition) textposition_store.GetValue (iter, (int) ColumnsCombo.Column_Data);
-				if (selected_image.position == position) {
+				if (selected_image.Position == position) {
 					textposition_combo.SetActiveIter (iter);
 					return;
 				}
@@ -339,11 +335,9 @@
 
 		public override void FreeResources ()
 		{
-			Dialog.Destroy ();
 			file_view.Dispose ();
-
-			foreach (KeyValuePair <string, Gdk.Pixbuf> kvp in thumbnail_cache)
-				kvp.Value.Dispose ();
+			image_view.Dispose ();
+			Dialog.Destroy ();
 		}
 
 		void LoadTextPositionsIntoCombo ()
@@ -375,7 +369,7 @@
 
 			gr.MoveTo (10, 45);
 			gr.SetFontSize (50);
-			// Translators: Used as an example of how a transition effect looks like, transitioning from 'A' to 'B'
+			// Translators: Used as an example of how a transition looks like, transitioning from 'A' to 'B'
 			gr.ShowText (Catalog.GetString ("A"));
 			gr.Stroke ();
 			((IDisposable)gr).Dispose ();
@@ -385,7 +379,7 @@
 
 			gr.MoveTo (10, 45);
 			gr.SetFontSize (50);
-			// Translators: Used as an example of how a transition effect looks like, transitioning from 'A' to 'B'
+			// Translators: Used as an example of how a transition looks like, transitioning from 'A' to 'B'
 			gr.ShowText (Catalog.GetString ("B"));
 			gr.Stroke ();
 			((IDisposable)gr).Dispose ();

Modified: trunk/src/mistelix.addin.xml
==============================================================================
--- trunk/src/mistelix.addin.xml	(original)
+++ trunk/src/mistelix.addin.xml	Wed Mar 25 20:26:34 2009
@@ -7,6 +7,10 @@
 	<ExtensionPoint path="/Mistelix/SlideTransitions">
 		<ExtensionNode name="SlideTransitions" objectType="Mistelix.Transitions.ITransition" />
 	</ExtensionPoint>
+
+	<ExtensionPoint path="/Mistelix/Effects">
+		<ExtensionNode name="Effects" objectType="Mistelix.Effects.IEffect" />
+	</ExtensionPoint>
 </Addin>
 
 

Modified: trunk/src/mistelix.cs
==============================================================================
--- trunk/src/mistelix.cs	(original)
+++ trunk/src/mistelix.cs	Wed Mar 25 20:26:34 2009
@@ -96,6 +96,10 @@
 				Logger.Info ("Extension:" + node.CreateInstance ());
 			}
 
+			foreach (TypeExtensionNode node in AddinManager.GetExtensionNodes ("/Mistelix/Effects")) {
+				Logger.Info ("Extension:" + node.CreateInstance ());
+			}
+
 			if (String.Compare (Environment.GetEnvironmentVariable ("MISTELIX_DEBUG"), "true", false) == 0) { 
 				debugging = true;
 			} else {

Added: trunk/src/widgets/DataImageSurface.cs
==============================================================================
--- (empty file)
+++ trunk/src/widgets/DataImageSurface.cs	Wed Mar 25 20:26:34 2009
@@ -0,0 +1,117 @@
+//
+// Copyright (C) 2008 Aaron Bockover, abockover novell com
+// Copyright (C) 2008 Jordi Mas i Hernandez, jmas softcatala org
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Runtime.InteropServices;
+
+using Cairo;
+
+namespace Mistelix.Widgets
+{
+	public class DataImageSurface : ImageSurface, IDisposable
+	{
+		delegate void cairo_destroy_func_t (IntPtr userdata);
+		static int user_data_key = 0;
+		static cairo_destroy_func_t destroy_func;
+		protected IntPtr data;
+		protected bool is_le = BitConverter.IsLittleEndian;
+		       
+		static void DestroyPixelData (IntPtr data)
+		{
+			Marshal.FreeHGlobal (data);
+		}
+
+		static DataImageSurface ()
+		{
+		    	destroy_func = new cairo_destroy_func_t (DestroyPixelData);
+		}
+
+		static public IntPtr Allocate (byte [] pixels)
+		{
+			IntPtr ptr = Marshal.AllocHGlobal (pixels.Length);		
+			Marshal.Copy (pixels, 0, ptr, pixels.Length);
+			return ptr;
+		}
+
+		public DataImageSurface (IntPtr data, Cairo.Format format, int width, int height, int stride) : base (data, format, width, height, width * 4)
+		{
+			this.data = data;
+			SetDestroyFunc ();
+		}
+
+		// Converts from Cairo 32-bits to GTK 24 bits (3 channels)
+		public byte[] Get24bitsPixBuf ()
+		{
+			int pos_cairo = 0;
+			int pos_gdk = 0;
+			byte [] cairo_pixels;
+			byte [] pixels;
+			int width, height;
+			const int channels = 3;
+
+			width = Width;
+			height = Height;
+			pixels = new byte [height * Width * channels];
+			cairo_pixels = Data;
+
+			for (int h = 0; h < height; h++) 
+			{
+				for (int i = 0; i < width; i++)
+				{
+					if (is_le) {
+						pixels[pos_gdk + 2] = cairo_pixels[pos_cairo + 0];
+					   	pixels[pos_gdk + 1] = cairo_pixels[pos_cairo + 1];
+					   	pixels[pos_gdk + 0] = cairo_pixels[pos_cairo + 2];
+					} else {
+					   	pixels[pos_gdk + 0] = cairo_pixels[pos_cairo + 1];
+					   	pixels[pos_gdk + 1] = cairo_pixels[pos_cairo + 2];
+					   	pixels[pos_gdk + 2] = cairo_pixels[pos_cairo + 3];
+					}
+
+					pos_gdk += channels;
+					pos_cairo += 4;
+				}
+			}
+			return pixels;
+		}
+
+
+		[DllImport ("libcairo.so.2")]
+		static extern Cairo.Status cairo_surface_set_user_data (IntPtr surface, ref int key, IntPtr userdata, cairo_destroy_func_t destroy);
+
+		void SetDestroyFunc ()
+		{
+			try {
+				Cairo.Status status = cairo_surface_set_user_data (Handle, ref user_data_key, data, destroy_func);
+				if (status != Cairo.Status.Success) {
+				    throw new ApplicationException (String.Format (
+					"cairo_surface_set_user_data returned {0}", status));
+				}
+			} catch (Exception e) {
+				Console.Error.WriteLine (e);	
+			}
+		}
+	}
+}	
+

Modified: trunk/src/widgets/FileView.cs
==============================================================================
--- trunk/src/widgets/FileView.cs	(original)
+++ trunk/src/widgets/FileView.cs	Wed Mar 25 20:26:34 2009
@@ -56,7 +56,6 @@
 
 		protected int thumbnail_height;
 		protected int thumbnail_width;
-		protected Dictionary <string, Gdk.Pixbuf> thumbnail_cache;
 			
 		protected FileView ()
 		{
@@ -79,10 +78,6 @@
 			Dispose (false);
 		}
 
-		public Dictionary <string, Gdk.Pixbuf> Cache {
-			set { thumbnail_cache = value; }
-		}
-
 		public List <string> SelectedFiles {
 			get {
 				List <string> files = new List <string> ();
@@ -140,15 +135,6 @@
 			if (thumbnailing != null)
 				thumbnailing.Dispose ();
 
-			if (thumbnail_cache != null) {
-				lock (thumbnail_cache) {
-					foreach (KeyValuePair <string, Gdk.Pixbuf> kvp in thumbnail_cache)
-						kvp.Value.Dispose ();
-
-					thumbnail_cache.Clear ();
-				}
-			}
-
 			thumbnailing = new BackgroundWorker ();
 			thumbnailing.DoWork += new DoWorkEventHandler (DoWork);
 

Modified: trunk/src/widgets/GtkMenu.cs
==============================================================================
--- trunk/src/widgets/GtkMenu.cs	(original)
+++ trunk/src/widgets/GtkMenu.cs	Wed Mar 25 20:26:34 2009
@@ -33,7 +33,7 @@
 		{
 		}
 
-		public void AddItem (string label, EventHandler ev)
+		public Gtk.MenuItem AddItem (string label, EventHandler ev)
 		{
 			Gtk.MenuItem item = new Gtk.MenuItem (label);
 			Append (item);
@@ -42,6 +42,15 @@
 				item.Activated += ev;
 
 			item.Show ();
+
+			return item;
+		}
+
+		public void AddSeparator ()
+		{				
+			Gtk.SeparatorMenuItem item = new Gtk.SeparatorMenuItem ();
+			Append (item);
+			item.Show ();
 		}
 
 		public void Popup (ButtonPressEventArgs args)

Modified: trunk/src/widgets/ImagesFileView.cs
==============================================================================
--- trunk/src/widgets/ImagesFileView.cs	(original)
+++ trunk/src/widgets/ImagesFileView.cs	Wed Mar 25 20:26:34 2009
@@ -133,10 +133,6 @@
 				Application.Invoke (delegate {
 						store.SetValue (iter, 2, scaled);
 					} );
-				
-				lock (thumbnail_cache) {
-					thumbnail_cache[file] = scaled;
-				}
 
 				im.Dispose ();
 				return false;

Modified: trunk/src/widgets/PixbufImageSurface.cs
==============================================================================
--- trunk/src/widgets/PixbufImageSurface.cs	(original)
+++ trunk/src/widgets/PixbufImageSurface.cs	Wed Mar 25 20:26:34 2009
@@ -29,26 +29,13 @@
 
 namespace Mistelix.Widgets
 {
-	public class PixbufImageSurface : ImageSurface, IDisposable
-	{
-		private delegate void cairo_destroy_func_t (IntPtr userdata);
-
-		private static bool is_le = BitConverter.IsLittleEndian;
-		private static int user_data_key = 0;
-		private static cairo_destroy_func_t destroy_func;
-		       
+	public class PixbufImageSurface : DataImageSurface, IDisposable
+	{ 
 		private static void DestroyPixelData (IntPtr data)
 		{
 			Marshal.FreeHGlobal (data);
 		}
 
-		static PixbufImageSurface ()
-		{
-		    	destroy_func = new cairo_destroy_func_t (DestroyPixelData);
-		}
-
-		private IntPtr data;
-
 		public PixbufImageSurface (Gdk.Pixbuf pixbuf) : this (pixbuf, false)
 		{
 		}
@@ -70,49 +57,12 @@
 			this.data = data;
 
 			CreateSurface (width, height, channels, rowstride, pixels);
-			SetDestroyFunc ();
 
 			if (pixbuf != null && pixbuf.Handle != IntPtr.Zero) {
 				pixbuf.Dispose ();
 			}
 		}
 
-		// Converts from Cairo 32-bits to GTK 24 bits (3 channels)
-		public byte[] Get24bitsPixBuf ()
-		{
-			bool is_le = BitConverter.IsLittleEndian;
-			int pos_cairo = 0;
-			int pos_gdk = 0;
-			byte [] cairo_pixels;
-			byte [] pixels;
-			int width, height;
-			const int channels = 3;
-
-			width = Width;
-			height = Height;
-			pixels = new byte [height * Width * channels];
-			cairo_pixels = Data;
-
-			for (int h = 0; h < height; h++) 
-			{
-				for (int i = 0; i < width; i++)
-				{
-					if (is_le) {
-						pixels[pos_gdk + 2] = cairo_pixels[pos_cairo + 0];
-					   	pixels[pos_gdk + 1] = cairo_pixels[pos_cairo + 1];
-					   	pixels[pos_gdk + 0] = cairo_pixels[pos_cairo + 2];
-					} else {
-					   	pixels[pos_gdk + 0] = cairo_pixels[pos_cairo + 1];
-					   	pixels[pos_gdk + 1] = cairo_pixels[pos_cairo + 2];
-					   	pixels[pos_gdk + 2] = cairo_pixels[pos_cairo + 3];
-					}
-
-					pos_gdk += channels;
-					pos_cairo += 4;
-				}
-			}
-			return pixels;
-		}
 
 		private unsafe void CreateSurface (int width, int height, int channels, int gdk_rowstride, IntPtr pixels)
 		{
@@ -177,23 +127,6 @@
 			cr.Paint ();
 			cr.Restore ();			
 		}
-
-		[DllImport ("libcairo.so.2")]
-		private static extern Cairo.Status cairo_surface_set_user_data (IntPtr surface, 
-		    ref int key, IntPtr userdata, cairo_destroy_func_t destroy);
-
-		private void SetDestroyFunc ()
-		{
-			try {
-				Status status = cairo_surface_set_user_data (Handle, ref user_data_key, data, destroy_func);
-				if (status != Status.Success) {
-				    throw new ApplicationException (String.Format (
-					"cairo_surface_set_user_data returned {0}", status));
-				}
-			} catch (Exception e) {
-				Console.Error.WriteLine (e);	
-			}
-		}
 	}
 }	
 

Modified: trunk/src/widgets/SlideShowImageView.cs
==============================================================================
--- trunk/src/widgets/SlideShowImageView.cs	(original)
+++ trunk/src/widgets/SlideShowImageView.cs	Wed Mar 25 20:26:34 2009
@@ -1,5 +1,5 @@
 //
-// Copyright (C) 2008 Jordi Mas i Hernandez, jmas softcatala org
+// Copyright (C) 2008-2009 Jordi Mas i Hernandez, jmas softcatala org
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -30,9 +30,11 @@
 using Gdk;
 using Cairo;
 using Mono.Unix;
+using Mono.Addins;
 
 using Mistelix.DataModel;
 using Mistelix.Core;
+using Mistelix.Effects;
 
 namespace Mistelix.Widgets
 {
@@ -50,6 +52,56 @@
 
 	public delegate void ShowImageUpdatedElementsEventHandler (object sender, EventArgs e);
 
+	// Renders a Cairo Image into a tree Cell
+	public class CellRendererCairoImage : CellRenderer 
+	{
+		int width, height;
+		Cairo.ImageSurface surface, default_surface;
+		Gtk.TreeIter iter;
+		SlideShowImageView parent;
+		
+		public CellRendererCairoImage (int width, int height, Cairo.ImageSurface default_surface, SlideShowImageView parent)
+		{
+			this.width = width;
+			this.height = height;
+			this.default_surface = default_surface;
+			this.parent = parent;
+		}
+
+		public Gtk.TreeIter Iter {
+			get { return iter; }
+			set { iter = value; }
+		}
+
+		public override void GetSize (Widget widget, ref Gdk.Rectangle cell_area, out int x_offset, out int y_offset, out int w, out int h)
+		{
+			x_offset = 0;
+			y_offset = 0;
+			w = width + ((int) Xpad) * 2;
+			h = height + ((int) Ypad) * 2;
+		}
+
+		protected override void Render (Gdk.Drawable window, Widget widget, Gdk.Rectangle background, Gdk.Rectangle cell, Gdk.Rectangle expose, CellRendererState flags)
+		{
+			Cairo.Context cr = Gdk.CairoHelper.Create (window);
+			//Console.WriteLine ("Render {0}", DateTime.Now);
+
+			SlideImage image = (SlideImage) parent.store.GetValue (iter, SlideShowImageView.COL_OBJECT);
+			string filename = image.image;
+
+			surface = (DataImageSurface) parent.store.GetValue (iter, SlideShowImageView.COL_CAIROIMAGE);
+
+			if (surface == null) {
+				parent.RequestThumbnail (iter);
+			}
+
+			cr.Rectangle (cell.X, cell.Y, cell.Width, cell.Height);
+			cr.SetSourceSurface (surface != null ? surface : default_surface, cell.X, cell.Y);
+			cr.Paint ();
+			(cr as System.IDisposable).Dispose ();
+		}
+	}
+
 	//
 	// Displays the images selected by the user when creating a slideshow. Drop target.
 	//
@@ -63,32 +115,36 @@
 			Date_Descending
 		}
 
-		ListStore store;
+		public ListStore store; // todo: property
 		Gtk.Image image;
 		BackgroundWorker thumbnailing;
-		Pixbuf def_image;
 		int cnt = 1;
 		bool paint_control = false;
 		readonly int thumbnail_height;
 		readonly int thumbnail_width;
 		readonly string notitle;
-		Dictionary <string, Gdk.Pixbuf> thumbnail_cache;
 		ImageSortType sort_type;
+		IEffect[] effects;
+		Cairo.ImageSurface def_image;
 
 		public const int COL_INDEX = 0;
-		public const int COL_PIXBUF = 1;
+		public const int COL_CAIROIMAGE = 1;
 		public const int COL_DESCRIPTION = 2;
 		public const int COL_OBJECT = 3;
+		public const string EFFECT = "effect";
 
 		static TargetEntry [] tag_dest_target_table = new TargetEntry [] {
 					new TargetEntry ("application/x-mistelix-img", 0, (uint) 2)};
 
 		public ShowImageSelectionEventHandler 		ChangeEvent;
 		public ShowImageUpdatedElementsEventHandler	UpdatedElements;
+		DateTime start_time = DateTime.Now;
+		List <Gtk.TreeIter> iters_list;
 
 		public SlideShowImageView ()
 		{
 			Model = store = CreateStore ();
+			iters_list = new List <Gtk.TreeIter> ();
 
 			notitle = Catalog.GetString ("<No title>");
 	
@@ -103,11 +159,12 @@
 					  DragAction.Copy | DragAction.Move );
 
 			CursorChanged += OnCursorChanged;
-			def_image = Gtk.IconTheme.Default.LoadIcon ("gtk-new", thumbnail_width, (Gtk.IconLookupFlags) 0);
 			thumbnailing = new BackgroundWorker ();
 			thumbnailing.DoWork += new DoWorkEventHandler (DoWork);
 			ButtonPressEvent += new ButtonPressEventHandler (OnButtonPressed);
 			KeyPressEvent += OnKeyPressed;
+
+			CreateDefaultImage ();
 		}
 
 		~SlideShowImageView ()
@@ -115,10 +172,6 @@
 			Dispose (false);
 		}
 
-		public Dictionary <string, Gdk.Pixbuf> Cache {
-			set { thumbnail_cache = value; }
-		}
-
 		public override void Dispose ()
 		{
 			Dispose (true);
@@ -130,13 +183,16 @@
 			Logger.Debug ("SlideShowImageView.Disposing");
 			store.Foreach (delegate (TreeModel model, TreePath path, TreeIter iter)  
 			{
-				Gdk.Pixbuf im = (Gdk.Pixbuf) store.GetValue (iter, COL_PIXBUF);
-				im.Dispose ();
+				DataImageSurface image = (DataImageSurface) store.GetValue (iter, COL_CAIROIMAGE);
+
+				if (image != null)
+					image.Dispose ();
 
 				return false;
 			});
-
+			Logger.Debug ("SlideShowImageView.Disposed");
 			thumbnailing.Dispose ();
+			def_image.Dispose ();
 		}
 
 		public ListStore ListStore {
@@ -151,13 +207,15 @@
 			foreach (SlideShow.Image image in slideshow.images) 
 			{
 				FileInfo fi = new FileInfo (image.image);
-				store.AppendValues (cnt.ToString (), def_image, 
-					image.title == null ? notitle : image.title,
+				store.AppendValues (cnt.ToString (), null, 
+					image.Title == null ? notitle : image.Title,
 					image);
 				cnt++;
 			}
 
-			thumbnailing.RunWorkerAsync (store);
+			if (thumbnailing.IsBusy == false)
+				thumbnailing.RunWorkerAsync (store);
+
 			UpdateButtonSensitivity ();
 		}
 
@@ -168,7 +226,10 @@
 			title_cell.Edited += OnDescriptionCellEdited;
 
 			AppendColumn (Catalog.GetString ("#"), new CellRendererText (), "text", COL_INDEX);
-			AppendColumn (Catalog.GetString ("Image"), new CellRendererPixbuf (), "pixbuf", COL_PIXBUF);
+			AppendColumn (Catalog.GetString ("Image"), 
+				new CellRendererCairoImage (thumbnail_width, thumbnail_height, def_image, this), 
+				new TreeCellDataFunc (SetTreeIter));
+
 			AppendColumn (Catalog.GetString ("Description"), title_cell, "text", COL_DESCRIPTION);
 		}
 
@@ -183,15 +244,31 @@
 			if (String.Compare (notitle, args.NewText) != 0) 
 			{
 				if (args.NewText.Length == 0) { // Title has been clean up
-					image.title = null;
+					image.Title = null;
 					store.SetValue (iter, COL_DESCRIPTION, notitle);
 				}
 				else {
-					image.title = args.NewText;
-					store.SetValue (iter, COL_DESCRIPTION, image.title);
+					image.Title = args.NewText;
+					store.SetValue (iter, COL_DESCRIPTION, image.Title);
 				}
 			}
 		}
+		
+		// Sets the Iter element to paint into the CellRendererCairoImage (shared by all cells)
+		void SetTreeIter (Gtk.TreeViewColumn tree_column, Gtk.CellRenderer c, Gtk.TreeModel tree_model, Gtk.TreeIter iter)
+		{
+			((CellRendererCairoImage)c).Iter = iter;
+		}
+
+		public void RequestThumbnail (Gtk.TreeIter iter)
+		{
+			lock (iters_list) {
+				iters_list.Add (iter);
+			}
+	
+			if (thumbnailing.IsBusy == false)
+				thumbnailing.RunWorkerAsync (store);
+		}
 
 		void HandleTargetDragDrop (object sender, DragDropArgs args)
 		{
@@ -221,14 +298,16 @@
 			args.RetVal = true;
 			UpdateButtonSensitivity ();
 			Gtk.Drag.Finish (args.Context, true, false, args.Time);
-			thumbnailing.RunWorkerAsync (store);
+
+			if (thumbnailing.IsBusy == false)
+				thumbnailing.RunWorkerAsync (store);
 		}
 
 		// Adds a new dropped file into the view
 		void LoadFile (string file)
 		{
 			FileInfo fi = new FileInfo (file);
-			store.AppendValues (cnt.ToString (), def_image, notitle,
+			store.AppendValues (cnt.ToString (), null, notitle,
 				new SlideImage (fi.FullName));
 			cnt++;
 		}
@@ -241,7 +320,7 @@
 		ListStore CreateStore ()
 		{
 			// Image number, image pixbuf, object 
-			return new ListStore (typeof (string), typeof (Gdk.Pixbuf), typeof (string), typeof (SlideImage));
+			return new ListStore (typeof (string), typeof (DataImageSurface), typeof (string), typeof (SlideImage));
 		}
 
 		protected override void OnSizeAllocated (Gdk.Rectangle allocation)
@@ -297,43 +376,37 @@
 				ChangeEvent (this, new ShowImageSelectionEventArgs (images));
 		}
 
-		// TODO: Only new dragged elements should be loaded
-		public void DoWork (object sender, DoWorkEventArgs e)
-        	{   
+		void DoWork (object sender, DoWorkEventArgs e)
+        	{
 			Logger.Debug ("SlideShowImageView.Dowork start");
-			ListStore store = (ListStore) e.Argument;
-			
-			store.Foreach (delegate (TreeModel model, TreePath path, TreeIter iter)  
-			{
+
+			while (true) {
+	
+				Gtk.TreeIter iter;
+
+				lock (iters_list) {
+					if (iters_list.Count == 0) 
+						break;
+
+					iter = iters_list [0];
+					iters_list.RemoveAt (0);
+				}
+
 				SlideImage image = (SlideImage) store.GetValue (iter, COL_OBJECT);
 				string filename = image.image;
-				Gdk.Pixbuf scaled;
+				DataImageSurface prev_image;
+				DataImageSurface cairo_image;
 
-				try {
-					lock (thumbnail_cache) {
-						scaled = thumbnail_cache [filename];
-					}
-				}
-				catch {
-					scaled = null;
-				}
+				prev_image = (DataImageSurface) store.GetValue (iter, COL_CAIROIMAGE);
 
-				if (scaled == null) {
-					Logger.Debug ("file->{0}", filename);
-					Gdk.Pixbuf im = new Gdk.Pixbuf (filename);
-					int max = Math.Max (im.Width, im.Height);
-					scaled = im.ScaleSimple (thumbnail_width * im.Width / max, thumbnail_height * im.Height / max, InterpType.Nearest);
-					im.Dispose ();
+				if (prev_image != null) {
+					continue;
 				}
+				cairo_image = image.GetThumbnail (thumbnail_width, thumbnail_height);
+				Application.Invoke (delegate { store.SetValue (iter, COL_CAIROIMAGE, cairo_image); });
+			}
 
-				Application.Invoke (delegate {
-						store.SetValue (iter, COL_PIXBUF, scaled);
-					} );
-				
-				return false;
-			});
-
-			Logger.Debug ("SlideShowImageView.Dowork  end");
+			Logger.Debug ("SlideShowImageView.Dowork end");
 		}
 
 		public void MoveUpSelectedElements ()
@@ -410,16 +483,35 @@
 		void OnButtonPressed (object o, ButtonPressEventArgs args)
 		{
 			GtkMenu menu;
+			ExtensionNodeList nodelist;
 
 			if (args.Event.Button != 3)
 				return;
 
 			menu = new GtkMenu ();
 
+			nodelist = AddinManager.GetExtensionNodes ("/Mistelix/Effects");
+			if (nodelist.Count > 0) {
+
+				effects = new IEffect [nodelist.Count];
+				int pos = 0;
+				foreach (TypeExtensionNode node in nodelist) {
+					IEffect effect = (IEffect) node.CreateInstance ();
+
+					MenuItem item = menu.AddItem (effect.DisplayName, OnEffect);
+					item.SetData (EFFECT, new IntPtr (pos));
+					effects [pos] = effect;
+					pos++;
+				}
+				
+				menu.AddSeparator ();
+			}
+
 			menu.AddItem (Catalog.GetString ("Sort by filename (Ascending)"), OnSortAscendingbyName);
 			menu.AddItem (Catalog.GetString ("Sort by filename (Descending)"), OnSortDescendingbyName);
 			menu.AddItem (Catalog.GetString ("Sort by date on disc (Ascending)"), OnSortAscendingbyDate);
 			menu.AddItem (Catalog.GetString ("Sort by date on disc (Descending)"), OnSortDescendingbyDate);
+			menu.AddSeparator ();
 			menu.AddItem (Catalog.GetString ("Remove selected images"), OnRemoveSelectedImage);
 			menu.AddItem (Catalog.GetString ("Remove all images"), OnRemoveImages);
 
@@ -516,5 +608,62 @@
 				OnRemoveSelectedImage (sender, EventArgs.Empty);
 			}
 		}
+
+		void OnEffect (object obj, EventArgs e)
+		{
+			TreeIter iter;
+			TreePath[] paths;
+			IEffect effect;
+			SlideImage image, effect_image;
+			DataImageSurface cairo_image, prev_image;
+
+			paths = Selection.GetSelectedRows ();
+
+			if (paths.Length == 0)
+				return;
+
+			int pos = (int) ((MenuItem) obj).GetData (EFFECT);
+			effect = effects [pos];
+
+			Logger.Debug ("OnEffect {0} {1}", paths.Length, effect.Name);
+
+			for (int i = 0; i < paths.Length; i++)
+			{
+				store.GetIter (out iter, paths[i]);
+				prev_image = (DataImageSurface) store.GetValue (iter, COL_CAIROIMAGE);
+				image = (SlideImage) store.GetValue (iter, COL_OBJECT);
+				image.AddEffect (effect.Name);
+
+				cairo_image = image.GetThumbnail (thumbnail_width, thumbnail_height);
+				store.SetValue (iter, COL_CAIROIMAGE, cairo_image);
+
+				if (prev_image != null)
+					prev_image.Dispose ();
+			}
+		}
+	
+		// Create the image indicating that there is no preview available yet
+		void CreateDefaultImage ()
+		{
+			Cairo.Context cr;
+			int box_width, box_height;
+			const int min_xpad = 10;
+	
+			def_image = new Cairo.ImageSurface (Format.Argb32, thumbnail_width, thumbnail_height);
+			cr = new Cairo.Context (def_image);
+
+			using (Pango.Layout layout = Pango.CairoHelper.CreateLayout (cr))
+			{
+				layout.FontDescription = Pango.FontDescription.FromString ("sans 8");
+				layout.SetMarkup (Catalog.GetString ("No preview available"));
+				layout.Alignment = Pango.Alignment.Center;
+				layout.SingleParagraphMode = false;
+				layout.Width = (int) ((thumbnail_width - min_xpad * 2) * Pango.Scale.PangoScale);
+				layout.GetPixelSize (out box_width, out box_height);
+				cr.MoveTo (min_xpad, (thumbnail_height - box_height) / 2);
+	     			Pango.CairoHelper.ShowLayout (cr, layout);
+			}
+			((IDisposable)cr).Dispose ();
+		}
 	}
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]