Improve color extraction for media control card (#5189)

* Improve color extraction

* Lazy load media control

* Update src/panels/lovelace/cards/hui-media-control-card.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Extract constant

* Fix media control demo

* Remove quality(1)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Paulus Schoutsen 2020-03-13 10:01:36 -07:00 committed by GitHub
parent 0c3c007faf
commit 3c17ee03b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 110 additions and 48 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View File

@ -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(

View File

@ -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 = [
{

View File

@ -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;

View File

@ -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<Swatch, number>();
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

View File

@ -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"),