From 7d4e9b09398051d730390f413d7d7f786bf2a393 Mon Sep 17 00:00:00 2001 From: Pascal Pomper Date: Fri, 29 Nov 2024 18:06:33 +0100 Subject: [PATCH 1/2] Add `/speechbubble` command --- .../Commands/SpeechBubbleCommandModule.cs | 82 ++++++++++++++++++ src/Numerous.Bot/Numerous.Bot.csproj | 5 ++ src/Numerous.Bot/Resources/speech_bubble.png | Bin 0 -> 21649 bytes 3 files changed, 87 insertions(+) create mode 100644 src/Numerous.Bot/Discord/Interactions/Commands/SpeechBubbleCommandModule.cs create mode 100644 src/Numerous.Bot/Resources/speech_bubble.png diff --git a/src/Numerous.Bot/Discord/Interactions/Commands/SpeechBubbleCommandModule.cs b/src/Numerous.Bot/Discord/Interactions/Commands/SpeechBubbleCommandModule.cs new file mode 100644 index 0000000..9fb0c68 --- /dev/null +++ b/src/Numerous.Bot/Discord/Interactions/Commands/SpeechBubbleCommandModule.cs @@ -0,0 +1,82 @@ +// Copyright (C) Pasi4K5 +// This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with this program. If not, see . + +using System.Reflection; +using Discord; +using Discord.Interactions; +using JetBrains.Annotations; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using Image = SixLabors.ImageSharp.Image; + +namespace Numerous.Bot.Discord.Interactions.Commands; + +public sealed class SpeechBubbleCommandModule(IHttpClientFactory clientFactory) : InteractionModule +{ + [UsedImplicitly] + [SlashCommand("speechbubble", "Generates a speech bubble meme with the given image")] + public async Task SpeechBubble( + [Summary("image", "Source image")] + IAttachment attachment + ) + { + if (!attachment.ContentType.StartsWith("image/")) + { + await RespondWithEmbedAsync( + message: "This file type is not supported.", + type: ResponseType.Error + ); + + return; + } + + var speechBubbleStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Numerous.Bot.Resources.speech_bubble.png"); + + if (speechBubbleStream is null) + { + throw new FileNotFoundException("Could not find 'speech_bubble.png'."); + } + + await DeferAsync(); + + using var speechBubble = await Image.LoadAsync(speechBubbleStream); + + using var client = clientFactory.CreateClient(); + await using var customImageStream = await client.GetStreamAsync(attachment.Url); + var customImage = await Image.LoadAsync(customImageStream); + + var customImageAspectRatio = (float)customImage.Width / customImage.Height; + var speechBubbleAspectRatio = (float)speechBubble.Width / speechBubble.Height; + speechBubble.Mutate(x => x.Resize(new Size( + customImage.Width, + (int)Math.Floor((double)customImage.Width / speechBubbleAspectRatio / customImageAspectRatio))) + ); + + using var result = new Image(customImage.Width, customImage.Height); + + for (var y = 0; y < customImage.Height; y++) + { + for (var x = 0; x < customImage.Width; x++) + { + if (y < speechBubble.Height) + { + var imgPixel = customImage[x, y]; + var maskPixel = speechBubble[x, y]; + var alpha = (double)maskPixel.R / 255; + result[x, y] = new Rgba32(imgPixel.R, imgPixel.G, imgPixel.B, (byte)Math.Round(alpha * imgPixel.A)); + } + else + { + result[x, y] = customImage[x, y]; + } + } + } + + using var resultStream = new MemoryStream(); + await result.SaveAsPngAsync(resultStream); + await FollowupWithFileAsync(resultStream, "speech_bubble.png"); + } +} diff --git a/src/Numerous.Bot/Numerous.Bot.csproj b/src/Numerous.Bot/Numerous.Bot.csproj index d15f665..3d19594 100644 --- a/src/Numerous.Bot/Numerous.Bot.csproj +++ b/src/Numerous.Bot/Numerous.Bot.csproj @@ -26,11 +26,16 @@ + + + + + diff --git a/src/Numerous.Bot/Resources/speech_bubble.png b/src/Numerous.Bot/Resources/speech_bubble.png new file mode 100644 index 0000000000000000000000000000000000000000..ea1c5d2a70dac60edb75d55f3511902f803c9b80 GIT binary patch literal 21649 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 literal 0 HcmV?d00001 From 2c94540e5bbfac12b5e27adfb6d43c6c08348f1a Mon Sep 17 00:00:00 2001 From: Pascal Pomper Date: Fri, 29 Nov 2024 18:09:53 +0100 Subject: [PATCH 2/2] Properly handle unsupported image formats --- .../Commands/SpeechBubbleCommandModule.cs | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/src/Numerous.Bot/Discord/Interactions/Commands/SpeechBubbleCommandModule.cs b/src/Numerous.Bot/Discord/Interactions/Commands/SpeechBubbleCommandModule.cs index 9fb0c68..e93a84f 100644 --- a/src/Numerous.Bot/Discord/Interactions/Commands/SpeechBubbleCommandModule.cs +++ b/src/Numerous.Bot/Discord/Interactions/Commands/SpeechBubbleCommandModule.cs @@ -26,7 +26,7 @@ IAttachment attachment if (!attachment.ContentType.StartsWith("image/")) { await RespondWithEmbedAsync( - message: "This file type is not supported.", + message: "The file you uploaded is not an image.", type: ResponseType.Error ); @@ -46,37 +46,48 @@ await RespondWithEmbedAsync( using var client = clientFactory.CreateClient(); await using var customImageStream = await client.GetStreamAsync(attachment.Url); - var customImage = await Image.LoadAsync(customImageStream); - var customImageAspectRatio = (float)customImage.Width / customImage.Height; - var speechBubbleAspectRatio = (float)speechBubble.Width / speechBubble.Height; - speechBubble.Mutate(x => x.Resize(new Size( - customImage.Width, - (int)Math.Floor((double)customImage.Width / speechBubbleAspectRatio / customImageAspectRatio))) - ); + try + { + var customImage = await Image.LoadAsync(customImageStream); - using var result = new Image(customImage.Width, customImage.Height); + var customImageAspectRatio = (float)customImage.Width / customImage.Height; + var speechBubbleAspectRatio = (float)speechBubble.Width / speechBubble.Height; + speechBubble.Mutate(x => x.Resize(new Size( + customImage.Width, + (int)Math.Floor((double)customImage.Width / speechBubbleAspectRatio / customImageAspectRatio))) + ); - for (var y = 0; y < customImage.Height; y++) - { - for (var x = 0; x < customImage.Width; x++) + using var result = new Image(customImage.Width, customImage.Height); + + for (var y = 0; y < customImage.Height; y++) { - if (y < speechBubble.Height) - { - var imgPixel = customImage[x, y]; - var maskPixel = speechBubble[x, y]; - var alpha = (double)maskPixel.R / 255; - result[x, y] = new Rgba32(imgPixel.R, imgPixel.G, imgPixel.B, (byte)Math.Round(alpha * imgPixel.A)); - } - else + for (var x = 0; x < customImage.Width; x++) { - result[x, y] = customImage[x, y]; + if (y < speechBubble.Height) + { + var imgPixel = customImage[x, y]; + var maskPixel = speechBubble[x, y]; + var alpha = (double)maskPixel.R / 255; + result[x, y] = new Rgba32(imgPixel.R, imgPixel.G, imgPixel.B, (byte)Math.Round(alpha * imgPixel.A)); + } + else + { + result[x, y] = customImage[x, y]; + } } } - } - using var resultStream = new MemoryStream(); - await result.SaveAsPngAsync(resultStream); - await FollowupWithFileAsync(resultStream, "speech_bubble.png"); + using var resultStream = new MemoryStream(); + await result.SaveAsPngAsync(resultStream); + await FollowupWithFileAsync(resultStream, "speech_bubble.png"); + } + catch (UnknownImageFormatException) + { + await FollowupWithEmbedAsync( + message: "This image format is not supported.", + type: ResponseType.Error + ); + } } }