diff --git a/Yugen.Mosaic.Uwp.sln b/Yugen.Mosaic.Uwp.sln index 72d5923..7bda10a 100644 --- a/Yugen.Mosaic.Uwp.sln +++ b/Yugen.Mosaic.Uwp.sln @@ -8,6 +8,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Azure", "Azure", "{E44DE1EE-E0B3-4250-8CBE-F9B13C6DF19A}" ProjectSection(SolutionItems) = preProject build-pipelines.yml = build-pipelines.yml + GitVersion.yml = GitVersion.yml EndProjectSection EndProject Global diff --git a/Yugen.Mosaic.Uwp/Enums/FileFormat.cs b/Yugen.Mosaic.Uwp/Enums/FileFormat.cs index a5358af..b9b8b2a 100644 --- a/Yugen.Mosaic.Uwp/Enums/FileFormat.cs +++ b/Yugen.Mosaic.Uwp/Enums/FileFormat.cs @@ -7,6 +7,9 @@ public enum FileFormat [Description(".jpg")] Jpg, + [Description(".jpeg")] + Jpeg, + [Description(".png")] Png, diff --git a/Yugen.Mosaic.Uwp/Interfaces/IMosaicService.cs b/Yugen.Mosaic.Uwp/Interfaces/IMosaicService.cs index f1780e2..ab6c30e 100644 --- a/Yugen.Mosaic.Uwp/Interfaces/IMosaicService.cs +++ b/Yugen.Mosaic.Uwp/Interfaces/IMosaicService.cs @@ -1,5 +1,6 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; +using System; using System.Threading.Tasks; using Windows.Storage; using Windows.Storage.Streams; diff --git a/Yugen.Mosaic.Uwp/Package.appxmanifest b/Yugen.Mosaic.Uwp/Package.appxmanifest index 773b22f..93986d6 100644 --- a/Yugen.Mosaic.Uwp/Package.appxmanifest +++ b/Yugen.Mosaic.Uwp/Package.appxmanifest @@ -36,7 +36,7 @@ Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="Yugen.Mosaic.Uwp" - BackgroundColor="#C93535"> + BackgroundColor="transparent"> @@ -44,7 +44,7 @@ - + diff --git a/Yugen.Mosaic.Uwp/Services/AdjustHueSearchAndReplaceService.cs b/Yugen.Mosaic.Uwp/Services/AdjustHueSearchAndReplaceService.cs index 239a96e..162a6e6 100644 --- a/Yugen.Mosaic.Uwp/Services/AdjustHueSearchAndReplaceService.cs +++ b/Yugen.Mosaic.Uwp/Services/AdjustHueSearchAndReplaceService.cs @@ -5,13 +5,17 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Yugen.Mosaic.Uwp.Helpers; using Yugen.Mosaic.Uwp.Models; +using Yugen.Mosaic.Uwp.ViewModels; 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) + public AdjustHueSearchAndReplaceService(Image outputImage, Size tileSize, + int tX, int tY, List tileImageList, Rgba32[,] avgsMaster) + : base(outputImage, tileSize, tX, tY, tileImageList, avgsMaster) { } @@ -22,6 +26,10 @@ public override void SearchAndReplace() var seq = Enumerable.Range(0, _tX * _tY).Select(x => x % _tileImageList.Count); var tileShuffledList = seq.OrderBy(a => r.Next()); + ProgressService.Instance.Reset(); + + int max = _tX * _tY; + Parallel.For(0, _tX * _tY, xy => { var y = xy / _tX; @@ -41,7 +49,7 @@ public override void SearchAndReplace() // Apply found tile to section ApplyTileFound(x, y, adjustedImage); - //_progress++; + ProgressService.Instance.IncrementProgress(max, 66, 100); }); } diff --git a/Yugen.Mosaic.Uwp/Services/ClassicSearchAndReplaceService.cs b/Yugen.Mosaic.Uwp/Services/ClassicSearchAndReplaceService.cs index f86928a..e369cec 100644 --- a/Yugen.Mosaic.Uwp/Services/ClassicSearchAndReplaceService.cs +++ b/Yugen.Mosaic.Uwp/Services/ClassicSearchAndReplaceService.cs @@ -6,17 +6,24 @@ using System.Threading.Tasks; using Yugen.Mosaic.Uwp.Helpers; using Yugen.Mosaic.Uwp.Models; +using Yugen.Mosaic.Uwp.ViewModels; 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 ClassicSearchAndReplaceService(Image outputImage, Size tileSize, + int tX, int tY, List tileImageList, Rgba32[,] avgsMaster) + : base(outputImage, tileSize, tX, tY, tileImageList, avgsMaster) { } public override void SearchAndReplace() { + ProgressService.Instance.Reset(); + + int max = _tX * _tY; + Parallel.For(0, _tX * _tY, xy => { int y = xy / _tX; @@ -46,7 +53,7 @@ public override void SearchAndReplace() // Apply found tile to section ApplyTileFound(x, y, tileFound.ResizedImage); - //_progress++; + ProgressService.Instance.IncrementProgress(max, 66, 100); }); } } diff --git a/Yugen.Mosaic.Uwp/Services/MosaicService.cs b/Yugen.Mosaic.Uwp/Services/MosaicService.cs index 2be357c..6219d59 100644 --- a/Yugen.Mosaic.Uwp/Services/MosaicService.cs +++ b/Yugen.Mosaic.Uwp/Services/MosaicService.cs @@ -13,19 +13,18 @@ using Yugen.Mosaic.Uwp.Helpers; using Yugen.Mosaic.Uwp.Interfaces; using Yugen.Mosaic.Uwp.Models; +using Yugen.Mosaic.Uwp.ViewModels; namespace Yugen.Mosaic.Uwp.Services { public class MosaicService : IMosaicService { internal Rgba32[,] _avgsMaster; - internal int _progress; internal int _tX; internal int _tY; internal List _tileImageList = new List(); private Image _masterImage; - private int _progressMax; private Size _tileSize; public async Task AddMasterImage(StorageFile file) @@ -112,28 +111,30 @@ private void GetTilesAverage(Image masterImage) { _avgsMaster[x, y].FromRgba32(ColorHelper.GetAverageColor(masterImage, x, y, _tileSize)); } + + ProgressService.Instance.IncrementProgress(_tY, 0, 33); }); } private async Task LoadTilesAndResize() { - _progressMax = _tileImageList.Count; - _progress = 0; + ProgressService.Instance.Reset(); + + var processTiles = _tileImageList.AsParallel().Select(tile => ProcessTile(tile)); - var processTiles = _tileImageList.Select(ProcessTile).ToArray(); await Task.WhenAll(processTiles); - - _progress++; } - private async Task ProcessTile(Tile tile) => - await tile.Process(_tileSize); + private async Task ProcessTile(Tile tile) + { + await tile.Process(_tileSize); + + ProgressService.Instance.IncrementProgress(_tileImageList.Count, 33, 66); + } private Image SearchAndReplace(Size tileSize, MosaicTypeEnum selectedMosaicType, Size outputSize) { var outputImage = new Image(outputSize.Width, outputSize.Height); - _progressMax = _tileImageList.Count; - _progress = 0; ISearchAndReplaceService SearchAndReplaceService; diff --git a/Yugen.Mosaic.Uwp/Services/PlainColorSearchAndReplaceService.cs b/Yugen.Mosaic.Uwp/Services/PlainColorSearchAndReplaceService.cs index 324a37b..23e6609 100644 --- a/Yugen.Mosaic.Uwp/Services/PlainColorSearchAndReplaceService.cs +++ b/Yugen.Mosaic.Uwp/Services/PlainColorSearchAndReplaceService.cs @@ -1,22 +1,31 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using System; using System.Collections.Generic; using System.Numerics; using System.Threading.Tasks; +using Yugen.Mosaic.Uwp.Helpers; using Yugen.Mosaic.Uwp.Models; +using Yugen.Mosaic.Uwp.ViewModels; 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) + 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() { + ProgressService.Instance.Reset(); + + int max = _tX * _tY; + Parallel.For(0, _tX * _tY, xy => { int y = xy / _tX; @@ -37,7 +46,7 @@ public override void SearchAndReplace() // Apply found tile to section ApplyTileFound(x, y, adjustedImage); - //_progress++; + ProgressService.Instance.IncrementProgress(max, 66, 100); }); } } diff --git a/Yugen.Mosaic.Uwp/Services/ProgressService.cs b/Yugen.Mosaic.Uwp/Services/ProgressService.cs new file mode 100644 index 0000000..9566479 --- /dev/null +++ b/Yugen.Mosaic.Uwp/Services/ProgressService.cs @@ -0,0 +1,32 @@ +using System; + +namespace Yugen.Mosaic.Uwp.ViewModels +{ + public class ProgressService + { + private IProgress _progress; + private int current; + + public static ProgressService Instance { get; } = new ProgressService(); + + public void Init(Action progress) + { + // The Progress constructor captures our UI context, + // so the lambda will be run on the UI thread. + _progress = new Progress(progress); + current = 0; + } + + public void Reset() + { + current = 0; + } + + public void IncrementProgress(int total, int startPercentage = 0, int maxPercentage = 100) + { + var currentPercentage = current * (maxPercentage - startPercentage ) / total; + ++current; + _progress.Report(startPercentage + currentPercentage); + } + } +} \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Services/RandomSearchAndReplaceService.cs b/Yugen.Mosaic.Uwp/Services/RandomSearchAndReplaceService.cs index d44e716..70f0e01 100644 --- a/Yugen.Mosaic.Uwp/Services/RandomSearchAndReplaceService.cs +++ b/Yugen.Mosaic.Uwp/Services/RandomSearchAndReplaceService.cs @@ -5,12 +5,15 @@ using System.Threading.Tasks; using Yugen.Mosaic.Uwp.Helpers; using Yugen.Mosaic.Uwp.Models; +using Yugen.Mosaic.Uwp.ViewModels; 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) + public RandomSearchAndReplaceService(Image outputImage, Size tileSize, int tX, int tY, + List tileImageList, Rgba32[,] avgsMaster) + : base(outputImage, tileSize, tX, tY, tileImageList, avgsMaster) { } @@ -19,6 +22,10 @@ public override void SearchAndReplace() { var r = new Random(); + ProgressService.Instance.Reset(); + + int max = _tX * _tY; + Parallel.For(0, _tX * _tY, xy => { var y = xy / _tX; @@ -51,7 +58,7 @@ public override void SearchAndReplace() // Apply found tile to section ApplyTileFound(x, y, tileFound.ResizedImage); - //_progress++; + ProgressService.Instance.IncrementProgress(max, 66, 100); }); } } diff --git a/Yugen.Mosaic.Uwp/Strings/en-US/Resources.resw b/Yugen.Mosaic.Uwp/Strings/en-US/Resources.resw index 74fd2a2..af6b7e9 100644 --- a/Yugen.Mosaic.Uwp/Strings/en-US/Resources.resw +++ b/Yugen.Mosaic.Uwp/Strings/en-US/Resources.resw @@ -117,9 +117,21 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Do you want to Remove this picture? + + + No + + + Yes + Click here to choose a Master image + + Add Tiles from Folder + Add Tiles diff --git a/Yugen.Mosaic.Uwp/ViewModels/MainViewModel.cs b/Yugen.Mosaic.Uwp/ViewModels/MainViewModel.cs index 4de70ae..60985ba 100644 --- a/Yugen.Mosaic.Uwp/ViewModels/MainViewModel.cs +++ b/Yugen.Mosaic.Uwp/ViewModels/MainViewModel.cs @@ -31,6 +31,7 @@ public class MainViewModel : ViewModelBase private bool _isAddMasterUIVisible = true; private bool _isAlignmentGridVisibile = true; + private bool _isIndeterminateLoading; private bool _isLoading; private bool _isTeachingTipOpen; private bool _isButtonEnabled = true; @@ -46,10 +47,12 @@ public class MainViewModel : ViewModelBase private ObservableCollection _tileBmpCollection = new ObservableCollection(); private int _tileHeight = 25; private int _tileWidth = 25; + private int _progress = 0; private ICommand _pointerEnteredCommand; private ICommand _pointerExitedCommand; private ICommand _addMasterImmageCommand; private ICommand _addTilesCommand; + private ICommand _addTilesFolderCommand; private ICommand _clickTileCommand; private ICommand _generateCommand; private ICommand _saveCommand; @@ -77,6 +80,12 @@ public bool IsAlignmentGridVisibile set => Set(ref _isAlignmentGridVisibile, value); } + public bool IsIndeterminateLoading + { + get => _isIndeterminateLoading; + set => Set(ref _isIndeterminateLoading, value); + } + public bool IsLoading { get => _isLoading; @@ -169,10 +178,17 @@ public int TileWidth set => Set(ref _tileWidth, value); } + public int Progress + { + get => _progress; + set => Set(ref _progress, value); + } + 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 AddTilesFolderCommand => _addTilesFolderCommand ?? (_addTilesFolderCommand = new AsyncRelayCommand(AddTilesFolderCommandBehavior)); public ICommand ClickTileCommand => _clickTileCommand ?? (_clickTileCommand = new AsyncRelayCommand(ClickTileCommandBehavior)); public ICommand GenerateCommand => _generateCommand ?? (_generateCommand = new AsyncRelayCommand(GenerateCommandBehavior)); public ICommand SaveCommand => _saveCommand ?? (_saveCommand = new AsyncRelayCommand(SaveCommandBehavior)); @@ -230,13 +246,17 @@ public void TeachingTipClosingCommandBehavior() private async Task AddMasterImmageCommandBehavior() { StorageFile masterFile = await FilePickerHelper.OpenFile( - new List { FileFormat.Jpg.GetStringRepresentation(), FileFormat.Png.GetStringRepresentation() }, + new List { + FileFormat.Jpg.GetStringRepresentation(), + FileFormat.Jpeg.GetStringRepresentation(), + FileFormat.Png.GetStringRepresentation() + }, Windows.Storage.Pickers.PickerLocationId.PicturesLibrary); if (masterFile != null) { IsButtonEnabled = false; - IsLoading = true; + IsIndeterminateLoading = true; using (var inputStream = await masterFile.OpenReadAsync()) { @@ -257,7 +277,7 @@ await DispatcherHelper.ExecuteOnUIThreadAsync(async () => OutputWidth = newSize.Item1; OutputHeight = newSize.Item2; - IsLoading = false; + IsIndeterminateLoading = false; IsButtonEnabled = true; } @@ -267,16 +287,45 @@ await DispatcherHelper.ExecuteOnUIThreadAsync(async () => private async Task AddTilesCommandBehavior() { IReadOnlyList files = await FilePickerHelper.OpenFiles( - new List { FileFormat.Jpg.GetStringRepresentation(), FileFormat.Png.GetStringRepresentation() }, + new List { + FileFormat.Jpg.GetStringRepresentation(), + FileFormat.Jpeg.GetStringRepresentation(), + FileFormat.Png.GetStringRepresentation() + }, Windows.Storage.Pickers.PickerLocationId.PicturesLibrary); - if (files == null) + if (files != null) { - return; + await AddTiles(files); + } + } + + private async Task AddTilesFolderCommandBehavior() + { + var folderPicker = new Windows.Storage.Pickers.FolderPicker + { + SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary + }; + folderPicker.FileTypeFilter.Add(FileFormat.Jpg.GetStringRepresentation()); + folderPicker.FileTypeFilter.Add(FileFormat.Jpg.GetStringRepresentation()); + folderPicker.FileTypeFilter.Add(FileFormat.Png.GetStringRepresentation()); + + StorageFolder folder = await folderPicker.PickSingleFolderAsync(); + if (folder != null) + { + // Application now has read/write access to all contents in the picked folder + // (including other sub-folder contents) + Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList.AddOrReplace("PickedFolderToken", folder); + + var files = await folder.GetFilesAsync(); + await AddTiles(files); } + } + private async Task AddTiles(IReadOnlyList files) + { IsButtonEnabled = false; - IsLoading = true; + IsIndeterminateLoading = true; await Task.Run(() => Parallel.ForEach(files, async file => @@ -299,21 +348,18 @@ await DispatcherHelper.ExecuteOnUIThreadAsync(async () => }) ); - IsLoading = false; + IsIndeterminateLoading = false; IsButtonEnabled = true; } private async Task ClickTileCommandBehavior(TileBmp item) { - await MessageDialogHelper.Confirm("Do you want to Remove this picture?", - "", - new UICommand("Yes", - action => - { - TileBmpCollection.Remove(item); - _mosaicService.RemoveTileImage(item.Name); - }), - new UICommand("No")); + await ContentDialogHelper.Confirm(ResourceHelper.GetText("DefaultDeletePicture"), "", ResourceHelper.GetText("DefaultNo"), + new RelayCommand(() => + { + TileBmpCollection.Remove(item); + _mosaicService.RemoveTileImage(item.Name); + }), ResourceHelper.GetText("DefaultYes")); } private async Task GenerateCommandBehavior() @@ -321,8 +367,13 @@ private async Task GenerateCommandBehavior() IsButtonEnabled = false; IsLoading = true; + ProgressService.Instance.Init(percent => + { + Progress = percent; + }); + await Task.Run(async () => - _outputImage = await _mosaicService.GenerateMosaic(OutputSize, TileSize, SelectedMosaicType.MosaicTypeEnum)); + _outputImage = await _mosaicService.GenerateMosaic(OutputSize, TileSize, SelectedMosaicType.MosaicTypeEnum)); if (_outputImage != null) { @@ -396,6 +447,7 @@ private async Task SettingsCommandBehavior() await settingsDialog.ShowAsync(); } - private void UpdateIsAddMasterUIVisible() => IsAddMasterUIVisible = MasterBpmSource.PixelWidth <= 0 || MasterBpmSource.PixelHeight <= 0; + private void UpdateIsAddMasterUIVisible() => + IsAddMasterUIVisible = MasterBpmSource.PixelWidth <= 0 || MasterBpmSource.PixelHeight <= 0; } } \ No newline at end of file diff --git a/Yugen.Mosaic.Uwp/Views/MainPage.xaml b/Yugen.Mosaic.Uwp/Views/MainPage.xaml index 6858db4..77b4d77 100644 --- a/Yugen.Mosaic.Uwp/Views/MainPage.xaml +++ b/Yugen.Mosaic.Uwp/Views/MainPage.xaml @@ -131,6 +131,17 @@ + + - + Visibility="{x:Bind ViewModel.IsIndeterminateLoading, Mode=OneWay}"/> - + Visibility="{x:Bind ViewModel.IsLoading, Mode=OneWay}" /> diff --git a/Yugen.Mosaic.Uwp/Yugen.Mosaic.Uwp.csproj b/Yugen.Mosaic.Uwp/Yugen.Mosaic.Uwp.csproj index 4c53d70..a1ba929 100644 --- a/Yugen.Mosaic.Uwp/Yugen.Mosaic.Uwp.csproj +++ b/Yugen.Mosaic.Uwp/Yugen.Mosaic.Uwp.csproj @@ -132,6 +132,7 @@ + MainPage.xaml @@ -231,22 +232,22 @@ - 3.2.2 + 3.3.0 - 3.2.2 + 3.3.0 6.2.10 - 6.0.0 + 6.1.0 - 6.0.0 + 6.1.0 - 6.0.0 + 6.1.0 2.4.2 @@ -255,16 +256,16 @@ 1.0.0-rc0001 - 1.6.5 + 1.6.7 - 1.0.27 + 1.0.37 - 1.0.27 + 1.0.37 - 1.0.27 + 1.0.37 diff --git a/build-pipelines.yml b/build-pipelines.yml index cf7f302..7bf3e1f 100644 --- a/build-pipelines.yml +++ b/build-pipelines.yml @@ -17,24 +17,16 @@ variables: appxPackageDir: '$(build.artifactStagingDirectory)\AppxPackages\\' appxmanifest: '**/*.appxmanifest' versionNumber: 'Set dynamically below in a task' - -name: '$(Rev:r)' + +name: $(GITVERSION_AssemblySemFileVer) steps: -- task: PowerShell@2 +- task: UseGitVersion@5 + displayName: GitVersion + continueOnError: true inputs: - targetType: 'inline' - script: | - [xml] $manifestXml = Get-Content '$(appxmanifest)' - $version = [version]$manifestXml.Package.Identity.Version - - [string] $newVersion = "{0}.{1}.{2}.{3}" -f $version.Major, $version.Minor, $(Build.BuildNumber), 0 - Write-Host "Setting the release version number variable to '$newVersion'." - Write-Host "##vso[task.setvariable variable=versionNumber]$newVersion" - - Write-Host "Setting the name of the build to '$newVersion'." - Write-Host "##vso[build.updatebuildnumber]$newVersion" + versionSpec: '5.x' - task: NuGetToolInstaller@1 @@ -48,7 +40,8 @@ steps: restoreSolution: '$(solution)' - task: VersionAPPX@2 - displayName: 'Version MSIX' + inputs: + VersionNumber: '$(GitVersion.AssemblySemFileVer)' - task: VSBuild@1 inputs: