diff --git a/gallery/public/images/album_cover_2.jpg b/gallery/public/images/album_cover_2.jpg new file mode 100644 index 0000000000..4a2339f733 Binary files /dev/null and b/gallery/public/images/album_cover_2.jpg differ diff --git a/gallery/src/data/media_players.ts b/gallery/src/data/media_players.ts index 4ca2551783..f15e6949b6 100644 --- a/gallery/src/data/media_players.ts +++ b/gallery/src/data/media_players.ts @@ -7,7 +7,7 @@ export const createMediaPlayerEntities = () => [ media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)", media_artist: "Technohead", supported_features: 64063, - entity_picture: "/images/album_cover.jpg", + entity_picture: "/images/album_cover_2.jpg", media_duration: 300, media_position: 50, media_position_updated_at: new Date( diff --git a/gallery/src/demos/demo-hui-media-control-card.ts b/gallery/src/demos/demo-hui-media-control-card.ts index f79284a13f..aca2612dd5 100644 --- a/gallery/src/demos/demo-hui-media-control-card.ts +++ b/gallery/src/demos/demo-hui-media-control-card.ts @@ -4,6 +4,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element"; import { provideHass } from "../../../src/fake_data/provide_hass"; import "../components/demo-cards"; import { createMediaPlayerEntities } from "../data/media_players"; +import "../../../src/panels/lovelace/cards/hui-media-control-card"; const CONFIGS = [ { diff --git a/src/data/media-player.ts b/src/data/media-player.ts index 2527731956..1f716cbee1 100644 --- a/src/data/media-player.ts +++ b/src/data/media-player.ts @@ -14,7 +14,7 @@ export const SUPPORT_SELECT_SOURCE = 2048; export const SUPPORT_STOP = 4096; export const SUPPORTS_PLAY = 16384; export const SUPPORT_SELECT_SOUND_MODE = 65536; -export const CONTRAST_RATIO = 3.5; +export const CONTRAST_RATIO = 4.5; export interface MediaPlayerThumbnail { content_type: string; diff --git a/src/panels/lovelace/cards/hui-media-control-card.ts b/src/panels/lovelace/cards/hui-media-control-card.ts index e8ea6d4722..3d44e0895a 100644 --- a/src/panels/lovelace/cards/hui-media-control-card.ts +++ b/src/panels/lovelace/cards/hui-media-control-card.ts @@ -12,7 +12,7 @@ import { import { classMap } from "lit-html/directives/class-map"; import { styleMap } from "lit-html/directives/style-map"; import Vibrant from "node-vibrant"; -import { Palette } from "node-vibrant/lib/color"; +import { Swatch } from "node-vibrant/lib/color"; import "@polymer/paper-icon-button/paper-icon-button"; import "@polymer/paper-progress/paper-progress"; // tslint:disable-next-line: no-duplicate-imports @@ -59,6 +59,107 @@ function getContrastRatio( return Math.round((contrast(rgb1, rgb2) + Number.EPSILON) * 100) / 100; } +// How much the total diff between 2 RGB colors can be +// to be considered similar. +const COLOR_SIMILARITY_THRESHOLD = 150; + +// For debug purposes, is being tree shaken. +const DEBUG_COLOR = __DEV__ && false; + +const logColor = ( + color: Swatch, + label: string = `${color.getHex()} - ${color.getPopulation()}` +) => + // tslint:disable-next-line:no-console + console.log( + `%c${label}`, + `color: ${color.getBodyTextColor()}; background-color: ${color.getHex()}` + ); + +const customGenerator = (colors: Swatch[]) => { + colors.sort((colorA, colorB) => colorB.population - colorA.population); + + const backgroundColor = colors[0]; + let foregroundColor: string | undefined; + + const contrastRatios = new Map(); + const approvedContrastRatio = (color: Swatch) => { + if (!contrastRatios.has(color)) { + contrastRatios.set( + color, + getContrastRatio(backgroundColor.rgb, color.rgb) + ); + } + + return contrastRatios.get(color)! > CONTRAST_RATIO; + }; + + // We take each next color and find one that has better contrast. + for (let i = 1; i < colors.length && foregroundColor === undefined; i++) { + // If this color matches, score, take it. + if (approvedContrastRatio(colors[i])) { + if (DEBUG_COLOR) { + logColor(colors[i], "PICKED"); + } + foregroundColor = colors[i].hex; + break; + } + + // This color has the wrong contrast ratio, but it is the right color. + // Let's find similar colors that might have the right contrast ratio + + const currentColor = colors[i]; + if (DEBUG_COLOR) { + logColor(colors[i], "Finding similar color with better contrast"); + } + + for (let j = i + 1; j < colors.length; j++) { + const compareColor = colors[j]; + + // difference. 0 is same, 765 max difference + const diffScore = + Math.abs(currentColor.rgb[0] - compareColor.rgb[0]) + + Math.abs(currentColor.rgb[1] - compareColor.rgb[1]) + + Math.abs(currentColor.rgb[2] - compareColor.rgb[2]); + + if (DEBUG_COLOR) { + logColor(colors[j], `${colors[j].hex} - ${diffScore}`); + } + + if (diffScore > COLOR_SIMILARITY_THRESHOLD) { + continue; + } + + if (approvedContrastRatio(compareColor)) { + if (DEBUG_COLOR) { + logColor(compareColor, "PICKED"); + } + foregroundColor = compareColor.hex; + break; + } + } + } + + if (foregroundColor === undefined) { + foregroundColor = backgroundColor.bodyTextColor; + } + + if (DEBUG_COLOR) { + // tslint:disable-next-line:no-console + console.log(); + // tslint:disable-next-line:no-console + console.log( + "%cPicked colors", + `color: ${foregroundColor}; background-color: ${backgroundColor.hex}; font-weight: bold; padding: 16px;` + ); + colors.forEach((color) => logColor(color)); + // tslint:disable-next-line:no-console + console.log(); + } + + return [foregroundColor, backgroundColor.hex]; +}; + interface ControlButton { icon: string; action: string; @@ -594,50 +695,11 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { } Vibrant.from(this._image) - .quality(1) + .useGenerator(customGenerator) .getPalette() - .then((palette: Palette) => { - const paletteColors: any[] = []; - - Object.keys(palette).forEach((color) => { - paletteColors.push({ - hex: palette[color]!.getHex(), - rgb: palette[color]!.getRgb(), - textColor: palette[color]!.getBodyTextColor(), - population: palette[color]!.getPopulation(), - }); - }); - - if (!paletteColors.length) { - this._foregroundColor = undefined; - this._backgroundColor = undefined; - return; - } - - paletteColors.sort((colorA, colorB) => { - if (colorA.population > colorB.population) { - return -1; - } - if (colorA.population < colorB.population) { - return 1; - } - - return 0; - }); - - this._backgroundColor = paletteColors[0].hex; - - for (let i = 1; i < paletteColors.length; i++) { - if ( - getContrastRatio(paletteColors[0].rgb, paletteColors[i].rgb) >= - CONTRAST_RATIO - ) { - this._foregroundColor = paletteColors[i].hex; - return; - } - } - - this._foregroundColor = paletteColors[0].textColor; + .then(([foreground, background]: [string, string]) => { + this._backgroundColor = background; + this._foregroundColor = foreground; }) .catch((err: any) => { // tslint:disable-next-line:no-console diff --git a/src/panels/lovelace/create-element/create-card-element.ts b/src/panels/lovelace/create-element/create-card-element.ts index 3669bba1f4..2fab4496c0 100644 --- a/src/panels/lovelace/create-element/create-card-element.ts +++ b/src/panels/lovelace/create-element/create-card-element.ts @@ -5,7 +5,6 @@ import "../cards/hui-glance-card"; import "../cards/hui-history-graph-card"; import "../cards/hui-horizontal-stack-card"; import "../cards/hui-light-card"; -import "../cards/hui-media-control-card"; import "../cards/hui-sensor-card"; import "../cards/hui-thermostat-card"; import "../cards/hui-vertical-stack-card"; @@ -25,7 +24,6 @@ const ALWAYS_LOADED_TYPES = new Set([ "history-graph", "horizontal-stack", "light", - "media-control", "sensor", "thermostat", "vertical-stack", @@ -36,6 +34,7 @@ const LAZY_LOAD_TYPES = { "alarm-panel": () => import("../cards/hui-alarm-panel-card"), "empty-state": () => import("../cards/hui-empty-state-card"), "entity-filter": () => import("../cards/hui-entity-filter-card"), + "media-control": () => import("../cards/hui-media-control-card"), "picture-elements": () => import("../cards/hui-picture-elements-card"), "picture-entity": () => import("../cards/hui-picture-entity-card"), "picture-glance": () => import("../cards/hui-picture-glance-card"),