mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-22 16:56:35 +00:00
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:
parent
0c3c007faf
commit
3c17ee03b6
BIN
gallery/public/images/album_cover_2.jpg
Normal file
BIN
gallery/public/images/album_cover_2.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 130 KiB |
@ -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(
|
||||
|
@ -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 = [
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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"),
|
||||
|
Loading…
x
Reference in New Issue
Block a user