@ -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"))); |
||||||
|
} |
@ -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(); |
||||||
|
} |
||||||
|
} |
@ -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 |
||||||
|
} |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 85 KiB |