├── Stuart ├── Stuart.pfx ├── Stuart_StoreKey.pfx ├── Assets │ ├── StoreLogo.png │ ├── SplashScreen.png │ ├── LockScreenLogo.scale-200.png │ ├── Square44x44Logo.scale-200.png │ ├── Wide310x150Logo.scale-200.png │ ├── Square150x150Logo.scale-200.png │ └── Square44x44Logo.targetsize-24_altform-unplated.png ├── project.json ├── Properties │ ├── AssemblyInfo.cs │ └── Default.rd.xml ├── App.xaml ├── EffectPropertiesControl.xaml ├── Converters.cs ├── EditGroupControl.xaml.cs ├── App.xaml.cs ├── CachedImage.cs ├── Observable.cs ├── Stuart.appxmanifest ├── ExtensionMethods.cs ├── Photo.cs ├── MainPage.xaml ├── Effect.cs ├── EditGroupControl.xaml ├── Stuart.csproj ├── EffectPropertiesControl.xaml.cs ├── EffectMetadata.cs ├── EditGroup.cs ├── MainPage.xaml.cs └── Package.StoreAssociation.xml ├── Screenshots ├── retro.jpg ├── stylize.jpg ├── adjusted.jpg ├── original.jpg ├── regionEdit.jpg └── regionSelect.jpg ├── .gitignore ├── PRIVACY.txt ├── LICENSE.txt ├── Stuart.sln └── README.md /Stuart/Stuart.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnhar/stuart/HEAD/Stuart/Stuart.pfx -------------------------------------------------------------------------------- /Screenshots/retro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnhar/stuart/HEAD/Screenshots/retro.jpg -------------------------------------------------------------------------------- /Screenshots/stylize.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnhar/stuart/HEAD/Screenshots/stylize.jpg -------------------------------------------------------------------------------- /Screenshots/adjusted.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnhar/stuart/HEAD/Screenshots/adjusted.jpg -------------------------------------------------------------------------------- /Screenshots/original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnhar/stuart/HEAD/Screenshots/original.jpg -------------------------------------------------------------------------------- /Screenshots/regionEdit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnhar/stuart/HEAD/Screenshots/regionEdit.jpg -------------------------------------------------------------------------------- /Stuart/Stuart_StoreKey.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnhar/stuart/HEAD/Stuart/Stuart_StoreKey.pfx -------------------------------------------------------------------------------- /Screenshots/regionSelect.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnhar/stuart/HEAD/Screenshots/regionSelect.jpg -------------------------------------------------------------------------------- /Stuart/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnhar/stuart/HEAD/Stuart/Assets/StoreLogo.png -------------------------------------------------------------------------------- /Stuart/Assets/SplashScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnhar/stuart/HEAD/Stuart/Assets/SplashScreen.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | packages/ 4 | AppPackages/ 5 | .vs/ 6 | *.user 7 | Visual Studio 2015/ 8 | project.lock.json 9 | -------------------------------------------------------------------------------- /Stuart/Assets/LockScreenLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnhar/stuart/HEAD/Stuart/Assets/LockScreenLogo.scale-200.png -------------------------------------------------------------------------------- /Stuart/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnhar/stuart/HEAD/Stuart/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /Stuart/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnhar/stuart/HEAD/Stuart/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /Stuart/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnhar/stuart/HEAD/Stuart/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /Stuart/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnhar/stuart/HEAD/Stuart/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /PRIVACY.txt: -------------------------------------------------------------------------------- 1 | Stuart does not collect, store or transmit any personal information. 2 | 3 | It accesses images from the user photo collection only when a photograph is explicitly 4 | selected via the "Open" command. Copies of the selected photo are only stored to other 5 | locations if the user explicitly chooses to via the "Save As" command. 6 | -------------------------------------------------------------------------------- /Stuart/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "Microsoft.NETCore.UniversalWindowsPlatform": "5.0.0", 4 | "Win2D.uwp": "1.18.0" 5 | }, 6 | "frameworks": { 7 | "uap10.0": {} 8 | }, 9 | "runtimes": { 10 | "win10-arm": {}, 11 | "win10-arm-aot": {}, 12 | "win10-x86": {}, 13 | "win10-x86-aot": {}, 14 | "win10-x64": {}, 15 | "win10-x64-aot": {} 16 | } 17 | } -------------------------------------------------------------------------------- /Stuart/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("Stuart")] 5 | [assembly: AssemblyDescription("Shawn's Terrific Universal App for photogRaph Tweaking")] 6 | [assembly: AssemblyProduct("Stuart")] 7 | [assembly: AssemblyCopyright("Copyright © Shawn Hargreaves 2015")] 8 | 9 | [assembly: AssemblyVersion("1.0.0.0")] 10 | [assembly: AssemblyFileVersion("1.0.0.0")] 11 | [assembly: ComVisible(false)] -------------------------------------------------------------------------------- /Stuart/App.xaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Shawn Hargreaves 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Stuart/Properties/Default.rd.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Stuart/EffectPropertiesControl.xaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Stuart/Converters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Windows.UI.Xaml; 3 | using Windows.UI.Xaml.Data; 4 | 5 | namespace Stuart 6 | { 7 | class EnumToIntConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, string language) 10 | { 11 | return (int)value; 12 | } 13 | 14 | public object ConvertBack(object value, Type targetType, object parameter, string language) 15 | { 16 | return value; 17 | } 18 | } 19 | 20 | 21 | class NullToBooleanConverter : IValueConverter 22 | { 23 | public object Convert(object value, Type targetType, object parameter, string language) 24 | { 25 | return value != null; 26 | } 27 | 28 | public object ConvertBack(object value, Type targetType, object parameter, string language) 29 | { 30 | throw new NotImplementedException(); 31 | } 32 | } 33 | 34 | 35 | class BooleanToVisibilityConverter : IValueConverter 36 | { 37 | public object Convert(object value, Type targetType, object parameter, string language) 38 | { 39 | return System.Convert.ToBoolean(value) ? Visibility.Visible : Visibility.Collapsed; 40 | } 41 | 42 | public object ConvertBack(object value, Type targetType, object parameter, string language) 43 | { 44 | throw new NotImplementedException(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Stuart/EditGroupControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Windows.UI.Xaml; 4 | using Windows.UI.Xaml.Controls; 5 | 6 | namespace Stuart 7 | { 8 | // UI codebehind for configuring an EditGroup. 9 | public sealed partial class EditGroupControl : UserControl 10 | { 11 | public static EffectType[] EffectTypes 12 | { 13 | get { return Enum.GetValues(typeof(EffectType)).Cast().ToArray(); } 14 | } 15 | 16 | 17 | public EditGroupControl() 18 | { 19 | this.InitializeComponent(); 20 | } 21 | 22 | 23 | void UndoRegionEdit_Click(object sender, RoutedEventArgs e) 24 | { 25 | var edit = (EditGroup)DataContext; 26 | 27 | edit.UndoRegionEdit(); 28 | } 29 | 30 | 31 | void NewEffect_Click(object sender, RoutedEventArgs e) 32 | { 33 | var edit = (EditGroup)DataContext; 34 | 35 | var newEffect = new Effect(edit); 36 | 37 | edit.Effects.Add(newEffect); 38 | edit.Parent.SelectedEffect = newEffect; 39 | } 40 | 41 | 42 | void DeleteButton_Click(object sender, RoutedEventArgs e) 43 | { 44 | var effect = (Effect)effectList.SelectedValue; 45 | 46 | var edit = effect.Parent; 47 | 48 | effect.Dispose(); 49 | 50 | if (edit.Effects.Count == 0) 51 | { 52 | edit.Dispose(); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Stuart/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Windows.ApplicationModel.Activation; 3 | using Windows.Storage; 4 | using Windows.UI.Xaml; 5 | using Windows.UI.Xaml.Controls; 6 | 7 | namespace Stuart 8 | { 9 | // Provides application-specific behavior to supplement the default Application class. 10 | sealed partial class App : Application 11 | { 12 | public App() 13 | { 14 | this.InitializeComponent(); 15 | } 16 | 17 | 18 | protected override void OnLaunched(LaunchActivatedEventArgs args) 19 | { 20 | Initialize(args.PreviousExecutionState); 21 | } 22 | 23 | 24 | protected override void OnFileActivated(FileActivatedEventArgs args) 25 | { 26 | Initialize(args.Files); 27 | } 28 | 29 | 30 | void Initialize(object launchArg) 31 | { 32 | Frame rootFrame = Window.Current.Content as Frame; 33 | 34 | if (rootFrame == null) 35 | { 36 | rootFrame = new Frame(); 37 | 38 | Window.Current.Content = rootFrame; 39 | } 40 | 41 | if (rootFrame.Content == null) 42 | { 43 | rootFrame.Navigate(typeof(MainPage), launchArg); 44 | } 45 | else 46 | { 47 | ((MainPage)rootFrame.Content).TryLoadPhoto(launchArg as IReadOnlyList); 48 | } 49 | 50 | Window.Current.Activate(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Stuart/CachedImage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Graphics.Canvas; 2 | using System.Linq; 3 | 4 | namespace Stuart 5 | { 6 | // Caches expensive image effect results in a rendertarget, to minimize repeated recomputation. 7 | class CachedImage 8 | { 9 | CanvasRenderTarget cachedImage; 10 | 11 | bool isCacheValid; 12 | object[] cacheKeys; 13 | 14 | 15 | public ICanvasImage Get(params object[] keys) 16 | { 17 | if (!isCacheValid) 18 | return null; 19 | 20 | if (keys != null && (cacheKeys == null || !keys.SequenceEqual(cacheKeys))) 21 | return null; 22 | 23 | return cachedImage; 24 | } 25 | 26 | 27 | public ICanvasImage Cache(Photo photo, ICanvasImage image, params object[] keys) 28 | { 29 | if (cachedImage == null) 30 | { 31 | cachedImage = new CanvasRenderTarget(photo.SourceBitmap.Device, photo.Size.X, photo.Size.Y, 96); 32 | } 33 | 34 | using (var drawingSession = cachedImage.CreateDrawingSession()) 35 | { 36 | drawingSession.Blend = CanvasBlend.Copy; 37 | drawingSession.DrawImage(image); 38 | } 39 | 40 | isCacheValid = true; 41 | cacheKeys = keys; 42 | 43 | return cachedImage; 44 | } 45 | 46 | 47 | public void Reset() 48 | { 49 | isCacheValid = false; 50 | } 51 | 52 | 53 | public void RecoverAfterDeviceLost() 54 | { 55 | if (cachedImage != null) 56 | { 57 | cachedImage.Dispose(); 58 | cachedImage = null; 59 | } 60 | 61 | isCacheValid = false; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Stuart.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stuart", "Stuart\Stuart.csproj", "{A9368EF5-472F-4168-A20D-917D172B0FC1}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|ARM = Debug|ARM 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|ARM = Release|ARM 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Debug|ARM.ActiveCfg = Debug|ARM 19 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Debug|ARM.Build.0 = Debug|ARM 20 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Debug|ARM.Deploy.0 = Debug|ARM 21 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Debug|x64.ActiveCfg = Debug|x64 22 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Debug|x64.Build.0 = Debug|x64 23 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Debug|x64.Deploy.0 = Debug|x64 24 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Debug|x86.ActiveCfg = Debug|x86 25 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Debug|x86.Build.0 = Debug|x86 26 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Debug|x86.Deploy.0 = Debug|x86 27 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Release|ARM.ActiveCfg = Release|ARM 28 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Release|ARM.Build.0 = Release|ARM 29 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Release|ARM.Deploy.0 = Release|ARM 30 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Release|x64.ActiveCfg = Release|x64 31 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Release|x64.Build.0 = Release|x64 32 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Release|x64.Deploy.0 = Release|x64 33 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Release|x86.ActiveCfg = Release|x86 34 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Release|x86.Build.0 = Release|x86 35 | {A9368EF5-472F-4168-A20D-917D172B0FC1}.Release|x86.Deploy.0 = Release|x86 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /Stuart/Observable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.Specialized; 3 | using System.ComponentModel; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace Stuart 7 | { 8 | // Helper for implementing INotifyPropertyChanged. 9 | public abstract class Observable : INotifyPropertyChanged 10 | { 11 | public event PropertyChangedEventHandler PropertyChanged; 12 | 13 | 14 | protected void NotifyPropertyChanged(object sender, PropertyChangedEventArgs e) 15 | { 16 | if (PropertyChanged != null) 17 | { 18 | PropertyChanged(sender, e); 19 | } 20 | } 21 | 22 | 23 | protected void NotifyPropertyChanged(string propertyName) 24 | { 25 | NotifyPropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 26 | } 27 | 28 | 29 | protected void NotifyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e, string propertyName) 30 | { 31 | // Unsubscribe property change events of items that were removed from the collection. 32 | if (e.OldItems != null) 33 | { 34 | foreach (INotifyPropertyChanged old in e.OldItems) 35 | { 36 | old.PropertyChanged -= NotifyPropertyChanged; 37 | } 38 | } 39 | 40 | // Subscribe to the property change events of newly added items. 41 | if (e.NewItems != null) 42 | { 43 | foreach (INotifyPropertyChanged item in e.NewItems) 44 | { 45 | item.PropertyChanged += NotifyPropertyChanged; 46 | } 47 | } 48 | 49 | // Also notify listeners that the collection itself has changed. 50 | NotifyPropertyChanged(propertyName); 51 | } 52 | 53 | 54 | protected void SetField(ref T field, T value, [CallerMemberName] string propertyName = null) 55 | { 56 | if (EqualityComparer.Default.Equals(field, value)) 57 | return; 58 | 59 | field = value; 60 | 61 | NotifyPropertyChanged(propertyName); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Stuart/Stuart.appxmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stuart Photo Editor 7 | Shawn Hargreaves 8 | Assets\StoreLogo.png 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Stuart Photo 27 | 28 | .jpg 29 | .jpeg 30 | .png 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Stuart/ExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Numerics; 5 | using Windows.Foundation; 6 | using Windows.UI; 7 | 8 | namespace Stuart 9 | { 10 | static class BinaryWriterExtensions 11 | { 12 | public static void WriteCollection(this BinaryWriter writer, ICollection collection, Action writeItem) 13 | { 14 | writer.Write(collection.Count); 15 | 16 | foreach (var item in collection) 17 | { 18 | writeItem(item); 19 | } 20 | } 21 | 22 | 23 | public static void WriteByteArray(this BinaryWriter writer, byte[] array) 24 | { 25 | if (array == null) 26 | { 27 | writer.Write(0); 28 | } 29 | else 30 | { 31 | writer.Write(array.Length); 32 | writer.Write(array); 33 | } 34 | } 35 | 36 | 37 | public static void WriteColor(this BinaryWriter writer, Color color) 38 | { 39 | writer.Write(color.R); 40 | writer.Write(color.G); 41 | writer.Write(color.B); 42 | writer.Write(color.A); 43 | } 44 | 45 | 46 | public static void WriteRect(this BinaryWriter writer, Rect rect) 47 | { 48 | writer.Write(rect.X); 49 | writer.Write(rect.Y); 50 | writer.Write(rect.Width); 51 | writer.Write(rect.Height); 52 | } 53 | 54 | 55 | public static void WriteVector2(this BinaryWriter writer, Vector2 vector) 56 | { 57 | writer.Write(vector.X); 58 | writer.Write(vector.Y); 59 | } 60 | } 61 | 62 | 63 | static class BinaryReaderExtensions 64 | { 65 | public static void ReadCollection(this BinaryReader reader, ICollection collection, Func readItem) 66 | { 67 | collection.Clear(); 68 | 69 | var count = reader.ReadInt32(); 70 | 71 | for (int i = 0; i < count; i++) 72 | { 73 | collection.Add(readItem()); 74 | } 75 | } 76 | 77 | 78 | public static byte[] ReadByteArray(this BinaryReader reader) 79 | { 80 | var count = reader.ReadInt32(); 81 | 82 | return (count == 0) ? null : reader.ReadBytes(count); 83 | } 84 | 85 | 86 | public static Color ReadColor(this BinaryReader reader) 87 | { 88 | Color color; 89 | 90 | color.R = reader.ReadByte(); 91 | color.G = reader.ReadByte(); 92 | color.B = reader.ReadByte(); 93 | color.A = reader.ReadByte(); 94 | 95 | return color; 96 | } 97 | 98 | 99 | public static Rect ReadRect(this BinaryReader reader) 100 | { 101 | Rect rect; 102 | 103 | rect.X = reader.ReadDouble(); 104 | rect.Y = reader.ReadDouble(); 105 | rect.Width = reader.ReadDouble(); 106 | rect.Height = reader.ReadDouble(); 107 | 108 | return rect; 109 | } 110 | 111 | 112 | public static Vector2 ReadVector2(this BinaryReader reader) 113 | { 114 | Vector2 vector; 115 | 116 | vector.X = reader.ReadSingle(); 117 | vector.Y = reader.ReadSingle(); 118 | 119 | return vector; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Shawn's Terrific Universal App for photogRaph Tweaking 2 | 3 | This is a simple, powerful, and contrivedly acronymed Windows 10 photo editing app, 4 | created during an app building exercise to test the [Win2D](http:/github.com/microsoft/win2d) graphics API. 5 | 6 | 7 | #### Where to get it 8 | 9 | - [Download from the Store](https://www.microsoft.com/store/apps/9NBLGGH1XSQK) 10 | - Or clone the source from github and load Stuart.sln into Visual Studio 2015 11 | 12 | 13 | #### Features 14 | 15 | - Use a rich set of image effects to tweak your photos 16 | - Apply effects to the whole image or just selected parts of it 17 | - Feather selected regions for smooth transitions 18 | - Runs on Windows 10 PCs and phones 19 | 20 | 21 | #### Supported effects 22 | 23 | - Framing: 24 | - Crop 25 | - Straighten 26 | - Color adjustment: 27 | - Exposure 28 | - Highlights & Shadows 29 | - Temperature & Tint 30 | - Contrast 31 | - Saturate 32 | - Stylize: 33 | - Grayscale 34 | - Sepia 35 | - Vignette 36 | - Special effects: 37 | - Blur 38 | - Motion blur 39 | - Sharpen 40 | - Edge detection 41 | - Emboss 42 | - Invert 43 | - Posterize 44 | 45 | 46 | #### How to use it 47 | 48 | Start by loading a photo: 49 | 50 | ![original.jpg](Screenshots/original.jpg) 51 | 52 | At the top of the UI are the open, save, save-as and help buttons. Below this is a list of edit groups, 53 | each of which contains a list of effects. To start off, there is just one edit group, containing a single 54 | Crop effect. 55 | 56 | To view different parts of the image, use pinch zoom or the A and Z keys. 57 | 58 | Click the '+' buttons to add new effects or edit groups. Drag things to reorder them, and use the 'x' 59 | button to delete the selected effect. The pair-of-eyes icons are used to show or hide effects and edit 60 | groups - this is handy for doing quick before/after comparisons. 61 | 62 | To adjust the settings of an effect, click the '...' to the right of the effect name, which brings 63 | up a set of sliders. Here is our photo after Highlights, Temperature, and Saturate adjustment: 64 | 65 | ![adjusted.jpg](Screenshots/adjusted.jpg) 66 | 67 | For a retro look, try Sepia and Vignette, with some Contrast thrown in for good measure: 68 | 69 | ![retro.jpg](Screenshots/retro.jpg) 70 | 71 | Sometimes you want to adjust only part of a photo without affecting other areas. To do this, click 72 | the square icon at the top of an edit group. This brings up region editing options for that group. 73 | All the effects inside the group will apply only to the selected region. 74 | 75 | ![regionSelect.jpg](Screenshots/regionSelect.jpg) 76 | 77 | The region editing options are: 78 | - Selection mode (rectangle, ellipse, freehand, or magic wand) 79 | - Selection operation (replace region, add to region, subtract from region, or invert region) 80 | - Undo region edit 81 | - Show region (another pair-of-eyes icon - if enabled, the region border is displayed in pink while everything outside the region is grayed out) 82 | - Feather - softens the edge of the region, so its effects will smoothly crossfade with the outside unaffected part of the photo 83 | - Dilate - expands or shrinks the selected region (useful for cleaning up magic wand selections) 84 | 85 | Magic wand mode selects areas of the photo that have similar colors. To use it, 86 | click and drag on the photo. The initial click point defines what color to select, 87 | and how far you drag controls the color matching tolerance. 88 | 89 | While in region editing mode, clicking on the photo will change the selected region 90 | rather than panning or zooming the view. If you want to pan or zoom again, click the 91 | square 'edit region' button a second time to turn off region edit mode. 92 | 93 | Here is our photo using a region to apply Exposure and Saturation effects only to 94 | the foreground trees and rocks, brightening them up without changing the sky. It uses 95 | a second edit group to blur the distant hills only in the bottom left of the photo: 96 | 97 | ![regionEdit.jpg](Screenshots/regionEdit.jpg) 98 | 99 | And finally, something rather more extreme: this screenshot uses two edit groups to apply 100 | different effects to the sky vs. ground: 101 | 102 | ![stylize.jpg](Screenshots/stylize.jpg) 103 | -------------------------------------------------------------------------------- /Stuart/Photo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Graphics.Canvas; 2 | using System; 3 | using System.Collections.ObjectModel; 4 | using System.IO; 5 | using System.Numerics; 6 | using System.Threading.Tasks; 7 | using Windows.Foundation; 8 | using Windows.Graphics.DirectX; 9 | using Windows.Storage; 10 | 11 | namespace Stuart 12 | { 13 | // Top level DOM type stores the photo plus a list of edits. 14 | public class Photo : Observable 15 | { 16 | public CanvasBitmap SourceBitmap 17 | { 18 | get { return sourceBitmap; } 19 | private set { SetField(ref sourceBitmap, value); } 20 | } 21 | 22 | CanvasBitmap sourceBitmap; 23 | 24 | DirectXPixelFormat bitmapFormat; 25 | byte[] bitmapData; 26 | 27 | 28 | public Vector2 Size { get; private set; } 29 | 30 | 31 | public ObservableCollection Edits { get; } = new ObservableCollection(); 32 | 33 | 34 | public Effect SelectedEffect 35 | { 36 | get { return selectedEffect; } 37 | set { SetField(ref selectedEffect, value); } 38 | } 39 | 40 | Effect selectedEffect; 41 | 42 | 43 | public Photo() 44 | { 45 | Edits.CollectionChanged += (sender, e) => NotifyCollectionChanged(sender, e, "Edits"); 46 | } 47 | 48 | 49 | public async Task Load(CanvasDevice device, StorageFile file) 50 | { 51 | using (var stream = await file.OpenReadAsync()) 52 | { 53 | SourceBitmap = await CanvasBitmap.LoadAsync(device, stream); 54 | } 55 | 56 | bitmapFormat = sourceBitmap.Format; 57 | bitmapData = sourceBitmap.GetPixelBytes(); 58 | 59 | Size = sourceBitmap.Size.ToVector2(); 60 | 61 | Edits.Clear(); 62 | Edits.Add(new EditGroup(this)); 63 | 64 | SelectedEffect = null; 65 | } 66 | 67 | 68 | public async Task Save(StorageFile file) 69 | { 70 | var image = GetImage(); 71 | 72 | // Measure the extent of the image (which may be cropped). 73 | Rect imageBounds; 74 | 75 | using (var commandList = new CanvasCommandList(sourceBitmap.Device)) 76 | using (var drawingSession = commandList.CreateDrawingSession()) 77 | { 78 | imageBounds = image.GetBounds(drawingSession); 79 | } 80 | 81 | // Rasterize the image into a rendertarget. 82 | using (var renderTarget = new CanvasRenderTarget(sourceBitmap.Device, (float)imageBounds.Width, (float)imageBounds.Height, 96)) 83 | { 84 | using (var drawingSession = renderTarget.CreateDrawingSession()) 85 | { 86 | drawingSession.Blend = CanvasBlend.Copy; 87 | 88 | drawingSession.DrawImage(image, -(float)imageBounds.X, -(float)imageBounds.Y); 89 | } 90 | 91 | // Save it out. 92 | var format = file.FileType.Equals(".png", StringComparison.OrdinalIgnoreCase) ? CanvasBitmapFileFormat.Png : CanvasBitmapFileFormat.Jpeg; 93 | 94 | using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite)) 95 | { 96 | stream.Size = 0; 97 | 98 | await renderTarget.SaveAsync(stream, format); 99 | } 100 | } 101 | } 102 | 103 | 104 | public void RecoverAfterDeviceLost(CanvasDevice device) 105 | { 106 | SourceBitmap = CanvasBitmap.CreateFromBytes(device, bitmapData, (int)Size.X, (int)Size.Y, bitmapFormat); 107 | 108 | foreach (var edit in Edits) 109 | { 110 | edit.RecoverAfterDeviceLost(); 111 | } 112 | } 113 | 114 | 115 | public void SaveSuspendedState(BinaryWriter writer) 116 | { 117 | writer.Write((int)bitmapFormat); 118 | writer.WriteByteArray(bitmapData); 119 | 120 | writer.WriteVector2(Size); 121 | 122 | writer.WriteCollection(Edits, edit => edit.SaveSuspendedState(writer)); 123 | } 124 | 125 | 126 | public void RestoreSuspendedState(CanvasDevice device, BinaryReader reader) 127 | { 128 | bitmapFormat = (DirectXPixelFormat)reader.ReadInt32(); 129 | bitmapData = reader.ReadByteArray(); 130 | 131 | Size = reader.ReadVector2(); 132 | 133 | reader.ReadCollection(Edits, () => EditGroup.RestoreSuspendedState(this, reader)); 134 | 135 | RecoverAfterDeviceLost(device); 136 | } 137 | 138 | 139 | public ICanvasImage GetImage() 140 | { 141 | ICanvasImage image = sourceBitmap; 142 | 143 | foreach (var edit in Edits) 144 | { 145 | image = edit.Apply(image); 146 | } 147 | 148 | return image; 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Stuart/MainPage.xaml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |