diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 0000000..66e7ddd --- /dev/null +++ b/NuGet.config @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/.idea/.idea.Kynareth/.idea/.gitignore b/src/.idea/.idea.Kynareth/.idea/.gitignore new file mode 100644 index 0000000..a02bb95 --- /dev/null +++ b/src/.idea/.idea.Kynareth/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/contentModel.xml +/projectSettingsUpdater.xml +/.idea.Kynareth.iml +/modules.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/src/.idea/.idea.Kynareth/.idea/.name b/src/.idea/.idea.Kynareth/.idea/.name new file mode 100644 index 0000000..09d1032 --- /dev/null +++ b/src/.idea/.idea.Kynareth/.idea/.name @@ -0,0 +1 @@ +Kynareth \ No newline at end of file diff --git a/src/.idea/.idea.Kynareth/.idea/efCoreCommonOptions.xml b/src/.idea/.idea.Kynareth/.idea/efCoreCommonOptions.xml new file mode 100644 index 0000000..84f5122 --- /dev/null +++ b/src/.idea/.idea.Kynareth/.idea/efCoreCommonOptions.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.Kynareth/.idea/efCoreDialogsState.xml b/src/.idea/.idea.Kynareth/.idea/efCoreDialogsState.xml new file mode 100644 index 0000000..cb1c70d --- /dev/null +++ b/src/.idea/.idea.Kynareth/.idea/efCoreDialogsState.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.Kynareth/.idea/indexLayout.xml b/src/.idea/.idea.Kynareth/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/src/.idea/.idea.Kynareth/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.Kynareth/.idea/vcs.xml b/src/.idea/.idea.Kynareth/.idea/vcs.xml new file mode 100644 index 0000000..64713b8 --- /dev/null +++ b/src/.idea/.idea.Kynareth/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/.idea/config/applicationhost.config b/src/.idea/config/applicationhost.config new file mode 100644 index 0000000..6dbfa84 --- /dev/null +++ b/src/.idea/config/applicationhost.config @@ -0,0 +1,997 @@ + + + + + + +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Attributes/EndpointMetrics.cs b/src/Attributes/EndpointMetrics.cs new file mode 100644 index 0000000..6ac9e7c --- /dev/null +++ b/src/Attributes/EndpointMetrics.cs @@ -0,0 +1,39 @@ +using Exsersewo.Common.Extensions; +using Kynareth.Helpers; +using Kynareth.Models; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Primitives; + +namespace Kynareth.Attributes; + +public class EndpointMetricsAttribute : ActionFilterAttribute +{ + public string EndpointMetricName; + + public EndpointMetricsAttribute(string metricName) + { + EndpointMetricName = metricName; + } + + public override void OnActionExecuted(ActionExecutedContext context) + { + base.OnActionExecuted(context); + + string key = context.HttpContext.Request.GetToken(); + + if (string.IsNullOrWhiteSpace(key)) return; + + using var database = context.HttpContext.RequestServices.GetRequiredService(); + + var token = database.Tokens.FirstOrDefault(x => x.Token.Equals(key)); + + if (token is null) return; + + token.ApiCalls.Add(new() { + TimeStamp = DateTime.UtcNow, + Route = EndpointMetricName + }); + + database.SaveChanges(); + } +} \ No newline at end of file diff --git a/src/Attributes/RequireTokenAttribute.cs b/src/Attributes/RequireTokenAttribute.cs new file mode 100644 index 0000000..15f3be7 --- /dev/null +++ b/src/Attributes/RequireTokenAttribute.cs @@ -0,0 +1,26 @@ +using System.Net; +using Exsersewo.Common.Utilities; +using Kynareth.Helpers; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Kynareth.Attributes; + +[AttributeUsage(AttributeTargets.Class)] +public class RequireTokenAttribute : ActionFilterAttribute +{ + public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + if (await RequestHelper.IsRequestAuthenticatedAsync(context.HttpContext.Request)) + { + await next(); + return; + } + + context.HttpContext.Response.ContentType = "application/json"; + context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + + var invalidRequest = EventResult.FromFailure("Unauthorized request"); + await context.HttpContext.Response.WriteAsync(invalidRequest.ToJson()); + return; + } +} \ No newline at end of file diff --git a/src/Controllers/BaseController.cs b/src/Controllers/BaseController.cs new file mode 100644 index 0000000..e9525d3 --- /dev/null +++ b/src/Controllers/BaseController.cs @@ -0,0 +1,22 @@ +using Kynareth.Attributes; +using Microsoft.AspNetCore.Mvc; + +namespace Kynareth.Controllers; + +[ApiController] +[RequireToken] +public class BaseController : ControllerBase +{ + protected readonly IWebHostEnvironment _appEnvironment; + + protected readonly ILogger _logger; + + protected readonly IConfiguration _configuration; + + public BaseController(IConfiguration configuration, ILoggerFactory loggerFactory, IWebHostEnvironment appEnvironment) : base() + { + _configuration = configuration; + _appEnvironment = appEnvironment; + _logger = loggerFactory.CreateLogger(); + } +} \ No newline at end of file diff --git a/src/Controllers/ImageGenerationController.cs b/src/Controllers/ImageGenerationController.cs new file mode 100644 index 0000000..0ce57f6 --- /dev/null +++ b/src/Controllers/ImageGenerationController.cs @@ -0,0 +1,53 @@ +using System.Text.Json; +using Exsersewo.Common.Utilities; +using ImageMagick; +using Kynareth.Attributes; +using Kynareth.Helpers; +using Kynareth.Managers; +using Kynareth.Models; +using Microsoft.AspNetCore.Mvc; + +namespace Kynareth.Controllers; + +[Route("image/generate")] +public class ImageGenerationController : BaseController +{ + public ImageGenerationController(IConfiguration configuration, ILoggerFactory loggerFactory, IWebHostEnvironment environment) : base(configuration, loggerFactory, environment) + { } + + [HttpGet] + [EndpointMetrics("image.generate.get.templates")] + public Task GetTemplates() + { + string serialized = JsonSerializer.Serialize(ImageManager.GetGenerationEndpoints()); + + return Task.FromResult(HttpContext.Send(EventResult.FromSuccess())); + } + + [HttpGet("{template}")] + [EndpointMetrics("image.generate.generate")] + public async Task GetImageAsync(string template, [FromQuery(Name = "source")] string[] sources, [FromQuery] string variant = null) + { + try + { + var result = await ImageManager.GenerateImageAsync(template, variant, sources); + + if (result is byte[] data) + { + var image = new MemoryStream(data); + + return HttpContext.SendStream(image, "image/png"); + } + + return result as IResult; + } + catch (ArgumentException ex) + { + return HttpContext.Send( + EventResult.FromFailureException($"Can't parse result. Reason:\"{ex.Message}\"", ex), + System.Net.HttpStatusCode.InternalServerError + ); + } + } + +} \ No newline at end of file diff --git a/src/Controllers/ImageManipulationController.cs b/src/Controllers/ImageManipulationController.cs new file mode 100644 index 0000000..b1bf0cc --- /dev/null +++ b/src/Controllers/ImageManipulationController.cs @@ -0,0 +1,76 @@ +using Exsersewo.Common.Utilities; +using Kynareth.Attributes; +using Kynareth.Helpers; +using Kynareth.Managers; +using Kynareth.Models; +using Microsoft.AspNetCore.Mvc; + +namespace Kynareth.Controllers; + +[Route("image/manipulation")] +public class ImageManipulationController : BaseController +{ + public ImageManipulationController(IConfiguration configuration, ILoggerFactory loggerFactory, IWebHostEnvironment appEnvironment) : base(configuration, loggerFactory, appEnvironment) + { } + + /// ---------- /// + /// MAGIK /// + /// ---------- /// + [HttpGet("magik")] + [EndpointMetrics("image.manipulation.magik")] + public async Task MagikImage([FromQuery] string image) + { + try + { + if (!string.IsNullOrWhiteSpace(image)) + { + var (data, contentType) = await ImageManager.GetLiquidRescaledImageAsync(image); + + return HttpContext.SendStream(data, contentType); + } + + return HttpContext.Send(EventResult.FromFailure("No image has been provided"), System.Net.HttpStatusCode.InternalServerError); + } + catch (ArgumentException ex) + { + _logger.LogCritical(ex.Message, ex); + + return HttpContext.Send(EventResult.FromFailure("Can't parse result"), System.Net.HttpStatusCode.InternalServerError); + } + } + + /// ------------- /// + /// TOP TEXT /// + /// /// + /// BOTTOM TEXT /// + /// ------------- /// + [HttpGet("{template}")] + [EndpointMetrics("image.manipulation.generate")] + public async Task GenerateMemeAsync(string template, [FromQuery] string[] text) + { + try + { + if (!string.IsNullOrWhiteSpace(template)) + { + var result = await ImageManager.GenerateMemeImageAsync(template, text); + + if (result is byte[] data) + { + var image = new MemoryStream(data); + + return HttpContext.SendStream(image, "image/png"); + } + + return result as IResult; + } + + return HttpContext.Send(EventResult.FromFailure("No template has been provided"), System.Net.HttpStatusCode.InternalServerError); + } + catch (ArgumentException ex) + { + _logger.LogCritical(ex.Message, ex); + + return HttpContext.Send(EventResult.FromFailure("Can't parse result"), System.Net.HttpStatusCode.InternalServerError); + } + } +} \ No newline at end of file diff --git a/src/Helpers/RequestHelper.cs b/src/Helpers/RequestHelper.cs new file mode 100644 index 0000000..9c3c730 --- /dev/null +++ b/src/Helpers/RequestHelper.cs @@ -0,0 +1,39 @@ +using Exsersewo.Common.Extensions; +using Kynareth.Models; +using Microsoft.Extensions.Primitives; + +namespace Kynareth.Helpers; + +public static class RequestHelper +{ + public static string GetUrlHostname(this HttpContext context) + { + var origin = context.Request.Headers["X-Original-Host"]; + + return origin.Count > 0 ? origin[0] : context.Request.Host.Host; + } + + public static async Task IsRequestAuthenticatedAsync(this HttpRequest request) + { + string key = request.GetToken(); + + if (string.IsNullOrWhiteSpace(key)) return false; + + await using var database = request.HttpContext.RequestServices.GetRequiredService(); + + var tokenEntry = database.Tokens.FirstOrDefault(token => token.Token.Equals(key)); + + if (tokenEntry is not { IsValid: true }) return false; + + return true; + } + + public static string GetToken(this HttpRequest request) + { + if (!request.Headers.TryGetValue("Authorization", out StringValues authKey)) return string.Empty; + + if (!authKey.ToArray().AnyStartWith("Bearer", out string key)) return string.Empty; + + return key.Replace("Bearer ", ""); + } +} \ No newline at end of file diff --git a/src/Helpers/ResponseHelper.cs b/src/Helpers/ResponseHelper.cs new file mode 100644 index 0000000..e39cbec --- /dev/null +++ b/src/Helpers/ResponseHelper.cs @@ -0,0 +1,34 @@ +using System.Net; +using System.Text.Json; +using System.Text.Json.Serialization; +using Exsersewo.Common.Converters; +using Exsersewo.Common.Utilities; +using Microsoft.AspNetCore.Mvc; + +namespace Kynareth.Helpers; + +public static class ResponseHelper +{ + private static readonly JsonSerializerOptions SerializerOptions = new() + { + NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + WriteIndented = true, + Converters = { new LongToStringConverter() } + }; + + public static object Send(this HttpContext Context, EventResult result, HttpStatusCode status = HttpStatusCode.OK) + { + Context.Response.ContentType = "application/json"; + Context.Response.StatusCode = (int)status; + + return new JsonResult(result, SerializerOptions); + } + + public static object SendStream(this HttpContext Context, T stream, string contentType, HttpStatusCode status = HttpStatusCode.OK) where T : Stream + => new StreamResult(stream) + { + ContentType = contentType, + StatusCode = (int)status + }; +} \ No newline at end of file diff --git a/src/Helpers/StreamResult.cs b/src/Helpers/StreamResult.cs new file mode 100644 index 0000000..ec127d6 --- /dev/null +++ b/src/Helpers/StreamResult.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace Kynareth.Helpers; + +public class StreamResult : ActionResult, IStatusCodeActionResult, IActionResult +{ + public string ContentType { get; set; } + public int? StatusCode { get; set; } + public Stream Value { get; set; } + + public StreamResult(Stream data) + { + Value = data; + } + + public override void ExecuteResult(ActionContext context) + => ExecuteResultAsync(context).ConfigureAwait(false); + + public override async Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var response = context.HttpContext.Response; + + response.StatusCode = StatusCode ?? 200; + + response.ContentType = !string.IsNullOrEmpty(ContentType) + ? ContentType + : "image/png"; + + await Value.CopyToAsync(response.Body); + + await Value.FlushAsync(); + + await Value.DisposeAsync(); + + + context.HttpContext.Response.RegisterForDisposeAsync(Value); + + GC.Collect(); + } +} \ No newline at end of file diff --git a/src/Kynareth.csproj b/src/Kynareth.csproj new file mode 100644 index 0000000..1442113 --- /dev/null +++ b/src/Kynareth.csproj @@ -0,0 +1,38 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + Always + + + + + + + + + <_ContentIncludedByDefault Remove="data\templates.json" /> + + + diff --git a/src/Kynareth.csproj.DotSettings b/src/Kynareth.csproj.DotSettings new file mode 100644 index 0000000..6ab219e --- /dev/null +++ b/src/Kynareth.csproj.DotSettings @@ -0,0 +1,5 @@ + + True + True + True + True \ No newline at end of file diff --git a/src/Kynareth.sln b/src/Kynareth.sln new file mode 100644 index 0000000..e23b4ea --- /dev/null +++ b/src/Kynareth.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kynareth", "Kynareth.csproj", "{CEE9FA49-AAE3-49AA-997A-DD3D5BAB9CF6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CEE9FA49-AAE3-49AA-997A-DD3D5BAB9CF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CEE9FA49-AAE3-49AA-997A-DD3D5BAB9CF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CEE9FA49-AAE3-49AA-997A-DD3D5BAB9CF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CEE9FA49-AAE3-49AA-997A-DD3D5BAB9CF6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/Managers/ImageManager.Generation.cs b/src/Managers/ImageManager.Generation.cs new file mode 100644 index 0000000..1b93faa --- /dev/null +++ b/src/Managers/ImageManager.Generation.cs @@ -0,0 +1,117 @@ +using Exsersewo.Common.Utilities; +using ImageMagick; +using Kynareth.Models; + +namespace Kynareth.Managers; + +public static partial class ImageManager +{ + private static List GenerationEndpoints; + + private static string GenerationTemplateBaseFolder; + + static void ConfigureGeneration(IConfiguration configuration, IWebHostEnvironment _appEnvironment) + { + GenerationTemplateBaseFolder = Path.Combine(_appEnvironment.WebRootPath, configuration.GetValue("ShitpostBot:Folder")); + GenerationEndpoints = configuration.GetSection("ShitpostBot:Templates").Get>(); + } + + public static async Task GenerateImageAsync(string template, string variant, string[] sources) + { + ImageGenerationImage imageTemplate = null; + + var endpoint = GenerationEndpoints.FirstOrDefault(e => e.Name.ToLowerInvariant().Equals(template.ToLowerInvariant())); + + if (endpoint is null) + { + var ex = new ArgumentException($"Couldn't find endpoint named \"{template}\"", nameof(template)); + + return GetResult(StatusCodes.Status404NotFound, EventResult.FromFailureException(ex.Message, ex)); + } + + if (endpoint.SourcesRequired > sources.Length) + { + var ex = new ArgumentException("Not enough sources provided", nameof(sources)); + + return GetResult(StatusCodes.Status400BadRequest, EventResult.FromFailureException(ex.Message, ex)); + } + + if (!string.IsNullOrWhiteSpace(variant)) + { + imageTemplate = endpoint.Variants? + .FirstOrDefault(v => v.Name.ToLowerInvariant().Equals(variant.ToLowerInvariant())); + + if (imageTemplate is null) + { + var ex = new ArgumentException( + $"Invalid variant given \"{variant}\" for endpoint \"{template}\"", + nameof(variant) + ); + + return GetResult(StatusCodes.Status400BadRequest, EventResult.FromFailureException(ex.Message, ex)); + } + } + else + { + imageTemplate = endpoint; + } + + string image = Path.Combine(GenerationTemplateBaseFolder, !string.IsNullOrWhiteSpace(endpoint.Folder) ? endpoint.Folder : "", imageTemplate.Image); + + using MagickImage templateImage = new(System.IO.File.ReadAllBytes(image)); + using MagickImage baseImage = new(MagickColors.Transparent, templateImage.Width, templateImage.Height); + + baseImage.Format = MagickFormat.Png; + + if (imageTemplate.PlaceUnder) + { + await AddSourcesToImage(baseImage, imageTemplate, sources); + } + + baseImage.Composite(templateImage, Gravity.Northwest, CompositeOperator.Over); + + if (!imageTemplate.PlaceUnder) + { + await AddSourcesToImage(baseImage, imageTemplate, sources); + } + + return baseImage.ToByteArray(); + } + + static async Task AddSourcesToImage(MagickImage image, ImageGenerationImage template, IEnumerable sources) + { + int img = 0; + foreach (var src in sources) + { + var position = template.Positions.ElementAtOrDefault(img); + + using MagickImage srcImg = new MagickImage(await HttpWebClient.GetStreamAsync(new Uri(src))); + + srcImg.Resize(position.Width, position.Height); + + if (position.Rotation != 0) + { + srcImg.Rotate(template.Rotate > 0 ? -position.Rotation : position.Rotation); + } + + string background = !string.IsNullOrWhiteSpace(position.Background) ? $"#{position.Background}" : MagickColors.Transparent.ToHexString(); + + var tmp = new MagickImage($"xc:{background}", position.Width, position.Height); + + tmp.Composite(srcImg, Gravity.Center, 0, 0, CompositeOperator.Over); + + image.Composite(tmp, Gravity.Northwest, position.X, position.Y, CompositeOperator.Over); + + img++; + } + } + + public static IEnumerable GetGenerationEndpoints() + { + return GenerationEndpoints.Select(e => new ImageGenerationEndpointRead + { + Name = e.Name, + SourcesRequired = e.SourcesRequired + }); + } +} \ No newline at end of file diff --git a/src/Managers/ImageManager.Magik.cs b/src/Managers/ImageManager.Magik.cs new file mode 100644 index 0000000..57ed272 --- /dev/null +++ b/src/Managers/ImageManager.Magik.cs @@ -0,0 +1,58 @@ +using System.Text; +using Exsersewo.Common.Utilities; +using ImageMagick; + +namespace Kynareth.Managers; + +public static partial class ImageManager +{ + readonly static Encoding s_defaultEncoding = Encoding.Unicode; + const double c_defaultFontSize = 20; + + public static async Task<(MemoryStream data, string contentType)> GetLiquidRescaledImageAsync(string image) + { + var imageStream = await HttpWebClient.GetStreamAsync(new Uri(image)).ConfigureAwait(false); + + using var magikImage = new MagickImageCollection(imageStream); + + Parallel.ForEach(magikImage, LiquidResizeFrame); + + MemoryStream stream = new(); + + magikImage.Write(stream); + + stream.Position = 0; + + magikImage.Dispose(); + + string cType = "image/png"; + + if (magikImage.Count > 1) cType = "image/gif"; + + return (stream, cType); + } + + static void LiquidResizeFrame(IMagickImage frame) + { + int originalWidth = frame.Width; + int originalHeight = frame.Height; + + frame.Resize(800, 600); + + frame.LiquidRescale(new MagickGeometry + { + X = 1, + Width = Convert.ToInt32(frame.Width * 0.5), + Height = Convert.ToInt32(frame.Height * 0.5) + }); + + frame.LiquidRescale(new MagickGeometry + { + X = 2, + Width = Convert.ToInt32(frame.Width * 1.5), + Height = Convert.ToInt32(frame.Height * 1.5) + }); + + frame.Resize(originalWidth, originalHeight); + } +} \ No newline at end of file diff --git a/src/Managers/ImageManager.Manipulation.cs b/src/Managers/ImageManager.Manipulation.cs new file mode 100644 index 0000000..1344aef --- /dev/null +++ b/src/Managers/ImageManager.Manipulation.cs @@ -0,0 +1,56 @@ +using System.Text; +using Exsersewo.Common.Utilities; +using ImageMagick; +using Kynareth.Models; + +namespace Kynareth.Managers; + +public static partial class ImageManager +{ + private static List ManipulationEndpoints; + + private static string ManipulationTemplateBaseFolder; + + static void ConfigureManipulation(IConfiguration configuration, IWebHostEnvironment _appEnvironment) + { + ManipulationTemplateBaseFolder = Path.Combine(_appEnvironment.WebRootPath, configuration.GetValue("ImageManipulation:Folder")); + ManipulationEndpoints = configuration.GetSection("ImageManipulation:Templates").Get>(); + } + + public static async Task GenerateMemeImageAsync(string template, string[] texts) + { + ImageManipulationImage imageTemplate = ManipulationEndpoints.FirstOrDefault(e => e.Name.ToLowerInvariant().Equals(template.ToLowerInvariant())); + + if (imageTemplate is null) + { + var ex = new ArgumentException($"Couldn't find endpoint named \"{template}\"", nameof(template)); + + return GetResult(StatusCodes.Status404NotFound, EventResult.FromFailureException(ex.Message, ex)); + } + + if (imageTemplate.TextPositions.Count > texts.Length) + { + var ex = new ArgumentException("Not enough texts provided", nameof(texts)); + + return GetResult(StatusCodes.Status400BadRequest, EventResult.FromFailureException(ex.Message, ex)); + } + + string image = Path.Combine(ManipulationTemplateBaseFolder, imageTemplate.Image); + + using MagickImage templateImage = new(File.ReadAllBytes(image)); + using MagickImage baseImage = new(MagickColors.Transparent, templateImage.Width, templateImage.Height); + + baseImage.Format = MagickFormat.Png; + + baseImage.Composite(templateImage, CompositeOperator.Over); + + int textPoint = 0; + foreach (var textArea in imageTemplate.TextPositions) + { + baseImage.WriteText(texts[textPoint], textArea, MagickColors.White, Encoding.Unicode); + textPoint++; + } + + return baseImage.ToByteArray(); + } +} \ No newline at end of file diff --git a/src/Managers/ImageManager.cs b/src/Managers/ImageManager.cs new file mode 100644 index 0000000..714bd40 --- /dev/null +++ b/src/Managers/ImageManager.cs @@ -0,0 +1,59 @@ +using System.Text; +using ImageMagick; +using Kynareth.Models; +using Microsoft.AspNetCore.Mvc; + +namespace Kynareth.Managers; + +public static partial class ImageManager +{ + public static void Configure(IConfiguration configuration, IWebHostEnvironment appEnvironment) + { + ConfigureGeneration(configuration, appEnvironment); + ConfigureManipulation(configuration, appEnvironment); + } + + private static ObjectResult GetResult(int statusCode, object data) + => new(data) { StatusCode = statusCode }; + + public static MagickImage WriteText(this MagickImage image, string text, PositionRect rect, MagickColor fontColor, Encoding encoding, double fontSize, string font = "Impact") + { + using var label = new MagickImage($"caption:{text}", new MagickReadSettings() + { + Width = rect.Width, + Height = rect.Height, + BackgroundColor = MagickColors.Transparent, + FillColor = fontColor, + TextGravity = Gravity.Center, + FontPointsize = fontSize, + Font = font, + TextEncoding = encoding, + StrokeColor = MagickColors.Black, + StrokeWidth = 2 + }); + + image.Composite(label, rect.X, rect.Y, CompositeOperator.Over); + + return image; + } + + public static MagickImage WriteText(this MagickImage image, string text, PositionRect rect, MagickColor fontColor, Encoding encoding, string font = "Impact") + { + using var label = new MagickImage($"label:{text}", new MagickReadSettings() + { + Width = rect.Width, + Height = rect.Height, + BackgroundColor = MagickColors.Transparent, + FillColor = fontColor, + TextGravity = Gravity.Center, + Font = font, + TextEncoding = encoding, + StrokeColor = MagickColors.Black, + StrokeWidth = 2 + }); + + image.Composite(label, rect.X, rect.Y, CompositeOperator.Over); + + return image; + } +} \ No newline at end of file diff --git a/src/Managers/Models/Common.cs b/src/Managers/Models/Common.cs new file mode 100644 index 0000000..0ee7d3b --- /dev/null +++ b/src/Managers/Models/Common.cs @@ -0,0 +1,32 @@ +using System.Text.Json.Serialization; + +namespace Kynareth.Models; + +public class PositionRect +{ + [JsonPropertyName("x"), ConfigurationKeyName("x")] + public int X { get; set; } + [JsonPropertyName("y"), ConfigurationKeyName("y")] + public int Y { get; set; } + [JsonPropertyName("w"), ConfigurationKeyName("w")] + public int Width { get; set; } + [JsonPropertyName("h"), ConfigurationKeyName("h")] + public int Height { get; set; } +} + +public class GenerationPositionRect : PositionRect +{ + [JsonPropertyName("r"), ConfigurationKeyName("r")] + public int Rotation { get; set; } + [JsonPropertyName("background"), ConfigurationKeyName("background")] + public string Background { get; set; } +} + +public class ImageEndpoint +{ + [JsonPropertyName("name"), ConfigurationKeyName("name")] + public string Name { get; set; } + + [JsonPropertyName("image"), ConfigurationKeyName("image")] + public string Image { get; set; } +} \ No newline at end of file diff --git a/src/Managers/Models/ImageGenerationImage.cs b/src/Managers/Models/ImageGenerationImage.cs new file mode 100644 index 0000000..5aeeebf --- /dev/null +++ b/src/Managers/Models/ImageGenerationImage.cs @@ -0,0 +1,36 @@ +using System.Text.Json.Serialization; + +namespace Kynareth.Models; + +public class ImageGenerationImage : ImageEndpoint +{ + [JsonPropertyName("rotate"), ConfigurationKeyName("rotate")] + public int Rotate { get; set; } = 0; + + [JsonPropertyName("under"), ConfigurationKeyName("under")] + public bool PlaceUnder { get; set; } = false; + + [JsonPropertyName("position"), ConfigurationKeyName("position")] + public ICollection Positions { get; set; } +} + +public class ImageGenerationEndpointRead +{ + [JsonPropertyName("name"), ConfigurationKeyName("name")] + public string Name { get; set; } + + [JsonPropertyName("sources"), ConfigurationKeyName("sources")] + public int SourcesRequired { get; set; } +} + +public class ImageGenerationEndpoint : ImageGenerationImage +{ + [JsonPropertyName("folder"), ConfigurationKeyName("folder")] + public string Folder { get; set; } + + [JsonPropertyName("sources"), ConfigurationKeyName("sources")] + public int SourcesRequired { get; set; } = 0; + + [JsonPropertyName("variants"), ConfigurationKeyName("variants")] + public ICollection Variants { get; set; } +} \ No newline at end of file diff --git a/src/Managers/Models/ImageManipulationImage.cs b/src/Managers/Models/ImageManipulationImage.cs new file mode 100644 index 0000000..e1e187d --- /dev/null +++ b/src/Managers/Models/ImageManipulationImage.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace Kynareth.Models; + +public class ImageManipulationImage : ImageEndpoint +{ + [JsonPropertyName("textpositions"), ConfigurationKeyName("textpositions")] + public ICollection TextPositions { get; set; } +} \ No newline at end of file diff --git a/src/Migrations/20230114194230_Initial.Designer.cs b/src/Migrations/20230114194230_Initial.Designer.cs new file mode 100644 index 0000000..800f86a --- /dev/null +++ b/src/Migrations/20230114194230_Initial.Designer.cs @@ -0,0 +1,98 @@ +// +using System; +using Kynareth.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Kynareth.Migrations +{ + [DbContext(typeof(KynarethDbContext))] + [Migration("20230114194230_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.1") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true) + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Kynareth.Models.ApiCall", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Route") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TimeStamp") + .HasColumnType("datetime(6)"); + + b.Property("TokenId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("TokenId"); + + b.ToTable("Calls"); + }); + + modelBuilder.Entity("Kynareth.Models.ApiToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ClientId") + .HasColumnType("bigint unsigned"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsValid") + .HasColumnType("tinyint(1)"); + + b.Property("OwnerEmail") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Token") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Kynareth.Models.ApiCall", b => + { + b.HasOne("Kynareth.Models.ApiToken", "Token") + .WithMany("ApiCalls") + .HasForeignKey("TokenId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Token"); + }); + + modelBuilder.Entity("Kynareth.Models.ApiToken", b => + { + b.Navigation("ApiCalls"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Migrations/20230114194230_Initial.cs b/src/Migrations/20230114194230_Initial.cs new file mode 100644 index 0000000..cdcc876 --- /dev/null +++ b/src/Migrations/20230114194230_Initial.cs @@ -0,0 +1,77 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Kynareth.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Tokens", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + ClientId = table.Column(type: "bigint unsigned", nullable: false), + Token = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + OwnerEmail = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Description = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + IsValid = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Tokens", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "Calls", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Route = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + TimeStamp = table.Column(type: "datetime(6)", nullable: false), + TokenId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci") + }, + constraints: table => + { + table.PrimaryKey("PK_Calls", x => x.Id); + table.ForeignKey( + name: "FK_Calls_Tokens_TokenId", + column: x => x.TokenId, + principalTable: "Tokens", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_Calls_TokenId", + table: "Calls", + column: "TokenId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Calls"); + + migrationBuilder.DropTable( + name: "Tokens"); + } + } +} diff --git a/src/Migrations/KynarethDbContextModelSnapshot.cs b/src/Migrations/KynarethDbContextModelSnapshot.cs new file mode 100644 index 0000000..21fbb4b --- /dev/null +++ b/src/Migrations/KynarethDbContextModelSnapshot.cs @@ -0,0 +1,95 @@ +// +using System; +using Kynareth.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Kynareth.Migrations +{ + [DbContext(typeof(KynarethDbContext))] + partial class KynarethDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.1") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true) + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Kynareth.Models.ApiCall", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Route") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TimeStamp") + .HasColumnType("datetime(6)"); + + b.Property("TokenId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("TokenId"); + + b.ToTable("Calls"); + }); + + modelBuilder.Entity("Kynareth.Models.ApiToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ClientId") + .HasColumnType("bigint unsigned"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsValid") + .HasColumnType("tinyint(1)"); + + b.Property("OwnerEmail") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Token") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Kynareth.Models.ApiCall", b => + { + b.HasOne("Kynareth.Models.ApiToken", "Token") + .WithMany("ApiCalls") + .HasForeignKey("TokenId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Token"); + }); + + modelBuilder.Entity("Kynareth.Models.ApiToken", b => + { + b.Navigation("ApiCalls"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Models/ApiCall.cs b/src/Models/ApiCall.cs new file mode 100644 index 0000000..994faf3 --- /dev/null +++ b/src/Models/ApiCall.cs @@ -0,0 +1,10 @@ +namespace Kynareth.Models; + +public class ApiCall +{ + public int Id { get; set; } + public string Route { get; set; } + public DateTime TimeStamp { get; set; } + + public virtual ApiToken Token { get; set; } +} \ No newline at end of file diff --git a/src/Models/ApiToken.cs b/src/Models/ApiToken.cs new file mode 100644 index 0000000..9ddc7f4 --- /dev/null +++ b/src/Models/ApiToken.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Kynareth.Models; + +public class ApiToken +{ + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + public ulong ClientId { get; set; } + public string Token { get; set; } + public string OwnerEmail { get; set; } + public string Description { get; set; } + public bool IsValid { get; set; } + + public long TotalAPICalls => ApiCalls.LongCount(); + + public virtual ICollection ApiCalls { get; set; } +} \ No newline at end of file diff --git a/src/Models/KynarethDbContext.cs b/src/Models/KynarethDbContext.cs new file mode 100644 index 0000000..5940731 --- /dev/null +++ b/src/Models/KynarethDbContext.cs @@ -0,0 +1,43 @@ +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Kynareth.Models; + +public class KynarethDbContext : DbContext +{ + public DbSet Calls { get; set; } + public DbSet Tokens { get; set; } + + public KynarethDbContext() : base() + { + ChangeTracker.LazyLoadingEnabled = true; + } + + public KynarethDbContext(DbContextOptions options) : base(options) + { + ChangeTracker.LazyLoadingEnabled = true; + } + + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + configurationBuilder + .Properties() + .HaveConversion(); + } +} + +/// +/// Deserializes DateTime so if its Kind is Unspecified then it's set to Utc. +/// Does nothing on Serialization as SQL Server discards this info when saving to DateTime fields. +/// +public class DateTimeToUtcConverter : ValueConverter +{ + public DateTimeToUtcConverter() : base(Serialize, Deserialize, null) + { + } + + static Expression> Deserialize = + x => x.Kind == DateTimeKind.Unspecified ? DateTime.SpecifyKind(x, DateTimeKind.Utc) : x; + static Expression> Serialize = x => x; +} \ No newline at end of file diff --git a/src/Models/KynarethDbContextFactory.cs b/src/Models/KynarethDbContextFactory.cs new file mode 100644 index 0000000..b7ceafb --- /dev/null +++ b/src/Models/KynarethDbContextFactory.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace Kynareth.Models; + +public class KynarethDbContextFactory : IDesignTimeDbContextFactory +{ + public KynarethDbContext CreateDbContext(ILoggerFactory? loggerFactory, IConfiguration config) + { + var connStr = config.GetConnectionString("Database"); + + var serverVersion = ServerVersion.AutoDetect(connStr); + + var optionsBuilder = new DbContextOptionsBuilder() + .UseLoggerFactory(loggerFactory) + .UseMySql(connStr, serverVersion, x => x.EnableRetryOnFailure()) + .UseLazyLoadingProxies(); + + return new KynarethDbContext(optionsBuilder.Options); + } + + public KynarethDbContext CreateDbContext(string[] args = null) { + var config = new ConfigurationBuilder () + .SetBasePath(AppDomain.CurrentDomain.BaseDirectory) + .AddJsonFile("appsettings.json") + .Build(); + + return CreateDbContext(null, config); + } +} \ No newline at end of file diff --git a/src/Program.cs b/src/Program.cs new file mode 100644 index 0000000..03672a2 --- /dev/null +++ b/src/Program.cs @@ -0,0 +1,52 @@ +using Exsersewo.Common.Utilities; +using Kynareth.Managers; +using Kynareth.Models; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); +builder.WebHost.UseKestrel(); + +HttpWebClient.UserAgent = "IBrowse/2.4 (AmigaOS 3.9; 68K)"; + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services + .AddEndpointsApiExplorer() + .AddSwaggerGen(); + +builder.Services + .AddAuthentication(); + +builder.Services.AddDbContext(options => +{ + var connStr = builder.Configuration.GetConnectionString("Database"); + + var serverVersion = ServerVersion.AutoDetect(connStr); + + options.UseMySql(connStr, serverVersion, x => x.EnableRetryOnFailure()); + options.UseLazyLoadingProxies(); +}, contextLifetime: ServiceLifetime.Transient, optionsLifetime: ServiceLifetime.Singleton); + +var app = builder.Build(); + +ImageManager.Configure(app.Configuration, app.Environment); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); +app.UseStaticFiles(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Services.GetService>().LogInformation($"Ready and waiting requests @ {string.Join(";", app.Urls)}"); + +app.Run(); \ No newline at end of file diff --git a/src/Properties/launchSettings.json b/src/Properties/launchSettings.json new file mode 100644 index 0000000..b91d186 --- /dev/null +++ b/src/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:21388", + "sslPort": 44356 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5054", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7072;http://localhost:5054", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/appsettings.Development.json b/src/appsettings.Development.json new file mode 100644 index 0000000..9918811 --- /dev/null +++ b/src/appsettings.Development.json @@ -0,0 +1,11 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "Database": "" + } +} diff --git a/src/appsettings.json b/src/appsettings.json new file mode 100644 index 0000000..8f46ef1 --- /dev/null +++ b/src/appsettings.json @@ -0,0 +1,1711 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://0.0.0.0:5000" + } + } + }, + "ConnectionStrings": { + "Database": "" + }, + "ShitpostBot" : { + "Folder": "imagegen/templates", + "Templates": [ + { + "name": "drake", + "image": "Drake-Hotline-Bling.jpg", + "folder": "drake", + "rotate": 0, + "under": false, + "sources": 2, + "position": [ + { + "x": 600, + "y": 0, + "w": 600, + "h": 600, + "r": 0 + }, + { + "x": 600, + "y": 600, + "w": 600, + "h": 600, + "r": 0 + } + ], + "variants": [ + { + "name": "mememan", + "image": "meme-man-cool-and-nice-57d652ff95027.png", + "rotate": 0, + "under": false, + "position": [ + { + "x": 482, + "y": 0, + "w": 478, + "h": 384, + "r": 0 + }, + { + "x": 482, + "y": 392, + "w": 478, + "h": 440, + "r": 0 + } + ] + }, + { + "name": "pewdiepie", + "image": "pewdiepie-drake.png", + "rotate": 0, + "under": false, + "position": [ + { + "x": 362, + "y": 0, + "w": 356, + "h": 287, + "r": 0 + }, + { + "x": 362, + "y": 294, + "w": 356, + "h": 325, + "r": 0 + } + ] + }, + { + "name": "kevin", + "image": "kevin-rates-594fc2f0354ce.png", + "rotate": 0, + "under": false, + "position": [ + { + "x": 0, + "y": 0, + "w": 640, + "h": 479, + "r": 0 + }, + { + "x": 0, + "y": 483, + "w": 640, + "h": 479, + "r": 0 + } + ] + }, + { + "name": "419-420", + "image": "419-420-57f31c1e3e7d7.png", + "rotate": 0, + "under": false, + "position": [ + { + "x": 250, + "y": 0, + "w": 242, + "h": 217, + "r": 0 + }, + { + "x": 250, + "y": 220, + "w": 242, + "h": 217, + "r": 0 + } + ] + }, + { + "name": "arstotzka", + "image": "arstotzkabot-58473bf98d2af.png", + "position": [ + { + "x": 22, + "y": 4, + "w": 478, + "h": 275, + "r": 0 + }, + { + "x": 22, + "y": 279, + "w": 478, + "h": 303, + "r": 0 + } + ] + }, + { + "name": "shibe", + "image": "doggo-reactions-57b2056db8526.png", + "rotate": 0, + "under": false, + "position": [ + { + "x": 10, + "y": 10, + "w": 465, + "h": 465, + "r": 0 + }, + { + "x": 10, + "y": 485, + "w": 465, + "h": 465, + "r": 0 + } + ] + }, + { + "name": "alwaysgoforthis", + "image": "alwaysgoforthis.png", + "rotate": 0, + "under": true, + "position": [ + { + "x": 0, + "y": 0, + "w": 442, + "h": 396, + "r": 0 + }, + { + "x": 0, + "y": 405, + "w": 484, + "h": 384, + "r": 0 + } + ] + } + ] + }, + { + "name": "difference", + "image": "find-the-difference.jpg", + "folder": "difference", + "rotate": 0, + "under": false, + "sources": 2, + "position": [ + { + "x": 600, + "y": 0, + "w": 600, + "h": 600, + "r": 0 + }, + { + "x": 600, + "y": 600, + "w": 600, + "h": 600, + "r": 0 + } + ], + "variants": [ + { + "name": "win10", + "image": "win10meme.png", + "rotate": 0, + "under": true, + "sources": 2, + "position": [ + { + "x": 63, + "y": 370, + "w": 233, + "h": 240, + "r": 0 + }, + { + "x": 63, + "y": 670, + "w": 233, + "h": 240, + "r": 0 + } + ] + } + ] + }, + { + "name": "computersuicide", + "image": "computer-death-wish-5a62d094507aa.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 470, + "y": 89, + "w": 425, + "h": 265, + "r": 0 + } + ] + }, + { + "name": "home-sweet-home", + "image": "home-sweet-home-5a4ab71646f1b.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 91, + "y": 487, + "w": 248, + "h": 429, + "r": 0 + } + ] + }, + { + "name": "ahegao", + "image": "h-template-596fef01d35d5.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 304, + "y": 299, + "w": 300, + "h": 300, + "r": 0 + } + ] + }, + { + "name": "monitor-gun", + "image": "koksal-father-aiming-at-monitor-5a899657146e3.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 544, + "y": 250, + "w": 416, + "h": 375, + "r": 0 + } + ] + }, + { + "name": "sponge-lick", + "image": "lickylickylick-57e7f9305c361.png", + "rotate": -1, + "under": true, + "sources": 1, + "position": [ + { + "x": 280, + "y": -25, + "w": 320, + "h": 300, + "r": 1 + } + ] + }, + { + "name": "markiplier-crying", + "image": "markiplier-crying-5a4a05deb0562.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 0, + "y": 0, + "w": 750, + "h": 427, + "r": 0 + } + ] + }, + { + "name": "mcdonalds-hmeal", + "image": "mcdonalds-happy-meal-57d5334a8858a.jpg", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 6, + "y": 125, + "w": 318, + "h": 240, + "r": 0 + } + ] + }, + { + "name": "menacing", + "image": "menacing-58dd0b32bb9a4.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 0, + "y": 0, + "w": 612, + "h": 674, + "r": 0 + } + ] + }, + { + "name": "psychiatrist", + "image": "physiatrist-meeting-598d9aff60b31.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 6, + "y": 70, + "w": 563, + "h": 435, + "r": 0 + } + ] + }, + { + "name": "steamupdate", + "image": "steamupdate-5bbaf8edb2972.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 25, + "y": 185, + "w": 410, + "h": 320, + "r": 0 + } + ] + }, + { + "name": "thankyoukanye", + "image": "thank-you-kanye-very-cool-5ae1d0be61b5b.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 28, + "y": 267, + "w": 577, + "h": 405, + "r": 0 + } + ] + }, + { + "name": "animesaviour", + "image": "anime-saviour-58398799b215b.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 53, + "y": 228, + "w": 375, + "h": 239, + "r": 0 + } + ] + }, + { + "name": "breadsquirt", + "image": "breadsquirt-5850e390a0775.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 193, + "y": 165, + "w": 478, + "h": 334, + "r": 0 + } + ] + }, + { + "name": "crocs", + "image": "crocs-584a5341f0da5.png", + "rotate": 0, + "under": false, + "sources": 2, + "position": [ + { + "x": 50, + "y": 118, + "w": 280, + "h": 309, + "r": 0 + }, + { + "x": 470, + "y": 118, + "w": 280, + "h": 309, + "r": 0 + } + ] + }, + { + "name": "daredevil", + "image": "dare-devil-5940e111dd127.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 4, + "y": 84, + "w": 220, + "h": 223, + "r": 0 + } + ] + }, + { + "name": "delet", + "image": "d-e-l-e-t-59d9ec331e545.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 18, + "y": 18, + "w": 462, + "h": 461, + "r": 0 + } + ] + }, + { + "name": "etika", + "image": "etika-reaction-580e03b8bcd04.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 412, + "y": 0, + "w": 658, + "h": 418, + "r": 0 + } + ] + }, + { + "name": "feelsmask", + "image": "feels-guy-mask-582eab07da9fe.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 212, + "y": 91, + "w": 167, + "h": 213, + "r": 0 + } + ] + }, + { + "name": "reddit", + "image": "i-am-so-putting-this-on-reddit-58e282c5554dd.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 26, + "y": 21, + "w": 286, + "h": 287, + "r": 0 + } + ] + }, + { + "name": "killerqueen", + "image": "killer-queen-583ba1e7b947f.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 0, + "y": 0, + "w": 680, + "h": 385, + "r": 0 + } + ] + }, + { + "name": "mclane", + "image": "mclane-58a3b0b61b1cc.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 185, + "y": 158, + "w": 144, + "h": 195, + "r": 0 + } + ] + }, + { + "name": "fetish", + "image": "my-fetish-5910119d98851.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 52, + "y": 30, + "w": 476, + "h": 529, + "r": 0 + } + ] + }, + { + "name": "nihilism", + "image": "nihilism-581ec22da347a.png", + "rotate": 0, + "under": true, + "sources": 6, + "position": [ + { + "x": 66, + "y": 76, + "w": 158, + "h": 158, + "r": 0 + }, + { + "x": 296, + "y": 76, + "w": 158, + "h": 158, + "r": 0 + }, + { + "x": 510, + "y": 76, + "w": 158, + "h": 158, + "r": 0 + }, + { + "x": 66, + "y": 286, + "w": 158, + "h": 158, + "r": 0 + }, + { + "x": 296, + "y": 286, + "w": 158, + "h": 158, + "r": 0 + }, + { + "x": 510, + "y": 286, + "w": 158, + "h": 158, + "r": 0 + } + ] + }, + { + "name": "disney", + "image": "oh-no-5a582c5d4fe01.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 27, + "y": 370, + "w": 846, + "h": 481, + "r": 0 + } + ] + }, + { + "name": "jacksfilms-photoshop", + "image": "please-dont-photoshop-this-2-59951adea457b.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 509, + "y": 362, + "w": 241, + "h": 241, + "r": 0 + } + ] + }, + { + "name": "anthony", + "image": "shurethony-betano-57e48584b135f.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 14, + "y": 60, + "w": 156, + "h": 156, + "r": 0 + } + ] + }, + { + "name": "trump-ad", + "image": "trump-looking-at-picture-59b4668af1897.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 378, + "y": 122, + "w": 309, + "h": 526, + "r": 0 + } + ] + }, + { + "name": "well", + "image": "well-59175edd1893f.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 260, + "y": 262, + "w": 225, + "h": 225, + "r": 0 + } + ] + }, + { + "name": "nodick", + "image": "when-she-doesnt-have-a-dick-580ef0f2906a7.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 42, + "y": 371, + "w": 1961, + "h": 1632, + "r": 0 + } + ] + }, + { + "name": "ugly", + "image": "you-are-very-ugly-58a371fe8f685.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 19, + "y": 109, + "w": 355, + "h": 334, + "r": 0 + } + ] + }, + { + "name": "edboy", + "image": "edboy.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 0, + "y": 0, + "w": 500, + "h": 363, + "r": 0 + } + ] + }, + { + "name": "yugi", + "image": "yugis-best-card-57fccc0856178.png", + "rotate": 1, + "under": true, + "sources": 1, + "position": [ + { + "x": 29, + "y": 245, + "w": 393, + "h": 393, + "r": 10 + } + ] + }, + { + "name": "torture", + "image": "worst-torture-possible-5974cdd82a3a1.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 261, + "y": 173, + "w": 161, + "h": 163, + "r": 0 + } + ] + }, + { + "name": "hitler", + "image": "worse-than-hitler-589cf7b728702.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 43, + "y": 30, + "w": 148, + "h": 171, + "r": 0 + } + ] + }, + { + "name": "paint", + "image": "windows-paint-57d39e6f21ba5.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 65, + "y": 55, + "w": 310, + "h": 260, + "r": 0 + } + ] + }, + { + "name": "game", + "image": "who-remember-this-game-57bf035c47ead.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 163, + "y": 192, + "w": 172, + "h": 166, + "r": 0 + } + ] + }, + { + "name": "screen", + "image": "whats-on-her-screen-5997685739b76.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 44, + "y": 348, + "w": 412, + "h": 363, + "r": 0 + } + ] + }, + { + "name": "technology", + "image": "we-have-technology-57e870b82d563.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 49, + "y": 573, + "w": 302, + "h": 270, + "r": 0 + } + ] + }, + { + "name": "trump-tv", + "image": "trump-learns-watching-tv-59cf2303c2454.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 276, + "y": 520, + "w": 326, + "h": 173, + "r": 0 + } + ] + }, + { + "name": "animegirls", + "image": "top-10-anime-girls-57f3904c656bf.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 9, + "y": 10, + "w": 960, + "h": 540, + "r": 0 + } + ] + }, + { + "name": "epicanime", + "image": "top-5-epic-anime-moments-57b20f92b67ae.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 23, + "y": 58, + "w": 854, + "h": 441, + "r": 0 + } + ] + }, + { + "name": "sendpic", + "image": "send-a-pic-594ec162b5b2b.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 164, + "y": 132, + "w": 354, + "h": 363, + "r": 0 + } + ] + }, + { + "name": "nothingisperfect", + "image": "nothing-is-perfect-57ce506ecec8b.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 326, + "y": 71, + "w": 292, + "h": 413, + "r": 0 + } + ] + }, + { + "name": "neptune", + "image": "neptune-58f0532a30257.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 275, + "y": 89, + "w": 360, + "h": 365, + "r": 0 + } + ] + }, + { + "name": "meemay", + "image": "meemay-57b1ddc7ae86a.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 347, + "y": 0, + "w": 333, + "h": 331, + "r": 0 + } + ] + }, + { + "name": "duel", + "image": "its-time-to-duel-57e539aa8fa96.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 293, + "y": 119, + "w": 119, + "h": 128, + "r": 0 + } + ] + }, + { + "name": "harassment", + "image": "i-swear-to-god-5886a80901713.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 230, + "y": 6, + "w": 366, + "h": 312, + "r": 0 + } + ] + }, + { + "name": "thinking", + "image": "increasingly-makes-you-think-59b3a3c144f2c.png", + "rotate": 0, + "under": false, + "sources": 4, + "position": [ + { + "x": 542, + "y": 0, + "w": 538, + "h": 480, + "r": 0 + }, + { + "x": 542, + "y": 483, + "w": 538, + "h": 476, + "r": 0 + }, + { + "x": 542, + "y": 962, + "w": 538, + "h": 476, + "r": 0 + }, + { + "x": 542, + "y": 1441, + "w": 538, + "h": 476, + "r": 0 + } + ] + }, + { + "name": "idubbbz-dirt", + "image": "idubbzz-588eadd697717-design.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 94, + "y": 51, + "w": 200, + "h": 282, + "r": 0 + } + ] + }, + { + "name": "idols", + "image": "idols-58c6c43f6ecd3.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 6, + "y": 166, + "w": 574, + "h": 345, + "r": 0 + } + ] + }, + { + "name": "future", + "image": "future-vision-58bfcedeca83e.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 2, + "y": 595, + "w": 796, + "h": 453, + "r": 0 + } + ] + }, + { + "name": "condom", + "image": "failure-596131a70097a.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 25, + "y": 439, + "w": 325, + "h": 327, + "r": 0 + } + ] + }, + { + "name": "erect", + "image": "erected-57ab5615842c4.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 0, + "y": 0, + "w": 250, + "h": 234, + "r": 0 + } + ] + }, + { + "name": "erb", + "image": "epic-rap-battle-5944b0b894fd6.png", + "rotate": 0, + "under": true, + "sources": 2, + "position": [ + { + "x": 7, + "y": 254, + "w": 358, + "h": 338, + "r": 0 + }, + { + "x": 626, + "y": 249, + "w": 382, + "h": 347, + "r": 0 + } + ] + }, + { + "name": "howifeel", + "image": "draw-how-you-feel-5902a903335b3.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 400, + "y": 648, + "w": 245, + "h": 187, + "r": 0 + } + ] + }, + { + "name": "4horsemen", + "image": "4horsemen-57debcaba9939.png", + "rotate": 0, + "under": true, + "sources": 4, + "position": [ + { + "x": 6, + "y": 148, + "w": 203, + "h": 188, + "r": 0 + }, + { + "x": 216, + "y": 148, + "w": 203, + "h": 188, + "r": 0 + }, + { + "x": 436, + "y": 148, + "w": 203, + "h": 188, + "r": 0 + }, + { + "x": 656, + "y": 148, + "w": 203, + "h": 188, + "r": 0 + } + ] + }, + { + "name": "22million", + "image": "22-million-views-596fe4e603665.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 4, + "y": 3, + "w": 454, + "h": 234, + "r": 0 + } + ] + }, + { + "name": "roblox", + "image": "account-deleted-57c6294a68ccc.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 237, + "y": 366, + "w": 275, + "h": 276, + "r": 0 + } + ] + }, + { + "name": "turnon", + "image": "are-you-trying-to-turn-me-on-593165731050f.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 455, + "y": 165, + "w": 372, + "h": 567, + "r": 0 + } + ] + }, + { + "name": "chatroulette", + "image": "chatroulette-58c265bfbff6f.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 18, + "y": 349, + "w": 320, + "h": 240, + "r": 0 + } + ] + }, + { + "name": "chooseyourclass", + "image": "choose-your-class-57f2f27cdc502.png", + "rotate": 0, + "under": true, + "sources": 6, + "position": [ + { + "x": 23, + "y": 95, + "w": 189, + "h": 186, + "r": 0 + }, + { + "x": 253, + "y": 92, + "w": 194, + "h": 189, + "r": 0 + }, + { + "x": 479, + "y": 91, + "w": 184, + "h": 190, + "r": 0 + }, + { + "x": 19, + "y": 308, + "w": 191, + "h": 186, + "r": 0 + }, + { + "x": 245, + "y": 308, + "w": 197, + "h": 186, + "r": 0 + }, + { + "x": 471, + "y": 308, + "w": 192, + "h": 186, + "r": 0 + } + ] + }, + { + "name": "cancer", + "image": "disgusting-57c72991e588f.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 371, + "y": 426, + "w": 325, + "h": 243, + "r": 0 + } + ] + }, + { + "name": "duckmans-tv", + "image": "duckmans-tv-58c0fc0528cff.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 129, + "y": 277, + "w": 114, + "h": 153, + "r": 0 + } + ] + }, + { + "name": "humanity", + "image": "humanity-57fd4468a8ffe.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 106, + "y": 9, + "w": 236, + "h": 200, + "r": 0 + } + ] + }, + { + "name": "idubbbz-canvas", + "image": "idubbbz-canvas-593e2342edad9.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 171, + "y": 95, + "w": 510, + "h": 415, + "r": 0 + } + ] + }, + { + "name": "discordcall", + "image": "incoming-discord-call-58fe3b013ae63.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 68, + "y": 58, + "w": 102, + "h": 102, + "r": 0 + } + ] + }, + { + "name": "minecraft", + "image": "minecraft-painting-58f39f9dcafe9.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 316, + "y": 96, + "w": 223, + "h": 221, + "r": 0 + } + ] + }, + { + "name": "tedcruz", + "image": "tedcruzwindow-5930ada147311.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 152, + "y": 102, + "w": 285, + "h": 211, + "r": 0 + } + ] + }, + { + "name": "thisisart", + "image": "this-is-art-appreciate-it-593bfe6b81a51.png", + "rotate": 1, + "under": true, + "sources": 1, + "position": [ + { + "x": 90, + "y": 72, + "w": 151, + "h": 203, + "r": 5 + }, + { + "x": 174, + "y": 412, + "w": 169, + "h": 193, + "r": -15 + } + ] + }, + { + "name": "trash", + "image": "trash-59142e3424128.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 350, + "y": 317, + "w": 122, + "h": 85, + "r": 0 + }, + { + "x": 558, + "y": 105, + "w": 422, + "h": 335, + "r": 0 + } + ] + }, + { + "name": "oldmancloud", + "image": "oldmancloud.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 107, + "y": 82, + "w": 325, + "h": 270, + "r": 0 + } + ] + }, + { + "name": "delete", + "image": "delete.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 108, + "y": 121, + "w": 177, + "h": 177, + "r": 0 + } + ] + }, + { + "name": "bad_memes", + "image": "bad_memes.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 10, + "y": 203, + "w": 692, + "h": 690, + "r": 0 + } + ] + }, + { + "name": "ahshit", + "image": "ahshit.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 0, + "y": 0, + "w": 360, + "h": 207, + "r": 0 + } + ] + }, + { + "name": "trust", + "image": "trust-with-my-life-5865c1241588a.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 82, + "y": 37, + "w": 300, + "h": 300, + "r": -1 + } + ] + }, + { + "name": "jojo-point", + "image": "point.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 2271, + "y": 1001, + "w": 1074, + "h": 615, + "r": 5 + } + ] + }, + { + "name": "disappointment", + "image": "disappointment.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 352, + "y": 219, + "w": 100, + "h": 71, + "r": 0 + } + ] + }, + { + "name": "savetheworld", + "image": "to-save-the-world-5945e4eb0298f.jpg", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 775, + "y": 806, + "w": 365, + "h": 365, + "r": 0 + } + ] + }, + { + "name": "smile", + "image": "67765645_355276032091743_8357909824331055104_n.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "x": 460, + "y": 513, + "w": 302, + "h": 447, + "r": 0 + } + ] + }, + { + "name": "commodore", + "image": "commodore-64-59c065fc4e3f9-overlay.png", + "rotate": 0, + "under": true, + "sources": 1, + "position": [ + { + "background": "000000", + "x": 353, + "y": 109, + "w": 348, + "h": 260, + "r": 0 + } + ] + }, + { + "name": "helloilikemoney", + "image": "helloilikemoney.png", + "rotate": 0, + "under": false, + "sources": 1, + "position": [ + { + "x": 185, + "y": 175, + "w": 150, + "h": 150, + "r": 0 + } + ] + } + ] + }, + "ImageManipulation": { + "Folder": "imagemanip/templates", + "Templates": [ + { + "name": "seagull", + "image": "seagull.jpg", + "textpositions": [ + { + "x": 213, + "y": 4, + "w": 275, + "h": 125 + }, + { + "x": 593, + "y": 411, + "w": 169, + "h": 75 + } + ] + } + ] + } +} diff --git a/src/wwwroot/imagegen/templates/22-million-views-596fe4e603665.png b/src/wwwroot/imagegen/templates/22-million-views-596fe4e603665.png new file mode 100644 index 0000000..b16e400 Binary files /dev/null and b/src/wwwroot/imagegen/templates/22-million-views-596fe4e603665.png differ diff --git a/src/wwwroot/imagegen/templates/4horsemen-57debcaba9939.png b/src/wwwroot/imagegen/templates/4horsemen-57debcaba9939.png new file mode 100644 index 0000000..459077e Binary files /dev/null and b/src/wwwroot/imagegen/templates/4horsemen-57debcaba9939.png differ diff --git a/src/wwwroot/imagegen/templates/67765645_355276032091743_8357909824331055104_n.png b/src/wwwroot/imagegen/templates/67765645_355276032091743_8357909824331055104_n.png new file mode 100644 index 0000000..ee799d5 Binary files /dev/null and b/src/wwwroot/imagegen/templates/67765645_355276032091743_8357909824331055104_n.png differ diff --git a/src/wwwroot/imagegen/templates/account-deleted-57c6294a68ccc.png b/src/wwwroot/imagegen/templates/account-deleted-57c6294a68ccc.png new file mode 100644 index 0000000..9cb4e85 Binary files /dev/null and b/src/wwwroot/imagegen/templates/account-deleted-57c6294a68ccc.png differ diff --git a/src/wwwroot/imagegen/templates/ahshit.png b/src/wwwroot/imagegen/templates/ahshit.png new file mode 100644 index 0000000..59341a5 Binary files /dev/null and b/src/wwwroot/imagegen/templates/ahshit.png differ diff --git a/src/wwwroot/imagegen/templates/anime-saviour-58398799b215b.png b/src/wwwroot/imagegen/templates/anime-saviour-58398799b215b.png new file mode 100644 index 0000000..46d9dea Binary files /dev/null and b/src/wwwroot/imagegen/templates/anime-saviour-58398799b215b.png differ diff --git a/src/wwwroot/imagegen/templates/are-you-trying-to-turn-me-on-593165731050f.png b/src/wwwroot/imagegen/templates/are-you-trying-to-turn-me-on-593165731050f.png new file mode 100644 index 0000000..3bd6248 Binary files /dev/null and b/src/wwwroot/imagegen/templates/are-you-trying-to-turn-me-on-593165731050f.png differ diff --git a/src/wwwroot/imagegen/templates/bad_memes.png b/src/wwwroot/imagegen/templates/bad_memes.png new file mode 100644 index 0000000..3061dff Binary files /dev/null and b/src/wwwroot/imagegen/templates/bad_memes.png differ diff --git a/src/wwwroot/imagegen/templates/breadsquirt-5850e390a0775.png b/src/wwwroot/imagegen/templates/breadsquirt-5850e390a0775.png new file mode 100644 index 0000000..596095e Binary files /dev/null and b/src/wwwroot/imagegen/templates/breadsquirt-5850e390a0775.png differ diff --git a/src/wwwroot/imagegen/templates/chatroulette-58c265bfbff6f.png b/src/wwwroot/imagegen/templates/chatroulette-58c265bfbff6f.png new file mode 100644 index 0000000..3595b4d Binary files /dev/null and b/src/wwwroot/imagegen/templates/chatroulette-58c265bfbff6f.png differ diff --git a/src/wwwroot/imagegen/templates/choose-your-class-57f2f27cdc502.png b/src/wwwroot/imagegen/templates/choose-your-class-57f2f27cdc502.png new file mode 100644 index 0000000..26fe3e5 Binary files /dev/null and b/src/wwwroot/imagegen/templates/choose-your-class-57f2f27cdc502.png differ diff --git a/src/wwwroot/imagegen/templates/commodore-64-59c065fc4e3f9-overlay.png b/src/wwwroot/imagegen/templates/commodore-64-59c065fc4e3f9-overlay.png new file mode 100644 index 0000000..0109929 Binary files /dev/null and b/src/wwwroot/imagegen/templates/commodore-64-59c065fc4e3f9-overlay.png differ diff --git a/src/wwwroot/imagegen/templates/computer-death-wish-5a62d094507aa.png b/src/wwwroot/imagegen/templates/computer-death-wish-5a62d094507aa.png new file mode 100644 index 0000000..1e5cc87 Binary files /dev/null and b/src/wwwroot/imagegen/templates/computer-death-wish-5a62d094507aa.png differ diff --git a/src/wwwroot/imagegen/templates/crocs-584a5341f0da5.png b/src/wwwroot/imagegen/templates/crocs-584a5341f0da5.png new file mode 100644 index 0000000..deebcff Binary files /dev/null and b/src/wwwroot/imagegen/templates/crocs-584a5341f0da5.png differ diff --git a/src/wwwroot/imagegen/templates/d-e-l-e-t-59d9ec331e545.png b/src/wwwroot/imagegen/templates/d-e-l-e-t-59d9ec331e545.png new file mode 100644 index 0000000..c318120 Binary files /dev/null and b/src/wwwroot/imagegen/templates/d-e-l-e-t-59d9ec331e545.png differ diff --git a/src/wwwroot/imagegen/templates/dare-devil-5940e111dd127.png b/src/wwwroot/imagegen/templates/dare-devil-5940e111dd127.png new file mode 100644 index 0000000..2831b64 Binary files /dev/null and b/src/wwwroot/imagegen/templates/dare-devil-5940e111dd127.png differ diff --git a/src/wwwroot/imagegen/templates/delete.png b/src/wwwroot/imagegen/templates/delete.png new file mode 100644 index 0000000..9ccf0f1 Binary files /dev/null and b/src/wwwroot/imagegen/templates/delete.png differ diff --git a/src/wwwroot/imagegen/templates/difference/find-the-difference.jpg b/src/wwwroot/imagegen/templates/difference/find-the-difference.jpg new file mode 100644 index 0000000..2b1d31a Binary files /dev/null and b/src/wwwroot/imagegen/templates/difference/find-the-difference.jpg differ diff --git a/src/wwwroot/imagegen/templates/difference/win10meme.png b/src/wwwroot/imagegen/templates/difference/win10meme.png new file mode 100644 index 0000000..fe9d197 Binary files /dev/null and b/src/wwwroot/imagegen/templates/difference/win10meme.png differ diff --git a/src/wwwroot/imagegen/templates/disappointment.png b/src/wwwroot/imagegen/templates/disappointment.png new file mode 100644 index 0000000..79fae1f Binary files /dev/null and b/src/wwwroot/imagegen/templates/disappointment.png differ diff --git a/src/wwwroot/imagegen/templates/disgusting-57c72991e588f.png b/src/wwwroot/imagegen/templates/disgusting-57c72991e588f.png new file mode 100644 index 0000000..6a379e2 Binary files /dev/null and b/src/wwwroot/imagegen/templates/disgusting-57c72991e588f.png differ diff --git a/src/wwwroot/imagegen/templates/drake/419-420-57f31c1e3e7d7.png b/src/wwwroot/imagegen/templates/drake/419-420-57f31c1e3e7d7.png new file mode 100644 index 0000000..08b9d35 Binary files /dev/null and b/src/wwwroot/imagegen/templates/drake/419-420-57f31c1e3e7d7.png differ diff --git a/src/wwwroot/imagegen/templates/drake/Drake-Hotline-Bling.jpg b/src/wwwroot/imagegen/templates/drake/Drake-Hotline-Bling.jpg new file mode 100644 index 0000000..682b38c Binary files /dev/null and b/src/wwwroot/imagegen/templates/drake/Drake-Hotline-Bling.jpg differ diff --git a/src/wwwroot/imagegen/templates/drake/alwaysgoforthis.png b/src/wwwroot/imagegen/templates/drake/alwaysgoforthis.png new file mode 100644 index 0000000..e630d15 Binary files /dev/null and b/src/wwwroot/imagegen/templates/drake/alwaysgoforthis.png differ diff --git a/src/wwwroot/imagegen/templates/drake/arstotzkabot-58473bf98d2af.png b/src/wwwroot/imagegen/templates/drake/arstotzkabot-58473bf98d2af.png new file mode 100644 index 0000000..72eee13 Binary files /dev/null and b/src/wwwroot/imagegen/templates/drake/arstotzkabot-58473bf98d2af.png differ diff --git a/src/wwwroot/imagegen/templates/drake/doggo-reactions-57b2056db8526.png b/src/wwwroot/imagegen/templates/drake/doggo-reactions-57b2056db8526.png new file mode 100644 index 0000000..7bed1a2 Binary files /dev/null and b/src/wwwroot/imagegen/templates/drake/doggo-reactions-57b2056db8526.png differ diff --git a/src/wwwroot/imagegen/templates/drake/kevin-rates-594fc2f0354ce.png b/src/wwwroot/imagegen/templates/drake/kevin-rates-594fc2f0354ce.png new file mode 100644 index 0000000..25f486d Binary files /dev/null and b/src/wwwroot/imagegen/templates/drake/kevin-rates-594fc2f0354ce.png differ diff --git a/src/wwwroot/imagegen/templates/drake/meme-man-cool-and-nice-57d652ff95027.png b/src/wwwroot/imagegen/templates/drake/meme-man-cool-and-nice-57d652ff95027.png new file mode 100644 index 0000000..ef7d75d Binary files /dev/null and b/src/wwwroot/imagegen/templates/drake/meme-man-cool-and-nice-57d652ff95027.png differ diff --git a/src/wwwroot/imagegen/templates/drake/pewdiepie-drake.png b/src/wwwroot/imagegen/templates/drake/pewdiepie-drake.png new file mode 100644 index 0000000..4159878 Binary files /dev/null and b/src/wwwroot/imagegen/templates/drake/pewdiepie-drake.png differ diff --git a/src/wwwroot/imagegen/templates/draw-how-you-feel-5902a903335b3.png b/src/wwwroot/imagegen/templates/draw-how-you-feel-5902a903335b3.png new file mode 100644 index 0000000..a3e5ea4 Binary files /dev/null and b/src/wwwroot/imagegen/templates/draw-how-you-feel-5902a903335b3.png differ diff --git a/src/wwwroot/imagegen/templates/duckmans-tv-58c0fc0528cff.png b/src/wwwroot/imagegen/templates/duckmans-tv-58c0fc0528cff.png new file mode 100644 index 0000000..bd5acd5 Binary files /dev/null and b/src/wwwroot/imagegen/templates/duckmans-tv-58c0fc0528cff.png differ diff --git a/src/wwwroot/imagegen/templates/edboy.png b/src/wwwroot/imagegen/templates/edboy.png new file mode 100644 index 0000000..83f7dfc Binary files /dev/null and b/src/wwwroot/imagegen/templates/edboy.png differ diff --git a/src/wwwroot/imagegen/templates/epic-rap-battle-5944b0b894fd6.png b/src/wwwroot/imagegen/templates/epic-rap-battle-5944b0b894fd6.png new file mode 100644 index 0000000..2ae11d4 Binary files /dev/null and b/src/wwwroot/imagegen/templates/epic-rap-battle-5944b0b894fd6.png differ diff --git a/src/wwwroot/imagegen/templates/erected-57ab5615842c4.png b/src/wwwroot/imagegen/templates/erected-57ab5615842c4.png new file mode 100644 index 0000000..f704eef Binary files /dev/null and b/src/wwwroot/imagegen/templates/erected-57ab5615842c4.png differ diff --git a/src/wwwroot/imagegen/templates/etika-reaction-580e03b8bcd04.png b/src/wwwroot/imagegen/templates/etika-reaction-580e03b8bcd04.png new file mode 100644 index 0000000..a6e2e0d Binary files /dev/null and b/src/wwwroot/imagegen/templates/etika-reaction-580e03b8bcd04.png differ diff --git a/src/wwwroot/imagegen/templates/failure-596131a70097a.png b/src/wwwroot/imagegen/templates/failure-596131a70097a.png new file mode 100644 index 0000000..2f9f282 Binary files /dev/null and b/src/wwwroot/imagegen/templates/failure-596131a70097a.png differ diff --git a/src/wwwroot/imagegen/templates/feels-guy-mask-582eab07da9fe.png b/src/wwwroot/imagegen/templates/feels-guy-mask-582eab07da9fe.png new file mode 100644 index 0000000..b19d9e2 Binary files /dev/null and b/src/wwwroot/imagegen/templates/feels-guy-mask-582eab07da9fe.png differ diff --git a/src/wwwroot/imagegen/templates/future-vision-58bfcedeca83e.png b/src/wwwroot/imagegen/templates/future-vision-58bfcedeca83e.png new file mode 100644 index 0000000..cddc274 Binary files /dev/null and b/src/wwwroot/imagegen/templates/future-vision-58bfcedeca83e.png differ diff --git a/src/wwwroot/imagegen/templates/h-template-596fef01d35d5.png b/src/wwwroot/imagegen/templates/h-template-596fef01d35d5.png new file mode 100644 index 0000000..94e86c3 Binary files /dev/null and b/src/wwwroot/imagegen/templates/h-template-596fef01d35d5.png differ diff --git a/src/wwwroot/imagegen/templates/helloilikemoney.png b/src/wwwroot/imagegen/templates/helloilikemoney.png new file mode 100644 index 0000000..70d60c4 Binary files /dev/null and b/src/wwwroot/imagegen/templates/helloilikemoney.png differ diff --git a/src/wwwroot/imagegen/templates/home-sweet-home-5a4ab71646f1b.png b/src/wwwroot/imagegen/templates/home-sweet-home-5a4ab71646f1b.png new file mode 100644 index 0000000..e044a6a Binary files /dev/null and b/src/wwwroot/imagegen/templates/home-sweet-home-5a4ab71646f1b.png differ diff --git a/src/wwwroot/imagegen/templates/humanity-57fd4468a8ffe.png b/src/wwwroot/imagegen/templates/humanity-57fd4468a8ffe.png new file mode 100644 index 0000000..6402b95 Binary files /dev/null and b/src/wwwroot/imagegen/templates/humanity-57fd4468a8ffe.png differ diff --git a/src/wwwroot/imagegen/templates/i-am-so-putting-this-on-reddit-58e282c5554dd.png b/src/wwwroot/imagegen/templates/i-am-so-putting-this-on-reddit-58e282c5554dd.png new file mode 100644 index 0000000..3c780d6 Binary files /dev/null and b/src/wwwroot/imagegen/templates/i-am-so-putting-this-on-reddit-58e282c5554dd.png differ diff --git a/src/wwwroot/imagegen/templates/i-swear-to-god-5886a80901713.png b/src/wwwroot/imagegen/templates/i-swear-to-god-5886a80901713.png new file mode 100644 index 0000000..49e5ca6 Binary files /dev/null and b/src/wwwroot/imagegen/templates/i-swear-to-god-5886a80901713.png differ diff --git a/src/wwwroot/imagegen/templates/idols-58c6c43f6ecd3.png b/src/wwwroot/imagegen/templates/idols-58c6c43f6ecd3.png new file mode 100644 index 0000000..6680354 Binary files /dev/null and b/src/wwwroot/imagegen/templates/idols-58c6c43f6ecd3.png differ diff --git a/src/wwwroot/imagegen/templates/idubbbz-canvas-593e2342edad9.png b/src/wwwroot/imagegen/templates/idubbbz-canvas-593e2342edad9.png new file mode 100644 index 0000000..6db8df9 Binary files /dev/null and b/src/wwwroot/imagegen/templates/idubbbz-canvas-593e2342edad9.png differ diff --git a/src/wwwroot/imagegen/templates/idubbzz-588eadd697717-design.png b/src/wwwroot/imagegen/templates/idubbzz-588eadd697717-design.png new file mode 100644 index 0000000..998f57b Binary files /dev/null and b/src/wwwroot/imagegen/templates/idubbzz-588eadd697717-design.png differ diff --git a/src/wwwroot/imagegen/templates/incoming-discord-call-58fe3b013ae63.png b/src/wwwroot/imagegen/templates/incoming-discord-call-58fe3b013ae63.png new file mode 100644 index 0000000..2677efe Binary files /dev/null and b/src/wwwroot/imagegen/templates/incoming-discord-call-58fe3b013ae63.png differ diff --git a/src/wwwroot/imagegen/templates/increasingly-makes-you-think-59b3a3c144f2c.png b/src/wwwroot/imagegen/templates/increasingly-makes-you-think-59b3a3c144f2c.png new file mode 100644 index 0000000..311711d Binary files /dev/null and b/src/wwwroot/imagegen/templates/increasingly-makes-you-think-59b3a3c144f2c.png differ diff --git a/src/wwwroot/imagegen/templates/its-time-to-duel-57e539aa8fa96.png b/src/wwwroot/imagegen/templates/its-time-to-duel-57e539aa8fa96.png new file mode 100644 index 0000000..193b457 Binary files /dev/null and b/src/wwwroot/imagegen/templates/its-time-to-duel-57e539aa8fa96.png differ diff --git a/src/wwwroot/imagegen/templates/killer-queen-583ba1e7b947f.png b/src/wwwroot/imagegen/templates/killer-queen-583ba1e7b947f.png new file mode 100644 index 0000000..bdffb5d Binary files /dev/null and b/src/wwwroot/imagegen/templates/killer-queen-583ba1e7b947f.png differ diff --git a/src/wwwroot/imagegen/templates/koksal-father-aiming-at-monitor-5a899657146e3.png b/src/wwwroot/imagegen/templates/koksal-father-aiming-at-monitor-5a899657146e3.png new file mode 100644 index 0000000..3abe342 Binary files /dev/null and b/src/wwwroot/imagegen/templates/koksal-father-aiming-at-monitor-5a899657146e3.png differ diff --git a/src/wwwroot/imagegen/templates/lickylickylick-57e7f9305c361.png b/src/wwwroot/imagegen/templates/lickylickylick-57e7f9305c361.png new file mode 100644 index 0000000..e678bda Binary files /dev/null and b/src/wwwroot/imagegen/templates/lickylickylick-57e7f9305c361.png differ diff --git a/src/wwwroot/imagegen/templates/markiplier-crying-5a4a05deb0562.png b/src/wwwroot/imagegen/templates/markiplier-crying-5a4a05deb0562.png new file mode 100644 index 0000000..5d1e1d3 Binary files /dev/null and b/src/wwwroot/imagegen/templates/markiplier-crying-5a4a05deb0562.png differ diff --git a/src/wwwroot/imagegen/templates/mcdonalds-happy-meal-57d5334a8858a.jpg b/src/wwwroot/imagegen/templates/mcdonalds-happy-meal-57d5334a8858a.jpg new file mode 100644 index 0000000..34ae9d8 Binary files /dev/null and b/src/wwwroot/imagegen/templates/mcdonalds-happy-meal-57d5334a8858a.jpg differ diff --git a/src/wwwroot/imagegen/templates/mclane-58a3b0b61b1cc.png b/src/wwwroot/imagegen/templates/mclane-58a3b0b61b1cc.png new file mode 100644 index 0000000..4c88c08 Binary files /dev/null and b/src/wwwroot/imagegen/templates/mclane-58a3b0b61b1cc.png differ diff --git a/src/wwwroot/imagegen/templates/meemay-57b1ddc7ae86a.png b/src/wwwroot/imagegen/templates/meemay-57b1ddc7ae86a.png new file mode 100644 index 0000000..f1f4a33 Binary files /dev/null and b/src/wwwroot/imagegen/templates/meemay-57b1ddc7ae86a.png differ diff --git a/src/wwwroot/imagegen/templates/menacing-58dd0b32bb9a4.png b/src/wwwroot/imagegen/templates/menacing-58dd0b32bb9a4.png new file mode 100644 index 0000000..3ae32dc Binary files /dev/null and b/src/wwwroot/imagegen/templates/menacing-58dd0b32bb9a4.png differ diff --git a/src/wwwroot/imagegen/templates/minecraft-painting-58f39f9dcafe9.png b/src/wwwroot/imagegen/templates/minecraft-painting-58f39f9dcafe9.png new file mode 100644 index 0000000..8149601 Binary files /dev/null and b/src/wwwroot/imagegen/templates/minecraft-painting-58f39f9dcafe9.png differ diff --git a/src/wwwroot/imagegen/templates/my-fetish-5910119d98851.png b/src/wwwroot/imagegen/templates/my-fetish-5910119d98851.png new file mode 100644 index 0000000..1151177 Binary files /dev/null and b/src/wwwroot/imagegen/templates/my-fetish-5910119d98851.png differ diff --git a/src/wwwroot/imagegen/templates/neptune-58f0532a30257.png b/src/wwwroot/imagegen/templates/neptune-58f0532a30257.png new file mode 100644 index 0000000..2616e62 Binary files /dev/null and b/src/wwwroot/imagegen/templates/neptune-58f0532a30257.png differ diff --git a/src/wwwroot/imagegen/templates/nihilism-581ec22da347a.png b/src/wwwroot/imagegen/templates/nihilism-581ec22da347a.png new file mode 100644 index 0000000..9d20631 Binary files /dev/null and b/src/wwwroot/imagegen/templates/nihilism-581ec22da347a.png differ diff --git a/src/wwwroot/imagegen/templates/nothing-is-perfect-57ce506ecec8b.png b/src/wwwroot/imagegen/templates/nothing-is-perfect-57ce506ecec8b.png new file mode 100644 index 0000000..9acdcd7 Binary files /dev/null and b/src/wwwroot/imagegen/templates/nothing-is-perfect-57ce506ecec8b.png differ diff --git a/src/wwwroot/imagegen/templates/oh-no-5a582c5d4fe01.png b/src/wwwroot/imagegen/templates/oh-no-5a582c5d4fe01.png new file mode 100644 index 0000000..31562a6 Binary files /dev/null and b/src/wwwroot/imagegen/templates/oh-no-5a582c5d4fe01.png differ diff --git a/src/wwwroot/imagegen/templates/oldmancloud.png b/src/wwwroot/imagegen/templates/oldmancloud.png new file mode 100644 index 0000000..1900694 Binary files /dev/null and b/src/wwwroot/imagegen/templates/oldmancloud.png differ diff --git a/src/wwwroot/imagegen/templates/physiatrist-meeting-598d9aff60b31.png b/src/wwwroot/imagegen/templates/physiatrist-meeting-598d9aff60b31.png new file mode 100644 index 0000000..4ed38b7 Binary files /dev/null and b/src/wwwroot/imagegen/templates/physiatrist-meeting-598d9aff60b31.png differ diff --git a/src/wwwroot/imagegen/templates/please-dont-photoshop-this-2-59951adea457b.png b/src/wwwroot/imagegen/templates/please-dont-photoshop-this-2-59951adea457b.png new file mode 100644 index 0000000..aad4339 Binary files /dev/null and b/src/wwwroot/imagegen/templates/please-dont-photoshop-this-2-59951adea457b.png differ diff --git a/src/wwwroot/imagegen/templates/point.png b/src/wwwroot/imagegen/templates/point.png new file mode 100644 index 0000000..d671c76 Binary files /dev/null and b/src/wwwroot/imagegen/templates/point.png differ diff --git a/src/wwwroot/imagegen/templates/send-a-pic-594ec162b5b2b.png b/src/wwwroot/imagegen/templates/send-a-pic-594ec162b5b2b.png new file mode 100644 index 0000000..8f7783a Binary files /dev/null and b/src/wwwroot/imagegen/templates/send-a-pic-594ec162b5b2b.png differ diff --git a/src/wwwroot/imagegen/templates/shurethony-betano-57e48584b135f.png b/src/wwwroot/imagegen/templates/shurethony-betano-57e48584b135f.png new file mode 100644 index 0000000..943f172 Binary files /dev/null and b/src/wwwroot/imagegen/templates/shurethony-betano-57e48584b135f.png differ diff --git a/src/wwwroot/imagegen/templates/steamupdate-5bbaf8edb2972.png b/src/wwwroot/imagegen/templates/steamupdate-5bbaf8edb2972.png new file mode 100644 index 0000000..d115a4f Binary files /dev/null and b/src/wwwroot/imagegen/templates/steamupdate-5bbaf8edb2972.png differ diff --git a/src/wwwroot/imagegen/templates/tedcruzwindow-5930ada147311.png b/src/wwwroot/imagegen/templates/tedcruzwindow-5930ada147311.png new file mode 100644 index 0000000..34c6f0b Binary files /dev/null and b/src/wwwroot/imagegen/templates/tedcruzwindow-5930ada147311.png differ diff --git a/src/wwwroot/imagegen/templates/thank-you-kanye-very-cool-5ae1d0be61b5b.png b/src/wwwroot/imagegen/templates/thank-you-kanye-very-cool-5ae1d0be61b5b.png new file mode 100644 index 0000000..8aab54f Binary files /dev/null and b/src/wwwroot/imagegen/templates/thank-you-kanye-very-cool-5ae1d0be61b5b.png differ diff --git a/src/wwwroot/imagegen/templates/this-is-art-appreciate-it-593bfe6b81a51.png b/src/wwwroot/imagegen/templates/this-is-art-appreciate-it-593bfe6b81a51.png new file mode 100644 index 0000000..61245f4 Binary files /dev/null and b/src/wwwroot/imagegen/templates/this-is-art-appreciate-it-593bfe6b81a51.png differ diff --git a/src/wwwroot/imagegen/templates/to-save-the-world-5945e4eb0298f.jpg b/src/wwwroot/imagegen/templates/to-save-the-world-5945e4eb0298f.jpg new file mode 100644 index 0000000..6c7ec6e Binary files /dev/null and b/src/wwwroot/imagegen/templates/to-save-the-world-5945e4eb0298f.jpg differ diff --git a/src/wwwroot/imagegen/templates/top-10-anime-girls-57f3904c656bf.png b/src/wwwroot/imagegen/templates/top-10-anime-girls-57f3904c656bf.png new file mode 100644 index 0000000..67555d1 Binary files /dev/null and b/src/wwwroot/imagegen/templates/top-10-anime-girls-57f3904c656bf.png differ diff --git a/src/wwwroot/imagegen/templates/top-5-epic-anime-moments-57b20f92b67ae.png b/src/wwwroot/imagegen/templates/top-5-epic-anime-moments-57b20f92b67ae.png new file mode 100644 index 0000000..891cf9c Binary files /dev/null and b/src/wwwroot/imagegen/templates/top-5-epic-anime-moments-57b20f92b67ae.png differ diff --git a/src/wwwroot/imagegen/templates/trash-59142e3424128.png b/src/wwwroot/imagegen/templates/trash-59142e3424128.png new file mode 100644 index 0000000..0f6ff7e Binary files /dev/null and b/src/wwwroot/imagegen/templates/trash-59142e3424128.png differ diff --git a/src/wwwroot/imagegen/templates/trump-learns-watching-tv-59cf2303c2454.png b/src/wwwroot/imagegen/templates/trump-learns-watching-tv-59cf2303c2454.png new file mode 100644 index 0000000..c2294da Binary files /dev/null and b/src/wwwroot/imagegen/templates/trump-learns-watching-tv-59cf2303c2454.png differ diff --git a/src/wwwroot/imagegen/templates/trump-looking-at-picture-59b4668af1897.png b/src/wwwroot/imagegen/templates/trump-looking-at-picture-59b4668af1897.png new file mode 100644 index 0000000..87f390c Binary files /dev/null and b/src/wwwroot/imagegen/templates/trump-looking-at-picture-59b4668af1897.png differ diff --git a/src/wwwroot/imagegen/templates/trust-with-my-life-5865c1241588a.png b/src/wwwroot/imagegen/templates/trust-with-my-life-5865c1241588a.png new file mode 100644 index 0000000..d0bddf5 Binary files /dev/null and b/src/wwwroot/imagegen/templates/trust-with-my-life-5865c1241588a.png differ diff --git a/src/wwwroot/imagegen/templates/we-have-technology-57e870b82d563.png b/src/wwwroot/imagegen/templates/we-have-technology-57e870b82d563.png new file mode 100644 index 0000000..9b032c0 Binary files /dev/null and b/src/wwwroot/imagegen/templates/we-have-technology-57e870b82d563.png differ diff --git a/src/wwwroot/imagegen/templates/well-59175edd1893f.png b/src/wwwroot/imagegen/templates/well-59175edd1893f.png new file mode 100644 index 0000000..d0302da Binary files /dev/null and b/src/wwwroot/imagegen/templates/well-59175edd1893f.png differ diff --git a/src/wwwroot/imagegen/templates/whats-on-her-screen-5997685739b76.png b/src/wwwroot/imagegen/templates/whats-on-her-screen-5997685739b76.png new file mode 100644 index 0000000..7cc254b Binary files /dev/null and b/src/wwwroot/imagegen/templates/whats-on-her-screen-5997685739b76.png differ diff --git a/src/wwwroot/imagegen/templates/when-she-doesnt-have-a-dick-580ef0f2906a7.png b/src/wwwroot/imagegen/templates/when-she-doesnt-have-a-dick-580ef0f2906a7.png new file mode 100644 index 0000000..69cfb04 Binary files /dev/null and b/src/wwwroot/imagegen/templates/when-she-doesnt-have-a-dick-580ef0f2906a7.png differ diff --git a/src/wwwroot/imagegen/templates/who-remember-this-game-57bf035c47ead.png b/src/wwwroot/imagegen/templates/who-remember-this-game-57bf035c47ead.png new file mode 100644 index 0000000..71b3c9e Binary files /dev/null and b/src/wwwroot/imagegen/templates/who-remember-this-game-57bf035c47ead.png differ diff --git a/src/wwwroot/imagegen/templates/windows-paint-57d39e6f21ba5.png b/src/wwwroot/imagegen/templates/windows-paint-57d39e6f21ba5.png new file mode 100644 index 0000000..970874a Binary files /dev/null and b/src/wwwroot/imagegen/templates/windows-paint-57d39e6f21ba5.png differ diff --git a/src/wwwroot/imagegen/templates/worse-than-hitler-589cf7b728702.png b/src/wwwroot/imagegen/templates/worse-than-hitler-589cf7b728702.png new file mode 100644 index 0000000..390a920 Binary files /dev/null and b/src/wwwroot/imagegen/templates/worse-than-hitler-589cf7b728702.png differ diff --git a/src/wwwroot/imagegen/templates/worst-torture-possible-5974cdd82a3a1.png b/src/wwwroot/imagegen/templates/worst-torture-possible-5974cdd82a3a1.png new file mode 100644 index 0000000..43c8980 Binary files /dev/null and b/src/wwwroot/imagegen/templates/worst-torture-possible-5974cdd82a3a1.png differ diff --git a/src/wwwroot/imagegen/templates/you-are-very-ugly-58a371fe8f685.png b/src/wwwroot/imagegen/templates/you-are-very-ugly-58a371fe8f685.png new file mode 100644 index 0000000..5e84329 Binary files /dev/null and b/src/wwwroot/imagegen/templates/you-are-very-ugly-58a371fe8f685.png differ diff --git a/src/wwwroot/imagegen/templates/yugis-best-card-57fccc0856178.png b/src/wwwroot/imagegen/templates/yugis-best-card-57fccc0856178.png new file mode 100644 index 0000000..c6e0420 Binary files /dev/null and b/src/wwwroot/imagegen/templates/yugis-best-card-57fccc0856178.png differ diff --git a/src/wwwroot/imagemanip/templates/seagull.jpg b/src/wwwroot/imagemanip/templates/seagull.jpg new file mode 100644 index 0000000..5a58081 Binary files /dev/null and b/src/wwwroot/imagemanip/templates/seagull.jpg differ