Skip to content
Open
Changes from 2 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
85 changes: 54 additions & 31 deletions azuravian/TitleColorMatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

class TitleColorMatch(BaseCardType):
"""
This class describes a type of CardType created by azuravian, and is
This class describes a type of CardType created by azuravian, and is
a modification of Beedman's GradientLogoTitleCard class with a few
changes, specifically the ability to autoselect a font color that
matches the logo, as well as trimming the logo of any extra
Expand All @@ -38,6 +38,9 @@ class CardModel(BaseCardTypeCustomFontAllText):
TITLE_FONT = str((REF_DIRECTORY / 'Sequel-Neue.otf').resolve())
TITLE_COLOR = '#EBEBEB'

"""Threshold (%) undet which logos will have their colors inverted"""
TITLE_MIN_LUMINANCE = 10

"""Default characters to replace in the generic font"""
FONT_REPLACEMENTS = {
'[': '(', ']': ')', '(': '[', ')': ']', '―': '-', '…': '...'
Expand All @@ -64,7 +67,7 @@ class CardModel(BaseCardTypeCustomFontAllText):
'source_file', 'output_file', 'title_text', 'season_text',
'episode_text', 'hide_season_text', 'font_color', 'font_file',
'font_interline_spacing', 'font_kerning', 'font_size',
'font_stroke_width', 'font_vertical_shift', 'logo',
'font_stroke_width', 'font_vertical_shift', 'logo',
)

def __init__(self,
Expand All @@ -85,11 +88,12 @@ def __init__(self,
blur: bool = False,
grayscale: bool = False,
preferences: Optional['Preferences'] = None,
**unused) -> None:
**unused
) -> None:
"""
Construct a new instance of this Card.
"""

# Initialize the parent class - this sets up an ImageMagickInterface
super().__init__(blur, grayscale, preferences=preferences)

Expand All @@ -102,7 +106,7 @@ def __init__(self,
self.season_text = self.image_magick.escape_chars(season_text.upper())
self.episode_text = self.image_magick.escape_chars(episode_text.upper())
self.hide_season_text = hide_season_text

self.font_color = font_color
self.font_file = font_file
self.font_interline_spacing = font_interline_spacing
Expand All @@ -112,8 +116,7 @@ def __init__(self,
self.font_vertical_shift = font_vertical_shift


@property
def logo_command(self) -> ImageMagickCommands:
def logo_command(self, luminance: int) -> ImageMagickCommands:
"""
Get the ImageMagick commands to add the resized logo to the
source image.
Expand All @@ -122,36 +125,48 @@ def logo_command(self) -> ImageMagickCommands:
List of ImageMagick commands.
"""

negate_commands = []
if luminance < self.TITLE_MIN_LUMINANCE:
negate_commands = [
f'-channel RGB',
f'-negate',
]

return [
# Resize logo
f'\( "{self.logo.resolve()}"',
f'-trim',
f'+repage',
f'-resize x650',
f'-resize 1155x650\> \)',
f'-resize 1155x650\>',
# Recolor dark logos to be visible on the black gradient
*negate_commands,
# Overlay resized logo
f'-gravity northwest',
f'\) -gravity northwest',
f'-define colorspace:auto-grayscale=false',
f'-type TrueColorAlpha',
f'-type TrueColorAlpha',
f'-geometry "+50+50"',
f'-composite',
]


@property
def title_text_command(self) -> ImageMagickCommands:
def title_text_command(self,
title_color: str,
stroke_color: str,
) -> ImageMagickCommands:
"""
ImageMagick commands to implement the title text's global
effects. Specifically the the font, kerning, fontsize, and
center gravity.

Args:
title_color: Color to utilize for the title text.
stroke_color: Color to utilize for the stroke.

Returns:
List of ImageMagick commands.
"""

# Get the title color and stroke for this logo
title_color, stroke_color = self._get_logo_color()

font_size = 157.41 * self.font_size
interline_spacing = -22 + self.font_interline_spacing
kerning = -1.25 * self.font_kerning
Expand All @@ -174,18 +189,18 @@ def title_text_command(self) -> ImageMagickCommands:
]


def _get_logo_color(self) -> tuple[str, str]:
def _get_logo_color(self) -> tuple[str, str, int]:
"""
Get the logo color for this card's logo.

Returns:
Tuple whose values are the title color text and the stroke
width color.
Tuple whose values are the title color text, the stroke
width color and luminance (or 100 where not applicable).
"""

# If auto color wasn't indicated use indicated color and black stroke
if str(self.font_color) != 'auto':
return self.font_color, 'black'
return self.font_color, 'black', 100

# Command to get histogram of the colors in logo image
command = ' '.join([
Expand Down Expand Up @@ -225,19 +240,24 @@ def _get_logo_color(self) -> tuple[str, str]:
color_ = hexcolor.lstrip('#')
lv = len(color_)
r, g, b = (int(color_[i:i+lv//3], 16) for i in range(0, lv, lv//3))

# Skip values that are too dark/light
if min(r, g, b) > 240 or max(r, g, b) < 15:
continue

# First valid color, return color and stroke based on luminance
luminance = (r * 0.299) + (g * 0.587) + (b * 0.114)
return hexcolor, 'black' if luminance > 50 else 'white'

# Determine returns
if luminance >= self.TITLE_MIN_LUMINANCE:
title_color = hexcolor
stroke_color = 'black' if luminance > 50 else 'white'
else:
title_color = self.TITLE_COLOR
stroke_color = 'black'

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I thought about this earlier and now I'm even more certain that there should probably be a STROKE_COLOR var thats used here

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

What would the variable be for?


return title_color, stroke_color, luminance

# No valid colors identified, return defaults
return self.TITLE_COLOR, 'black'
return self.TITLE_COLOR, 'black', 100



@property
def index_text_command(self) -> ImageMagickCommands:
"""
Expand Down Expand Up @@ -302,7 +322,7 @@ def is_custom_font(font: 'Font') -> bool:
"""
Determines whether the given arguments represent a custom font
for this card.

Args:
font: The Font being evaluated.

Expand All @@ -322,7 +342,9 @@ def is_custom_font(font: 'Font') -> bool:

@staticmethod
def is_custom_season_titles(
custom_episode_map: bool, episode_text_format: str) -> bool:
custom_episode_map: bool,
episode_text_format: str,
) -> bool:
"""
Determines whether the given attributes constitute custom or
generic season titles.
Expand All @@ -347,6 +369,7 @@ def create(self) -> None:
object's defined title card.
"""

title_color, stroke_color, luminance = self._get_logo_color()
command = ' '.join([
f'convert',
# Resize source image
Expand All @@ -356,14 +379,14 @@ def create(self) -> None:
f'"{self.__GRADIENT_IMAGE}"',
f'-composite',
# Overlay resized logo
*self.logo_command,
*self.logo_command(luminance),
# Put title text
*self.title_text_command,
*self.title_text_command(title_color, stroke_color),
# Put season/episode text
*self.index_text_command,
# Create and resize output
*self.resize_output,
f'"{self.output_file.resolve()}"',
])

self.image_magick.run(command)
self.image_magick.run(command)