diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000..86ca269 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1 @@ +assembly-file-versioning-format: '{Major}.{Minor}.{BuildMetaData ?? 0}.0' \ No newline at end of file diff --git a/README.md b/README.md index 0cbb32c..95b7770 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,9 @@ # Yugen Mosaic UWP -## The Toolkit is a collection of helper functions, custom controls, and app services. It helps develop common task for UWP apps. +Yugen Mosaic is a free app that helps you to create digital art pictures made from your own pictures. For example you can create a photo mosaic, an image composed of many tiled photos. -### Contributing +## Contributing Everyone is welcome to contribute, if you're looking to help out with the project, feel free to join and submit anything that you find useful. If you find an issue please could you raise it in the issues section or it would be fantastic if you could have a look at rectifying the issue and submitting a pull request. -### Getting started +## Getting started The project has been primarily built for the universal Windows platform (UWP), so you'll need the latest version of [Visual Studio 2019](https://www.visualstudio.com/) (including the community edition) and the latest Windows 10 SDK which you can install as part of the Visual Studio installer. - -I'm using github packages from my toolkit for nuget, to restore them in VS you need to add this, -go to tools -> options -> nuget package manager -> package sources -Name: Github (or whatever) -Source: https://nuget.pkg.github.com/emiliano84/index.json - -when VS will ask for credentials -username: emiliano84 -password: {ask me for the token} - diff --git a/Yugen.Mosaic.Uwp/App.xaml.cs b/Yugen.Mosaic.Uwp/App.xaml.cs index af2d86b..f932fcf 100644 --- a/Yugen.Mosaic.Uwp/App.xaml.cs +++ b/Yugen.Mosaic.Uwp/App.xaml.cs @@ -7,15 +7,17 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; -using Yugen.Mosaic.Uwp.Helpers; +using Yugen.Mosaic.Uwp.Extensions; using Yugen.Mosaic.Uwp.Services; +using Yugen.Mosaic.Uwp.Views; +using Yugen.Toolkit.Standard.Extensions; namespace Yugen.Mosaic.Uwp { /// /// Provides application-specific behavior to supplement the default Application class. /// - sealed partial class App : Application + public sealed partial class App : Application { /// /// Initializes the singleton application object. This is the first line of authored code @@ -23,8 +25,8 @@ sealed partial class App : Application /// public App() { - this.InitializeComponent(); - this.Suspending += OnSuspending; + InitializeComponent(); + Suspending += OnSuspending; AppCenter.Start("7df4b441-69ae-49c5-b27d-5a532f33b554", typeof(Analytics), typeof(Crashes)); @@ -76,10 +78,7 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) /// /// The Frame which failed navigation /// Details about the navigation failure - void OnNavigationFailed(object sender, NavigationFailedEventArgs e) - { - throw new Exception("Failed to load Page " + e.SourcePageType.FullName); - } + private void OnNavigationFailed(object sender, NavigationFailedEventArgs e) => throw new Exception("Failed to load Page " + e.SourcePageType.FullName); /// /// Invoked when application execution is being suspended. Application state is saved @@ -90,7 +89,7 @@ void OnNavigationFailed(object sender, NavigationFailedEventArgs e) /// Details about the suspend request. private void OnSuspending(object sender, SuspendingEventArgs e) { - var deferral = e.SuspendingOperation.GetDeferral(); + SuspendingDeferral deferral = e.SuspendingOperation.GetDeferral(); //TODO: Save application state and stop any background activity deferral.Complete(); } diff --git a/Yugen.Mosaic.Uwp/Assets/Store/Screenshot.png b/Yugen.Mosaic.Uwp/Assets/Store/Screenshot.png index 0d14682..6cb8bdf 100644 Binary files a/Yugen.Mosaic.Uwp/Assets/Store/Screenshot.png and b/Yugen.Mosaic.Uwp/Assets/Store/Screenshot.png differ diff --git a/Yugen.Mosaic.Uwp/Assets/Store/Screenshot2.png b/Yugen.Mosaic.Uwp/Assets/Store/Screenshot2.png new file mode 100644 index 0000000..0d14682 Binary files /dev/null and b/Yugen.Mosaic.Uwp/Assets/Store/Screenshot2.png differ diff --git a/Yugen.Mosaic.Uwp/Controls/AlignmentGrid.cs b/Yugen.Mosaic.Uwp/Controls/AlignmentGrid.cs index 563fc4f..27e08de 100644 --- a/Yugen.Mosaic.Uwp/Controls/AlignmentGrid.cs +++ b/Yugen.Mosaic.Uwp/Controls/AlignmentGrid.cs @@ -60,8 +60,8 @@ private static void OnPropertyChanged(DependencyObject dependencyObject, Depende /// public Brush LineBrush { - get { return (Brush)GetValue(LineBrushProperty); } - set { SetValue(LineBrushProperty, value); } + get => (Brush)GetValue(LineBrushProperty); + set => SetValue(LineBrushProperty, value); } /// @@ -69,8 +69,8 @@ public Brush LineBrush /// public double HorizontalStep { - get { return (double)GetValue(HorizontalStepProperty); } - set { SetValue(HorizontalStepProperty, value); } + get => (double)GetValue(HorizontalStepProperty); + set => SetValue(HorizontalStepProperty, value); } /// @@ -78,8 +78,8 @@ public double HorizontalStep /// public double VerticalStep { - get { return (double)GetValue(VerticalStepProperty); } - set { SetValue(VerticalStepProperty, value); } + get => (double)GetValue(VerticalStepProperty); + set => SetValue(VerticalStepProperty, value); } /// @@ -87,8 +87,8 @@ public double VerticalStep /// public double ContainerWidth { - get { return (double)GetValue(ContainerWidthProperty); } - set { SetValue(ContainerWidthProperty, value); } + get => (double)GetValue(ContainerWidthProperty); + set => SetValue(ContainerWidthProperty, value); } /// @@ -96,8 +96,8 @@ public double ContainerWidth /// public double ContainerHeight { - get { return (double)GetValue(ContainerHeightProperty); } - set { SetValue(ContainerHeightProperty, value); } + get => (double)GetValue(ContainerHeightProperty); + set => SetValue(ContainerHeightProperty, value); } /// @@ -121,7 +121,7 @@ private void Rebuild() containerCanvas.Children.Clear(); var horizontalStep = HorizontalStep; var verticalStep = VerticalStep; - var brush = LineBrush ?? (Brush)Application.Current.Resources["ApplicationForegroundThemeBrush"]; + Brush brush = LineBrush ?? (Brush)Application.Current.Resources["ApplicationForegroundThemeBrush"]; if (horizontalStep > 0) { @@ -133,7 +133,7 @@ private void Rebuild() Height = ActualHeight, Fill = brush }; - Canvas.SetLeft(line, MathHelper.RangesConverter(x, 0, ContainerWidth, 0, ActualWidth)); + Canvas.SetLeft(line, MathHelper.RangeConvert(x, 0, ContainerWidth, 0, ActualWidth)); containerCanvas.Children.Add(line); } @@ -149,16 +149,13 @@ private void Rebuild() Height = 1, Fill = brush }; - Canvas.SetTop(line, MathHelper.RangesConverter(y, 0, ContainerHeight, 0, ActualHeight)); + Canvas.SetTop(line, MathHelper.RangeConvert(y, 0, ContainerHeight, 0, ActualHeight)); containerCanvas.Children.Add(line); } } } - private void AlignmentGrid_SizeChanged(object sender, SizeChangedEventArgs e) - { - Rebuild(); - } + private void AlignmentGrid_SizeChanged(object sender, SizeChangedEventArgs e) => Rebuild(); } } \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/SettingsDialog.xaml b/Yugen.Mosaic.Uwp/Controls/SettingsDialog.xaml similarity index 76% rename from Yugen.Mosaic.Uwp/SettingsDialog.xaml rename to Yugen.Mosaic.Uwp/Controls/SettingsDialog.xaml index 59340e8..2fb41a9 100644 --- a/Yugen.Mosaic.Uwp/SettingsDialog.xaml +++ b/Yugen.Mosaic.Uwp/Controls/SettingsDialog.xaml @@ -1,4 +1,4 @@ - @@ -35,7 +35,7 @@ @@ -46,24 +46,24 @@ Foreground="{ThemeResource AppForegroundBrush}" Style="{StaticResource SubtitleTextBlockStyle}" /> Light Dark Default @@ -80,10 +80,10 @@ - - + - + - + - + + Command="{x:Bind ViewModel.LaunchRateAndReviewCommand}" /> diff --git a/Yugen.Mosaic.Uwp/Controls/SettingsDialog.xaml.cs b/Yugen.Mosaic.Uwp/Controls/SettingsDialog.xaml.cs new file mode 100644 index 0000000..badd68c --- /dev/null +++ b/Yugen.Mosaic.Uwp/Controls/SettingsDialog.xaml.cs @@ -0,0 +1,23 @@ +using System.Windows.Input; +using Windows.UI.Xaml.Controls; +using Yugen.Mosaic.Uwp.ViewModels; +using Yugen.Toolkit.Standard.Commands; + +// The Content Dialog item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238 + +namespace Yugen.Mosaic.Uwp.Controls +{ + public sealed partial class SettingsDialog : ContentDialog + { + private ICommand _hideCommand; + public SettingsViewModel ViewModel => DataContext as SettingsViewModel; + + public SettingsDialog() + { + InitializeComponent(); + //DataContext = new SettingsViewModel(); + } + + public ICommand HideCommand => _hideCommand ?? (_hideCommand = new RelayCommand(() => Hide())); + } +} \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Enums/FileFormat.cs b/Yugen.Mosaic.Uwp/Enums/FileFormat.cs index bba1ec7..28343ed 100644 --- a/Yugen.Mosaic.Uwp/Enums/FileFormat.cs +++ b/Yugen.Mosaic.Uwp/Enums/FileFormat.cs @@ -1,11 +1,18 @@ -namespace Yugen.Mosaic.Uwp.Enums +using System.ComponentModel; + +namespace Yugen.Mosaic.Uwp.Enums { public enum FileFormat { + [Description(".jpg")] Jpg, + [Description(".png")] Png, + [Description(".bmp")] Bmp, + [Description(".tiff")] Tiff, + [Description(".gif")] Gif } } diff --git a/Yugen.Mosaic.Uwp/Enums/MosaicTypeEnum.cs b/Yugen.Mosaic.Uwp/Enums/MosaicTypeEnum.cs new file mode 100644 index 0000000..7d15b94 --- /dev/null +++ b/Yugen.Mosaic.Uwp/Enums/MosaicTypeEnum.cs @@ -0,0 +1,16 @@ +using System.ComponentModel; + +namespace Yugen.Mosaic.Uwp.Enums +{ + public enum MosaicTypeEnum + { + [Description("Classic")] + Classic, + [Description("Random")] + Random, + [Description("Adjust Hue")] + AdjustHue, + [Description("Plain Color")] + PlainColor + } +} diff --git a/Yugen.Mosaic.Uwp/Enums/OnboardingStage.cs b/Yugen.Mosaic.Uwp/Enums/OnboardingStage.cs new file mode 100644 index 0000000..91b2221 --- /dev/null +++ b/Yugen.Mosaic.Uwp/Enums/OnboardingStage.cs @@ -0,0 +1,13 @@ +namespace Yugen.Mosaic.Uwp.Enums +{ + public enum OnboardingStage + { + MasterImage, + AddTiles, + TileProperties, + MosaicType, + OutputProperties, + Generate, + Save + } +} \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Extensions/EnumExtensions.cs b/Yugen.Mosaic.Uwp/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..956a240 --- /dev/null +++ b/Yugen.Mosaic.Uwp/Extensions/EnumExtensions.cs @@ -0,0 +1,33 @@ +using System; +using System.ComponentModel; +using System.Reflection; + +namespace Yugen.Mosaic.Uwp.Extensions +{ + public static class EnumExtensions + { + public static string GetStringRepresentation(this Enum en) + { + if (en == null) + { + return null; + } + + Type type = en.GetType(); + + MemberInfo[] memInfo = type.GetMember(en.ToString()); + + if (memInfo != null && memInfo.Length > 0) + { + var attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false); + + if (attrs != null && attrs.Length > 0) + { + return ((DescriptionAttribute)attrs[0]).Description; + } + } + + return en.ToString(); + } + } +} \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Extensions/FileFormatExtensions.cs b/Yugen.Mosaic.Uwp/Extensions/FileFormatExtensions.cs deleted file mode 100644 index 0110fd8..0000000 --- a/Yugen.Mosaic.Uwp/Extensions/FileFormatExtensions.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Yugen.Mosaic.Uwp.Enums; - -namespace Yugen.Mosaic.Uwp.Extensions -{ - public static class FileFormatExtensions - { - public static string FileFormatToString(this FileFormat fileFormat) => $".{fileFormat.ToString().ToLower()}"; - } -} diff --git a/Yugen.Mosaic.Uwp/Extensions/SettingsStorageExtensions.cs b/Yugen.Mosaic.Uwp/Extensions/SettingsStorageExtensions.cs deleted file mode 100644 index dfd7b75..0000000 --- a/Yugen.Mosaic.Uwp/Extensions/SettingsStorageExtensions.cs +++ /dev/null @@ -1,98 +0,0 @@ -//using System; -//using System.IO; -//using System.Threading.Tasks; -//using Windows.Storage; -//using Windows.Storage.Streams; -//using Yugen.Toolkit.Standard.Providers; - -//namespace Yugen.Mosaic.Uwp.Extensions -//{ - -// public static class SettingsStorageExtensions -// { -// private const string FileExtension = ".json"; - -// public static bool IsRoamingStorageAvailable(this ApplicationData appData) -// { -// return appData.RoamingStorageQuota == 0; -// } - -// public static async Task SaveAsync(this StorageFolder folder, string name, T content) -// { -// var file = await folder.CreateFileAsync(GetFileName(name), CreationCollisionOption.ReplaceExisting); -// var fileContent = await JsonProvider.StringifyAsync(content); - -// await FileIO.WriteTextAsync(file, fileContent); -// } - -// public static async Task ReadAsync(this StorageFolder folder, string name) -// { -// if (!File.Exists(Path.Combine(folder.Path, GetFileName(name)))) -// { -// return default; -// } - -// var file = await folder.GetFileAsync($"{name}.json"); -// var fileContent = await FileIO.ReadTextAsync(file); - -// return await JsonProvider.ToObjectAsync(fileContent); -// } - - - -// public static async Task SaveFileAsync(this StorageFolder folder, byte[] content, string fileName, CreationCollisionOption options = CreationCollisionOption.ReplaceExisting) -// { -// if (content == null) -// { -// throw new ArgumentNullException(nameof(content)); -// } - -// if (string.IsNullOrEmpty(fileName)) -// { -// throw new ArgumentException("ExceptionSettingsStorageExtensionsFileNameIsNullOrEmpty", nameof(fileName)); -// } - -// var storageFile = await folder.CreateFileAsync(fileName, options); -// await FileIO.WriteBytesAsync(storageFile, content); -// return storageFile; -// } - -// public static async Task ReadFileAsync(this StorageFolder folder, string fileName) -// { -// var item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false); - -// if (item != null && item.IsOfType(StorageItemTypes.File)) -// { -// var storageFile = await folder.GetFileAsync(fileName); -// byte[] content = await storageFile.ReadBytesAsync(); -// return content; -// } - -// return null; -// } - -// public static async Task ReadBytesAsync(this StorageFile file) -// { -// if (file != null) -// { -// using (IRandomAccessStream stream = await file.OpenReadAsync()) -// { -// using (var reader = new DataReader(stream.GetInputStreamAt(0))) -// { -// await reader.LoadAsync((uint)stream.Size); -// var bytes = new byte[stream.Size]; -// reader.ReadBytes(bytes); -// return bytes; -// } -// } -// } - -// return null; -// } - -// private static string GetFileName(string name) -// { -// return string.Concat(name, FileExtension); -// } -// } -//} diff --git a/Yugen.Mosaic.Uwp/Helpers/ColorHelper.cs b/Yugen.Mosaic.Uwp/Helpers/ColorHelper.cs new file mode 100644 index 0000000..7c37e46 --- /dev/null +++ b/Yugen.Mosaic.Uwp/Helpers/ColorHelper.cs @@ -0,0 +1,52 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using System; +using System.Threading.Tasks; + +namespace Yugen.Mosaic.Uwp.Helpers +{ + public static class ColorHelper + { + public static Rgba32 GetAverageColor(Image source, int x, int y, Size tileSize) => GetAverageColor(source, x * tileSize.Width, y * tileSize.Height, + tileSize.Width, tileSize.Height, x * tileSize.Width + tileSize.Width, y * tileSize.Height + tileSize.Height); + + public static Rgba32 GetAverageColor(Image source) => GetAverageColor(source, 0, 0, source.Width, source.Height, source.Width, source.Height); + + private static Rgba32 GetAverageColor(Image source, int startX, int startY, int width, int height, int endX, int endY) + { + long aR = 0; + long aG = 0; + long aB = 0; + + Parallel.For(startY, endY, h => + { + Span rowSpan = source.GetPixelRowSpan(h); + + for (var w = startX; w < endX; w++) + { + var pixel = new Rgba32(); + rowSpan[w].ToRgba32(ref pixel); + + aR += pixel.R; + aG += pixel.G; + aB += pixel.B; + } + }); + + aR /= width * height; + aG /= width * height; + aB /= width * height; + + return new Rgba32(Convert.ToByte(aR), Convert.ToByte(aG), Convert.ToByte(aB)); + } + + public static int GetDifference(Rgba32 source, Rgba32 target) + { + var dR = Math.Abs(source.R - target.R); + var dG = Math.Abs(source.G - target.G); + var dB = Math.Abs(source.B - target.B); + var diff = Math.Max(dR, dG); + return Math.Max(diff, dB); + } + } +} diff --git a/Yugen.Mosaic.Uwp/Helpers/EnumHelper.cs b/Yugen.Mosaic.Uwp/Helpers/EnumHelper.cs new file mode 100644 index 0000000..6c7dba8 --- /dev/null +++ b/Yugen.Mosaic.Uwp/Helpers/EnumHelper.cs @@ -0,0 +1,40 @@ +using System; +using System.ComponentModel; +using System.Reflection; + +namespace Yugen.Mosaic.Uwp.Helpers +{ + public static class EnumHelper + { + public static T GetValueFromDescription(string description) + { + Type type = typeof(T); + + if (!type.IsEnum) + { + throw new InvalidOperationException(); + } + + foreach (FieldInfo field in type.GetFields()) + { + if (Attribute.GetCustomAttribute(field, + typeof(DescriptionAttribute)) is DescriptionAttribute attribute) + { + if (attribute.Description == description) + { + return (T)field.GetValue(null); + } + } + else + { + if (field.Name == description) + { + return (T)field.GetValue(null); + } + } + } + + return default; + } + } +} \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Helpers/MathHelper.cs b/Yugen.Mosaic.Uwp/Helpers/MathHelper.cs index c6ab97a..06829cd 100644 --- a/Yugen.Mosaic.Uwp/Helpers/MathHelper.cs +++ b/Yugen.Mosaic.Uwp/Helpers/MathHelper.cs @@ -1,4 +1,6 @@ -namespace Yugen.Mosaic.Uwp.Helpers +using System; + +namespace Yugen.Mosaic.Uwp.Helpers { public static class MathHelper { @@ -12,8 +14,20 @@ public static class MathHelper /// /// /// newValue - public static double RangesConverter(double oldValue, double oldMin, double oldMax, double newMin, double newMax) => + public static double RangeConvert(double oldValue, double oldMin, double oldMax, double newMin, double newMax) => (oldValue - oldMin) * (newMax - newMin) / (oldMax - oldMin) + newMin; + + public static Tuple RatioConvert(int width, int height, int newHeight, int newWidth) + { + //calculate the ratio + var ratio = (double)width / (double)height; + + //set height of image to boxHeight and check if resulting width is less than boxWidth, + //else set width of image to boxWidth and calculate new height + return (int)(newHeight * ratio) <= newWidth + ? new Tuple((int)(newHeight * ratio), newHeight) + : new Tuple(newWidth, (int)(newWidth / ratio)); + } } } \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Helpers/OnboardingHelper.cs b/Yugen.Mosaic.Uwp/Helpers/OnboardingHelper.cs index 960c771..fbe97c6 100644 --- a/Yugen.Mosaic.Uwp/Helpers/OnboardingHelper.cs +++ b/Yugen.Mosaic.Uwp/Helpers/OnboardingHelper.cs @@ -1,41 +1,15 @@ using Windows.UI.Xaml; -using Yugen.Mosaic.Uwp.Extensions; +using Yugen.Mosaic.Uwp.Enums; +using Yugen.Mosaic.Uwp.Models; namespace Yugen.Mosaic.Uwp.Helpers { - public enum OnboardingStage - { - MasterImage, - AddTiles, - TileProperties, - MosaicType, - OutputProperties, - Generate, - Save - } - - public class OnboardingElement - { - public string Title { get; set; } - - public string Subtitle { get; set; } - - public FrameworkElement Target { get; set; } - - public OnboardingStage Stage { get; set; } - - public OnboardingElement(string title, string subtitle, FrameworkElement target, OnboardingStage stage) - { - Title = title; - Subtitle = subtitle; - Target = target; - Stage = stage; - } - } public static class OnboardingHelper { private const string SettingsKey = "OnboardingIsEnabled"; + private static OnboardingElement[] _onboardingElements; + private static int _step; public static bool IsDisabled { @@ -43,52 +17,39 @@ public static bool IsDisabled set => SettingsHelper.Write(SettingsKey, value); } - private static int _step; - private static OnboardingElement[] _onboardingElements; - - public static void Init(FrameworkElement[] frameworkElements) - { - var masterImageDescription = "Choose what image you want to use as the Matrix of the mosaic. " + - "Yugen Mosaic will create a mosaic as close as possible to the main image."; - - var addTilesDescription = "Add a list of Tile Images, Yugen Mosaic will use as tiles to build your mosaic"; - - var tilesPropertiesDescription = "After you created the Tile Images List, it is necessary to set all the parameters of the mosaic." + - "Here you can set the size of every single tile"; - - var mosaicTypeDescription = "Choose the tpe of mosaic."; - - var outputDescription = "Choose the size of the mosaic."; - - var generateDescription = "Create the mosaic."; - - var saveDescription = "The last step is to save the mosaic."; - - _onboardingElements = new OnboardingElement[] + public static void Init(FrameworkElement[] frameworkElements) => _onboardingElements = new OnboardingElement[] { - new OnboardingElement("Select the Main Image", masterImageDescription, - frameworkElements[0], OnboardingStage.MasterImage), - new OnboardingElement("Create a Tile Images List", addTilesDescription, - frameworkElements[1], OnboardingStage.AddTiles), - new OnboardingElement("TileProperties", tilesPropertiesDescription, - frameworkElements[2], OnboardingStage.TileProperties), - new OnboardingElement("MosaicType", mosaicTypeDescription, - frameworkElements[3], OnboardingStage.MosaicType), - new OnboardingElement("Set the parameters of the Mosaic", outputDescription, - frameworkElements[4], OnboardingStage.OutputProperties), - new OnboardingElement("Create the Mosaic", generateDescription, - frameworkElements[5], OnboardingStage.Generate), - new OnboardingElement("Save the Mosaic", saveDescription, - frameworkElements[6], OnboardingStage.Save), + new OnboardingElement( + frameworkElements[0], + OnboardingStage.MasterImage), + new OnboardingElement( + frameworkElements[1], + OnboardingStage.AddTiles), + new OnboardingElement( + frameworkElements[2], + OnboardingStage.TileProperties), + new OnboardingElement( + frameworkElements[3], + OnboardingStage.MosaicType), + new OnboardingElement( + frameworkElements[4], + OnboardingStage.OutputProperties), + new OnboardingElement( + frameworkElements[5], + OnboardingStage.Generate), + new OnboardingElement( + frameworkElements[6], + OnboardingStage.Save), }; - } public static OnboardingElement ShowTeachingTip() { OnboardingElement onboardingElement = null; if (_step < 0 || IsDisabled) + { return onboardingElement; + } if (_step < _onboardingElements.Length) { diff --git a/Yugen.Mosaic.Uwp/Helpers/RatioHelper.cs b/Yugen.Mosaic.Uwp/Helpers/RatioHelper.cs deleted file mode 100644 index 077d185..0000000 --- a/Yugen.Mosaic.Uwp/Helpers/RatioHelper.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace Yugen.Mosaic.Uwp.Helpers -{ - public static class RatioHelper - { - public static Tuple Convert(int width, int height, int newHeight, int newWidth) - { - //calculate the ratio - double ratio = (double)width / (double)height; - - //set height of image to boxHeight and check if resulting width is less than boxWidth, - //else set width of image to boxWidth and calculate new height - return (int)(newHeight * ratio) <= newWidth - ? new Tuple((int)(newHeight * ratio), newHeight) - : new Tuple(newWidth, (int)(newWidth / ratio)); - } - } -} diff --git a/Yugen.Mosaic.Uwp/Helpers/ResourceHelper.cs b/Yugen.Mosaic.Uwp/Helpers/ResourceHelper.cs new file mode 100644 index 0000000..497e718 --- /dev/null +++ b/Yugen.Mosaic.Uwp/Helpers/ResourceHelper.cs @@ -0,0 +1,12 @@ +using Windows.ApplicationModel.Resources; + +namespace Yugen.Mosaic.Uwp.Helpers +{ + public static class ResourceHelper + { + private static readonly ResourceLoader _resourceLoader = _resourceLoader ?? new ResourceLoader(); + + public static string GetText(string key) => + _resourceLoader.GetString(key); + } +} \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Extensions/SettingsHelper.cs b/Yugen.Mosaic.Uwp/Helpers/SettingsHelper.cs similarity index 61% rename from Yugen.Mosaic.Uwp/Extensions/SettingsHelper.cs rename to Yugen.Mosaic.Uwp/Helpers/SettingsHelper.cs index 8e617bb..5da42b8 100644 --- a/Yugen.Mosaic.Uwp/Extensions/SettingsHelper.cs +++ b/Yugen.Mosaic.Uwp/Helpers/SettingsHelper.cs @@ -3,7 +3,7 @@ using Windows.Storage; using Yugen.Toolkit.Standard.Providers; -namespace Yugen.Mosaic.Uwp.Extensions +namespace Yugen.Mosaic.Uwp.Helpers { public static class SettingsHelper { @@ -21,18 +21,12 @@ public static void Write(string key, T value) LocalSettings.Values[key] = valueString; } - public static async Task ReadAsync(string key) - { - return LocalSettings.Values.TryGetValue(key, out object value) - ? await JsonProvider.ToObjectAsync((string) value) + public static async Task ReadAsync(string key) => LocalSettings.Values.TryGetValue(key, out var value) + ? await JsonProvider.ToObjectAsync((string)value) : default; - } - public static T Read(string key) - { - return LocalSettings.Values.TryGetValue(key, out object value) - ? JsonConvert.DeserializeObject((string) value) + public static T Read(string key) => LocalSettings.Values.TryGetValue(key, out var value) + ? JsonConvert.DeserializeObject((string)value) : default; - } } } \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Helpers/TaskHelper.cs b/Yugen.Mosaic.Uwp/Helpers/TaskHelper.cs deleted file mode 100644 index 497cd3e..0000000 --- a/Yugen.Mosaic.Uwp/Helpers/TaskHelper.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Threading.Tasks; -using Yugen.Toolkit.Standard.Helpers; - -namespace Yugen.Mosaic.Uwp.Helpers -{ - public static class TaskHelper - { -#pragma warning disable RECS0165 // Asynchronous methods should return a Task instead of void - public static async void FireAndForgetSafeAsync(this Task task) -#pragma warning restore RECS0165 // Asynchronous methods should return a Task instead of void - { - try - { - await task; - } - catch (Exception ex) - { - LoggerHelper.WriteLine(task.GetType(), ex); - } - } - } -} diff --git a/Yugen.Mosaic.Uwp/Interfaces/IMosaicService.cs b/Yugen.Mosaic.Uwp/Interfaces/IMosaicService.cs new file mode 100644 index 0000000..c14d673 --- /dev/null +++ b/Yugen.Mosaic.Uwp/Interfaces/IMosaicService.cs @@ -0,0 +1,19 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using System.IO; +using Windows.Storage.Streams; +using Yugen.Mosaic.Uwp.Enums; + +namespace Yugen.Mosaic.Uwp.Interfaces +{ + public interface IMosaicService + { + Image AddMasterImage(Stream stream); + Image AddTileImage(string name, Stream stream); + Image GenerateMosaic(Size outputSize, Size tileSize, MosaicTypeEnum selectedMosaicType); + Image GetResizedImage(Image image, int size); + InMemoryRandomAccessStream GetStream(Image image); + void RemoveTileImage(string name); + void Reset(); + } +} \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Interfaces/ISearchAndReplaceService.cs b/Yugen.Mosaic.Uwp/Interfaces/ISearchAndReplaceService.cs new file mode 100644 index 0000000..022c4c8 --- /dev/null +++ b/Yugen.Mosaic.Uwp/Interfaces/ISearchAndReplaceService.cs @@ -0,0 +1,7 @@ +namespace Yugen.Mosaic.Uwp.Interfaces +{ + public interface ISearchAndReplaceService + { + void SearchAndReplace(); + } +} \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Models/MosaicType.cs b/Yugen.Mosaic.Uwp/Models/MosaicType.cs index 550a6d4..8f6712d 100644 --- a/Yugen.Mosaic.Uwp/Models/MosaicType.cs +++ b/Yugen.Mosaic.Uwp/Models/MosaicType.cs @@ -1,10 +1,17 @@ -namespace Yugen.Mosaic.Uwp.Models +using Yugen.Mosaic.Uwp.Enums; +using Yugen.Mosaic.Uwp.Extensions; + +namespace Yugen.Mosaic.Uwp.Models { public class MosaicType { - public int Id { get; set; } - public string Title { get; set; } + public MosaicType(MosaicTypeEnum mosaicTypeEnum) + { + MosaicTypeEnum = mosaicTypeEnum; + } + + public MosaicTypeEnum MosaicTypeEnum { get; set; } - public override string ToString() => Title; + public override string ToString() => MosaicTypeEnum.GetStringRepresentation(); } } diff --git a/Yugen.Mosaic.Uwp/Models/OnboardingElement.cs b/Yugen.Mosaic.Uwp/Models/OnboardingElement.cs new file mode 100644 index 0000000..848feaf --- /dev/null +++ b/Yugen.Mosaic.Uwp/Models/OnboardingElement.cs @@ -0,0 +1,22 @@ +using Windows.UI.Xaml; +using Yugen.Mosaic.Uwp.Enums; +using Yugen.Mosaic.Uwp.Helpers; + +namespace Yugen.Mosaic.Uwp.Models +{ + public class OnboardingElement + { + public OnboardingElement(FrameworkElement target, OnboardingStage stage) + { + Title = ResourceHelper.GetText($"OnboardingStage{stage}Title"); + Subtitle = ResourceHelper.GetText($"OnboardingStage{stage}Description"); + Target = target; + Stage = stage; + } + + public OnboardingStage Stage { get; set; } + public string Subtitle { get; set; } + public FrameworkElement Target { get; set; } + public string Title { get; set; } + } +} \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Models/Tile.cs b/Yugen.Mosaic.Uwp/Models/Tile.cs index 76a11a8..1b17ff1 100644 --- a/Yugen.Mosaic.Uwp/Models/Tile.cs +++ b/Yugen.Mosaic.Uwp/Models/Tile.cs @@ -18,29 +18,3 @@ public Tile(Image originalImage, string name) } } } - -//Rgba32 targetColor = _averageColor.ToPixel(); - -//Color clAvg = Color.FromRgba(255, Convert.ToByte(R), Convert.ToByte(G), Convert.ToByte(B)); -//Rgba32 clAvg = Color.FromRgba(255, Convert.ToByte(R), Convert.ToByte(G), Convert.ToByte(B)); -//Rgba32 clAvg = new Rgba32(Convert.ToByte(R), Convert.ToByte(G), Convert.ToByte(B), 255); - -//TPixel pixelColor = new TPixel(); -//pixelColor.FromRgba32(clAvg); - -//public async Task RunTasks(WriteableBitmap clone) -//{ -// var tasks = new List(); - -// tasks.Add(Task.Run(() => DoWork(400, 1, clone))); -// tasks.Add(Task.Run(() => DoWork(200, 2, clone))); -// tasks.Add(Task.Run(() => DoWork(300, 3, clone))); - -// await Task.WhenAll(tasks); -//} - -//public async Task DoWork(int delay, int n, WriteableBitmap masterImageSource) -//{ -// await Task.Delay(delay); -// System.Diagnostics.Debug.WriteLine($"{n} {masterImageSource.PixelHeight}"); -//} diff --git a/Yugen.Mosaic.Uwp/Models/TileBmp.cs b/Yugen.Mosaic.Uwp/Models/TileBmp.cs index a728b2f..65a9a5a 100644 --- a/Yugen.Mosaic.Uwp/Models/TileBmp.cs +++ b/Yugen.Mosaic.Uwp/Models/TileBmp.cs @@ -4,13 +4,13 @@ namespace Yugen.Mosaic.Uwp.Models { public class TileBmp { - public string Name { get; set; } - public BitmapImage Image { get; set; } - public TileBmp(string name, BitmapImage image) { Name = name; Image = image; } + + public BitmapImage Image { get; set; } + public string Name { get; set; } } } diff --git a/Yugen.Mosaic.Uwp/Package.appxmanifest b/Yugen.Mosaic.Uwp/Package.appxmanifest index 00b24ed..c2d63b4 100644 --- a/Yugen.Mosaic.Uwp/Package.appxmanifest +++ b/Yugen.Mosaic.Uwp/Package.appxmanifest @@ -7,7 +7,7 @@ IgnorableNamespaces="uap mp"> diff --git a/Yugen.Mosaic.Uwp/Processors/AdjustHueProcessor.cs b/Yugen.Mosaic.Uwp/Processors/AdjustHueProcessor.cs deleted file mode 100644 index 9a03a40..0000000 --- a/Yugen.Mosaic.Uwp/Processors/AdjustHueProcessor.cs +++ /dev/null @@ -1,81 +0,0 @@ -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Primitives; -using System; -using System.Threading.Tasks; - -namespace Yugen.Mosaic.Uwp.Processors -{ - public sealed class AdjustHueProcessor : IImageProcessor - { - public Image InputImage { get; } - public Rgba32 AverageColor { get; } - - public AdjustHueProcessor(Image inputImage, Rgba32 averageColor) - { - InputImage = inputImage; - AverageColor = averageColor; - } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) where TPixel : struct, IPixel - { - return new AdjustHueProcessor(this, source, sourceRectangle); - } - } - - public class AdjustHueProcessor : IImageProcessor where TPixel : struct, IPixel - { - /// - /// The source instance to modify - /// - private readonly Image _source; - private readonly Image _inputImage; - private readonly Rgba32 _averageColor; - - /// - /// Initializes a new instance of the class - /// - /// The defining the processor parameters - /// The source for the current processor instance - /// The source area to process for the current processor instance - public AdjustHueProcessor(AdjustHueProcessor definition, Image source, Rectangle sourceRectangle) - { - _source = source; - _inputImage = definition.InputImage; - _averageColor = definition.AverageColor; - } - - /// - public void Apply() - { - //int width = Source.Width; - //Image source = Source; - - Parallel.For(0, _inputImage.Height, h => - { - var rowSpan = _inputImage.GetPixelRowSpan(h); - - for (int w = 0; w < _inputImage.Width; w++) - { - Rgba32 pixel = new Rgba32(); - rowSpan[w].ToRgba32(ref pixel); - - int R = Math.Min(255, Math.Max(0, (pixel.R + _averageColor.R) / 2)); - int G = Math.Min(255, Math.Max(0, (pixel.G + _averageColor.G) / 2)); - int B = Math.Min(255, Math.Max(0, (pixel.B + _averageColor.B) / 2)); - - Color clAvg = new Rgba32(Convert.ToByte(R), Convert.ToByte(G), Convert.ToByte(B)); - - TPixel pixelColor = clAvg.ToPixel(); - _source[w, h] = pixelColor; - } - }); - } - - /// - public void Dispose() { } - } -} diff --git a/Yugen.Mosaic.Uwp/Processors/ApplyTileFoundProcessor.cs b/Yugen.Mosaic.Uwp/Processors/ApplyTileFoundProcessor.cs deleted file mode 100644 index 1fb9632..0000000 --- a/Yugen.Mosaic.Uwp/Processors/ApplyTileFoundProcessor.cs +++ /dev/null @@ -1,90 +0,0 @@ -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Primitives; -using System; -using System.Threading.Tasks; - -namespace Yugen.Mosaic.Uwp.Processors -{ - public sealed class ApplyTileFoundProcessor : IImageProcessor - { - public int X { get; } - public int Y { get; } - public int Width { get; } - public int Height { get; } - - public Image OutputImage { get; } - - public ApplyTileFoundProcessor(int x, int y, int width, int height, Image outputImage) - { - X = x; - Y = y; - Width = width; - Height = height; - - OutputImage = outputImage; - } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) where TPixel : struct, IPixel - { - return new ApplyTileFoundProcessor(this, source, sourceRectangle); - } - } - - public class ApplyTileFoundProcessor : IImageProcessor where TPixel : struct, IPixel - { - /// - /// The source instance to modify - /// - private readonly Image _source; - - private readonly int _x; - private readonly int _y; - private readonly int _width; - private readonly int _height; - - private readonly Image _outputImage; - - /// - /// Initializes a new instance of the class - /// - /// The defining the processor parameters - /// The source for the current processor instance - /// The source area to process for the current processor instance - public ApplyTileFoundProcessor(ApplyTileFoundProcessor definition, Image source, Rectangle sourceRectangle) - { - _source = source; - - _x = definition.X; - _y = definition.Y; - _width = definition.Width; - _height = definition.Height; - - _outputImage = definition.OutputImage; - } - - /// - public void Apply() - { - Parallel.For(0, _height, h => - { - var rowSpan = _source.GetPixelRowSpan(h); - - for (int w = 0; w < _width; w++) - { - Rgba32 pixel = new Rgba32(); - rowSpan[w].ToRgba32(ref pixel); - - _outputImage[_x * _width + w, _y * _height + h] = pixel; - } - }); - } - - /// - public void Dispose() { } - } -} diff --git a/Yugen.Mosaic.Uwp/Processors/GetTileAverageProcessor.cs b/Yugen.Mosaic.Uwp/Processors/GetTileAverageProcessor.cs deleted file mode 100644 index 6abb968..0000000 --- a/Yugen.Mosaic.Uwp/Processors/GetTileAverageProcessor.cs +++ /dev/null @@ -1,106 +0,0 @@ -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Primitives; -using System; -using System.Threading.Tasks; - -namespace Yugen.Mosaic.Uwp.Processors -{ - public sealed class GetTileAverageProcessor : IImageProcessor - { - public int X { get; } - public int Y { get; } - public int Width { get; } - public int Height { get; } - - public Image ResizedImage { get; } - public Rgba32[] AverageColor { get; } = new Rgba32[1]; - - public GetTileAverageProcessor(int x, int y, int width, int height, Image resizedImage) - { - X = x; - Y = y; - Width = width; - Height = height; - ResizedImage = resizedImage.CloneAs(); - } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) where TPixel : struct, IPixel - { - return new GetTileAverageProcessor(this, source, sourceRectangle); - } - } - - public class GetTileAverageProcessor : IImageProcessor where TPixel : struct, IPixel - { - /// - /// The source instance to modify - /// - private readonly Image _source; - - private readonly int _x; - private readonly int _y; - private readonly int _width; - private readonly int _height; - - private readonly Image _resizedImage; - private readonly Rgba32[] _averageColor; - - /// - /// Initializes a new instance of the class - /// - /// The defining the processor parameters - /// The source for the current processor instance - /// The source area to process for the current processor instance - public GetTileAverageProcessor(GetTileAverageProcessor definition, Image source, Rectangle sourceRectangle) - { - _source = source; - - _x = definition.X; - _y = definition.Y; - _width = definition.Width; - _height = definition.Height; - - _resizedImage = definition.ResizedImage; - _averageColor = definition.AverageColor; - } - - /// - public void Apply() - { - _resizedImage.Mutate(x => x.Resize(_width, _height)); - - long aR = 0; - long aG = 0; - long aB = 0; - - Parallel.For(_y, _y+_height, h => - { - var rowSpan = _resizedImage.GetPixelRowSpan(h); - - for (int w = _x; w < _x + _width; w++) - { - Rgba32 pixel = new Rgba32(); - rowSpan[w].ToRgba32(ref pixel); - - aR += pixel.R; - aG += pixel.G; - aB += pixel.B; - } - }); - - aR /= _width * _height; - aG /= _width * _height; - aB /= _width * _height; - - _averageColor[0] = new Rgba32(Convert.ToByte(aR), Convert.ToByte(aG), Convert.ToByte(aB)); - } - - /// - public void Dispose() { } - } -} diff --git a/Yugen.Mosaic.Uwp/Processors/GetTilesAverageProcessor.cs b/Yugen.Mosaic.Uwp/Processors/GetTilesAverageProcessor.cs deleted file mode 100644 index d980341..0000000 --- a/Yugen.Mosaic.Uwp/Processors/GetTilesAverageProcessor.cs +++ /dev/null @@ -1,111 +0,0 @@ -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Primitives; -using System; -using System.Threading.Tasks; - -namespace Yugen.Mosaic.Uwp.Processors -{ - public sealed class GetTilesAverageProcessor : IImageProcessor - { - public int TX { get; } - public int TY { get; } - public Size TileSize { get; } - - public Rgba32[,] AverageColors { get; } - - public GetTilesAverageProcessor(int tX, int tY, Size tileSize, Rgba32[,] avgsMaster) - { - TX = tX; - TY = tY; - TileSize = tileSize; - - AverageColors = avgsMaster; - } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) where TPixel : struct, IPixel - { - return new GetTilesAverageProcessor(this, source, sourceRectangle); - } - } - - public class GetTilesAverageProcessor : IImageProcessor where TPixel : struct, IPixel - { - /// - /// The source instance to modify - /// - private readonly Image _source; - - private readonly int _tX; - private readonly int _tY; - private Size _tileSize; - - private readonly Rgba32[,] _averageColors; - - /// - /// Initializes a new instance of the class - /// - /// The defining the processor parameters - /// The source for the current processor instance - /// The source area to process for the current processor instance - public GetTilesAverageProcessor(GetTilesAverageProcessor definition, Image source, Rectangle sourceRectangle) - { - _source = source; - - _tX = definition.TX; - _tY = definition.TY; - _tileSize = definition.TileSize; - - _averageColors = definition.AverageColors; - } - - /// - public void Apply() - { - Parallel.For(0, _tY, y => - { - var rowSpan = _source.GetPixelRowSpan(y); - - for (int x = 0; x < _tX; x++) - { - _averageColors[x, y].FromRgba32(GetTileAverage(_source, x * _tileSize.Width, y * _tileSize.Height, _tileSize.Width, _tileSize.Height)); - } - }); - } - - private Rgba32 GetTileAverage(Image source, int x, int y, int width, int height) - { - long aR = 0; - long aG = 0; - long aB = 0; - - Parallel.For(y, y + height, h => - { - var rowSpan = _source.GetPixelRowSpan(h); - - for (int w = x; w < x + width; w++) - { - Rgba32 pixel = new Rgba32(); - rowSpan[w].ToRgba32(ref pixel); - - aR += pixel.R; - aG += pixel.G; - aB += pixel.B; - } - }); - - aR /= width * height; - aG /= width * height; - aB /= width * height; - - return new Rgba32(Convert.ToByte(aR), Convert.ToByte(aG), Convert.ToByte(aB)); - } - - - /// - public void Dispose() { } - } -} diff --git a/Yugen.Mosaic.Uwp/Processors/PlainColorProcessor.cs b/Yugen.Mosaic.Uwp/Processors/PlainColorProcessor.cs deleted file mode 100644 index 07c6af7..0000000 --- a/Yugen.Mosaic.Uwp/Processors/PlainColorProcessor.cs +++ /dev/null @@ -1,77 +0,0 @@ -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Primitives; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; - -namespace Yugen.Mosaic.Uwp.Processors -{ - public sealed class PlainColorProcessor : IImageProcessor - { - public Rgba32 AverageColor { get; } - - public PlainColorProcessor(Rgba32 averageColor) - { - AverageColor = averageColor; - } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle) where TPixel : struct, IPixel - { - return new PlainColorProcessor(this, source, sourceRectangle); - } - } - - public class PlainColorProcessor : IImageProcessor where TPixel : struct, IPixel - { - /// - /// The source instance to modify - /// - private readonly Image _source; - - private readonly Rgba32 _averageColor; - - /// - /// Initializes a new instance of the class - /// - /// The defining the processor parameters - /// The source for the current processor instance - /// The source area to process for the current processor instance - public PlainColorProcessor(PlainColorProcessor definition, Image source, Rectangle sourceRectangle) - { - _source = source; - _averageColor = definition.AverageColor; - } - - /// - public void Apply() - { - int width = _source.Width; - Image source = _source; - //RgbaVector averageColorVector = AverageColor.ToPixel(); - - var averageColor4 = _averageColor.ToVector4(); //var b = AverageColor.ToScaledVector4(); - - Parallel.For(0, source.Height, y => - { - //Vector4 averageColor4 = Unsafe.As(ref averageColorVector); - ref TPixel r0 = ref source.GetPixelRowSpan(y).GetPinnableReference(); - - for (int x = 0; x < width; x++) - { - Vector4 color4 = Unsafe.Add(ref r0, x).ToVector4(); - color4 = (color4 + averageColor4) / 2; - color4 = Vector4.Clamp(color4, Vector4.Zero, Vector4.One); - - Unsafe.Add(ref r0, x).FromVector4(color4); - } - }); - } - - /// - public void Dispose() { } - } -} diff --git a/Yugen.Mosaic.Uwp/Properties/AssemblyInfo.cs b/Yugen.Mosaic.Uwp/Properties/AssemblyInfo.cs index 9a1479d..d53b0dd 100644 --- a/Yugen.Mosaic.Uwp/Properties/AssemblyInfo.cs +++ b/Yugen.Mosaic.Uwp/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/Yugen.Mosaic.Uwp/Services/AdjustHueSearchAndReplaceService.cs b/Yugen.Mosaic.Uwp/Services/AdjustHueSearchAndReplaceService.cs new file mode 100644 index 0000000..227c705 --- /dev/null +++ b/Yugen.Mosaic.Uwp/Services/AdjustHueSearchAndReplaceService.cs @@ -0,0 +1,84 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Yugen.Mosaic.Uwp.Models; + +namespace Yugen.Mosaic.Uwp.Services +{ + public class AdjustHueSearchAndReplaceService : SearchAndReplaceService + { + public AdjustHueSearchAndReplaceService(Image outputImage, Size tileSize, int tX, int tY, List tileImageList, Rgba32[,] avgsMaster) : base(outputImage, tileSize, tX, tY, tileImageList, avgsMaster) + { + } + + // Adjust hue - get the first (random) tile found and adjust its colours to suit the average + public override void SearchAndReplace() + { + var r = new Random(); + var tileQueue = new List(); + //int maxQueueLength = Math.Min(1000, Math.Max(0, _tileImageList.Count - 50)); + + Parallel.For(0, _tX * _tY, xy => + { + var y = xy / _tX; + var x = xy % _tX; + + // (R * ColCount) + C + var index = ((y * _tX) + x) % _tileImageList.Count; + + // Check if it's the same as the last (X)? + //if (tileQueue.Count > 1) + //{ + // while (tileQueue.Contains(_tileImageList[index])) + // { + // index = r.Next(_tileImageList.Count); + // } + //} + + // Add to the 'queue' + Tile tileFound = _tileImageList[index]; + //if (tileQueue.Count >= maxQueueLength && tileQueue.Count > 0) + // tileQueue.RemoveAt(0); + //tileQueue.Add(tileFound); + + // Adjust the hue + var adjustedImage = new Image(tileFound.ResizedImage.Width, tileFound.ResizedImage.Height); + AdjustHue(tileFound.ResizedImage, adjustedImage, _avgsMaster[x, y]); + + // Apply found tile to section + ApplyTileFound(x, y, adjustedImage); + + //_progress++; + }); + } + + private void AdjustHue(Image source, Image output, Rgba32 averageColor) + { + output.Mutate(c => + { + Parallel.For(0, source.Height, h => + { + var rowSpan = source.GetPixelRowSpan(h); + + for (int w = 0; w < source.Width; w++) + { + Rgba32 pixel = new Rgba32(); + rowSpan[w].ToRgba32(ref pixel); + + int R = Math.Min(255, Math.Max(0, (pixel.R + averageColor.R) / 2)); + int G = Math.Min(255, Math.Max(0, (pixel.G + averageColor.G) / 2)); + int B = Math.Min(255, Math.Max(0, (pixel.B + averageColor.B) / 2)); + + Color clAvg = new Rgba32(Convert.ToByte(R), Convert.ToByte(G), Convert.ToByte(B)); + + Rgba32 pixelColor = clAvg.ToPixel(); + output[w, h] = pixelColor; + } + }); + }); + } + } +} diff --git a/Yugen.Mosaic.Uwp/Services/ClassicSearchAndReplaceService.cs b/Yugen.Mosaic.Uwp/Services/ClassicSearchAndReplaceService.cs new file mode 100644 index 0000000..965cbc8 --- /dev/null +++ b/Yugen.Mosaic.Uwp/Services/ClassicSearchAndReplaceService.cs @@ -0,0 +1,46 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using System.Collections.Generic; +using System.Threading.Tasks; +using Yugen.Mosaic.Uwp.Helpers; +using Yugen.Mosaic.Uwp.Models; + +namespace Yugen.Mosaic.Uwp.Services +{ + public class ClassicSearchAndReplaceService : SearchAndReplaceService + { + public ClassicSearchAndReplaceService(Image outputImage, Size tileSize, int tX, int tY, List tileImageList, Rgba32[,] avgsMaster) : base(outputImage, tileSize, tX, tY, tileImageList, avgsMaster) + { + } + + public override void SearchAndReplace() + { + Parallel.For(0, _tX * _tY, xy => + { + int y = xy / _tX; + int x = xy % _tX; + + int index = 0; + int difference = 100; + Tile tileFound = _tileImageList[0]; + + // Search for a tile with a similar color + foreach (var tile in _tileImageList) + { + var newDifference = ColorHelper.GetDifference(_avgsMaster[x, y], _tileImageList[index].AverageColor); + if (newDifference < difference) + { + tileFound = _tileImageList[index]; + difference = newDifference; + } + index++; + } + + // Apply found tile to section + ApplyTileFound(x, y, tileFound.ResizedImage); + + //_progress++; + }); + } + } +} \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Services/MosaicService.cs b/Yugen.Mosaic.Uwp/Services/MosaicService.cs index 133ac21..dec1603 100644 --- a/Yugen.Mosaic.Uwp/Services/MosaicService.cs +++ b/Yugen.Mosaic.Uwp/Services/MosaicService.cs @@ -1,7 +1,6 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.Primitives; using System; using System.Collections.Generic; using System.IO; @@ -9,25 +8,25 @@ using System.Threading.Tasks; using Windows.Storage.Streams; using Windows.UI.Xaml.Media.Imaging; +using Yugen.Mosaic.Uwp.Enums; +using Yugen.Mosaic.Uwp.Helpers; +using Yugen.Mosaic.Uwp.Interfaces; using Yugen.Mosaic.Uwp.Models; -using Yugen.Mosaic.Uwp.Processors; namespace Yugen.Mosaic.Uwp.Services { - public class MosaicService + public class MosaicService : IMosaicService { - private Image _masterImage; - - private List _tileImageList { get; set; } = new List(); - private Size _tileSize; - - private int _tX; - private int _tY; - private Rgba32[,] _avgsMaster; + internal Rgba32[,] _avgsMaster; + internal int _progress; + internal int _tX; + internal int _tY; - private int _progress; + private Image _masterImage; private int _progressMax; + private Size _tileSize; + internal List _tileImageList { get; set; } = new List(); public Image AddMasterImage(Stream stream) { @@ -42,37 +41,12 @@ public Image AddTileImage(string name, Stream stream) return image; } - public void RemoveTileImage(string name) - { - var item = _tileImageList.FirstOrDefault(x => x.Name.Equals(name)); - if (item != null) - _tileImageList.Remove(item); - } - - public Image GetResizedImage(Image image, int size) + public Image GenerateMosaic(Size outputSize, Size tileSize, MosaicTypeEnum selectedMosaicType) { - var resizeOptions = new ResizeOptions() + if (_masterImage == null || (selectedMosaicType != MosaicTypeEnum.PlainColor && _tileImageList.Count < 1)) { - Mode = ResizeMode.Max, - Size = new Size(size, size) - }; - - return image.Clone(x => x.Resize(resizeOptions)); - } - - public InMemoryRandomAccessStream GetStream(Image image) - { - InMemoryRandomAccessStream outputStream = new InMemoryRandomAccessStream(); - image.SaveAsJpeg(outputStream.AsStreamForWrite()); - outputStream.Seek(0); - return outputStream; - } - - - public Image GenerateMosaic(Size outputSize, Size tileSize, int mosaicType) - { - if (_masterImage == null || (mosaicType != 2 && _tileImageList.Count < 1)) return null; + } Image resizedMasterImage = _masterImage.Clone(x => x.Resize(outputSize.Width, outputSize.Height)); @@ -85,210 +59,105 @@ public Image GenerateMosaic(Size outputSize, Size tileSize, int mosaicTy LoadTilesAndResize(); - var outputImage = new Image(outputSize.Width, outputSize.Height); - SearchAndReplace(outputImage, tileSize, mosaicType); - return outputImage; - } - - - private void GetTilesAverage(Image masterImage) - { - var getTilesAverageProcessor = new GetTilesAverageProcessor(_tX, _tY, _tileSize, _avgsMaster); - masterImage.Mutate(c => c.ApplyProcessor(getTilesAverageProcessor)); + return SearchAndReplace(tileSize, selectedMosaicType, outputSize); } - private void LoadTilesAndResize() + public Image GetResizedImage(Image image, int size) { - _progressMax = _tileImageList.Count; - _progress = 0; - - foreach (var tile in _tileImageList) + var resizeOptions = new ResizeOptions() { - var getTileAverageProcessor = new GetTileAverageProcessor(0, 0, _tileSize.Width, _tileSize.Height, tile.OriginalImage); - tile.OriginalImage.Mutate(c => c.ApplyProcessor(getTileAverageProcessor)); - - tile.ResizedImage = getTileAverageProcessor.ResizedImage; - tile.AverageColor = getTileAverageProcessor.AverageColor[0]; + Mode = ResizeMode.Max, + Size = new Size(size, size) + }; - _progress++; - } + return image.Clone(x => x.Resize(resizeOptions)); } - - private void SearchAndReplace(Image outputImage, Size tileSize, int mosaicType) + public InMemoryRandomAccessStream GetStream(Image image) { - _progressMax = _tileImageList.Count; - _progress = 0; + var outputStream = new InMemoryRandomAccessStream(); + image.SaveAsJpeg(outputStream.AsStreamForWrite()); + outputStream.Seek(0); + return outputStream; + } - switch (mosaicType) + public void RemoveTileImage(string name) + { + Tile item = _tileImageList.FirstOrDefault(x => x.Name.Equals(name)); + if (item != null) { - case 0: - SearchAndReplaceClassic(outputImage, tileSize); - break; - case 1: - SearchAndReplaceRandom(outputImage, tileSize); - break; - case 2: - SearchAndReplaceAdjustHue(outputImage, tileSize); - break; - case 3: - PlainColor(outputImage, tileSize); - break; + _tileImageList.Remove(item); } - - GC.Collect(); } - - private void SearchAndReplaceClassic(Image outputImage, Size tileSize) + public void Reset() { - Parallel.For(0, _tX * _tY, xy => - { - int y = xy / _tX; - int x = xy % _tX; - - int index = 0; - int difference = 100; - Tile tileFound = _tileImageList[0]; - - // Search for a tile with a similar color - foreach (var tile in _tileImageList) - { - var newDifference = GetDifference(_avgsMaster[x, y], _tileImageList[index].AverageColor); - if (newDifference < difference) - { - tileFound = _tileImageList[index]; - difference = newDifference; - } - index++; - } - - // Apply found tile to section - var applyTileFoundProcessor = new ApplyTileFoundProcessor(x, y, tileSize.Width, tileSize.Height, outputImage); - tileFound.ResizedImage.Mutate(c => c.ApplyProcessor(applyTileFoundProcessor)); - - _progress++; - }); + _masterImage = null; + _tileImageList.Clear(); } - // Don't adjust hue - keep searching for a tile close enough - private void SearchAndReplaceRandom(Image outputImage, Size tileSize) + private void GetTilesAverage(Image masterImage) { - Random r = new Random(); - - Parallel.For(0, _tX * _tY, xy => + Parallel.For(0, _tY, y => { - int y = xy / _tX; - int x = xy % _tX; + Span rowSpan = masterImage.GetPixelRowSpan(y); - // Reset searching variables - int threshold = 0; - int searchCounter = 0; - Tile tileFound = null; - - // Search for a tile with a similar color - while (tileFound == null) + for (var x = 0; x < _tX; x++) { - int index = r.Next(_tileImageList.Count); - var difference = GetDifference(_avgsMaster[x, y], _tileImageList[index].AverageColor); - if (difference < threshold) - { - tileFound = _tileImageList[index]; - } - else - { - searchCounter++; - if (searchCounter >= _tileImageList.Count) - threshold += 5; - } + _avgsMaster[x, y].FromRgba32(ColorHelper.GetAverageColor(masterImage, x, y, _tileSize)); } - - // Apply found tile to section - var applyTileFoundProcessor = new ApplyTileFoundProcessor(x, y, tileSize.Width, tileSize.Height, outputImage); - tileFound.ResizedImage.Mutate(c => c.ApplyProcessor(applyTileFoundProcessor)); - - _progress++; }); } - // Adjust hue - get the first (random) tile found and adjust its colours to suit the average - private void SearchAndReplaceAdjustHue(Image outputImage, Size tileSize) + private void LoadTilesAndResize() { - Random r = new Random(); - List tileQueue = new List(); - int maxQueueLength = Math.Min(1000, Math.Max(0, _tileImageList.Count - 50)); + _progressMax = _tileImageList.Count; + _progress = 0; - Parallel.For(0, _tX * _tY, xy => + foreach (Tile tile in _tileImageList) { - int y = xy / _tX; - int x = xy % _tX; - - int index = 0; - - // Check if it's the same as the last (X)? - if (tileQueue.Count > 1) - { - while (tileQueue.Contains(_tileImageList[index])) - { - index = r.Next(_tileImageList.Count); - } - } - - // Add to the 'queue' - Tile tileFound = _tileImageList[index]; - if (tileQueue.Count >= maxQueueLength && tileQueue.Count > 0) - tileQueue.RemoveAt(0); - tileQueue.Add(tileFound); - - // Adjust the hue - Image adjustedImage = new Image(tileFound.ResizedImage.Width, tileFound.ResizedImage.Height); - var adjustHueProcessor = new AdjustHueProcessor(tileFound.ResizedImage, _avgsMaster[x, y]); - adjustedImage.Mutate(c => c.ApplyProcessor(adjustHueProcessor)); - - // Apply found tile to section - var applyTileFoundProcessor = new ApplyTileFoundProcessor(x, y, tileSize.Width, tileSize.Height, outputImage); - adjustedImage.Mutate(c => c.ApplyProcessor(applyTileFoundProcessor)); + tile.ResizedImage = tile.OriginalImage.CloneAs(); ; + tile.ResizedImage.Mutate(x => x.Resize(_tileSize.Width, _tileSize.Height)); + tile.AverageColor = ColorHelper.GetAverageColor(tile.ResizedImage); _progress++; - }); + } } - // Use just mosic colored tiles - private void PlainColor(Image outputImage, Size tileSize) + private Image SearchAndReplace(Size tileSize, MosaicTypeEnum selectedMosaicType, Size outputSize) { - Parallel.For(0, _tX * _tY, xy => - { - int y = xy / _tX; - int x = xy % _tX; + var outputImage = new Image(outputSize.Width, outputSize.Height); + _progressMax = _tileImageList.Count; + _progress = 0; - // Generate colored tile - Image adjustedImage = new Image(tileSize.Width, tileSize.Height); - var plainColorProcessor = new PlainColorProcessor(_avgsMaster[x, y]); - adjustedImage.Mutate(c => c.ApplyProcessor(plainColorProcessor)); + ISearchAndReplaceService SearchAndReplaceService; - // Apply found tile to section - var applyTileFoundProcessor = new ApplyTileFoundProcessor(x, y, tileSize.Width, tileSize.Height, outputImage); - adjustedImage.Mutate(c => c.ApplyProcessor(applyTileFoundProcessor)); + switch (selectedMosaicType) + { + case MosaicTypeEnum.Classic: + SearchAndReplaceService = new ClassicSearchAndReplaceService(outputImage, tileSize, _tX, _tY, _tileImageList, _avgsMaster); + SearchAndReplaceService.SearchAndReplace(); + break; - _progress++; - }); - } + case MosaicTypeEnum.Random: + SearchAndReplaceService = new RandomSearchAndReplaceService(outputImage, tileSize, _tX, _tY, _tileImageList, _avgsMaster); + SearchAndReplaceService.SearchAndReplace(); + break; + case MosaicTypeEnum.AdjustHue: + SearchAndReplaceService = new AdjustHueSearchAndReplaceService(outputImage, tileSize, _tX, _tY, _tileImageList, _avgsMaster); + SearchAndReplaceService.SearchAndReplace(); + break; - private int GetDifference(Rgba32 source, Rgba32 target) - { - int dR = Math.Abs(source.R - target.R); - int dG = Math.Abs(source.G - target.G); - int dB = Math.Abs(source.B - target.B); - int diff = Math.Max(dR, dG); - return Math.Max(diff, dB); - } + case MosaicTypeEnum.PlainColor: + SearchAndReplaceService = new PlainColorSearchAndReplaceService(outputImage, tileSize, _tX, _tY, _tileImageList, _avgsMaster); + SearchAndReplaceService.SearchAndReplace(); + break; + } + GC.Collect(); - public void Reset() - { - _masterImage = null; - _tileImageList.Clear(); + return outputImage; } } -} +} \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Services/PlainColorSearchAndReplaceService.cs b/Yugen.Mosaic.Uwp/Services/PlainColorSearchAndReplaceService.cs new file mode 100644 index 0000000..324a37b --- /dev/null +++ b/Yugen.Mosaic.Uwp/Services/PlainColorSearchAndReplaceService.cs @@ -0,0 +1,44 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using System.Collections.Generic; +using System.Numerics; +using System.Threading.Tasks; +using Yugen.Mosaic.Uwp.Models; + +namespace Yugen.Mosaic.Uwp.Services +{ + public class PlainColorSearchAndReplaceService : SearchAndReplaceService + { + public PlainColorSearchAndReplaceService(Image outputImage, Size tileSize, int tX, int tY, List tileImageList, Rgba32[,] avgsMaster) : base(outputImage, tileSize, tX, tY, tileImageList, avgsMaster) + { + } + + // Use just mosic colored tiles + public override void SearchAndReplace() + { + Parallel.For(0, _tX * _tY, xy => + { + int y = xy / _tX; + int x = xy % _tX; + + // Generate colored tile + var adjustedImage = new Image(_tileSize.Width, _tileSize.Height); + var averageColor4 = _avgsMaster[x, y].ToVector4(); + + adjustedImage.Mutate(c => c.ProcessPixelRowsAsVector4(row => + { + foreach (ref Vector4 pixel in row) + { + pixel = (pixel + averageColor4) / 2; + } + })); + + // Apply found tile to section + ApplyTileFound(x, y, adjustedImage); + + //_progress++; + }); + } + } +} \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Services/RandomSearchAndReplaceService.cs b/Yugen.Mosaic.Uwp/Services/RandomSearchAndReplaceService.cs new file mode 100644 index 0000000..d44e716 --- /dev/null +++ b/Yugen.Mosaic.Uwp/Services/RandomSearchAndReplaceService.cs @@ -0,0 +1,58 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Yugen.Mosaic.Uwp.Helpers; +using Yugen.Mosaic.Uwp.Models; + +namespace Yugen.Mosaic.Uwp.Services +{ + public class RandomSearchAndReplaceService : SearchAndReplaceService + { + public RandomSearchAndReplaceService(Image outputImage, Size tileSize, int tX, int tY, List tileImageList, Rgba32[,] avgsMaster) : base(outputImage, tileSize, tX, tY, tileImageList, avgsMaster) + { + } + + // Don't adjust hue - keep searching for a tile close enough + public override void SearchAndReplace() + { + var r = new Random(); + + Parallel.For(0, _tX * _tY, xy => + { + var y = xy / _tX; + var x = xy % _tX; + + // Reset searching variables + var threshold = 0; + var searchCounter = 0; + Tile tileFound = null; + + // Search for a tile with a similar color + while (tileFound == null) + { + var index = r.Next(_tileImageList.Count); + var difference = ColorHelper.GetDifference(_avgsMaster[x, y], _tileImageList[index].AverageColor); + if (difference < threshold) + { + tileFound = _tileImageList[index]; + } + else + { + searchCounter++; + if (searchCounter >= _tileImageList.Count) + { + threshold += 5; + } + } + } + + // Apply found tile to section + ApplyTileFound(x, y, tileFound.ResizedImage); + + //_progress++; + }); + } + } +} \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Services/SearchAndReplaceService.cs b/Yugen.Mosaic.Uwp/Services/SearchAndReplaceService.cs new file mode 100644 index 0000000..626f942 --- /dev/null +++ b/Yugen.Mosaic.Uwp/Services/SearchAndReplaceService.cs @@ -0,0 +1,65 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Yugen.Mosaic.Uwp.Interfaces; +using Yugen.Mosaic.Uwp.Models; + +namespace Yugen.Mosaic.Uwp.Services +{ + public abstract class SearchAndReplaceService : ISearchAndReplaceService + { + internal readonly Rgba32[,] _avgsMaster; + internal readonly Image _outputImage; + internal readonly List _tileImageList; + internal readonly Size _tileSize; + internal readonly int _tX; + internal readonly int _tY; + + public SearchAndReplaceService(Image outputImage, Size tileSize, int tX, int tY, List tileImageList, Rgba32[,] avgsMaster) + { + _outputImage = outputImage; + _tileSize = tileSize; + _tX = tX; + _tY = tY; + _tileImageList = tileImageList; + _avgsMaster = avgsMaster; + } + + public virtual void SearchAndReplace() + { + } + + // TODO: c.DrawImage crash (System.NullReferenceException) + // with the current SixLabors.ImageSharp.Drawing preview version + //internal void ApplyTileFound(int x, int y, Image source) + //{ + // _outputImage.Mutate(c => + // { + // var point = new Point(x * source.Width, y * source.Height); + // try + // { + // c.DrawImage(source, point, 1); + // } + // catch { } + // }); + //} + + internal void ApplyTileFound(int x, int y, Image source) + { + Parallel.For(0, source.Height, h => + { + Span rowSpan = source.GetPixelRowSpan(h); + + for (var w = 0; w < source.Width; w++) + { + var pixel = new Rgba32(); + rowSpan[w].ToRgba32(ref pixel); + + _outputImage[x * source.Width + w, y * source.Height + h] = pixel; + } + }); + } + } +} \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Services/ThemeSelectorService.cs b/Yugen.Mosaic.Uwp/Services/ThemeSelectorService.cs index e7caee3..33a4b19 100644 --- a/Yugen.Mosaic.Uwp/Services/ThemeSelectorService.cs +++ b/Yugen.Mosaic.Uwp/Services/ThemeSelectorService.cs @@ -1,29 +1,41 @@ using System; using System.Threading.Tasks; using Windows.ApplicationModel.Core; -using Windows.Storage; using Windows.UI; using Windows.UI.Core; using Windows.UI.ViewManagement; using Windows.UI.Xaml; -using Yugen.Mosaic.Uwp.Extensions; +using Yugen.Mosaic.Uwp.Helpers; namespace Yugen.Mosaic.Uwp.Services { public static class ThemeSelectorService { - private const string SettingsKey = "AppBackgroundRequestedTheme"; private const string DARK_THEME_BCKG = "#FF000000"; private const string LIGHT_THEME_BCKG = "#FFFFFFFF"; - + private const string SettingsKey = "AppBackgroundRequestedTheme"; public static ElementTheme Theme { get; set; } = ElementTheme.Default; public static async Task InitializeAsync() { - var theme = await LoadThemeFromSettingsAsync(); + ElementTheme theme = await LoadThemeFromSettingsAsync(); await SetThemeAsync(theme); } + public static async Task SetRequestedThemeAsync() + { + foreach (CoreApplicationView view in CoreApplication.Views) + { + await view.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + { + if (Window.Current.Content is FrameworkElement frameworkElement) + { + frameworkElement.RequestedTheme = Theme; + } + }); + } + } + public static async Task SetThemeAsync(ElementTheme theme) { Theme = theme; @@ -31,7 +43,7 @@ public static async Task SetThemeAsync(ElementTheme theme) await SetRequestedThemeAsync(); await SaveThemeInSettingsAsync(Theme); - var titleBar = ApplicationView.GetForCurrentView().TitleBar; + ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar; //Active titleBar.BackgroundColor = Colors.Transparent; @@ -45,25 +57,27 @@ public static async Task SetThemeAsync(ElementTheme theme) titleBar.ButtonInactiveBackgroundColor = Colors.Transparent; titleBar.ButtonInactiveForegroundColor = GetThemeResource(theme, "TitleBarButtonForeground"); } + private static T GetThemeResource(ElementTheme theme, string resKey) + { + var isLightTheme = (theme == ElementTheme.Default) + ? (IsSystemThemeLight()) + : (theme == ElementTheme.Light); + var themeKey = isLightTheme ? "Light" : "Dark"; + var themeDictionary = (ResourceDictionary)Application.Current.Resources.ThemeDictionaries[themeKey]; + return (T)themeDictionary[resKey]; + } - public static async Task SetRequestedThemeAsync() + private static bool IsSystemThemeLight() { - foreach (var view in CoreApplication.Views) - { - await view.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => - { - if (Window.Current.Content is FrameworkElement frameworkElement) - { - frameworkElement.RequestedTheme = Theme; - } - }); - } + var DefaultTheme = new UISettings(); + var uiTheme = DefaultTheme.GetColorValue(UIColorType.Background).ToString(); + return uiTheme == LIGHT_THEME_BCKG; } private static async Task LoadThemeFromSettingsAsync() { ElementTheme cacheTheme = ElementTheme.Default; - string themeName = await SettingsHelper.ReadAsync(SettingsKey); + var themeName = await SettingsHelper.ReadAsync(SettingsKey); if (!string.IsNullOrEmpty(themeName)) { @@ -73,26 +87,6 @@ private static async Task LoadThemeFromSettingsAsync() return cacheTheme; } - private static async Task SaveThemeInSettingsAsync(ElementTheme theme) - { - await SettingsHelper.WriteAsync(SettingsKey, theme.ToString()); - } - - private static T GetThemeResource(ElementTheme theme, string resKey) - { - bool isLightTheme = (theme == ElementTheme.Default) - ? (IsSystemThemeLight()) - : (theme == ElementTheme.Light); - string themeKey = isLightTheme ? "Light" : "Dark"; - var themeDictionary = (ResourceDictionary)Application.Current.Resources.ThemeDictionaries[themeKey]; - return (T)themeDictionary[resKey]; - } - - private static bool IsSystemThemeLight() - { - var DefaultTheme = new UISettings(); - var uiTheme = DefaultTheme.GetColorValue(UIColorType.Background).ToString(); - return uiTheme == LIGHT_THEME_BCKG; - } + private static async Task SaveThemeInSettingsAsync(ElementTheme theme) => await SettingsHelper.WriteAsync(SettingsKey, theme.ToString()); } } diff --git a/Yugen.Mosaic.Uwp/SettingsDialog.xaml.cs b/Yugen.Mosaic.Uwp/SettingsDialog.xaml.cs deleted file mode 100644 index 53771c2..0000000 --- a/Yugen.Mosaic.Uwp/SettingsDialog.xaml.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Input; - -// The Content Dialog item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238 - -namespace Yugen.Mosaic.Uwp -{ - public sealed partial class SettingsDialog : ContentDialog - { - public SettingsDialog() - { - this.InitializeComponent(); - } - - private void Button_Tapped(object sender, TappedRoutedEventArgs e) - { - Hide(); - } - } -} diff --git a/Yugen.Mosaic.Uwp/Strings/en-US/Resources.resw b/Yugen.Mosaic.Uwp/Strings/en-US/Resources.resw new file mode 100644 index 0000000..1825bf9 --- /dev/null +++ b/Yugen.Mosaic.Uwp/Strings/en-US/Resources.resw @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Then add images. Yugen Mosaic will use them as tiles to build your mosaic. The more tiles you choose, the better result you will get! + + + Create a list of tiles + + + Now here is where Yugen Mosaic works its magic. Once you set all parameters, click ‘Create’ to start. The processing time is dependent on your pc hardware and on the parameters you set! + + + Create the mosaic + + + First, choose the master image you want to use as the matrix of the mosaic. Yugen Mosaic will create a mosaic as close as possible to this image. + + + Select the master image + + + Now choose the type of your mosaic: +- Choose "Classic" to automatically choose the best fitting image to apply in every tile. +- Choose "Random" to randomly choose images that have best possible fit. It is faster than Classic but less precise. +- Choose "Adjust Hue" to insert all the tiles you choose, adapting the color to the master image. +- Choose "Plain Color" to create a mosaic with plain colors based on the master image. You do not need to provide any images. + + + Set the mosaic type + + + Next choose the resolution of the final mosaic picture. This will determine your art’s height and width. + + + Set the mosaic parameters + + + This is the last step, if you like the result, you can click and save the mosaic picture on your device. + + + Save the mosaic + + + Next you can optionally set the mosaic’s tiles’ parameters. Afer you have created your list of tiles by adding your images, you can set the size of every single tile. The smaller the size the more detail your mosaic will have! + + + Set the tile properties + + \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/ViewModels/MainViewModel.cs b/Yugen.Mosaic.Uwp/ViewModels/MainViewModel.cs index 8517bc5..dc13706 100644 --- a/Yugen.Mosaic.Uwp/ViewModels/MainViewModel.cs +++ b/Yugen.Mosaic.Uwp/ViewModels/MainViewModel.cs @@ -2,24 +2,24 @@ using Microsoft.UI.Xaml.Controls; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.Primitives; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Threading.Tasks; +using System.Windows.Input; using Windows.Storage; using Windows.Storage.Streams; using Windows.UI.Popups; using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media.Imaging; +using Yugen.Mosaic.Uwp.Controls; using Yugen.Mosaic.Uwp.Enums; using Yugen.Mosaic.Uwp.Extensions; using Yugen.Mosaic.Uwp.Helpers; using Yugen.Mosaic.Uwp.Models; using Yugen.Mosaic.Uwp.Services; +using Yugen.Toolkit.Standard.Commands; using Yugen.Toolkit.Uwp.Helpers; using Yugen.Toolkit.Uwp.ViewModels; @@ -27,235 +27,170 @@ namespace Yugen.Mosaic.Uwp { public class MainViewModel : BaseViewModel { - private BitmapImage _masterBmpSource = new BitmapImage(); - - public BitmapImage MasterBpmSource - { - get { return _masterBmpSource; } - set { Set(ref _masterBmpSource, value); } - } - + private readonly MosaicService _mosaicService = new MosaicService(); private bool _isAddMasterUIVisible = true; - - public bool IsAddMasterUIVisible - { - get { return _isAddMasterUIVisible; } - set { Set(ref _isAddMasterUIVisible, value); } - } - - - private int _tileWidth = 25; - - public int TileWidth - { - get { return _tileWidth; } - set { Set(ref _tileWidth, value); } - } - - private int _tileHeight = 25; - - public int TileHeight - { - get { return _tileHeight; } - set { Set(ref _tileHeight, value); } - } - - private Size TileSize => new Size(_tileWidth, _tileHeight); - - private ObservableCollection _tileBmpCollection = new ObservableCollection(); - - public ObservableCollection TileBmpCollection - { - get { return _tileBmpCollection; } - set { Set(ref _tileBmpCollection, value); } - } - - + private bool _isAlignmentGridVisibile = true; + private bool _isLoading; + private bool _isTeachingTipOpen; + private BitmapImage _masterBmpSource = new BitmapImage(); private BitmapImage _outputBmpSource = new BitmapImage(); - - public BitmapImage OutputBmpSource - { - get { return _outputBmpSource; } - set { Set(ref _outputBmpSource, value); } - } - + private int _outputHeight = 1000; + private Image _outputImage; private int _outputWidth = 1000; + private MosaicType _selectedMosaicType; + private string _teachingTipSubTitle; + private FrameworkElement _teachingTipTarget; + private string _teachingTipTitle; + private ObservableCollection _tileBmpCollection = new ObservableCollection(); + private int _tileHeight = 25; + private int _tileWidth = 25; + private ICommand _pointerEnteredCommand; + private ICommand _pointerExitedCommand; + private ICommand _addMasterImmageCommand; + private ICommand _addTilesCommand; + private ICommand _clickTileCommand; + private ICommand _generateCommand; + private ICommand _saveCommand; + private ICommand _resetCommand; + private ICommand _helpCommand; + private ICommand _settingsCommand; - public int OutputWidth + public MainViewModel() { - get { return _outputWidth; } - set { Set(ref _outputWidth, value); } + SelectedMosaicType = MosaicTypeList[0]; } - private int _outputHeight = 1000; - - public int OutputHeight + public bool IsAddMasterUIVisible { - get { return _outputHeight; } - set { Set(ref _outputHeight, value); } + get => _isAddMasterUIVisible; + set => Set(ref _isAddMasterUIVisible, value); } - private Size OutputSize => new Size(_outputWidth, _outputHeight); - - - private bool _isAlignmentGridVisibile = true; - public bool IsAlignmentGridVisibile { - get { return _isAlignmentGridVisibile; } - set { Set(ref _isAlignmentGridVisibile, value); } + get => _isAlignmentGridVisibile; + set => Set(ref _isAlignmentGridVisibile, value); } - - public List MosaicTypeList { get; set; } = new List - { - new MosaicType {Id = 0, Title = "Classic"}, - new MosaicType {Id = 1, Title = "Random"}, - new MosaicType {Id = 2, Title = "AdjustHue"}, - new MosaicType {Id = 3, Title = "Plain Color"} - }; - - private MosaicType _selectedMosaicType; - - public MosaicType SelectedMosaicType - { - get { return _selectedMosaicType; } - set { Set(ref _selectedMosaicType, value); } - } - - - private bool _isLoading; - public bool IsLoading { - get { return _isLoading; } - set { Set(ref _isLoading, value); } + get => _isLoading; + set => Set(ref _isLoading, value); } - private readonly MosaicService _mosaicService = new MosaicService(); - - private Image _outputImage; - - - private bool _isTeachingTipOpen; - public bool IsTeachingTipOpen { - get { return _isTeachingTipOpen; } - set { Set(ref _isTeachingTipOpen, value); } + get => _isTeachingTipOpen; + set => Set(ref _isTeachingTipOpen, value); } - private string _teachingTipTitle; - - public string TeachingTipTitle + public BitmapImage MasterBpmSource { - get { return _teachingTipTitle; } - set { Set(ref _teachingTipTitle, value); } + get => _masterBmpSource; + set => Set(ref _masterBmpSource, value); } - private string _teachingTipSubTitle; - - public string TeachingTipSubTitle + public List MosaicTypeList { get; set; } = new List { - get { return _teachingTipSubTitle; } - set { Set(ref _teachingTipSubTitle, value); } - } - - private FrameworkElement _teachingTipTarget; + new MosaicType(MosaicTypeEnum.Classic), + new MosaicType(MosaicTypeEnum.Random), + new MosaicType(MosaicTypeEnum.AdjustHue), + new MosaicType(MosaicTypeEnum.PlainColor) + }; - public FrameworkElement TeachingTipTarget + public BitmapImage OutputBmpSource { - get { return _teachingTipTarget; } - set { Set(ref _teachingTipTarget, value); } + get => _outputBmpSource; + set => Set(ref _outputBmpSource, value); } - - public MainViewModel() + public int OutputHeight { - SelectedMosaicType = MosaicTypeList[0]; + get => _outputHeight; + set => Set(ref _outputHeight, value); } - - public void Grid_PointerEntered(object sender, PointerRoutedEventArgs e) + public int OutputWidth { - IsAddMasterUIVisible = true; + get => _outputWidth; + set => Set(ref _outputWidth, value); } - public void Grid_PointerExited(object sender, PointerRoutedEventArgs e) + public MosaicType SelectedMosaicType { - UpdateIsAddMasterUIVisible(); + get => _selectedMosaicType; + set => Set(ref _selectedMosaicType, value); } - private void UpdateIsAddMasterUIVisible() + public string TeachingTipSubTitle { - IsAddMasterUIVisible = (MasterBpmSource.PixelWidth > 0 && MasterBpmSource.PixelHeight > 0) ? false : true; + get => _teachingTipSubTitle; + set => Set(ref _teachingTipSubTitle, value); } - - public void AddMasterGrid_Tapped(object sender, TappedRoutedEventArgs e) + public FrameworkElement TeachingTipTarget { - AddMaster().FireAndForgetSafeAsync(); + get => _teachingTipTarget; + set => Set(ref _teachingTipTarget, value); } - public void AddTilesButton_Click(object sender, RoutedEventArgs e) + public string TeachingTipTitle { - AddTiles((Button) sender).FireAndForgetSafeAsync(); + get => _teachingTipTitle; + set => Set(ref _teachingTipTitle, value); } - public void AdaptiveGridView_ItemClick(object sender, ItemClickEventArgs e) + public ObservableCollection TileBmpCollection { - if (e.ClickedItem is TileBmp item) - RemoveTile(item).FireAndForgetSafeAsync(); + get => _tileBmpCollection; + set => Set(ref _tileBmpCollection, value); } - public void GenerateButton_Click(object sender, RoutedEventArgs e) + public int TileHeight { - Generate((Button) sender).FireAndForgetSafeAsync(); + get => _tileHeight; + set => Set(ref _tileHeight, value); } - public void SaveButton_Click(object sender, RoutedEventArgs e) + public int TileWidth { - Save((Button) sender).FireAndForgetSafeAsync(); + get => _tileWidth; + set => Set(ref _tileWidth, value); } - public void ResetButton_Click(object sender, RoutedEventArgs e) - { - _mosaicService.Reset(); + private Size OutputSize => new Size(_outputWidth, _outputHeight); + private Size TileSize => new Size(_tileWidth, _tileHeight); - MasterBpmSource = new BitmapImage(); - TileBmpCollection = new ObservableCollection(); + public ICommand PointerEnteredCommand => _pointerEnteredCommand ?? (_pointerEnteredCommand = new RelayCommand(PointerEnteredCommandBehavior)); + public ICommand PointerExitedCommand => _pointerExitedCommand ?? (_pointerExitedCommand = new RelayCommand(PointerExitedCommandBehavior)); + public ICommand AddMasterImmageCommand => _addMasterImmageCommand ?? (_addMasterImmageCommand = new AsyncRelayCommand(AddMasterImmageCommandBehavior)); + public ICommand AddTilesCommand => _addTilesCommand ?? (_addTilesCommand = new AsyncRelayCommand(AddTilesCommandBehavior)); + public ICommand ClickTileCommand => _clickTileCommand ?? (_clickTileCommand = new AsyncRelayCommand(ClickTileCommandBehavior)); + public ICommand GenerateCommand => _generateCommand ?? (_generateCommand = new AsyncRelayCommand(GenerateCommandBehavior)); + public ICommand SaveCommand => _saveCommand ?? (_saveCommand = new AsyncRelayCommand(SaveCommandBehavior)); + public ICommand ResetCommand => _resetCommand ?? (_resetCommand = new RelayCommand(ResetCommandBehavior)); + public ICommand HelpCommand => _helpCommand ?? (_helpCommand = new RelayCommand(HelpCommandBehavior)); + public ICommand SettingsCommand => _settingsCommand ?? (_settingsCommand = new AsyncRelayCommand(SettingsCommandBehavior)); - GC.Collect(); - UpdateIsAddMasterUIVisible(); - } + public void PointerEnteredCommandBehavior() => IsAddMasterUIVisible = true; - public void HelpButton_Click(object sender, RoutedEventArgs e) - { - OnboardingHelper.IsDisabled = false; - ShowTeachingTip(); - } + public void PointerExitedCommandBehavior() => UpdateIsAddMasterUIVisible(); - public void SettingsButton_Click(object sender, RoutedEventArgs e) - { - Settings().FireAndForgetSafeAsync(); - } - - - private async Task AddMaster() + private async Task AddMasterImmageCommandBehavior() { IsLoading = true; - var masterFile = await FilePickerHelper.OpenFile( - new List {".jpg", ".png"}, + StorageFile masterFile = await FilePickerHelper.OpenFile( + new List { ".jpg", ".png" }, Windows.Storage.Pickers.PickerLocationId.PicturesLibrary); if (masterFile != null) { - using (var inputStream = await masterFile.OpenReadAsync()) - using (var stream = inputStream.AsStreamForRead()) + using (IRandomAccessStreamWithContentType inputStream = await masterFile.OpenReadAsync()) + using (Stream stream = inputStream.AsStreamForRead()) { - var image = _mosaicService.AddMasterImage(stream); + Image image = _mosaicService.AddMasterImage(stream); using (Image copy = _mosaicService.GetResizedImage(image, 400)) { @@ -263,7 +198,7 @@ private async Task AddMaster() await MasterBpmSource.SetSourceAsync(outputStream); } - var newSize = RatioHelper.Convert(image.Width, image.Height, OutputSize.Width, OutputSize.Height); + Tuple newSize = MathHelper.RatioConvert(image.Width, image.Height, OutputSize.Width, OutputSize.Height); OutputWidth = newSize.Item1; OutputHeight = newSize.Item2; } @@ -274,24 +209,26 @@ private async Task AddMaster() IsLoading = false; } - private async Task AddTiles(Button button) + private async Task AddTilesCommandBehavior() { - var files = await FilePickerHelper.OpenFiles( - new List {".jpg", ".png"}, + IReadOnlyList files = await FilePickerHelper.OpenFiles( + new List { ".jpg", ".png" }, Windows.Storage.Pickers.PickerLocationId.PicturesLibrary); if (files == null) + { return; + } - button.IsEnabled = false; + //button.IsEnabled = false; IsLoading = true; await Task.Run(() => Parallel.ForEach(files, async file => { - using (var inputStream = await file.OpenReadAsync()) - using (var stream = inputStream.AsStreamForRead()) + using (IRandomAccessStreamWithContentType inputStream = await file.OpenReadAsync()) + using (Stream stream = inputStream.AsStreamForRead()) { - var image = _mosaicService.AddTileImage(file.DisplayName, stream); + Image image = _mosaicService.AddTileImage(file.DisplayName, stream); using (Image copy = _mosaicService.GetResizedImage(image, 200)) { @@ -310,10 +247,10 @@ await DispatcherHelper.ExecuteOnUIThreadAsync(async () => ); IsLoading = false; - button.IsEnabled = true; + //button.IsEnabled = true; } - private async Task RemoveTile(TileBmp item) + private async Task ClickTileCommandBehavior(TileBmp item) { await MessageDialogHelper.Confirm("Do you want to Remove this picture?", "", @@ -326,13 +263,13 @@ await MessageDialogHelper.Confirm("Do you want to Remove this picture?", new UICommand("No")); } - private async Task Generate(Button button) + private async Task GenerateCommandBehavior() { - button.IsEnabled = false; + //button.IsEnabled = false; IsLoading = true; await Task.Run(() => - _outputImage = _mosaicService.GenerateMosaic(OutputSize, TileSize, SelectedMosaicType.Id)); + _outputImage = _mosaicService.GenerateMosaic(OutputSize, TileSize, SelectedMosaicType.MosaicTypeEnum)); if (_outputImage != null) { @@ -341,33 +278,36 @@ await Task.Run(() => } IsLoading = false; - button.IsEnabled = true; + //button.IsEnabled = true; } - private async Task Save(Button button) + private async Task SaveCommandBehavior() { - button.IsEnabled = false; + //button.IsEnabled = false; IsLoading = true; var fileTypes = new Dictionary>() { - {FileFormat.Png.ToString(), new List() {FileFormat.Png.FileFormatToString()}}, - {FileFormat.Jpg.ToString(), new List() {FileFormat.Jpg.FileFormatToString()}} + {FileFormat.Png.ToString(), new List() {FileFormat.Png.GetStringRepresentation()}}, + {FileFormat.Jpg.ToString(), new List() {FileFormat.Jpg.GetStringRepresentation()}} }; - var file = await FilePickerHelper.SaveFile("Mosaic", fileTypes, + StorageFile file = await FilePickerHelper.SaveFile("Mosaic", fileTypes, Windows.Storage.Pickers.PickerLocationId.PicturesLibrary); if (file == null || _outputImage == null) + { return; + } - using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite)) + using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite)) { switch (file.FileType) { case ".png": _outputImage.SaveAsPng(stream.AsStreamForWrite()); break; + default: _outputImage.SaveAsJpeg(stream.AsStreamForWrite()); break; @@ -375,26 +315,56 @@ private async Task Save(Button button) } IsLoading = false; - button.IsEnabled = true; + //button.IsEnabled = true; + } + + private void ResetCommandBehavior() + { + _mosaicService.Reset(); + + MasterBpmSource = new BitmapImage(); + TileBmpCollection = new ObservableCollection(); + + GC.Collect(); + + UpdateIsAddMasterUIVisible(); + } + + private void HelpCommandBehavior() + { + OnboardingHelper.IsDisabled = false; + ShowTeachingTip(); } - private async Task Settings() + private async Task SettingsCommandBehavior() { - var d = new SettingsDialog(); - await d.ShowAsync(); + var settingsDialog = new SettingsDialog(); + await settingsDialog.ShowAsync(); } public void ShowTeachingTip() { - var onboardingElement = OnboardingHelper.ShowTeachingTip(); - if (onboardingElement == null) return; + OnboardingElement onboardingElement = OnboardingHelper.ShowTeachingTip(); + if (onboardingElement == null) + { + return; + } + TeachingTipTitle = onboardingElement.Title; TeachingTipSubTitle = onboardingElement.Subtitle; TeachingTipTarget = onboardingElement.Target; IsTeachingTipOpen = true; } + public void TeachingTip_ActionButtonClick(TeachingTip sender, object args) + { + OnboardingHelper.IsDisabled = true; + IsTeachingTipOpen = false; + } + + public void TeachingTip_Closed(TeachingTip sender, TeachingTipClosedEventArgs args) => ShowTeachingTip(); + public void TeachingTip_Closing(TeachingTip sender, TeachingTipClosingEventArgs args) { TeachingTipTitle = ""; @@ -403,15 +373,7 @@ public void TeachingTip_Closing(TeachingTip sender, TeachingTipClosingEventArgs IsTeachingTipOpen = false; } - public void TeachingTip_Closed(TeachingTip sender, TeachingTipClosedEventArgs args) - { - ShowTeachingTip(); - } - public void TeachingTip_ActionButtonClick(TeachingTip sender, object args) - { - OnboardingHelper.IsDisabled = true; - IsTeachingTipOpen = false; - } + private void UpdateIsAddMasterUIVisible() => IsAddMasterUIVisible = (MasterBpmSource.PixelWidth > 0 && MasterBpmSource.PixelHeight > 0) ? false : true; } } \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/ViewModels/SettingsViewModel.cs b/Yugen.Mosaic.Uwp/ViewModels/SettingsViewModel.cs index 7599402..331e44e 100644 --- a/Yugen.Mosaic.Uwp/ViewModels/SettingsViewModel.cs +++ b/Yugen.Mosaic.Uwp/ViewModels/SettingsViewModel.cs @@ -1,6 +1,8 @@ using System; +using System.Threading.Tasks; using System.Windows.Input; using Windows.ApplicationModel; +using Windows.System; using Windows.UI.Xaml; using Yugen.Mosaic.Uwp.Services; using Yugen.Toolkit.Standard.Commands; @@ -9,56 +11,36 @@ namespace Yugen.Mosaic.Uwp.ViewModels { - public class SettingsViewModel: BaseViewModel + public class SettingsViewModel : BaseViewModel { private const string STORE_REVIEWFORMAT = "ms-windows-store:REVIEW?PFN={0}"; private ElementTheme _elementTheme = ThemeSelectorService.Theme; - private ICommand _switchThemeCommand; private ICommand _launchRateAndReviewCommand; + private ICommand _switchThemeCommand; + //This may change if the app gets localized + public string AppName => "Yugen Mosaic"; + + public string AppVersion => SystemHelper.AppVersion; + + public string[] Collaborator => new[] { "Leisvan", "Yoshi" }; public ElementTheme ElementTheme { - get { return _elementTheme; } - set { Set(ref _elementTheme, value); } - } - public ICommand SwitchThemeCommand - { - get - { - if (_switchThemeCommand == null) - { - _switchThemeCommand = new RelayCommand( - async (param) => - { - ElementTheme = param; - await ThemeSelectorService.SetThemeAsync(param); - }); - } - return _switchThemeCommand; - } - } - public ICommand LaunchRateAndReviewCommand - { - get - { - if (_launchRateAndReviewCommand == null) - { - _launchRateAndReviewCommand = new RelayCommand(async () => - { - await Windows.System.Launcher.LaunchUriAsync(new Uri(string.Format(STORE_REVIEWFORMAT, Package.Current.Id.FamilyName))); - }); - } - return _launchRateAndReviewCommand; - } + get => _elementTheme; + set => Set(ref _elementTheme, value); } - //This may change if the app gets localized - public string AppName => "Yugen Mosaic"; + public string Publisher => SystemHelper.Publisher; - public string AppVersion => SystemHelper.AppVersion; + public ICommand LaunchRateAndReviewCommand => _launchRateAndReviewCommand ?? (_launchRateAndReviewCommand = new AsyncRelayCommand(LaunchRateAndReviewCommandBehavior)); + public ICommand SwitchThemeCommand => _switchThemeCommand ?? (_switchThemeCommand = new AsyncRelayCommand(SwitchThemeCommandBehavior)); - public string Publisher => SystemHelper.Publisher; + private async Task LaunchRateAndReviewCommandBehavior() => await Launcher.LaunchUriAsync(new Uri(string.Format(STORE_REVIEWFORMAT, Package.Current.Id.FamilyName))); - public string[] Collaborator => new[] { "Leisvan", "Yoshi" }; + private async Task SwitchThemeCommandBehavior(ElementTheme param) + { + ElementTheme = param; + await ThemeSelectorService.SetThemeAsync(param); + } } -} +} \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/MainPage.xaml b/Yugen.Mosaic.Uwp/Views/MainPage.xaml similarity index 90% rename from Yugen.Mosaic.Uwp/MainPage.xaml rename to Yugen.Mosaic.Uwp/Views/MainPage.xaml index ae449a9..e21163d 100644 --- a/Yugen.Mosaic.Uwp/MainPage.xaml +++ b/Yugen.Mosaic.Uwp/Views/MainPage.xaml @@ -1,4 +1,4 @@ - @@ -67,10 +69,18 @@ + extensions:Mouse.Cursor="Hand"> + + + + + + + + + + + -