> 24) & 0x7F; // 0 = fully opaque, 127 = fully transparent
if ($alpha >= 125) {
continue;
}
$opaquePixels++;
$red = ($color >> 16) & 0xFF;
$green = ($color >> 8) & 0xFF;
$blue = $color & 0xFF;
$spread = max($red, $green, $blue) - min($red, $green, $blue);
if ($spread > LOGO_THEME_MAX_CHANNEL_SPREAD) {
continue; // saturated/colored pixel: part of an icon mark, leave alone
}
$lightness = ($red + $green + $blue) / 3;
if ($lightness <= LOGO_THEME_BLACK_LIGHTNESS_MAX) {
$blackPixels++;
} elseif ($lightness >= LOGO_THEME_WHITE_LIGHTNESS_MIN) {
$whitePixels++;
}
}
}
if ($opaquePixels === 0) {
return null;
}
$isDark = ($blackPixels / $opaquePixels) >= LOGO_THEME_SIGNIFICANT_RATIO;
$isLight = ($whitePixels / $opaquePixels) >= LOGO_THEME_SIGNIFICANT_RATIO;
if (!$isDark && !$isLight) {
return null; // colorful logo, no significant black/white ink at all
}
if ($isDark && $isLight) {
// Both present: background removal often leaves a thin, fully-opaque
// residue of dark (or light) edge pixels that don't get cleared
// because they fall just outside the fuzz tolerance -- that's noise,
// not a second ink color. Only treat this as a genuinely ambiguous
// two-tone logo (skip) when the smaller amount is comparable in size
// to the larger one; otherwise the dominant color wins.
$minCount = min($blackPixels, $whitePixels);
$maxCount = max($blackPixels, $whitePixels);
if ($minCount / $maxCount > LOGO_THEME_DOMINANCE_RATIO) {
return null;
}
}
return $blackPixels > $whitePixels ? 'dark' : 'light';
}
/**
* Generates a themed variant of $image by inverting every low-saturation
* (grayscale-ish) pixel's color -- not just pixels near the dominant
* black/white extreme. Anti-aliasing and background-removal residue can
* leave faint ink-colored specks anywhere across the lightness range (not
* only at the extremes); inverting the whole grayscale region flips those
* along too, so nothing is left stuck in the old theme's tone. Colored
* pixels (an icon mark) are identified the same way as in
* classifyLogoTextColor() and are always copied through untouched; alpha
* is preserved exactly.
*
* @return resource|GdImage a new true color image with alpha
*/
function generateThemedLogoVariant($image)
{
$width = imagesx($image);
$height = imagesy($image);
$variant = imagecreatetruecolor($width, $height);
imagealphablending($variant, false);
imagesavealpha($variant, true);
for ($y = 0; $y < $height; $y++) {
for ($x = 0; $x < $width; $x++) {
$color = imagecolorat($image, $x, $y);
$alpha = ($color >> 24) & 0x7F;
$red = ($color >> 16) & 0xFF;
$green = ($color >> 8) & 0xFF;
$blue = $color & 0xFF;
if ($alpha < 125) {
$spread = max($red, $green, $blue) - min($red, $green, $blue);
if ($spread <= LOGO_THEME_MAX_CHANNEL_SPREAD) {
$red = 255 - $red;
$green = 255 - $green;
$blue = 255 - $blue;
}
}
$newColor = imagecolorallocatealpha($variant, $red, $green, $blue, $alpha);
imagesetpixel($variant, $x, $y, $newColor);
}
}
return $variant;
}
/**
* Renders a subscription's logo
tag(s), given already-resolved image
* URLs. When a themed variant exists, renders both the original and the
* variant image, each tagged with the theme the *original* already reads
* well on (data-native-theme); a small CSS rule (see styles.css) shows only
* the one matching the current theme so the swap works instantly, including
* live theme toggles with no page reload -- see the body.dark/body.light
* classes set in scripts/theme.js and scripts/common.js.
*
* @param string $originalSrc Resolved URL of the original logo (caller's own path convention)
* @param string|null $variantSrc Resolved URL of the themed variant, or empty/null if none
* @param string|null $textColor 'dark'/'light'/null, as stored in logo_text_color
* @param string $class Classes to apply to the image(s) (e.g. sizing/positioning)
* @param string $extraAttrs Extra raw HTML attributes (e.g. alt="", title="")
* @return string HTML for the logo image(s), or '' if there's no original logo
*/
function renderThemedLogoImg($originalSrc, $variantSrc, $textColor, $class = '', $extraAttrs = '')
{
if (empty($originalSrc)) {
return '';
}
if (empty($textColor) || empty($variantSrc)) {
return '
';
}
// The theme the ORIGINAL logo file already reads well on.
$nativeTheme = $textColor === 'dark' ? 'light' : 'dark';
$originalClass = htmlspecialchars(trim($class . ' logo-theme-original'));
$variantClass = htmlspecialchars(trim($class . ' logo-theme-variant'));
return '
'
. '
';
}
?>