@ -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 |