diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..412eeda
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,22 @@
+# Auto detect text files and perform LF normalization
+* text=auto
+
+# Custom for Visual Studio
+*.cs diff=csharp
+*.sln merge=union
+*.csproj merge=union
+*.vbproj merge=union
+*.fsproj merge=union
+*.dbproj merge=union
+
+# Standard to msysgit
+*.doc diff=astextplain
+*.DOC diff=astextplain
+*.docx diff=astextplain
+*.DOCX diff=astextplain
+*.dot diff=astextplain
+*.DOT diff=astextplain
+*.pdf diff=astextplain
+*.PDF diff=astextplain
+*.rtf diff=astextplain
+*.RTF diff=astextplain
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b9d6bd9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,215 @@
+#################
+## Eclipse
+#################
+
+*.pydevproject
+.project
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.classpath
+.settings/
+.loadpath
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# CDT-specific
+.cproject
+
+# PDT-specific
+.buildpath
+
+
+#################
+## Visual Studio
+#################
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+
+# Build results
+
+[Dd]ebug/
+[Rr]elease/
+x64/
+build/
+[Bb]in/
+[Oo]bj/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+*_i.c
+*_p.c
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.log
+*.scc
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+*.ncrunch*
+.*crunch*.local.xml
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.Publish.xml
+*.pubxml
+
+# NuGet Packages Directory
+## TODO: If you have NuGet Package Restore enabled, uncomment the next line
+#packages/
+
+# Windows Azure Build Output
+csx
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+sql/
+*.Cache
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.[Pp]ublish.xml
+*.pfx
+*.publishsettings
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file to a newer
+# Visual Studio version. Backup files are not needed, because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+App_Data/*.mdf
+App_Data/*.ldf
+
+#############
+## Windows detritus
+#############
+
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Mac crap
+.DS_Store
+
+
+#############
+## Python
+#############
+
+*.py[co]
+
+# Packages
+*.egg
+*.egg-info
+dist/
+build/
+eggs/
+parts/
+var/
+sdist/
+develop-eggs/
+.installed.cfg
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+
+#Translations
+*.mo
+
+#Mr Developer
+.mr.developer.cfg
diff --git a/EyePaint.sln b/EyePaint.sln
new file mode 100644
index 0000000..69bd8b9
--- /dev/null
+++ b/EyePaint.sln
@@ -0,0 +1,26 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Express 2013 for Windows Desktop
+VisualStudioVersion = 12.0.21005.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EyePaint", "EyePaint\EyePaint.csproj", "{0A8492D9-B72D-4D5E-AF92-01119AF21737}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {0A8492D9-B72D-4D5E-AF92-01119AF21737}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {0A8492D9-B72D-4D5E-AF92-01119AF21737}.Debug|x86.ActiveCfg = Debug|x86
+ {0A8492D9-B72D-4D5E-AF92-01119AF21737}.Debug|x86.Build.0 = Debug|x86
+ {0A8492D9-B72D-4D5E-AF92-01119AF21737}.Release|Any CPU.ActiveCfg = Release|x86
+ {0A8492D9-B72D-4D5E-AF92-01119AF21737}.Release|x86.ActiveCfg = Release|x86
+ {0A8492D9-B72D-4D5E-AF92-01119AF21737}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/EyePaint/App.config b/EyePaint/App.config
new file mode 100644
index 0000000..a6e5dcc
--- /dev/null
+++ b/EyePaint/App.config
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ default@default.default
+
+
+ default.default.default
+
+
+ a81591b96318cd9799982c11663df750
+
+
+ 05e84b538d7d4cf5
+
+
+ 10
+
+
+ 10
+
+
+ 30
+
+
+ 100
+
+
+ 10
+
+
+
+
+
+ Måla med ögonen
+
+
+ Målning med ögonen
+
+
+ En målning gjord med ögonen.
+
+
+
+
+
+
+
diff --git a/EyePaint/App.xaml b/EyePaint/App.xaml
new file mode 100644
index 0000000..0dc0936
--- /dev/null
+++ b/EyePaint/App.xaml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/EyePaint/App.xaml.cs b/EyePaint/App.xaml.cs
new file mode 100644
index 0000000..25f0ee0
--- /dev/null
+++ b/EyePaint/App.xaml.cs
@@ -0,0 +1,253 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Mail;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media.Animation;
+using Tobii.Gaze.Core;
+
+namespace EyePaint
+{
+ ///
+ /// Globally XAML bindable properties
+ ///
+ public class GlobalProperties : DependencyObject
+ {
+ public static DependencyProperty TrackingProperty = DependencyProperty.Register("Tracking", typeof(bool), typeof(GlobalProperties));
+ public bool Tracking
+ {
+ get { return (bool)GetValue(TrackingProperty); }
+ set { SetValue(TrackingProperty, value); }
+ }
+ }
+
+ ///
+ /// Eye tracking logic and gaze enabled UI elements.
+ ///
+ public partial class App : Application
+ {
+ IEyeTracker iet;
+ Size resolution;
+ List gazes;
+ Dictionary offsets;
+ TimeSpan? time;
+ PaintWindow paintWindow;
+ ResultWindow resultWindow;
+ public GlobalProperties Globals { get; set; }
+ bool tracking;
+ public bool Resettable;
+ int notTracking;
+
+ [DllImport("User32.dll")]
+ static public extern bool SetCursorPos(int X, int Y);
+
+ ///
+ /// Connect to the eye tracker on application startup.
+ ///
+ void onStartup(object s, StartupEventArgs e)
+ {
+ new SettingsWindow();
+
+ Globals = new GlobalProperties();
+ var connected = connect();
+ if (!connected)
+ {
+ SendErrorReport("(EyePaint) Startup Error", "The eye tracker could not be found. Ensure that the cable is connected, restart the computer, and try launching the application again.");
+ new ErrorWindow().ShowDialog();
+ }
+ }
+
+ ///
+ /// Connect to the eye tracker and start gaze tracking. Return true iff a successful connection has been established.
+ ///
+ bool connect()
+ {
+ int retry = 0;
+ while (++retry < EyePaint.Properties.Settings.Default.ConnectionAttempts) try
+ {
+ // Connect to eye tracker.
+ gazes = new List();
+ offsets = new Dictionary();
+ Uri url = new EyeTrackerCoreLibrary().GetConnectedEyeTracker();
+ iet = new EyeTracker(url);
+ iet.EyeTrackerError += onEyeTrackerError;
+ iet.GazeData += onGazeData;
+ Task.Factory.StartNew(() => iet.RunEventLoop());
+ iet.Connect();
+ iet.StartTracking();
+ Mouse.OverrideCursor = Cursors.None;
+
+ // Display image result on a secondary screen.
+ resultWindow = new ResultWindow();
+ if (System.Windows.Forms.SystemInformation.MonitorCount > 1)
+ {
+ var wa = System.Windows.Forms.Screen.AllScreens[1].WorkingArea;
+ resultWindow.WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ resultWindow.Left = wa.Left;
+ resultWindow.Top = wa.Top;
+ resultWindow.Topmost = true;
+ resultWindow.Show();
+ }
+
+ // Open paint window.
+ paintWindow = new PaintWindow();
+ paintWindow.Loaded += (_, __) => resolution = new Size(paintWindow.ActualWidth, paintWindow.ActualHeight);
+ paintWindow.ContentRendered += (_, __) => resultWindow.SetImageSource(paintWindow.Raster.Source);
+ paintWindow.Show();
+ return true;
+ }
+ catch (Exception)
+ {
+ Thread.Sleep(1000);
+ }
+ return false;
+ }
+
+ ///
+ /// Handles error data from the eye tracker. The application will be halted and the user will see an error on the screen while the application attempts to reestablish a connection to the eye tracker. If a connection cannot be reestablished a notification email is sent to the admin email.
+ ///
+ void onEyeTrackerError(object s, EyeTrackerErrorEventArgs e)
+ {
+ var ew = new ErrorWindow();
+ ew.Show();
+ var reconnected = connect();
+ if (reconnected) ew.Close();
+ else SendErrorReport("(EyePaint) Error " + e.ErrorCode, "The eye tracker encountered an error. Error: " + e.Message + ". Try rebooting the system.");
+ }
+
+ ///
+ /// Handles data from the eye tracker. Places the mouse cursor at the average gaze point so that WPF mouse events can be used throughout the application.
+ ///
+ void onGazeData(object s, GazeDataEventArgs e)
+ {
+ if (e.GazeData.TrackingStatus == TrackingStatus.NoEyesTracked)
+ {
+ // Determine if the user is inactive or just blinking.
+ if (++notTracking > EyePaint.Properties.Settings.Default.Blink)
+ {
+ if (tracking)
+ {
+ tracking = false;
+ Dispatcher.Invoke(() => Globals.Tracking = tracking);
+ }
+ }
+ }
+ else
+ {
+ // Determine if the user reactived the session.
+ if (notTracking > 0)
+ {
+ notTracking = 0;
+ if (!tracking)
+ {
+ tracking = true;
+ Dispatcher.Invoke(() => Globals.Tracking = tracking);
+ }
+ }
+
+ // Retrieve available gaze information from the eye tracker.
+ var left = (e.GazeData.TrackingStatus == TrackingStatus.BothEyesTracked || e.GazeData.TrackingStatus == TrackingStatus.OneEyeTrackedProbablyLeft || e.GazeData.TrackingStatus == TrackingStatus.OnlyLeftEyeTracked || e.GazeData.TrackingStatus == TrackingStatus.OneEyeTrackedUnknownWhich) ? e.GazeData.Left.GazePointOnDisplayNormalized : new Point2D(0, 0);
+ var right = (e.GazeData.TrackingStatus == TrackingStatus.BothEyesTracked || e.GazeData.TrackingStatus == TrackingStatus.OneEyeTrackedProbablyRight || e.GazeData.TrackingStatus == TrackingStatus.OnlyRightEyeTracked || e.GazeData.TrackingStatus == TrackingStatus.OneEyeTrackedUnknownWhich) ? e.GazeData.Right.GazePointOnDisplayNormalized : new Point2D(0, 0);
+
+ // Determine new gaze point position on the screen.
+ var newGazePoint = new Point(resolution.Width * (left.X + right.X) / 2, resolution.Height * (left.Y + right.Y) / 2);
+ gazes.Add(newGazePoint);
+
+ // Calculate average gaze point (i.e. naive noise reduction).
+ while (gazes.Count > EyePaint.Properties.Settings.Default.Stability) gazes.RemoveAt(0);
+ var gazePoint = new Point(gazes.Average(p => p.X), gazes.Average(p => p.Y));
+
+ // Calibrate average gaze point with known offsets.
+ if (offsets.Count == 1) gazePoint += offsets.Values.First();
+ var distances = offsets.Select(kvp => (gazePoint - kvp.Key).Length);
+ var distancesRatios = distances.Select(d => d / distances.Sum());
+ foreach (var v in offsets.Values.Zip(distancesRatios, (o, d) => (1.0 - d) * o)) gazePoint += v;
+
+ // Place the mouse cursor at the gaze point so mouse events can be used throughout the application.
+ SetCursorPos((int)gazePoint.X, (int)gazePoint.Y);
+ }
+ }
+
+ ///
+ /// When a gaze button is stared at for long enough a click event is raised on each animation completion.
+ ///
+ void onGazeButtonFocused(object s, EventArgs e)
+ {
+ var c = s as Clock;
+
+ if (time.HasValue && c.CurrentTime.HasValue && c.CurrentTime.Value < time.Value)
+ {
+ // Claim click.
+ time = null;
+
+ // Find button.
+ var activeWindow = Application.Current.Windows.OfType().Single(w => w.IsActive);
+ var focusedButton = FocusManager.GetFocusedElement(activeWindow) as Button;
+
+ // Store calibration offset.
+ if (gazes.Count > 0)
+ {
+ var expectedPoint = focusedButton.PointToScreen(new Point(focusedButton.ActualWidth / 2, focusedButton.ActualHeight / 2));
+ var actualPoint = Mouse.GetPosition(activeWindow);
+ offsets[actualPoint] = expectedPoint - actualPoint;
+ }
+ // Raise click event.
+ focusedButton.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
+ }
+
+ time = (c.CurrentState == ClockState.Active) ? c.CurrentTime : null;
+ }
+
+ ///
+ /// When a gaze button is touched a button click event is raised.
+ ///
+ void onGazeButtonTouched(object s, EventArgs e)
+ {
+ (s as Button).RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); //TODO Verify this works with an actual touch screen.
+ }
+
+ ///
+ /// Attempt to send an error report via email to an application defined admin email. If email could not be sent the error message will be displayed in a message box instead.
+ ///
+ public void SendErrorReport(string subject, string body)
+ {
+ try
+ {
+ var m = new MailMessage(EyePaint.Properties.Settings.Default.AdminEmail, EyePaint.Properties.Settings.Default.AdminEmail, subject, body);
+ var c = new SmtpClient(EyePaint.Properties.Settings.Default.SmtpServer);
+ c.Credentials = CredentialCache.DefaultNetworkCredentials;
+ c.Send(m);
+ }
+ catch (Exception)
+ {
+ MessageBox.Show("Email could not be sent. Make sure the application is configured correctly. Email: " + subject + " - " + body);
+ }
+ }
+
+ ///
+ /// Clear previous gaze data if neccessary.
+ ///
+ public void Reset()
+ {
+ if (Resettable)
+ {
+ offsets.Clear();
+ time = null;
+ var oldPaintWindow = paintWindow;
+ paintWindow = new PaintWindow();
+ paintWindow.ContentRendered += (s, e) =>
+ {
+ oldPaintWindow.Close();
+ resultWindow.SetImageSource(paintWindow.Raster.Source);
+ };
+ paintWindow.Show();
+ }
+ }
+ }
+}
diff --git a/EyePaint/CalibrationWindow.xaml b/EyePaint/CalibrationWindow.xaml
new file mode 100644
index 0000000..48bc89e
--- /dev/null
+++ b/EyePaint/CalibrationWindow.xaml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/EyePaint/CalibrationWindow.xaml.cs b/EyePaint/CalibrationWindow.xaml.cs
new file mode 100644
index 0000000..553a93e
--- /dev/null
+++ b/EyePaint/CalibrationWindow.xaml.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Windows;
+
+namespace EyePaint
+{
+ ///
+ /// Used to reduce calibration offset errors amongst different users.
+ ///
+ public partial class CalibrationWindow : Window
+ {
+ public CalibrationWindow(Window owner)
+ {
+ InitializeComponent();
+ Owner = owner;
+ ShowDialog();
+ }
+
+ void onClick(object s, RoutedEventArgs e)
+ {
+ DialogResult = true;
+ }
+ }
+}
diff --git a/EyePaint/DialogWindow.xaml b/EyePaint/DialogWindow.xaml
new file mode 100644
index 0000000..613bfd8
--- /dev/null
+++ b/EyePaint/DialogWindow.xaml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/EyePaint/DialogWindow.xaml.cs b/EyePaint/DialogWindow.xaml.cs
new file mode 100644
index 0000000..7c0b67d
--- /dev/null
+++ b/EyePaint/DialogWindow.xaml.cs
@@ -0,0 +1,29 @@
+using System;
+using System.ComponentModel;
+using System.Windows;
+
+namespace EyePaint
+{
+ ///
+ /// Used to display a yes/no dialog to the user.
+ ///
+ public partial class DialogWindow : Window
+ {
+ public DialogWindow(Window owner)
+ {
+ InitializeComponent();
+ Owner = owner;
+ ShowDialog();
+ }
+
+ void onConfirm(object s, EventArgs e)
+ {
+ DialogResult = true;
+ }
+
+ void onCancel(object s, EventArgs e)
+ {
+ DialogResult = false;
+ }
+ }
+}
diff --git a/EyePaint/ErrorWindow.xaml b/EyePaint/ErrorWindow.xaml
new file mode 100644
index 0000000..1e3879e
--- /dev/null
+++ b/EyePaint/ErrorWindow.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/EyePaint/ErrorWindow.xaml.cs b/EyePaint/ErrorWindow.xaml.cs
new file mode 100644
index 0000000..9ff6d6e
--- /dev/null
+++ b/EyePaint/ErrorWindow.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+using System.Windows.Input;
+
+namespace EyePaint
+{
+ ///
+ /// Used to display an error message to the user when the appliction is experiencing a critical halt, such as hardware faults.
+ ///
+ public partial class ErrorWindow : Window
+ {
+ public ErrorWindow()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/EyePaint/EyePaint.csproj b/EyePaint/EyePaint.csproj
new file mode 100644
index 0000000..6104d04
--- /dev/null
+++ b/EyePaint/EyePaint.csproj
@@ -0,0 +1,222 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {0A8492D9-B72D-4D5E-AF92-01119AF21737}
+ WinExe
+ Properties
+ EyePaint
+ EyePaint
+ v4.5.1
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+
+ false
+ C:\Users\Carl\Desktop\EyePaint\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 2
+ 2.0.0.%2a
+ false
+ true
+ true
+ true
+
+
+ true
+ bin\x86\Debug\
+ DEBUG;TRACE
+ full
+ x86
+ prompt
+ ManagedMinimumRules.ruleset
+ true
+ true
+
+
+ bin\x86\Release\
+ TRACE
+ true
+ pdbonly
+ x86
+ prompt
+ ManagedMinimumRules.ruleset
+ true
+
+
+ 9AD8DE16F6D79FDF7C35059EDCBF5F203AC1B2FA
+
+
+ EyePaint_TemporaryKey.pfx
+
+
+ true
+
+
+ false
+
+
+ LocalIntranet
+
+
+ Properties\app.manifest
+
+
+
+ ..\packages\FlickrNet.3.14.0\lib\net20\FlickrNet.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 4.0
+
+
+ False
+ ..\..\..\..\..\..\..\Program Files (x86)\Tobii\Tobii EyeX\Tobii.Gaze.Core.NET.dll
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ CalibrationWindow.xaml
+
+
+ DialogWindow.xaml
+
+
+ ErrorWindow.xaml
+
+
+ ResultWindow.xaml
+
+
+ SettingsWindow.xaml
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+ Designer
+
+
+ App.xaml
+ Code
+
+
+ PaintWindow.xaml
+ Code
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+ Code
+
+
+ True
+ True
+ Resources.resx
+
+
+ True
+ Settings.settings
+ True
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+ Designer
+
+
+
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+
+
+
+ False
+ Microsoft .NET Framework 4.5.1 %28x86 and x64%29
+ true
+
+
+ False
+ .NET Framework 3.5 SP1 Client Profile
+ false
+
+
+ False
+ .NET Framework 3.5 SP1
+ false
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/EyePaint/FlickrNet.chm b/EyePaint/FlickrNet.chm
new file mode 100644
index 0000000..46acf8e
Binary files /dev/null and b/EyePaint/FlickrNet.chm differ
diff --git a/EyePaint/PaintWindow.xaml b/EyePaint/PaintWindow.xaml
new file mode 100644
index 0000000..67d55e5
--- /dev/null
+++ b/EyePaint/PaintWindow.xaml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/EyePaint/PaintWindow.xaml.cs b/EyePaint/PaintWindow.xaml.cs
new file mode 100644
index 0000000..0df24cb
--- /dev/null
+++ b/EyePaint/PaintWindow.xaml.cs
@@ -0,0 +1,459 @@
+using FlickrNet;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Media.Effects;
+using System.Windows.Media.Imaging;
+using System.Windows.Threading;
+
+namespace EyePaint
+{
+ ///
+ /// Model representing a paint blob in the application.
+ ///
+ struct Tree
+ {
+ public Point Root;
+ public PointCollection Leaves;
+ public Dictionary Parents;
+
+ public Tree(Point p, int maxBranches, ref Random rng)
+ {
+ Root = p;
+ Leaves = new PointCollection();
+ Parents = new Dictionary();
+ for (int i = 0; i < rng.Next((maxBranches + 1) / 2, maxBranches + 1); ++i) Leaves.Add(Root);
+ Parents[Root] = Root;
+ }
+ }
+
+ ///
+ /// Graphical interpretation parameters of paint strokes in the application.
+ ///
+ struct Shape
+ {
+ public double MaxBranches, BranchStepLength, BranchStraightness, GenerationRotation, ColorVariety, VerticesSize, VerticesSquashVariety, CenterSize, CenterOpacity, EdgesOpacity, VerticesOpacity, HullOpacity;
+ }
+
+ ///
+ /// Lets the user select different shapes and colors and paint them onto a canvas.
+ ///
+ public partial class PaintWindow : Window
+ {
+ Random rng = new Random();
+ DispatcherTimer paintTimer;
+ Tree model;
+ Point gaze;
+ List gazes = new List();
+ Shape shape;
+ Color color;
+ HashSet shapes = new HashSet();
+ HashSet colors = new HashSet();
+ DateTime usage;
+ Dictionary shapeUsage = new Dictionary();
+ Dictionary colorUsage = new Dictionary();
+
+ public PaintWindow()
+ {
+ InitializeComponent();
+ }
+
+ void onLoaded(object s, RoutedEventArgs e)
+ {
+ // Render clock. Note: single-threaded. Approximately 20 FPS.
+ (paintTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(50), DispatcherPriority.Render, (_, __) => paint(ref this.model, Raster.Source as RenderTargetBitmap), Dispatcher)).Stop();
+
+ // Initialize drawing.
+ Raster.Source = createDrawing((int)ActualWidth, (int)ActualHeight);
+ }
+
+ void onContentRendered(object s, EventArgs e)
+ {
+ // Choose initial shape and color by simulating a button click.
+ ShapeButton.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));
+ ColorButton.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent));
+
+ // Perform initial offset calibration.
+ while (!new CalibrationWindow(this).DialogResult.Value) ;
+
+ // Reset window on user inactivity. Note: IsEnabled is used with the eye tracker to determine user status.
+ IsEnabledChanged += (_s, _e) =>
+ {
+ var sb = (FindResource("InactivityStoryboard") as Storyboard);
+ if (!(bool)_e.NewValue) sb.Begin();
+ else sb.Stop();
+ };
+
+ (App.Current as App).Resettable = true;
+ }
+
+ void onUnloaded(object s, RoutedEventArgs e)
+ {
+ foreach (var w in OwnedWindows) (w as Window).Close();
+ }
+
+ void onInactivity(object s, EventArgs e)
+ {
+ if (IsVisible) (App.Current as App).Reset();
+ }
+
+ void onCanvasMouseDown(object s, MouseButtonEventArgs e)
+ {
+ gaze = calculateGaze(e.GetPosition(s as Canvas));
+ startPainting();
+ PaintMarker.Visibility = Visibility.Hidden;
+ (PaintMarker.FindResource("GazePaintStoryboard") as Storyboard).Stop();
+ }
+
+ void onCanvasMouseUp(object s, MouseButtonEventArgs e)
+ {
+ stopPainting();
+ PaintMarker.Visibility = Visibility.Visible;
+ (PaintMarker.FindResource("GazePaintStoryboard") as Storyboard).Begin();
+ }
+
+ void onCanvasMouseEnter(object s, MouseEventArgs e)
+ {
+ (PaintMarker.FindResource("GazePaintStoryboard") as Storyboard).Begin();
+ }
+
+ void onCanvasMouseLeave(object s, MouseEventArgs e)
+ {
+ stopPainting();
+ (PaintMarker.FindResource("GazePaintStoryboard") as Storyboard).Stop();
+ }
+
+ void onCanvasMouseMove(object s, MouseEventArgs e)
+ {
+ var p = calculateGaze(e.GetPosition(s as Canvas));
+
+ Canvas.SetLeft(PaintMarker, p.X - PaintMarker.ActualWidth / 2);
+ Canvas.SetTop(PaintMarker, p.Y - PaintMarker.ActualHeight / 2);
+
+ if (paintTimer.IsEnabled && (model.Root - p).Length > Properties.Settings.Default.Spacing / 2) model = new Tree(p, (int)shape.MaxBranches, ref rng);
+ if ((gaze - p).Length > Properties.Settings.Default.Spacing)
+ {
+ var sb = PaintMarker.FindResource("GazePaintStoryboard") as Storyboard;
+ switch (sb.GetCurrentState())
+ {
+ case ClockState.Active: sb.Seek(TimeSpan.Zero); break;
+ case ClockState.Filling: sb.Seek(TimeSpan.Zero); stopPainting(); break;
+ }
+ }
+ gaze = p;
+ }
+
+ void onGazePaint(object s, EventArgs e)
+ {
+ startPainting();
+ }
+
+ void onPublishButtonClick(object s, EventArgs e)
+ {
+ if (new DialogWindow(this).DialogResult.Value) { (App.Current as App).Resettable = false; PaintControls.IsEnabled = false; (FindResource("SaveDrawingStoryboard") as Storyboard).Begin(); }
+ }
+
+ void onResetButtonClick(object s, EventArgs e)
+ {
+ if (new DialogWindow(this).DialogResult.Value) (App.Current as App).Reset();
+ }
+
+ void onShapeButtonClick(object s, EventArgs e)
+ {
+ // Sort shapes by usage.
+ shapes.OrderBy(sh => shapeUsage[sh]);
+
+ // Remove underused shapes.
+ foreach (var sh in shapes.ToList())
+ {
+ if (shapeUsage[sh].Seconds < 0.1 * shapeUsage.Max(kvp => kvp.Value).Seconds || shapeUsage[sh] == TimeSpan.Zero)
+ {
+ shapes.Remove(sh);
+ shapeUsage.Remove(sh);
+ }
+ }
+
+ // Determine whether to pick a previous shape or generate a new.
+ if (shapes.Count > 0 && rng.NextDouble() <= 0.01 * shapes.Count - 0.1)
+ {
+ // Pick a previously used shape.
+ shape = shapes.ElementAt((shapes.ToList().IndexOf(shape) + 1) % shapes.Count); //TODO Verify that the index applies to the HashSet.
+ }
+ else
+ {
+ // Generate a new shape.
+ var maxBranches = rng.Next(1, 100);
+ var branchStepLength = Math.Sqrt(rng.Next(1, 1001));
+ var branchStraightness = Math.Sqrt(rng.NextDouble());
+ var generationRotation = rng.NextDouble();
+ var colorVariety = rng.NextDouble();
+ var verticesSize = Math.Sqrt(rng.Next(0, 101));
+ var verticesSquashVariety = rng.NextDouble() * rng.NextDouble();
+ var centerSize = (branchStepLength < 10) ? rng.Next(10, 101) : Math.Sqrt(rng.Next(1, 101));
+ var centerOpacity = Math.Max(0, rng.NextDouble() * (rng.NextDouble() - centerSize / 100d));
+ var edgesOpacity = rng.NextDouble() * rng.NextDouble() * branchStraightness;
+ var verticesOpacity = (verticesSize == 0) ? 0 : rng.NextDouble();
+ var hullOpacity = rng.NextDouble() * branchStraightness * generationRotation;
+ var sumOpacity = centerOpacity + edgesOpacity + verticesOpacity + hullOpacity;
+ centerOpacity /= sumOpacity; edgesOpacity /= sumOpacity; verticesOpacity /= sumOpacity; hullOpacity /= sumOpacity;
+ shape = new Shape { MaxBranches = maxBranches, BranchStepLength = branchStepLength, BranchStraightness = branchStraightness, GenerationRotation = generationRotation, ColorVariety = colorVariety, VerticesSize = verticesSize, VerticesSquashVariety = verticesSquashVariety, CenterSize = centerSize, CenterOpacity = centerOpacity, EdgesOpacity = edgesOpacity, VerticesOpacity = verticesOpacity, HullOpacity = hullOpacity };
+ }
+
+ // Add shape.
+ shapes.Add(shape);
+ shapeUsage[shape] = TimeSpan.Zero;
+
+ // Update GUI.
+ updateIcons();
+ }
+
+ void onColorButtonClick(object s, EventArgs e)
+ {
+ // Sort colors by usage.
+ colors.OrderBy(c => colorUsage[c]);
+
+ // Remove underused colors.
+ foreach (var c in colors.ToList())
+ {
+ if (colorUsage[c].Seconds < 0.1 * colorUsage.Max(kvp => kvp.Value).Seconds || colorUsage[c] == TimeSpan.Zero)
+ {
+ colors.Remove(c);
+ colorUsage.Remove(c);
+ }
+ }
+
+ // Either pick a previously used color or generate a new, based on how many previously used colors there are.
+ color = (colors.Count > 0 && rng.NextDouble() <= 0.01 * colors.Count - 0.1) ? colors.ElementAt((colors.ToList().IndexOf(color) + 1) % colors.Count) : createColor(); //TODO Verify that the index applies to the HashSet.
+
+ // Add color.
+ colors.Add(color);
+ colorUsage[color] = TimeSpan.Zero;
+
+ // Update GUI.
+ updateIcons();
+ }
+
+ void onSaveDrawing(object s, EventArgs e)
+ {
+ // Save image to file system.
+ var pbe = new PngBitmapEncoder();
+ pbe.Frames.Add(BitmapFrame.Create(Raster.Source as RenderTargetBitmap));
+ using (var fs = System.IO.File.OpenWrite("drawing.png")) pbe.Save(fs);
+
+ // Upload image to Flickr.
+ try
+ {
+ var bw = new BackgroundWorker();
+ bw.WorkerReportsProgress = true;
+ bw.ProgressChanged += (_s, _e) => ProgessBar.Value = _e.ProgressPercentage;
+ bw.DoWork += (_s, _e) =>
+ {
+ // Login
+ bw.ReportProgress(0);
+ var f = new Flickr(Properties.Settings.Default.FlickrKey, Properties.Settings.Default.FlickrSecret);
+ f.OAuthAccessToken = Properties.Settings.Default.FlickrAccessToken.Token;
+ f.OAuthAccessTokenSecret = Properties.Settings.Default.FlickrAccessToken.TokenSecret;
+ f.OnUploadProgress += (__s, __e) => bw.ReportProgress(__e.ProcessPercentage);
+ bw.ReportProgress(100);
+
+ // Upload photo.
+ var photoId = f.UploadPicture("drawing.png", Properties.Settings.Default.FlickrTitle, Properties.Settings.Default.FlickrDescription, Properties.Settings.Default.FlickrTags, true, true, true);
+
+ // Add photo to set. If set doesn't exist, create it first.
+ bw.ReportProgress(0);
+ var photosets = f.PhotosetsGetList().Where(set => set.Title == Properties.Settings.Default.FlickrPhotoset).ToList();
+ if (photosets.Count == 0) f.PhotosetsCreate(Properties.Settings.Default.FlickrPhotoset, photoId);
+ else f.PhotosetsAddPhoto(photosets[0].PhotosetId, photoId);
+ bw.ReportProgress(100);
+ };
+ bw.RunWorkerCompleted += (_s, _e) =>
+ {
+ // Restart session.
+ (App.Current as App).Resettable = true;
+ (App.Current as App).Reset();
+ };
+ bw.RunWorkerAsync();
+ }
+ catch (Exception ex)
+ {
+ (App.Current as App).SendErrorReport("(EyePaint) Image Upload Error", "The application encountered an error when uploading an image. Error: " + ex.Message + ". The image is only available on the file system until the application is used again.");
+ new ErrorWindow().ShowDialog();
+ }
+ }
+
+ Point calculateGaze(Point p)
+ {
+ gazes.Add(p);
+ while (gazes.Count > Properties.Settings.Default.Inertia) gazes.RemoveAt(0);
+ return new Point(gazes.Average(_p => _p.X), gazes.Average(_p => _p.Y));
+ }
+
+ void startPainting()
+ {
+ if (!paintTimer.IsEnabled)
+ {
+ model = new Tree(gaze, (int)shape.MaxBranches, ref rng);
+ usage = DateTime.Now;
+ paintTimer.Start();
+ }
+ }
+
+ void stopPainting()
+ {
+ if (paintTimer.IsEnabled)
+ {
+ paintTimer.Stop();
+ var t = DateTime.Now - usage;
+ shapeUsage[shape] += t;
+ colorUsage[color] += t;
+ }
+ }
+
+ RenderTargetBitmap createDrawing(int width, int height)
+ {
+ return new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
+ }
+
+ void paint(ref Tree model, RenderTargetBitmap drawing)
+ {
+ // Grow model.
+ var newLeaves = new PointCollection();
+ var newParents = new Dictionary();
+ var rotation = shape.GenerationRotation * rng.NextDouble() * 2 * Math.PI;
+ for (int i = 0; i < model.Leaves.Count; ++i)
+ {
+ var q = (model.Leaves.Count == 0) ? model.Root : model.Leaves[i];
+ var angle =
+ i * (2 * Math.PI / model.Leaves.Count)
+ + rotation
+ + (1 - shape.BranchStraightness) * rng.NextDouble() * 2 * Math.PI;
+ var p = new Point(
+ q.X + shape.BranchStepLength * Math.Cos(angle),
+ q.Y + shape.BranchStepLength * Math.Sin(angle)
+ );
+ newParents[p] = q;
+ newLeaves.Add(p);
+ }
+
+ // Render model.
+ var dv = new DrawingVisual();
+ using (var dc = dv.RenderOpen())
+ {
+ var centerSize = rng.NextDouble() * shape.CenterSize;
+ var centerBrush = new SolidColorBrush(createColor(color, shape.ColorVariety));
+ centerBrush.Opacity = rng.NextDouble() * shape.CenterOpacity;
+ var centerPen = new Pen(centerBrush, 1);
+ dc.DrawEllipse(centerBrush, centerPen, model.Root, centerSize, centerSize);
+
+ var edges = new GeometryGroup();
+ var edgesPen = new Pen(new SolidColorBrush(createColor(color, shape.ColorVariety)), 1);
+ edgesPen.StartLineCap = edgesPen.EndLineCap = PenLineCap.Round;
+ edgesPen.LineJoin = PenLineJoin.Round;
+ edgesPen.Brush.Opacity = shape.EdgesOpacity;
+ foreach (var p in model.Leaves) edges.Children.Add(new LineGeometry(p, model.Parents[p]));
+ dc.DrawGeometry(null, edgesPen, edges);
+
+ var vertices = new GeometryGroup();
+ var verticesBrush = new SolidColorBrush(createColor(color, shape.ColorVariety));
+ verticesBrush.Opacity = rng.NextDouble() * shape.VerticesOpacity;
+ var verticesPen = new Pen(verticesBrush, 1);
+ foreach (var p in model.Leaves)
+ {
+ var r = rng.NextDouble();
+ var eg = new EllipseGeometry(p, shape.VerticesSize * (r + shape.VerticesSquashVariety * rng.NextDouble()), shape.VerticesSize * (r + shape.VerticesSquashVariety * rng.NextDouble()));
+ vertices.Children.Add(eg);
+ }
+ dc.DrawGeometry(verticesBrush, verticesPen, vertices);
+
+ var hull = new StreamGeometry();
+ var hullBrush = new SolidColorBrush(createColor(color, shape.ColorVariety));
+ hullBrush.Opacity = rng.NextDouble() * shape.HullOpacity;
+ var hullPen = new Pen(hullBrush, 1);
+ using (var sgc = hull.Open())
+ {
+ sgc.BeginFigure(model.Leaves[0], true, true);
+ sgc.PolyLineTo(model.Leaves, true, true);
+ }
+ dc.DrawGeometry(hullBrush, hullPen, hull);
+ }
+ //dv.Opacity = rng.NextDouble();
+ var dse = new DropShadowEffect();
+ dse.BlurRadius = 20;
+ dse.ShadowDepth = 0;
+ dse.Opacity = 0.9;// rng.NextDouble();
+ dse.Color = createColor(color, shape.ColorVariety);
+ dv.Effect = dse;
+
+ // Persist update.
+ model.Leaves = newLeaves;
+ model.Parents = newParents;
+ drawing.Render(dv);
+ }
+
+ Color createColor(Color? baseColor = null, double randomness = 1)
+ {
+ // Generate a random color.
+ var H = rng.NextDouble();
+ var S = rng.NextDouble() > 0 || baseColor.HasValue ? 1.0 : 0;
+ var V = rng.NextDouble() > 0 || baseColor.HasValue ? 1.0 : 0;
+ byte R, G, B;
+ if (S == 0)
+ {
+ R = (byte)(V * 255);
+ G = (byte)(V * 255);
+ B = (byte)(V * 255);
+ }
+ else
+ {
+ var h = (H * 6 == 6) ? 0 : H * 6;
+ var i = (int)Math.Floor(h);
+ var d1 = V * (1 - S);
+ var d2 = V * (1 - S * (h - i));
+ var d3 = V * (1 - S * (1 - (h - i)));
+
+ double r, g, b;
+ switch (i)
+ {
+ case 0: r = V; g = d3; b = d1; break;
+ case 1: r = d2; g = V; b = d1; break;
+ case 2: r = d1; g = V; b = d3; break;
+ case 3: r = d1; g = d2; b = V; break;
+ case 4: r = d3; g = d1; b = V; break;
+ default: r = V; g = d1; b = d2; break;
+ }
+
+ R = (byte)(r * 255);
+ G = (byte)(g * 255);
+ B = (byte)(b * 255);
+ }
+
+ // Mix colors if neccessary.
+ if (baseColor.HasValue)
+ {
+ var c1 = baseColor.Value;
+ var c2 = Color.Multiply(Color.FromRgb(R, G, B), (float)randomness);
+ var brightness = System.Drawing.Color.FromArgb(baseColor.Value.A, baseColor.Value.R, baseColor.Value.G, baseColor.Value.B).GetBrightness();
+ return (brightness >= 0.5) ? c1 + c2 : c1 - c2;
+ }
+ else return Color.FromRgb(R, G, B);
+ }
+
+ void updateIcons()
+ {
+ ColorButtonBackgroundBaseColor.Color = color;
+ ColorButtonBackgroundColorVariety.Color = createColor(color, shape.ColorVariety);
+ var toolIcon = new Image();
+ toolIcon.Source = createDrawing((int)(ShapeButton.ActualWidth), (int)(ShapeButton.ActualHeight));
+ var model = new Tree(new Point(ShapeButton.ActualWidth / 2, ShapeButton.ActualHeight / 2), (int)shape.MaxBranches, ref rng);
+ for (int i = 0; i < 10; ++i) paint(ref model, toolIcon.Source as RenderTargetBitmap);
+ ShapeButton.Content = toolIcon;
+ }
+ }
+}
diff --git a/EyePaint/Properties/AssemblyInfo.cs b/EyePaint/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..fe35726
--- /dev/null
+++ b/EyePaint/Properties/AssemblyInfo.cs
@@ -0,0 +1,55 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("EyePaint")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("EyePaint")]
+[assembly: AssemblyCopyright("Copyright © 2014")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+//In order to begin building localizable applications, set
+//CultureYouAreCodingWith in your .csproj file
+//inside a . For example, if you are using US english
+//in your source files, set the to en-US. Then uncomment
+//the NeutralResourceLanguage attribute below. Update the "en-US" in
+//the line below to match the UICulture setting in the project file.
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
+
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/EyePaint/Properties/Resources.Designer.cs b/EyePaint/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..09631a1
--- /dev/null
+++ b/EyePaint/Properties/Resources.Designer.cs
@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.18444
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace EyePaint.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EyePaint.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/EyePaint/Properties/Resources.resx b/EyePaint/Properties/Resources.resx
new file mode 100644
index 0000000..2aacd41
--- /dev/null
+++ b/EyePaint/Properties/Resources.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/EyePaint/Properties/Settings.Designer.cs b/EyePaint/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..bc37c67
--- /dev/null
+++ b/EyePaint/Properties/Settings.Designer.cs
@@ -0,0 +1,205 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.18444
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace EyePaint.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("default@default.default")]
+ public string AdminEmail {
+ get {
+ return ((string)(this["AdminEmail"]));
+ }
+ set {
+ this["AdminEmail"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("default.default.default")]
+ public string SmtpServer {
+ get {
+ return ((string)(this["SmtpServer"]));
+ }
+ set {
+ this["SmtpServer"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("a81591b96318cd9799982c11663df750")]
+ public string FlickrKey {
+ get {
+ return ((string)(this["FlickrKey"]));
+ }
+ set {
+ this["FlickrKey"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("05e84b538d7d4cf5")]
+ public string FlickrSecret {
+ get {
+ return ((string)(this["FlickrSecret"]));
+ }
+ set {
+ this["FlickrSecret"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("10")]
+ public int Spacing {
+ get {
+ return ((int)(this["Spacing"]));
+ }
+ set {
+ this["Spacing"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("10")]
+ public int Stability {
+ get {
+ return ((int)(this["Stability"]));
+ }
+ set {
+ this["Stability"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("30")]
+ public int Blink {
+ get {
+ return ((int)(this["Blink"]));
+ }
+ set {
+ this["Blink"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("100")]
+ public int Inertia {
+ get {
+ return ((int)(this["Inertia"]));
+ }
+ set {
+ this["Inertia"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("10")]
+ public int ConnectionAttempts {
+ get {
+ return ((int)(this["ConnectionAttempts"]));
+ }
+ set {
+ this["ConnectionAttempts"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ public global::FlickrNet.OAuthAccessToken FlickrAccessToken {
+ get {
+ return ((global::FlickrNet.OAuthAccessToken)(this["FlickrAccessToken"]));
+ }
+ set {
+ this["FlickrAccessToken"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string FlickrVerificationCode {
+ get {
+ return ((string)(this["FlickrVerificationCode"]));
+ }
+ set {
+ this["FlickrVerificationCode"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("Måla med ögonen")]
+ public string FlickrPhotoset {
+ get {
+ return ((string)(this["FlickrPhotoset"]));
+ }
+ set {
+ this["FlickrPhotoset"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("Målning med ögonen")]
+ public string FlickrTitle {
+ get {
+ return ((string)(this["FlickrTitle"]));
+ }
+ set {
+ this["FlickrTitle"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("En målning gjord med ögonen.")]
+ public string FlickrDescription {
+ get {
+ return ((string)(this["FlickrDescription"]));
+ }
+ set {
+ this["FlickrDescription"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string FlickrTags {
+ get {
+ return ((string)(this["FlickrTags"]));
+ }
+ set {
+ this["FlickrTags"] = value;
+ }
+ }
+ }
+}
diff --git a/EyePaint/Properties/Settings.settings b/EyePaint/Properties/Settings.settings
new file mode 100644
index 0000000..7f5411a
--- /dev/null
+++ b/EyePaint/Properties/Settings.settings
@@ -0,0 +1,51 @@
+
+
+
+
+
+ default@default.default
+
+
+ default.default.default
+
+
+ a81591b96318cd9799982c11663df750
+
+
+ 05e84b538d7d4cf5
+
+
+ 10
+
+
+ 10
+
+
+ 30
+
+
+ 100
+
+
+ 10
+
+
+
+
+
+
+
+
+ Måla med ögonen
+
+
+ Målning med ögonen
+
+
+ En målning gjord med ögonen.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/EyePaint/Properties/app.manifest b/EyePaint/Properties/app.manifest
new file mode 100644
index 0000000..9a0d40a
--- /dev/null
+++ b/EyePaint/Properties/app.manifest
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/EyePaint/ResultWindow.xaml b/EyePaint/ResultWindow.xaml
new file mode 100644
index 0000000..c9b4d2d
--- /dev/null
+++ b/EyePaint/ResultWindow.xaml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/EyePaint/ResultWindow.xaml.cs b/EyePaint/ResultWindow.xaml.cs
new file mode 100644
index 0000000..e70e357
--- /dev/null
+++ b/EyePaint/ResultWindow.xaml.cs
@@ -0,0 +1,20 @@
+using System.Windows;
+using System.Windows.Media;
+
+namespace EyePaint
+{
+ ///
+ /// Used to display the resulting drawing in its own window.
+ ///
+ public partial class ResultWindow : Window
+ {
+ public ResultWindow()
+ {
+ InitializeComponent();
+ }
+
+ public void SetImageSource(ImageSource imageSource) {
+ Result.Source = imageSource;
+ }
+ }
+}
diff --git a/EyePaint/SettingsWindow.xaml b/EyePaint/SettingsWindow.xaml
new file mode 100644
index 0000000..814a264
--- /dev/null
+++ b/EyePaint/SettingsWindow.xaml
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/EyePaint/SettingsWindow.xaml.cs b/EyePaint/SettingsWindow.xaml.cs
new file mode 100644
index 0000000..635358d
--- /dev/null
+++ b/EyePaint/SettingsWindow.xaml.cs
@@ -0,0 +1,99 @@
+using FlickrNet;
+using System;
+using System.Net;
+using System.Net.Mail;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media.Animation;
+
+namespace EyePaint
+{
+ ///
+ /// Displays editable settings to the museum staff.
+ ///
+ public partial class SettingsWindow : Window
+ {
+ OAuthRequestToken request;
+
+ public SettingsWindow()
+ {
+ InitializeComponent();
+ DataContext = Properties.Settings.Default;
+ ShowDialog();
+ }
+
+ void onContentRendered(object s, EventArgs e)
+ {
+ (FindResource("CountdownAnimation") as Storyboard).Begin();
+ }
+
+ void onClose(object s, EventArgs e)
+ {
+ (FindResource("CountdownAnimation") as Storyboard).Stop();
+ Close();
+ }
+
+ void onShowSettingsButtonClick(object s, RoutedEventArgs e)
+ {
+ Countdown.Visibility = Visibility.Collapsed;
+ Settings.Visibility = Visibility.Visible;
+ (FindResource("CountdownAnimation") as Storyboard).Stop();
+ }
+
+ void onSaveButtonClick(object s, RoutedEventArgs e)
+ {
+ Properties.Settings.Default.Save();
+ MessageBox.Show("Settings saved.");
+ Close();
+ }
+
+ void onGetVerificationCodeButtonClick(object s, RoutedEventArgs e)
+ {
+ var f = new Flickr(Properties.Settings.Default.FlickrKey, Properties.Settings.Default.FlickrSecret);
+ request = f.OAuthGetRequestToken("oob");
+ var url = f.OAuthCalculateAuthorizationUrl(request.Token, AuthLevel.Write);
+ System.Diagnostics.Process.Start(url);
+ }
+
+ void onStoreVerificationCodeButtonClick(object s, RoutedEventArgs e)
+ {
+ if (request == null) MessageBox.Show("Request a new verification code first.");
+ else
+ {
+ try
+ {
+ var f = new Flickr(Properties.Settings.Default.FlickrKey, Properties.Settings.Default.FlickrSecret);
+ Properties.Settings.Default.FlickrAccessToken = f.OAuthGetAccessToken(request, FlickrCode.Text);
+ Properties.Settings.Default.Save();
+ MessageBox.Show("Login complete.");
+ }
+ catch (FlickrApiException)
+ {
+ MessageBox.Show("Login failed.");
+ }
+ }
+ }
+
+ void onSendEmailButtonClick(object s, RoutedEventArgs e)
+ {
+ try
+ {
+ var m = new MailMessage(
+ EyePaint.Properties.Settings.Default.AdminEmail,
+ EyePaint.Properties.Settings.Default.AdminEmail,
+ "(EyePaint) Test Email",
+ ""
+ );
+ var c = new SmtpClient(EyePaint.Properties.Settings.Default.SmtpServer);
+ c.Credentials = CredentialCache.DefaultNetworkCredentials;
+ c.Send(m);
+ }
+ catch (Exception)
+ {
+ MessageBox.Show("Email not sent.");
+ return;
+ }
+ MessageBox.Show("Email sent.");
+ }
+ }
+}
diff --git a/EyePaint/TobiiGazeCore32.dll b/EyePaint/TobiiGazeCore32.dll
new file mode 100644
index 0000000..2ad2872
Binary files /dev/null and b/EyePaint/TobiiGazeCore32.dll differ
diff --git a/EyePaint/packages.config b/EyePaint/packages.config
new file mode 100644
index 0000000..a6877c4
--- /dev/null
+++ b/EyePaint/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d7da4ea
--- /dev/null
+++ b/README.md
@@ -0,0 +1,14 @@
+#EyePaint
+## Om programmet
+EyePaint (sv: Måla med ögonen) är ett Windows program skrivet i .NET som tillåter användaren att måla på skärmen med ögonen, genom att använda en Tobii EyeX (eller REX) eye tracker kamera.
+
+## Installationsinstruktioner
+1. Programmet kräver en Tobii EyeX eller REX eye tracker och dess tillhörande drivrutiner (http://developer.tobii.com/downloads/).
+ 1. Installera EyeX ramverket och följ instruktionerna.
+ 2. Genomför en grundkalibrering.
+ 3. Stäng av ögonindikatorn i Tobiis kontrollpanel.
+2. Hårdvaruknappen för målning måste skicka vänstermusklicksignaler.
+3. Datorskärmen bör ha touchstöd.
+4. Programmet kräver en internetuppkoppling.
+5. Vid uppstart behöver programmet konfigureras. Starta programmet och klicka in i inställningsmenyn. Fyll i fälten och spara. Konfigurationen behöver bara göras en gång per installation.
+6. Kom ihåg att konfigurera operativsystemet också, såsom att stänga av Windows Update, skärmsläckaren, sovlägen m.m, samt se till att applikationen autostartar med Windows.
diff --git a/packages/FlickrNet.3.14.0/FlickrNet.3.14.0.nupkg b/packages/FlickrNet.3.14.0/FlickrNet.3.14.0.nupkg
new file mode 100644
index 0000000..73cbe3c
Binary files /dev/null and b/packages/FlickrNet.3.14.0/FlickrNet.3.14.0.nupkg differ
diff --git a/packages/FlickrNet.3.14.0/FlickrNet.3.14.0.nuspec b/packages/FlickrNet.3.14.0/FlickrNet.3.14.0.nuspec
new file mode 100644
index 0000000..13419f0
--- /dev/null
+++ b/packages/FlickrNet.3.14.0/FlickrNet.3.14.0.nuspec
@@ -0,0 +1,16 @@
+
+
+
+ FlickrNet
+ 3.14.0
+ FlickrNet API Library
+ Sam Judson
+ Sam Judson
+ http://flickrnet.codeplex.com/license
+ http://flickrnet.codeplex.com/
+ false
+ The Flickr.Net API Library is a .Net Library for accessing the Flickr API.
+ en-US
+ flickr photo photography social
+
+
\ No newline at end of file
diff --git a/packages/FlickrNet.3.14.0/content/FlickrNet.chm b/packages/FlickrNet.3.14.0/content/FlickrNet.chm
new file mode 100644
index 0000000..46acf8e
Binary files /dev/null and b/packages/FlickrNet.3.14.0/content/FlickrNet.chm differ
diff --git a/packages/FlickrNet.3.14.0/lib/net20/FlickrNet.dll b/packages/FlickrNet.3.14.0/lib/net20/FlickrNet.dll
new file mode 100644
index 0000000..35a9888
Binary files /dev/null and b/packages/FlickrNet.3.14.0/lib/net20/FlickrNet.dll differ
diff --git a/packages/repositories.config b/packages/repositories.config
new file mode 100644
index 0000000..0c03ac9
--- /dev/null
+++ b/packages/repositories.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file