Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions tux/cogs/fun/imgeffect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import io

import discord
import httpx
from discord import app_commands
from discord.ext import commands
from loguru import logger
from PIL import Image, ImageEnhance, ImageOps

from tux.utils.embeds import EmbedCreator


class ImgEffect(commands.Cog):
def __init__(self, bot: commands.Bot) -> None:
self.bot = bot
self.allowed_mimetypes = [
"image/jpeg",
"image/png",
]

imgeffect = app_commands.Group(name="imgeffect", description="Image effects")

@imgeffect.command(
name="deepfry",
description="Deepfry an image",
)
async def deepfry(self, interaction: discord.Interaction, image: discord.Attachment) -> None:
"""
Deepfry an image.

Parameters
----------
interaction : discord.Interaction
The interaction object for the command.
image : discord.File
The image to deepfry.
"""

# check if the image is a image
logger.info(f"Content type: {image.content_type}, Filename: {image.filename}, URL: {image.url}")
if image.content_type not in self.allowed_mimetypes:
logger.error("The file is not a permitted image.")
embed = EmbedCreator.create_error_embed(
title="Invalid File",
description="The file must be an image. Allowed types are PNG, JPEG, and JPG.",
interaction=interaction,
)
await interaction.response.send_message(embed=embed, ephemeral=True)
return

# say that the image is being processed
logger.info("Processing image...")
await interaction.response.send_message("Processing image...")

# open url with PIL
logger.info("Opening image with PIL and HTTPX...")
async with httpx.AsyncClient() as client:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Consider using a single httpx.AsyncClient instance for the cog

Creating a new AsyncClient for each request may be inefficient. Consider creating a single client instance in the init method and reusing it across the cog.

def __init__(self, bot):
    self.bot = bot
    self.http_client = httpx.AsyncClient()

async def cog_unload(self):
    await self.http_client.aclose()

# In the method where the image is processed:
response = await self.http_client.get(image.url)

response = await client.get(image.url)
pil_image = Image.open(io.BytesIO(response.content))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Add error handling for image processing operations

Consider wrapping the image processing operations in a try-except block to catch and handle potential errors, such as corrupted images or unexpected formats.

Suggested change
pil_image = Image.open(io.BytesIO(response.content))
try:
pil_image = Image.open(io.BytesIO(response.content))
except (IOError, OSError) as e:
logger.error(f"Failed to open image: {e}")
raise ValueError("Unable to process the image. It may be corrupted or in an unsupported format.")

pil_image = pil_image.convert("RGB")
logger.info("Image opened with PIL.")

# resize image to 50% then back to original size
logger.info("Resizing image...")
pil_image = pil_image.resize((int(pil_image.width * 0.25), int(pil_image.height * 0.25)))
logger.info("Image resized.")

# increase sharpness
logger.info("Increasing sharpness...")
pil_image = ImageEnhance.Sharpness(pil_image).enhance(100.0)
logger.info("Sharpness increased.")

logger.info("Adjusting color...")
r = pil_image.split()[0]
r = ImageEnhance.Contrast(r).enhance(2.0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Parameterize enhancement factors for flexibility

Consider making the enhancement factors (like 2.0 for contrast, 1.5 for brightness) configurable parameters. This would allow for easy adjustment of the 'deepfry' effect without changing the code.

def adjust_color(image, contrast_factor=2.0, brightness_factor=1.5):
    logger.info("Adjusting color...")
    r = image.split()[0]
    r = ImageEnhance.Contrast(r).enhance(contrast_factor)
    r = ImageEnhance.Brightness(r).enhance(brightness_factor)
    return r

r = adjust_color(pil_image)

r = ImageEnhance.Brightness(r).enhance(1.5)

colours = ((254, 0, 2), (255, 255, 15))
r = ImageOps.colorize(r, colours[0], colours[1])

pil_image = Image.blend(pil_image, r, 0.75)

logger.info("Color adjustment complete.")

# send image
logger.info("Sending image...")
pil_image = pil_image.resize((int(pil_image.width * 2), int(pil_image.height * 2)))
arr = io.BytesIO()
pil_image.save(arr, format="JPEG", quality=1)
arr.seek(0)
file = discord.File(arr, filename="deepfried.jpg")
# edit message with image
await interaction.followup.send(content="Here is your deepfried image:", file=file)


async def setup(bot: commands.Bot) -> None:
await bot.add_cog(ImgEffect(bot))