mirror of https://github.com/nextcloud/calendar
148 lines
4.5 KiB
JavaScript
148 lines
4.5 KiB
JavaScript
/**
|
|
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
|
|
*
|
|
* @author Richard Steinmetz <richard@steinmetz.cloud>
|
|
*
|
|
* @license AGPL-3.0-or-later
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
// Code was taken from:
|
|
// - https://github.com/juliuste/closest-css-color
|
|
// - https://github.com/gausie/colour-proximity
|
|
// - https://github.com/gausie/colour-proximity/pull/3
|
|
|
|
import cssColors from 'css-color-names'
|
|
import sortBy from 'lodash/sortBy.js'
|
|
import pick from 'lodash/pick.js'
|
|
import uniqBy from 'lodash/uniqBy.js'
|
|
import { get } from 'color-string'
|
|
|
|
const uniqColorKeys = uniqBy(Object.keys(cssColors), c => cssColors[c])
|
|
const filteredColors = pick(cssColors, uniqColorKeys)
|
|
|
|
const colors = sortBy(
|
|
Object.keys(filteredColors).map(name => ({
|
|
name,
|
|
hex: filteredColors[name],
|
|
})),
|
|
c => c.hex,
|
|
)
|
|
|
|
const defaults = {
|
|
detailed: false,
|
|
}
|
|
|
|
/**
|
|
* Find the closest CSS color to a given hex color.
|
|
*
|
|
* Adapted from https://github.com/juliuste/closest-css-color
|
|
*
|
|
* Copyright (c) 2021, Julius Tens
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any purpose with or without
|
|
* fee is hereby granted, provided that the above copyright notice and this permission notice
|
|
* appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
|
|
* SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
|
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
|
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
|
* OF THIS SOFTWARE.
|
|
*
|
|
* @param {string} hex Hex color string
|
|
* @param {object} opt Options
|
|
* @param {boolean=} opt.detailed Return color object instead of just the name
|
|
* @return {string|{name: string, hex: string}} Closest color name or object
|
|
*/
|
|
export default function closestColor(hex, opt = {}) {
|
|
const options = { ...defaults, ...opt }
|
|
const sortedColors = sortBy(colors, c => proximity(hex, c.hex))
|
|
if (options.detailed) {
|
|
return sortedColors[0]
|
|
}
|
|
return sortedColors[0].name
|
|
}
|
|
|
|
/**
|
|
* Calculate the proximity between two colors.
|
|
*
|
|
* Adapted from https://github.com/gausie/colour-proximity
|
|
*
|
|
* Copyright (c) 2013, Samuel Gaus
|
|
*
|
|
* @param {string} s1 Hex color string 1
|
|
* @param {string} s2 Hex color string 2
|
|
* @return {number}
|
|
*/
|
|
function proximity(s1, s2) {
|
|
const c1 = rgb2lab(get.rgb(s1))
|
|
const c2 = rgb2lab(get.rgb(s2))
|
|
return Math.sqrt(
|
|
Math.pow(c1[0] - c2[0], 2)
|
|
+ Math.pow(c1[1] - c2[1], 2)
|
|
+ Math.pow(c1[2] - c2[2], 2),
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Adapted from https://github.com/gausie/colour-proximity
|
|
*
|
|
* Copyright (c) 2013, Samuel Gaus
|
|
*
|
|
* @param {number[]} input RGB array
|
|
*/
|
|
function rgb2lab(input) {
|
|
// This code is adapted from various functions at http://www.easyrgb.com/index.php?X=MATH
|
|
|
|
const rgb = [0, 0, 0]
|
|
const xyz = [0, 0, 0]
|
|
const Lab = [0, 0, 0]
|
|
|
|
for (let i = 0; i < input.length; i++) {
|
|
let value = input[i] / 255
|
|
|
|
if (value > 0.04045) {
|
|
value = Math.pow(((value + 0.055) / 1.055), 2.4)
|
|
} else {
|
|
value = value / 12.92
|
|
}
|
|
|
|
rgb[i] = value * 100
|
|
}
|
|
|
|
xyz[0] = (rgb[0] * 0.4124 + rgb[1] * 0.3576 + rgb[2] * 0.1805) / 95.047 // ref_X = 95.047 Observer= 2°, Illuminant= D65
|
|
xyz[1] = (rgb[0] * 0.2126 + rgb[1] * 0.7152 + rgb[2] * 0.0722) / 100.0 // ref_Y = 100.000
|
|
xyz[2] = (rgb[0] * 0.0193 + rgb[1] * 0.1192 + rgb[2] * 0.9505) / 108.883 // ref_Z = 108.883
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
let value = xyz[i]
|
|
if (value > 0.008856) {
|
|
value = Math.pow(value, 1 / 3)
|
|
} else {
|
|
value = (7.787 * value) + (16 / 116)
|
|
}
|
|
xyz[i] = value
|
|
}
|
|
|
|
Lab[0] = parseFloat(((116 * xyz[1]) - 16).toFixed(3))
|
|
Lab[1] = parseFloat((500 * (xyz[0] - xyz[1])).toFixed(3))
|
|
Lab[2] = parseFloat((200 * (xyz[1] - xyz[2])).toFixed(3))
|
|
|
|
return Lab
|
|
}
|