feat : put back the original logic, the streaming mechanism didn't produce a result as expected
This commit is contained in:
parent
8b033d0725
commit
dd9f0d9cbe
3 changed files with 9 additions and 164 deletions
|
|
@ -1,6 +1,4 @@
|
||||||
using StitcherApi.Models;
|
using StitcherApi.Models;
|
||||||
using StitcherApi.Services.Fast;
|
|
||||||
using StitcherApi.Services.Streaming;
|
|
||||||
using StitcherApi.Services.Utilities;
|
using StitcherApi.Services.Utilities;
|
||||||
|
|
||||||
namespace StitcherApi.Services;
|
namespace StitcherApi.Services;
|
||||||
|
|
@ -8,56 +6,29 @@ namespace StitcherApi.Services;
|
||||||
public class ImageService : IImageService
|
public class ImageService : IImageService
|
||||||
{
|
{
|
||||||
private readonly ILogger<ImageService> _logger;
|
private readonly ILogger<ImageService> _logger;
|
||||||
private readonly FastProcessor _fastProcessor;
|
private readonly StitchProcessor _stitchProcessor;
|
||||||
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;
|
|
||||||
|
|
||||||
public ImageService(IConfiguration config, ILoggerFactory loggerFactory)
|
public ImageService(IConfiguration config, ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
_logger = loggerFactory.CreateLogger<ImageService>();
|
_logger = loggerFactory.CreateLogger<ImageService>();
|
||||||
string assetPath =
|
string assetPath =
|
||||||
config["AssetPath"] ?? throw new InvalidOperationException("AssetPath not configured.");
|
config["AssetPath"] ?? throw new InvalidOperationException("AssetPath not configured.");
|
||||||
|
_stitchProcessor = new StitchProcessor(
|
||||||
_fastProcessor = new FastProcessor(assetPath, loggerFactory.CreateLogger<FastProcessor>());
|
|
||||||
_streamingProcessor = new StreamingProcessor(
|
|
||||||
assetPath,
|
assetPath,
|
||||||
loggerFactory.CreateLogger<StreamingProcessor>()
|
loggerFactory.CreateLogger<StitchProcessor>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> GenerateImageAsync(GenerateImageRequest request)
|
public async Task<byte[]> GenerateImageAsync(GenerateImageRequest request)
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("Processing request with the fast processor...");
|
||||||
(int minRow, int minCol, int maxRow, int maxCol) = CoordinateParser.ParseCanvasRect(
|
(int minRow, int minCol, int maxRow, int maxCol) = CoordinateParser.ParseCanvasRect(
|
||||||
request.CanvasRect
|
request.CanvasRect
|
||||||
);
|
);
|
||||||
|
|
||||||
int tileGridWidth = maxCol - minCol + 1;
|
|
||||||
int tileGridHeight = maxRow - minRow + 1;
|
|
||||||
int totalTiles = tileGridWidth * tileGridHeight;
|
|
||||||
|
|
||||||
StitchRequest stitchRequest = CreateStitchRequest(request, minRow, minCol, maxRow, maxCol);
|
StitchRequest stitchRequest = CreateStitchRequest(request, minRow, minCol, maxRow, maxCol);
|
||||||
|
|
||||||
double aspectRatio = (tileGridHeight > 0) ? (double)tileGridWidth / tileGridHeight : 0;
|
return await _stitchProcessor.ProcessAsync(stitchRequest);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private StitchRequest CreateStitchRequest(
|
private StitchRequest CreateStitchRequest(
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using SixLabors.ImageSharp.Processing;
|
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 string _assetPath;
|
||||||
private readonly ILogger<FastProcessor> _logger;
|
private readonly ILogger<StitchProcessor> _logger;
|
||||||
private const int TILE_SIZE = 720;
|
private const int TILE_SIZE = 720;
|
||||||
|
|
||||||
public FastProcessor(string assetPath, ILogger<FastProcessor> logger)
|
public StitchProcessor(string assetPath, ILogger<StitchProcessor> logger)
|
||||||
{
|
{
|
||||||
_assetPath = assetPath;
|
_assetPath = assetPath;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
@ -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<StreamingProcessor> _logger;
|
|
||||||
private const int TILE_SIZE = 720;
|
|
||||||
private const int BAND_HEIGHT = TILE_SIZE;
|
|
||||||
|
|
||||||
public StreamingProcessor(string assetPath, ILogger<StreamingProcessor> logger)
|
|
||||||
{
|
|
||||||
_assetPath = assetPath;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<byte[]> ProcessAsync(StitchRequest request)
|
|
||||||
{
|
|
||||||
_logger.LogDebug(
|
|
||||||
"Starting streaming processor for crop size {W}x{H}",
|
|
||||||
request.CropW,
|
|
||||||
request.CropH
|
|
||||||
);
|
|
||||||
|
|
||||||
ConcurrentDictionary<(int, int), Image<Rgba32>> 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<Rgba32> bandImage = new Image<Rgba32>(request.CropW, currentBandHeight);
|
|
||||||
ComposeBandOptimized(bandImage, bandY, request, tileCache);
|
|
||||||
await bandImage.SaveAsPngAsync(memoryStream, pngEncoder);
|
|
||||||
}
|
|
||||||
|
|
||||||
return memoryStream.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<ConcurrentDictionary<(int, int), Image<Rgba32>>> LoadRequiredTilesAsync(
|
|
||||||
StitchRequest r
|
|
||||||
)
|
|
||||||
{
|
|
||||||
ConcurrentDictionary<(int, int), Image<Rgba32>> cache = new();
|
|
||||||
List<Task> 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<Rgba32>(tileFilePath);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Task.WhenAll(loadTasks);
|
|
||||||
return cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ComposeBandOptimized(
|
|
||||||
Image<Rgba32> bandImage,
|
|
||||||
int bandY,
|
|
||||||
StitchRequest r,
|
|
||||||
ConcurrentDictionary<(int, int), Image<Rgba32>> 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<Rgba32>? 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())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue