using EllieBot.Modules.Gambling.Services; using SixLabors.Fonts; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using Image = SixLabors.ImageSharp.Image; namespace EllieBot.Modules.Games; public partial class Games { public sealed class NCanvasCommands : EllieModule { private readonly INCanvasService _service; private readonly IHttpClientFactory _http; private readonly FontProvider _fonts; private readonly GamblingConfigService _gcs; public NCanvasCommands( INCanvasService service, IHttpClientFactory http, FontProvider fonts, GamblingConfigService gcs) { _service = service; _http = http; _fonts = fonts; _gcs = gcs; } [Cmd] public async Task NCanvas() { var pixels = await _service.GetCanvas(); var image = new Image<Rgba32>(_service.GetWidth(), _service.GetHeight()); Parallel.For(0, image.Height, y => { var pixelAccessor = image.DangerousGetPixelRowMemory(y); var row = pixelAccessor.Span; for (int x = 0; x < image.Width; x++) { row[x] = new Rgba32(pixels[(y * image.Width) + x]); } }); await using var stream = await image.ToStreamAsync(); var hint = GetText(strs.nc_hint(prefix, _service.GetWidth(), _service.GetHeight())); await Response() .File(stream, "ncanvas.png") .Embed(CreateEmbed() .WithOkColor() #if GLOBAL_ELLIE .WithDescription("This is not available yet.") #endif .WithFooter(hint) .WithImageUrl("attachment://ncanvas.png")) .SendAsync(); } [Cmd] public Task NCzoom(int row, int col) => NCzoom((col * _service.GetWidth()) + row); [Cmd] public async Task NCzoom(kwum position) { var w = _service.GetWidth(); var h = _service.GetHeight(); if (position < 0 || position >= w * h) { await Response().Error(strs.invalid_input).SendAsync(); return; } using var img = await GetZoomImage(position); await using var stream = await img.ToStreamAsync(); await ctx.Channel.SendFileAsync(stream, $"zoom_{position}.png"); } private async Task<Image<Rgba32>> GetZoomImage(kwum position) { var w = _service.GetWidth(); var pixels = await _service.GetPixelGroup(position); var origX = ((position % w) - 2) * 100; var origY = ((position / w) - 2) * 100; var image = new Image<Rgba32>(500, 500); const float fontSize = 30; var posFont = _fonts.NotoSans.CreateFont(fontSize, FontStyle.Bold); var size = TextMeasurer.MeasureSize("wwww", new TextOptions(posFont)); var scale = 100f / size.Width; if (scale < 1) posFont = _fonts.NotoSans.CreateFont(fontSize * scale, FontStyle.Bold); var outlinePen = new SolidPen(SixLabors.ImageSharp.Color.Black, 1f); Parallel.For(0, pixels.Length, i => { var pix = pixels[i]; var startX = pix.Position % w * 100 - origX; var startY = pix.Position / w * 100 - origY; var color = new Rgba32(pix.Color); image.Mutate(x => FillRectangleExtensions.Fill(x, new SolidBrush(color), new RectangleF(startX, startY, 100, 100))); image.Mutate(x => { x.DrawText(new RichTextOptions(posFont) { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, Origin = new(startX + 50, startY + 50) }, ((kwum)pix.Position).ToString().PadLeft(2, '2'), Brushes.Solid(SixLabors.ImageSharp.Color.White), outlinePen); }); }); // write the position on each section of the image return image; } [Cmd] public async Task NcSetPixel(kwum position, string colorHex, [Leftover] string text = "") { if (position < 0 || position >= _service.GetWidth() * _service.GetHeight()) { await Response().Error(strs.invalid_input).SendAsync(); return; } if (colorHex.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) colorHex = colorHex[2..]; if (!Rgba32.TryParseHex(colorHex, out var clr)) { await Response().Error(strs.invalid_color).SendAsync(); return; } var pixel = await _service.GetPixel(position); if (pixel is null) { await Response().Error(strs.nc_pixel_not_found).SendAsync(); return; } var prompt = GetText(strs.nc_pixel_set_confirm(Format.Code(position.ToString()), Format.Bold(CurrencyHelper.N(pixel.Price, Culture, _gcs.Data.Currency.Sign)))); if (!await PromptUserConfirmAsync(CreateEmbed() .WithPendingColor() .WithDescription(prompt))) { return; } var result = await _service.SetPixel(position, clr.PackedValue, text, ctx.User.Id, pixel.Price); if (result == SetPixelResult.NotEnoughMoney) { await Response().Error(strs.not_enough(_gcs.Data.Currency.Sign)).SendAsync(); return; } else if (result == SetPixelResult.InsufficientPayment) { await Response().Error(strs.nc_insuff_payment).SendAsync(); return; } else if (result == SetPixelResult.InvalidInput) { await Response().Error(strs.invalid_input).SendAsync(); return; } using var img = await GetZoomImage(position); await using var stream = await img.ToStreamAsync(); await Response() .Embed(CreateEmbed() .WithOkColor() .WithDescription(GetText(strs.nc_pixel_set(Format.Code(position.ToString())))) .WithImageUrl($"attachment://zoom_{position}.png")) .File(stream, $"zoom_{position}.png") .SendAsync(); } [Cmd] public async Task NcPixel(int x, int y) => await NcPixel((y * _service.GetWidth()) + x); [Cmd] public async Task NcPixel(kwum position) { if (position < 0 || position >= _service.GetWidth() * _service.GetHeight()) { await Response().Error(strs.invalid_input).SendAsync(); return; } var pixel = await _service.GetPixel(position); if (pixel is null) { await Response().Error(strs.nc_pixel_not_found).SendAsync(); return; } var image = new Image<Rgba32>(100, 100); image.Mutate(x => x.Fill(new SolidBrush(new Rgba32(pixel.Color)), new RectangleF(0, 0, 100, 100))); await using var stream = await image.ToStreamAsync(); var pos = new kwum(pixel.Position); await Response() .File(stream, $"{pixel.Position}.png") .Embed(CreateEmbed() .WithOkColor() .WithDescription(string.IsNullOrWhiteSpace(pixel.Text) ? string.Empty : pixel.Text) .WithTitle(GetText(strs.nc_pixel(pos))) .AddField(GetText(strs.nc_position), $"{pixel.Position % _service.GetWidth()} {pixel.Position / _service.GetWidth()}", true) .AddField(GetText(strs.price), pixel.Price.ToString(), true) .AddField(GetText(strs.color), "#" + new Rgba32(pixel.Color).ToHex()) .WithImageUrl($"attachment://{pixel.Position}.png")) .SendAsync(); } [Cmd] [OwnerOnly] public async Task NcSetImg() { var attach = ctx.Message.Attachments.FirstOrDefault(); if (attach is null) { await Response().Error(strs.no_attach_found).SendAsync(); return; } var w = _service.GetWidth(); var h = _service.GetHeight(); if (attach.Width != w || attach.Height != h) { await Response().Error(strs.invalid_img_size(w, h)).SendAsync(); return; } if (!await PromptUserConfirmAsync(CreateEmbed() .WithDescription( "This will reset the canvas to the specified image. All prices, text and colors will be reset.\n\n" + "Are you sure you want to continue?"))) return; using var http = _http.CreateClient(); await using var stream = await http.GetStreamAsync(attach.Url); using var img = await Image.LoadAsync<Rgba32>(stream); var pixels = new uint[_service.GetWidth() * _service.GetHeight()]; Parallel.For(0, _service.GetWidth() * _service.GetHeight(), i => pixels[i] = img[i % _service.GetWidth(), i / _service.GetWidth()].PackedValue); // for (var y = 0; y < _service.GetHeight(); y++) // for (var x = 0; x < _service.GetWidth(); x++) // pixels[(y * _service.GetWidth()) + x] = img[x, y].PackedValue; await _service.SetImage(pixels); await ctx.OkAsync(); } [Cmd] [OwnerOnly] public async Task NcReset() { await _service.ResetAsync(); if (!await PromptUserConfirmAsync(CreateEmbed() .WithDescription( "This will delete all pixels and reset the canvas.\n\n" + "Are you sure you want to continue?"))) return; await ctx.OkAsync(); } } }