Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions API.Benchmark/API.Benchmark.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<UseImageMagick>false</UseImageMagick>
<DefineConstants Condition="'$(UseImageMagick)' == 'true'">ImageMagick;$(DefineConstants)</DefineConstants>
</PropertyGroup>

<ItemGroup>
Expand Down
62 changes: 28 additions & 34 deletions API.Benchmark/ArchiveServiceBenchmark.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
using System;
using System;
using System.IO;
using System.IO.Abstractions;
using API.Entities.Enums;
using Microsoft.Extensions.Logging.Abstractions;
using API.Services;
using API.Services.ImageServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Order;
using EasyCaching.Core;
using NSubstitute;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Processing;


namespace API.Benchmark;

Expand All @@ -24,16 +23,22 @@ public class ArchiveServiceBenchmark
private readonly ArchiveService _archiveService;
private readonly IDirectoryService _directoryService;
private readonly IImageService _imageService;
private readonly PngEncoder _pngEncoder = new PngEncoder();
private readonly WebpEncoder _webPEncoder = new WebpEncoder();
private readonly IImageFactory _imageFactory;
private const string SourceImage = "C:/Users/josep/Pictures/obey_by_grrsa-d6llkaa_colored_by_me.png";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever test this is, we probably need to recreate or remove it. I dont even have this file anymore.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just ported it over, should remove the test?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah



public ArchiveServiceBenchmark()
{
#if ImageMagick
_imageFactory = new API.Services.ImageServices.ImageMagick.ImageMagickImageFactory();
#else
_imageFactory = new API.Services.ImageServices.NetVips.NetVipsImageFactory();
#endif

_directoryService = new DirectoryService(null, new FileSystem());
_imageService = new ImageService(null, _directoryService);
_imageService = new ImageService(null, _directoryService,_imageFactory);
_archiveService = new ArchiveService(new NullLogger<ArchiveService>(), _directoryService, _imageService, Substitute.For<IMediaErrorService>());

}

[Benchmark(Baseline = true)]
Expand Down Expand Up @@ -61,50 +66,39 @@ public void TestGetComicInfo_outside_root()
}

[Benchmark]
public void ImageSharp_ExtractImage_PNG()
public void ImageFactory_ExtractImage_PNG()
{
var outputDirectory = "C:/Users/josep/Pictures/imagesharp/";
var outputDirectory = "C:/Users/josep/Pictures/netvips/";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to refactor this to use the TestData folder within the project and not paths on my dev machine. (../../../Services/Test Data/ArchiveService/)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do.

_directoryService.ExistOrCreate(outputDirectory);

using var stream = new FileStream(SourceImage, FileMode.Open);
using var thumbnail2 = SixLabors.ImageSharp.Image.Load(stream);
thumbnail2.Mutate(x => x.Resize(320, 0));
thumbnail2.Save(_directoryService.FileSystem.Path.Join(outputDirectory, "imagesharp.png"), _pngEncoder);
using var thumbnail2 = _imageFactory.Create(stream);
thumbnail2.Resize(320, 0);
thumbnail2.Save(_directoryService.FileSystem.Path.Join(outputDirectory, "imagesharp.png"), EncodeFormat.PNG);
}

[Benchmark]
public void ImageSharp_ExtractImage_WebP()
private void Resize320(IImage image)
{
var outputDirectory = "C:/Users/josep/Pictures/imagesharp/";
_directoryService.ExistOrCreate(outputDirectory);

using var stream = new FileStream(SourceImage, FileMode.Open);
using var thumbnail2 = SixLabors.ImageSharp.Image.Load(stream);
thumbnail2.Mutate(x => x.Resize(320, 0));
thumbnail2.Save(_directoryService.FileSystem.Path.Join(outputDirectory, "imagesharp.webp"), _webPEncoder);
int originalWidth = image.Width;
int originalHeight = image.Height;
int targetWidth = 320;
int targetHeight = (int)Math.Round((double)originalHeight * targetWidth / originalWidth);
image.Resize(targetWidth, targetHeight);
}

[Benchmark]
public void NetVips_ExtractImage_PNG()
public void ImageSharp_ExtractImage_WebP()
{
var outputDirectory = "C:/Users/josep/Pictures/netvips/";
_directoryService.ExistOrCreate(outputDirectory);

using var stream = new FileStream(SourceImage, FileMode.Open);
using var thumbnail = NetVips.Image.ThumbnailStream(stream, 320);
thumbnail.WriteToFile(_directoryService.FileSystem.Path.Join(outputDirectory, "netvips.png"));
using var thumbnail2 = _imageFactory.Create(stream);
Resize320(thumbnail2);
thumbnail2.Save(_directoryService.FileSystem.Path.Join(outputDirectory, "imagesharp.webp"), EncodeFormat.WEBP);
}

[Benchmark]
public void NetVips_ExtractImage_WebP()
{
var outputDirectory = "C:/Users/josep/Pictures/netvips/";
_directoryService.ExistOrCreate(outputDirectory);

using var stream = new FileStream(SourceImage, FileMode.Open);
using var thumbnail = NetVips.Image.ThumbnailStream(stream, 320);
thumbnail.WriteToFile(_directoryService.FileSystem.Path.Join(outputDirectory, "netvips.webp"));
}

// Benchmark to test default GetNumberOfPages from archive
// vs a new method where I try to open the archive and return said stream
Expand Down
2 changes: 2 additions & 0 deletions API.Tests/API.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<IsPackable>false</IsPackable>
<UseImageMagick>false</UseImageMagick>
<DefineConstants Condition="'$(UseImageMagick)' == 'true'">ImageMagick;$(DefineConstants)</DefineConstants>
</PropertyGroup>

<ItemGroup>
Expand Down
5 changes: 3 additions & 2 deletions API.Tests/Extensions/EncodeFormatExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using API.Entities.Enums;
Expand All @@ -17,7 +17,8 @@ public void GetExtension_ShouldReturnCorrectExtensionForAllValues()
{
{ EncodeFormat.PNG, ".png" },
{ EncodeFormat.WEBP, ".webp" },
{ EncodeFormat.AVIF, ".avif" }
{ EncodeFormat.AVIF, ".avif" },
{ EncodeFormat.JPEG, ".jpg" }
};

// Act & Assert
Expand Down
25 changes: 18 additions & 7 deletions API.Tests/Services/ArchiveServiceTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using System.IO;
using System.IO.Abstractions;
using System.IO.Abstractions.TestingHelpers;
Expand All @@ -7,8 +7,8 @@
using API.Archive;
using API.Entities.Enums;
using API.Services;
using API.Services.ImageServices;
using Microsoft.Extensions.Logging;
using NetVips;
using NSubstitute;
using NSubstitute.Extensions;
using Xunit;
Expand All @@ -23,12 +23,17 @@ public class ArchiveServiceTests
private readonly ILogger<ArchiveService> _logger = Substitute.For<ILogger<ArchiveService>>();
private readonly ILogger<DirectoryService> _directoryServiceLogger = Substitute.For<ILogger<DirectoryService>>();
private readonly IDirectoryService _directoryService = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new FileSystem());

private readonly IImageFactory _imageFactory;
public ArchiveServiceTests(ITestOutputHelper testOutputHelper)
{
#if ImageMagick
_imageFactory = new API.Services.ImageServices.ImageMagick.ImageMagickImageFactory();
#else
_imageFactory = new API.Services.ImageServices.NetVips.NetVipsImageFactory();
#endif
_testOutputHelper = testOutputHelper;
_archiveService = new ArchiveService(_logger, _directoryService,
new ImageService(Substitute.For<ILogger<ImageService>>(), _directoryService),
new ImageService(Substitute.For<ILogger<ImageService>>(), _directoryService, _imageFactory),
Substitute.For<IMediaErrorService>());
}

Expand Down Expand Up @@ -166,11 +171,17 @@ public void FindFirstEntry(string[] files, string expected)
public void GetCoverImage_Default_Test(string inputFile, string expectedOutputFile)
{
var ds = Substitute.For<DirectoryService>(_directoryServiceLogger, new FileSystem());
var imageService = new ImageService(Substitute.For<ILogger<ImageService>>(), ds);
var imageService = new ImageService(Substitute.For<ILogger<ImageService>>(), ds, _imageFactory);
var archiveService = Substitute.For<ArchiveService>(_logger, ds, imageService, Substitute.For<IMediaErrorService>());

var testDirectory = Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/CoverImages"));
var expectedBytes = Image.Thumbnail(Path.Join(testDirectory, expectedOutputFile), 320).WriteToBuffer(".png");
var thumbnail = imageService.ImageFactory.Create(Path.Join(testDirectory, expectedOutputFile));
int width = 320;
int height =(int)(thumbnail.Height * (width / (double)thumbnail.Width));
thumbnail = thumbnail.Thumbnail(width, height);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not wrap this in the Image Interface so it's not so manual across the tests? Looks like a duplication of logic to me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also refactor this to use var dims = CoverImageSize.Default.GetDimensions();. It never got updated when I introduced the concept.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, I don't see the use case for this, archiveService.GetCoverImage would internally make the same calls anyway.

using MemoryStream stream = new MemoryStream();
thumbnail.Save(stream, EncodeFormat.PNG);
var expectedBytes = stream.ToArray();

archiveService.Configure().CanOpen(Path.Join(testDirectory, inputFile)).Returns(ArchiveLibrary.Default);

Expand All @@ -197,7 +208,7 @@ public void GetCoverImage_Default_Test(string inputFile, string expectedOutputFi
[InlineData("sorting.zip", "sorting.expected.png")]
public void GetCoverImage_SharpCompress_Test(string inputFile, string expectedOutputFile)
{
var imageService = new ImageService(Substitute.For<ILogger<ImageService>>(), _directoryService);
var imageService = new ImageService(Substitute.For<ILogger<ImageService>>(), _directoryService, _imageFactory);
var archiveService = Substitute.For<ArchiveService>(_logger,
new DirectoryService(_directoryServiceLogger, new FileSystem()), imageService,
Substitute.For<IMediaErrorService>());
Expand Down
11 changes: 9 additions & 2 deletions API.Tests/Services/BookServiceTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using System.IO;
using System.IO;
using System.IO.Abstractions;
using API.Entities.Enums;
using API.Services;
using API.Services.ImageServices;
using API.Services.Tasks.Scanner.Parser;
using Microsoft.Extensions.Logging;
using NSubstitute;
Expand All @@ -16,9 +17,15 @@ public class BookServiceTests

public BookServiceTests()
{
#if ImageMagick
IImageFactory imageFactory = new API.Services.ImageServices.ImageMagick.ImageMagickImageFactory();
#else
IImageFactory imageFactory = new API.Services.ImageServices.NetVips.NetVipsImageFactory();
#endif

var directoryService = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new FileSystem());
_bookService = new BookService(_logger, directoryService,
new ImageService(Substitute.For<ILogger<ImageService>>(), directoryService)
new ImageService(Substitute.For<ILogger<ImageService>>(), directoryService, imageFactory)
, Substitute.For<IMediaErrorService>());
}

Expand Down
16 changes: 8 additions & 8 deletions API.Tests/Services/CacheServiceTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Data.Common;
using System.Data.Common;
using System.IO;
using System.IO.Abstractions.TestingHelpers;
using System.Linq;
Expand Down Expand Up @@ -138,7 +138,7 @@ public async Task Ensure_DirectoryAlreadyExists_DontExtractAnything()
new ReadingItemService(Substitute.For<IArchiveService>(),
Substitute.For<IBookService>(),
Substitute.For<IImageService>(), ds, Substitute.For<ILogger<ReadingItemService>>()),
Substitute.For<IBookmarkService>());
Substitute.For<IBookmarkService>(), Substitute.For<IImageService>());

await ResetDB();
var s = new SeriesBuilder("Test").Build();
Expand Down Expand Up @@ -214,7 +214,7 @@ public void CleanupChapters_AllFilesShouldBeDeleted()
var cleanupService = new CacheService(_logger, _unitOfWork, ds,
new ReadingItemService(Substitute.For<IArchiveService>(),
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds, Substitute.For<ILogger<ReadingItemService>>()),
Substitute.For<IBookmarkService>());
Substitute.For<IBookmarkService>(), Substitute.For<IImageService>());

cleanupService.CleanupChapters(new []{1, 3});
Assert.Empty(ds.GetFiles(CacheDirectory, searchOption:SearchOption.AllDirectories));
Expand All @@ -236,7 +236,7 @@ public void GetCachedEpubFile_ShouldReturnFirstEpub()
var cs = new CacheService(_logger, _unitOfWork, ds,
new ReadingItemService(Substitute.For<IArchiveService>(),
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds, Substitute.For<ILogger<ReadingItemService>>()),
Substitute.For<IBookmarkService>());
Substitute.For<IBookmarkService>(), Substitute.For<IImageService>());

var c = new ChapterBuilder("1")
.WithFile(new MangaFileBuilder($"{DataDirectory}1.epub", MangaFormat.Epub).Build())
Expand Down Expand Up @@ -277,7 +277,7 @@ public void GetCachedPagePath_ReturnNullIfNoFiles()
var cs = new CacheService(_logger, _unitOfWork, ds,
new ReadingItemService(Substitute.For<IArchiveService>(),
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds, Substitute.For<ILogger<ReadingItemService>>()),
Substitute.For<IBookmarkService>());
Substitute.For<IBookmarkService>(), Substitute.For<IImageService>());

// Flatten to prepare for how GetFullPath expects
ds.Flatten($"{CacheDirectory}1/");
Expand Down Expand Up @@ -321,7 +321,7 @@ public void GetCachedPagePath_GetFileFromFirstFile()
var cs = new CacheService(_logger, _unitOfWork, ds,
new ReadingItemService(Substitute.For<IArchiveService>(),
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds, Substitute.For<ILogger<ReadingItemService>>()),
Substitute.For<IBookmarkService>());
Substitute.For<IBookmarkService>(), Substitute.For<IImageService>());

// Flatten to prepare for how GetFullPath expects
ds.Flatten($"{CacheDirectory}1/");
Expand Down Expand Up @@ -362,7 +362,7 @@ public void GetCachedPagePath_GetLastPageFromSingleFile()
var cs = new CacheService(_logger, _unitOfWork, ds,
new ReadingItemService(Substitute.For<IArchiveService>(),
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds, Substitute.For<ILogger<ReadingItemService>>()),
Substitute.For<IBookmarkService>());
Substitute.For<IBookmarkService>(), Substitute.For<IImageService>());

// Flatten to prepare for how GetFullPath expects
ds.Flatten($"{CacheDirectory}1/");
Expand Down Expand Up @@ -407,7 +407,7 @@ public void GetCachedPagePath_GetFileFromSecondFile()
var cs = new CacheService(_logger, _unitOfWork, ds,
new ReadingItemService(Substitute.For<IArchiveService>(),
Substitute.For<IBookService>(), Substitute.For<IImageService>(), ds, Substitute.For<ILogger<ReadingItemService>>()),
Substitute.For<IBookmarkService>());
Substitute.For<IBookmarkService>(), Substitute.For<IImageService>());

// Flatten to prepare for how GetFullPath expects
ds.Flatten($"{CacheDirectory}1/");
Expand Down
16 changes: 12 additions & 4 deletions API.Tests/Services/CoverDbServiceTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using System.IO;
using System.IO;
using System.IO.Abstractions;
using System.Reflection;
using System.Threading.Tasks;
using API.Constants;
using API.Entities.Enums;
using API.Extensions;
using API.Services;
using API.Services.ImageServices;
using API.Services.Tasks.Metadata;
using API.SignalR;
using EasyCaching.Core;
Expand All @@ -22,6 +23,7 @@ public class CoverDbServiceTests : AbstractDbTest
private readonly DirectoryService _directoryService;
private readonly IEasyCachingProviderFactory _cacheFactory = Substitute.For<IEasyCachingProviderFactory>();
private readonly ICoverDbService _coverDbService;
private readonly IImageService _imageService;

private static readonly string FaviconPath = Path.Join(Directory.GetCurrentDirectory(),
"../../../Services/Test Data/CoverDbService/Favicons");
Expand All @@ -34,10 +36,16 @@ public class CoverDbServiceTests : AbstractDbTest
public CoverDbServiceTests()
{
_directoryService = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), CreateFileSystem());
var imageService = new ImageService(Substitute.For<ILogger<ImageService>>(), _directoryService);
#if ImageMagick
IImageFactory imageFactory = new API.Services.ImageServices.ImageMagick.ImageMagickImageFactory();
#else
IImageFactory imageFactory = new API.Services.ImageServices.NetVips.NetVipsImageFactory();
#endif

_imageService = new ImageService(Substitute.For<ILogger<ImageService>>(), _directoryService, imageFactory);

_coverDbService = new CoverDbService(Substitute.For<ILogger<CoverDbService>>(), _directoryService, _cacheFactory,
Substitute.For<IHostEnvironment>(), imageService, UnitOfWork, Substitute.For<IEventHub>());
Substitute.For<IHostEnvironment>(), _imageService, UnitOfWork, Substitute.For<IEventHub>());
}

protected override Task ResetDb()
Expand Down Expand Up @@ -89,7 +97,7 @@ public async Task DownloadFaviconAsync_ShouldDownloadAndMatchExpectedFavicon()

// Load and compare similarity

var similarity = expectedFaviconPath.CalculateSimilarity(actualFaviconPath); // Assuming you have this extension
var similarity = _imageService.ImageFactory.CalculateSimilarity(expectedFaviconPath, actualFaviconPath); // Assuming you have this extension
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious why the imageService.CalculateSimularity doesn't internally call the Image factory leading to a much cleaner API?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can go one way or the other, depending of SRP, will change

Assert.True(similarity > 0.9f, $"Image similarity too low: {similarity}");
}

Expand Down
Loading