first commit
This commit is contained in:
commit
b344b6a03f
16 changed files with 405 additions and 0 deletions
52
Services/Utilities/CoordinateParser.cs
Normal file
52
Services/Utilities/CoordinateParser.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace StitcherApi.Services.Utilities;
|
||||
|
||||
public static class CoordinateParser
|
||||
{
|
||||
private static readonly Regex CoordRegex = new(
|
||||
@"([A-Z]+)(\d+)",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase
|
||||
);
|
||||
|
||||
public static (int MinRow, int MinCol, int MaxRow, int MaxCol) ParseCanvasRect(string rect)
|
||||
{
|
||||
if (string.IsNullOrEmpty(rect))
|
||||
{
|
||||
throw new ArgumentException("canvas_rect cannot be null or empty.");
|
||||
}
|
||||
|
||||
var parts = rect.Split(':');
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
throw new ArgumentException("Invalid canvas_rect format.");
|
||||
}
|
||||
|
||||
var (row1, col1) = ParseSingleCoordinate(parts[0]);
|
||||
var (row2, col2) = ParseSingleCoordinate(parts[1]);
|
||||
|
||||
return (
|
||||
Math.Min(row1, row2),
|
||||
Math.Min(col1, col2),
|
||||
Math.Max(row1, row2),
|
||||
Math.Max(col1, col2)
|
||||
);
|
||||
}
|
||||
|
||||
private static (int Row, int Col) ParseSingleCoordinate(string coord)
|
||||
{
|
||||
var match = CoordRegex.Match(coord);
|
||||
if (!match.Success)
|
||||
{
|
||||
throw new ArgumentException($"Invalid coordinate format: {coord}");
|
||||
}
|
||||
|
||||
var rowStr = match.Groups[1].Value.ToUpper();
|
||||
var colStr = match.Groups[2].Value;
|
||||
|
||||
int row = rowStr.Length == 1 ? rowStr[0] - 'A' : 26 + (rowStr[1] - 'A');
|
||||
int col = int.Parse(colStr) - 1;
|
||||
|
||||
return (row, col);
|
||||
}
|
||||
}
|
||||
94
Services/Utilities/ImageProcessor.cs
Normal file
94
Services/Utilities/ImageProcessor.cs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace StitcherApi.Services.Utilities;
|
||||
|
||||
internal class ImageProcessor
|
||||
{
|
||||
private readonly string _assetPath;
|
||||
private const int TILE_SIZE = 720;
|
||||
|
||||
public ImageProcessor(string assetPath)
|
||||
{
|
||||
_assetPath = assetPath;
|
||||
}
|
||||
|
||||
public async Task<byte[]> StitchAndCropAsync(StitchRequest stitchRequest)
|
||||
{
|
||||
using var finalImage = new Image<Rgba32>(stitchRequest.CropW, stitchRequest.CropH);
|
||||
|
||||
for (var r = stitchRequest.StartTileRow; r <= stitchRequest.EndTileRow; r++)
|
||||
{
|
||||
for (var c = stitchRequest.StartTileCol; c <= stitchRequest.EndTileCol; c++)
|
||||
{
|
||||
var tileFileName = TileHelper.GetTileFileName(r, c);
|
||||
var tileFilePath = Path.Combine(_assetPath, tileFileName);
|
||||
|
||||
if (!File.Exists(tileFilePath))
|
||||
{
|
||||
throw new FileNotFoundException($"Asset not found: {tileFileName}");
|
||||
}
|
||||
|
||||
using var tileImage = await Image.LoadAsync(tileFilePath);
|
||||
|
||||
var tileOriginX = (c - stitchRequest.MinCol) * TILE_SIZE;
|
||||
var tileOriginY = (r - stitchRequest.MinRow) * TILE_SIZE;
|
||||
|
||||
var srcX = Math.Max(0, stitchRequest.CropX - tileOriginX);
|
||||
var srcY = Math.Max(0, stitchRequest.CropY - tileOriginY);
|
||||
var destX = Math.Max(0, tileOriginX - stitchRequest.CropX);
|
||||
var destY = Math.Max(0, tileOriginY - stitchRequest.CropY);
|
||||
|
||||
var overlapW = Math.Max(
|
||||
0,
|
||||
Math.Min(stitchRequest.CropX + stitchRequest.CropW, tileOriginX + TILE_SIZE)
|
||||
- Math.Max(stitchRequest.CropX, tileOriginX)
|
||||
);
|
||||
var overlapH = Math.Max(
|
||||
0,
|
||||
Math.Min(stitchRequest.CropY + stitchRequest.CropH, tileOriginY + TILE_SIZE)
|
||||
- Math.Max(stitchRequest.CropY, tileOriginY)
|
||||
);
|
||||
|
||||
if (overlapW > 0 && overlapH > 0)
|
||||
{
|
||||
var sourceRect = new Rectangle(srcX, srcY, overlapW, overlapH);
|
||||
finalImage.Mutate(ctx =>
|
||||
ctx.DrawImage(
|
||||
tileImage,
|
||||
new Point(destX, destY),
|
||||
sourceRect,
|
||||
new GraphicsOptions()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stitchRequest.OutputScale > 0 && stitchRequest.OutputScale < 1.0)
|
||||
{
|
||||
var newWidth = (int)(stitchRequest.CropW * stitchRequest.OutputScale);
|
||||
var newHeight = (int)(stitchRequest.CropH * stitchRequest.OutputScale);
|
||||
finalImage.Mutate(x => x.Resize(newWidth, newHeight, KnownResamplers.Bicubic));
|
||||
}
|
||||
|
||||
using var memoryStream = new MemoryStream();
|
||||
await finalImage.SaveAsPngAsync(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal record StitchRequest(
|
||||
int MinRow,
|
||||
int MinCol,
|
||||
int StartTileRow,
|
||||
int StartTileCol,
|
||||
int EndTileRow,
|
||||
int EndTileCol,
|
||||
int CropX,
|
||||
int CropY,
|
||||
int CropW,
|
||||
int CropH,
|
||||
float OutputScale
|
||||
);
|
||||
11
Services/Utilities/TileHelper.cs
Normal file
11
Services/Utilities/TileHelper.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
namespace StitcherApi.Services.Utilities;
|
||||
|
||||
public static class TileHelper
|
||||
{
|
||||
public static string GetTileFileName(int row, int col)
|
||||
{
|
||||
string rowStr =
|
||||
row < 26 ? ((char)('A' + row)).ToString() : "A" + ((char)('A' + (row - 26)));
|
||||
return $"{rowStr}{col + 1}.png";
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue