aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar bradtimmis <[email protected]>2021-10-25 19:30:34 -0400
committerGravatar bradtimmis <[email protected]>2021-10-25 19:30:34 -0400
commitd28b932f4177f5d3056991a3c1872988f69dc952 (patch)
tree035cbc14a137f21a914ac677b76cc7d724edace0
parenttemp: add restructured layout in comments (diff)
fix: finish restructure with all functionality
Added commands for `name` and `random`. Added the ability to look up the color name based on the hex value. Co-authored-by: Mohammad Rafivulla <[email protected]> Co-authored-by: Vivaan Verma <[email protected]>
-rw-r--r--bot/exts/utilities/color.py395
1 files changed, 66 insertions, 329 deletions
diff --git a/bot/exts/utilities/color.py b/bot/exts/utilities/color.py
index c1523281..dc63cf84 100644
--- a/bot/exts/utilities/color.py
+++ b/bot/exts/utilities/color.py
@@ -1,7 +1,7 @@
import colorsys
import json
import logging
-import re
+import random
from io import BytesIO
from discord import Embed, File
@@ -10,81 +10,97 @@ from PIL import Image, ImageColor
from rapidfuzz import process
from bot.bot import Bot
-from bot.constants import Colours
-
-# from bot.exts.core.extension import invoke_help_command
-
+from bot.exts.core.extensions import invoke_help_command
logger = logging.getLogger(__name__)
-ERROR_MSG = """The color code {user_color} is not a possible color combination.
-The range of possible values are:
-RGB & HSV: 0-255
-CMYK: 0-100%
-HSL: 0-360 degrees
-Hex: #000000-#FFFFFF
-"""
-
with open("bot/resources/utilities/ryanzec_colours.json") as f:
COLOR_MAPPING = json.load(f)
THUMBNAIL_SIZE = 80
-"""
+
class Colour(commands.Cog):
+ """Cog for the Colour command."""
def __init__(self, bot: Bot) -> None:
self.bot = bot
@commands.group(aliases=["color"])
async def colour(self, ctx: commands.Context) -> None:
+ """
+ User initiated command to create an embed that displays color information.
+
+ For the commands `hsl`, `hsv` and `rgb`: input is in the form `.color <mode> <int> <int> <int>`
+ For the command `cmyk`: input is in the form `.color cmyk <int> <int> <int> <int>`
+ For the command `hex`: input is in the form `.color hex #<hex code>`
+ For the command `name`: input is in the form `.color name <color name>`
+ For the command `random`: input is in the form `.color random`
+ """
if ctx.invoked_subcommand is None:
await invoke_help_command(ctx)
@colour.command()
async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None:
+ """Function to create an embed from an RGB input."""
rgb_tuple = ImageColor.getrgb(f"rgb({red}, {green}, {blue})")
await Colour.send_colour_response(ctx, list(rgb_tuple))
@colour.command()
async def hsv(self, ctx: commands.Context, hue: int, saturation: int, value: int) -> None:
+ """Function to create an embed from an HSV input."""
hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {saturation}%, {value}%)")
await Colour.send_colour_response(ctx, list(hsv_tuple))
@colour.command()
async def hsl(self, ctx: commands.Context, hue: int, saturation: int, lightness: int) -> None:
+ """Function to create an embed from an HSL input."""
hsl_tuple = ImageColor.getrgb(f"hsl({hue}, {saturation}%, {lightness}%)")
await Colour.send_colour_response(ctx, list(hsl_tuple))
@colour.command()
async def cmyk(self, ctx: commands.Context, cyan: int, yellow: int, magenta: int, key: int) -> None:
- ...
+ """Function to create an embed from a CMYK input."""
+ r = int(255 * (1.0 - cyan / float(100)) * (1.0 - key / float(100)))
+ g = int(255 * (1.0 - magenta / float(100)) * (1.0 - key / float(100)))
+ b = int(255 * (1.0 - yellow / float(100)) * (1.0 - key / float(100)))
+ await Colour.send_colour_response(ctx, list((r, g, b)))
@colour.command()
async def hex(self, ctx: commands.Context, hex_code: str) -> None:
+ """Function to create an embed from a HEX input. (Requires # as a prefix)."""
hex_tuple = ImageColor.getrgb(hex_code)
await Colour.send_colour_response(ctx, list(hex_tuple))
@colour.command()
- async def yiq(
- self,
- ctx: commands.Context,
- perceived_luminesence: int,
- in_phase: int,
- quadrature: int
- ) -> None:
- yiq_list = list(colorsys.yiq_to_rgb(perceived_luminesence, in_phase, quadrature))
- yiq_tuple = [int(val * 255.0) for val in yiq_list]
- await Colour.send_colour_response(ctx, list(yiq_tuple))
+ async def name(self, ctx: commands.Context, user_color: str) -> None:
+ """Function to create an embed from a name input."""
+ _, hex_color = self.match_color_name(user_color)
+ hex_tuple = ImageColor.getrgb(hex_color)
+ await Colour.send_colour_response(ctx, list(hex_tuple))
+
+ @colour.command()
+ async def random(self, ctx: commands.Context) -> None:
+ """Function to create an embed from a randomly chosen color from the ryanzec.json file."""
+ color_choices = list(COLOR_MAPPING.values())
+ hex_color = random.choice(color_choices)
+ hex_tuple = ImageColor.getrgb(f"#{hex_color}")
+ await Colour.send_colour_response(ctx, list(hex_tuple))
@staticmethod
- async def send_colour_response(ctx: commands.Context, rgb: list[int]) -> Message:
+ async def send_colour_response(ctx: commands.Context, rgb: list[int]) -> None:
+ """Function to create and send embed from color information."""
r, g, b = rgb[0], rgb[1], rgb[2]
+ name = Colour._rgb_to_name(rgb)
+ if name is None:
+ desc = "Color information for the input color."
+ else:
+ desc = f"Color information for {name}"
colour_embed = Embed(
title="Colour",
- description="Here lies thy colour",
+ description=desc,
colour=int(f"{r:02x}{g:02x}{b:02x}", 16)
)
colour_conversions = Colour.get_colour_conversions(rgb)
@@ -107,17 +123,19 @@ class Colour(commands.Cog):
@staticmethod
def get_colour_conversions(rgb: list[int]) -> dict[str, str]:
+ """Create a dictionary mapping of color types and their values."""
return {
"rgb": tuple(rgb),
"hsv": Colour._rgb_to_hsv(rgb),
"hsl": Colour._rgb_to_hsl(rgb),
"cmyk": Colour._rgb_to_cmyk(rgb),
"hex": Colour._rgb_to_hex(rgb),
- "yiq": Colour._rgb_to_yiq(rgb)
+ "name": Colour._rgb_to_name(rgb)
}
@staticmethod
def _rgb_to_hsv(rgb: list[int]) -> tuple[int, int, int]:
+ """Function to convert an RGB list to a HSV list."""
rgb = [val / 255.0 for val in rgb]
h, v, s = colorsys.rgb_to_hsv(*rgb)
hsv = (round(h * 360), round(s * 100), round(v * 100))
@@ -125,6 +143,7 @@ class Colour(commands.Cog):
@staticmethod
def _rgb_to_hsl(rgb: list[int]) -> tuple[int, int, int]:
+ """Function to convert an RGB list to a HSL list."""
rgb = [val / 255.0 for val in rgb]
h, l, s = colorsys.rgb_to_hls(*rgb)
hsl = (round(h * 360), round(s * 100), round(l * 100))
@@ -132,269 +151,39 @@ class Colour(commands.Cog):
@staticmethod
def _rgb_to_cmyk(rgb: list[int]) -> tuple[int, int, int, int]:
+ """Function to convert an RGB list to a CMYK list."""
rgb = [val / 255.0 for val in rgb]
-
if all(val == 0 for val in rgb):
return 0, 0, 0, 100
-
cmy = [1 - val / 255 for val in rgb]
min_cmy = min(cmy)
-
cmyk = [(val - min_cmy) / (1 - min_cmy) for val in cmy] + [min_cmy]
cmyk = [round(val * 100) for val in cmyk]
-
return tuple(cmyk)
@staticmethod
def _rgb_to_hex(rgb: list[int]) -> str:
+ """Function to convert an RGB list to a HEX string."""
hex_ = ''.join([hex(val)[2:].zfill(2) for val in rgb])
hex_code = f"#{hex_}".upper()
return hex_code
@staticmethod
- def _rgb_to_yiq(rgb: list[int]) -> tuple[int, int, int, int]:
- rgb = [val / 255.0 for val in rgb]
- y, i, q = colorsys.rgb_to_yiq(*rgb)
- yiq = (round(y), round(i), round(q))
- return yiq
-
-def setup(bot: commands.Bot) -> None:
- bot.add_cog(Colour(bot))
-"""
-
-
-class Color(commands.Cog):
- """User initiated commands to receive color information."""
-
- def __init__(self, bot: Bot):
- self.bot = bot
-
- @commands.command(aliases=["colour"])
- async def color(self, ctx: commands.Context, mode: str, *, user_color: str) -> None:
- """
- Send information on input color code or color name.
-
- Possible modes are: "hex", "rgb", "hsv", "hsl", "cmyk" or "name".
- """
- logger.debug(f"{mode = }")
- logger.debug(f"{user_color = }")
- if mode.lower() == "hex":
- await self.hex_to_rgb(ctx, user_color)
- elif mode.lower() == "rgb":
- rgb_color = self.tuple_create(user_color)
- await self.color_embed(ctx, rgb_color)
- elif mode.lower() == "hsv":
- await self.hsv_to_rgb(ctx, user_color)
- elif mode.lower() == "hsl":
- await self.hsl_to_rgb(ctx, user_color)
- elif mode.lower() == "cmyk":
- await self.cmyk_to_rgb(ctx, user_color)
- elif mode.lower() == "name":
- color_name, hex_color = self.match_color_name(user_color)
- if "#" in hex_color:
- rgb_color = ImageColor.getcolor(hex_color, "RGB")
- else:
- rgb_color = ImageColor.getcolor("#" + hex_color, "RGB")
- await self.color_embed(ctx, rgb_color, color_name)
- else:
- # mode is either None or an invalid code
- if mode is None:
- no_mode_embed = Embed(
- title="No mode was passed, please define a color code.",
- description="Possible modes are: Name, Hex, RGB, HSV, HSL and CMYK.",
- color=Colours.soft_red,
- )
- await ctx.send(embed=no_mode_embed)
- return
- wrong_mode_embed = Embed(
- title=f"The color code {mode} is not a valid option",
- description="Possible modes are: Name, Hex, RGB, HSV, HSL and CMYK.",
- color=Colours.soft_red,
- )
- await ctx.send(embed=wrong_mode_embed)
- return
-
- @staticmethod
- def tuple_create(input_color: str) -> tuple[int, int, int]:
- """
- Create a tuple of integers based on user's input.
-
- Can handle inputs of the types:
- (100, 100, 100)
- 100, 100, 100
- 100 100 100
- """
- if "(" in input_color:
- remove = "[() ]"
- color_tuple = re.sub(remove, "", input_color)
- color_tuple = tuple(map(int, color_tuple.split(",")))
- elif "," in input_color:
- color_tuple = tuple(map(int, input_color.split(",")))
- else:
- color_tuple = tuple(map(int, input_color.split(" ")))
- return color_tuple
-
- async def hex_to_rgb(self, ctx: commands.Context, hex_string: str) -> None:
- """Function to convert hex color to rgb color and send main embed."""
- hex_match = re.fullmatch(r"(#?[0x]?)((?:[0-9a-fA-F]{3}){1,2})", hex_string)
- if hex_match:
- if "#" in hex_string:
- rgb_color = ImageColor.getcolor(hex_string, "RGB")
- elif "0x" in hex_string:
- hex_ = hex_string.replace("0x", "#")
- rgb_color = ImageColor.getcolor(hex_, "RGB")
- else:
- hex_ = "#" + hex_string
- rgb_color = ImageColor.getcolor(hex_, "RGB")
- await self.color_embed(ctx, rgb_color)
- else:
- await ctx.send(
- embed=Embed(
- title="There was an issue converting the hex color code.",
- description=ERROR_MSG.format(user_color=hex_string),
- )
+ def _rgb_to_name(rgb: list[int]) -> str:
+ """Function to convert from an RGB list to a fuzzy matched color name."""
+ input_hex_color = Colour._rgb_to_hex(rgb)
+ try:
+ match, certainty, _ = process.extractOne(
+ query=input_hex_color,
+ choices=COLOR_MAPPING.values(),
+ score_cutoff=80
)
-
- async def hsv_to_rgb(self, ctx: commands.Context, input_color: tuple[int, int, int]) -> tuple[int, int, int]:
- """Function to convert hsv color to rgb color and send main embed."""
- input_color = self.tuple_create(input_color)
- (h, v, s) = input_color # the function hsv_to_rgb expects v and s to be swapped
- h = h / 360
- s = s / 100
- v = v / 100
- rgb_color = colorsys.hsv_to_rgb(h, s, v)
- (r, g, b) = rgb_color
- r = int(r * 255)
- g = int(g * 255)
- b = int(b * 255)
- await self.color_embed(ctx, (r, g, b))
-
- async def hsl_to_rgb(self, ctx: commands.Context, input_color: tuple[int, int, int]) -> tuple[int, int, int]:
- """Function to convert hsl color to rgb color and send main embed."""
- input_color = self.tuple_create(input_color)
- (h, s, l) = input_color
- h = h / 360
- s = s / 100
- l = l / 100 # noqa: E741 It's little `L`, Reason: To maintain consistency.
- rgb_color = colorsys.hls_to_rgb(h, l, s)
- (r, g, b) = rgb_color
- r = int(r * 255)
- g = int(g * 255)
- b = int(b * 255)
- await self.color_embed(ctx, (r, g, b))
-
- async def cmyk_to_rgb(
- self,
- ctx: commands.Context,
- input_color: tuple[int, int, int, int]
- ) -> tuple[int, int, int]:
- """Function to convert cmyk color to rgb color and send main embed."""
- input_color = self.tuple_create(input_color)
- c = input_color[0]
- m = input_color[1]
- y = input_color[2]
- k = input_color[3]
- r = int(255 * (1.0 - c / float(100)) * (1.0 - k / float(100)))
- g = int(255 * (1.0 - m / float(100)) * (1.0 - k / float(100)))
- b = int(255 * (1.0 - y / float(100)) * (1.0 - k / float(100)))
- await self.color_embed(ctx, (r, g, b))
-
- @staticmethod
- async def create_thumbnail_attachment(color: tuple[int, int, int]) -> File:
- """
- Generate a thumbnail from `color`.
-
- Assumes that color is an rgb tuple.
- """
- thumbnail = Image.new("RGB", (80, 80), color=color)
- bufferedio = BytesIO()
- thumbnail.save(bufferedio, format="PNG")
- bufferedio.seek(0)
-
- file = File(bufferedio, filename="color.png")
-
- return file
-
- @staticmethod
- def get_color_fields(rgb_color: tuple[int, int, int]) -> list[dict]:
- """Converts from `RGB` to `CMYK`, `HSV`, `HSL` and returns a list of fields."""
-
- def _rgb_to_hex(rgb_color: tuple[int, int, int]) -> str:
- """To convert from `RGB` to `Hex` notation."""
- return '#' + ''.join(hex(int(color))[2:].zfill(2) for color in rgb_color).upper()
-
- def _rgb_to_cmyk(rgb_color: tuple[int, int, int]) -> tuple[int, int, int, int]:
- """To convert from `RGB` to `CMYK` color space."""
- r, g, b = rgb_color
-
- # RGB_SCALE -> 255
- # CMYK_SCALE -> 100
-
- if (r == g == b == 0):
- return 0, 0, 0, 100 # Representing Black
-
- # rgb [0,RGB_SCALE] -> cmy [0,1]
- c = 1 - r / 255
- m = 1 - g / 255
- y = 1 - b / 255
-
- # extract out k [0, 1]
- min_cmy = min(c, m, y)
- c = (c - min_cmy) / (1 - min_cmy)
- m = (m - min_cmy) / (1 - min_cmy)
- y = (y - min_cmy) / (1 - min_cmy)
- k = min_cmy
-
- # rescale to the range [0,CMYK_SCALE] and round off
- c = round(c * 100)
- m = round(m * 100)
- y = round(y * 100)
- k = round(k * 100)
-
- return c, m, y, k
-
- def _rgb_to_hsv(rgb_color: tuple[int, int, int]) -> tuple[int, int, int]:
- """To convert from `RGB` to `HSV` color space."""
- r, g, b = rgb_color
- h, v, s = colorsys.rgb_to_hsv(r / float(255), g / float(255), b / float(255))
- h = round(h * 360)
- s = round(s * 100)
- v = round(v * 100)
- return h, s, v
-
- def _rgb_to_hsl(rgb_color: tuple[int, int, int]) -> tuple[int, int, int]:
- """To convert from `RGB` to `HSL` color space."""
- r, g, b = rgb_color
- h, l, s = colorsys.rgb_to_hls(r / float(255), g / float(255), b / float(255))
- h = round(h * 360)
- s = round(s * 100)
- l = round(l * 100) # noqa: E741 It's little `L`, Reason: To maintain consistency.
- return h, s, l
-
- all_fields = [
- {
- "name": "RGB",
- "value": f"» rgb {rgb_color}"
- },
- {
- "name": "HEX",
- "value": f"» hex {_rgb_to_hex(rgb_color)}"
- },
- {
- "name": "CMYK",
- "value": f"» cmyk {_rgb_to_cmyk(rgb_color)}"
- },
- {
- "name": "HSV",
- "value": f"» hsv {_rgb_to_hsv(rgb_color)}"
- },
- {
- "name": "HSL",
- "value": f"» hsl {_rgb_to_hsl(rgb_color)}"
- },
- ]
-
- return all_fields
+ logger.debug(f"{match = }, {certainty = }")
+ color_name = [name for name, _ in COLOR_MAPPING.items() if _ == match][0]
+ logger.debug(f"{color_name = }")
+ except TypeError:
+ color_name = None
+ return color_name
@staticmethod
def match_color_name(input_color_name: str) -> str:
@@ -406,66 +195,14 @@ class Color(commands.Cog):
score_cutoff=50
)
logger.debug(f"{match = }, {certainty = }")
- hex_match = COLOR_MAPPING[match]
+ hex_match = f"#{COLOR_MAPPING[match]}"
logger.debug(f"{hex_match = }")
except TypeError:
match = "No color name match found."
hex_match = input_color_name
-
return match, hex_match
- @staticmethod
- def match_color_hex(input_hex_color: str) -> str:
- """Use fuzzy matching to return a hex color code based on the user's input."""
- try:
- match, certainty, _ = process.extractOne(
- query=input_hex_color,
- choices=COLOR_MAPPING.values(),
- score_cutoff=80
- )
- logger.debug(f"{match = }, {certainty = }")
- color_name = [name for name, _ in COLOR_MAPPING.items() if _ == match][0]
- logger.debug(f"{color_name = }")
- except TypeError:
- color_name = "No color name match found."
-
- return color_name
-
- async def color_embed(
- self,
- ctx: commands.Context,
- rgb_color: tuple[int, int, int],
- color_name: str = None
- ) -> None:
- """Take a RGB color tuple, create embed, and send."""
- (r, g, b) = rgb_color
- discord_rgb_int = int(f"{r:02x}{g:02x}{b:02x}", 16)
- all_colors = self.get_color_fields(rgb_color)
- hex_color = all_colors[1]["value"].replace("» hex ", "")
- if color_name is None:
- logger.debug(f"Find color name from hex color: {hex_color}")
- color_name = self.match_color_hex(hex_color)
-
- async with ctx.typing():
- main_embed = Embed(
- title=color_name,
- description='(Approx..)',
- color=discord_rgb_int,
- )
-
- file = await self.create_thumbnail_attachment(rgb_color)
- main_embed.set_thumbnail(url="attachment://color.png")
-
- for field in all_colors:
- main_embed.add_field(
- name=field['name'],
- value=field['value'],
- inline=False,
- )
-
- await ctx.send(file=file, embed=main_embed)
-
def setup(bot: Bot) -> None:
- """Load the Color Cog."""
- bot.add_cog(Color(bot))
+ """Load the Colour cog."""
+ bot.add_cog(Colour(bot))