From 159e7b0af229e93e206008e91954801855c59a1b Mon Sep 17 00:00:00 2001 From: Silly Date: Sun, 6 Oct 2024 16:03:43 -0400 Subject: [PATCH] Make headpats animated :3 --- .../astralbot/commands/discord/FunCommands.kt | 77 ++++++++++-------- .../dev/erdragh/astralbot/util/GifWriter.kt | 70 ++++++++++++++++ common/src/main/resources/headpat.png | Bin 2960 -> 0 bytes common/src/main/resources/headpat/pet0.gif | Bin 0 -> 2764 bytes common/src/main/resources/headpat/pet1.gif | Bin 0 -> 3042 bytes common/src/main/resources/headpat/pet2.gif | Bin 0 -> 2934 bytes common/src/main/resources/headpat/pet3.gif | Bin 0 -> 2936 bytes common/src/main/resources/headpat/pet4.gif | Bin 0 -> 2598 bytes 8 files changed, 115 insertions(+), 32 deletions(-) create mode 100644 common/src/main/kotlin/dev/erdragh/astralbot/util/GifWriter.kt delete mode 100644 common/src/main/resources/headpat.png create mode 100644 common/src/main/resources/headpat/pet0.gif create mode 100644 common/src/main/resources/headpat/pet1.gif create mode 100644 common/src/main/resources/headpat/pet2.gif create mode 100644 common/src/main/resources/headpat/pet3.gif create mode 100644 common/src/main/resources/headpat/pet4.gif diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/commands/discord/FunCommands.kt b/common/src/main/kotlin/dev/erdragh/astralbot/commands/discord/FunCommands.kt index 3a38048..3a50e4a 100644 --- a/common/src/main/kotlin/dev/erdragh/astralbot/commands/discord/FunCommands.kt +++ b/common/src/main/kotlin/dev/erdragh/astralbot/commands/discord/FunCommands.kt @@ -1,11 +1,13 @@ package dev.erdragh.astralbot.commands.discord +import dev.erdragh.astralbot.util.GifWriter import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent import net.dv8tion.jda.api.interactions.commands.OptionType import net.dv8tion.jda.api.interactions.commands.build.Commands import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData import net.dv8tion.jda.api.utils.FileUpload -import java.awt.Color +import java.awt.Graphics2D +import java.awt.RenderingHints import java.awt.image.BufferedImage import java.io.ByteArrayOutputStream import java.net.URL @@ -15,7 +17,13 @@ object HeadpatCommand : HandledSlashCommand { private const val USER_OPTION = "user" override val command: SlashCommandData = Commands.slash("headpat", "Headpats a user") .addOption(OptionType.USER, USER_OPTION, "The user whose avatar will be headpat.", true) - private val headpatBaseImage = ImageIO.read(this.javaClass.getResource("/headpat.png")) + + private val ANIMATION = floatArrayOf(-.05f, .1f, .2f, .19f, .1f) + private val FRAMES: Array = Array(5) { ImageIO.read(this::class.java.getResourceAsStream("/headpat/pet$it.gif")) } + private val RENDERING_HINTS = RenderingHints(mapOf( + RenderingHints.KEY_ANTIALIASING to RenderingHints.VALUE_ANTIALIAS_ON, + RenderingHints.KEY_RENDERING to RenderingHints.VALUE_RENDER_QUALITY + )) override fun handle(event: SlashCommandInteractionEvent) { event.deferReply(false).queue() @@ -28,35 +36,40 @@ object HeadpatCommand : HandledSlashCommand { val url = URL(user.effectiveAvatarUrl) val avatar = ImageIO.read(url) - val headpatImage = BufferedImage(headpatBaseImage.width, headpatBaseImage.height, BufferedImage.TYPE_INT_ARGB) - - val graphics = headpatImage.createGraphics() - - val xOffset = 20 - val yOffset = 20 - graphics.drawImage( - avatar, - xOffset, - yOffset, - headpatImage.width - xOffset, - headpatImage.height - yOffset, - Color(0, 0, 0, 0), - null - ) - graphics.drawImage( - headpatBaseImage, - 0, - 0, - headpatBaseImage.width, - headpatBaseImage.height, - Color(0, 0, 0, 0), - null - ) - - graphics.dispose() - val byteStream = ByteArrayOutputStream() - ImageIO.write(headpatImage, "png", byteStream) - event.hook.sendFiles(FileUpload.fromData(byteStream.toByteArray(), "headpat.png")).queue() - } + val stream: ByteArrayOutputStream + + ByteArrayOutputStream().use { output -> + ImageIO.createImageOutputStream(output).use { out -> + GifWriter( + out, BufferedImage.TYPE_INT_ARGB, + timeBetweenFramesMS = 50, loopContinuously = true, transparent = true + ).use { gifWriter -> + for (i in FRAMES.indices) { + val frame = BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB) + val graphics = frame.graphics as Graphics2D + // set rendering hints to slightly improve quality + graphics.setRenderingHints(RENDERING_HINTS) + + val offset1 = Math.round(ANIMATION[i] * 64) + val offset2 = Math.round((-ANIMATION[i]) * 64) + + // draw avatar + graphics.drawImage(avatar, 2, 32 + offset1, 128 - offset2, 128 - 32 - offset1, null) + // draw hand + graphics.drawImage(FRAMES[i], 0, 0, 128, 128, null) + + gifWriter.write(frame) + + graphics.dispose() + } + } + out.flush() + + stream = output + } + } + + event.hook.sendFiles(FileUpload.fromData(stream.toByteArray(), "headpat.gif")).queue() + } } \ No newline at end of file diff --git a/common/src/main/kotlin/dev/erdragh/astralbot/util/GifWriter.kt b/common/src/main/kotlin/dev/erdragh/astralbot/util/GifWriter.kt new file mode 100644 index 0000000..1b61f5a --- /dev/null +++ b/common/src/main/kotlin/dev/erdragh/astralbot/util/GifWriter.kt @@ -0,0 +1,70 @@ +package dev.erdragh.astralbot.util + +import java.awt.image.RenderedImage +import java.io.IOException +import javax.imageio.* +import javax.imageio.metadata.IIOMetadata +import javax.imageio.metadata.IIOMetadataNode +import javax.imageio.stream.ImageOutputStream + +/** + * A simple utility for creating gifs. + * + * @author femmeromantic + */ +class GifWriter( + os: ImageOutputStream?, imageType: Int, + timeBetweenFramesMS: Int, loopContinuously: Boolean, transparent: Boolean +) : AutoCloseable { + private val writer: ImageWriter = + ImageIO.getImageWritersBySuffix("gif").next() ?: throw IOException("No GIF Image Writers Exist!") + private val imageWriteParam: ImageWriteParam = writer.defaultWriteParam + private val metadata: IIOMetadata = writer.getDefaultImageMetadata( + ImageTypeSpecifier.createFromBufferedImageType(imageType), + imageWriteParam + ) + + init { + val root = metadata.getAsTree(metadata.nativeMetadataFormatName) as IIOMetadataNode + setGifAttributes(root, timeBetweenFramesMS, transparent, loopContinuously) + metadata.setFromTree(metadata.nativeMetadataFormatName, root) + writer.output = os + writer.prepareWriteSequence(null) + } + + fun write(img: RenderedImage) { + writer.writeToSequence(IIOImage(img, null, metadata), imageWriteParam) + } + + override fun close() { + writer.endWriteSequence() + } + + private fun getOrCreate(root: IIOMetadataNode, name: String): IIOMetadataNode = + (0 until root.length) + .map { root.item(it) as IIOMetadataNode } + .firstOrNull { it.nodeName == name } + ?: IIOMetadataNode(name).also { root.appendChild(it) } + + private fun setGifAttributes( + root: IIOMetadataNode, timeBetweenFramesMS: Int, transparent: Boolean, loopContinuously: Boolean + ) { + getOrCreate(root, "GraphicControlExtension").apply { + setAttribute("disposalMethod", "restoreToBackgroundColor") + setAttribute("userInputFlag", "FALSE") + setAttribute("transparentColorFlag", if (transparent) "TRUE" else "FALSE") + setAttribute("delayTime", (timeBetweenFramesMS / 10).toString()) + setAttribute("transparentColorIndex", "0") + } + getOrCreate(root, "CommentExtensions").setAttribute("CommentExtension", "Test Comment") + + val appEN = getOrCreate(root, "ApplicationExtensions") + val loop = if (loopContinuously) 0 else 1 + IIOMetadataNode("ApplicationExtension").apply { + setAttribute("applicationID", "NETSCAPE") + setAttribute("authenticationCode", "2.0") + userObject = byteArrayOf(0x1, loop.toByte(), 0) + appEN.appendChild(this) + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/headpat.png b/common/src/main/resources/headpat.png deleted file mode 100644 index 1e2763495537d787f2075e991257eac118cae8bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2960 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ℜ%j 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 literal 0 HcmV?d00001 diff --git a/common/src/main/resources/headpat/pet4.gif b/common/src/main/resources/headpat/pet4.gif new file mode 100644 index 0000000000000000000000000000000000000000..f9fb14c6d828624605155b6ae4e290ec2a8d6c88 GIT binary patch literal 2598 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 I{T>7WJAqclwEzGB literal 0 HcmV?d00001