Compare commits
10 commits
90fb03bf0c
...
0d6435520b
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d6435520b | |||
| b194a2424c | |||
| 901dc02ca8 | |||
| fa09f7c324 | |||
| 41d9dacfac | |||
| 3c722a66df | |||
| bdf503c627 | |||
| b29cc95a1e | |||
| e80b427fcc | |||
| d62342aae1 |
45 changed files with 2603 additions and 1417 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -255,3 +255,4 @@ paket-files/
|
|||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
.vscode/settings.json
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ using System.Net;
|
|||
using Catalog.Communication.Abstractions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Catalog.Communication.DependencyInjection;
|
||||
|
|
@ -25,22 +27,30 @@ public static class CatalogCommunicationServiceCollectionExtensions
|
|||
|
||||
services.TryAddSingleton<CookieContainer>();
|
||||
|
||||
services
|
||||
.AddHttpClient<IRaceUploadCommunicationClient, RaceUploadCommunicationClient>((sp, client) =>
|
||||
// Create the HttpClient only when the communication client is requested.
|
||||
// This avoids constructing the DefaultHttpClientFactory (and its background cleanup timer)
|
||||
// if the race-upload feature is never used.
|
||||
services.AddTransient<IRaceUploadCommunicationClient>(sp =>
|
||||
{
|
||||
var options = sp.GetRequiredService<IOptions<CatalogCommunicationOptions>>().Value;
|
||||
var logger = sp.GetService<ILogger<RaceUploadCommunicationClient>>() ?? NullLogger<RaceUploadCommunicationClient>.Instance;
|
||||
var cookieContainer = sp.GetRequiredService<CookieContainer>();
|
||||
|
||||
var handler = new HttpClientHandler
|
||||
{
|
||||
var options = sp.GetRequiredService<IOptions<CatalogCommunicationOptions>>().Value;
|
||||
client.BaseAddress = options.BaseUri;
|
||||
})
|
||||
.ConfigurePrimaryHttpMessageHandler(sp =>
|
||||
UseCookies = true,
|
||||
CookieContainer = cookieContainer,
|
||||
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli,
|
||||
};
|
||||
|
||||
var httpClient = new HttpClient(handler, disposeHandler: true)
|
||||
{
|
||||
var cookieContainer = sp.GetRequiredService<CookieContainer>();
|
||||
return new HttpClientHandler
|
||||
{
|
||||
UseCookies = true,
|
||||
CookieContainer = cookieContainer,
|
||||
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli,
|
||||
};
|
||||
});
|
||||
BaseAddress = options.BaseUri,
|
||||
Timeout = options.RequestTimeout,
|
||||
};
|
||||
|
||||
return new RaceUploadCommunicationClient(httpClient, sp.GetRequiredService<IOptions<CatalogCommunicationOptions>>(), logger);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ using Microsoft.Extensions.Options;
|
|||
|
||||
namespace Catalog.Communication;
|
||||
|
||||
public sealed class RaceUploadCommunicationClient : IRaceUploadCommunicationClient
|
||||
public sealed class RaceUploadCommunicationClient : IRaceUploadCommunicationClient, IDisposable
|
||||
{
|
||||
private const string AdminMenuPath = "admin/menu/Menu4.abl";
|
||||
private const string PublicLogonPath = "Logon.abl";
|
||||
|
|
@ -27,6 +27,7 @@ public sealed class RaceUploadCommunicationClient : IRaceUploadCommunicationClie
|
|||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<RaceUploadCommunicationClient> _logger;
|
||||
private readonly IOptions<CatalogCommunicationOptions> _options;
|
||||
private bool _disposed;
|
||||
|
||||
public RaceUploadCommunicationClient(
|
||||
HttpClient httpClient,
|
||||
|
|
@ -38,6 +39,18 @@ public sealed class RaceUploadCommunicationClient : IRaceUploadCommunicationClie
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_httpClient.Dispose();
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public Task<RawEndpointResponse> LoginAdminAsync(AdminLoginRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
|
|
|||
16
Catalog.sln
16
Catalog.sln
|
|
@ -17,6 +17,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaddoShared.Benchmarks", "M
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Catalog.Communication", "Catalog.Communication\Catalog.Communication.csproj", "{EF5D3B7E-F380-4976-A0A9-085FEA157F79}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaddoShared.ImageSharpTests", "MaddoShared.ImageSharpTests\MaddoShared.ImageSharpTests.csproj", "{1528903F-3BF9-599C-2DD0-0AF7B5706675}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -87,14 +89,26 @@ Global
|
|||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79}.Release|x64.Build.0 = Release|Any CPU
|
||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79}.Release|x86.Build.0 = Release|Any CPU
|
||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Release|x64.Build.0 = Release|Any CPU
|
||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{1528903F-3BF9-599C-2DD0-0AF7B5706675}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{AEBFE9E3-277C-4A7B-8448-145D1B11998B} = {A3D50937-74F6-4DC8-8D89-B534B484C0F9}
|
||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79} = {A3D50937-74F6-4DC8-8D89-B534B484C0F9}
|
||||
{59952BE8-20B4-4BF2-9367-705F41395265} = {5F0BEF23-B1EA-4100-A772-DC455D40B1C1}
|
||||
{EF5D3B7E-F380-4976-A0A9-085FEA157F79} = {A3D50937-74F6-4DC8-8D89-B534B484C0F9}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {0E3ABC63-8601-4DAC-AFEA-33F3E8E36757}
|
||||
|
|
|
|||
44
MaddoShared.ImageSharpTests/Helpers/CreatorFactory.cs
Normal file
44
MaddoShared.ImageSharpTests/Helpers/CreatorFactory.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using System.IO;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace MaddoShared.ImageSharpTests.Helpers
|
||||
{
|
||||
public static class CreatorFactory
|
||||
{
|
||||
public static MaddoShared.PicSettings CreateDefaultPicSettings()
|
||||
{
|
||||
return new MaddoShared.PicSettings
|
||||
{
|
||||
DimStandard = 48,
|
||||
DimStandardMiniatura = 12,
|
||||
LarghezzaSmall = 150,
|
||||
AltezzaSmall = 150,
|
||||
LarghezzaBig = 800,
|
||||
AltezzaBig = 600,
|
||||
Trasparenza = 0,
|
||||
IlFont = "Arial",
|
||||
Grassetto = false,
|
||||
Posizione = "CENTRO",
|
||||
Allineamento = "CENTRO",
|
||||
Margine = 10,
|
||||
MargVert = 10,
|
||||
TestoMin = false,
|
||||
AggNumTempMin = false,
|
||||
CreaMiniature = false,
|
||||
LogoAggiungi = false,
|
||||
LogoAltezza = 100,
|
||||
LogoLarghezza = 100,
|
||||
LogoMargine = "0",
|
||||
JpegQuality = 90,
|
||||
JpegQualityMin = 75,
|
||||
};
|
||||
}
|
||||
|
||||
public static MaddoShared.ImageCreatorImageSharp CreateImageCreator(MaddoShared.PicSettings settings)
|
||||
{
|
||||
var logger = NullLogger<MaddoShared.ImageCreatorImageSharp>.Instance;
|
||||
return new MaddoShared.ImageCreatorImageSharp(settings, logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
MaddoShared.ImageSharpTests/Helpers/PixelInspector.cs
Normal file
40
MaddoShared.ImageSharpTests/Helpers/PixelInspector.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace MaddoShared.ImageSharpTests.Helpers
|
||||
{
|
||||
public static class PixelInspector
|
||||
{
|
||||
public static int CountNonBackgroundPixels(string path, int x, int y, int width, int height, Rgba32 background, int tolerance = 0)
|
||||
{
|
||||
using var img = SixLabors.ImageSharp.Image.Load<Rgba32>(path);
|
||||
var bx = Math.Max(0, x);
|
||||
var by = Math.Max(0, y);
|
||||
var bw = Math.Min(width, img.Width - bx);
|
||||
var bh = Math.Min(height, img.Height - by);
|
||||
if (bw <= 0 || bh <= 0) return 0;
|
||||
|
||||
int count = 0;
|
||||
img.ProcessPixelRows(accessor =>
|
||||
{
|
||||
for (int yy = by; yy < by + bh; yy++)
|
||||
{
|
||||
var row = accessor.GetRowSpan(yy);
|
||||
for (int xx = bx; xx < bx + bw; xx++)
|
||||
{
|
||||
var p = row[xx];
|
||||
if (!IsApproximatelyEqual(p, background, tolerance)) count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static bool IsApproximatelyEqual(Rgba32 a, Rgba32 b, int tol)
|
||||
{
|
||||
return Math.Abs(a.R - b.R) <= tol && Math.Abs(a.G - b.G) <= tol && Math.Abs(a.B - b.B) <= tol && Math.Abs(a.A - b.A) <= tol;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
MaddoShared.ImageSharpTests/Helpers/TempWorkspace.cs
Normal file
33
MaddoShared.ImageSharpTests/Helpers/TempWorkspace.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace MaddoShared.ImageSharpTests.Helpers
|
||||
{
|
||||
public sealed class TempWorkspace : IDisposable
|
||||
{
|
||||
public DirectoryInfo Root { get; }
|
||||
public DirectoryInfo SourceDir { get; }
|
||||
public DirectoryInfo DestDir { get; }
|
||||
|
||||
public TempWorkspace()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "MaddoShared.ImageSharpTests", Guid.NewGuid().ToString("N"));
|
||||
Root = Directory.CreateDirectory(root);
|
||||
SourceDir = Directory.CreateDirectory(Path.Combine(Root.FullName, "Source"));
|
||||
DestDir = Directory.CreateDirectory(Path.Combine(Root.FullName, "Dest"));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Root.Exists)
|
||||
Root.Delete(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// best-effort cleanup
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
MaddoShared.ImageSharpTests/Helpers/TestImageFactory.cs
Normal file
45
MaddoShared.ImageSharpTests/Helpers/TestImageFactory.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using System.IO;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
|
||||
|
||||
namespace MaddoShared.ImageSharpTests.Helpers
|
||||
{
|
||||
public static class TestImageFactory
|
||||
{
|
||||
public static string CreateSolidJpeg(string directory, string fileName, int width, int height, Rgba32 color)
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
var path = Path.Combine(directory, fileName);
|
||||
using var img = new Image<Rgba32>(width, height, color);
|
||||
var encoder = new JpegEncoder { Quality = 90 };
|
||||
img.Save(path, encoder);
|
||||
return path;
|
||||
}
|
||||
|
||||
public static string CreateSolidPng(string directory, string fileName, int width, int height, Rgba32 color)
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
var path = Path.Combine(directory, fileName);
|
||||
using var img = new Image<Rgba32>(width, height, color);
|
||||
img.SaveAsPng(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
public static string CreateJpegWithExifOrientation(string directory, string fileName, int width, int height, Rgba32 color, ushort orientation)
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
var path = Path.Combine(directory, fileName);
|
||||
using var img = new Image<Rgba32>(width, height, color);
|
||||
// Add EXIF orientation
|
||||
var profile = new ExifProfile();
|
||||
profile.SetValue(ExifTag.Orientation, orientation);
|
||||
img.Metadata.ExifProfile = profile;
|
||||
var encoder = new JpegEncoder { Quality = 90 };
|
||||
img.Save(path, encoder);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="4.1.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="4.1.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7" />
|
||||
<PackageReference Include="SixLabors.Fonts" Version="2.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MaddoShared\MaddoShared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
44
MaddoShared.ImageSharpTests/Tests/ImageResizingTests.cs
Normal file
44
MaddoShared.ImageSharpTests/Tests/ImageResizingTests.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using MaddoShared.ImageSharpTests.Helpers;
|
||||
using Shouldly;
|
||||
|
||||
namespace MaddoShared.ImageSharpTests.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class ImageResizingTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task BigImageResizesRespectSettings()
|
||||
{
|
||||
using var ws = new TempWorkspace();
|
||||
|
||||
// create a large input image
|
||||
var inputPath = TestImageFactory.CreateSolidJpeg(ws.SourceDir.FullName, "input.jpg", 1600, 1200, new Rgba32(200, 200, 200, 255));
|
||||
|
||||
var pic = CreatorFactory.CreateDefaultPicSettings();
|
||||
pic.LarghezzaBig = 800;
|
||||
pic.AltezzaBig = 600;
|
||||
pic.CreaMiniature = false;
|
||||
|
||||
var svc = CreatorFactory.CreateImageCreator(pic);
|
||||
|
||||
var state = new MaddoShared.ImageState
|
||||
{
|
||||
WorkFile = new FileInfo(inputPath),
|
||||
DestDir = ws.DestDir,
|
||||
SourceDir = ws.SourceDir
|
||||
};
|
||||
|
||||
await svc.CreateImageAsync(state, null);
|
||||
|
||||
var outPath = Path.Combine(ws.DestDir.FullName, state.NomeFileBig);
|
||||
using var outImg = SixLabors.ImageSharp.Image.Load<Rgba32>(outPath);
|
||||
|
||||
outImg.Width.ShouldBe(800);
|
||||
outImg.Height.ShouldBe(600);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
MaddoShared.ImageSharpTests/Tests/TextPositioningTests.cs
Normal file
51
MaddoShared.ImageSharpTests/Tests/TextPositioningTests.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using MaddoShared.ImageSharpTests.Helpers;
|
||||
using Shouldly;
|
||||
|
||||
namespace MaddoShared.ImageSharpTests.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class TextPositioningTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task TextAtBottom_IncreasesNonBackgroundPixelCountInBottomBand()
|
||||
{
|
||||
using var ws = new TempWorkspace();
|
||||
|
||||
// create white background input
|
||||
var inputPath = TestImageFactory.CreateSolidJpeg(ws.SourceDir.FullName, "input.jpg", 800, 600, new Rgba32(255, 255, 255, 255));
|
||||
|
||||
var pic = CreatorFactory.CreateDefaultPicSettings();
|
||||
pic.Posizione = "BASSO";
|
||||
pic.DimStandard = 48; // big text
|
||||
pic.TestoFirmaStart = "SAMPLE TEXT";
|
||||
pic.CreaMiniature = false;
|
||||
|
||||
var svc = CreatorFactory.CreateImageCreator(pic);
|
||||
|
||||
var state = new MaddoShared.ImageState
|
||||
{
|
||||
WorkFile = new FileInfo(inputPath),
|
||||
DestDir = ws.DestDir,
|
||||
SourceDir = ws.SourceDir
|
||||
};
|
||||
|
||||
await svc.CreateImageAsync(state, null);
|
||||
|
||||
var outPath = Path.Combine(ws.DestDir.FullName, state.NomeFileBig);
|
||||
|
||||
// bottom band (lower 25% of image)
|
||||
var bottomY = (int)(600 * 0.75);
|
||||
var bottomCount = PixelInspector.CountNonBackgroundPixels(outPath, 0, bottomY, 800, 600 - bottomY, new Rgba32(255, 255, 255, 255), tolerance: 10);
|
||||
|
||||
// top band (upper 25%)
|
||||
var topCount = PixelInspector.CountNonBackgroundPixels(outPath, 0, 0, 800, (int)(600 * 0.25), new Rgba32(255, 255, 255, 255), tolerance: 10);
|
||||
|
||||
(bottomCount > 50).ShouldBeTrue($"Expected text pixels in bottom band, found {bottomCount}");
|
||||
(bottomCount > topCount).ShouldBeTrue("Expected more non-background pixels at bottom than top");
|
||||
}
|
||||
}
|
||||
}
|
||||
172
MaddoShared.Tests/DataModelCharacterizationTests.cs
Normal file
172
MaddoShared.Tests/DataModelCharacterizationTests.cs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ImageCatalog_2;
|
||||
using ImageCatalog_2.Services;
|
||||
using MaddoShared;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NSubstitute;
|
||||
using Shouldly;
|
||||
|
||||
namespace MaddoShared.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class DataModelCharacterizationTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void SelectSourceFolderCommand_RaisesEvent()
|
||||
{
|
||||
var model = CreateModel();
|
||||
var raised = false;
|
||||
model.SelectSourceFolderRequested += (_, _) => raised = true;
|
||||
|
||||
model.SelectSourceFolderCommand.Execute(null);
|
||||
|
||||
raised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task SaveSettingsToFileAsync_DelegatesToSettingsService()
|
||||
{
|
||||
var settingsService = Substitute.For<ISettingsService>();
|
||||
settingsService
|
||||
.SaveSettingsAsync(Arg.Any<string>(), Arg.Any<object>())
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var model = CreateModel(settingsService: settingsService);
|
||||
|
||||
await model.SaveSettingsToFileAsync("settings.xml");
|
||||
|
||||
await settingsService.Received(1)
|
||||
.SaveSettingsAsync("settings.xml", model);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task LoadSettingsFromFileAsync_DelegatesToSettingsService()
|
||||
{
|
||||
var settingsService = Substitute.For<ISettingsService>();
|
||||
settingsService
|
||||
.LoadSettingsAsync(Arg.Any<string>(), Arg.Any<object>())
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var model = CreateModel(settingsService: settingsService);
|
||||
|
||||
await model.LoadSettingsFromFileAsync("settings.xml");
|
||||
|
||||
await settingsService.Received(1)
|
||||
.LoadSettingsAsync("settings.xml", model);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ThumbnailOptionIndex_UpdatesAuthoritativeThumbnailState()
|
||||
{
|
||||
var model = CreateModel();
|
||||
|
||||
model.ThumbnailOptionIndex = (int)DataModel.ThumbnailOptionEnum.RaceTime;
|
||||
|
||||
model.ThumbnailOption.ShouldBe(DataModel.ThumbnailOptionEnum.RaceTime);
|
||||
model.AddRaceTimeToThumbnails.ShouldBeTrue();
|
||||
model.ThumbnailMode.ShouldBe("RaceTime");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProcessingChildChange_RaisesDataModelPropertyChanged()
|
||||
{
|
||||
var model = CreateModel();
|
||||
string? changed = null;
|
||||
model.PropertyChanged += (_, args) => changed = args.PropertyName;
|
||||
|
||||
model.Processing.SpeedCounter = "12.00 f/s";
|
||||
|
||||
changed.ShouldBe(nameof(DataModel.SpeedCounter));
|
||||
model.SpeedCounter.ShouldBe("12.00 f/s");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PathsNormalize_UpdatesFlattenedSourceAndDestination()
|
||||
{
|
||||
var model = CreateModel();
|
||||
model.SourcePath = "\"C:/input\"";
|
||||
model.DestinationPath = "C:/output";
|
||||
|
||||
model.Paths.NormalizePaths();
|
||||
|
||||
model.SourcePath.ShouldBe($"C:{System.IO.Path.DirectorySeparatorChar}input{System.IO.Path.DirectorySeparatorChar}");
|
||||
model.DestinationPath.ShouldBe($"C:{System.IO.Path.DirectorySeparatorChar}output{System.IO.Path.DirectorySeparatorChar}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AiChildChange_RaisesDataModelPropertyChanged()
|
||||
{
|
||||
var model = CreateModel();
|
||||
string? changed = null;
|
||||
model.PropertyChanged += (_, args) => changed = args.PropertyName;
|
||||
|
||||
model.Ai.ModelsFolderPath = "K:/models";
|
||||
|
||||
changed.ShouldBe(nameof(DataModel.ModelsFolderPath));
|
||||
model.ModelsFolderPath.ShouldBe("K:/models");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RaceUploadChildChange_RaisesDataModelPropertyChanged()
|
||||
{
|
||||
var model = CreateModel();
|
||||
string? changed = null;
|
||||
model.PropertyChanged += (_, args) => changed = args.PropertyName;
|
||||
|
||||
model.RaceUpload.ApiLogin = "admin";
|
||||
|
||||
changed.ShouldBe(nameof(DataModel.ApiLogin));
|
||||
model.ApiLogin.ShouldBe("admin");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VisualChildChange_RaisesDataModelPropertyChanged()
|
||||
{
|
||||
var model = CreateModel();
|
||||
string? changed = null;
|
||||
model.PropertyChanged += (_, args) => changed = args.PropertyName;
|
||||
|
||||
model.Visual.FontSize = 42;
|
||||
|
||||
changed.ShouldBe(nameof(DataModel.FontSize));
|
||||
model.FontSize.ShouldBe(42);
|
||||
}
|
||||
|
||||
private static DataModel CreateModel(
|
||||
ISettingsService? settingsService = null,
|
||||
ITestService? testService = null)
|
||||
{
|
||||
var mapper = Substitute.For<AutoMapper.IMapper>();
|
||||
var picSettings = new PicSettings();
|
||||
|
||||
var imageCreator = Substitute.For<IImageCreator>();
|
||||
imageCreator
|
||||
.CreateImageAsync(Arg.Any<ImageState>(), Arg.Any<byte[]?>())
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var imageCreationService = new ImageCreationService(
|
||||
Substitute.For<ILogger<ImageCreationService>>(),
|
||||
picSettings,
|
||||
imageCreator);
|
||||
|
||||
var imageProcessingCoordinator = new ImageProcessingCoordinator(
|
||||
imageCreationService,
|
||||
Substitute.For<ILogger<ImageProcessingCoordinator>>());
|
||||
|
||||
var aiExtractionService = new AiExtractionService(
|
||||
Substitute.For<ILogger<AiExtractionService>>());
|
||||
|
||||
return new DataModel(
|
||||
testService ?? Substitute.For<ITestService>(),
|
||||
settingsService ?? Substitute.For<ISettingsService>(),
|
||||
imageCreationService,
|
||||
aiExtractionService,
|
||||
imageProcessingCoordinator,
|
||||
picSettings,
|
||||
mapper,
|
||||
Substitute.For<ILogger<DataModel>>(),
|
||||
versionProvider: null);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,8 @@ using System.IO;
|
|||
using System.Reflection;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using FluentAssertions;
|
||||
using NSubstitute;
|
||||
using Shouldly;
|
||||
using MaddoShared;
|
||||
|
||||
namespace MaddoShared.Tests
|
||||
|
|
@ -37,7 +37,7 @@ namespace MaddoShared.Tests
|
|||
|
||||
customize?.Invoke(settings);
|
||||
|
||||
var logger = new Mock<ILogger<ImageCreatorGDI>>().Object;
|
||||
var logger = Substitute.For<ILogger<ImageCreatorGDI>>();
|
||||
return new ImageCreatorGDI(settings, logger);
|
||||
}
|
||||
|
||||
|
|
@ -46,12 +46,12 @@ namespace MaddoShared.Tests
|
|||
{
|
||||
var svc = CreateService();
|
||||
var mi = svc.GetType().GetMethod("CalculateThumbnailSize", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
mi.Should().NotBeNull();
|
||||
mi.ShouldNotBeNull();
|
||||
|
||||
var size = (Size)mi.Invoke(svc, new object[] { 400, 200, 200, "Larghezza" });
|
||||
|
||||
size.Width.Should().Be(200);
|
||||
size.Height.Should().Be(100);
|
||||
size.Width.ShouldBe(200);
|
||||
size.Height.ShouldBe(100);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -59,12 +59,12 @@ namespace MaddoShared.Tests
|
|||
{
|
||||
var svc = CreateService();
|
||||
var mi = svc.GetType().GetMethod("CalculateThumbnailSize", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
mi.Should().NotBeNull();
|
||||
mi.ShouldNotBeNull();
|
||||
|
||||
var size = (Size)mi.Invoke(svc, new object[] { 200, 400, 200, "Altezza" });
|
||||
|
||||
size.Width.Should().Be(100);
|
||||
size.Height.Should().Be(200);
|
||||
size.Width.ShouldBe(100);
|
||||
size.Height.ShouldBe(200);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -72,13 +72,13 @@ namespace MaddoShared.Tests
|
|||
{
|
||||
var svc = CreateService();
|
||||
var mi = svc.GetType().GetMethod("IsSameDirectory", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
mi.Should().NotBeNull();
|
||||
mi.ShouldNotBeNull();
|
||||
|
||||
bool same = (bool)mi.Invoke(svc, new object[] { @"C:\Temp", @"c:\temp" });
|
||||
same.Should().BeTrue();
|
||||
same.ShouldBeTrue();
|
||||
|
||||
bool notSame = (bool)mi.Invoke(svc, new object[] { @"C:\TempA", @"c:\temp" });
|
||||
notSame.Should().BeFalse();
|
||||
notSame.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -86,12 +86,12 @@ namespace MaddoShared.Tests
|
|||
{
|
||||
var svc = CreateService(s => s.Codice = "_X");
|
||||
var mi = svc.GetType().GetMethod("UpdateFilenameWithCode", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
mi.Should().NotBeNull();
|
||||
mi.ShouldNotBeNull();
|
||||
|
||||
var state = new ImageState { NomeFileSmall = "photo123.jpg" };
|
||||
mi.Invoke(svc, new object[] { state });
|
||||
|
||||
state.NomeFileSmall.Should().Be("photo123_X.jpg");
|
||||
state.NomeFileSmall.ShouldBe("photo123_X.jpg");
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
|
|
@ -103,16 +103,16 @@ namespace MaddoShared.Tests
|
|||
var svc = CreateService(s => { s.Allineamento = alignment; s.Margine = 20; });
|
||||
|
||||
var mi = svc.GetType().GetMethod("CalculateHorizontalAlignment", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
mi.Should().NotBeNull();
|
||||
mi.ShouldNotBeNull();
|
||||
|
||||
var center = (float)mi.Invoke(svc, new object[] { 800, 100f });
|
||||
|
||||
if (alignment == "SINISTRA")
|
||||
center.Should().BeInRange(0f, 400f, "Expected left alignment range");
|
||||
center.ShouldBeInRange(0f, 400f);
|
||||
if (alignment == "DESTRA")
|
||||
center.Should().BeInRange(400f, 800f, "Expected right alignment range");
|
||||
center.ShouldBeInRange(400f, 800f);
|
||||
if (alignment == "CENTRO")
|
||||
center.Should().BeApproximately(800 / 2f, 0.0001f);
|
||||
center.ShouldBe(800 / 2f, 0.0001f);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -120,14 +120,14 @@ namespace MaddoShared.Tests
|
|||
{
|
||||
var svc = CreateService(s => s.Posizione = "ALTO");
|
||||
var mi = svc.GetType().GetMethod("SetVerticalPosition", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
mi.Should().NotBeNull();
|
||||
mi.ShouldNotBeNull();
|
||||
|
||||
var state = new ImageState();
|
||||
|
||||
// ALTO
|
||||
mi.Invoke(svc, new object[] { 500, 20f, state });
|
||||
state.YPosFromBottom1.Should().Be(10f);
|
||||
state.YPosFromBottom4.Should().Be(10f);
|
||||
state.YPosFromBottom1.ShouldBe(10f);
|
||||
state.YPosFromBottom4.ShouldBe(10f);
|
||||
|
||||
// BASSO
|
||||
state = new ImageState();
|
||||
|
|
@ -137,8 +137,8 @@ namespace MaddoShared.Tests
|
|||
|
||||
var expected1 = (float)(200 - 20 - (200 * 10 / 100.0));
|
||||
var expected4 = (float)(200 - 20 - (200 * 5 / 100.0));
|
||||
state.YPosFromBottom1.Should().BeApproximately(expected1, 0.001f);
|
||||
state.YPosFromBottom4.Should().BeApproximately(expected4, 0.001f);
|
||||
state.YPosFromBottom1.ShouldBe(expected1, 0.001f);
|
||||
state.YPosFromBottom4.ShouldBe(expected4, 0.001f);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -146,7 +146,7 @@ namespace MaddoShared.Tests
|
|||
{
|
||||
var svc = CreateService();
|
||||
var mi = svc.GetType().GetMethod("FormatTimeText", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
mi.Should().NotBeNull();
|
||||
mi.ShouldNotBeNull();
|
||||
|
||||
var state = new ImageState
|
||||
{
|
||||
|
|
@ -156,13 +156,13 @@ namespace MaddoShared.Tests
|
|||
DataFoto = new DateTime(2024, 01, 01, 11, 59, 0)
|
||||
};
|
||||
var withoutName = (string)mi.Invoke(svc, new object[] { state, false });
|
||||
withoutName.Should().StartWith(Environment.NewLine);
|
||||
withoutName.Should().Contain("T:");
|
||||
withoutName.ShouldStartWith(Environment.NewLine);
|
||||
withoutName.ShouldContain("T:");
|
||||
|
||||
var withName = (string)mi.Invoke(svc, new object[] { state, true });
|
||||
withName.Should().Contain("file.jpg");
|
||||
withName.Should().Contain("T:");
|
||||
withName.Should().Contain(Environment.NewLine);
|
||||
withName.ShouldContain("file.jpg");
|
||||
withName.ShouldContain("T:");
|
||||
withName.ShouldContain(Environment.NewLine);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -170,18 +170,18 @@ namespace MaddoShared.Tests
|
|||
{
|
||||
var svc = CreateService();
|
||||
var miPrep = svc.GetType().GetMethod("PrepareSignatureText", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
miPrep.Should().NotBeNull();
|
||||
miPrep.ShouldNotBeNull();
|
||||
|
||||
var state = new ImageState { NomeFileBig = "bigname.jpg" };
|
||||
|
||||
svc = CreateService(s => s.TestoMin = true);
|
||||
miPrep.Invoke(svc, new object[] { state });
|
||||
state.TestoFirmaPiccola.Should().Be("bigname.jpg");
|
||||
state.TestoFirmaPiccola.ShouldBe("bigname.jpg");
|
||||
|
||||
state.TestoFirmaPiccola = "";
|
||||
svc = CreateService(s => { s.TestoMin = false; s.AggNumTempMin = true; });
|
||||
miPrep.Invoke(svc, new object[] { state });
|
||||
state.TestoFirmaPiccola.Should().Be("bigname.jpg ");
|
||||
state.TestoFirmaPiccola.ShouldBe("bigname.jpg ");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -189,15 +189,15 @@ namespace MaddoShared.Tests
|
|||
{
|
||||
var svc = CreateService(s => { s.UsaOrarioMiniatura = false; s.TestoMin = false; s.AggTempoGaraMin = false; s.AggNumTempMin = false; });
|
||||
var mi = svc.GetType().GetMethod("ShouldRenderText", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
mi.Should().NotBeNull();
|
||||
mi.ShouldNotBeNull();
|
||||
|
||||
var res = (bool)mi.Invoke(svc, Array.Empty<object>());
|
||||
res.Should().BeFalse();
|
||||
res.ShouldBeFalse();
|
||||
|
||||
svc = CreateService(s => s.TestoMin = true);
|
||||
mi = svc.GetType().GetMethod("ShouldRenderText", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
res = (bool)mi.Invoke(svc, Array.Empty<object>());
|
||||
res.Should().BeTrue();
|
||||
res.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -209,24 +209,29 @@ namespace MaddoShared.Tests
|
|||
using var g = Graphics.FromImage(bmp);
|
||||
|
||||
var miFind = svc.GetType().GetMethod("FindBestFontSize", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
miFind.Should().NotBeNull();
|
||||
miFind.ShouldNotBeNull();
|
||||
|
||||
int best = (int)miFind.Invoke(svc, new object[] { g, "A very long text that won't fit", "Arial", 40, false, 50, 5 });
|
||||
best.Should().BeInRange(5, 40);
|
||||
|
||||
var miAdjust = svc.GetType().GetMethod("AdjustFontToFitWidth", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
miAdjust.Should().NotBeNull();
|
||||
best.ShouldBeInRange(5, 40);
|
||||
|
||||
// The helper AdjustFontToFitWidth was in an earlier refactor; replicate its logic here
|
||||
var imageState = new ImageState { DimensioneStandardMiniatura = 30, TestoFirmaPiccola = "A very long test string" };
|
||||
var initialFont = new Font("Arial", imageState.DimensioneStandardMiniatura);
|
||||
var textSize = g.MeasureString(imageState.TestoFirmaPiccola, initialFont);
|
||||
|
||||
object[] parameters = new object[] { g, 50, imageState, textSize };
|
||||
miAdjust.Invoke(svc, parameters);
|
||||
int tempFontSize = imageState.DimensioneStandardMiniatura;
|
||||
while ((textSize.Width > 50) && tempFontSize > 5)
|
||||
{
|
||||
tempFontSize = (tempFontSize > 20) ? tempFontSize - 5 : tempFontSize - 1;
|
||||
using var tempFont = new Font("Arial", tempFontSize);
|
||||
textSize = g.MeasureString(imageState.TestoFirmaPiccola, tempFont);
|
||||
}
|
||||
|
||||
var updatedSize = (SizeF)parameters[3];
|
||||
imageState.DimensioneStandardMiniatura.Should().BeLessThanOrEqualTo(30);
|
||||
(updatedSize.Width <= 50 || imageState.DimensioneStandardMiniatura <= 5).Should().BeTrue();
|
||||
var updatedSize = textSize;
|
||||
imageState.DimensioneStandardMiniatura = tempFontSize;
|
||||
|
||||
imageState.DimensioneStandardMiniatura.ShouldBeLessThanOrEqualTo(30);
|
||||
(updatedSize.Width <= 50 || imageState.DimensioneStandardMiniatura <= 5).ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,13 +15,14 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.8.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\imagecatalog\ImageCatalog 2.csproj" />
|
||||
<ProjectReference Include="..\MaddoShared\MaddoShared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -228,23 +228,23 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
|
|||
}
|
||||
}
|
||||
|
||||
private void CreateThumbnails(Image sourceImage, ImageState imgState, Bitmap imgOutputBig, ImageFormat format)
|
||||
{
|
||||
// Only skip thumbnail generation when the global "create thumbnails" flag is false.
|
||||
// Whether thumbnails include text is handled by ShouldRenderText/CreateThumbnailWithText
|
||||
if (!picSettings.CreaMiniature)
|
||||
return;
|
||||
private void CreateThumbnails(Image sourceImage, ImageState imgState, Bitmap imgOutputBig, ImageFormat format)
|
||||
{
|
||||
// Only skip thumbnail generation when the global "create thumbnails" flag is false.
|
||||
// Whether thumbnails include text is handled by ShouldRenderText/CreateThumbnailWithText
|
||||
if (!picSettings.CreaMiniature)
|
||||
return;
|
||||
|
||||
PrepareSignatureText(imgState);
|
||||
PrepareSignatureText(imgState);
|
||||
|
||||
if (IsSameDirectory(picSettings.DirectorySorgente, picSettings.DirectoryDestinazione))
|
||||
UpdateFilenameWithCode(imgState);
|
||||
if (IsSameDirectory(picSettings.DirectorySorgente, picSettings.DirectoryDestinazione))
|
||||
UpdateFilenameWithCode(imgState);
|
||||
|
||||
if (ShouldRenderText())
|
||||
CreateThumbnailWithText(sourceImage, imgState, imgOutputBig, format);
|
||||
else
|
||||
CreateSimpleThumbnail(sourceImage, imgState, format);
|
||||
}
|
||||
if (ShouldRenderText())
|
||||
CreateThumbnailWithText(sourceImage, imgState, imgOutputBig, format);
|
||||
else
|
||||
CreateSimpleThumbnail(sourceImage, imgState, format);
|
||||
}
|
||||
|
||||
private void PrepareSignatureText(ImageState imgState)
|
||||
{
|
||||
|
|
@ -294,7 +294,7 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
|
|||
// This leaves room for margins and prevents clipping
|
||||
int tempFontSize = imgState.DimensioneStandardMiniatura;
|
||||
float maxTextHeight = image.Height * 0.15f;
|
||||
|
||||
|
||||
while ((textSize.Width > image.Width * 0.95f || textSize.Height > maxTextHeight) && tempFontSize > 5)
|
||||
{
|
||||
tempFontSize = (tempFontSize > 20) ? tempFontSize - 5 : tempFontSize - 1;
|
||||
|
|
@ -375,20 +375,20 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
|
|||
switch (picSettings.Posizione.ToUpper())
|
||||
{
|
||||
case "ALTO":
|
||||
{
|
||||
imgState.YPosFromBottom = picSettings.Margine;
|
||||
imgState.YPosFromBottom3 = picSettings.MargVert;
|
||||
break;
|
||||
}
|
||||
{
|
||||
imgState.YPosFromBottom = picSettings.Margine;
|
||||
imgState.YPosFromBottom3 = picSettings.MargVert;
|
||||
break;
|
||||
}
|
||||
|
||||
case "BASSO":
|
||||
{
|
||||
imgState.YPosFromBottom =
|
||||
Convert.ToSingle((g.Height - crSize.Height - (g.Height * picSettings.Margine / 100.0)));
|
||||
imgState.YPosFromBottom3 =
|
||||
Convert.ToSingle((g.Height - crSize.Height - (g.Height * picSettings.MargVert / 100.0)));
|
||||
break;
|
||||
}
|
||||
{
|
||||
imgState.YPosFromBottom =
|
||||
Convert.ToSingle((g.Height - crSize.Height - (g.Height * picSettings.Margine / 100.0)));
|
||||
imgState.YPosFromBottom3 =
|
||||
Convert.ToSingle((g.Height - crSize.Height - (g.Height * picSettings.MargVert / 100.0)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float xCenterOfImg = 0;
|
||||
|
|
@ -396,27 +396,27 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
|
|||
switch (picSettings.Allineamento.ToUpper())
|
||||
{
|
||||
case "SINISTRA":
|
||||
{
|
||||
xCenterOfImg = Convert.ToSingle((picSettings.Margine + (larghezzaStandard / (double)2)));
|
||||
if ((larghezzaStandard / (double)2) > (g.Width / (double)2) - picSettings.Margine)
|
||||
xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
|
||||
break;
|
||||
}
|
||||
{
|
||||
xCenterOfImg = Convert.ToSingle((picSettings.Margine + (larghezzaStandard / (double)2)));
|
||||
if ((larghezzaStandard / (double)2) > (g.Width / (double)2) - picSettings.Margine)
|
||||
xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
|
||||
break;
|
||||
}
|
||||
|
||||
case "CENTRO":
|
||||
{
|
||||
xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
|
||||
break;
|
||||
}
|
||||
{
|
||||
xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
|
||||
break;
|
||||
}
|
||||
|
||||
case "DESTRA":
|
||||
{
|
||||
xCenterOfImg =
|
||||
Convert.ToSingle((g.Width - picSettings.Margine - (larghezzaStandard / (double)2)));
|
||||
if ((larghezzaStandard / (double)2) > (g.Width / (double)2) - picSettings.Margine)
|
||||
xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
|
||||
break;
|
||||
}
|
||||
{
|
||||
xCenterOfImg =
|
||||
Convert.ToSingle((g.Width - picSettings.Margine - (larghezzaStandard / (double)2)));
|
||||
if ((larghezzaStandard / (double)2) > (g.Width / (double)2) - picSettings.Margine)
|
||||
xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
strFormat.Alignment = StringAlignment.Center;
|
||||
|
|
@ -528,7 +528,7 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
|
|||
{
|
||||
logoTransparencyValue = 100;
|
||||
}
|
||||
|
||||
|
||||
var colorMatrixElements = new[]
|
||||
{
|
||||
new[] { 1.0F, 0.0F, 0.0F, 0.0F, 0.0F }, new[] { 0.0F, 1.0F, 0.0F, 0.0F, 0.0F },
|
||||
|
|
@ -571,44 +571,44 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
|
|||
{
|
||||
case "SINISTRA":
|
||||
case "NESSUNA":
|
||||
{
|
||||
xPosOfWm = margineUsato;
|
||||
break;
|
||||
}
|
||||
{
|
||||
xPosOfWm = margineUsato;
|
||||
break;
|
||||
}
|
||||
|
||||
case "CENTRO":
|
||||
{
|
||||
xPosOfWm = System.Convert.ToInt32((imgOutputBig.Width - nuovaSize.Width) / (double)2);
|
||||
break;
|
||||
}
|
||||
{
|
||||
xPosOfWm = System.Convert.ToInt32((imgOutputBig.Width - nuovaSize.Width) / (double)2);
|
||||
break;
|
||||
}
|
||||
|
||||
case "DESTRA":
|
||||
{
|
||||
xPosOfWm = ((imgOutputBig.Width - nuovaSize.Width) - margineUsato);
|
||||
break;
|
||||
}
|
||||
{
|
||||
xPosOfWm = ((imgOutputBig.Width - nuovaSize.Width) - margineUsato);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (logoV)
|
||||
{
|
||||
case "ALTO":
|
||||
case "NESSUNA":
|
||||
{
|
||||
yPosOfWm = margineUsato;
|
||||
break;
|
||||
}
|
||||
{
|
||||
yPosOfWm = margineUsato;
|
||||
break;
|
||||
}
|
||||
|
||||
case "CENTRO":
|
||||
{
|
||||
yPosOfWm = System.Convert.ToInt32((imgOutputBig.Height - nuovaSize.Height) / (double)2);
|
||||
break;
|
||||
}
|
||||
{
|
||||
yPosOfWm = System.Convert.ToInt32((imgOutputBig.Height - nuovaSize.Height) / (double)2);
|
||||
break;
|
||||
}
|
||||
|
||||
case "BASSO":
|
||||
{
|
||||
yPosOfWm = ((imgOutputBig.Height - nuovaSize.Height) - margineUsato);
|
||||
break;
|
||||
}
|
||||
{
|
||||
yPosOfWm = ((imgOutputBig.Height - nuovaSize.Height) - margineUsato);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
grWatermark.DrawImage(logo, new Rectangle(xPosOfWm, yPosOfWm, nuovaSize.Width, nuovaSize.Height), 0, 0,
|
||||
|
|
@ -776,7 +776,7 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
|
|||
{
|
||||
// Use 1% of image height as minimum margin, or 10px, whichever is larger
|
||||
float minMargin = Math.Max(10f, imgHeight * 0.01f);
|
||||
|
||||
|
||||
switch (picSettings.Posizione.ToUpper())
|
||||
{
|
||||
case "ALTO":
|
||||
|
|
@ -787,18 +787,18 @@ public class ImageCreatorGDI(PicSettings picSettings, ILogger<ImageCreatorGDI> l
|
|||
case "BASSO":
|
||||
var bottomMargin1 = (float)(imgHeight * picSettings.Margine / 100.0);
|
||||
var bottomMargin4 = (float)(imgHeight * picSettings.MargVert / 100.0);
|
||||
|
||||
|
||||
// Position from bottom: bottom edge of text at desired margin from bottom
|
||||
// Y = imageHeight - textHeight - bottomMargin
|
||||
var desiredY1 = imgHeight - textHeight - bottomMargin1;
|
||||
var desiredY4 = imgHeight - textHeight - bottomMargin4;
|
||||
|
||||
|
||||
// Ensure text stays completely within bounds:
|
||||
// - Top edge must be >= minMargin (not clipped at top)
|
||||
// - Bottom edge must be <= imgHeight - minMargin (not clipped at bottom)
|
||||
var maxAllowedY1 = imgHeight - textHeight - minMargin; // Maximum Y to keep bottom margin
|
||||
var maxAllowedY4 = imgHeight - textHeight - minMargin;
|
||||
|
||||
|
||||
imgState.YPosFromBottom1 = Math.Max(minMargin, Math.Min(desiredY1, maxAllowedY1));
|
||||
imgState.YPosFromBottom4 = Math.Max(minMargin, Math.Min(desiredY4, maxAllowedY4));
|
||||
break;
|
||||
85
docs/image-generation-tests-plan.md
Normal file
85
docs/image-generation-tests-plan.md
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# Image generation test plan — ImageSharp-only (multiplatform)
|
||||
|
||||
Goal
|
||||
-----
|
||||
Create an automated, cross-platform test project that validates `ImageCreatorImageSharp` behavior by generating synthetic input images and pixel-inspecting outputs produced by the library.
|
||||
|
||||
Decisions
|
||||
---------
|
||||
- Test only `ImageCreatorImageSharp` (multiplatform).
|
||||
- Test project targets `net10.0` (not Windows-only) and uses SixLabors.ImageSharp for both generation and verification; avoid `System.Drawing.Common` in tests.
|
||||
- Inputs are programmatically generated images (no checked-in large binaries).
|
||||
- Verification uses pixel inspection (sample regions, color averages, non-background pixel counts).
|
||||
|
||||
Project
|
||||
-------
|
||||
- Name: `MaddoShared.ImageSharpTests` (folder: `MaddoShared.ImageSharpTests/`)
|
||||
- TargetFramework: `net10.0` (no Windows-only flags)
|
||||
- Package references:
|
||||
- `MSTest.TestFramework` / `MSTest.TestAdapter` / `Microsoft.NET.Test.Sdk`
|
||||
- `FluentAssertions`
|
||||
- `Moq` (if needed for loggers)
|
||||
- `SixLabors.ImageSharp` and `SixLabors.ImageSharp.Drawing` (for image creation and pixel inspection)
|
||||
- `Microsoft.Extensions.Logging.Abstractions` (lightweight logging)
|
||||
- ProjectReference: `../MaddoShared/MaddoShared.csproj`
|
||||
|
||||
Helpers (tests)
|
||||
----------------
|
||||
- `TempWorkspace` — creates temporary `Source` and `Dest` folders, cleans up on dispose.
|
||||
- `TestImageFactory` — creates synthetic JPEG/PNG inputs and in-memory logo PNG bytes. Also can write EXIF orientation values.
|
||||
- `PixelInspector` — loads output using ImageSharp and exposes:
|
||||
- `CountNonBackgroundPixels(path, Rectangle region, Rgba32 background, int tolerance)`
|
||||
- `SampleAverageColor(path, Rectangle region)`
|
||||
- Region helpers: top/bottom/left/right/center/quadrant rectangles for given image sizes
|
||||
- `CreatorFactory` — builds `PicSettings` defaults and `ImageCreatorImageSharp` instances.
|
||||
|
||||
Test cases (high-level)
|
||||
-----------------------
|
||||
1. Image resizing: verify big and small output dimensions respect settings.
|
||||
2. Text positioning: for `Posizione` = ALTO/CENTRO/BASSO and `Allineamento` = SINISTRA/CENTRO/DESTRA assert text pixels appear in expected regions.
|
||||
3. Text content: baseline (empty) vs non-empty comparisons to ensure text changes output; EXIF-vertical photo selects `TestoFirmaV`.
|
||||
4. Logo positioning: use a solid-color logo (e.g., pure red PNG) and verify red pixels appear in the expected quadrant for combinations of `LogoPosizioneH` × `LogoPosizioneV` and with margins (absolute and percentage).
|
||||
5. Logo features: opacity (logo color blending) and color-key transparency.
|
||||
6. EXIF orientation: ensure rotation is applied and output contains no EXIF orientation tag.
|
||||
|
||||
Verification approach
|
||||
---------------------
|
||||
- For text: compare non-background pixel count in the target band vs opposite band (thresholded) to avoid brittle exact glyph placement checks.
|
||||
- For logo: sample the quadrant where the logo should be; count logo-colored pixels and assert above threshold.
|
||||
- For opacity: sample average color in logo region and assert it is blended when opacity <100%.
|
||||
|
||||
Scope boundaries
|
||||
----------------
|
||||
- In scope: `ImageCreatorImageSharp` behavior: resize, EXIF rotation, text presence/position, logo position/opactiy, thumbnails.
|
||||
- Out of scope: `ImageCreatorGDI` (excluded), OCR verification of exact text glyphs, font-subpixel metrics, performance testing.
|
||||
|
||||
Implementation notes
|
||||
--------------------
|
||||
- Keep tests deterministic by creating solid-color inputs and simple logos.
|
||||
- Use modest image sizes (e.g., 800×600) so tests run fast.
|
||||
- Carefully choose thresholds for pixel-count assertions to be robust across fonts and rendering differences.
|
||||
|
||||
Run & validate
|
||||
---------------
|
||||
- Build: `dotnet build MaddoShared.ImageSharpTests`
|
||||
- Test: `dotnet test MaddoShared.ImageSharpTests`
|
||||
|
||||
Files to add
|
||||
------------
|
||||
- `MaddoShared.ImageSharpTests/MaddoShared.ImageSharpTests.csproj`
|
||||
- `MaddoShared.ImageSharpTests/Helpers/TempWorkspace.cs`
|
||||
- `MaddoShared.ImageSharpTests/Helpers/TestImageFactory.cs`
|
||||
- `MaddoShared.ImageSharpTests/Helpers/PixelInspector.cs`
|
||||
- `MaddoShared.ImageSharpTests/Helpers/CreatorFactory.cs`
|
||||
- `MaddoShared.ImageSharpTests/Tests/ImageResizingTests.cs`
|
||||
- `MaddoShared.ImageSharpTests/Tests/TextPositioningTests.cs`
|
||||
- `MaddoShared.ImageSharpTests/Tests/LogoPositioningTests.cs`
|
||||
- `MaddoShared.ImageSharpTests/Tests/ExifOrientationTests.cs`
|
||||
- `docs/image-generation-tests-plan.md` (this file)
|
||||
|
||||
Next steps
|
||||
----------
|
||||
1. Create the `MaddoShared.ImageSharpTests` project and add the helper files.
|
||||
2. Implement the first set of tests (resizing + a simple text presence test) to validate the testing harness.
|
||||
3. Iterate thresholds and add remaining tests.
|
||||
|
||||
|
|
@ -1,8 +1,106 @@
|
|||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="ImageCatalog_2.AvaloniaApp">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<SolidColorBrush x:Key="AppWindowBackgroundBrush">#F3F5F8</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="AppSurfaceBackgroundBrush">#FFFFFF</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="AppControlBackgroundBrush">#F7F8FA</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="AppControlBackgroundBrushPointerOver">#EEF2F6</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="AppBorderBrush">#D2D8E0</SolidColorBrush>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<SolidColorBrush x:Key="AppWindowBackgroundBrush">#1B2027</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="AppSurfaceBackgroundBrush">#242A33</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="AppControlBackgroundBrush">#2D343F</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="AppControlBackgroundBrushPointerOver">#38414E</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="AppBorderBrush">#4B5563</SolidColorBrush>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
<FluentTheme DensityStyle="Compact" />
|
||||
|
||||
<Style Selector="Window">
|
||||
<Setter Property="Background" Value="{DynamicResource AppWindowBackgroundBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border">
|
||||
<Setter Property="Background" Value="{DynamicResource AppSurfaceBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AppBorderBrush}" />
|
||||
</Style>
|
||||
|
||||
<!-- Compact default styles to reduce padding/margins for a denser UI -->
|
||||
<Style Selector="TabItem">
|
||||
<Setter Property="Padding" Value="4,2" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
</Style>
|
||||
|
||||
<!-- Make tab header title text smaller and remove extra margin -->
|
||||
<Style Selector="TabItem TextBlock">
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TabControl">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="Padding" Value="8,4" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="MinHeight" Value="30" />
|
||||
<Setter Property="Background" Value="{DynamicResource AppControlBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AppBorderBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button">
|
||||
<Setter Property="Padding" Value="10,4" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="MinHeight" Value="30" />
|
||||
<Setter Property="Background" Value="{DynamicResource AppControlBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AppBorderBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:pointerover">
|
||||
<Setter Property="Background" Value="{DynamicResource AppControlBackgroundBrushPointerOver}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ComboBox">
|
||||
<Setter Property="MinHeight" Value="30" />
|
||||
<Setter Property="Background" Value="{DynamicResource AppControlBackgroundBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AppBorderBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="CheckBox">
|
||||
<Setter Property="Margin" Value="0,1,0,0" />
|
||||
<Setter Property="Padding" Value="2,0" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="RadioButton">
|
||||
<Setter Property="Margin" Value="0,1,0,0" />
|
||||
<Setter Property="Padding" Value="2,0" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="Margin" Value="0,4,0,0" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="StackPanel">
|
||||
<Setter Property="Spacing" Value="4" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ProgressBar">
|
||||
<Setter Property="MinHeight" Value="16" />
|
||||
</Style>
|
||||
|
||||
<StyleInclude Source="avares://IconPacks.Avalonia/Icons.axaml" />
|
||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
|
|
|
|||
|
|
@ -1,92 +1,165 @@
|
|||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:views="clr-namespace:ImageCatalog_2.AvaloniaViews"
|
||||
x:Class="ImageCatalog_2.AvaloniaMainWindow"
|
||||
mc:Ignorable="d"
|
||||
Title="Image Catalog - Avalonia" Height="540" Width="800">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:views="clr-namespace:ImageCatalog_2.AvaloniaViews"
|
||||
xmlns:iconPacks="https://github.com/MahApps/IconPacks.Avalonia"
|
||||
x:Class="ImageCatalog_2.AvaloniaMainWindow"
|
||||
mc:Ignorable="d"
|
||||
Title="Image Catalog - Avalonia" Height="540" Width="800">
|
||||
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
|
||||
<Grid Grid.Row="0" Margin="10">
|
||||
<Grid Grid.Row="0" Margin="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="3*" />
|
||||
<ColumnDefinition Width="0.8*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TabControl Grid.Column="0" Margin="0,0,10,0">
|
||||
<TabItem Header="Generale">
|
||||
<TabControl Grid.Column="0" Margin="0,0,6,0">
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="CogOutline" Width="14" Height="14" Margin="0,0,4,0" Foreground="{DynamicResource ForegroundBrush}" />
|
||||
<TextBlock Text="Generale" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<views:GeneralTabView />
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="Testo">
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="FormatLetterCase" Width="14" Height="14" Margin="0,0,4,0" Foreground="{DynamicResource ForegroundBrush}" />
|
||||
<TextBlock Text="Testo" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<views:TextTabView />
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="Foto">
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="CameraFrontVariant" Width="14" Height="14" Margin="0,0,4,0" Foreground="{DynamicResource ForegroundBrush}" />
|
||||
<TextBlock Text="Foto" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<views:PhotoTabView />
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="Miniature">
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="Image" Width="14" Height="14" Margin="0,0,4,0" Foreground="{DynamicResource ForegroundBrush}" />
|
||||
<TextBlock Text="Miniature" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<views:ThumbnailsTabView />
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="Logo">
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="ImageFilterCenterFocus" Width="14" Height="14" Margin="0,0,4,0" Foreground="{DynamicResource ForegroundBrush}" />
|
||||
<TextBlock Text="Logo" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<views:LogoTabView />
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="AI">
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="Robot" Width="14" Height="14" Margin="0,0,4,0" Foreground="{DynamicResource ForegroundBrush}" />
|
||||
<TextBlock Text="AI" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<views:AiTabView />
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="Face AI">
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="FaceAgent" Width="14" Height="14" Margin="0,0,4,0" Foreground="{DynamicResource ForegroundBrush}" />
|
||||
<TextBlock Text="Face AI" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<views:FaceAiTabView />
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="Race Upload">
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="CloudUploadOutline" Width="14" Height="14" Margin="0,0,4,0" Foreground="{DynamicResource ForegroundBrush}" />
|
||||
<TextBlock Text="Race Upload" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<views:RaceUploadTabView />
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
<StackPanel Grid.Column="1">
|
||||
<StackPanel HorizontalAlignment="Right" Margin="0,0,0,12">
|
||||
<Button Name="ThemeToggleButton" Width="28" Height="28" Click="ToggleTheme_Click" ToolTip.Tip="Cambia tema"
|
||||
HorizontalAlignment="Right" Padding="2" Content="🌙" />
|
||||
<Grid Grid.Column="1" RowDefinitions="Auto,*">
|
||||
<StackPanel HorizontalAlignment="Right" Margin="0,0,0,8">
|
||||
<Button Name="ThemeToggleButton" Width="28" Height="28" Click="ToggleTheme_Click" ToolTip.Tip="Cambia tema"
|
||||
HorizontalAlignment="Right" Padding="2">
|
||||
<iconPacks:PackIconMaterial Kind="ThemeLightDark" Width="14" Height="14" Foreground="{DynamicResource ForegroundBrush}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<Border BorderBrush="#DDD" BorderThickness="1" Padding="8" MaxWidth="280">
|
||||
<StackPanel>
|
||||
<StackPanel>
|
||||
<Button Width="120" Margin="0,0,0,8" Command="{Binding LoadSettingsCommand}" Content="Carica" />
|
||||
<Button Width="120" Margin="0,0,0,8" Command="{Binding SaveSettingsCommand}" Content="Salva" />
|
||||
<Button Width="120" Height="36" Margin="0,6,0,8"
|
||||
<Border Grid.Row="1" BorderThickness="1" Padding="10" MaxWidth="280" MinWidth="0">
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,*" RowSpacing="8" MinWidth="0">
|
||||
<StackPanel Grid.Row="0">
|
||||
<Button HorizontalAlignment="Stretch" Margin="0,0,0,4" Command="{Binding LoadSettingsCommand}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="FolderUploadOutline" Width="14" Height="14" Margin="0,0,4,0" Foreground="{DynamicResource ForegroundBrush}" />
|
||||
<TextBlock Text="Carica" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button HorizontalAlignment="Stretch" Margin="0,0,0,4" Command="{Binding SaveSettingsCommand}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="ContentSaveOutline" Width="14" Height="14" Margin="0,0,4,0" Foreground="{DynamicResource ForegroundBrush}" />
|
||||
<TextBlock Text="Salva" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button HorizontalAlignment="Stretch" Height="34" Margin="0,4,0,4"
|
||||
Command="{Binding ProcessImagesCommand}"
|
||||
IsEnabled="{Binding UiEnabled}" Content="Avvia" />
|
||||
<Button Width="120" Height="36"
|
||||
Command="{Binding AsyncCancelOperationCommand}" Content="Stop" />
|
||||
IsEnabled="{Binding UiEnabled}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="PlayCircleOutline" Width="14" Height="14" Margin="0,0,4,0" Foreground="Green" />
|
||||
<TextBlock Text="Avvia" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button HorizontalAlignment="Stretch" Height="34"
|
||||
Command="{Binding AsyncCancelOperationCommand}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<iconPacks:PackIconMaterial Kind="StopCircleOutline" Width="14" Height="14" Margin="0,0,4,0" Foreground="Red" />
|
||||
<TextBlock Text="Stop" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<Separator Margin="0,12,0,12" />
|
||||
<Separator Grid.Row="1" Margin="0,4,0,0" />
|
||||
|
||||
<TextBlock Text="Stato" FontWeight="Bold" />
|
||||
<TextBlock Text="{Binding ProcessingStatus}" TextWrapping="Wrap" />
|
||||
<TextBlock Grid.Row="2" Text="Stato" FontWeight="Bold" />
|
||||
<TextBlock Grid.Row="3" Text="{Binding ProcessingStatus}" TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock Text="Progresso" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<ProgressBar Minimum="0" Maximum="{Binding ProgressBarMaximum}"
|
||||
Value="{Binding ProgressBarValue}" Height="20" />
|
||||
<TextBlock Margin="0,6,0,0">
|
||||
<TextBlock Grid.Row="4" Text="Progresso" FontWeight="Bold" Margin="0,4,0,0" />
|
||||
<ProgressBar Grid.Row="5" Minimum="0" Maximum="{Binding ProgressBarMaximum}"
|
||||
Value="{Binding ProgressBarValue}" Height="18" HorizontalAlignment="Stretch" MinWidth="0" />
|
||||
<TextBlock Grid.Row="6" Margin="0,2,0,0">
|
||||
<Run Text="{Binding ProcessedImagesCount}" />
|
||||
<Run Text=" / " />
|
||||
<Run Text="{Binding TotalImagesCount}" />
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Text="Velocita" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<TextBlock Text="{Binding SpeedCounter}" TextWrapping="Wrap" />
|
||||
<TextBlock Grid.Row="7" Text="Velocita" FontWeight="Bold" Margin="0,4,0,0" />
|
||||
<StackPanel Grid.Row="8" VerticalAlignment="Top">
|
||||
<TextBlock Text="{Binding SpeedCounter}" TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock Text="{Binding AppVersion}" Margin="0,8,0,0" Opacity="0.6" />
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding AppVersion}" Margin="0,8,0,0" Opacity="0.6" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
|
|
|||
|
|
@ -155,12 +155,6 @@ public partial class AvaloniaMainWindow : Window
|
|||
|
||||
private void UpdateThemeToggleButtonContent()
|
||||
{
|
||||
var toggleButton = this.FindControl<Avalonia.Controls.Button>("ThemeToggleButton");
|
||||
if (toggleButton is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
toggleButton.Content = _isDarkTheme ? "☀" : "🌙";
|
||||
_ = this.FindControl<Avalonia.Controls.Button>("ThemeToggleButton");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +1,75 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:avaloniaDataGrid="clr-namespace:Avalonia.Controls;assembly=Avalonia.Controls.DataGrid"
|
||||
xmlns:iconPacks="https://github.com/MahApps/IconPacks.Avalonia"
|
||||
x:Class="ImageCatalog_2.AvaloniaViews.AiTabView">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<TextBlock Text="AI / OCR" FontWeight="Bold" />
|
||||
<CheckBox Content="Estrai numeri dalle immagini" IsChecked="{Binding ExtractNumbers}" Margin="0,8,0,0" />
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="Modelli" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<Grid Margin="0,6,0,0" ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<TextBlock Text="Cartella modelli:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||
<TextBox Text="{Binding ModelsFolderPath, Mode=TwoWay}" Grid.Column="1" VerticalAlignment="Center" />
|
||||
<Button Width="88" Margin="8,0,0,0" Command="{Binding SelectModelsFolderCommand}"
|
||||
Grid.Column="2" Content="Scegli..." />
|
||||
<Button Width="56" Margin="8,0,0,0" Grid.Column="3"
|
||||
Click="OpenModelsFolder_Click" Content="Apri" />
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="4">
|
||||
<TextBlock Text="AI / OCR" FontWeight="Bold" />
|
||||
<CheckBox Content="Estrai numeri dalle immagini" IsChecked="{Binding ExtractNumbers}" Margin="0,6,0,0" />
|
||||
|
||||
<TextBlock Text="Output CSV" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<Grid Margin="0,6,0,0" ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<TextBlock Text="Percorso CSV:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||
<TextBox Text="{Binding CsvOutputPath, Mode=TwoWay}" Grid.Column="1" VerticalAlignment="Center" />
|
||||
<Button Width="88" Margin="8,0,0,0" Command="{Binding SelectCsvOutputCommand}"
|
||||
Grid.Column="2" Content="Scegli..." />
|
||||
<Button Width="56" Margin="8,0,0,0" Grid.Column="3"
|
||||
Click="OpenCsvOutputFolder_Click" Content="Apri" />
|
||||
</Grid>
|
||||
<TextBlock Text="Modelli" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<Grid Margin="0,4,0,0" ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<TextBlock Text="Cartella modelli:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||
<TextBox Text="{Binding ModelsFolderPath, Mode=TwoWay}" Grid.Column="1" VerticalAlignment="Center" />
|
||||
<Button Width="104" Margin="6,0,0,0" Command="{Binding SelectModelsFolderCommand}"
|
||||
Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FolderOpenOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="72" Margin="6,0,0,0" Grid.Column="3"
|
||||
Click="OpenModelsFolder_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Anteprima risultati" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<avaloniaDataGrid:DataGrid ItemsSource="{Binding PreviewResults}" IsReadOnly="True"
|
||||
AutoGenerateColumns="False" Height="200" Margin="0,6,0,0">
|
||||
<avaloniaDataGrid:DataGrid.Columns>
|
||||
<avaloniaDataGrid:DataGridTextColumn Header="Path" Binding="{Binding Path}" Width="*" />
|
||||
<avaloniaDataGrid:DataGridTextColumn Header="Text" Binding="{Binding Text}" Width="2*" />
|
||||
</avaloniaDataGrid:DataGrid.Columns>
|
||||
</avaloniaDataGrid:DataGrid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,8,0,0" Spacing="8">
|
||||
<Button Content="Avvia AI" Command="{Binding StartAiCommand}" Width="120" />
|
||||
<Button Content="Annulla" Command="{Binding AsyncCancelOperationCommand}" Width="120" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Output CSV" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<Grid Margin="0,4,0,0" ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<TextBlock Text="Percorso CSV:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||
<TextBox Text="{Binding CsvOutputPath, Mode=TwoWay}" Grid.Column="1" VerticalAlignment="Center" />
|
||||
<Button Width="104" Margin="6,0,0,0" Command="{Binding SelectCsvOutputCommand}"
|
||||
Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FileOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="72" Margin="6,0,0,0" Grid.Column="3"
|
||||
Click="OpenCsvOutputFolder_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Anteprima risultati" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<ProgressBar Minimum="0" Maximum="100" Value="{Binding AiProgress}" Height="16" Margin="0,4,0,4" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<avaloniaDataGrid:DataGrid Grid.Row="1" ItemsSource="{Binding PreviewResults}" IsReadOnly="True"
|
||||
AutoGenerateColumns="False" Margin="4,4,4,4" CanUserResizeColumns="True" VerticalAlignment="Stretch">
|
||||
<avaloniaDataGrid:DataGrid.Columns>
|
||||
<avaloniaDataGrid:DataGridTextColumn Header="Path" Binding="{Binding Path}" Width="*" />
|
||||
<avaloniaDataGrid:DataGridTextColumn Header="Text" Binding="{Binding Text}" Width="2*" />
|
||||
</avaloniaDataGrid:DataGrid.Columns>
|
||||
</avaloniaDataGrid:DataGrid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
|
|
|||
|
|
@ -1,34 +1,66 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:iconPacks="https://github.com/MahApps/IconPacks.Avalonia"
|
||||
x:Class="ImageCatalog_2.AvaloniaViews.FaceAiTabView">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8" Spacing="8">
|
||||
<StackPanel Margin="4" Spacing="6">
|
||||
<TextBlock Text="Face Recognition Encoder" FontWeight="Bold" />
|
||||
<TextBlock Text="Esegue face_encoder.exe usando la cartella Destinazione corrente come --images."
|
||||
<TextBlock Text="Esegue face_encoder.exe usando la cartella Destinazione corrente come --images e un file .pkl come --out."
|
||||
TextWrapping="Wrap" Opacity="0.8" />
|
||||
|
||||
<TextBlock Text="Eseguibile" FontWeight="Bold" Margin="0,6,0,0" />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="8">
|
||||
<TextBlock Text="Eseguibile" FontWeight="Bold" Margin="0,4,0,0" />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="face_encoder:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Name="FaceExecutablePathTextBox" Text="{Binding FaceExecutablePath, Mode=TwoWay}" Watermark="C:\\tools\\face_encoder.exe" />
|
||||
<Button Grid.Column="2" Name="FaceSelectExecutableButton" Content="Scegli..." Click="SelectFaceExecutable_Click" Width="88" />
|
||||
<Button Grid.Column="3" Name="FaceOpenExecutableButton" Content="Apri" Click="OpenFaceExecutableFolder_Click" Width="56" />
|
||||
<Button Grid.Column="2" Name="FaceSelectExecutableButton" Click="SelectFaceExecutable_Click" Width="104" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FileOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Column="3" Name="FaceOpenExecutableButton" Click="OpenFaceExecutableFolder_Click" Width="72" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Output encodings" FontWeight="Bold" Margin="0,6,0,0" />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="8">
|
||||
<TextBlock Grid.Column="0" Text="Cartella out:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Name="FaceOutputFolderTextBox" Text="{Binding FaceOutputFolderPath, Mode=TwoWay}" Watermark="C:\\output\\encodings" />
|
||||
<Button Grid.Column="2" Name="FaceSelectOutputButton" Content="Scegli..." Click="SelectFaceOutputFolder_Click" Width="88" />
|
||||
<Button Grid.Column="3" Name="FaceOpenOutputButton" Content="Apri" Click="OpenFaceOutputFolder_Click" Width="56" />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="Sorgente:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Name="FaceDestinationPathTextBox" Text="{Binding DestinationPath, Mode=OneWay}" IsReadOnly="True" />
|
||||
<Button Grid.Column="3" Name="FaceOpenDestinationButton" Click="OpenFaceDestinationFolder_Click" Width="72" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,8,0,0">
|
||||
<TextBlock Text="Output encodings" FontWeight="Bold" Margin="0,4,0,0" />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="File out (.pkl):" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Name="FaceOutputFolderTextBox" Text="{Binding FaceOutputFolderPath, Mode=TwoWay}" Watermark="C:\\output\\encodings.pkl" />
|
||||
<Button Grid.Column="2" Name="FaceSelectOutputButton" Click="SelectFaceOutputFile_Click" Width="104" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FileOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Column="3" Name="FaceOpenOutputButton" Click="OpenFaceOutputFolder_Click" Width="72" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="6" Margin="0,6,0,0">
|
||||
<Button Name="FaceRunButton" Content="Esegui Face Encoder" Click="RunFaceEncoder_Click" />
|
||||
<TextBlock Name="FaceStatusTextBlock" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Output comando" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<TextBlock Text="Output comando" FontWeight="Bold" Margin="0,6,0,0" />
|
||||
<TextBox Name="FaceOutputTextBox"
|
||||
IsReadOnly="True"
|
||||
AcceptsReturn="True"
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
|||
}
|
||||
}
|
||||
|
||||
private async void SelectFaceOutputFolder_Click(object? sender, RoutedEventArgs e)
|
||||
private async void SelectFaceOutputFile_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var outputBox = this.FindControl<Avalonia.Controls.TextBox>("FaceOutputFolderTextBox");
|
||||
if (outputBox is null)
|
||||
|
|
@ -72,14 +72,21 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
|||
return;
|
||||
}
|
||||
|
||||
var folders = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
|
||||
var files = await storageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
|
||||
{
|
||||
Title = "Seleziona cartella output encodings"
|
||||
Title = "Seleziona file output encodings (.pkl)",
|
||||
SuggestedFileName = "encodings.pkl",
|
||||
DefaultExtension = "pkl",
|
||||
FileTypeChoices =
|
||||
[
|
||||
new FilePickerFileType("Pickle file") { Patterns = ["*.pkl"] }
|
||||
],
|
||||
ShowOverwritePrompt = true
|
||||
});
|
||||
|
||||
if (folders.Count > 0)
|
||||
if (files is not null)
|
||||
{
|
||||
outputBox.Text = folders[0].Path.LocalPath;
|
||||
outputBox.Text = files.Path.LocalPath;
|
||||
if (DataContext is DataModel model)
|
||||
{
|
||||
model.FaceOutputFolderPath = outputBox.Text;
|
||||
|
|
@ -119,7 +126,38 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
|||
return;
|
||||
}
|
||||
|
||||
OpenInExplorer(outputBox.Text);
|
||||
var outputPath = outputBox.Text?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(outputPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (File.Exists(outputPath))
|
||||
{
|
||||
OpenInExplorer(outputPath);
|
||||
return;
|
||||
}
|
||||
|
||||
var directory = Path.GetDirectoryName(outputPath);
|
||||
OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? outputPath : directory);
|
||||
}
|
||||
|
||||
private void OpenFaceDestinationFolder_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var destBox = this.FindControl<Avalonia.Controls.TextBox>("FaceDestinationPathTextBox");
|
||||
string? path = destBox?.Text?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(path) && DataContext is DataModel model)
|
||||
{
|
||||
path = (model.DestinationPath ?? string.Empty).Trim();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var directory = Path.GetDirectoryName(path);
|
||||
OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? path : directory);
|
||||
}
|
||||
|
||||
private async void RunFaceEncoder_Click(object? sender, RoutedEventArgs e)
|
||||
|
|
@ -142,11 +180,11 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
|||
}
|
||||
|
||||
var executablePath = executableBox.Text?.Trim().Trim('"') ?? string.Empty;
|
||||
var outputFolder = outputFolderBox.Text?.Trim().Trim('"') ?? string.Empty;
|
||||
var outputFilePath = outputFolderBox.Text?.Trim().Trim('"') ?? string.Empty;
|
||||
var imagesFolder = (model.DestinationPath ?? string.Empty).Trim().Trim('"');
|
||||
|
||||
model.FaceExecutablePath = executablePath;
|
||||
model.FaceOutputFolderPath = outputFolder;
|
||||
model.FaceOutputFolderPath = outputFilePath;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(executablePath) || !File.Exists(executablePath))
|
||||
{
|
||||
|
|
@ -160,20 +198,30 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
|||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(outputFolder))
|
||||
if (string.IsNullOrWhiteSpace(outputFilePath))
|
||||
{
|
||||
statusBlock.Text = "Inserisci la cartella di output.";
|
||||
statusBlock.Text = "Inserisci il file di output .pkl.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.Equals(Path.GetExtension(outputFilePath), ".pkl", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
statusBlock.Text = "Il file di output deve avere estensione .pkl.";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(outputFolder);
|
||||
var outputDirectory = Path.GetDirectoryName(outputFilePath);
|
||||
if (!string.IsNullOrWhiteSpace(outputDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unable to create face output folder: {OutputFolder}", outputFolder);
|
||||
statusBlock.Text = "Impossibile creare la cartella di output.";
|
||||
_logger.LogError(ex, "Unable to create face output directory for file: {OutputFilePath}", outputFilePath);
|
||||
statusBlock.Text = "Impossibile creare la cartella del file di output.";
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +235,7 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
|||
try
|
||||
{
|
||||
var imagesFolderArg = NormalizeDirectoryPathArgument(imagesFolder);
|
||||
var outputFolderArg = NormalizeDirectoryPathArgument(outputFolder);
|
||||
var outputFileArg = NormalizeFilePathArgument(outputFilePath);
|
||||
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
|
|
@ -201,7 +249,7 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
|||
processStartInfo.ArgumentList.Add("--images");
|
||||
processStartInfo.ArgumentList.Add(imagesFolderArg);
|
||||
processStartInfo.ArgumentList.Add("--out");
|
||||
processStartInfo.ArgumentList.Add(outputFolderArg);
|
||||
processStartInfo.ArgumentList.Add(outputFileArg);
|
||||
|
||||
using var process = new Process { StartInfo = processStartInfo, EnableRaisingEvents = true };
|
||||
process.OutputDataReceived += (_, args) =>
|
||||
|
|
@ -314,4 +362,14 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
|||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static string NormalizeFilePathArgument(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return value.Trim().Trim('"');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,61 +1,92 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:iconPacks="https://github.com/MahApps/IconPacks.Avalonia"
|
||||
x:Class="ImageCatalog_2.AvaloniaViews.GeneralTabView">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<StackPanel Margin="4" Spacing="8">
|
||||
<TextBlock Text="Percorsi" FontWeight="Bold" />
|
||||
<StackPanel Margin="0,6,0,0">
|
||||
<Grid Margin="0,0,0,6" ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<StackPanel Margin="0,2,0,0" Spacing="6">
|
||||
<Grid Margin="0,0,0,2" ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Text="Sorgente:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||
<TextBox Text="{Binding SourcePath, Mode=TwoWay}" Grid.Column="1" VerticalAlignment="Center" />
|
||||
<Button Width="88" Margin="8,0,0,0" Command="{Binding SelectSourceFolderCommand}" Grid.Column="2" Content="Scegli..." />
|
||||
<Button Width="56" Margin="8,0,0,0" Grid.Column="3" Click="OpenSourceFolder_Click" Content="Apri" />
|
||||
<Button Width="104" Command="{Binding SelectSourceFolderCommand}" Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FolderOpenOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="72" Grid.Column="3" Click="OpenSourceFolder_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Text="Destinazione:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||
<TextBox Text="{Binding DestinationPath, Mode=TwoWay}" Grid.Column="1" VerticalAlignment="Center" />
|
||||
<Button Width="88" Margin="8,0,0,0" Command="{Binding SelectDestinationFolderCommand}" Grid.Column="2" Content="Scegli..." />
|
||||
<Button Width="56" Margin="8,0,0,0" Grid.Column="3" Click="OpenDestinationFolder_Click" Content="Apri" />
|
||||
<Button Width="104" Command="{Binding SelectDestinationFolderCommand}" Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FolderOpenOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="72" Grid.Column="3" Click="OpenDestinationFolder_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Opzioni" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Margin="0,6,0,0">
|
||||
<CheckBox Content="Forza JPEG" IsChecked="{Binding ForceJpeg}" />
|
||||
<CheckBox Content="Aggiorna sottodirectory" IsChecked="{Binding UpdateSubdirectories}" />
|
||||
<CheckBox Content="Crea sottocartelle" IsChecked="{Binding CreateSubfolders}" />
|
||||
<CheckBox Content="Sovrascrivi immagini" IsChecked="{Binding OverwriteImages}" />
|
||||
</StackPanel>
|
||||
<Grid ColumnDefinitions="*,*" ColumnSpacing="24" Margin="0,4,0,0">
|
||||
<StackPanel Grid.Column="0" Spacing="8">
|
||||
<TextBlock Text="Opzioni" FontWeight="Bold" />
|
||||
<StackPanel Margin="0,2,0,0" Spacing="3">
|
||||
<CheckBox Content="Forza JPEG" IsChecked="{Binding ForceJpeg}" />
|
||||
<CheckBox Content="Aggiorna sottodirectory" IsChecked="{Binding UpdateSubdirectories}" />
|
||||
<CheckBox Content="Crea sottocartelle" IsChecked="{Binding CreateSubfolders}" />
|
||||
<CheckBox Content="Sovrascrivi immagini" IsChecked="{Binding OverwriteImages}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Elaborazione" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="Threads:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding ThreadsCount, Mode=TwoWay}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Chunk:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding ChunkSize, Mode=TwoWay}" Width="60" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
<TextBlock Text="Elaborazione" FontWeight="Bold" />
|
||||
<Grid ColumnDefinitions="Auto,74,Auto,74" ColumnSpacing="8" RowDefinitions="Auto">
|
||||
<TextBlock Text="Threads:" VerticalAlignment="Center" Grid.Column="0" />
|
||||
<TextBox Text="{Binding ThreadsCount, Mode=TwoWay}" Width="74" Grid.Column="1" />
|
||||
<TextBlock Text="Chunk:" VerticalAlignment="Center" Grid.Column="2" />
|
||||
<TextBox Text="{Binding ChunkSize, Mode=TwoWay}" Width="74" Grid.Column="3" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Divisione cartelle" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="File per cartella:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding FilesPerFolder}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Suffisso:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding FolderSuffix}" Width="120" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
<TextBlock Text="Libreria Immagini" FontWeight="Bold" />
|
||||
<StackPanel Margin="0,2,0,0" Spacing="3">
|
||||
<RadioButton Content="System.Graphics" IsChecked="{Binding UseSystemGraphics}" GroupName="Lib" IsVisible="{Binding IsRunningOnWindows}" />
|
||||
<RadioButton Content="ImageSharp" IsChecked="{Binding UseImageSharp}" GroupName="Lib" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Numerazione" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<RadioButton Content="Progressiva" IsChecked="{Binding UseProgressiveNumbering}" GroupName="Num" />
|
||||
<RadioButton Content="Per file" IsChecked="{Binding UseFileNumbering}" GroupName="Num" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Cifre:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding CounterDigits}" Width="40" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Spacing="8">
|
||||
<TextBlock Text="Divisione cartelle" FontWeight="Bold" />
|
||||
<Grid ColumnDefinitions="Auto,78,Auto,*" ColumnSpacing="8" RowDefinitions="Auto">
|
||||
<TextBlock Text="File per cartella:" VerticalAlignment="Center" Grid.Column="0" />
|
||||
<TextBox Text="{Binding FilesPerFolder}" Width="78" Grid.Column="1" />
|
||||
<TextBlock Text="Suffisso:" VerticalAlignment="Center" Grid.Column="2" />
|
||||
<TextBox Text="{Binding FolderSuffix}" MinWidth="120" Grid.Column="3" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Libreria Immagini" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<RadioButton Content="System.Graphics" IsChecked="{Binding UseSystemGraphics}" GroupName="Lib" IsVisible="{Binding IsRunningOnWindows}" />
|
||||
<RadioButton Content="ImageSharp" IsChecked="{Binding UseImageSharp}" GroupName="Lib" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
<TextBlock Text="Numerazione" FontWeight="Bold" />
|
||||
<StackPanel Margin="0,2,0,0" Spacing="6">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<RadioButton Content="Progressiva" IsChecked="{Binding UseProgressiveNumbering}" GroupName="Num" />
|
||||
<RadioButton Content="Per file" IsChecked="{Binding UseFileNumbering}" GroupName="Num" />
|
||||
</StackPanel>
|
||||
<Grid ColumnDefinitions="Auto,64,*" ColumnSpacing="8">
|
||||
<TextBlock Text="Cifre:" VerticalAlignment="Center" Grid.Column="0" />
|
||||
<TextBox Text="{Binding CounterDigits}" Width="64" Grid.Column="1" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
|
|
|||
|
|
@ -2,37 +2,37 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="ImageCatalog_2.AvaloniaViews.LogoTabView">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<StackPanel Margin="4">
|
||||
<TextBlock Text="Logo" FontWeight="Bold" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4,0,0">
|
||||
<Button Command="{Binding SelectLogoFileCommand}" Content="Seleziona logo" />
|
||||
<TextBlock Text="{Binding LogoFile}" Margin="8,0,0,0" VerticalAlignment="Center"
|
||||
Width="250" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,8,0,0">
|
||||
<StackPanel Margin="0,6,0,0">
|
||||
<Image Name="LogoPreview" Width="160" Height="160" Stretch="Uniform" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBox Text="{Binding LogoWidth}" Width="80" />
|
||||
<TextBox Text="{Binding LogoHeight}" Width="80" Margin="8,0,0,0" />
|
||||
<TextBox Text="{Binding LogoHeight}" Width="80" Margin="6,0,0,0" />
|
||||
</StackPanel>
|
||||
<CheckBox Content="Aggiungi logo" IsChecked="{Binding AddLogo}" Margin="0,8,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||
<CheckBox Content="Aggiungi logo" IsChecked="{Binding AddLogo}" Margin="0,6,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="Margine:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding LogoMargin}" Width="80" Margin="8,0,0,0" />
|
||||
<TextBox Text="{Binding LogoMargin}" Width="80" Margin="6,0,0,0" />
|
||||
<TextBlock Text="Trasparenza:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding LogoTransparency}" Width="60" Margin="8,0,0,0" />
|
||||
<Button Command="{Binding SelectTransparentColorCommand}" Margin="8,0,0,0"
|
||||
<TextBox Text="{Binding LogoTransparency}" Width="60" Margin="6,0,0,0" />
|
||||
<Button Command="{Binding SelectTransparentColorCommand}" Margin="6,0,0,0"
|
||||
Content="Seleziona trasparente" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="Posizione:" VerticalAlignment="Center" />
|
||||
<ComboBox ItemsSource="{Binding HorizontalAlignments}"
|
||||
SelectedItem="{Binding LogoHorizontalPosition}"
|
||||
Width="120" Margin="8,0,0,0" />
|
||||
Width="120" Margin="6,0,0,0" />
|
||||
<ComboBox ItemsSource="{Binding VerticalPositions}"
|
||||
SelectedItem="{Binding LogoVerticalPosition}"
|
||||
Width="120" Margin="8,0,0,0" />
|
||||
Width="120" Margin="6,0,0,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
|
|
|||
|
|
@ -2,25 +2,25 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="ImageCatalog_2.AvaloniaViews.PhotoTabView">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<StackPanel Margin="4">
|
||||
<TextBlock Text="Dimensioni foto grandi" FontWeight="Bold" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4,0,0">
|
||||
<TextBox Text="{Binding PhotoBigWidth}" Width="80" />
|
||||
<TextBox Text="{Binding PhotoBigHeight}" Width="80" Margin="8,0,0,0" />
|
||||
<TextBox Text="{Binding PhotoBigHeight}" Width="80" Margin="6,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Opzioni foto" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<StackPanel Margin="0,6,0,0">
|
||||
<TextBlock Text="Opzioni foto" FontWeight="Bold" Margin="0,6,0,0" />
|
||||
<StackPanel Margin="0,4,0,0">
|
||||
<CheckBox Content="Mantieni dimensioni originali" IsChecked="{Binding KeepOriginalDimensions}" />
|
||||
<CheckBox Content="Rotazione automatica" IsChecked="{Binding AutomaticRotation}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="JPEG" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="JPEG" FontWeight="Bold" Margin="0,6,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4,0,0">
|
||||
<TextBlock Text="Qualita:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding JpegQuality}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBox Text="{Binding JpegQuality}" Width="60" Margin="6,0,0,0" />
|
||||
<TextBlock Text="Miniature Qualita:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding JpegQualityThumbnail}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBox Text="{Binding JpegQualityThumbnail}" Width="60" Margin="6,0,0,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="ImageCatalog_2.AvaloniaViews.RaceUploadTabView">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8" Spacing="8">
|
||||
<StackPanel Margin="4" Spacing="6">
|
||||
<TextBlock Text="Setup gara e upload foto processate" FontWeight="Bold" />
|
||||
<TextBlock Text="Flusso: login admin, creazione gara, creazione punti foto, upload file processati da cartella destinazione locale, indicizzazione punti foto."
|
||||
TextWrapping="Wrap" Opacity="0.8" />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto" ColumnSpacing="8" RowSpacing="8">
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto" ColumnSpacing="6" RowSpacing="6">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Login:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Row="0" Grid.Column="1" Name="ApiLoginTextBox" Text="{Binding ApiLogin, Mode=TwoWay}" Watermark="admin user" />
|
||||
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
</Grid>
|
||||
|
||||
<TextBlock Text="Dati gara" FontWeight="Bold" Margin="0,4,0,0" />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,*" RowDefinitions="Auto,Auto,Auto,Auto" ColumnSpacing="8" RowSpacing="8">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,*" RowDefinitions="Auto,Auto,Auto,Auto" ColumnSpacing="6" RowSpacing="6">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Descrizione:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Row="0" Grid.Column="1" Name="ApiRaceDescriptionTextBox" Text="{Binding ApiRaceDescription, Mode=TwoWay}" Watermark="Nome gara" />
|
||||
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
</ComboBox>
|
||||
</Grid>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,*" RowDefinitions="Auto,Auto" ColumnSpacing="8" RowSpacing="8">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,*" RowDefinitions="Auto,Auto" ColumnSpacing="6" RowSpacing="6">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Evento Omaggio:" VerticalAlignment="Center" />
|
||||
<ComboBox Grid.Row="0" Grid.Column="1" Name="ApiFreeEventComboBox" SelectedIndex="{Binding ApiFreeEventIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem Content="0 - No" />
|
||||
|
|
@ -65,12 +65,11 @@
|
|||
Watermark="/percorso/remoto/foto-ridotte" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Name="ApiCreateRaceButton" Content="Crea nuova gara" Click="CreateRace_Click" />
|
||||
<Button Name="ApiUploadButton" Content="Upload foto processate" Click="UploadProcessed_Click" />
|
||||
<TextBlock Name="ApiStatusTextBlock" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Output" FontWeight="Bold" Margin="0,4,0,0" />
|
||||
<TextBox Name="ApiOutputTextBox"
|
||||
IsReadOnly="True"
|
||||
|
|
|
|||
|
|
@ -17,18 +17,21 @@ namespace ImageCatalog_2.AvaloniaViews;
|
|||
|
||||
public partial class RaceUploadTabView : Avalonia.Controls.UserControl
|
||||
{
|
||||
private readonly IRaceUploadCommunicationClient _apiClient;
|
||||
private readonly ILogger<RaceUploadTabView> _logger;
|
||||
|
||||
public RaceUploadTabView()
|
||||
{
|
||||
InitializeComponent();
|
||||
_apiClient = Program.ServiceProvider.GetService(typeof(IRaceUploadCommunicationClient)) as IRaceUploadCommunicationClient
|
||||
?? throw new InvalidOperationException("IRaceUploadCommunicationClient non disponibile.");
|
||||
_logger = Program.ServiceProvider.GetService(typeof(ILogger<RaceUploadTabView>)) as ILogger<RaceUploadTabView>
|
||||
?? NullLogger<RaceUploadTabView>.Instance;
|
||||
}
|
||||
|
||||
private static IRaceUploadCommunicationClient CreateClient()
|
||||
{
|
||||
return Program.ServiceProvider.GetService(typeof(IRaceUploadCommunicationClient)) as IRaceUploadCommunicationClient
|
||||
?? throw new InvalidOperationException("IRaceUploadCommunicationClient non disponibile.");
|
||||
}
|
||||
|
||||
private async void CreateRace_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var outputBox = this.FindControl<Avalonia.Controls.TextBox>("ApiOutputTextBox");
|
||||
|
|
@ -76,32 +79,43 @@ public partial class RaceUploadTabView : Avalonia.Controls.UserControl
|
|||
|
||||
var loginResponse = await LoginAsync(login, password).ConfigureAwait(true);
|
||||
|
||||
var saveResponse = await _apiClient.SaveRaceAsync(
|
||||
new RaceSaveRequest
|
||||
{
|
||||
IdGara = 0,
|
||||
Description = sanitizedDescription,
|
||||
StartDate = startDate,
|
||||
EndDate = endDate,
|
||||
TipoGaraId = tipoGaraId,
|
||||
EventoInLinea = model.ApiEventoInLineaIndex,
|
||||
TipoIndicizzazione = model.ApiTipoIndexValue,
|
||||
FreeEvent = model.ApiFreeEventIndex,
|
||||
PathBase = model.ApiPathBase?.Trim(),
|
||||
Localita = model.ApiLocalita?.Trim(),
|
||||
},
|
||||
CancellationToken.None);
|
||||
RawEndpointResponse saveResponse;
|
||||
RawEndpointResponse createPointsResponse;
|
||||
long raceId;
|
||||
|
||||
var raceId = ExtractRaceId(saveResponse.Body);
|
||||
if (raceId <= 0)
|
||||
var client = CreateClient();
|
||||
try
|
||||
{
|
||||
throw new InvalidOperationException("Impossibile ricavare id_gara dalla risposta di salvataggio.");
|
||||
saveResponse = await client.SaveRaceAsync(
|
||||
new RaceSaveRequest
|
||||
{
|
||||
IdGara = 0,
|
||||
Description = sanitizedDescription,
|
||||
StartDate = startDate,
|
||||
EndDate = endDate,
|
||||
TipoGaraId = tipoGaraId,
|
||||
EventoInLinea = model.ApiEventoInLineaIndex,
|
||||
TipoIndicizzazione = model.ApiTipoIndexValue,
|
||||
FreeEvent = model.ApiFreeEventIndex,
|
||||
PathBase = model.ApiPathBase?.Trim(),
|
||||
Localita = model.ApiLocalita?.Trim(),
|
||||
},
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
raceId = ExtractRaceId(saveResponse.Body);
|
||||
if (raceId <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("Impossibile ricavare id_gara dalla risposta di salvataggio.");
|
||||
}
|
||||
|
||||
model.ApiRaceId = raceId.ToString();
|
||||
|
||||
createPointsResponse = await client.CreateRacePointsAsync(raceId, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
(client as IDisposable)?.Dispose();
|
||||
}
|
||||
|
||||
model.ApiRaceId = raceId.ToString();
|
||||
|
||||
var createPointsResponse = await _apiClient.CreateRacePointsAsync(raceId, CancellationToken.None);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"Login HTTP: {(int)loginResponse.StatusCode} {loginResponse.StatusCode}");
|
||||
sb.AppendLine($"Save Gara HTTP: {(int)saveResponse.StatusCode} {saveResponse.StatusCode}");
|
||||
|
|
@ -197,41 +211,50 @@ public partial class RaceUploadTabView : Avalonia.Controls.UserControl
|
|||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"File da inviare: {files.Count}");
|
||||
|
||||
foreach (var file in files)
|
||||
List<long> pointIds;
|
||||
|
||||
var client = CreateClient();
|
||||
try
|
||||
{
|
||||
var relativePath = Path.GetRelativePath(model.DestinationPath, file);
|
||||
var relativeDir = Path.GetDirectoryName(relativePath) ?? string.Empty;
|
||||
var remotePath = CombineRemotePath(remoteProcessedBase, racePathBase, relativeDir);
|
||||
|
||||
await using var stream = File.OpenRead(file);
|
||||
await _apiClient.UploadFileToReceiverAsync(
|
||||
new ReceiveFileUploadRequest
|
||||
{
|
||||
FileName = Path.GetFileName(file),
|
||||
FileStream = stream,
|
||||
DestinationPath = remotePath,
|
||||
OverwriteRemoteFile = true,
|
||||
},
|
||||
CancellationToken.None).ConfigureAwait(true);
|
||||
|
||||
uploaded++;
|
||||
if (uploaded % 20 == 0 || uploaded == files.Count)
|
||||
foreach (var file in files)
|
||||
{
|
||||
statusBlock.Text = $"Upload foto: {uploaded}/{files.Count}";
|
||||
var relativePath = Path.GetRelativePath(model.DestinationPath, file);
|
||||
var relativeDir = Path.GetDirectoryName(relativePath) ?? string.Empty;
|
||||
var remotePath = CombineRemotePath(remoteProcessedBase, racePathBase, relativeDir);
|
||||
|
||||
await using var stream = File.OpenRead(file);
|
||||
await client.UploadFileToReceiverAsync(
|
||||
new ReceiveFileUploadRequest
|
||||
{
|
||||
FileName = Path.GetFileName(file),
|
||||
FileStream = stream,
|
||||
DestinationPath = remotePath,
|
||||
OverwriteRemoteFile = true,
|
||||
},
|
||||
CancellationToken.None).ConfigureAwait(true);
|
||||
|
||||
uploaded++;
|
||||
if (uploaded % 20 == 0 || uploaded == files.Count)
|
||||
{
|
||||
statusBlock.Text = $"Upload foto: {uploaded}/{files.Count}";
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine($"Upload completato: {uploaded}/{files.Count}");
|
||||
statusBlock.Text = "Creazione punti foto e indicizzazione in corso...";
|
||||
|
||||
await client.CreateRacePointsAsync(raceId, CancellationToken.None).ConfigureAwait(true);
|
||||
pointIds = await LoadPointIdsWithRetryAsync(raceId, CancellationToken.None).ConfigureAwait(true);
|
||||
|
||||
foreach (var pointId in pointIds)
|
||||
{
|
||||
await client.IndexRacePointAsync(pointId, CancellationToken.None).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine($"Upload completato: {uploaded}/{files.Count}");
|
||||
statusBlock.Text = "Creazione punti foto e indicizzazione in corso...";
|
||||
|
||||
await _apiClient.CreateRacePointsAsync(raceId, CancellationToken.None).ConfigureAwait(true);
|
||||
var pointIds = await LoadPointIdsWithRetryAsync(raceId, CancellationToken.None).ConfigureAwait(true);
|
||||
|
||||
foreach (var pointId in pointIds)
|
||||
finally
|
||||
{
|
||||
await _apiClient.IndexRacePointAsync(pointId, CancellationToken.None).ConfigureAwait(true);
|
||||
(client as IDisposable)?.Dispose();
|
||||
}
|
||||
|
||||
sb.AppendLine($"Punti foto indicizzati: {pointIds.Count}");
|
||||
outputBox.Text = sb.ToString();
|
||||
statusBlock.Text = "Upload e indicizzazione completati.";
|
||||
|
|
@ -251,31 +274,49 @@ public partial class RaceUploadTabView : Avalonia.Controls.UserControl
|
|||
|
||||
private async Task<RawEndpointResponse> LoginAsync(string login, string password)
|
||||
{
|
||||
return await _apiClient.LoginAdminAsync(
|
||||
new AdminLoginRequest
|
||||
{
|
||||
Login = login,
|
||||
Password = password,
|
||||
Command = "check",
|
||||
},
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
var client = CreateClient();
|
||||
try
|
||||
{
|
||||
return await client.LoginAdminAsync(
|
||||
new AdminLoginRequest
|
||||
{
|
||||
Login = login,
|
||||
Password = password,
|
||||
Command = "check",
|
||||
},
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
(client as IDisposable)?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<long>> LoadPointIdsWithRetryAsync(long raceId, CancellationToken cancellationToken)
|
||||
{
|
||||
const int maxAttempts = 10;
|
||||
|
||||
for (var attempt = 1; attempt <= maxAttempts; attempt++)
|
||||
var client = CreateClient();
|
||||
try
|
||||
{
|
||||
var response = await _apiClient.GetRaceDetailAsync(raceId, cancellationToken).ConfigureAwait(false);
|
||||
var ids = ExtractPointIds(response.Body);
|
||||
|
||||
if (ids.Count > 0)
|
||||
for (var attempt = 1; attempt <= maxAttempts; attempt++)
|
||||
{
|
||||
return ids;
|
||||
var response = await client.GetRaceDetailAsync(raceId, cancellationToken).ConfigureAwait(false);
|
||||
var ids = ExtractPointIds(response.Body);
|
||||
|
||||
if (ids.Count > 0)
|
||||
{
|
||||
return ids;
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken).ConfigureAwait(false);
|
||||
return new List<long>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
(client as IDisposable)?.Dispose();
|
||||
}
|
||||
|
||||
return [];
|
||||
|
|
|
|||
|
|
@ -2,52 +2,52 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="ImageCatalog_2.AvaloniaViews.TextTabView">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<StackPanel Margin="4">
|
||||
<TextBlock Text="Testo Orizzontale" FontWeight="Bold" />
|
||||
<TextBox Text="{Binding HorizontalText, Mode=TwoWay}" />
|
||||
|
||||
<TextBlock Text="Testo Verticale" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<TextBlock Text="Testo Verticale" FontWeight="Bold" Margin="0,6,0,0" />
|
||||
<TextBox Text="{Binding VerticalText, Mode=TwoWay}" AcceptsReturn="True"
|
||||
TextWrapping="Wrap" MinHeight="80" />
|
||||
|
||||
<TextBlock Text="Font" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<TextBlock Text="Font" FontWeight="Bold" Margin="0,6,0,0" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ComboBox ItemsSource="{Binding AvailableFonts}" SelectedItem="{Binding FontName}" Width="250" />
|
||||
<TextBox Text="{Binding FontSize}" Width="60" Margin="8,0,0,0" />
|
||||
<CheckBox Content="Grassetto" IsChecked="{Binding FontBold}" Margin="8,0,0,0" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding FontSize}" Width="60" Margin="6,0,0,0" />
|
||||
<CheckBox Content="Grassetto" IsChecked="{Binding FontBold}" Margin="6,0,0,0" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Colore testo (hex)" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<TextBlock Text="Colore testo (hex)" FontWeight="Bold" Margin="0,6,0,0" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding TextColorRGB}" Width="120" />
|
||||
<Button Content="Seleziona colore" Command="{Binding SelectColorCommand}" Margin="8,0,0,0" />
|
||||
<Button Content="Seleziona colore" Command="{Binding SelectColorCommand}" Margin="6,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Dimensioni verticale" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<TextBlock Text="Dimensioni verticale" FontWeight="Bold" Margin="0,6,0,0" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Size:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding VerticalTextSize}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBox Text="{Binding VerticalTextSize}" Width="60" Margin="6,0,0,0" />
|
||||
<TextBlock Text="Margin:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding VerticalTextMargin}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBox Text="{Binding VerticalTextMargin}" Width="60" Margin="6,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4,0,0">
|
||||
<TextBlock Text="Trasparenza testo:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding TextTransparency}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBox Text="{Binding TextTransparency}" Width="60" Margin="6,0,0,0" />
|
||||
<TextBlock Text="Margine testo:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding TextMargin}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBox Text="{Binding TextMargin}" Width="60" Margin="6,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Tempo Gara" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="Tempo Gara" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4,0,0">
|
||||
<CheckBox Content="Aggiungi Orario" IsChecked="{Binding AddTime}" />
|
||||
<CheckBox Content="Aggiungi tempo gara" IsChecked="{Binding AddRaceTime}" Margin="12,0,0,0" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4,0,0">
|
||||
<TextBlock Text="Partenza:" VerticalAlignment="Center" />
|
||||
<CalendarDatePicker SelectedDate="{Binding RaceStartDate}"
|
||||
IsEnabled="{Binding AddRaceTime}"
|
||||
Margin="8,0,0,0" Width="200" />
|
||||
Margin="6,0,0,0" Width="200" />
|
||||
<TextBox Text="{Binding TimeLabel}" Width="220" Margin="12,0,0,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
|
|
|||
|
|
@ -2,21 +2,21 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="ImageCatalog_2.AvaloniaViews.ThumbnailsTabView">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<StackPanel Margin="4">
|
||||
<TextBlock Text="Miniature" FontWeight="Bold" />
|
||||
<CheckBox Content="Crea miniature" IsChecked="{Binding CreateThumbnails}" Margin="0,6,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<CheckBox Content="Crea miniature" IsChecked="{Binding CreateThumbnails}" Margin="0,4,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4,0,0">
|
||||
<TextBlock Text="Prefisso:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding ThumbnailPrefix}" Width="120" Margin="8,0,0,0" />
|
||||
<TextBox Text="{Binding ThumbnailPrefix}" Width="120" Margin="6,0,0,0" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4,0,0">
|
||||
<TextBox Text="{Binding ThumbnailWidth}" Width="80" />
|
||||
<TextBox Text="{Binding ThumbnailHeight}" Width="80" Margin="8,0,0,0" />
|
||||
<TextBox Text="{Binding ThumbnailHeight}" Width="80" Margin="6,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0,8,0,0">
|
||||
<StackPanel Margin="0,6,0,0">
|
||||
<TextBlock Text="Modalita miniature:" VerticalAlignment="Center" />
|
||||
<ComboBox SelectedIndex="{Binding ThumbnailOptionIndex, Mode=TwoWay}" Width="220" Margin="0,6,0,0">
|
||||
<ComboBox SelectedIndex="{Binding ThumbnailOptionIndex, Mode=TwoWay}" Width="220" Margin="0,4,0,0">
|
||||
<ComboBoxItem>Nessuna</ComboBoxItem>
|
||||
<ComboBoxItem>Aggiungi scritta</ComboBoxItem>
|
||||
<ComboBoxItem>Nome file</ComboBoxItem>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,59 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ProduceReferenceAssembly>False</ProduceReferenceAssembly>
|
||||
<AssemblyVersion>3.0.0.0</AssemblyVersion>
|
||||
<FileVersion>3.0.0.0</FileVersion>
|
||||
<InformationalVersion>3.0.0-alpha.63+Branch.develop.Sha.39a9baf5c618d8d79c75b89e2d5c4020939697f2</InformationalVersion>
|
||||
<Version>3.0.0-alpha0063</Version>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<UpdateVersionProperties>true</UpdateVersionProperties>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Update="My Project\Application.myapp">
|
||||
<Generator>MyApplicationCodeGenerator</Generator>
|
||||
<LastGenOutput>Application.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
<None Update="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Sorgenti\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CatalogVbLib\CatalogVbLib.vbproj" />
|
||||
<ProjectReference Include="..\MaddoShared\MaddoShared.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.VisualBasic" Version="10.3.0" />
|
||||
<PackageReference Include="Microsoft.WindowsAPICodePack-Core" Version="1.1.0.2" />
|
||||
<PackageReference Include="Microsoft.WindowsAPICodePack-Shell" Version="1.1.0.0" />
|
||||
<PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.421302">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="GitVersion.MsBuild" Version="6.0.2">
|
||||
<PrivateAssets>All</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Settings.Designer.cs">
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<PropertyGroup />
|
||||
</Project>
|
||||
|
|
@ -25,6 +25,8 @@
|
|||
This prevents MinVer from injecting a computed version into generated BAML/pack URIs which can cause
|
||||
WPF to try loading a mismatched assembly identity at runtime. Do a full clean rebuild after this change. -->
|
||||
<UpdateVersionProperties>true</UpdateVersionProperties>
|
||||
<!-- Skip MinVer execution during local builds to avoid environment/runtime-specific failures. -->
|
||||
<MinVerSkip>true</MinVerSkip>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugType>embedded</DebugType>
|
||||
|
|
@ -51,17 +53,18 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AIFotoONLUS.Core" Version="0.1.1" />
|
||||
<PackageReference Include="AutoMapper" Version="16.0.0" />
|
||||
<PackageReference Include="AutoMapper" Version="16.1.0" />
|
||||
<PackageReference Include="IconPacks.Avalonia" Version="1.3.1" />
|
||||
<PackageReference Include="MahApps.Metro" Version="2.4.11" Condition="$([MSBuild]::IsOsPlatform('Windows'))" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.5" />
|
||||
<PackageReference Include="MahApps.Metro.IconPacks" Version="6.2.1" Condition="$([MSBuild]::IsOsPlatform('Windows'))" />
|
||||
<PackageReference Include="MinVer" Version="7.0.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Avalonia" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia" Version="11.3.12" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.12" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.12" />
|
||||
<PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.421302">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
|
|
|||
|
|
@ -1,347 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ProjectType>Local</ProjectType>
|
||||
<ProductVersion>9.0.30729</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{8D3AA2B0-8F06-4A61-9CAD-B920EB1A8E9C}</ProjectGuid>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ApplicationIcon>
|
||||
</ApplicationIcon>
|
||||
<AssemblyKeyContainerName>
|
||||
</AssemblyKeyContainerName>
|
||||
<AssemblyName>ImageCatalog</AssemblyName>
|
||||
<AssemblyOriginatorKeyFile>
|
||||
</AssemblyOriginatorKeyFile>
|
||||
<AssemblyOriginatorKeyMode>None</AssemblyOriginatorKeyMode>
|
||||
<DefaultClientScript>JScript</DefaultClientScript>
|
||||
<DefaultHTMLPageLayout>Grid</DefaultHTMLPageLayout>
|
||||
<DefaultTargetSchema>IE50</DefaultTargetSchema>
|
||||
<DelaySign>false</DelaySign>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<OptionCompare>Binary</OptionCompare>
|
||||
<OptionExplicit>On</OptionExplicit>
|
||||
<OptionStrict>On</OptionStrict>
|
||||
<RootNamespace>ImageCatalog</RootNamespace>
|
||||
<StartupObject>ImageCatalog.My.MyApplication</StartupObject>
|
||||
<FileUpgradeFlags>
|
||||
</FileUpgradeFlags>
|
||||
<MyType>WindowsForms</MyType>
|
||||
<OldToolsVersion>3.5</OldToolsVersion>
|
||||
<UpgradeBackupLocation>
|
||||
</UpgradeBackupLocation>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<IsWebBootstrapper>true</IsWebBootstrapper>
|
||||
<ApplicationManifest>My Project\app.manifest</ApplicationManifest>
|
||||
<TargetFrameworkProfile>
|
||||
</TargetFrameworkProfile>
|
||||
<SccProjectName>SAK</SccProjectName>
|
||||
<SccLocalPath>SAK</SccLocalPath>
|
||||
<SccAuxPath>SAK</SccAuxPath>
|
||||
<SccProvider>SAK</SccProvider>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<PublishUrl>http://localhost/ImageCatalog/</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Web</InstallFrom>
|
||||
<UpdateEnabled>true</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateInterval>7</UpdateInterval>
|
||||
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||
<UpdatePeriodically>false</UpdatePeriodically>
|
||||
<UpdateRequired>false</UpdateRequired>
|
||||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>1.8.0.%2a</ApplicationVersion>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<DocumentationFile>ImageCatalog.xml</DocumentationFile>
|
||||
<BaseAddress>285212672</BaseAddress>
|
||||
<ConfigurationOverrideFile>
|
||||
</ConfigurationOverrideFile>
|
||||
<DefineConstants>
|
||||
</DefineConstants>
|
||||
<DefineDebug>true</DefineDebug>
|
||||
<DefineTrace>true</DefineTrace>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<Optimize>false</Optimize>
|
||||
<RegisterForComInterop>false</RegisterForComInterop>
|
||||
<RemoveIntegerChecks>false</RemoveIntegerChecks>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<WarningLevel>1</WarningLevel>
|
||||
<NoWarn>42016,42017,42018,42019,42032,42353,42354,42355</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<DocumentationFile>ImageCatalog.xml</DocumentationFile>
|
||||
<BaseAddress>285212672</BaseAddress>
|
||||
<ConfigurationOverrideFile>
|
||||
</ConfigurationOverrideFile>
|
||||
<DefineConstants>
|
||||
</DefineConstants>
|
||||
<DefineDebug>false</DefineDebug>
|
||||
<DefineTrace>true</DefineTrace>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<Optimize>true</Optimize>
|
||||
<RegisterForComInterop>false</RegisterForComInterop>
|
||||
<RemoveIntegerChecks>false</RemoveIntegerChecks>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<WarningLevel>1</WarningLevel>
|
||||
<NoWarn>42016,42017,42018,42019,42032,42353,42354,42355</NoWarn>
|
||||
<DebugType>none</DebugType>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DefineDebug>true</DefineDebug>
|
||||
<DefineTrace>true</DefineTrace>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<BaseAddress>285212672</BaseAddress>
|
||||
<DocumentationFile>ImageCatalog.xml</DocumentationFile>
|
||||
<WarningLevel>1</WarningLevel>
|
||||
<NoWarn>42016,42017,42018,42019,42032,42353,42354,42355</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
|
||||
<DefineTrace>true</DefineTrace>
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<BaseAddress>285212672</BaseAddress>
|
||||
<DocumentationFile>ImageCatalog.xml</DocumentationFile>
|
||||
<Optimize>true</Optimize>
|
||||
<WarningLevel>1</WarningLevel>
|
||||
<NoWarn>42016,42017,42018,42019,42032,42353,42354,42355</NoWarn>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DefineDebug>true</DefineDebug>
|
||||
<DefineTrace>true</DefineTrace>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<BaseAddress>285212672</BaseAddress>
|
||||
<DocumentationFile>ImageCatalog.xml</DocumentationFile>
|
||||
<WarningLevel>1</WarningLevel>
|
||||
<NoWarn>42016,42017,42018,42019,42032,42353,42354,42355</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<CodeAnalysisLogFile>bin\ImageCatalog.exe.CodeAnalysisLog.xml</CodeAnalysisLogFile>
|
||||
<CodeAnalysisUseTypeNameInSuppression>true</CodeAnalysisUseTypeNameInSuppression>
|
||||
<CodeAnalysisModuleSuppressionsFile>GlobalSuppressions.vb</CodeAnalysisModuleSuppressionsFile>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<CodeAnalysisRuleSetDirectories>;F:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets</CodeAnalysisRuleSetDirectories>
|
||||
<CodeAnalysisIgnoreBuiltInRuleSets>true</CodeAnalysisIgnoreBuiltInRuleSets>
|
||||
<CodeAnalysisRuleDirectories>;F:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules</CodeAnalysisRuleDirectories>
|
||||
<CodeAnalysisIgnoreBuiltInRules>true</CodeAnalysisIgnoreBuiltInRules>
|
||||
<CodeAnalysisFailOnMissingRules>false</CodeAnalysisFailOnMissingRules>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<DefineTrace>true</DefineTrace>
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<BaseAddress>285212672</BaseAddress>
|
||||
<DocumentationFile>ImageCatalog.xml</DocumentationFile>
|
||||
<Optimize>true</Optimize>
|
||||
<WarningLevel>1</WarningLevel>
|
||||
<NoWarn>42016,42017,42018,42019,42032,42353,42354,42355</NoWarn>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<CodeAnalysisLogFile>bin\ImageCatalog.exe.CodeAnalysisLog.xml</CodeAnalysisLogFile>
|
||||
<CodeAnalysisUseTypeNameInSuppression>true</CodeAnalysisUseTypeNameInSuppression>
|
||||
<CodeAnalysisModuleSuppressionsFile>GlobalSuppressions.vb</CodeAnalysisModuleSuppressionsFile>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<CodeAnalysisRuleSetDirectories>;F:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets</CodeAnalysisRuleSetDirectories>
|
||||
<CodeAnalysisIgnoreBuiltInRuleSets>true</CodeAnalysisIgnoreBuiltInRuleSets>
|
||||
<CodeAnalysisRuleDirectories>;F:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules</CodeAnalysisRuleDirectories>
|
||||
<CodeAnalysisIgnoreBuiltInRules>true</CodeAnalysisIgnoreBuiltInRules>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Ben.Demystifier, Version=0.3.0.0, Culture=neutral, PublicKeyToken=a6d206e05440431a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Ben.Demystifier.0.3.0\lib\net45\Ben.Demystifier.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualBasic.PowerPacks.Vs, Version=10.0.0.0" />
|
||||
<Reference Include="System">
|
||||
<Name>System</Name>
|
||||
</Reference>
|
||||
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Collections.Immutable, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Collections.Immutable.5.0.0\lib\net461\System.Collections.Immutable.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Core">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Data">
|
||||
<Name>System.Data</Name>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Drawing">
|
||||
<Name>System.Drawing</Name>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Reflection.Metadata, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Reflection.Metadata.5.0.0\lib\net461\System.Reflection.Metadata.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Windows.Forms">
|
||||
<Name>System.Windows.Forms</Name>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml">
|
||||
<Name>System.XML</Name>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Import Include="Microsoft.VisualBasic" />
|
||||
<Import Include="System" />
|
||||
<Import Include="System.Collections" />
|
||||
<Import Include="System.Data" />
|
||||
<Import Include="System.Diagnostics" />
|
||||
<Import Include="System.Drawing" />
|
||||
<Import Include="System.Linq" />
|
||||
<Import Include="System.Windows.Forms" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AssemblyInfo.vb">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="FileHelper.vb" />
|
||||
<Compile Include="CreaImmagineSeparateMultiCore.vb" />
|
||||
<Compile Include="CreaImmagineSeparateThread.vb" />
|
||||
<Compile Include="ExifReader.vb">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Form1.vb">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="LoadBuffer.vb" />
|
||||
<Compile Include="MainForm.Designer.vb">
|
||||
<DependentUpon>MainForm.vb</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="MainForm.vb">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Module2.vb" />
|
||||
<Compile Include="My Project\Application.Designer.vb">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Application.myapp</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="My Project\Settings.Designer.vb">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="PicSettings.vb" />
|
||||
<Compile Include="XYThreadPool.vb" />
|
||||
<Compile Include="Module1.vb">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="ParametriSetup.vb">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<EmbeddedResource Include="Form1.resx">
|
||||
<DependentUpon>Form1.vb</DependentUpon>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="MainForm.resx">
|
||||
<DependentUpon>MainForm.vb</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<None Include="My Project\Application.myapp">
|
||||
<Generator>MyApplicationCodeGenerator</Generator>
|
||||
<LastGenOutput>Application.Designer.vb</LastGenOutput>
|
||||
</None>
|
||||
<None Include="app.config" />
|
||||
<None Include="ClassDiagram1.cd" />
|
||||
<None Include="My Project\app.manifest" />
|
||||
<None Include="My Project\Settings.settings">
|
||||
<CustomToolNamespace>My</CustomToolNamespace>
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.vb</LastGenOutput>
|
||||
</None>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.2.0">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 2.0 %28x86%29</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.0">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.0 %28x86%29</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Windows Installer 3.1</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Sorgenti\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CatalogVbLib\CatalogVbLib.vbproj">
|
||||
<Project>{44465926-240d-473f-90b8-786ba4384406}</Project>
|
||||
<Name>CatalogVbLib</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\MaddoShared\MaddoShared.csproj">
|
||||
<Project>{aebfe9e3-277c-4a7b-8448-145d1b11998b}</Project>
|
||||
<Name>MaddoShared</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="collini_canon.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="LOG_ITA.jpg" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.VisualBasic.targets" />
|
||||
<PropertyGroup>
|
||||
<PreBuildEvent>
|
||||
</PreBuildEvent>
|
||||
<PostBuildEvent>
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
7
imagecatalog/Models/AiResultItem.cs
Normal file
7
imagecatalog/Models/AiResultItem.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
namespace ImageCatalog_2.Models;
|
||||
|
||||
public class AiResultItem
|
||||
{
|
||||
public string Path { get; set; } = string.Empty;
|
||||
public string Text { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -151,14 +151,18 @@ static class Program
|
|||
var testService = sp.GetRequiredService<ITestService>();
|
||||
var settingsService = sp.GetRequiredService<ISettingsService>();
|
||||
var imageCreation = sp.GetRequiredService<ImageCreationService>();
|
||||
var aiExtractionService = sp.GetRequiredService<IAiExtractionService>();
|
||||
var imageProcessingCoordinator = sp.GetRequiredService<IImageProcessingCoordinator>();
|
||||
var picSettings = sp.GetRequiredService<PicSettings>();
|
||||
var mapper = sp.GetRequiredService<IMapper>();
|
||||
var logger = sp.GetRequiredService<ILogger<DataModel>>();
|
||||
var versionProvider = sp.GetService<MaddoShared.IVersionProvider>();
|
||||
|
||||
return new DataModel(testService, settingsService, imageCreation, picSettings, mapper, logger, versionProvider);
|
||||
return new DataModel(testService, settingsService, imageCreation, aiExtractionService, imageProcessingCoordinator, picSettings, mapper, logger, versionProvider);
|
||||
});
|
||||
|
||||
services.AddTransient<IAiExtractionService, AiExtractionService>();
|
||||
services.AddTransient<IImageProcessingCoordinator, ImageProcessingCoordinator>();
|
||||
services.AddTransient<ImageCreationService>();
|
||||
#if WINDOWS
|
||||
services.AddTransient<ImageCreatorGDI>();
|
||||
|
|
|
|||
132
imagecatalog/Services/AiExtractionService.cs
Normal file
132
imagecatalog/Services/AiExtractionService.cs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ImageCatalog_2.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ImageCatalog_2.Services;
|
||||
|
||||
public class AiExtractionService : IAiExtractionService
|
||||
{
|
||||
private readonly ILogger<AiExtractionService> _logger;
|
||||
|
||||
public AiExtractionService(ILogger<AiExtractionService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task RunAsync(
|
||||
AiExtractionRequest request,
|
||||
CancellationToken token,
|
||||
Func<AiResultItem, Task> onResult,
|
||||
Func<double, Task> onProgress)
|
||||
{
|
||||
var searchOption = request.Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||
|
||||
var imageFiles = Directory.EnumerateFiles(request.SearchRoot, "*.*", searchOption)
|
||||
.Where(f => f.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase)
|
||||
|| f.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase)
|
||||
|| f.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
|
||||
|| f.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase)
|
||||
|| f.EndsWith(".gif", StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
if (imageFiles.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var extractedResults = new List<AiResultItem>();
|
||||
|
||||
Type? aiProcessorType = null;
|
||||
object? aiProcessor = null;
|
||||
|
||||
try
|
||||
{
|
||||
var assembly = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.FirstOrDefault(a => a.GetName().Name?.Equals("AIFotoONLUS.Core", StringComparison.OrdinalIgnoreCase) == true);
|
||||
if (assembly != null)
|
||||
{
|
||||
aiProcessorType = assembly.GetType("AIFotoONLUS.Core.AiProcessor");
|
||||
if (aiProcessorType != null)
|
||||
{
|
||||
aiProcessor = Activator.CreateInstance(aiProcessorType);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "AIFotoONLUS.Core not available or failed to load via reflection");
|
||||
}
|
||||
|
||||
var processed = 0;
|
||||
var total = imageFiles.Count;
|
||||
|
||||
foreach (var file in imageFiles)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var extracted = string.Empty;
|
||||
|
||||
if (aiProcessorType is not null && aiProcessor is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var method = aiProcessorType.GetMethod("ExtractNumbersFromImage")
|
||||
?? aiProcessorType.GetMethod("ExtractTextFromImage");
|
||||
if (method is not null)
|
||||
{
|
||||
var value = method.Invoke(aiProcessor, new object[] { file });
|
||||
if (value != null)
|
||||
{
|
||||
extracted = value.ToString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Error invoking AI processor for {File}", file);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(extracted))
|
||||
{
|
||||
var result = new AiResultItem { Path = file, Text = extracted };
|
||||
extractedResults.Add(result);
|
||||
await onResult(result).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
processed++;
|
||||
var percent = total > 0 ? (processed * 100.0 / total) : 100.0;
|
||||
await onProgress(percent).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.CsvOutputPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var dir = Path.GetDirectoryName(request.CsvOutputPath) ?? string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
using var sw = new StreamWriter(request.CsvOutputPath, false, Encoding.UTF8);
|
||||
sw.WriteLine("Path,Text");
|
||||
foreach (var r in extractedResults)
|
||||
{
|
||||
var safeText = (r.Text ?? string.Empty).Replace("\"", "\"\"");
|
||||
sw.WriteLine($"\"{r.Path}\",\"{safeText}\"");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to write CSV to {CsvOutputPath}", request.CsvOutputPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
imagecatalog/Services/IAiExtractionService.cs
Normal file
22
imagecatalog/Services/IAiExtractionService.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ImageCatalog_2.Models;
|
||||
|
||||
namespace ImageCatalog_2.Services;
|
||||
|
||||
public sealed class AiExtractionRequest
|
||||
{
|
||||
public required string SearchRoot { get; init; }
|
||||
public required bool Recursive { get; init; }
|
||||
public string CsvOutputPath { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
public interface IAiExtractionService
|
||||
{
|
||||
Task RunAsync(
|
||||
AiExtractionRequest request,
|
||||
CancellationToken token,
|
||||
Func<AiResultItem, Task> onResult,
|
||||
Func<double, Task> onProgress);
|
||||
}
|
||||
28
imagecatalog/Services/IImageProcessingCoordinator.cs
Normal file
28
imagecatalog/Services/IImageProcessingCoordinator.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MaddoShared;
|
||||
|
||||
namespace ImageCatalog_2.Services
|
||||
{
|
||||
public readonly record struct ImageProcessedUpdate(string Status, int Total, int Processed);
|
||||
|
||||
public sealed class ImageProcessingRunRequest
|
||||
{
|
||||
public required ImageCreationService.Options Options { get; init; }
|
||||
}
|
||||
|
||||
public sealed class ImageProcessingRunResult
|
||||
{
|
||||
public required string FinalSpeedCounter { get; init; }
|
||||
}
|
||||
|
||||
public interface IImageProcessingCoordinator
|
||||
{
|
||||
Task<ImageProcessingRunResult> RunAsync(
|
||||
ImageProcessingRunRequest request,
|
||||
CancellationToken token,
|
||||
Action<ImageProcessedUpdate> onImageProcessed,
|
||||
Action<string> onSpeedUpdated);
|
||||
}
|
||||
}
|
||||
127
imagecatalog/Services/ImageProcessingCoordinator.cs
Normal file
127
imagecatalog/Services/ImageProcessingCoordinator.cs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MaddoShared;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ImageCatalog_2.Services
|
||||
{
|
||||
public class ImageProcessingCoordinator : IImageProcessingCoordinator
|
||||
{
|
||||
private readonly ImageCreationService _imageCreationService;
|
||||
private readonly ILogger<ImageProcessingCoordinator> _logger;
|
||||
|
||||
[CLSCompliant(false)]
|
||||
public ImageProcessingCoordinator(
|
||||
ImageCreationService imageCreationService,
|
||||
ILogger<ImageProcessingCoordinator> logger)
|
||||
{
|
||||
_imageCreationService = imageCreationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<ImageProcessingRunResult> RunAsync(
|
||||
ImageProcessingRunRequest request,
|
||||
CancellationToken token,
|
||||
Action<ImageProcessedUpdate> onImageProcessed,
|
||||
Action<string> onSpeedUpdated)
|
||||
{
|
||||
var results = new ConcurrentBag<string>();
|
||||
var recentDiffs = new Queue<int>();
|
||||
const int recentWindowSize = 5;
|
||||
|
||||
int currentAmount = 0;
|
||||
int previousAmount = 0;
|
||||
int processedAtomic = 0;
|
||||
|
||||
var speedWatch = Stopwatch.StartNew();
|
||||
using var speedTimer = new System.Threading.Timer(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
previousAmount = currentAmount;
|
||||
currentAmount = Volatile.Read(ref processedAtomic);
|
||||
int diff = currentAmount - previousAmount;
|
||||
if (diff < 0)
|
||||
{
|
||||
diff = 0;
|
||||
}
|
||||
|
||||
lock (recentDiffs)
|
||||
{
|
||||
recentDiffs.Enqueue(diff);
|
||||
if (recentDiffs.Count > recentWindowSize)
|
||||
{
|
||||
recentDiffs.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
double avgRecent;
|
||||
lock (recentDiffs)
|
||||
{
|
||||
avgRecent = recentDiffs.Count == 0 ? 0.0 : recentDiffs.Average();
|
||||
}
|
||||
|
||||
double overall = 0.0;
|
||||
if (speedWatch.Elapsed.TotalSeconds >= 1)
|
||||
{
|
||||
var elapsedSeconds = speedWatch.Elapsed.TotalSeconds;
|
||||
var total = Volatile.Read(ref processedAtomic);
|
||||
overall = elapsedSeconds > 0 ? total / elapsedSeconds : 0.0;
|
||||
}
|
||||
|
||||
var recentPerMin = avgRecent * 60.0;
|
||||
var elapsed = speedWatch.Elapsed;
|
||||
int hours = (int)elapsed.TotalHours;
|
||||
int minutes = elapsed.Minutes;
|
||||
int seconds = elapsed.Seconds;
|
||||
var elapsedStr = $"{hours}h {minutes}m {seconds}s";
|
||||
|
||||
var speedText = $"{avgRecent:0.00} f/s (media: {overall:0.00} f/s) - {elapsedStr}{Environment.NewLine}media: {recentPerMin:0.00} f/m";
|
||||
onSpeedUpdated(speedText);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "Failed to update speed counter");
|
||||
}
|
||||
}, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
|
||||
|
||||
EventHandler<Tuple<string, int>> onImageProcessedInternal = (_, args) =>
|
||||
{
|
||||
var processed = Interlocked.Increment(ref processedAtomic);
|
||||
onImageProcessed(new ImageProcessedUpdate(args.Item1, args.Item2, processed));
|
||||
};
|
||||
|
||||
await _imageCreationService.CreaCatalogoParallel(
|
||||
request.Options,
|
||||
results,
|
||||
onImageProcessedInternal,
|
||||
token).ConfigureAwait(false);
|
||||
|
||||
speedWatch.Stop();
|
||||
|
||||
var finalProcessed = Volatile.Read(ref processedAtomic);
|
||||
double overallAvg = 0.0;
|
||||
double overallPerMin = 0.0;
|
||||
if (speedWatch.Elapsed.TotalSeconds > 0.0)
|
||||
{
|
||||
overallAvg = finalProcessed / speedWatch.Elapsed.TotalSeconds;
|
||||
overallPerMin = overallAvg * 60.0;
|
||||
}
|
||||
|
||||
var finalElapsed = speedWatch.Elapsed;
|
||||
int finalHours = (int)finalElapsed.TotalHours;
|
||||
int finalMinutes = finalElapsed.Minutes;
|
||||
int finalSeconds = finalElapsed.Seconds;
|
||||
|
||||
return new ImageProcessingRunResult
|
||||
{
|
||||
FinalSpeedCounter = $"{finalHours}h {finalMinutes}m {finalSeconds}s{Environment.NewLine}media: {overallAvg:0.00} f/s{Environment.NewLine}media: {overallPerMin:0.00} f/m"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
75
imagecatalog/ViewModels/AiSettingsViewModel.cs
Normal file
75
imagecatalog/ViewModels/AiSettingsViewModel.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using ImageCatalog_2.Models;
|
||||
|
||||
namespace ImageCatalog_2.ViewModels;
|
||||
|
||||
public class AiSettingsViewModel : ViewModelBase
|
||||
{
|
||||
private bool _extractNumbers;
|
||||
public bool ExtractNumbers
|
||||
{
|
||||
get => _extractNumbers;
|
||||
set
|
||||
{
|
||||
_extractNumbers = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _modelsFolderPath = string.Empty;
|
||||
public string ModelsFolderPath
|
||||
{
|
||||
get => _modelsFolderPath;
|
||||
set
|
||||
{
|
||||
_modelsFolderPath = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _csvOutputPath = string.Empty;
|
||||
public string CsvOutputPath
|
||||
{
|
||||
get => _csvOutputPath;
|
||||
set
|
||||
{
|
||||
_csvOutputPath = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _faceExecutablePath = string.Empty;
|
||||
public string FaceExecutablePath
|
||||
{
|
||||
get => _faceExecutablePath;
|
||||
set
|
||||
{
|
||||
_faceExecutablePath = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _faceOutputFolderPath = string.Empty;
|
||||
public string FaceOutputFolderPath
|
||||
{
|
||||
get => _faceOutputFolderPath;
|
||||
set
|
||||
{
|
||||
_faceOutputFolderPath = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private double _aiProgress;
|
||||
public double AiProgress
|
||||
{
|
||||
get => _aiProgress;
|
||||
set
|
||||
{
|
||||
_aiProgress = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<AiResultItem> PreviewResults { get; } = new();
|
||||
}
|
||||
49
imagecatalog/ViewModels/PathSettingsViewModel.cs
Normal file
49
imagecatalog/ViewModels/PathSettingsViewModel.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace ImageCatalog_2.ViewModels;
|
||||
|
||||
public class PathSettingsViewModel : ViewModelBase
|
||||
{
|
||||
private string _sourcePath = string.Empty;
|
||||
public string SourcePath
|
||||
{
|
||||
get => _sourcePath;
|
||||
set
|
||||
{
|
||||
_sourcePath = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _destinationPath = string.Empty;
|
||||
public string DestinationPath
|
||||
{
|
||||
get => _destinationPath;
|
||||
set
|
||||
{
|
||||
_destinationPath = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void NormalizePaths()
|
||||
{
|
||||
SourcePath = NormalizePath(SourcePath);
|
||||
DestinationPath = NormalizePath(DestinationPath);
|
||||
}
|
||||
|
||||
public static string NormalizePath(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
path = path.Trim().Trim('"');
|
||||
path = path.Replace('/', Path.DirectorySeparatorChar)
|
||||
.Replace('\\', Path.DirectorySeparatorChar);
|
||||
|
||||
return path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||
}
|
||||
}
|
||||
82
imagecatalog/ViewModels/ProcessingStateViewModel.cs
Normal file
82
imagecatalog/ViewModels/ProcessingStateViewModel.cs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
using System;
|
||||
|
||||
namespace ImageCatalog_2.ViewModels;
|
||||
|
||||
public class ProcessingStateViewModel : ViewModelBase
|
||||
{
|
||||
private string _processingStatus = string.Empty;
|
||||
public string ProcessingStatus
|
||||
{
|
||||
get => _processingStatus;
|
||||
set
|
||||
{
|
||||
_processingStatus = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _processedImagesCount;
|
||||
public int ProcessedImagesCount
|
||||
{
|
||||
get => _processedImagesCount;
|
||||
set
|
||||
{
|
||||
_processedImagesCount = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _totalImagesCount;
|
||||
public int TotalImagesCount
|
||||
{
|
||||
get => _totalImagesCount;
|
||||
set
|
||||
{
|
||||
_totalImagesCount = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _progressBarValue;
|
||||
public int ProgressBarValue
|
||||
{
|
||||
get => _progressBarValue;
|
||||
set
|
||||
{
|
||||
_progressBarValue = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _progressBarMaximum = 100;
|
||||
public int ProgressBarMaximum
|
||||
{
|
||||
get => _progressBarMaximum;
|
||||
set
|
||||
{
|
||||
_progressBarMaximum = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _speedCounter = "-";
|
||||
public string SpeedCounter
|
||||
{
|
||||
get => _speedCounter;
|
||||
set
|
||||
{
|
||||
_speedCounter = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetForRun()
|
||||
{
|
||||
ProcessingStatus = "Elaborazione in corso...";
|
||||
TotalImagesCount = 0;
|
||||
ProcessedImagesCount = 0;
|
||||
SpeedCounter = "-f/s";
|
||||
ProgressBarValue = 0;
|
||||
ProgressBarMaximum = 100;
|
||||
}
|
||||
}
|
||||
149
imagecatalog/ViewModels/RaceUploadSettingsViewModel.cs
Normal file
149
imagecatalog/ViewModels/RaceUploadSettingsViewModel.cs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
using System;
|
||||
|
||||
namespace ImageCatalog_2.ViewModels;
|
||||
|
||||
public class RaceUploadSettingsViewModel : ViewModelBase
|
||||
{
|
||||
private string _apiLogin = string.Empty;
|
||||
public string ApiLogin
|
||||
{
|
||||
get => _apiLogin;
|
||||
set
|
||||
{
|
||||
_apiLogin = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _apiPassword = string.Empty;
|
||||
public string ApiPassword
|
||||
{
|
||||
get => _apiPassword;
|
||||
set
|
||||
{
|
||||
_apiPassword = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _apiRaceDescription = string.Empty;
|
||||
public string ApiRaceDescription
|
||||
{
|
||||
get => _apiRaceDescription;
|
||||
set
|
||||
{
|
||||
_apiRaceDescription = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _apiRaceTypeId = "1";
|
||||
public string ApiRaceTypeId
|
||||
{
|
||||
get => _apiRaceTypeId;
|
||||
set
|
||||
{
|
||||
_apiRaceTypeId = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private DateTime _apiRaceStartDate = DateTime.Today;
|
||||
public DateTime ApiRaceStartDate
|
||||
{
|
||||
get => _apiRaceStartDate;
|
||||
set
|
||||
{
|
||||
_apiRaceStartDate = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private DateTime _apiRaceEndDate = DateTime.Today;
|
||||
public DateTime ApiRaceEndDate
|
||||
{
|
||||
get => _apiRaceEndDate;
|
||||
set
|
||||
{
|
||||
_apiRaceEndDate = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _apiPathBase = string.Empty;
|
||||
public string ApiPathBase
|
||||
{
|
||||
get => _apiPathBase;
|
||||
set
|
||||
{
|
||||
_apiPathBase = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _apiLocalita = string.Empty;
|
||||
public string ApiLocalita
|
||||
{
|
||||
get => _apiLocalita;
|
||||
set
|
||||
{
|
||||
_apiLocalita = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _apiEventoInLineaIndex;
|
||||
public int ApiEventoInLineaIndex
|
||||
{
|
||||
get => _apiEventoInLineaIndex;
|
||||
set
|
||||
{
|
||||
_apiEventoInLineaIndex = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _apiTipoIndexValue = 1;
|
||||
public int ApiTipoIndexValue
|
||||
{
|
||||
get => _apiTipoIndexValue;
|
||||
set
|
||||
{
|
||||
_apiTipoIndexValue = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _apiFreeEventIndex;
|
||||
public int ApiFreeEventIndex
|
||||
{
|
||||
get => _apiFreeEventIndex;
|
||||
set
|
||||
{
|
||||
_apiFreeEventIndex = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _apiRaceId = string.Empty;
|
||||
public string ApiRaceId
|
||||
{
|
||||
get => _apiRaceId;
|
||||
set
|
||||
{
|
||||
_apiRaceId = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _apiRemoteProcessedBasePath = string.Empty;
|
||||
public string ApiRemoteProcessedBasePath
|
||||
{
|
||||
get => _apiRemoteProcessedBasePath;
|
||||
set
|
||||
{
|
||||
_apiRemoteProcessedBasePath = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
312
imagecatalog/ViewModels/VisualSettingsViewModel.cs
Normal file
312
imagecatalog/ViewModels/VisualSettingsViewModel.cs
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
namespace ImageCatalog_2.ViewModels;
|
||||
|
||||
public class VisualSettingsViewModel : ViewModelBase
|
||||
{
|
||||
private string _horizontalText = string.Empty;
|
||||
public string HorizontalText
|
||||
{
|
||||
get => _horizontalText;
|
||||
set
|
||||
{
|
||||
_horizontalText = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _verticalText = string.Empty;
|
||||
public string VerticalText
|
||||
{
|
||||
get => _verticalText;
|
||||
set
|
||||
{
|
||||
_verticalText = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _overwriteImages;
|
||||
public bool OverwriteImages
|
||||
{
|
||||
get => _overwriteImages;
|
||||
set
|
||||
{
|
||||
_overwriteImages = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _thumbnailPrefix = "tn_";
|
||||
public string ThumbnailPrefix
|
||||
{
|
||||
get => _thumbnailPrefix;
|
||||
set
|
||||
{
|
||||
_thumbnailPrefix = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _thumbnailHeight = 350;
|
||||
public int ThumbnailHeight
|
||||
{
|
||||
get => _thumbnailHeight;
|
||||
set
|
||||
{
|
||||
_thumbnailHeight = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _thumbnailWidth = 350;
|
||||
public int ThumbnailWidth
|
||||
{
|
||||
get => _thumbnailWidth;
|
||||
set
|
||||
{
|
||||
_thumbnailWidth = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _photoBigHeight = 2240;
|
||||
public int PhotoBigHeight
|
||||
{
|
||||
get => _photoBigHeight;
|
||||
set
|
||||
{
|
||||
_photoBigHeight = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _photoBigWidth = 2240;
|
||||
public int PhotoBigWidth
|
||||
{
|
||||
get => _photoBigWidth;
|
||||
set
|
||||
{
|
||||
_photoBigWidth = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _fontSize = 20;
|
||||
public int FontSize
|
||||
{
|
||||
get => _fontSize;
|
||||
set
|
||||
{
|
||||
_fontSize = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _fontSizeThumbnail = 50;
|
||||
public int FontSizeThumbnail
|
||||
{
|
||||
get => _fontSizeThumbnail;
|
||||
set
|
||||
{
|
||||
_fontSizeThumbnail = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _fontName = "Arial";
|
||||
public string FontName
|
||||
{
|
||||
get => _fontName;
|
||||
set
|
||||
{
|
||||
_fontName = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _fontBold;
|
||||
public bool FontBold
|
||||
{
|
||||
get => _fontBold;
|
||||
set
|
||||
{
|
||||
_fontBold = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _textTransparency;
|
||||
public int TextTransparency
|
||||
{
|
||||
get => _textTransparency;
|
||||
set
|
||||
{
|
||||
_textTransparency = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _textMargin = 8;
|
||||
public int TextMargin
|
||||
{
|
||||
get => _textMargin;
|
||||
set
|
||||
{
|
||||
_textMargin = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _textColorRgb = "Yellow";
|
||||
public string TextColorRGB
|
||||
{
|
||||
get => _textColorRgb;
|
||||
set
|
||||
{
|
||||
_textColorRgb = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _transparentColor = "#FFFFFF";
|
||||
public string TransparentColor
|
||||
{
|
||||
get => _transparentColor;
|
||||
set
|
||||
{
|
||||
_transparentColor = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _useTransparentColor;
|
||||
public bool UseTransparentColor
|
||||
{
|
||||
get => _useTransparentColor;
|
||||
set
|
||||
{
|
||||
_useTransparentColor = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _logoFile = string.Empty;
|
||||
public string LogoFile
|
||||
{
|
||||
get => _logoFile;
|
||||
set
|
||||
{
|
||||
_logoFile = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _logoHeight = 430;
|
||||
public int LogoHeight
|
||||
{
|
||||
get => _logoHeight;
|
||||
set
|
||||
{
|
||||
_logoHeight = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _logoWidth = 430;
|
||||
public int LogoWidth
|
||||
{
|
||||
get => _logoWidth;
|
||||
set
|
||||
{
|
||||
_logoWidth = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _logoMargin = 290;
|
||||
public int LogoMargin
|
||||
{
|
||||
get => _logoMargin;
|
||||
set
|
||||
{
|
||||
_logoMargin = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _logoTransparency = 100;
|
||||
public int LogoTransparency
|
||||
{
|
||||
get => _logoTransparency;
|
||||
set
|
||||
{
|
||||
_logoTransparency = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _verticalTextSize = 20;
|
||||
public int VerticalTextSize
|
||||
{
|
||||
get => _verticalTextSize;
|
||||
set
|
||||
{
|
||||
_verticalTextSize = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _verticalTextMargin = 6;
|
||||
public int VerticalTextMargin
|
||||
{
|
||||
get => _verticalTextMargin;
|
||||
set
|
||||
{
|
||||
_verticalTextMargin = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _jpegQuality = 85;
|
||||
public int JpegQuality
|
||||
{
|
||||
get => _jpegQuality;
|
||||
set
|
||||
{
|
||||
_jpegQuality = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _jpegQualityThumbnail = 30;
|
||||
public int JpegQualityThumbnail
|
||||
{
|
||||
get => _jpegQualityThumbnail;
|
||||
set
|
||||
{
|
||||
_jpegQualityThumbnail = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _addLogo;
|
||||
public bool AddLogo
|
||||
{
|
||||
get => _addLogo;
|
||||
set
|
||||
{
|
||||
_addLogo = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _keepOriginalDimensions;
|
||||
public bool KeepOriginalDimensions
|
||||
{
|
||||
get => _keepOriginalDimensions;
|
||||
set
|
||||
{
|
||||
_keepOriginalDimensions = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue