feat : algorithm improvements

- use FastStitchProcessor when the canvas is either large, tall or wide (original algorithm)
- introduce StreamingProcessor algorithm for better robust performance
- add logger
This commit is contained in:
gelaws-hub 2025-08-01 20:18:01 +07:00
parent 8cec6e92c5
commit 4ed4eea462
7 changed files with 210 additions and 46 deletions

View file

@ -1,49 +1,86 @@
using StitcherApi.Models;
using StitcherApi.Services.Fast;
using StitcherApi.Services.Streaming;
using StitcherApi.Services.Utilities;
namespace StitcherApi.Services;
public class ImageService : IImageService
{
private readonly ImageProcessor _processor;
private const int TILE_SIZE = 720;
private readonly ILogger<ImageService> _logger;
private readonly FastProcessor _fastProcessor;
private readonly StreamingProcessor _streamingProcessor;
public ImageService(IConfiguration configuration)
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)
{
_logger = loggerFactory.CreateLogger<ImageService>();
string assetPath =
configuration["AssetPath"]
?? throw new InvalidOperationException("AssetPath is not configured.");
_processor = new ImageProcessor(assetPath);
config["AssetPath"] ?? throw new InvalidOperationException("AssetPath not configured.");
_fastProcessor = new FastProcessor(assetPath, loggerFactory.CreateLogger<FastProcessor>());
_streamingProcessor = new StreamingProcessor(
assetPath,
loggerFactory.CreateLogger<StreamingProcessor>()
);
}
public async Task<byte[]> GenerateImageAsync(GenerateImageRequest request)
{
// 1. Delegate parsing to the CoordinateParser
(int minRow, int minCol, int maxRow, int maxCol) = CoordinateParser.ParseCanvasRect(
request.CanvasRect
);
// 2. Perform high-level calculations
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);
}
}
private StitchRequest CreateStitchRequest(
GenerateImageRequest request,
int minRow,
int minCol,
int maxRow,
int maxCol
)
{
const int TILE_SIZE = 720;
int stitchedCanvasWidth = (maxCol - minCol + 1) * TILE_SIZE;
int stitchedCanvasHeight = (maxRow - minRow + 1) * TILE_SIZE;
int cropX = (int)(request.CropOffset[0] * stitchedCanvasWidth);
int cropY = (int)(request.CropOffset[1] * stitchedCanvasHeight);
int cropW = (int)(request.CropSize[0] * stitchedCanvasWidth);
int cropH = (int)(request.CropSize[1] * stitchedCanvasHeight);
int startTileCol = minCol + cropX / TILE_SIZE;
int endTileCol = minCol + (cropX + cropW - 1) / TILE_SIZE;
int startTileRow = minRow + cropY / TILE_SIZE;
int endTileRow = minRow + (cropY + cropH - 1) / TILE_SIZE;
if (cropW <= 0 || cropH <= 0)
{
throw new ArgumentException("Calculated crop dimensions are invalid.");
}
int startTileCol = minCol + (cropX / TILE_SIZE);
int endTileCol = minCol + ((cropX + cropW - 1) / TILE_SIZE);
int startTileRow = minRow + (cropY / TILE_SIZE);
int endTileRow = minRow + ((cropY + cropH - 1) / TILE_SIZE);
// 3. Create a parameter object for the processor
StitchRequest stitchRequest = new StitchRequest(
return new StitchRequest(
minRow,
minCol,
startTileRow,
@ -56,8 +93,5 @@ public class ImageService : IImageService
cropH,
request.OutputScale
);
// 4. Delegate image processing work
return await _processor.StitchAndCropAsync(stitchRequest);
}
}