diff --git a/GeneratorServer.sln b/GeneratorServer.sln
new file mode 100644
index 0000000..872b78c
--- /dev/null
+++ b/GeneratorServer.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.10.35027.167
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeneratorServer", "GeneratorServer\GeneratorServer.csproj", "{3CD16E16-87B1-4B3A-A482-06BC2FF38406}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HTTP", "HTTP\HTTP.csproj", "{BA36BD06-3E6F-4C71-8FBB-3F17AD60A7B1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PNG", "PNG\PNG.csproj", "{30C7F87B-1C7C-49E9-8563-4D845D204CC0}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3CD16E16-87B1-4B3A-A482-06BC2FF38406}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3CD16E16-87B1-4B3A-A482-06BC2FF38406}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3CD16E16-87B1-4B3A-A482-06BC2FF38406}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3CD16E16-87B1-4B3A-A482-06BC2FF38406}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BA36BD06-3E6F-4C71-8FBB-3F17AD60A7B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BA36BD06-3E6F-4C71-8FBB-3F17AD60A7B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BA36BD06-3E6F-4C71-8FBB-3F17AD60A7B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BA36BD06-3E6F-4C71-8FBB-3F17AD60A7B1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {30C7F87B-1C7C-49E9-8563-4D845D204CC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {30C7F87B-1C7C-49E9-8563-4D845D204CC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {30C7F87B-1C7C-49E9-8563-4D845D204CC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {30C7F87B-1C7C-49E9-8563-4D845D204CC0}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {5755C00D-E811-4169-9B74-4EEE07099BE6}
+ EndGlobalSection
+EndGlobal
diff --git a/GeneratorServer/GeneratorServer.csproj b/GeneratorServer/GeneratorServer.csproj
new file mode 100644
index 0000000..6e9e24e
--- /dev/null
+++ b/GeneratorServer/GeneratorServer.csproj
@@ -0,0 +1,29 @@
+
+
+ net8.0
+ disable
+ enable
+
+
+
+ Exe
+
+
+
+ none
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/GeneratorServer/Program.cs b/GeneratorServer/Program.cs
new file mode 100644
index 0000000..f9a4746
--- /dev/null
+++ b/GeneratorServer/Program.cs
@@ -0,0 +1,89 @@
+using System.Security.Cryptography.X509Certificates;
+using Uwaa.HTTP;
+using Uwaa.HTTP.Routing;
+using Uwaa.PNG;
+
+namespace GeneratorServer;
+
+internal class Program
+{
+ static void Main(string[] args)
+ {
+ Console.WriteLine("Creating router");
+ Router router = CreateRouter();
+
+ Console.WriteLine("Starting HTTP");
+ HttpServer http = new HttpServer(80, null, router);
+ http.Start();
+
+ if (!File.Exists("certs/certificate.crt") || !File.Exists("certs/private.key"))
+ {
+ #if RELEASE
+ Console.WriteLine("Warning: No HTTPS, \"certificate.crt\" and/or \"private.key\" are missing in \"certs\" directory.");
+ #endif
+ }
+ else
+ {
+ Console.WriteLine("Starting HTTPS");
+ HttpServer https = new HttpServer(443, X509Certificate2.CreateFromPemFile("certs/certificate.crt", "certs/private.key"), router);
+ https.Start();
+ }
+
+ Console.WriteLine("Ready");
+ Task.Delay(-1).Wait();
+ }
+
+ static Router CreateRouter()
+ {
+ var router = new Router();
+ router.Add("background", new BGGenerator());
+ router.Add(new StaticEndpoint(HttpResponse.NotFound()));
+ return router;
+ }
+}
+
+class BGGenerator : RouterBase
+{
+ public override HttpMethod Method => HttpMethod.GET;
+
+ public override int Arguments => 0;
+
+ protected override Task GetResponseInner(HttpRequest req, HttpClientInfo info, ArraySegment path)
+ {
+ MIMEType contentType = new MIMEType("image", "png");
+ if (!req.CanAccept(contentType))
+ return Task.FromResult(HttpResponse.NotAcceptable())!;
+
+ const int width = 100;
+ const int height = 100;
+ PngBuilder png = PngBuilder.Create(width, height, false);
+
+ Pixel left = new Pixel((byte)Random.Shared.Next(0, 256), (byte)Random.Shared.Next(0, 256), (byte)Random.Shared.Next(0, 256));
+ Pixel right = new Pixel((byte)Random.Shared.Next(0, 256), (byte)Random.Shared.Next(0, 256), (byte)Random.Shared.Next(0, 256));
+ Pixel top = new Pixel((byte)Random.Shared.Next(0, 256), (byte)Random.Shared.Next(0, 256), (byte)Random.Shared.Next(0, 256));
+ Pixel bottom = new Pixel((byte)Random.Shared.Next(0, 256), (byte)Random.Shared.Next(0, 256), (byte)Random.Shared.Next(0, 256));
+ for (int y = 0; y < height; y++)
+ for (int x = 0; x < width; x++)
+ {
+ Pixel px = Lerp(Lerp(left, right, (float)x / width), Lerp(top, bottom, (float)y / height), 0.5f);
+ png.SetPixel(px, x, y);
+ }
+
+ return Task.FromResult(HttpResponse.OK(new HttpContent(contentType, png.Save())))!;
+ }
+
+ static Pixel Lerp(Pixel min, Pixel max, float factor)
+ {
+ return new Pixel(Lerp(min.R, max.R, factor), Lerp(min.G, max.G, factor), Lerp(min.B, max.B, factor));
+ }
+
+ static byte Lerp(byte min, byte max, float factor)
+ {
+ return (byte)(min + (max - min) * factor);
+ }
+
+ static float Lerp(float min, float max, float factor)
+ {
+ return min + (max - min) * factor;
+ }
+}
\ No newline at end of file
diff --git a/GeneratorServer/README.md b/GeneratorServer/README.md
new file mode 100644
index 0000000..0bc291b
--- /dev/null
+++ b/GeneratorServer/README.md
@@ -0,0 +1 @@
+HTTP server which generates images on request
\ No newline at end of file