Meme Generation API
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Kynareth/src/Managers/ImageGenerator.cs

200 lines
6.1 KiB

using System.Collections.Immutable;
using Exsersewo.Common.Extensions;
using Exsersewo.Common.Utilities;
using ImageMagick;
using Kynareth.Models;
using Kynareth.Extensions;
namespace Kynareth.Managers;
public class ImageGenerator : ImageModifier
{
private static List<ImageGenerationEndpoint> GenerationEndpoints;
private static IReadOnlyList<string> sourceFiles;
private static string GenerationTemplateBaseFolder;
public ImageGenerator(IConfiguration configuration, IWebHostEnvironment appEnvironment)
{
sourceFiles = Directory.EnumerateFiles(Path.Combine(appEnvironment.WebRootPath, configuration.GetValue<string>("ShitpostBot:Folder"), "..", "random")).ToImmutableList();
GenerationTemplateBaseFolder = Path.Combine(appEnvironment.WebRootPath, configuration.GetValue<string>("ShitpostBot:Folder"));
GenerationEndpoints = configuration.GetSection("ShitpostBot:Templates").Get<List<ImageGenerationEndpoint>>();
}
public async Task<Object> GenerateImageAsync(CancellationToken cancellationToken)
{
ImageGenerationImage imageTemplate = null;
var endpoint = GenerationEndpoints.Random();
if (endpoint is null)
{
var ex = new ArgumentException($"Couldn't find endpoint");
return GetResult(StatusCodes.Status404NotFound, EventResult.FromFailureException(ex.Message, ex));
}
string variant = null;
if (endpoint.Variants != null && endpoint.Variants.Any() && TrueRandom.Next(0, 50) > 25)
{
variant = endpoint.Variants.Random().Name;
}
var sources = sourceFiles.GetRandomAmount(endpoint.SourcesRequired) as string[];
return await GenerateImageAsync(endpoint.Name, variant, sources, cancellationToken);
}
public async Task<Object> GenerateImageAsync(string template, string variant, CancellationToken cancellationToken)
{
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));
}
var sources = sourceFiles.GetRandomAmount(endpoint.SourcesRequired) as string[];
return await GenerateImageAsync(template, variant, sources, cancellationToken);
}
public async Task<object> GenerateImageAsync(string template, string variant, string[] sources, CancellationToken cancellationToken)
{
while (true)
{
if(cancellationToken.IsCancellationRequested)
{
return null;
}
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, cancellationToken);
}
baseImage.Composite(templateImage, Gravity.Northwest, CompositeOperator.Over);
if (!imageTemplate.PlaceUnder)
{
await AddSourcesToImage(baseImage, imageTemplate, sources, cancellationToken);
}
return baseImage.ToByteArray();
}
return null;
}
static async Task AddSourcesToImage(MagickImage image, ImageGenerationImage template, IEnumerable<string> sources, CancellationToken cancellationToken)
{
while (true)
{
if(cancellationToken.IsCancellationRequested)
{
return;
}
int img = 0;
for(int x = 0; x < template.Positions.Count; x++)
{
var position = template.Positions.ElementAtOrDefault(x);
var src = sources.ElementAtOrDefault(img);
Stream imageData = null;
if (File.Exists(src))
{
imageData = new MemoryStream(await File.ReadAllBytesAsync(src));
}
else if (src.StartsWith("http"))
{
imageData = await HttpWebClient.GetStreamAsync(new Uri(src));
}
using MagickImage srcImg = new MagickImage(imageData);
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);
if (img + 1 < sources.Count())
{
img++;
}
}
return;
}
}
public IEnumerable<ImageGenerationEndpointRead> GetGenerationEndpoints()
{
return GenerationEndpoints.Select(e => new ImageGenerationEndpointRead
{
Name = e.Name,
SourcesRequired = e.SourcesRequired,
Variants = e.Variants.Select(v => v.Name)
});
}
}