diff --git a/src/renderer/src/components/Gradient.tsx b/src/renderer/src/components/Gradient.tsx index 11f90dd..8c96e2f 100644 --- a/src/renderer/src/components/Gradient.tsx +++ b/src/renderer/src/components/Gradient.tsx @@ -1,17 +1,12 @@ import "../assets/css/gradient.css"; import Impulse from "../lib/Impulse"; -import { Component, createEffect, createSignal, JSXElement, onCleanup, onMount } from "solid-js"; +import { Component, JSXElement, onCleanup, onMount } from "solid-js"; export type GradientColors = { top: string; bottom: string; }; -export const [gradientColors, setGradientColors] = createSignal({ - top: "dodgerblue", - bottom: "crimson", -}); - type GradientProps = { classTop?: string; classBottom?: string; @@ -52,13 +47,6 @@ const Gradient: Component = (props) => { window.removeEventListener("resize", calculateBackground); }); - createEffect(() => { - const colors = gradientColors(); - - document.documentElement.style.setProperty("--circle-0", colors.top); - document.documentElement.style.setProperty("--circle-1", colors.bottom); - }); - return (
diff --git a/src/renderer/src/components/song/SongImage.tsx b/src/renderer/src/components/song/SongImage.tsx index 4eb4bed..79e323d 100644 --- a/src/renderer/src/components/song/SongImage.tsx +++ b/src/renderer/src/components/song/SongImage.tsx @@ -11,6 +11,7 @@ type SongImageProps = { src: string | undefined | Accessor; group?: string; instantLoad?: boolean; + onImageLoaded?: (src: string) => void; } & JSX.IntrinsicElements["div"]; const SongImage: Component = (props) => { @@ -23,6 +24,7 @@ const SongImage: Component = (props) => { } setSrc(evt.detail); + props.onImageLoaded?.(evt.detail); delete image?.dataset.eventHandler; }; diff --git a/src/renderer/src/components/song/colorExtractor.tsx b/src/renderer/src/components/song/color-extractor.ts similarity index 69% rename from src/renderer/src/components/song/colorExtractor.tsx rename to src/renderer/src/components/song/color-extractor.ts index c08da2b..f363be8 100644 --- a/src/renderer/src/components/song/colorExtractor.tsx +++ b/src/renderer/src/components/song/color-extractor.ts @@ -1,4 +1,3 @@ -import { setGradientColors } from "../Gradient"; import { extractColors } from "extract-colors"; import { lighten, darken, getContrast, parseToHsl, hslToColorString } from "polished"; import { Accessor, createSignal } from "solid-js"; @@ -12,40 +11,57 @@ const AREA_WEIGHT = 0.3; type UseColorExtractorResult = { primaryColor: Accessor; secondaryColor: Accessor; + processImage(src: string): void; }; export function useColorExtractor() { const extractColorFromImage = (song: Song): UseColorExtractorResult => { - const songId = song.audio; + const [primaryColor, setPrimartColor] = createSignal(song.primaryColor); + const [secondaryColor, setSecondaryColor] = createSignal( + song.secondaryColor, + ); - // State - const [primaryColor, setPrimartColor] = createSignal(); - const [secondaryColor, setSecondaryColor] = createSignal(); + const processImage = async (src: string) => { + if (primaryColor() || secondaryColor()) { + return; + } - // Check if color already exists - if (song.primaryColor && song.secondaryColor) { - document.documentElement.style.setProperty( - "--extracted-color-rgb", - hexToRgb(song.primaryColor), - ); - setPrimartColor(song.primaryColor); - setSecondaryColor(song.secondaryColor); - return { primaryColor, secondaryColor }; - } + try { + const colors = await extractColorsFromImage(src); + console.log("colors", colors); + if (!colors.primaryColor || !colors.secondaryColor) { + return; + } - if (!song.bg) { - console.error("No background image found for color extraction."); - setPrimartColor("gray"); - return { primaryColor, secondaryColor }; - } + setPrimartColor(colors.primaryColor); + setSecondaryColor(colors.secondaryColor); - const img = new Image(); - img.crossOrigin = "Anonymous"; - img.src = `file://${song.bg}`; + await window.api.request( + "save::songColors", + colors.primaryColor, + colors.secondaryColor, + song.audio, + ); + } catch (err) { + console.error("Error extracting color:", err); + } + }; + return { primaryColor, secondaryColor, processImage }; + }; + + return { extractColorFromImage }; +} + +type ExtractColorsFromImageResult = { primaryColor: string; secondaryColor: string }; +function extractColorsFromImage(src: string): Promise { + const img = new Image(); + img.crossOrigin = "Anonymous"; + img.src = src; + + return new Promise((resolve, reject) => { img.onload = async () => { try { - console.log("calculating", songId); const colors = await extractColors(img.src); const validColors = colors.filter((color) => { @@ -61,7 +77,7 @@ export function useColorExtractor() { return scoreB - scoreA; }); - let songPrimaryColor = sortedColors[0]?.hex || "gray"; + let primaryColor = sortedColors[0]?.hex || "gray"; // Find the first valid color that meets contrast and vibrancy thresholds for (const color of sortedColors) { @@ -70,48 +86,29 @@ export function useColorExtractor() { contrast >= MIN_CONTRAST_RATIO && getVibrancy(parseToHsl(color.hex)) >= MIN_VIBRANCY_THRESHOLD ) { - songPrimaryColor = color.hex; + primaryColor = color.hex; break; } } - songPrimaryColor = alterUnappealingColor(songPrimaryColor); + primaryColor = alterUnappealingColor(primaryColor); // Improve contrast if necessary - if (getContrast(songPrimaryColor, "#FFFFFF") < MIN_CONTRAST_RATIO) { - songPrimaryColor = improveContrast(songPrimaryColor); + if (getContrast(primaryColor, "#FFFFFF") < MIN_CONTRAST_RATIO) { + primaryColor = improveContrast(primaryColor); } - const { lightness } = parseToHsl(songPrimaryColor); - const songSecondaryColor = - lightness < 0.2 ? lighten(0.1, songPrimaryColor) : darken(0.1, songPrimaryColor); + const { lightness } = parseToHsl(primaryColor); + const secondaryColor = + lightness < 0.2 ? lighten(0.1, primaryColor) : darken(0.1, primaryColor); - setPrimartColor(songPrimaryColor); - setSecondaryColor(songSecondaryColor); - - document.documentElement.style.setProperty( - "--extracted-color-rgb", - hexToRgb(songPrimaryColor), - ); - - // Set gradient colors if needed - setGradientColors({ - top: songPrimaryColor, - bottom: lighten(0.2, songPrimaryColor), - }); - - await window.api.request("save::songColors", songPrimaryColor, songSecondaryColor, songId); + document.documentElement.style.setProperty("--extracted-color-rgb", hexToRgb(primaryColor)); + resolve({ primaryColor, secondaryColor }); } catch (err) { - console.error("Error extracting color:", err); - setPrimartColor(undefined); - setSecondaryColor(undefined); + reject(err); } }; - - return { primaryColor, secondaryColor }; - }; - - return { extractColorFromImage }; + }); } // Helper function to improve contrast by darkening the color diff --git a/src/renderer/src/components/song/song-detail/SongDetail.tsx b/src/renderer/src/components/song/song-detail/SongDetail.tsx index 106c6ac..faeae23 100644 --- a/src/renderer/src/components/song/song-detail/SongDetail.tsx +++ b/src/renderer/src/components/song/song-detail/SongDetail.tsx @@ -1,6 +1,6 @@ import formatTime from "../../../lib/time-formatter"; import SongImage from "../SongImage"; -import { useColorExtractor } from "../colorExtractor"; +import { useColorExtractor } from "../color-extractor"; import SongControls from "./SongControls"; import Slider from "@renderer/components/slider/Slider"; import { @@ -24,6 +24,7 @@ const SongDetail: Component = () => {
diff --git a/src/renderer/src/components/song/song-item/SongItem.tsx b/src/renderer/src/components/song/song-item/SongItem.tsx index 3c5fb28..b0c6fcf 100644 --- a/src/renderer/src/components/song/song-item/SongItem.tsx +++ b/src/renderer/src/components/song/song-item/SongItem.tsx @@ -2,7 +2,7 @@ import { ResourceID, Song } from "../../../../../@types"; import draggable from "../../../lib/draggable/draggable"; import SongHint from "../SongHint"; import SongImage from "../SongImage"; -import { useColorExtractor } from "../colorExtractor"; +import { useColorExtractor } from "../color-extractor"; import { ignoreClickInContextMenu } from "../context-menu/SongContextMenu"; import { song as selectedSong } from "../song.utils"; import { transparentize } from "polished"; @@ -30,7 +30,7 @@ const SongItem: Component = ({ const [, setCoords] = createSignal<[number, number]>([0, 0], { equals: false }); const { extractColorFromImage } = useColorExtractor(); - const { primaryColor, secondaryColor } = extractColorFromImage(song); + const { primaryColor, secondaryColor, processImage } = extractColorFromImage(song); onMount(() => { if (!item) return; @@ -97,6 +97,7 @@ const SongItem: Component = ({ class={`absolute inset-0 z-[-1] h-full w-full rounded-md bg-cover bg-center bg-no-repeat`} src={song.bg} group={group} + onImageLoaded={processImage} />