Clean up node-vibrant (#7777)

This commit is contained in:
Paulus Schoutsen 2020-11-23 12:47:47 +01:00 committed by GitHub
parent c0aa353f83
commit e2fed24995
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 256 additions and 169 deletions

View File

@ -83,6 +83,9 @@
"@types/sortablejs": "^1.10.6",
"@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7",
"@vibrant/color": "^3.2.1-alpha.1",
"@vibrant/core": "^3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
"@vue/web-component-wrapper": "^1.2.0",
"@webcomponents/webcomponentsjs": "^2.2.7",
"chart.js": "~2.8.0",
@ -109,7 +112,7 @@
"marked": "^1.1.1",
"mdn-polyfills": "^5.16.0",
"memoize-one": "^5.0.2",
"node-vibrant": "^3.1.6",
"node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "^0.3.1",
"punycode": "^2.1.1",
"qrcode": "^1.4.4",

View File

@ -22,3 +22,8 @@ export const rgbContrast = (
return (lum2 + 0.05) / (lum1 + 0.05);
};
export const getRGBContrastRatio = (
rgb1: [number, number, number],
rgb2: [number, number, number]
) => Math.round((rgbContrast(rgb1, rgb2) + Number.EPSILON) * 100) / 100;

View File

@ -0,0 +1,132 @@
import Vibrant from "node-vibrant/lib/browser";
import MMCQ from "@vibrant/quantizer-mmcq";
import { BasicPipeline } from "@vibrant/core/lib/pipeline";
import { Swatch, Vec3 } from "@vibrant/color";
import { getRGBContrastRatio } from "../color/rgb";
const CONTRAST_RATIO = 4.5;
// 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 = `${color.hex} - ${color.population}`
) =>
// eslint-disable-next-line no-console
console.log(
`%c${label}`,
`color: ${color.bodyTextColor}; background-color: ${color.hex}`
);
const customGenerator = (colors: Swatch[]) => {
colors.sort((colorA, colorB) => colorB.population - colorA.population);
const backgroundColor = colors[0];
let foregroundColor: Vec3 | undefined;
const contrastRatios = new Map<string, number>();
const approvedContrastRatio = (hex: string, rgb: Swatch["rgb"]) => {
if (!contrastRatios.has(hex)) {
contrastRatios.set(hex, getRGBContrastRatio(backgroundColor.rgb, rgb));
}
return contrastRatios.get(hex)! > 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].hex, colors[i].rgb)) {
if (DEBUG_COLOR) {
logColor(colors[i], "PICKED");
}
foregroundColor = colors[i].rgb;
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.hex, compareColor.rgb)) {
if (DEBUG_COLOR) {
logColor(compareColor, "PICKED");
}
foregroundColor = compareColor.rgb;
break;
}
}
}
if (foregroundColor === undefined) {
foregroundColor =
// @ts-expect-error
backgroundColor.getYiq() < 200 ? [255, 255, 255] : [0, 0, 0];
}
if (DEBUG_COLOR) {
// eslint-disable-next-line no-console
console.log();
// eslint-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));
// eslint-disable-next-line no-console
console.log();
}
return {
foreground: new Swatch(foregroundColor, 0),
background: backgroundColor,
};
};
Vibrant.use(
new BasicPipeline().filter
.register(
"default",
(r: number, g: number, b: number, a: number) =>
a >= 125 && !(r > 250 && g > 250 && b > 250)
)
.quantizer.register("mmcq", MMCQ)
// Our generator has different output
// @ts-expect-error
.generator.register("default", customGenerator)
);
export const extractColors = (url: string, downsampleColors = 16) =>
new Vibrant(url, {
colorCount: downsampleColors,
})
.getPalette()
.then(({ foreground, background }) => {
return { background: background!, foreground: foreground! };
});

View File

@ -36,7 +36,6 @@ export const SUPPORT_STOP = 4096;
export const SUPPORT_PLAY = 16384;
export const SUPPORT_SELECT_SOUND_MODE = 65536;
export const SUPPORT_BROWSE_MEDIA = 131072;
export const CONTRAST_RATIO = 4.5;
export type MediaPlayerBrowseAction = "pick" | "play";

View File

@ -16,14 +16,13 @@ import {
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { styleMap } from "lit-html/directives/style-map";
import Vibrant from "node-vibrant";
import { Swatch } from "node-vibrant/lib/color";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { stateIcon } from "../../../common/entity/state_icon";
import { supportsFeature } from "../../../common/entity/supports-feature";
import { debounce } from "../../../common/util/debounce";
import { extractColors } from "../../../common/image/extract_color";
import "../../../components/ha-card";
import "../../../components/ha-icon";
import "../../../components/ha-icon-button";
@ -33,7 +32,6 @@ import { UNAVAILABLE_STATES } from "../../../data/entity";
import {
computeMediaDescription,
computeMediaControls,
CONTRAST_RATIO,
getCurrentProgress,
MediaPickedEvent,
SUPPORT_BROWSE_MEDIA,
@ -41,7 +39,6 @@ import {
SUPPORT_TURN_ON,
} from "../../../data/media-player";
import type { HomeAssistant, MediaEntity } from "../../../types";
import { contrast } from "../common/color/contrast";
import { findEntities } from "../common/find-entites";
import { hasConfigOrEntityChanged } from "../common/has-changed";
import { installResizeObserver } from "../common/install-resize-observer";
@ -50,114 +47,6 @@ import { createEntityNotFoundWarning } from "../components/hui-warning";
import type { LovelaceCard, LovelaceCardEditor } from "../types";
import { MediaControlCardConfig } from "./types";
function getContrastRatio(
rgb1: [number, number, number],
rgb2: [number, number, number]
): number {
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 = `${color.getHex()} - ${color.getPopulation()}`
) =>
// eslint-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) {
// eslint-disable-next-line no-console
console.log();
// eslint-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));
// eslint-disable-next-line no-console
console.log();
}
return [foregroundColor, backgroundColor.hex];
};
@customElement("hui-media-control-card")
export class HuiMediaControlCard extends LitElement implements LovelaceCard {
public static async getConfigElement(): Promise<LovelaceCardEditor> {
@ -634,26 +523,21 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
});
}
private _setColors(): void {
private async _setColors(): Promise<void> {
if (!this._image) {
return;
}
new Vibrant(this._image, {
colorCount: 16,
generator: customGenerator,
})
.getPalette()
.then(([foreground, background]: [string, string]) => {
this._backgroundColor = background;
this._foregroundColor = foreground;
})
.catch((err: any) => {
// eslint-disable-next-line no-console
console.error("Error getting Image Colors", err);
this._foregroundColor = undefined;
this._backgroundColor = undefined;
});
try {
const { foreground, background } = await extractColors(this._image);
this._backgroundColor = background.hex;
this._foregroundColor = foreground.hex;
} catch (err) {
// eslint-disable-next-line no-console
console.error("Error getting Image Colors", err);
this._foregroundColor = undefined;
this._backgroundColor = undefined;
}
}
private _marqueeMouseOver(): void {

View File

@ -1,12 +0,0 @@
import { luminanace } from "./luminanace";
export const contrast = (
rgb1: [number, number, number],
rgb2: [number, number, number]
): number => {
const lum1 = luminanace(...rgb1);
const lum2 = luminanace(...rgb2);
const brightest = Math.max(lum1, lum2);
const darkest = Math.min(lum1, lum2);
return (brightest + 0.05) / (darkest + 0.05);
};

View File

@ -1,7 +0,0 @@
export const luminanace = (r: number, g: number, b: number): number => {
const a = [r, g, b].map((v) => {
v /= 255;
return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
});
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
};

123
yarn.lock
View File

@ -3299,11 +3299,6 @@
dependencies:
"@types/geojson" "*"
"@types/lodash@^4.14.53":
version "4.14.149"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440"
integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==
"@types/marked@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-1.1.0.tgz#53509b5f127e0c05c19176fcf1d743a41e00ff19"
@ -3329,10 +3324,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.12.1.tgz#d90123f6c61fdf2f7cddd286ddae891586dd3488"
integrity sha512-sKDlqv6COJrR7ar0+GqqhrXQDzQlMcqMnF2iEU6m9hLo8kxozoAGUazwPyELHlRVmjsbvlnGXjnzyptSXVmceA==
"@types/node@^10.11.7":
version "10.17.17"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.17.tgz#7a183163a9e6ff720d86502db23ba4aade5999b8"
integrity sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q==
"@types/node@^10.12.18":
version "10.17.46"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.46.tgz#1cd867ebfe9957ab45951f2f715f8de5f3dab7a3"
integrity sha512-Tice8a+sJtlP9C1EUo0DYyjq52T37b3LexVu3p871+kfIBIN+OQ7PKPei1oF3MgF39olEpUfxaLtD+QFc1k69Q==
"@types/resize-observer-browser@^0.1.3":
version "0.1.3"
@ -3611,6 +3606,94 @@
dependencies:
"@vaadin/vaadin-development-mode-detector" "^2.0.0"
"@vibrant/color@^3.2.1-alpha.1":
version "3.2.1-alpha.1"
resolved "https://registry.yarnpkg.com/@vibrant/color/-/color-3.2.1-alpha.1.tgz#1bcee4545d2276d36f9a1acb42ab3485a9b489ec"
integrity sha512-cvm+jAPwao2NerTr3d1JttYyLhp3eD/AQBeevxF7KT6HctToWZCwr2AeTr003/wKgbjzdOV1qySnbyOeu+R+Jw==
"@vibrant/core@^3.2.1-alpha.1":
version "3.2.1-alpha.1"
resolved "https://registry.yarnpkg.com/@vibrant/core/-/core-3.2.1-alpha.1.tgz#9adff0835b5c750be3386ec01669d2a8d6389fdb"
integrity sha512-X9Oa9WfPEQnZ6L+5dLRlh+IlsxJkYTw9b/g3stFKoNXbVRKCeXHmH48l7jIBBOg3VcXOGUdsYBqsTwPNkIveaA==
dependencies:
"@vibrant/color" "^3.2.1-alpha.1"
"@vibrant/generator" "^3.2.1-alpha.1"
"@vibrant/image" "^3.2.1-alpha.1"
"@vibrant/quantizer" "^3.2.1-alpha.1"
"@vibrant/types" "^3.2.1-alpha.1"
"@vibrant/worker" "^3.2.1-alpha.1"
"@vibrant/generator-default@^3.2.1-alpha.1":
version "3.2.1-alpha.1"
resolved "https://registry.yarnpkg.com/@vibrant/generator-default/-/generator-default-3.2.1-alpha.1.tgz#70ae71ea1f72d3e71aa6b244830d01ecae1d755a"
integrity sha512-BWnQhDaz92UhyHnpdAzKXHQecY+jvyMXtzjKYbveFxThm6+HVoLjwONlbck7oyOpFzV2OM7V11XuR85BxaHvjw==
dependencies:
"@vibrant/color" "^3.2.1-alpha.1"
"@vibrant/generator" "^3.2.1-alpha.1"
"@vibrant/generator@^3.2.1-alpha.1":
version "3.2.1-alpha.1"
resolved "https://registry.yarnpkg.com/@vibrant/generator/-/generator-3.2.1-alpha.1.tgz#887b36f7ed978ff94c93cc8a3ac742ce769b6112"
integrity sha512-luS5YvMhwMqG01YTj1dJ+cmkuIw1VCByOR6zIaCOwQqI/mcOs88JBWcA1r2TywJTOPlVpjfnDvAlyaKBKh4dMA==
dependencies:
"@vibrant/color" "^3.2.1-alpha.1"
"@vibrant/types" "^3.2.1-alpha.1"
"@vibrant/image-browser@^3.2.1-alpha.1":
version "3.2.1-alpha.1"
resolved "https://registry.yarnpkg.com/@vibrant/image-browser/-/image-browser-3.2.1-alpha.1.tgz#fe595bfe0c0ddc412300b5419e1e42d8b88d4380"
integrity sha512-6xWvQfB20sE6YtCWylgEAHuee3iD8h3aFIDbCS2yj7jIelKcYTrrp5jg2d2BhOOB6pC5JzF+QfpCrm0DmAIlgQ==
dependencies:
"@vibrant/image" "^3.2.1-alpha.1"
"@vibrant/image-node@^3.2.1-alpha.1":
version "3.2.1-alpha.1"
resolved "https://registry.yarnpkg.com/@vibrant/image-node/-/image-node-3.2.1-alpha.1.tgz#2901e09aee05d64ac9e792a951ee0727299ab80f"
integrity sha512-/Io/Rpo4EkO6AhaXdcxUXkbOFhSFtjm0LSAM4c0AyGA5EbC8PyZqjk8b11bQAEMCaYaweFQfTdGD7oVbXe21CQ==
dependencies:
"@jimp/custom" "^0.16.1"
"@jimp/plugin-resize" "^0.16.1"
"@jimp/types" "^0.16.1"
"@vibrant/image" "^3.2.1-alpha.1"
"@vibrant/image@^3.2.1-alpha.1":
version "3.2.1-alpha.1"
resolved "https://registry.yarnpkg.com/@vibrant/image/-/image-3.2.1-alpha.1.tgz#8bcde820f5ad873e2e96b00479def80f86e925a5"
integrity sha512-4aF5k79QfyhZOqRovJpbnIjWfe3uuWhY8voqVdd4/qgu4o70/AwVlM+pYmCaJVzI45VWNWWHYA5QlYuKsXnBqQ==
dependencies:
"@vibrant/color" "^3.2.1-alpha.1"
"@vibrant/types" "^3.2.1-alpha.1"
"@vibrant/quantizer-mmcq@^3.2.1-alpha.1":
version "3.2.1-alpha.1"
resolved "https://registry.yarnpkg.com/@vibrant/quantizer-mmcq/-/quantizer-mmcq-3.2.1-alpha.1.tgz#b36ecb48f4bff9ea35ed23389d8af79c079c079a"
integrity sha512-Wuk9PTZtxr8qsWTcgP6lcrrmrq36syVwxf+BUxdgQYntBcQ053SaN34lVGOJ0WPdK5vABoxbYljhceCgiILtZw==
dependencies:
"@vibrant/color" "^3.2.1-alpha.1"
"@vibrant/image" "^3.2.1-alpha.1"
"@vibrant/quantizer" "^3.2.1-alpha.1"
"@vibrant/quantizer@^3.2.1-alpha.1":
version "3.2.1-alpha.1"
resolved "https://registry.yarnpkg.com/@vibrant/quantizer/-/quantizer-3.2.1-alpha.1.tgz#8d29e288ea7acbcd0c9ab8c6b86f80adce606210"
integrity sha512-iHnPx/+n4iLtYLm1GClSfyg2fFbMatFG0ipCyp9M6tXNIPAg+pSvUJSGBnVnH7Nl/bR8Gkkj1h0pJ4RsKcdIrQ==
dependencies:
"@vibrant/color" "^3.2.1-alpha.1"
"@vibrant/image" "^3.2.1-alpha.1"
"@vibrant/types" "^3.2.1-alpha.1"
"@vibrant/types@^3.2.1-alpha.1":
version "3.2.1-alpha.1"
resolved "https://registry.yarnpkg.com/@vibrant/types/-/types-3.2.1-alpha.1.tgz#54ecf8b4d1045af699bfaf592e455079801bc951"
integrity sha512-ts9u7nsrENoYI5s0MmPOeY5kCLFKvQndKVDOPFCbTA0z493uhDp8mpiQhjFYTf3kPbS04z9zbHLE2luFC7x4KQ==
"@vibrant/worker@^3.2.1-alpha.1":
version "3.2.1-alpha.1"
resolved "https://registry.yarnpkg.com/@vibrant/worker/-/worker-3.2.1-alpha.1.tgz#d09e4ec72902d36b9632c2c0aab855747acf1015"
integrity sha512-mtSlBdHkFNr4FOnMtqtHJxy9z5AsUcZzGlpiHzvWOoaoN9lNTDPwxOBd0q4VTYWuGPrIm6Fuq5m7aRbLv7KqiQ==
dependencies:
"@vibrant/types" "^3.2.1-alpha.1"
"@vue/web-component-wrapper@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@vue/web-component-wrapper/-/web-component-wrapper-1.2.0.tgz#bb0e46f1585a7e289b4ee6067dcc5a6ae62f1dd1"
@ -8870,7 +8953,7 @@ lodash@^4.17.10, lodash@^4.17.11:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
lodash@^4.17.19, lodash@^4.17.20:
lodash@^4.17.19:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
@ -9546,17 +9629,17 @@ node-releases@^1.1.61:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.61.tgz#707b0fca9ce4e11783612ba4a2fcba09047af16e"
integrity sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g==
node-vibrant@^3.1.6:
version "3.1.6"
resolved "https://registry.yarnpkg.com/node-vibrant/-/node-vibrant-3.1.6.tgz#8554c3108903232cbe1e722f928469ee4379aa18"
integrity sha512-Wlc/hQmBMOu6xon12ZJHS2N3M+I6J8DhrD3Yo6m5175v8sFkVIN+UjhKVRcO+fqvre89ASTpmiFEP3nPO13SwA==
node-vibrant@3.2.1-alpha.1:
version "3.2.1-alpha.1"
resolved "https://registry.yarnpkg.com/node-vibrant/-/node-vibrant-3.2.1-alpha.1.tgz#d80a3dd22741150b804ae0d3eb99ceaf9f79980a"
integrity sha512-EQergCp7fvbvUCE0VMCBnvaAV0lGWSP8SXLmuWQIBzQK5M5pIwcd9fIOXuzFkJx/8hUiiiLvAzzGDS/bIy2ikA==
dependencies:
"@jimp/custom" "^0.16.1"
"@jimp/plugin-resize" "^0.16.1"
"@jimp/types" "^0.16.1"
"@types/lodash" "^4.14.53"
"@types/node" "^10.11.7"
lodash "^4.17.20"
"@types/node" "^10.12.18"
"@vibrant/core" "^3.2.1-alpha.1"
"@vibrant/generator-default" "^3.2.1-alpha.1"
"@vibrant/image-browser" "^3.2.1-alpha.1"
"@vibrant/image-node" "^3.2.1-alpha.1"
"@vibrant/quantizer-mmcq" "^3.2.1-alpha.1"
url "^0.11.0"
nopt@^4.0.1: