commit 8d4b2bc9438af0253d0573e282e7f3b153b2c4a6 Author: Meizar Date: Sat Jul 26 06:56:07 2025 +0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..97b42be --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +**/bin/** +**/obj/** +**/node_modules/** +**/**.suo +**/**.user \ No newline at end of file diff --git a/.idea/.idea.StitchATon/.idea/.gitignore b/.idea/.idea.StitchATon/.idea/.gitignore new file mode 100644 index 0000000..9ed2af6 --- /dev/null +++ b/.idea/.idea.StitchATon/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/.idea.StitchATon.iml +/modules.xml +/projectSettingsUpdater.xml +/contentModel.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.StitchATon/.idea/encodings.xml b/.idea/.idea.StitchATon/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.StitchATon/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.StitchATon/.idea/indexLayout.xml b/.idea/.idea.StitchATon/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.StitchATon/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.StitchATon/.idea/vcs.xml b/.idea/.idea.StitchATon/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.StitchATon/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/StitchATon.sln b/StitchATon.sln new file mode 100644 index 0000000..9266ba9 --- /dev/null +++ b/StitchATon.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApp", "WebApp\WebApp.csproj", "{C7ECADED-5C93-45BB-8F9F-27DCF06BAA59}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C7ECADED-5C93-45BB-8F9F-27DCF06BAA59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7ECADED-5C93-45BB-8F9F-27DCF06BAA59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7ECADED-5C93-45BB-8F9F-27DCF06BAA59}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7ECADED-5C93-45BB-8F9F-27DCF06BAA59}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/WebApp/Coordinate.cs b/WebApp/Coordinate.cs new file mode 100644 index 0000000..7f5cd29 --- /dev/null +++ b/WebApp/Coordinate.cs @@ -0,0 +1,44 @@ +namespace WebApp; + +internal record Coordinate +{ + public string Name { get; } + public int Row { get; } + public int Col { get; } + public string Path => PATH + Name + ".png"; + + private const string PATH = "D:/tiles1705/"; + + public Coordinate(string name) + { + Name = name; + int row = 0; + int col = 0; + foreach (var item in name) + { + if (item >= 'A') + { + row = row * 26 + (item - 'A' + 1); + } + else if (item >= '1') + { + col = col * 10 + (item - '1' + 1); + } + } + + Row = row; + Col = col; + } + + public Coordinate(int row, int col) + { + Row = row; + Col = col; + Name = Helper.ToLetters(row) + col; + } + + public override string ToString() + { + return Name; + } +} \ No newline at end of file diff --git a/WebApp/Helper.cs b/WebApp/Helper.cs new file mode 100644 index 0000000..4fd0f14 --- /dev/null +++ b/WebApp/Helper.cs @@ -0,0 +1,17 @@ +namespace WebApp; + +public static class Helper +{ + public static string ToLetters(int number) + { + var result = ""; + while (number > 0) + { + number--; // Adjust for 1-based indexing + char letter = (char)('A' + (number % 26)); + result = letter + result; + number /= 26; + } + return result; + } +} \ No newline at end of file diff --git a/WebApp/ImageGenerator.cs b/WebApp/ImageGenerator.cs new file mode 100644 index 0000000..deaf9fe --- /dev/null +++ b/WebApp/ImageGenerator.cs @@ -0,0 +1,130 @@ +using System.Collections.Concurrent; +using OpenCvSharp; + +namespace WebApp; + +public class ImageGenerator +{ + private const string PATH = "D:/tiles1705/"; + + public byte[] GenerateImage(RequestBody requestBody) + { + var start = DateTime.Now; + + string[] inputs = requestBody.CanvasRect.Split(":"); + Coordinate coord0 = new Coordinate(inputs[0]); + Coordinate coord1 = new Coordinate(inputs[1]); + (var matrix, _, _) = GenerateDict(coord0, coord1); + + ConcurrentDictionary rows = new(); + Parallel.ForEach(matrix, pair => + { + Mat row = new Mat(); + List mats = new(); + foreach (var coord in pair.Value) + { + string fileName = PATH + coord.Name + ".png"; + mats.Add(new Mat( fileName ) ); + } + + Cv2.HConcat(mats, row); + rows[pair.Key] = row; + }); + + Mat output = new Mat(); + Cv2.VConcat(rows.Values, output); + + var result = output.ImEncode(); + var end = DateTime.Now; + var elapsed = end - start; + Console.WriteLine($"Elapsed: {elapsed.TotalMilliseconds} ms"); + return result; + } + + public byte[] GenerateImage2(RequestBody requestBody) + { + var start = DateTime.Now; + Coordinate a1 = new Coordinate("A1"); + Mat a1Mat = new Mat(a1.Path); + + string[] inputs = requestBody.CanvasRect.Split(":"); + Coordinate coord0 = new Coordinate(inputs[0]); + Coordinate coord1 = new Coordinate(inputs[1]); + + double scale = requestBody.OutputScale; + + (var matrix, int rowCount, int colCount) = GenerateMatrix(coord0, coord1); + Mat temp = GenerateTempMat(a1Mat, rowCount, colCount); + + Parallel.ForEach(matrix, item => + { + Mat mat = new Mat(item.Path); + Rect rect = new Rect((item.Col - 1) * a1Mat.Cols, (item.Row - 1) * a1Mat.Rows, a1Mat.Cols, a1Mat.Rows); + mat.CopyTo(temp[rect]); + }); + + double newWidth = temp.Width * scale; + double newHeight = temp.Height * scale; + + temp.Resize(new Size(newWidth, newHeight)); + var result = temp.ImEncode(); + var end = DateTime.Now; + var elapsed = end - start; + Console.WriteLine($"Elapsed: {elapsed.TotalMilliseconds} ms"); + return result; + } + + internal (List Matrix, int RowCount, int ColCount) GenerateMatrix(Coordinate coordinate1, Coordinate coordinate2) + { + int minRow = Math.Min(coordinate1.Row, coordinate2.Row); + int maxRow = Math.Max(coordinate1.Row, coordinate2.Row); + int minCol = Math.Min(coordinate1.Col, coordinate2.Col); + int maxCol = Math.Max(coordinate1.Col, coordinate2.Col); + + int rowCount = maxRow - minRow + 1; + int colCount = maxCol - minCol + 1; + + // Initialize collections + List results = new List(rowCount*colCount); + for (int i = 1; i <= rowCount; i++) + { + for (int j = 1; j <= colCount; j++) + { + results.Add(new Coordinate(i,j)); + } + } + + return (results, rowCount, colCount); + } + + internal (ConcurrentDictionary> Matrix, int RowCount, int ColCount) GenerateDict(Coordinate coordinate1, Coordinate coordinate2) + { + ConcurrentDictionary> results = new(); + int minRow = Math.Min(coordinate1.Row, coordinate2.Row); + int maxRow = Math.Max(coordinate1.Row, coordinate2.Row); + int minCol = Math.Min(coordinate1.Col, coordinate2.Col); + int maxCol = Math.Max(coordinate1.Col, coordinate2.Col); + + int rowCount = maxRow - minRow + 1; + int colCount = maxCol - minCol + 1; + + for (int i = 0; i < rowCount; i++) + { + List result = new(); + for (int j = 0; j < colCount; j++) + { + int row = minRow + i; + int col = minCol + j; + result.Add(new Coordinate(row, col)); + } + results[i] = result; + } + + return (results, rowCount, colCount); + } + + internal Mat GenerateTempMat(Mat reference, int rowCount, int colCount) + { + return new Mat(reference.Rows * rowCount, reference.Cols * colCount, reference.Type()); + } +} \ No newline at end of file diff --git a/WebApp/Program.cs b/WebApp/Program.cs new file mode 100644 index 0000000..f972a72 --- /dev/null +++ b/WebApp/Program.cs @@ -0,0 +1,44 @@ + +using Microsoft.OpenApi.Models; +using WebApp; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +// Configure the HTTP request pipeline. +if (builder.Environment.IsDevelopment()) +{ + builder.Services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Stitch a Ton", Description = "Meizar's stitch a ton solution", Version = "v1" }); + }); +} + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Stitch a Ton"); + }); +} + +app.UseHttpsRedirection(); + +app.MapPost("/api/image/generate", + ( RequestBody requestBody ) => +{ + ImageGenerator imageGenerator = new ImageGenerator(); + var png = imageGenerator.GenerateImage2( requestBody ); + return Results.File(png, "image/png", "result.png"); +}) + .WithName("ImageGenerator") + .Produces(StatusCodes.Status200OK, contentType:"image/png"); + +app.Run(); diff --git a/WebApp/Properties/launchSettings.json b/WebApp/Properties/launchSettings.json new file mode 100644 index 0000000..7e66a20 --- /dev/null +++ b/WebApp/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:47070", + "sslPort": 44330 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5184", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7224;http://localhost:5184", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/WebApp/RequestBody.cs b/WebApp/RequestBody.cs new file mode 100644 index 0000000..7421f68 --- /dev/null +++ b/WebApp/RequestBody.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace WebApp; + +public record struct RequestBody( + [property: JsonPropertyName("canvas_rect")] + string CanvasRect, + [property: JsonPropertyName("output_scale")] + double OutputScale ); \ No newline at end of file diff --git a/WebApp/WebApp.csproj b/WebApp/WebApp.csproj new file mode 100644 index 0000000..41d6f81 --- /dev/null +++ b/WebApp/WebApp.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + enable + WebApp + + + + + + + + + diff --git a/WebApp/WebApplication.http b/WebApp/WebApplication.http new file mode 100644 index 0000000..d4de5da --- /dev/null +++ b/WebApp/WebApplication.http @@ -0,0 +1,6 @@ +@WebApplication_HostAddress = http://localhost:5184 + +GET {{WebApplication_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/WebApp/appsettings.Development.json b/WebApp/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/WebApp/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/WebApp/appsettings.json b/WebApp/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/WebApp/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/global.json b/global.json new file mode 100644 index 0000000..9e5e1fd --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "6.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file