diff --git a/Services/ImageService.cs b/Services/ImageService.cs index 924e993..6692d75 100644 --- a/Services/ImageService.cs +++ b/Services/ImageService.cs @@ -1,6 +1,4 @@ using StitcherApi.Models; -using StitcherApi.Services.Fast; -using StitcherApi.Services.Streaming; using StitcherApi.Services.Utilities; namespace StitcherApi.Services; @@ -8,56 +6,29 @@ namespace StitcherApi.Services; public class ImageService : IImageService { private readonly ILogger _logger; - private readonly FastProcessor _fastProcessor; - private readonly StreamingProcessor _streamingProcessor; - - private const double ASPECT_RATIO_THRESHOLD_TALL = 0.25; - private const double ASPECT_RATIO_THRESHOLD_WIDE = 8.0; - private const int TILE_COUNT_THRESHOLD = 30; + private readonly StitchProcessor _stitchProcessor; public ImageService(IConfiguration config, ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(); string assetPath = config["AssetPath"] ?? throw new InvalidOperationException("AssetPath not configured."); - - _fastProcessor = new FastProcessor(assetPath, loggerFactory.CreateLogger()); - _streamingProcessor = new StreamingProcessor( + _stitchProcessor = new StitchProcessor( assetPath, - loggerFactory.CreateLogger() + loggerFactory.CreateLogger() ); } public async Task GenerateImageAsync(GenerateImageRequest request) { + _logger.LogInformation("Processing request with the fast processor..."); (int minRow, int minCol, int maxRow, int maxCol) = CoordinateParser.ParseCanvasRect( request.CanvasRect ); - int tileGridWidth = maxCol - minCol + 1; - int tileGridHeight = maxRow - minRow + 1; - int totalTiles = tileGridWidth * tileGridHeight; - StitchRequest stitchRequest = CreateStitchRequest(request, minRow, minCol, maxRow, maxCol); - double aspectRatio = (tileGridHeight > 0) ? (double)tileGridWidth / tileGridHeight : 0; - - if ( - totalTiles > TILE_COUNT_THRESHOLD - || aspectRatio > ASPECT_RATIO_THRESHOLD_WIDE - || (aspectRatio > 0 && aspectRatio < ASPECT_RATIO_THRESHOLD_TALL) - ) - { - _logger.LogInformation("Large, Tall, or Wide canvas detected. Using fast processor."); - return await _fastProcessor.ProcessAsync(stitchRequest); - } - else - { - _logger.LogInformation( - "Small, block-shaped canvas detected. Using robust streaming processor." - ); - return await _streamingProcessor.ProcessAsync(stitchRequest); - } + return await _stitchProcessor.ProcessAsync(stitchRequest); } private StitchRequest CreateStitchRequest( diff --git a/Services/Utilities/FastStitchProcessor.cs b/Services/Utilities/StitchProcessor.cs similarity index 92% rename from Services/Utilities/FastStitchProcessor.cs rename to Services/Utilities/StitchProcessor.cs index 78f2c20..b36bf37 100644 --- a/Services/Utilities/FastStitchProcessor.cs +++ b/Services/Utilities/StitchProcessor.cs @@ -1,17 +1,16 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using StitcherApi.Services.Utilities; -namespace StitcherApi.Services.Fast; +namespace StitcherApi.Services.Utilities; -public class FastProcessor +public class StitchProcessor { private readonly string _assetPath; - private readonly ILogger _logger; + private readonly ILogger _logger; private const int TILE_SIZE = 720; - public FastProcessor(string assetPath, ILogger logger) + public StitchProcessor(string assetPath, ILogger logger) { _assetPath = assetPath; _logger = logger; diff --git a/Services/Utilities/StreamingProcessor.cs b/Services/Utilities/StreamingProcessor.cs deleted file mode 100644 index 67fa1f2..0000000 --- a/Services/Utilities/StreamingProcessor.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System.Collections.Concurrent; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using StitcherApi.Services.Utilities; - -namespace StitcherApi.Services.Streaming; - -public class StreamingProcessor -{ - private readonly string _assetPath; - private readonly ILogger _logger; - private const int TILE_SIZE = 720; - private const int BAND_HEIGHT = TILE_SIZE; - - public StreamingProcessor(string assetPath, ILogger logger) - { - _assetPath = assetPath; - _logger = logger; - } - - public async Task ProcessAsync(StitchRequest request) - { - _logger.LogDebug( - "Starting streaming processor for crop size {W}x{H}", - request.CropW, - request.CropH - ); - - ConcurrentDictionary<(int, int), Image> tileCache = await LoadRequiredTilesAsync( - request - ); - - using MemoryStream memoryStream = new MemoryStream(); - PngEncoder pngEncoder = new PngEncoder { CompressionLevel = PngCompressionLevel.BestSpeed }; - - for (int bandY = 0; bandY < request.CropH; bandY += BAND_HEIGHT) - { - int currentBandHeight = Math.Min(BAND_HEIGHT, request.CropH - bandY); - using Image bandImage = new Image(request.CropW, currentBandHeight); - ComposeBandOptimized(bandImage, bandY, request, tileCache); - await bandImage.SaveAsPngAsync(memoryStream, pngEncoder); - } - - return memoryStream.ToArray(); - } - - private async Task>> LoadRequiredTilesAsync( - StitchRequest r - ) - { - ConcurrentDictionary<(int, int), Image> cache = new(); - List loadTasks = new(); - - for (int row = r.StartTileRow; row <= r.EndTileRow; row++) - { - for (int col = r.StartTileCol; col <= r.EndTileCol; col++) - { - int tileRow = row; - int tileCol = col; - loadTasks.Add( - Task.Run(async () => - { - string tileFilePath = Path.Combine( - _assetPath, - TileHelper.GetTileFileName(tileRow, tileCol) - ); - if (!File.Exists(tileFilePath)) - throw new FileNotFoundException($"Asset not found: {tileFilePath}"); - cache[(tileRow, tileCol)] = await Image.LoadAsync(tileFilePath); - }) - ); - } - } - await Task.WhenAll(loadTasks); - return cache; - } - - private void ComposeBandOptimized( - Image bandImage, - int bandY, - StitchRequest r, - ConcurrentDictionary<(int, int), Image> tileCache - ) - { - int bandAbsoluteY = r.CropY + bandY; - int currentTileRow = r.MinRow + (bandAbsoluteY / TILE_SIZE); - - for (int tileCol = r.StartTileCol; tileCol <= r.EndTileCol; tileCol++) - { - if (tileCache.TryGetValue((currentTileRow, tileCol), out Image? tileImage)) - { - int tileOriginX = (tileCol - r.MinCol) * TILE_SIZE; - int tileOriginY = (currentTileRow - r.MinRow) * TILE_SIZE; - - Rectangle bandRect = new Rectangle( - r.CropX, - bandAbsoluteY, - r.CropW, - bandImage.Height - ); - Rectangle tileRect = new Rectangle(tileOriginX, tileOriginY, TILE_SIZE, TILE_SIZE); - Rectangle intersection = Rectangle.Intersect(tileRect, bandRect); - - if (!intersection.IsEmpty) - { - Point sourcePoint = new Point( - intersection.X - tileOriginX, - intersection.Y - tileOriginY - ); - Point destPoint = new Point( - intersection.X - r.CropX, - intersection.Y - bandAbsoluteY - ); - Rectangle cropRectangle = new Rectangle(sourcePoint, intersection.Size); - - bandImage.Mutate(ctx => - ctx.DrawImage(tileImage, destPoint, cropRectangle, new GraphicsOptions()) - ); - } - } - } - } -}