From c2f382aba86aaebb9806ff1b43c1af69992e9a10 Mon Sep 17 00:00:00 2001 From: Evgeny Zinoviev Date: Mon, 11 Jul 2022 15:01:02 +0300 Subject: support dark mode for images with alpha channel --- cli_util.php | 8 +++++ engine/Skin.php | 4 +-- engine/themes.php | 38 ++++++++++++++++++++++ handler/Auto.php | 4 +-- htdocs/js/common/35-theme-switcher.js | 21 ++++++++++++- lib/MyParsedown.php | 1 + lib/markup.php | 11 ++++--- lib/posts.php | 3 ++ model/Page.php | 5 ++- model/Post.php | 18 +++++------ model/Upload.php | 59 +++++++++++++++++++++-------------- skin/admin.skin.php | 8 ++--- skin/main.skin.php | 33 ++++++++++++++++++-- skin/markdown.skin.php | 6 ++-- 14 files changed, 164 insertions(+), 55 deletions(-) create mode 100644 engine/themes.php diff --git a/cli_util.php b/cli_util.php index 97885ff..0c42bb5 100755 --- a/cli_util.php +++ b/cli_util.php @@ -57,6 +57,14 @@ function posts_html(): void { } } +function posts_images(): void { + $kw = ['include_hidden' => true]; + $posts = posts::getPosts(0, posts::getPostsCount(...$kw), ...$kw); + foreach ($posts as $p) { + $p->updateImagePreviews(true); + } +} + function pages_html(): void { $pages = pages::getAll(); foreach ($pages as $p) { diff --git a/engine/Skin.php b/engine/Skin.php index 917eef7..a8924d4 100644 --- a/engine/Skin.php +++ b/engine/Skin.php @@ -23,8 +23,8 @@ class Skin { else $js = null; - $theme = ($_COOKIE['theme'] ?? 'auto'); - if (!in_array($theme, ['auto', 'dark', 'light'])) + $theme = themes::getUserTheme(); + if ($theme != 'auto' && !themes::themeExists($theme)) $theme = 'auto'; $layout_ctx = new SkinContext('\\skin\\base'); diff --git a/engine/themes.php b/engine/themes.php new file mode 100644 index 0000000..839377f --- /dev/null +++ b/engine/themes.php @@ -0,0 +1,38 @@ + [ + 'bg' => 0x222222, + // 'alpha' => 0x303132, + 'alpha' => 0x222222, + ], + 'light' => [ + 'bg' => 0xffffff, + // 'alpha' => 0xf2f2f2, + 'alpha' => 0xffffff, + ] + ]; + + public static function getThemes(): array { + return array_keys(self::$Themes); + } + + public static function themeExists(string $name): bool { + return array_key_exists($name, self::$Themes); + } + + public static function getThemeAlphaColorAsRGB(string $name): array { + $color = self::$Themes[$name]['alpha']; + $r = ($color >> 16) & 0xff; + $g = ($color >> 8) & 0xff; + $b = $color & 0xff; + return [$r, $g, $b]; + } + + public static function getUserTheme(): string { + return ($_COOKIE['theme'] ?? 'auto'); + } + +} \ No newline at end of file diff --git a/handler/Auto.php b/handler/Auto.php index 9903993..361cc17 100644 --- a/handler/Auto.php +++ b/handler/Auto.php @@ -68,7 +68,7 @@ class Auto extends RequestHandler { return $s->renderPage('main/post', title: $post->title, id: $post->id, - unsafe_html: $post->getHtml($this->isRetina()), + unsafe_html: $post->getHtml($this->isRetina(), \themes::getUserTheme()), date: $post->getFullDate(), tags: $tags, visible: $post->visible, @@ -98,7 +98,7 @@ class Auto extends RequestHandler { $this->skin->title = $page ? $page->title : '???'; return $this->skin->renderPage('main/page', - unsafe_html: $page->getHtml($this->isRetina()), + unsafe_html: $page->getHtml($this->isRetina(), \themes::getUserTheme()), page_url: $page->getUrl(), short_name: $page->shortName); } diff --git a/htdocs/js/common/35-theme-switcher.js b/htdocs/js/common/35-theme-switcher.js index e716e4b..c612152 100644 --- a/htdocs/js/common/35-theme-switcher.js +++ b/htdocs/js/common/35-theme-switcher.js @@ -14,6 +14,11 @@ var ThemeSwitcher = (function() { */ var systemState = null; + /** + * @type {function[]} + */ + var changeListeners = []; + /** * @returns {boolean} */ @@ -71,6 +76,13 @@ var ThemeSwitcher = (function() { var onDone = function() { window.requestAnimationFrame(function() { removeClass(document.body, 'theme-changing'); + changeListeners.forEach(function(f) { + try { + f(dark) + } catch (e) { + console.error('[ThemeSwitcher->changeTheme->onDone] error while calling user callback:', e) + } + }) }) }; @@ -171,7 +183,7 @@ var ThemeSwitcher = (function() { currentModeIndex = (currentModeIndex + 1) % modes.length; switch (modes[currentModeIndex]) { case 'auto': - if (systemState !== null) + if (systemState !== null && systemState !== isDarkModeApplied()) changeTheme(systemState); break; @@ -190,6 +202,13 @@ var ThemeSwitcher = (function() { setCookie('theme', modes[currentModeIndex]); return cancelEvent(e); + }, + + /** + * @param {function} f + */ + addOnChangeListener: function(f) { + changeListeners.push(f); } }; })(); \ No newline at end of file diff --git a/lib/MyParsedown.php b/lib/MyParsedown.php index c2c0112..11e86d6 100644 --- a/lib/MyParsedown.php +++ b/lib/MyParsedown.php @@ -99,6 +99,7 @@ class MyParsedown extends ParsedownHighlight { nolabel: $opts['nolabel'], align: $opts['align'], padding_top: round($h / $w * 100, 4), + may_have_alpha: $image->imageMayHaveAlphaChannel(), url: $image_url, direct_url: $image->getDirectUrl(), diff --git a/lib/markup.php b/lib/markup.php index 52ccf24..2f25c6c 100644 --- a/lib/markup.php +++ b/lib/markup.php @@ -16,12 +16,15 @@ class markup { return $text; } - public static function htmlRetinaFix(string $html): string { + public static function htmlImagesFix(string $html, bool $is_retina, string $user_theme): string { global $config; + $is_dark_theme = $user_theme === 'dark'; return preg_replace_callback( - '/('.preg_quote($config['uploads_host'], '/').'\/\w{8}\/p)(\d+)x(\d+)(\.jpg)/', - function($match) { - return $match[1].(intval($match[2])*2).'x'.(intval($match[3])*2).$match[4]; + '/('.preg_quote($config['uploads_host'], '/').'\/\w{8}\/)([ap])(\d+)x(\d+)(\.jpg)/', + function($match) use ($is_retina, $is_dark_theme) { + $mult = $is_retina ? 2 : 1; + $is_alpha = $match[2] == 'a'; + return $match[1].$match[2].(intval($match[3])*$mult).'x'.(intval($match[4])*$mult).($is_alpha && $is_dark_theme ? '_dark' : '').$match[5]; }, $html ); diff --git a/lib/posts.php b/lib/posts.php index bf8d149..1537749 100644 --- a/lib/posts.php +++ b/lib/posts.php @@ -23,6 +23,9 @@ class posts { return (int)$db->result($db->query($sql, $tag_id)); } + /** + * @return Post[] + */ public static function getPosts(int $offset = 0, int $count = -1, bool $include_hidden = false): array { $db = getDb(); $sql = "SELECT * FROM posts"; diff --git a/model/Page.php b/model/Page.php index 6711a2c..f516836 100644 --- a/model/Page.php +++ b/model/Page.php @@ -24,10 +24,9 @@ class Page extends Model { return $this->updateTs && $this->updateTs != $this->ts; } - public function getHtml(bool $retina): string { + public function getHtml(bool $is_retina, string $user_theme): string { $html = $this->html; - if ($retina) - $html = markup::htmlRetinaFix($html); + $html = markup::htmlImagesFix($html, $is_retina, $user_theme); return $html; } diff --git a/model/Post.php b/model/Post.php index 10a396b..b0360ac 100644 --- a/model/Post.php +++ b/model/Post.php @@ -22,8 +22,8 @@ class Post extends Model { $data['update_ts'] = $cur_ts; if ($data['md'] != $this->md) { - $data['html'] = \markup::markdownToHtml($data['md']); - $data['text'] = \markup::htmlToText($data['html']); + $data['html'] = markup::markdownToHtml($data['md']); + $data['text'] = markup::htmlToText($data['html']); } parent::edit($data); @@ -31,15 +31,15 @@ class Post extends Model { } public function updateHtml() { - $html = \markup::markdownToHtml($this->md); + $html = markup::markdownToHtml($this->md); $this->html = $html; getDb()->query("UPDATE posts SET html=? WHERE id=?", $html, $this->id); } public function updateText() { - $html = \markup::markdownToHtml($this->md); - $text = \markup::htmlToText($html); + $html = markup::markdownToHtml($this->md); + $text = markup::htmlToText($html); $this->text = $text; getDb()->query("UPDATE posts SET text=? WHERE id=?", $text, $this->id); @@ -81,10 +81,9 @@ class Post extends Model { return date('j F Y', $this->updateTs); } - public function getHtml(bool $retina): string { + public function getHtml(bool $is_retina, string $theme): string { $html = $this->html; - if ($retina) - $html = markup::htmlRetinaFix($html); + $html = markup::htmlImagesFix($html, $is_retina, $theme); return $html; } @@ -173,9 +172,8 @@ class Post extends Model { foreach ($images[$u->randomId] as $s) { list($w, $h) = $s; list($w, $h) = $u->getImagePreviewSize($w, $h); - if ($u->createImagePreview($w, $h, $update)) { + if ($u->createImagePreview($w, $h, $update, $u->imageMayHaveAlphaChannel())) $images_affected++; - } } } diff --git a/model/Upload.php b/model/Upload.php index 441a14d..06b348b 100644 --- a/model/Upload.php +++ b/model/Upload.php @@ -38,7 +38,8 @@ class Upload extends Model { $h *= 2; } - return 'https://'.$config['uploads_host'].'/'.$this->randomId.'/p'.$w.'x'.$h.'.jpg'; + $prefix = $this->imageMayHaveAlphaChannel() ? 'a' : 'p'; + return 'https://'.$config['uploads_host'].'/'.$this->randomId.'/'.$prefix.$w.'x'.$h.'.jpg'; } // TODO remove? @@ -73,6 +74,12 @@ class Upload extends Model { return in_array(extension($this->name), self::$ImageExtensions); } + // assume all png images have alpha channel + // i know this is wrong, but anyway + public function imageMayHaveAlphaChannel(): bool { + return strtolower(extension($this->name)) == 'png'; + } + public function isVideo(): bool { return in_array(extension($this->name), self::$VideoExtensions); } @@ -94,36 +101,40 @@ class Upload extends Model { return [$w, $h]; } - /** - * @param ?int $w - * @param ?int $h - * @param bool $update Whether to proceed if preview already exists - * @return bool - */ - public function createImagePreview(?int $w = null, ?int $h = null, bool $update = false): bool { + public function createImagePreview(?int $w = null, + ?int $h = null, + bool $force_update = false, + bool $may_have_alpha = false): bool { global $config; $orig = $config['uploads_dir'].'/'.$this->randomId.'/'.$this->name; $updated = false; - for ($mult = 1; $mult <= 2; $mult++) { - $dw = $w * $mult; - $dh = $h * $mult; - $dst = $config['uploads_dir'].'/'.$this->randomId.'/p'.$dw.'x'.$dh.'.jpg'; + foreach (themes::getThemes() as $theme) { + if (!$may_have_alpha && $theme == 'dark') + continue; - if (file_exists($dst)) { - if (!$update) - continue; - unlink($dst); - } + for ($mult = 1; $mult <= 2; $mult++) { + $dw = $w * $mult; + $dh = $h * $mult; + + $prefix = $may_have_alpha ? 'a' : 'p'; + $dst = $config['uploads_dir'].'/'.$this->randomId.'/'.$prefix.$dw.'x'.$dh.($theme == 'dark' ? '_dark' : '').'.jpg'; - $img = imageopen($orig); - imageresize($img, $dw, $dh, [255, 255, 255]); - imagejpeg($img, $dst, $mult == 1 ? 93 : 67); - imagedestroy($img); + if (file_exists($dst)) { + if (!$force_update) + continue; + unlink($dst); + } - setperm($dst); - $updated = true; + $img = imageopen($orig); + imageresize($img, $dw, $dh, themes::getThemeAlphaColorAsRGB($theme)); + imagejpeg($img, $dst, $mult == 1 ? 93 : 67); + imagedestroy($img); + + setperm($dst); + $updated = true; + } } return $updated; @@ -138,7 +149,7 @@ class Upload extends Model { $files = scandir($dir); $deleted = 0; foreach ($files as $f) { - if (preg_match('/^p(\d+)x(\d+)\.jpg$/', $f)) { + if (preg_match('/^[ap](\d+)x(\d+)(?:_dark)?\.jpg$/', $f)) { if (is_file($dir.'/'.$f)) unlink($dir.'/'.$f); else diff --git a/skin/admin.skin.php b/skin/admin.skin.php index f03d7ce..5619dd0 100644 --- a/skin/admin.skin.php +++ b/skin/admin.skin.php @@ -26,9 +26,9 @@ $html = << HTML; -$js = << true, 'edit' => $is_edit]); -$js = << {$ctx->if_admin($ctx->pageAdminLinks, $page_url, $short_name)}
{$unsafe_html}
HTML; + +return [$html, markdownThemeChangeListener()]; } function pageAdminLinks($ctx, $url, $short_name) { @@ -139,7 +141,7 @@ HTML; // --------- function post($ctx, $id, $title, $unsafe_html, $date, $visible, $url, $tags, $email, $urlencoded_reply_subject) { -return <<

{$title}

@@ -158,6 +160,8 @@ return <<langRaw('blog_comments_text', $email, $urlencoded_reply_subject)}
HTML; + +return [$html, markdownThemeChangeListener()]; } function postAdminLinks($ctx, $url, $id) { @@ -171,7 +175,32 @@ function postTag($ctx, $url, $name) { return <<#{$name} HTML; +} +function markdownThemeChangeListener() { +return << div'); + if (!div) { + console.warn('could not found a>div on this node:', node); + continue; + } + var style = div.getAttribute('style'); + if (isDark) { + style = style.replace(/(a[\d]+x[\d]+)\.jpg/, '$1_dark.jpg'); + } else { + style = style.replace(/(a[\d]+x[\d]+)_dark\.jpg/, '$1.jpg'); + } + div.setAttribute('style', style); + } +}); +JS; } diff --git a/skin/markdown.skin.php b/skin/markdown.skin.php index 02d3a0f..58801f5 100644 --- a/skin/markdown.skin.php +++ b/skin/markdown.skin.php @@ -14,14 +14,14 @@ HTML; function image($ctx, // options - $align, $nolabel, $w, $padding_top, + $align, $nolabel, $w, $padding_top, $may_have_alpha, // image data $direct_url, $url, $note) { return << -
+
-
+
{$ctx->if_true( $note != '' && !$nolabel, -- cgit v1.2.3