> 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 '' . ''; } ?>