From c92383721dcf8a8520087848256ab6ed80185272 Mon Sep 17 00:00:00 2001 From: Renjaya Raga Zenta Date: Sun, 27 Jul 2025 16:02:56 +0700 Subject: [PATCH] initial commit --- .editorconfig | 234 ++++++++++++++++++ .gitignore | 5 + .idea/.idea.stitchaton/.idea/.gitignore | 13 + .idea/.idea.stitchaton/.idea/encodings.xml | 4 + .idea/.idea.stitchaton/.idea/indexLayout.xml | 8 + .idea/.idea.stitchaton/.idea/vcs.xml | 6 + src/Oh.My.Stitcher/Oh.My.Stitcher.csproj | 18 ++ src/Oh.My.Stitcher/Program.cs | 55 ++++ .../Properties/launchSettings.json | 15 ++ src/Oh.My.Stitcher/Stitch.cs | 67 +++++ src/Oh.My.Stitcher/Tile.cs | 105 ++++++++ .../appsettings.Development.json | 8 + src/Oh.My.Stitcher/appsettings.json | 9 + src/Oh.My.Stitcher/wwwroot/index.html | 214 ++++++++++++++++ stitchaton.sln | 23 ++ stitchaton.sln.DotSettings | 2 + 16 files changed, 786 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .idea/.idea.stitchaton/.idea/.gitignore create mode 100644 .idea/.idea.stitchaton/.idea/encodings.xml create mode 100644 .idea/.idea.stitchaton/.idea/indexLayout.xml create mode 100644 .idea/.idea.stitchaton/.idea/vcs.xml create mode 100644 src/Oh.My.Stitcher/Oh.My.Stitcher.csproj create mode 100644 src/Oh.My.Stitcher/Program.cs create mode 100644 src/Oh.My.Stitcher/Properties/launchSettings.json create mode 100644 src/Oh.My.Stitcher/Stitch.cs create mode 100644 src/Oh.My.Stitcher/Tile.cs create mode 100644 src/Oh.My.Stitcher/appsettings.Development.json create mode 100644 src/Oh.My.Stitcher/appsettings.json create mode 100644 src/Oh.My.Stitcher/wwwroot/index.html create mode 100644 stitchaton.sln create mode 100644 stitchaton.sln.DotSettings diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0bdc177 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,234 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +[*] +# Indentation and spacing +indent_size = 2 +indent_style = space +tab_width = 2 +# Prevent automatically adding a UTF-8 BOM (Byte Order Mark) to files +charset = utf-8 + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# New line preferences +end_of_line = lf +insert_final_newline = true + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = true +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false:silent +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_property = false:silent + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent + +# Expression-level preferences +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion + +# Field preferences +dotnet_style_readonly_field = true:suggestion + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:suggestion + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false:suggestion +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion + +# object creation +csharp_style_object_creation_when_type_is_apparent = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_prefer_switch_expression = true:suggestion + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_prefer_static_local_function = true:suggestion +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent + +# Code-block preferences +csharp_prefer_braces = true:silent +csharp_prefer_simple_using_statement = true:suggestion + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = no_change +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = control_flow_statements,expressions +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +### Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interfaces +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.should_be_pascal_case.severity = suggestion +dotnet_naming_rule.should_be_pascal_case.symbols = should_be_pascal_case +dotnet_naming_rule.should_be_pascal_case.style = should_be_pascal_case + +dotnet_naming_rule.private_fields_underscored.severity = suggestion +dotnet_naming_rule.private_fields_underscored.symbols = private_fields +dotnet_naming_rule.private_fields_underscored.style = private_fields + +dotnet_naming_rule.local_variables_should_be_camel_case.severity = suggestion +dotnet_naming_rule.local_variables_should_be_camel_case.symbols = local_variables +dotnet_naming_rule.local_variables_should_be_camel_case.style = local_variables + +dotnet_naming_rule.constants_should_be_screaming_snake_case.severity = suggestion +dotnet_naming_rule.constants_should_be_screaming_snake_case.symbols = constants +dotnet_naming_rule.constants_should_be_screaming_snake_case.style = screaming_snake_case + +### Symbol specifications + +dotnet_naming_symbols.interfaces.applicable_kinds = interface +dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interfaces.required_modifiers = + +dotnet_naming_symbols.should_be_pascal_case.applicable_kinds = class, struct, interface, enum, event, delegate, method, property +dotnet_naming_symbols.should_be_pascal_case.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.should_be_pascal_case.required_modifiers = + +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private + +dotnet_naming_symbols.local_variables.applicable_kinds = local +dotnet_naming_symbols.local_variables.applicable_accessibilities = local + +dotnet_naming_symbols.constants.applicable_kinds = field +dotnet_naming_symbols.constants.applicable_accessibilities = private +dotnet_naming_symbols.constants.required_modifiers = const + +### Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.should_be_pascal_case.required_prefix = +dotnet_naming_style.should_be_pascal_case.required_suffix = +dotnet_naming_style.should_be_pascal_case.word_separator = +dotnet_naming_style.should_be_pascal_case.capitalization = pascal_case + +dotnet_naming_style.private_fields.required_prefix = _ +dotnet_naming_style.private_fields.capitalization = camel_case + +dotnet_naming_style.screaming_snake_case.capitalization = all_upper +dotnet_naming_style.screaming_snake_case.word_separator = _ + +dotnet_naming_style.local_variables.capitalization = camel_case diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..add57be --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ \ 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..2c1911e --- /dev/null +++ b/.idea/.idea.stitchaton/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/projectSettingsUpdater.xml +/.idea.stitchaton.iml +/modules.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/src/Oh.My.Stitcher/Oh.My.Stitcher.csproj b/src/Oh.My.Stitcher/Oh.My.Stitcher.csproj new file mode 100644 index 0000000..ca91574 --- /dev/null +++ b/src/Oh.My.Stitcher/Oh.My.Stitcher.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + true + true + + + + + + + + + + diff --git a/src/Oh.My.Stitcher/Program.cs b/src/Oh.My.Stitcher/Program.cs new file mode 100644 index 0000000..ca98db0 --- /dev/null +++ b/src/Oh.My.Stitcher/Program.cs @@ -0,0 +1,55 @@ +using System.IO.Pipelines; +using Microsoft.AspNetCore.Http.Json; +using NetVips; +using Oh.My.Stitcher; +using Validation; +using ZLogger; + +WebApplicationBuilder builder = WebApplication.CreateSlimBuilder(args); +builder.Logging.ClearProviders().AddZLoggerConsole(); +builder.Services.Configure(options => +{ + options.SerializerOptions.TypeInfoResolver = StitchSerializerContext.Default; +}); +WebApplication app = builder.Build(); + +ILoggerFactory loggerFactory = app.Services.GetRequiredService(); +ILogger logger = loggerFactory.CreateLogger(); + +string? tilesDirectory = Environment.GetEnvironmentVariable("ASSET_PATH_RO"); + +// sanity check +Assumes.NotNullOrEmpty(tilesDirectory); +Assumes.True(File.Exists(Path.Combine(tilesDirectory, "A1.png"))); +Assumes.True(File.Exists(Path.Combine(tilesDirectory, "AE55.png"))); + +app.UseDefaultFiles(); +app.UseStaticFiles(); +app.MapPost("/api/image/generate", (Stitch request) => +{ + Pipe pipe = new(); + _ = Task.Run(async () => + { + List images = []; + try + { + using Image image = Tile.Create(in request, tilesDirectory, images, logger); + image.WriteToStream(pipe.Writer.AsStream(), ".png"); + } + catch( Exception e ) + { + logger.ZLogError(e, $"Error when generating image"); + using Image errorImage = Tile.CreateError(e); + errorImage.WriteToStream(pipe.Writer.AsStream(), ".png"); + } + finally + { + foreach( Image img in images ) + img.Dispose(); + await pipe.Writer.CompleteAsync(); + } + }); + return Results.Stream(pipe.Reader.AsStream(), "image/png"); +}); + +app.Run(); diff --git a/src/Oh.My.Stitcher/Properties/launchSettings.json b/src/Oh.My.Stitcher/Properties/launchSettings.json new file mode 100644 index 0000000..85495e1 --- /dev/null +++ b/src/Oh.My.Stitcher/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5108", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASSET_PATH_RO": "/home/formulatrix/Downloads/tiles1705" + } + } + } +} diff --git a/src/Oh.My.Stitcher/Stitch.cs b/src/Oh.My.Stitcher/Stitch.cs new file mode 100644 index 0000000..8c4076f --- /dev/null +++ b/src/Oh.My.Stitcher/Stitch.cs @@ -0,0 +1,67 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Oh.My.Stitcher; + +public readonly record struct CropOffset(float X, float Y); +public readonly record struct CropSize(float Width, float Height); + +public readonly record struct Stitch( + [property: JsonPropertyName("canvas_rect")] + string CanvasRect, + [property: JsonPropertyName("crop_offset"), JsonConverter(typeof(CropOffsetConverter))] + CropOffset CropOffset, + [property: JsonPropertyName("crop_size"), JsonConverter(typeof(CropSizeConverter))] + CropSize CropSize, + [property: JsonPropertyName("output_scale")] + float OutputScale +); + +[JsonSerializable(typeof(Stitch))] +internal partial class StitchSerializerContext : JsonSerializerContext; + +public class CropOffsetConverter : JsonConverter +{ + public override CropOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if( reader.TokenType != JsonTokenType.StartArray ) throw new JsonException(); + reader.Read(); + float x = reader.GetSingle(); + reader.Read(); + float y = reader.GetSingle(); + reader.Read(); + if( reader.TokenType != JsonTokenType.EndArray ) throw new JsonException(); + return new CropOffset(x, y); + } + + public override void Write(Utf8JsonWriter writer, CropOffset value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + writer.WriteNumberValue(value.X); + writer.WriteNumberValue(value.Y); + writer.WriteEndArray(); + } +} + +public class CropSizeConverter : JsonConverter +{ + public override CropSize Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if( reader.TokenType != JsonTokenType.StartArray ) throw new JsonException(); + reader.Read(); + float width = reader.GetSingle(); + reader.Read(); + float height = reader.GetSingle(); + reader.Read(); + if( reader.TokenType != JsonTokenType.EndArray ) throw new JsonException(); + return new CropSize(width, height); + } + + public override void Write(Utf8JsonWriter writer, CropSize value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + writer.WriteNumberValue(value.Width); + writer.WriteNumberValue(value.Height); + writer.WriteEndArray(); + } +} diff --git a/src/Oh.My.Stitcher/Tile.cs b/src/Oh.My.Stitcher/Tile.cs new file mode 100644 index 0000000..3400122 --- /dev/null +++ b/src/Oh.My.Stitcher/Tile.cs @@ -0,0 +1,105 @@ +// ReSharper disable ReplaceSliceWithRangeIndexer + +using NetVips; +using ZLogger; + +namespace Oh.My.Stitcher; + +public static class Tile +{ + public static Image Create(in Stitch request, string tilesDirectory, List images, ILogger logger) + { + if( !TryParseRect(request.CanvasRect, out int minRow, out int maxRow, out int minCol, out int maxCol) ) + throw new ArgumentException($"Invalid canvas_rect: '{request.CanvasRect}'"); + + logger.ZLogDebug( + $"rect: {request.CanvasRect}, minRow: {minRow}, maxRow: {maxRow}, minCol: {minCol}, maxCol: {maxCol}"); + int width = maxCol - minCol + 1; + for( int row = minRow; row <= maxRow; row++ ) + for( int col = minCol; col <= maxCol; col++ ) + images.Add(Image.NewFromFile(FullPath(tilesDirectory, row, col))); + + using var canvasImage = Image.Arrayjoin(images.ToArray(), width); + int cropLeft = (int)( canvasImage.Width * request.CropOffset.X ); + int cropTop = (int)( canvasImage.Height * request.CropOffset.Y ); + int cropWidth = (int)( canvasImage.Width * request.CropSize.Width ); + int cropHeight = (int)( canvasImage.Height * request.CropSize.Height ); + + return canvasImage.Crop(cropLeft, cropTop, cropWidth, cropHeight).Resize(request.OutputScale); + } + + public static Image CreateError(Exception e) + { + const int padding = 20; + using var text = Image.Text($"Error:\n{e.Message}", dpi: 96, align: Enums.Align.Low); + return text.Embed(padding, padding, text.Width + ( 2 * padding ), text.Height + ( 2 * padding )); + } + + private static string FullPath(string directory, int row, int col) + { + string letterPart = ""; + while (row > 0) + { + int remainder = (row - 1) % 26; + letterPart = (char)('A' + remainder) + letterPart; + row = (row - 1) / 26; + } + string fileName = $"{letterPart}{col}.png"; + return Path.Combine(directory, fileName); + } + + private static bool TryParseRect(string rect, out int minRow, out int maxRow, out int minCol, out int maxCol) + { + minRow = maxRow = minCol = maxCol = 0; + string[] corners = rect.Split(':'); + switch( corners.Length ) + { + case 1: + { + if( !TryParseName(corners[0], out int r, out int c) ) + return false; + + minRow = maxRow = r; + minCol = maxCol = c; + return true; + } + + case 2: + { + if( !TryParseName(corners[0], out int r1, out int c1) || !TryParseName(corners[1], out int r2, out int c2) ) + return false; + + minRow = Math.Min(r1, r2); + maxRow = Math.Max(r1, r2); + minCol = Math.Min(c1, c2); + maxCol = Math.Max(c1, c2); + return true; + } + + default: + return false; + } + } + + private static bool TryParseName(string name, out int row, out int col) + { + row = col = 0; + ReadOnlySpan span = name.AsSpan().Trim(); + int splitIndex = span.IndexOfAnyInRange('0', '9'); + if( splitIndex <= 0 ) + return false; + if( !int.TryParse(span.Slice(splitIndex), out col)) + return false; + int letter = 0; + foreach( char c in span.Slice(0, splitIndex) ) + { + char upper = char.ToUpperInvariant(c); + if( upper is < 'A' or > 'Z' ) + return false; + letter = letter * 26 + ( upper - 'A' + 1 ); + } + + row = letter; + return true; + } +} diff --git a/src/Oh.My.Stitcher/appsettings.Development.json b/src/Oh.My.Stitcher/appsettings.Development.json new file mode 100644 index 0000000..a6e86ac --- /dev/null +++ b/src/Oh.My.Stitcher/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Oh.My.Stitcher/appsettings.json b/src/Oh.My.Stitcher/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/src/Oh.My.Stitcher/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/Oh.My.Stitcher/wwwroot/index.html b/src/Oh.My.Stitcher/wwwroot/index.html new file mode 100644 index 0000000..ad64db4 --- /dev/null +++ b/src/Oh.My.Stitcher/wwwroot/index.html @@ -0,0 +1,214 @@ + + + + + + Oh My Stitcher! + + + +
+

Oh My Stitcher!

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+ + + diff --git a/stitchaton.sln b/stitchaton.sln new file mode 100644 index 0000000..6f825ef --- /dev/null +++ b/stitchaton.sln @@ -0,0 +1,23 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AEE9B1D3-6AD8-4EEE-800B-2873B0BB78DD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Oh.My.Stitcher", "src\Oh.My.Stitcher\Oh.My.Stitcher.csproj", "{9AB5F809-0D6A-4906-AB89-DC797FB7CF42}" +EndProject +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {9AB5F809-0D6A-4906-AB89-DC797FB7CF42} = {AEE9B1D3-6AD8-4EEE-800B-2873B0BB78DD} + {A9CC8F78-CB38-4986-9480-5FB4556F1356} = {AEE9B1D3-6AD8-4EEE-800B-2873B0BB78DD} + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9AB5F809-0D6A-4906-AB89-DC797FB7CF42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9AB5F809-0D6A-4906-AB89-DC797FB7CF42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AB5F809-0D6A-4906-AB89-DC797FB7CF42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9AB5F809-0D6A-4906-AB89-DC797FB7CF42}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/stitchaton.sln.DotSettings b/stitchaton.sln.DotSettings new file mode 100644 index 0000000..d094dec --- /dev/null +++ b/stitchaton.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file