chore: vendor closest-css-color and its dependencies

Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
This commit is contained in:
Richard Steinmetz 2023-02-22 12:16:36 +01:00
parent 38e76af521
commit 5358d5637d
No known key found for this signature in database
GPG Key ID: 27137D9E7D273FB2
5 changed files with 219 additions and 65 deletions

103
package-lock.json generated
View File

@ -32,12 +32,13 @@
"@nextcloud/vue": "^7.6.0",
"@nextcloud/vue-dashboard": "^2.0.1",
"autosize": "^6.0.1",
"closest-css-color": "^1.0.0",
"color-convert": "^2.0.1",
"color-string": "^1.9.1",
"core-js": "^3.28.0",
"css-color-names": "^1.0.1",
"debounce": "^1.2.1",
"jstz": "^2.1.1",
"lodash": "^4.17.21",
"md5": "^2.3.0",
"p-limit": "^4.0.0",
"v-tooltip": "^2.1.3",
@ -5844,19 +5845,6 @@
"node": ">=0.10.0"
}
},
"node_modules/closest-css-color": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/closest-css-color/-/closest-css-color-1.0.0.tgz",
"integrity": "sha512-tSBfCvnaW4d2gSTg/yKRhZXarp6lDFM0FQthfMWweOiLa2fqVyWJhDO7PXxT7PX//xT+Ci8iPJKaZCIhwGDFHw==",
"dependencies": {
"colour-proximity": "0.0.2",
"css-color-names": "1.0.1",
"lodash": "^4.17.21"
},
"engines": {
"node": ">=14"
}
},
"node_modules/co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@ -5890,19 +5878,12 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/color-string": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-0.1.3.tgz",
"integrity": "sha512-ERkoOp/s/VSrQ5OyH1Gs9LCgFWnTlQXUqAaGNBJzV2gjuunWuxISth8lOaDqfPfDIjiR9MI7WrzH1hDNRVOCfQ==",
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dependencies": {
"color-convert": "0.2.x"
}
},
"node_modules/color-string/node_modules/color-convert": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.2.1.tgz",
"integrity": "sha512-FWbwpCgyRV41Vml0iKU9UmL0dVTKORnm7ZC8h8cdfvutk2bU7ZcMLtSleggScK/IpUVXILg9Pw86LhPUQyTaVg==",
"engines": {
"node": "*"
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/colord": {
@ -5919,14 +5900,6 @@
"dev": true,
"peer": true
},
"node_modules/colour-proximity": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/colour-proximity/-/colour-proximity-0.0.2.tgz",
"integrity": "sha512-jaV4uLTEreh/6dXr1WW0sC0B9HEdUhho9MbOmgQxDPKR33/FrdAXXC/cgC6k9UdXbT5Dwy8d05z+R9FEo9PVEw==",
"dependencies": {
"color-string": "~0.1.2"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@ -14142,6 +14115,19 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/simple-swizzle/node_modules/is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@ -21049,16 +21035,6 @@
}
}
},
"closest-css-color": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/closest-css-color/-/closest-css-color-1.0.0.tgz",
"integrity": "sha512-tSBfCvnaW4d2gSTg/yKRhZXarp6lDFM0FQthfMWweOiLa2fqVyWJhDO7PXxT7PX//xT+Ci8iPJKaZCIhwGDFHw==",
"requires": {
"colour-proximity": "0.0.2",
"css-color-names": "1.0.1",
"lodash": "^4.17.21"
}
},
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@ -21085,18 +21061,12 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"color-string": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-0.1.3.tgz",
"integrity": "sha512-ERkoOp/s/VSrQ5OyH1Gs9LCgFWnTlQXUqAaGNBJzV2gjuunWuxISth8lOaDqfPfDIjiR9MI7WrzH1hDNRVOCfQ==",
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"requires": {
"color-convert": "0.2.x"
},
"dependencies": {
"color-convert": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.2.1.tgz",
"integrity": "sha512-FWbwpCgyRV41Vml0iKU9UmL0dVTKORnm7ZC8h8cdfvutk2bU7ZcMLtSleggScK/IpUVXILg9Pw86LhPUQyTaVg=="
}
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"colord": {
@ -21113,14 +21083,6 @@
"dev": true,
"peer": true
},
"colour-proximity": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/colour-proximity/-/colour-proximity-0.0.2.tgz",
"integrity": "sha512-jaV4uLTEreh/6dXr1WW0sC0B9HEdUhho9MbOmgQxDPKR33/FrdAXXC/cgC6k9UdXbT5Dwy8d05z+R9FEo9PVEw==",
"requires": {
"color-string": "~0.1.2"
}
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@ -27335,6 +27297,21 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true
},
"simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"requires": {
"is-arrayish": "^0.3.1"
},
"dependencies": {
"is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
}
}
},
"sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",

View File

@ -59,12 +59,13 @@
"@nextcloud/vue": "^7.6.0",
"@nextcloud/vue-dashboard": "^2.0.1",
"autosize": "^6.0.1",
"closest-css-color": "^1.0.0",
"color-convert": "^2.0.1",
"color-string": "^1.9.1",
"core-js": "^3.28.0",
"css-color-names": "^1.0.1",
"debounce": "^1.2.1",
"jstz": "^2.1.1",
"lodash": "^4.17.21",
"md5": "^2.3.0",
"p-limit": "^4.0.0",
"v-tooltip": "^2.1.3",

124
src/utils/closestColor.js Normal file
View File

@ -0,0 +1,124 @@
/**
* @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.
*
* @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.
*
* @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)
)
}
/**
* @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
}

View File

@ -22,7 +22,7 @@
import convert from 'color-convert'
import { uidToColor } from './uidToColor.js'
import css3Colors from 'css-color-names'
import closestColor from 'closest-css-color'
import closestColor from './closestColor.js'
/**
* Detect if a color is light or dark

View File

@ -0,0 +1,52 @@
/**
* @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
import closestColor from '../../../../src/utils/closestColor.js'
import cssColors from 'css-color-names'
import uniqBy from 'lodash/uniqBy.js'
import pick from 'lodash/pick.js'
describe('utils/closestColor test suite', () => {
it('should calculate the closest color', () => {
const uniqColorKeys = uniqBy(Object.keys(cssColors), c => cssColors[c])
const filteredColors = pick(cssColors, uniqColorKeys)
const color1 = closestColor('#fff')
expect(color1).toBe('white')
const color2 = closestColor('#a00a0a', { detailed: false })
expect(color2).toBe('darkred')
const color3 = closestColor('#1019a6', { detailed: true })
expect(color3).toEqual({
name: 'darkblue',
hex: '#00008b',
})
for (const color of Object.keys(filteredColors)) {
expect(closestColor(filteredColors[color])).toBe(color)
}
})
})