Add more overlay options

This commit is contained in:
Michał Gdula 2023-09-09 12:28:19 +01:00
parent 7e108350a5
commit f0f3d3b1d3
6 changed files with 141 additions and 105 deletions

BIN
lynxie/assets/bandicam.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
lynxie/assets/jerm-a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
lynxie/assets/jerma.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -13,27 +13,29 @@ class Animals(commands.Cog):
self.bot = bot self.bot = bot
@commands.command() @commands.command()
async def animal(self, ctx, animal): async def animal(self, ctx, animal_choice: str = ""):
animal = animal.lower().strip() or "racc" animal_choice = animal_choice.lower().strip() or None
if animal not in TINYFOX_ANIMALS: if not animal_choice:
await ctx.reply( error = f"You need to specify an animal! Try one of these: {', '.join(TINYFOX_ANIMALS)}"
embed=error_message( await ctx.reply(embed=error_message(error))
f"That animal doesn't exist! Try one of these:\n" return
f"`{', '.join(TINYFOX_ANIMALS)}`"
) if animal_choice not in TINYFOX_ANIMALS:
) error = f"That animal doesn't exist! Try one of these: {', '.join(TINYFOX_ANIMALS)}"
await ctx.reply(embed=error_message(error))
return return
async with ctx.typing(): async with ctx.typing():
request = requests.get(f"https://api.tinyfox.dev/img?animal={animal}&json") request = requests.get("https://api.tinyfox.dev/img?animal=" + animal_choice)
animal_image = BytesIO(request.content)
animal_image.seek(0)
animal_file = discord.File(animal_image, filename="image.png")
embed = discord.Embed( with BytesIO(request.content) as response:
title=animal.capitalize(), response.seek(0)
colour=discord.Colour.orange(), animal_file = discord.File(response, filename="image.png")
).set_image(url="attachment://image.png")
await ctx.reply(embed=embed, file=animal_file, mention_author=False) embed = discord.Embed(
title=animal_choice.capitalize(),
colour=discord.Colour.orange(),
).set_image(url="attachment://image.png")
await ctx.reply(embed=embed, file=animal_file, mention_author=False)

View file

@ -1,31 +1,28 @@
import os
import datetime import datetime
import requests import requests
from io import BytesIO from io import BytesIO
from PIL import Image from PIL import Image, ImageEnhance
import discord import discord
from discord.ext import commands from discord.ext import commands
from lynxie.config import IMAGE_EXTENSIONS, IMAGE_OVERLAYS, ASSETS_PATH from lynxie.config import IMAGE_EXTENSIONS, IMAGE_OVERLAYS
from lynxie.utils import error_message from lynxie.utils import error_message
class Img(commands.Cog): class Img(commands.Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
self._overlays = {
"bubble": Image.open(os.path.join(ASSETS_PATH, "bubble.png")),
"gang": Image.open(os.path.join(ASSETS_PATH, "gang.png")),
}
@commands.command() @commands.command()
async def overlay(self, ctx, style: str = None): async def overlay(self, ctx, overlay_choice: str = None, overlay_style: str = "default"):
start_time = datetime.datetime.now() start_time = datetime.datetime.now()
style = style.lower().strip() if style else None
image_attachments = None
overlay_choice = overlay_choice.lower().strip() if overlay_choice else None
overlay_style = overlay_style.lower().strip() if overlay_style else "default"
image_attachments = None
if ctx.message.attachments: if ctx.message.attachments:
image_attachments = ctx.message.attachments[0] image_attachments = ctx.message.attachments[0]
elif ctx.message.reference and ctx.message.reference.resolved.attachments: elif ctx.message.reference and ctx.message.reference.resolved.attachments:
@ -33,19 +30,22 @@ class Img(commands.Cog):
elif ctx.message.embeds and ctx.message.embeds[0].image: elif ctx.message.embeds and ctx.message.embeds[0].image:
image_attachments = ctx.message.embeds[0].image image_attachments = ctx.message.embeds[0].image
else: else:
async for message in ctx.guild.get_channel(ctx.channel.id).history( channel = ctx.guild.get_channel(ctx.channel.id)
limit=10 async for message in channel.history(limit=10):
):
if message.attachments: if message.attachments:
image_attachments = message.attachments[0] image_attachments = message.attachments[0]
break break
elif message.embeds and message.embeds[0].image: if message.embeds and message.embeds[0].image:
image_attachments = message.embeds[0].image image_attachments = message.embeds[0].image
break break
# Check if image should be processed
async with ctx.typing(): async with ctx.typing():
if not style or style not in IMAGE_OVERLAYS: if not image_attachments:
error = "No image was found!"
await ctx.reply(embed=error_message(error))
return
if not overlay_choice or overlay_choice not in IMAGE_OVERLAYS:
error = ( error = (
"That is not a valid option! Valid options are:\n" "That is not a valid option! Valid options are:\n"
f"`{', '.join(IMAGE_OVERLAYS)}`" f"`{', '.join(IMAGE_OVERLAYS)}`"
@ -53,94 +53,105 @@ class Img(commands.Cog):
await ctx.reply(embed=error_message(error)) await ctx.reply(embed=error_message(error))
return return
if not image_attachments: if overlay_style not in IMAGE_OVERLAYS[overlay_choice]["options"]:
error = "You need to attach an image to use this command!"
await ctx.reply(embed=error_message(error))
return
# Extracts file extension from filename or url
if (
image_attachments.filename
and not image_attachments.filename.split(".")[-1].lower()
in IMAGE_EXTENSIONS
):
error = ( error = (
"Unsupported file type! Supported file types are:\n" "That is not a valid option! Valid options are:\n"
f"`{', '.join(IMAGE_EXTENSIONS)}`" f"`{', '.join(IMAGE_OVERLAYS[overlay_choice]['options'])}`"
) )
await ctx.reply(embed=error_message(error)) await ctx.reply(embed=error_message(error))
return return
elif (
image_attachments.url # Defaults to gwa as I cant be asked to make a better error handler
and not image_attachments.url.split(".")[-1].lower() in IMAGE_EXTENSIONS filename = image_attachments.filename or image_attachments.url or "image.gwa"
): if not filename.split(".")[-1].lower() in IMAGE_EXTENSIONS:
error = ( error = (
"Unsupported file type! Supported file types are:\n" "Unsupported file type! Supported file types are "
f"`{', '.join(IMAGE_EXTENSIONS)}`" ", ".join(IMAGE_EXTENSIONS)
) )
await ctx.reply(embed=error_message(error)) await ctx.reply(embed=error_message(error))
return return
if image_attachments.size and image_attachments.size > 8 * 1024 * 1024: if image_attachments.size and image_attachments.size > 8 * 1024 * 1024:
error = ( error = (
"That image is too big! Please use an image that is less than 8MB." "That image is too big! "
"Please use an image that is less than 8MB."
) )
await ctx.reply(embed=error_message(error)) await ctx.reply(embed=error_message(error))
return return
if ( if (
not 0 < image_attachments.width <= 3500 not 10 < image_attachments.width <= 3500
or not 0 < image_attachments.height <= 3500 or not 10 < image_attachments.height <= 3500
): ):
error = "Image must be at least 1x1 and under 3500x3500!" error = "Image must be at least 10x10 and under 3500x3500!"
await ctx.reply(embed=error_message(error)) await ctx.reply(embed=error_message(error))
return return
response = requests.get(image_attachments.url) request = requests.get(image_attachments.url)
message_attachment = Image.open(BytesIO(response.content)) attachment = Image.open(BytesIO(request.content))
width, height = attachment.width, attachment.height
if message_attachment.width < message_attachment.height: if width < height:
message_attachment.thumbnail((200, message_attachment.height)) attachment.thumbnail((200, height))
else: else:
message_attachment.thumbnail((message_attachment.width, 200)) attachment.thumbnail((width, 200))
if style == "bubble": width, height = attachment.width, attachment.height
# The bubble is resized twice as for some reason .copy() doesn't work
message_attachment.paste( if overlay_choice == "bubble":
self._overlays["bubble"].resize( overlay = Image.open(IMAGE_OVERLAYS[overlay_choice]["path"])
(message_attachment.width, self._overlays["bubble"].height) overlay = overlay.resize((width, overlay.height))
),
(0, 0), if overlay_style in ["default", "top"]:
self._overlays["bubble"].resize( attachment.paste(overlay, (0, 0), overlay)
(message_attachment.width, self._overlays["bubble"].height) elif overlay_style in ["bottom"]:
), overlay = overlay.rotate(180)
) attachment.paste(overlay, (0, height - overlay.height), overlay)
elif style == "gang": elif overlay_style in ["mask", "mask-bottom"]:
message_attachment.paste( # This is a lazy method of creating a mask
self._overlays["gang"], # 1. Reduce brightness of overlay to 0 (black)
( # 2. Create a white square the size of the image
( # 3. Paste the overlay onto the white square
(message_attachment.width - self._overlays["gang"].width)
// 2 overlay = ImageEnhance.Brightness(overlay).enhance(0)
),
(message_attachment.height - self._overlays["gang"].height), mask = Image.new("RGB", (width, height), (255, 255, 255))
), mask.paste(overlay, (0, 0), overlay)
self._overlays["gang"],
if overlay_style == "mask-bottom":
mask = mask.rotate(180)
mask = mask.convert("L")
attachment.putalpha(mask)
elif overlay_choice == "gang":
overlay = Image.open(IMAGE_OVERLAYS[overlay_choice]["path"])
position = ((width - overlay.width) // 2, (height - overlay.height))
attachment.paste(overlay, position, overlay)
elif overlay_choice == "bandicam":
overlay = Image.open(IMAGE_OVERLAYS[overlay_choice]["path"])
overlay.thumbnail((width, overlay.height))
attachment.paste(overlay, ((width-overlay.width)//2, 0), overlay)
elif overlay_choice == "jerma":
overlay = Image.open(IMAGE_OVERLAYS[overlay_choice]["path"])
overlay.thumbnail((width, overlay.height))
attachment.paste(overlay, (width-overlay.width, height-overlay.height), overlay)
elif overlay_choice == "jerm-a":
overlay = Image.open(IMAGE_OVERLAYS[overlay_choice]["path"])
overlay.thumbnail((width, overlay.height))
attachment.paste(overlay, ((width-overlay.width)//2, height-overlay.height), overlay)
with BytesIO() as response:
attachment.save(response, format="PNG")
response.seek(0)
response = discord.File(response, filename="image.png")
time_taken = (datetime.datetime.now() - start_time).microseconds / 1000
embed = (
discord.Embed(title=overlay_choice.capitalize(), colour=discord.Colour.orange())
.set_image(url="attachment://image.png")
.set_footer(text=f"{width}x{height}, {time_taken}ms")
) )
message_file = BytesIO() await ctx.reply(embed=embed, file=response, mention_author=False)
message_attachment.save(message_file, format="PNG")
message_file.seek(0)
message_file = discord.File(message_file, filename="image.png")
time_taken = datetime.datetime.now() - start_time
embed = (
discord.Embed(title=style.capitalize(), colour=discord.Colour.orange())
.set_image(url="attachment://image.png")
.set_footer(
text=f"{message_attachment.width}x{message_attachment.height}, "
f"{time_taken.microseconds / 1000}ms"
)
)
await ctx.reply(embed=embed, file=message_file, mention_author=False)

View file

@ -4,7 +4,7 @@ from lynxie.utils import get_env_or_error
DISCORD_TOKEN = get_env_or_error("DISCORD_TOKEN") DISCORD_TOKEN = get_env_or_error("DISCORD_TOKEN")
DISCORD_GUILD_ID = Object(id=1040757387033849976) DISCORD_GUILD_ID = Object(id=1040757387033849976)
LYNXIE_PREFIX = "~" LYNXIE_PREFIX = "?"
DATA_PATH = "data" DATA_PATH = "data"
ASSETS_PATH = "assets" ASSETS_PATH = "assets"
@ -49,7 +49,30 @@ TINYFOX_ANIMALS = [
IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "webp"] IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "webp"]
IMAGE_OVERLAYS = [ IMAGE_OVERLAYS = {
"bubble", "bubble": {
"gang", "path": os.path.join(ASSETS_PATH, "bubble.png"),
] "options": [
"default", # Positioned at top
"bottom", # Positioned at bottom
"mask", # Positioned at top, but transparent
"mask-bottom", # Positioned at bottom, but transparent
],
},
"gang": {
"path": os.path.join(ASSETS_PATH, "gang.png"),
"options": ["default"],
},
"bandicam": {
"path": os.path.join(ASSETS_PATH, "bandicam.png"),
"options": ["default"],
},
"jerma": {
"path": os.path.join(ASSETS_PATH, "jerma.png"),
"options": ["default"],
},
"jerm-a": {
"path": os.path.join(ASSETS_PATH, "jerm-a.png"),
"options": ["default"],
},
}