Add amogus generator & fortune teller meme
44
src/Controllers/AmogusController.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Exsersewo.Common.Utilities;
|
||||
using ImageMagick;
|
||||
using Kynareth.Helpers;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Kynareth.Controllers;
|
||||
|
||||
[Route("amogus")]
|
||||
public class AmogusController : BaseController<AmogusController>
|
||||
{
|
||||
public AmogusController(IConfiguration configuration, ILoggerFactory loggerFactory, IWebHostEnvironment appEnvironment) : base(configuration, loggerFactory, appEnvironment)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("eject")]
|
||||
public async Task<object> Eject([FromQuery] string name, [FromQuery] AmogusRole role = AmogusRole.Unknown, [FromQuery] AmogusColour colour = AmogusColour.Red, [FromQuery] AmogusSkin skin = AmogusSkin.None)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) return StatusCode(StatusCodes.Status400BadRequest, EventResult.FromFailure(nameof(name) + " needs to be provided."));
|
||||
|
||||
using var result = AmogusHelper.GenerateEjectionGif(name, colour, role, skin);
|
||||
|
||||
if (result is MagickImageCollection data)
|
||||
{
|
||||
var memStream = new MemoryStream();
|
||||
data.Write(memStream, data.Count <= 1 ? MagickFormat.Png : MagickFormat.Gif);
|
||||
|
||||
return HttpContext.SendStream(memStream, data.Count <= 1 ? "image/png" : "image/gif");
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
|
||||
[HttpGet("roles")]
|
||||
public async Task<object> GetAmongUsRoles()
|
||||
=> Ok(EventResult.FromSuccess(Enum.GetNames<AmogusRole>().Except("None")));
|
||||
|
||||
[HttpGet("colours")]
|
||||
public async Task<object> GetAmongUsColours()
|
||||
=> Ok(EventResult.FromSuccess(Enum.GetNames<AmogusColour>().Except("None")));
|
||||
|
||||
[HttpGet("skins")]
|
||||
public async Task<object> GetAmongUsSkins()
|
||||
=> Ok(EventResult.FromSuccess(Enum.GetNames<AmogusSkin>().Except("None")));
|
||||
}
|
||||
22
src/Extensions.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace Kynareth;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static T GetRandomEnumValue<T>() where T : Enum
|
||||
=> (T) Enum.GetValues(typeof(T)).OfType<Enum>().OrderBy(_ => Guid.NewGuid()).FirstOrDefault();
|
||||
|
||||
public static IList<int> Range(int start, int stop, int step)
|
||||
{
|
||||
var list = new List<int>();
|
||||
|
||||
for (int i = start; i < stop; i += step) {
|
||||
list.Add(i);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static T[] Except<T>(this T[] array, T specificValue) where T : IComparable {
|
||||
return array.Where<T>(val => val.CompareTo(specificValue) != 0).ToArray();
|
||||
}
|
||||
}
|
||||
579
src/Helpers/AmogusHelper.cs
Normal file
@@ -0,0 +1,579 @@
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using ImageMagick;
|
||||
using Kynareth.Managers;
|
||||
using Kynareth.Models;
|
||||
|
||||
namespace Kynareth.Helpers;
|
||||
|
||||
public enum AmogusRole
|
||||
{
|
||||
Imposter,
|
||||
Crewmate,
|
||||
Unknown
|
||||
}
|
||||
|
||||
public enum AmogusColour
|
||||
{
|
||||
Red,
|
||||
Blue,
|
||||
Green,
|
||||
Pink,
|
||||
Orange,
|
||||
Yellow,
|
||||
Black,
|
||||
White,
|
||||
Purple,
|
||||
Brown,
|
||||
Cyan,
|
||||
Lime,
|
||||
Maroon,
|
||||
Rose,
|
||||
Banana,
|
||||
Gray,
|
||||
Tan,
|
||||
Coral,
|
||||
}
|
||||
|
||||
public enum AmogusSkin
|
||||
{
|
||||
Archaeologist,
|
||||
Astronaut,
|
||||
Captain,
|
||||
Hazmat,
|
||||
Mechanic,
|
||||
Military,
|
||||
Miner,
|
||||
Police,
|
||||
SecurityGuard,
|
||||
Scientist,
|
||||
BlackSuit,
|
||||
WhiteSuit,
|
||||
Tarmac,
|
||||
Wall,
|
||||
Winter,
|
||||
None,
|
||||
}
|
||||
|
||||
public static class AmogusHelper
|
||||
{
|
||||
#region CoreVariables
|
||||
public class ColourHelper
|
||||
{
|
||||
private static readonly Dictionary<AmogusColour, MagickColor> _RedReplaceColors = new()
|
||||
{
|
||||
{ AmogusColour.Red, new(215, 30, 34, 255) },
|
||||
{ AmogusColour.Blue, new(29, 60, 233, 255) },
|
||||
{ AmogusColour.Green, new(27, 145, 62, 255) },
|
||||
{ AmogusColour.Pink, new(255, 99, 212, 255) },
|
||||
{ AmogusColour.Orange, new(255, 141, 28, 255) },
|
||||
{ AmogusColour.Yellow, new(255, 255, 103, 255) },
|
||||
{ AmogusColour.Black, new(74, 86, 94, 255) },
|
||||
{ AmogusColour.White, new(233, 247, 255, 255) },
|
||||
{ AmogusColour.Purple, new(120, 61, 210, 255) },
|
||||
{ AmogusColour.Brown, new(128, 88, 45, 255) },
|
||||
{ AmogusColour.Cyan, new(68, 255, 247, 255) },
|
||||
{ AmogusColour.Lime, new(91, 255, 75, 255) },
|
||||
{ AmogusColour.Maroon, new(108, 43, 61, 255) },
|
||||
{ AmogusColour.Rose, new(255, 214, 236, 255) },
|
||||
{ AmogusColour.Banana, new(255, 255, 190, 255) },
|
||||
{ AmogusColour.Gray, new(131, 151, 167, 255) },
|
||||
{ AmogusColour.Tan, new(159, 153, 137, 255) },
|
||||
{ AmogusColour.Coral, new(236, 117, 120, 255) },
|
||||
};
|
||||
|
||||
private static readonly Dictionary<AmogusColour, MagickColor> _BlueReplaceColors = new()
|
||||
{
|
||||
{ AmogusColour.Red, new(122, 8, 56, 255) },
|
||||
{ AmogusColour.Blue, new(9, 21, 142, 255) },
|
||||
{ AmogusColour.Green, new(10, 77, 46, 255) },
|
||||
{ AmogusColour.Pink, new(172, 43, 174, 255) },
|
||||
{ AmogusColour.Orange, new(180, 62, 21, 255) },
|
||||
{ AmogusColour.Yellow, new(195, 136, 34, 255) },
|
||||
{ AmogusColour.Black, new(30, 31, 38, 255) },
|
||||
{ AmogusColour.White, new(132, 149, 192, 255) },
|
||||
{ AmogusColour.Purple, new(59, 23, 124, 255) },
|
||||
{ AmogusColour.Brown, new(94, 38, 21, 255) },
|
||||
{ AmogusColour.Cyan, new(36, 169, 191, 255) },
|
||||
{ AmogusColour.Lime, new(21, 168, 66, 255) },
|
||||
{ AmogusColour.Maroon, new(65, 15, 26, 255) },
|
||||
{ AmogusColour.Rose, new(222, 146, 179, 255) },
|
||||
{ AmogusColour.Banana, new(210, 188, 137, 255) },
|
||||
{ AmogusColour.Gray, new(70, 86, 100, 255) },
|
||||
{ AmogusColour.Tan, new(81, 65, 62, 255) },
|
||||
{ AmogusColour.Coral, new(180, 67, 98, 255) },
|
||||
};
|
||||
|
||||
private static readonly MagickColor _green = new MagickColor(149, 202, 220, 255);
|
||||
private readonly MagickColor _red;
|
||||
private readonly MagickColor _blue;
|
||||
|
||||
public MagickColor Red => _red;
|
||||
public MagickColor Green => _green;
|
||||
public MagickColor Blue => _blue;
|
||||
|
||||
public ColourHelper(AmogusColour colour = AmogusColour.Blue)
|
||||
{
|
||||
_red = _RedReplaceColors[colour];
|
||||
_blue = _BlueReplaceColors[colour];
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly Dictionary<AmogusSkin, string> IdleSkins = new()
|
||||
{
|
||||
{ AmogusSkin.Archaeologist, "archae-idle.png" },
|
||||
{ AmogusSkin.Astronaut, "astro-idle.png" },
|
||||
{ AmogusSkin.Captain, "captain-idle.png" },
|
||||
{ AmogusSkin.Hazmat, "hazmat-idle.png" },
|
||||
{ AmogusSkin.Mechanic, "mech-idle.png" },
|
||||
{ AmogusSkin.Military, "military-idle.png" },
|
||||
{ AmogusSkin.Miner, "miner-idle.png" },
|
||||
{ AmogusSkin.Police, "pol-idle.png" },
|
||||
{ AmogusSkin.SecurityGuard, "secguard-idle.png" },
|
||||
{ AmogusSkin.Scientist, "sci-idle.png" },
|
||||
{ AmogusSkin.BlackSuit, "suitBlack-idle.png" },
|
||||
{ AmogusSkin.WhiteSuit, "suitWhite-idle.png" },
|
||||
{ AmogusSkin.Tarmac, "tarmac-idle.png" },
|
||||
{ AmogusSkin.Wall, "wall-idle.png" },
|
||||
{ AmogusSkin.Winter, "winter-idle.png" },
|
||||
};
|
||||
|
||||
public static readonly Dictionary<AmogusSkin, string> EjectSkins = new()
|
||||
{
|
||||
{ AmogusSkin.Archaeologist, "archae-eject.png" },
|
||||
{ AmogusSkin.Astronaut, "astro-eject.png" },
|
||||
{ AmogusSkin.Captain, "captain-eject.png" },
|
||||
{ AmogusSkin.Hazmat, "hazmat-eject.png" },
|
||||
{ AmogusSkin.Mechanic, "mech-eject.png" },
|
||||
{ AmogusSkin.Military, "military-eject.png" },
|
||||
{ AmogusSkin.Miner, "miner-eject.png" },
|
||||
{ AmogusSkin.Police, "police-eject.png" },
|
||||
{ AmogusSkin.SecurityGuard, "secguard-eject.png" },
|
||||
{ AmogusSkin.Scientist, "sci-eject.png" },
|
||||
{ AmogusSkin.BlackSuit, "suitBlack-eject.png" },
|
||||
{ AmogusSkin.WhiteSuit, "suitWhite-eject.png" },
|
||||
{ AmogusSkin.Tarmac, "tarmac-eject.png" },
|
||||
{ AmogusSkin.Wall, "wall-eject.png" },
|
||||
{ AmogusSkin.Winter, "winter-eject.png" },
|
||||
};
|
||||
|
||||
public static readonly Dictionary<AmogusSkin, Point> IdleOffset = new()
|
||||
{
|
||||
{ AmogusSkin.Archaeologist, new(13, 41) },
|
||||
{ AmogusSkin.Astronaut, new(13, 46) },
|
||||
{ AmogusSkin.Captain, new(14, 45) },
|
||||
{ AmogusSkin.Hazmat, new(12, 34) },
|
||||
{ AmogusSkin.Mechanic, new(13, 46) },
|
||||
{ AmogusSkin.Military, new(11, 45) },
|
||||
{ AmogusSkin.Miner, new(13, 40) },
|
||||
{ AmogusSkin.Police, new(10, 45) },
|
||||
{ AmogusSkin.SecurityGuard, new(13, 42) },
|
||||
{ AmogusSkin.Scientist, new(14, 43) },
|
||||
{ AmogusSkin.BlackSuit, new(14, 44) },
|
||||
{ AmogusSkin.WhiteSuit, new(14, 44) },
|
||||
{ AmogusSkin.Tarmac, new(14, 40) },
|
||||
{ AmogusSkin.Wall, new(10, 44) },
|
||||
{ AmogusSkin.Winter, new(9, 35) },
|
||||
};
|
||||
|
||||
public static readonly Dictionary<AmogusSkin, Point> EjectOffset = new()
|
||||
{
|
||||
{ AmogusSkin.Archaeologist, new(12, 35) },
|
||||
{ AmogusSkin.Astronaut, new(12, 35) },
|
||||
{ AmogusSkin.Captain, new(12, 35) },
|
||||
{ AmogusSkin.Hazmat, new(13, 37) },
|
||||
{ AmogusSkin.Mechanic, new(13, 35) },
|
||||
{ AmogusSkin.Military, new(12, 35) },
|
||||
{ AmogusSkin.Miner, new(12, 36) },
|
||||
{ AmogusSkin.Police, new(12, 35) },
|
||||
{ AmogusSkin.SecurityGuard, new(13, 35) },
|
||||
{ AmogusSkin.Scientist, new(-10, 35) },
|
||||
{ AmogusSkin.BlackSuit, new(12, 35) },
|
||||
{ AmogusSkin.WhiteSuit, new(11, 35) },
|
||||
{ AmogusSkin.Tarmac, new(14, 37) },
|
||||
{ AmogusSkin.Wall, new(12, 35) },
|
||||
{ AmogusSkin.Winter, new(5, 30) },
|
||||
};
|
||||
|
||||
public static string IdleRoot, EjectRoot, CommonRoot;
|
||||
|
||||
public const string Arial = "Arial";
|
||||
#endregion CoreVariables
|
||||
|
||||
public static void Configure(IWebHostEnvironment appEnvironment)
|
||||
{
|
||||
string amogusBase = Path.Combine(appEnvironment.WebRootPath, "amogus");
|
||||
|
||||
IdleRoot = Path.Combine(amogusBase, "idle");
|
||||
EjectRoot = Path.Combine(amogusBase, "eject");
|
||||
CommonRoot = Path.Combine(amogusBase, "common");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a base image for an AmongUs Asset that needs color replacement
|
||||
/// The default image is the standing crewmate (idle) and the default colour is blue
|
||||
/// </summary>
|
||||
/// <param name="baseImageName">The image to load up</param>
|
||||
/// <param name="colour">The colour to make the base image, must be one <see cref="AmogusColour"/></param>
|
||||
/// <returns>A <see cref="MagickImage"/> generated from <paramref name="baseImageName"/></returns>
|
||||
static MagickImage GenerateBase(string baseImageName = "idle.png", AmogusColour colour = AmogusColour.Blue)
|
||||
{
|
||||
string baseIdle = Path.Combine(CommonRoot, baseImageName);
|
||||
MagickImage body = new MagickImage(baseIdle);
|
||||
|
||||
body = ColourReplace(body, colour);
|
||||
body.BackgroundColor = MagickColors.Transparent;
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the Colour of an AmogieCrewMate with the target colour
|
||||
/// </summary>
|
||||
/// <param name="image">Image to Colour Replace</param>
|
||||
/// <param name="colour">Colour to replace with</param>
|
||||
/// <returns>Colour Replaced Amogie Crewmate</returns>
|
||||
static MagickImage ColourReplace(MagickImage image, AmogusColour colour = AmogusColour.Blue)
|
||||
{
|
||||
var colours = new ColourHelper(colour);
|
||||
|
||||
var pixels = image.GetPixels();
|
||||
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < image.Width; x++)
|
||||
{
|
||||
var pixel = pixels.GetPixel(x, y);
|
||||
var pixelColor = pixel.ToColor();
|
||||
|
||||
if (pixelColor is null) continue;
|
||||
|
||||
float pixelSum = pixelColor.R + pixelColor.G + pixelColor.B;
|
||||
|
||||
var areEqual = pixelColor.R == pixelColor.G && pixelColor.G == pixelColor.B;
|
||||
|
||||
if (pixelColor.A == 0 || areEqual) continue;
|
||||
|
||||
var pixBr = pixelColor.ToByteArray();
|
||||
|
||||
int SumWithinRange(byte lower, byte upper)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
if (pixelColor.R >= lower && pixelColor.R <= upper) count++;
|
||||
if (pixelColor.G >= lower && pixelColor.G <= upper) count++;
|
||||
if (pixelColor.B >= lower && pixelColor.B <= upper) count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
if (SumWithinRange(45, 65) < 3)
|
||||
{
|
||||
byte[]
|
||||
newR = Enumerable.Repeat((byte)(pixelColor.R / 255), 3).ToArray(),
|
||||
newG = new byte[3],
|
||||
newB = Enumerable.Repeat((byte)(pixelColor.B / 255), 3).ToArray();
|
||||
|
||||
var rBArr = colours.Red.ToByteArray();
|
||||
var gBArr = colours.Green.ToByteArray();
|
||||
var bBArr = colours.Blue.ToByteArray();
|
||||
|
||||
int m = 0;
|
||||
foreach (var mask in gBArr)
|
||||
{
|
||||
if (m == 3) break;
|
||||
newG[m] = (byte)(mask * pixelColor.G / 255);
|
||||
m++;
|
||||
}
|
||||
|
||||
if (pixelColor.G == 0 || pixelSum is >= 508 and <= 520)
|
||||
{
|
||||
m = 0;
|
||||
foreach (var mask in rBArr)
|
||||
{
|
||||
if (m == 3) break;
|
||||
newR[m] = (byte)(mask * pixelColor.R / 255);
|
||||
m++;
|
||||
}
|
||||
|
||||
|
||||
m = 0;
|
||||
foreach (var mask in bBArr)
|
||||
{
|
||||
if (m == 3) break;
|
||||
newB[m] = (byte)(mask * pixelColor.B / 255);
|
||||
m++;
|
||||
}
|
||||
}
|
||||
else if (pixelColor.R == pixelColor.B && pixelSum > 512)
|
||||
{
|
||||
m = 0;
|
||||
foreach (var mask in newG)
|
||||
{
|
||||
if (m == 3) break;
|
||||
byte val = (byte)(((pixelColor.R / 255) * (255 - mask)) / 2);
|
||||
newR[m] = val;
|
||||
newB[m] = val;
|
||||
}
|
||||
}
|
||||
|
||||
byte minR = (byte)Math.Min(newR[0] + newG[0] + newB[0], 255);
|
||||
byte minG = (byte)Math.Min(newR[1] + newG[1] + newB[1], 255);
|
||||
byte minB = (byte)Math.Min(newR[2] + newG[2] + newB[2], 255);
|
||||
|
||||
byte[] newColors = { minR, minG, minB, pixelColor.A };
|
||||
|
||||
pixels.SetPixel(x, y, newColors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
image.ImportPixels(pixels.ToArray(), new PixelImportSettings(0, 0, image.Width, image.Height, StorageType.Quantum, PixelMapping.RGBA));
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the Eject variant of the Skin
|
||||
/// </summary>
|
||||
/// <param name="baseImage">Base Crewmate Image</param>
|
||||
/// <param name="layerImage">Skin to Apply</param>
|
||||
/// <param name="idleOffset">Skin offset from <see cref="baseImage"/></param>
|
||||
/// <param name="baseOrigin">Origin of <see cref="baseImage"/></param>
|
||||
/// <returns>Crewmate with Skin Applied</returns>
|
||||
static MagickImage ApplyLayer(MagickImage baseImage, MagickImage layerImage, Point layerOrigin, Point baseOrigin)
|
||||
{
|
||||
int left = layerOrigin.X + baseOrigin.X;
|
||||
int top = layerOrigin.Y + baseOrigin.Y;
|
||||
int right = layerOrigin.X + layerImage.Width - baseImage.Width;
|
||||
int bottom = layerOrigin.Y + layerImage.Height - baseImage.Height;
|
||||
|
||||
left = left < 0 ? Math.Abs(left) : 0;
|
||||
top = top < 0 ? Math.Abs(top) : 0;
|
||||
|
||||
MagickImage newImage = new MagickImage(MagickColors.Transparent, left + right + baseImage.Width, top + bottom + baseImage.Height);
|
||||
newImage.BackgroundColor = MagickColors.Transparent;
|
||||
|
||||
baseOrigin = new Point(left, top);
|
||||
Point newLayerOrigin = new Point(baseOrigin.X + layerOrigin.X, baseOrigin.Y + layerOrigin.Y);
|
||||
|
||||
newImage.Composite(baseImage, baseOrigin.X, baseOrigin.Y, CompositeOperator.Over);
|
||||
newImage.Composite(layerImage, newLayerOrigin.X, newLayerOrigin.Y, CompositeOperator.Over);
|
||||
|
||||
return newImage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a crewmate with the given settings
|
||||
/// </summary>
|
||||
/// <param name="colour">Colour of the crewmate</param>
|
||||
/// <param name="skin">Skin that the crewmate is wearing</param>
|
||||
/// <param name="ejected">If the crewmate has been ejected or not</param>
|
||||
/// <returns>Generated crewmate with the given settings</returns>
|
||||
static MagickImage GenerateCrewmate(AmogusColour colour, AmogusSkin skin, bool ejected = true)
|
||||
{
|
||||
MagickImage baseImage = GenerateBase(ejected ? "ejected.png" : "idle.png", colour);
|
||||
MagickImage skinImage = null;
|
||||
|
||||
if (skin != AmogusSkin.None)
|
||||
{
|
||||
skinImage = new MagickImage(
|
||||
Path.Combine(
|
||||
ejected ? EjectRoot : IdleRoot,
|
||||
ejected ? EjectSkins[skin] : IdleSkins[skin]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
var outImage = skinImage != null ?
|
||||
ApplyLayer(
|
||||
baseImage,
|
||||
skinImage,
|
||||
ejected ? EjectOffset[skin] : IdleOffset[skin], new Point(0, 0)
|
||||
) : baseImage;
|
||||
|
||||
outImage.Trim();
|
||||
|
||||
return outImage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate Amongus Stars
|
||||
/// </summary>
|
||||
/// <param name="width">Width of the stars</param>
|
||||
/// <returns>Generated image of stars</returns>
|
||||
static MagickImage GenerateStars(int width = 2000)
|
||||
{
|
||||
MagickImage baseImage = new MagickImage("xc:transparent", width, 588);
|
||||
|
||||
using MagickImage stars = new MagickImage(Path.Combine(CommonRoot, "Stars.png"));
|
||||
stars.Distort(DistortMethod.ScaleRotateTranslate, 90);
|
||||
using MagickImage stars2 = new MagickImage(stars);
|
||||
stars2.Distort(DistortMethod.ScaleRotateTranslate, 180);
|
||||
|
||||
foreach (var x in Extensions.Range(-800, width, 320))
|
||||
{
|
||||
baseImage.Composite(stars2, Gravity.Northwest, x, (int)(x / 60f) - 200, CompositeOperator.Over);
|
||||
baseImage.Composite(stars, Gravity.Northwest, x, 100 + (int)(x / 120f), CompositeOperator.Over);
|
||||
baseImage.Composite(stars2, Gravity.Northwest, x, 300 - (int)(x / 60f), CompositeOperator.Over);
|
||||
baseImage.Composite(stars, Gravity.Northwest, x, 500 + (int)(x / 120f), CompositeOperator.Over);
|
||||
baseImage.Composite(stars, Gravity.Northwest, x + 160 + (int)(x / 80f), (int)(x / 100f) - 100, CompositeOperator.Over);
|
||||
baseImage.Composite(stars2, Gravity.Northwest, x + 160 - (int)(x / 80f), 100 + (int)(x / 120f), CompositeOperator.Over);
|
||||
baseImage.Composite(stars, Gravity.Northwest, x + 160 + (int)(x / 80f), 300 - (int)(x / 100f), CompositeOperator.Over);
|
||||
baseImage.Composite(stars2, Gravity.Northwest, x + 160 - (int)(x / 80f), 500 - (int)(x / 100f), CompositeOperator.Over);
|
||||
}
|
||||
|
||||
return baseImage;
|
||||
}
|
||||
|
||||
static string GenerateEjectionMessage(string name = "I", AmogusRole role = AmogusRole.Unknown)
|
||||
{
|
||||
StringBuilder message = new StringBuilder(name);
|
||||
message.Append(" was");
|
||||
|
||||
if (role == AmogusRole.Unknown)
|
||||
{
|
||||
message.Append(" ejected.");
|
||||
return message.ToString();
|
||||
}
|
||||
|
||||
if (role == AmogusRole.Crewmate)
|
||||
{
|
||||
message.Append(" not");
|
||||
}
|
||||
|
||||
message.Append(" An Impostor.");
|
||||
|
||||
return message.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the Ejection Gif with all modifications applied
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="colour"></param>
|
||||
/// <param name="skin"></param>
|
||||
/// <param name="role"></param>
|
||||
/// <returns>A <see cref="MemoryStream"/> containing the Amogus Ejected Gif</returns>
|
||||
public static MagickImageCollection GenerateEjectionGif(string name, AmogusColour? colour = null, AmogusRole? role = null, AmogusSkin? skin = null, bool addStars = true)
|
||||
{
|
||||
if (role is null) role = Extensions.GetRandomEnumValue<AmogusRole>();
|
||||
|
||||
return GenerateCustomEjectionGif(GenerateEjectionMessage(name, role.Value), colour, role, skin, addStars);
|
||||
}
|
||||
|
||||
public static MagickImageCollection GenerateCustomEjectionGif(string text, AmogusColour? colour = null, AmogusRole? role = null, AmogusSkin? skin = null, bool addStars = true)
|
||||
{
|
||||
if (colour is null) colour = Extensions.GetRandomEnumValue<AmogusColour>();
|
||||
if (role is null) role = Extensions.GetRandomEnumValue<AmogusRole>();
|
||||
if (skin is null) skin = Extensions.GetRandomEnumValue<AmogusSkin>();
|
||||
|
||||
var crewmate = GenerateCrewmate(colour.Value, skin.Value);
|
||||
crewmate = MakeSquare(crewmate);
|
||||
var textImg = new MagickImage(MagickColors.Transparent, 4000, 64);
|
||||
textImg.WriteText(text, 0, 0, MagickColors.White, Encoding.Default, 24, Arial, 0, MagickColors.Transparent);
|
||||
textImg.Crop(textImg.BoundingBox);
|
||||
textImg.RePage();
|
||||
|
||||
(int X, int Y) center = new(crewmate.Width / 2, crewmate.Height / 2);
|
||||
|
||||
using var background = new MagickImage(MagickColors.Black, Math.Max(textImg.Width + 100, 600), 300);
|
||||
|
||||
MagickImage stars = null, stars1 = null, stars2 = null, stars3 = null;
|
||||
|
||||
if (addStars)
|
||||
{
|
||||
stars = GenerateStars(background.Width * 3);
|
||||
|
||||
stars1 = stars.Clone() as MagickImage;
|
||||
stars1.BackgroundColor = MagickColors.Transparent;
|
||||
stars1.Resize((int)(stars.Width * 3.5f), (int)(stars.Height * 3.5f));
|
||||
|
||||
stars2 = stars1.Clone() as MagickImage;
|
||||
stars2.BackgroundColor = MagickColors.Transparent;
|
||||
|
||||
stars3 = stars1.Clone() as MagickImage;
|
||||
stars3.BackgroundColor = MagickColors.Transparent;
|
||||
}
|
||||
|
||||
var range = Extensions.Range(-120, background.Width * 3, 12);
|
||||
|
||||
MagickImageCollection gifFrames = new MagickImageCollection();
|
||||
|
||||
foreach (var bodyX in range)
|
||||
{
|
||||
var image = background.Clone() as MagickImage;
|
||||
|
||||
if (addStars)
|
||||
{
|
||||
image.Composite(stars1, -background.Width + (int)(bodyX / 24f) - 100, -200, CompositeOperator.Over);
|
||||
image.Composite(stars2, -background.Width + (int)(bodyX / 12f) - 200, -800, CompositeOperator.Over);
|
||||
image.Composite(stars3, -background.Width + (int)(bodyX / 6f) - 100, -425, CompositeOperator.Over);
|
||||
}
|
||||
|
||||
using var rotatedBody = crewmate.Clone() as MagickImage;
|
||||
rotatedBody.BackgroundColor = MagickColors.Transparent;
|
||||
rotatedBody.Distort(DistortMethod.ScaleRotateTranslate, -(2 * bodyX / 3));
|
||||
rotatedBody.RePage();
|
||||
|
||||
var rotateX = bodyX - center.X;
|
||||
var rotateY = background.Height / 2 - center.Y;
|
||||
|
||||
image.Composite(rotatedBody, Gravity.Northwest, rotateX, rotateY, CompositeOperator.Atop);
|
||||
|
||||
using var txt = textImg.Clone() as MagickImage;
|
||||
txt.BackgroundColor = MagickColors.Transparent;
|
||||
|
||||
if (bodyX > background.Width / 2 && bodyX <= 3 * background.Width / 2)
|
||||
{
|
||||
var midX = background.Width / 2;
|
||||
var textY = background.Height / 2 - txt.Height / 2;
|
||||
var textX = Convert.ToInt32(midX - ((float)bodyX - midX) / background.Width * txt.Width / 2f);
|
||||
var textWidth = (midX - textX) * 2;
|
||||
|
||||
txt.Crop(new MagickGeometry(0, 0, textWidth, txt.Height));
|
||||
|
||||
image.Composite(txt, Gravity.Northwest, textX, textY, CompositeOperator.Over);
|
||||
}
|
||||
else if (bodyX > (int)(background.Width * 1.5f) && bodyX <= (int)(background.Width * 2.5f))
|
||||
{
|
||||
image.Composite(txt, (int)(background.Width / 2f - txt.Width / 2f), (int)(background.Height / 2f - txt.Height / 2f), CompositeOperator.Atop);
|
||||
}
|
||||
|
||||
image.AnimationIterations = 0;
|
||||
image.AnimationDelay = 4;
|
||||
|
||||
gifFrames.Add(image);
|
||||
}
|
||||
|
||||
if (addStars)
|
||||
{
|
||||
stars.Dispose();
|
||||
stars1.Dispose();
|
||||
stars2.Dispose();
|
||||
stars3.Dispose();
|
||||
}
|
||||
|
||||
crewmate.Dispose();
|
||||
|
||||
return gifFrames;
|
||||
}
|
||||
|
||||
#region Utilities
|
||||
static MagickImage MakeSquare(MagickImage image)
|
||||
{
|
||||
int size = Math.Max(image.Width, image.Height);
|
||||
|
||||
MagickImage newImage = new MagickImage("xc:transparent", size, size);
|
||||
newImage.Format = MagickFormat.Png;
|
||||
|
||||
int x = (size - image.Width) / 2;
|
||||
int y = (size - image.Height) / 2;
|
||||
|
||||
newImage.Composite(image, Gravity.Center, x, y, CompositeOperator.Over);
|
||||
|
||||
return newImage;
|
||||
}
|
||||
#endregion Utilities
|
||||
}
|
||||
@@ -32,13 +32,14 @@ public class StreamResult : ActionResult, IStatusCodeActionResult, IActionResult
|
||||
? ContentType
|
||||
: "image/png";
|
||||
|
||||
Value.Position = 0;
|
||||
|
||||
await Value.CopyToAsync(response.Body);
|
||||
|
||||
await Value.FlushAsync();
|
||||
|
||||
await Value.DisposeAsync();
|
||||
|
||||
|
||||
context.HttpContext.Response.RegisterForDisposeAsync(Value);
|
||||
|
||||
GC.Collect();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using ImageMagick;
|
||||
using Kynareth.Models;
|
||||
@@ -15,8 +16,28 @@ public static partial class ImageManager
|
||||
|
||||
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, string font = "Impact", int strokeWidth = 2, MagickColor? strokeColor = null)
|
||||
{
|
||||
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 = new MagickColor(strokeColor ?? MagickColors.Black),
|
||||
StrokeWidth = strokeWidth
|
||||
});
|
||||
|
||||
public static MagickImage WriteText(this MagickImage image, string text, PositionRect rect, MagickColor fontColor, Encoding encoding, double fontSize, string font = "Impact")
|
||||
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, double fontSize, string font = "Impact", int strokeWidth = 2, MagickColor? strokeColor = null)
|
||||
{
|
||||
using var label = new MagickImage($"caption:{text}", new MagickReadSettings()
|
||||
{
|
||||
@@ -28,8 +49,8 @@ public static partial class ImageManager
|
||||
FontPointsize = fontSize,
|
||||
Font = font,
|
||||
TextEncoding = encoding,
|
||||
StrokeColor = MagickColors.Black,
|
||||
StrokeWidth = 2
|
||||
StrokeColor = new MagickColor(strokeColor ?? MagickColors.Black),
|
||||
StrokeWidth = strokeWidth
|
||||
});
|
||||
|
||||
image.Composite(label, rect.X, rect.Y, CompositeOperator.Over);
|
||||
@@ -37,22 +58,125 @@ public static partial class ImageManager
|
||||
return image;
|
||||
}
|
||||
|
||||
public static MagickImage WriteText(this MagickImage image, string text, PositionRect rect, MagickColor fontColor, Encoding encoding, string font = "Impact")
|
||||
public static MagickImage WriteText(this MagickImage image, string text, Vector2 position, MagickColor fontColor, Encoding encoding, string font = "Impact", int strokeWidth = 2, MagickColor? strokeColor = null)
|
||||
{
|
||||
using var label = new MagickImage($"label:{text}", new MagickReadSettings()
|
||||
using var label = new MagickImage($"caption:{text}", new MagickReadSettings()
|
||||
{
|
||||
Width = rect.Width,
|
||||
Height = rect.Height,
|
||||
Width = image.Width,
|
||||
Height = image.Height,
|
||||
BackgroundColor = MagickColors.Transparent,
|
||||
FillColor = fontColor,
|
||||
TextGravity = Gravity.Center,
|
||||
Font = font,
|
||||
TextEncoding = encoding,
|
||||
StrokeColor = MagickColors.Black,
|
||||
StrokeWidth = 2
|
||||
StrokeColor = new MagickColor(strokeColor ?? MagickColors.Black),
|
||||
StrokeWidth = strokeWidth
|
||||
});
|
||||
|
||||
image.Composite(label, rect.X, rect.Y, CompositeOperator.Over);
|
||||
image.Composite(label, (int)position.X, (int)position.Y, CompositeOperator.Over);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
public static MagickImage WriteText(this MagickImage image, string text, Vector2 position, MagickColor fontColor, Encoding encoding, double fontSize, string font = "Impact", int strokeWidth = 2, MagickColor? strokeColor = null)
|
||||
{
|
||||
using var label = new MagickImage($"caption:{text}", new MagickReadSettings()
|
||||
{
|
||||
Width = image.Width,
|
||||
Height = image.Height,
|
||||
BackgroundColor = MagickColors.Transparent,
|
||||
FillColor = fontColor,
|
||||
TextGravity = Gravity.Center,
|
||||
FontPointsize = fontSize,
|
||||
Font = font,
|
||||
TextEncoding = encoding,
|
||||
StrokeColor = new MagickColor(strokeColor ?? MagickColors.Black),
|
||||
StrokeWidth = strokeWidth
|
||||
});
|
||||
|
||||
image.Composite(label, (int)position.X, (int)position.Y, CompositeOperator.Over);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
public static MagickImage WriteText(this MagickImage image, string text, PointD position, MagickColor fontColor, Encoding encoding, string font = "Impact", int strokeWidth = 2, MagickColor? strokeColor = null)
|
||||
{
|
||||
using var label = new MagickImage($"caption:{text}", new MagickReadSettings()
|
||||
{
|
||||
Width = image.Width,
|
||||
Height = image.Height,
|
||||
BackgroundColor = MagickColors.Transparent,
|
||||
FillColor = fontColor,
|
||||
TextGravity = Gravity.Center,
|
||||
Font = font,
|
||||
TextEncoding = encoding,
|
||||
StrokeColor = new MagickColor(strokeColor ?? MagickColors.Black),
|
||||
StrokeWidth = strokeWidth
|
||||
});
|
||||
|
||||
image.Composite(label, (int)position.X, (int)position.Y, CompositeOperator.Over);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
public static MagickImage WriteText(this MagickImage image, string text, PointD position, MagickColor fontColor, Encoding encoding, double fontSize, string font = "Impact", int strokeWidth = 2, MagickColor? strokeColor = null)
|
||||
{
|
||||
using var label = new MagickImage($"caption:{text}", new MagickReadSettings()
|
||||
{
|
||||
Width = image.Width,
|
||||
Height = image.Height,
|
||||
BackgroundColor = MagickColors.Transparent,
|
||||
FillColor = fontColor,
|
||||
TextGravity = Gravity.Center,
|
||||
FontPointsize = fontSize,
|
||||
Font = font,
|
||||
TextEncoding = encoding,
|
||||
StrokeColor = new MagickColor(strokeColor ?? MagickColors.Black),
|
||||
StrokeWidth = strokeWidth
|
||||
});
|
||||
|
||||
image.Composite(label, (int)position.X, (int)position.Y, CompositeOperator.Over);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
public static MagickImage WriteText(this MagickImage image, string text, int x, int y, MagickColor fontColor, Encoding encoding, string font = "Impact", int strokeWidth = 2, MagickColor? strokeColor = null)
|
||||
{
|
||||
using var label = new MagickImage($"caption:{text}", new MagickReadSettings()
|
||||
{
|
||||
Width = image.Width,
|
||||
Height = image.Height,
|
||||
BackgroundColor = MagickColors.Transparent,
|
||||
FillColor = fontColor,
|
||||
TextGravity = Gravity.Center,
|
||||
Font = font,
|
||||
TextEncoding = encoding,
|
||||
StrokeColor = new MagickColor(strokeColor ?? MagickColors.Black),
|
||||
StrokeWidth = strokeWidth
|
||||
});
|
||||
|
||||
image.Composite(label, x, y, CompositeOperator.Over);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
public static MagickImage WriteText(this MagickImage image, string text, int x, int y, MagickColor fontColor, Encoding encoding, double fontSize, string font = "Impact", int strokeWidth = 2, MagickColor? strokeColor = null)
|
||||
{
|
||||
using var label = new MagickImage($"caption:{text}", new MagickReadSettings()
|
||||
{
|
||||
Width = image.Width,
|
||||
Height = image.Height,
|
||||
BackgroundColor = MagickColors.Transparent,
|
||||
FillColor = fontColor,
|
||||
TextGravity = Gravity.Center,
|
||||
FontPointsize = fontSize,
|
||||
Font = font,
|
||||
TextEncoding = encoding,
|
||||
StrokeColor = new MagickColor(strokeColor ?? MagickColors.Black),
|
||||
StrokeWidth = strokeWidth
|
||||
});
|
||||
|
||||
image.Composite(label, x, y, CompositeOperator.Over);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Exsersewo.Common.Utilities;
|
||||
using Kynareth.Helpers;
|
||||
using Kynareth.Managers;
|
||||
using Kynareth.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -32,6 +33,7 @@ builder.Services.AddDbContext<KynarethDbContext>(options =>
|
||||
var app = builder.Build();
|
||||
|
||||
ImageManager.Configure(app.Configuration, app.Environment);
|
||||
AmogusHelper.Configure(app.Environment);
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
//if (app.Environment.IsDevelopment())
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5054",
|
||||
"applicationUrl": "http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7072;http://localhost:5054",
|
||||
"applicationUrl": "https://localhost:7072;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
||||
@@ -1705,6 +1705,24 @@
|
||||
"h": 75
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fortune",
|
||||
"image": "fortuneteller.jpg",
|
||||
"textpositions": [
|
||||
{
|
||||
"x": 130,
|
||||
"y": 413,
|
||||
"w": 101,
|
||||
"h": 67
|
||||
},
|
||||
{
|
||||
"x": 505,
|
||||
"y": 455,
|
||||
"w": 127,
|
||||
"h": 66
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
BIN
src/wwwroot/amogus/common/Dead0001.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/wwwroot/amogus/common/Dead0040.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src/wwwroot/amogus/common/SpacemanLarge.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
src/wwwroot/amogus/common/Stars.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/wwwroot/amogus/common/ejected.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
src/wwwroot/amogus/common/ghostbob0001.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
src/wwwroot/amogus/common/idle.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
src/wwwroot/amogus/common/noshadow.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/wwwroot/amogus/eject/archae-eject.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/wwwroot/amogus/eject/astro-eject.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/wwwroot/amogus/eject/captain-eject.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/wwwroot/amogus/eject/hazmat-eject.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/wwwroot/amogus/eject/mech-eject.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/wwwroot/amogus/eject/military_ejected.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/wwwroot/amogus/eject/miner-eject.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/wwwroot/amogus/eject/police_eject.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/wwwroot/amogus/eject/sci-eject.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/wwwroot/amogus/eject/secguard-eject.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/wwwroot/amogus/eject/suitBlack-eject.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/wwwroot/amogus/eject/suitWhite-eject.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
src/wwwroot/amogus/eject/tarmac-eject.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src/wwwroot/amogus/eject/wall-eject.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/wwwroot/amogus/eject/winter-eject.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/wwwroot/amogus/idle/archae-idle.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/wwwroot/amogus/idle/astro-idle.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/wwwroot/amogus/idle/captain-idle.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src/wwwroot/amogus/idle/hazmat-idle.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/wwwroot/amogus/idle/mech-idle.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src/wwwroot/amogus/idle/military-idle.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/wwwroot/amogus/idle/miner-idle.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
src/wwwroot/amogus/idle/pol-idle.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/wwwroot/amogus/idle/sci-idle.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/wwwroot/amogus/idle/secguard-idle.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
src/wwwroot/amogus/idle/suitBlack-idle.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src/wwwroot/amogus/idle/suitWhite-idle.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/wwwroot/amogus/idle/tarmac-idle.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src/wwwroot/amogus/idle/wall-idle.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/wwwroot/amogus/idle/winter-idle.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
src/wwwroot/imagemanip/templates/fortuneteller.jpg
Normal file
|
After Width: | Height: | Size: 85 KiB |