toot medias: extract color from blurhash

This commit is contained in:
thislight 2024-11-11 16:53:34 +08:00
parent c372ea4a92
commit 2aa2cf21da
No known key found for this signature in database
GPG key ID: FCFE5192241CCD4E
5 changed files with 226 additions and 40 deletions

View file

@ -20,6 +20,7 @@ import {
} from "@solid-primitives/resize-observer";
import { useStore } from "@nanostores/solid";
import { $settings } from "../settings/stores";
import { averageColorHex } from "../platform/blurhash";
type ElementSize = { width: number; height: number };
@ -120,7 +121,9 @@ const MediaAttachmentGrid: Component<{
// we may need better tool to manage the performance impact
// before using this. See #37.
// TODO: use fast average color to extract accent color
const accentColor = item.meta?.colors?.accent;
const accentColor =
item.meta?.colors?.accent ??
(item.blurhash ? averageColorHex(item.blurhash) : undefined);
return Object.assign(
{

View file

@ -6,6 +6,7 @@ import {
Show,
createRenderEffect,
createEffect,
createMemo,
} from "solid-js";
import tootStyle from "./toot.module.css";
import { formatRelative } from "date-fns";
@ -29,13 +30,13 @@ import { Divider } from "@suid/material";
import cardStyle from "../material/cards.module.css";
import Button from "../material/Button.js";
import MediaAttachmentGrid from "./MediaAttachmentGrid.js";
import { FastAverageColor } from "fast-average-color";
import Color from "colorjs.io";
import { useDateFnLocale } from "../platform/i18n";
import { canShare, share } from "../platform/share";
import { makeAcctText, useDefaultSession } from "../masto/clients";
import TootContent from "./toot-components/TootContent";
import BoostIcon from "./toot-components/BoostIcon";
import { averageColorHex } from "../platform/blurhash";
type TootActionGroupProps<T extends mastodon.v1.Status> = {
onRetoot?: (value: T) => void;
@ -205,31 +206,44 @@ export function TootPreviewCard(props: {
}
});
const onImgLoad = (event: Event & { currentTarget: HTMLImageElement }) => {
// TODO: better extraction algorithm
// I'd like to use a pattern panel and match one in the panel from the extracted color
const fac = new FastAverageColor();
const result = fac.getColor(event.currentTarget);
if (result.error) {
console.error(result.error);
fac.destroy();
const imgAverageColor = createMemo(() => {
if (!props.src.image) return;
return new Color(averageColorHex(props.src.blurhash));
});
const prefersWhiteText = createMemo(() => {
const oc = imgAverageColor();
if (!oc) return;
const colorWhite = new Color("white");
return colorWhite.luminance / oc.luminance > 3.5;
});
const focusSurfaceColor = createMemo(() => {
const oc = imgAverageColor();
if (!oc) return;
if (prefersWhiteText()) {
return new Color(oc).darken(0.2);
} else {
return new Color(oc).lighten(0.2);
}
});
const textColorName = createMemo(() => {
const useWhiteText = prefersWhiteText();
if (typeof useWhiteText === "undefined") {
return;
}
root.style.setProperty("--tutu-color-surface", result.hex);
const focusSurface = result.isDark
? new Color(result.hex).darken(0.2)
: new Color(result.hex).lighten(0.2);
root.style.setProperty("--tutu-color-surface-d", focusSurface.toString());
const textColor = result.isDark ? "white" : "black";
const secondaryTextColor = new Color(textColor);
secondaryTextColor.alpha = 0.75;
root.style.setProperty("--tutu-color-on-surface", textColor);
root.style.setProperty(
"--tutu-color-secondary-text-on-surface",
secondaryTextColor.toString(),
);
fac.destroy();
};
return useWhiteText ? "white" : "black";
});
const secondaryTextColor = createMemo(() => {
const tcn = textColorName();
if (!tcn) return;
const tc = new Color(tcn);
tc.alpha = 0.75;
return tc;
});
return (
<a
@ -238,12 +252,18 @@ export function TootPreviewCard(props: {
href={props.src.url}
target="_blank"
referrerPolicy="unsafe-url"
style={{
"--tutu-color-surface": imgAverageColor()?.toString(),
"--tutu-color-surface-d": focusSurfaceColor()?.toString(),
"--tutu-color-on-surface": textColorName(),
"--tutu-color-secondary-text-on-surface":
secondaryTextColor()?.toString(),
}}
>
<Show when={props.src.image}>
<img
crossOrigin="anonymous"
src={props.src.image!}
onLoad={onImgLoad}
width={props.src.width || undefined}
height={props.src.height || undefined}
loading="lazy"
@ -373,16 +393,16 @@ const RegularToot: Component<TootCardProps> = (props) => {
<Show when={!!status().reblog}>
<div class={tootStyle.tootRetootGrp}>
<BoostIcon />
<Body2
ref={(e: { innerHTML: string }) => {
createRenderEffect(() => {
e.innerHTML = resolveCustomEmoji(
status().account.displayName,
toot().emojis,
);
});
}}
></Body2>
<Body2
ref={(e: { innerHTML: string }) => {
createRenderEffect(() => {
e.innerHTML = resolveCustomEmoji(
status().account.displayName,
toot().emojis,
);
});
}}
></Body2>
<span>boosts</span>
</div>
</Show>

View file

@ -183,6 +183,7 @@ const TootList: Component<{
return (
<ErrorBoundary
fallback={(err, reset) => {
console.error(err);
return <p>Oops: {String(err)}</p>;
}}
>

View file

@ -135,6 +135,7 @@
position: relative;
>img {
background-color: #eeeeee;
max-width: 100%;
height: auto;
}
@ -260,11 +261,8 @@
&:hover,
&:focus-visible {
/* TODO: the goal is to use the media's accent color as the outline */
/* but our infra is not prepared for this. The average color thing is slow */
/* and we need further managing to control its performance impact. */
outline: 8px solid var(--media-color-accent, var(--tutu-color-surface-d));
border-color: transparent;
border: none;
}
}
}