@sinco/react 1.0.7-rc.2 → 1.0.7-rc.4
Sign up to get free protection for your applications and to get access to all the features.
- package/index.js +1739 -4
- package/package.json +1 -1
- package/src/lib/Hooks/useDynamicColor.d.ts +1 -2
package/index.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import * as React from 'react';
|
2
|
-
import React__default, { forwardRef, useContext } from 'react';
|
2
|
+
import React__default, { forwardRef, useContext, useState, useEffect } from 'react';
|
3
3
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
4
4
|
|
5
5
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
@@ -5209,7 +5209,7 @@ const properties = {
|
|
5209
5209
|
m: 'margin',
|
5210
5210
|
p: 'padding'
|
5211
5211
|
};
|
5212
|
-
const directions = {
|
5212
|
+
const directions$1 = {
|
5213
5213
|
t: 'Top',
|
5214
5214
|
r: 'Right',
|
5215
5215
|
b: 'Bottom',
|
@@ -5238,7 +5238,7 @@ const getCssProperties = memoize(prop => {
|
|
5238
5238
|
}
|
5239
5239
|
const [a, b] = prop.split('');
|
5240
5240
|
const property = properties[a];
|
5241
|
-
const direction = directions[b] || '';
|
5241
|
+
const direction = directions$1[b] || '';
|
5242
5242
|
return Array.isArray(direction) ? direction.map(dir => property + dir) : [property + direction];
|
5243
5243
|
});
|
5244
5244
|
const marginKeys = ['m', 'mt', 'mr', 'mb', 'ml', 'mx', 'my', 'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft', 'marginX', 'marginY', 'marginInline', 'marginInlineStart', 'marginInlineEnd', 'marginBlock', 'marginBlockStart', 'marginBlockEnd'];
|
@@ -6508,6 +6508,27 @@ function getContrastRatio(foreground, background) {
|
|
6508
6508
|
return (Math.max(lumA, lumB) + 0.05) / (Math.min(lumA, lumB) + 0.05);
|
6509
6509
|
}
|
6510
6510
|
|
6511
|
+
/**
|
6512
|
+
* Sets the absolute transparency of a color.
|
6513
|
+
* Any existing alpha values are overwritten.
|
6514
|
+
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color()
|
6515
|
+
* @param {number} value - value to set the alpha channel to in the range 0 - 1
|
6516
|
+
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
6517
|
+
*/
|
6518
|
+
function alpha(color, value) {
|
6519
|
+
color = decomposeColor(color);
|
6520
|
+
value = clamp(value);
|
6521
|
+
if (color.type === 'rgb' || color.type === 'hsl') {
|
6522
|
+
color.type += 'a';
|
6523
|
+
}
|
6524
|
+
if (color.type === 'color') {
|
6525
|
+
color.values[3] = `/${value}`;
|
6526
|
+
} else {
|
6527
|
+
color.values[3] = value;
|
6528
|
+
}
|
6529
|
+
return recomposeColor(color);
|
6530
|
+
}
|
6531
|
+
|
6511
6532
|
/**
|
6512
6533
|
* Darkens a color.
|
6513
6534
|
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color()
|
@@ -9903,6 +9924,19 @@ function clampDouble(min, max, input) {
|
|
9903
9924
|
}
|
9904
9925
|
return input;
|
9905
9926
|
}
|
9927
|
+
/**
|
9928
|
+
* Sanitizes a degree measure as an integer.
|
9929
|
+
*
|
9930
|
+
* @return a degree measure between 0 (inclusive) and 360
|
9931
|
+
* (exclusive).
|
9932
|
+
*/
|
9933
|
+
function sanitizeDegreesInt(degrees) {
|
9934
|
+
degrees = degrees % 360;
|
9935
|
+
if (degrees < 0) {
|
9936
|
+
degrees = degrees + 360;
|
9937
|
+
}
|
9938
|
+
return degrees;
|
9939
|
+
}
|
9906
9940
|
/**
|
9907
9941
|
* Sanitizes a degree measure as a floating-point number.
|
9908
9942
|
*
|
@@ -9916,6 +9950,30 @@ function sanitizeDegreesDouble(degrees) {
|
|
9916
9950
|
}
|
9917
9951
|
return degrees;
|
9918
9952
|
}
|
9953
|
+
/**
|
9954
|
+
* Sign of direction change needed to travel from one angle to
|
9955
|
+
* another.
|
9956
|
+
*
|
9957
|
+
* For angles that are 180 degrees apart from each other, both
|
9958
|
+
* directions have the same travel distance, so either direction is
|
9959
|
+
* shortest. The value 1.0 is returned in this case.
|
9960
|
+
*
|
9961
|
+
* @param from The angle travel starts from, in degrees.
|
9962
|
+
* @param to The angle travel ends at, in degrees.
|
9963
|
+
* @return -1 if decreasing from leads to the shortest travel
|
9964
|
+
* distance, 1 if increasing from leads to the shortest travel
|
9965
|
+
* distance.
|
9966
|
+
*/
|
9967
|
+
function rotationDirection(from, to) {
|
9968
|
+
const increasingDifference = sanitizeDegreesDouble(to - from);
|
9969
|
+
return increasingDifference <= 180.0 ? 1.0 : -1.0;
|
9970
|
+
}
|
9971
|
+
/**
|
9972
|
+
* Distance of two points on a circle, represented using degrees.
|
9973
|
+
*/
|
9974
|
+
function differenceDegrees(a, b) {
|
9975
|
+
return 180.0 - Math.abs(Math.abs(a - b) - 180.0);
|
9976
|
+
}
|
9919
9977
|
/**
|
9920
9978
|
* Multiplies a 1x3 row vector with a 3x3 matrix.
|
9921
9979
|
*/
|
@@ -9987,6 +10045,12 @@ function argbFromLinrgb(linrgb) {
|
|
9987
10045
|
const b = delinearized(linrgb[2]);
|
9988
10046
|
return argbFromRgb(r, g, b);
|
9989
10047
|
}
|
10048
|
+
/**
|
10049
|
+
* Returns the alpha component of a color in ARGB format.
|
10050
|
+
*/
|
10051
|
+
function alphaFromArgb(argb) {
|
10052
|
+
return argb >> 24 & 255;
|
10053
|
+
}
|
9990
10054
|
/**
|
9991
10055
|
* Returns the red component of a color in ARGB format.
|
9992
10056
|
*/
|
@@ -10027,6 +10091,50 @@ function xyzFromArgb(argb) {
|
|
10027
10091
|
const b = linearized(blueFromArgb(argb));
|
10028
10092
|
return matrixMultiply([r, g, b], SRGB_TO_XYZ);
|
10029
10093
|
}
|
10094
|
+
/**
|
10095
|
+
* Converts a color represented in Lab color space into an ARGB
|
10096
|
+
* integer.
|
10097
|
+
*/
|
10098
|
+
function argbFromLab(l, a, b) {
|
10099
|
+
const whitePoint = WHITE_POINT_D65;
|
10100
|
+
const fy = (l + 16.0) / 116.0;
|
10101
|
+
const fx = a / 500.0 + fy;
|
10102
|
+
const fz = fy - b / 200.0;
|
10103
|
+
const xNormalized = labInvf(fx);
|
10104
|
+
const yNormalized = labInvf(fy);
|
10105
|
+
const zNormalized = labInvf(fz);
|
10106
|
+
const x = xNormalized * whitePoint[0];
|
10107
|
+
const y = yNormalized * whitePoint[1];
|
10108
|
+
const z = zNormalized * whitePoint[2];
|
10109
|
+
return argbFromXyz(x, y, z);
|
10110
|
+
}
|
10111
|
+
/**
|
10112
|
+
* Converts a color from ARGB representation to L*a*b*
|
10113
|
+
* representation.
|
10114
|
+
*
|
10115
|
+
* @param argb the ARGB representation of a color
|
10116
|
+
* @return a Lab object representing the color
|
10117
|
+
*/
|
10118
|
+
function labFromArgb(argb) {
|
10119
|
+
const linearR = linearized(redFromArgb(argb));
|
10120
|
+
const linearG = linearized(greenFromArgb(argb));
|
10121
|
+
const linearB = linearized(blueFromArgb(argb));
|
10122
|
+
const matrix = SRGB_TO_XYZ;
|
10123
|
+
const x = matrix[0][0] * linearR + matrix[0][1] * linearG + matrix[0][2] * linearB;
|
10124
|
+
const y = matrix[1][0] * linearR + matrix[1][1] * linearG + matrix[1][2] * linearB;
|
10125
|
+
const z = matrix[2][0] * linearR + matrix[2][1] * linearG + matrix[2][2] * linearB;
|
10126
|
+
const whitePoint = WHITE_POINT_D65;
|
10127
|
+
const xNormalized = x / whitePoint[0];
|
10128
|
+
const yNormalized = y / whitePoint[1];
|
10129
|
+
const zNormalized = z / whitePoint[2];
|
10130
|
+
const fx = labF(xNormalized);
|
10131
|
+
const fy = labF(yNormalized);
|
10132
|
+
const fz = labF(zNormalized);
|
10133
|
+
const l = 116.0 * fy - 16;
|
10134
|
+
const a = 500.0 * (fx - fy);
|
10135
|
+
const b = 200.0 * (fy - fz);
|
10136
|
+
return [l, a, b];
|
10137
|
+
}
|
10030
10138
|
/**
|
10031
10139
|
* Converts an L* value to an ARGB representation.
|
10032
10140
|
*
|
@@ -11238,6 +11346,93 @@ class Hct {
|
|
11238
11346
|
}
|
11239
11347
|
}
|
11240
11348
|
|
11349
|
+
/**
|
11350
|
+
* @license
|
11351
|
+
* Copyright 2021 Google LLC
|
11352
|
+
*
|
11353
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
11354
|
+
* you may not use this file except in compliance with the License.
|
11355
|
+
* You may obtain a copy of the License at
|
11356
|
+
*
|
11357
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
11358
|
+
*
|
11359
|
+
* Unless required by applicable law or agreed to in writing, software
|
11360
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
11361
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
11362
|
+
* See the License for the specific language governing permissions and
|
11363
|
+
* limitations under the License.
|
11364
|
+
*/
|
11365
|
+
// material_color_utilities is designed to have a consistent API across
|
11366
|
+
// platforms and modular components that can be moved around easily. Using a
|
11367
|
+
// class as a namespace facilitates this.
|
11368
|
+
//
|
11369
|
+
// tslint:disable:class-as-namespace
|
11370
|
+
/**
|
11371
|
+
* Functions for blending in HCT and CAM16.
|
11372
|
+
*/
|
11373
|
+
class Blend {
|
11374
|
+
/**
|
11375
|
+
* Blend the design color's HCT hue towards the key color's HCT
|
11376
|
+
* hue, in a way that leaves the original color recognizable and
|
11377
|
+
* recognizably shifted towards the key color.
|
11378
|
+
*
|
11379
|
+
* @param designColor ARGB representation of an arbitrary color.
|
11380
|
+
* @param sourceColor ARGB representation of the main theme color.
|
11381
|
+
* @return The design color with a hue shifted towards the
|
11382
|
+
* system's color, a slightly warmer/cooler variant of the design
|
11383
|
+
* color's hue.
|
11384
|
+
*/
|
11385
|
+
static harmonize(designColor, sourceColor) {
|
11386
|
+
const fromHct = Hct.fromInt(designColor);
|
11387
|
+
const toHct = Hct.fromInt(sourceColor);
|
11388
|
+
const differenceDegrees$1 = differenceDegrees(fromHct.hue, toHct.hue);
|
11389
|
+
const rotationDegrees = Math.min(differenceDegrees$1 * 0.5, 15.0);
|
11390
|
+
const outputHue = sanitizeDegreesDouble(fromHct.hue +
|
11391
|
+
rotationDegrees * rotationDirection(fromHct.hue, toHct.hue));
|
11392
|
+
return Hct.from(outputHue, fromHct.chroma, fromHct.tone).toInt();
|
11393
|
+
}
|
11394
|
+
/**
|
11395
|
+
* Blends hue from one color into another. The chroma and tone of
|
11396
|
+
* the original color are maintained.
|
11397
|
+
*
|
11398
|
+
* @param from ARGB representation of color
|
11399
|
+
* @param to ARGB representation of color
|
11400
|
+
* @param amount how much blending to perform; 0.0 >= and <= 1.0
|
11401
|
+
* @return from, with a hue blended towards to. Chroma and tone
|
11402
|
+
* are constant.
|
11403
|
+
*/
|
11404
|
+
static hctHue(from, to, amount) {
|
11405
|
+
const ucs = Blend.cam16Ucs(from, to, amount);
|
11406
|
+
const ucsCam = Cam16.fromInt(ucs);
|
11407
|
+
const fromCam = Cam16.fromInt(from);
|
11408
|
+
const blended = Hct.from(ucsCam.hue, fromCam.chroma, lstarFromArgb(from));
|
11409
|
+
return blended.toInt();
|
11410
|
+
}
|
11411
|
+
/**
|
11412
|
+
* Blend in CAM16-UCS space.
|
11413
|
+
*
|
11414
|
+
* @param from ARGB representation of color
|
11415
|
+
* @param to ARGB representation of color
|
11416
|
+
* @param amount how much blending to perform; 0.0 >= and <= 1.0
|
11417
|
+
* @return from, blended towards to. Hue, chroma, and tone will
|
11418
|
+
* change.
|
11419
|
+
*/
|
11420
|
+
static cam16Ucs(from, to, amount) {
|
11421
|
+
const fromCam = Cam16.fromInt(from);
|
11422
|
+
const toCam = Cam16.fromInt(to);
|
11423
|
+
const fromJ = fromCam.jstar;
|
11424
|
+
const fromA = fromCam.astar;
|
11425
|
+
const fromB = fromCam.bstar;
|
11426
|
+
const toJ = toCam.jstar;
|
11427
|
+
const toA = toCam.astar;
|
11428
|
+
const toB = toCam.bstar;
|
11429
|
+
const jstar = fromJ + (toJ - fromJ) * amount;
|
11430
|
+
const astar = fromA + (toA - fromA) * amount;
|
11431
|
+
const bstar = fromB + (toB - fromB) * amount;
|
11432
|
+
return Cam16.fromUcs(jstar, astar, bstar).toInt();
|
11433
|
+
}
|
11434
|
+
}
|
11435
|
+
|
11241
11436
|
/**
|
11242
11437
|
* @license
|
11243
11438
|
* Copyright 2022 Google LLC
|
@@ -12482,4 +12677,1544 @@ MaterialDynamicColors.onTertiaryFixedVariant = DynamicColor.fromPalette({
|
|
12482
12677
|
contrastCurve: new ContrastCurve(3, 4.5, 7, 11),
|
12483
12678
|
});
|
12484
12679
|
|
12485
|
-
|
12680
|
+
/**
|
12681
|
+
* @license
|
12682
|
+
* Copyright 2021 Google LLC
|
12683
|
+
*
|
12684
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
12685
|
+
* you may not use this file except in compliance with the License.
|
12686
|
+
* You may obtain a copy of the License at
|
12687
|
+
*
|
12688
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
12689
|
+
*
|
12690
|
+
* Unless required by applicable law or agreed to in writing, software
|
12691
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
12692
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12693
|
+
* See the License for the specific language governing permissions and
|
12694
|
+
* limitations under the License.
|
12695
|
+
*/
|
12696
|
+
/**
|
12697
|
+
* A convenience class for retrieving colors that are constant in hue and
|
12698
|
+
* chroma, but vary in tone.
|
12699
|
+
*/
|
12700
|
+
class TonalPalette {
|
12701
|
+
/**
|
12702
|
+
* @param argb ARGB representation of a color
|
12703
|
+
* @return Tones matching that color's hue and chroma.
|
12704
|
+
*/
|
12705
|
+
static fromInt(argb) {
|
12706
|
+
const hct = Hct.fromInt(argb);
|
12707
|
+
return TonalPalette.fromHct(hct);
|
12708
|
+
}
|
12709
|
+
/**
|
12710
|
+
* @param hct Hct
|
12711
|
+
* @return Tones matching that color's hue and chroma.
|
12712
|
+
*/
|
12713
|
+
static fromHct(hct) {
|
12714
|
+
return new TonalPalette(hct.hue, hct.chroma, hct);
|
12715
|
+
}
|
12716
|
+
/**
|
12717
|
+
* @param hue HCT hue
|
12718
|
+
* @param chroma HCT chroma
|
12719
|
+
* @return Tones matching hue and chroma.
|
12720
|
+
*/
|
12721
|
+
static fromHueAndChroma(hue, chroma) {
|
12722
|
+
return new TonalPalette(hue, chroma, TonalPalette.createKeyColor(hue, chroma));
|
12723
|
+
}
|
12724
|
+
constructor(hue, chroma, keyColor) {
|
12725
|
+
this.hue = hue;
|
12726
|
+
this.chroma = chroma;
|
12727
|
+
this.keyColor = keyColor;
|
12728
|
+
this.cache = new Map();
|
12729
|
+
}
|
12730
|
+
static createKeyColor(hue, chroma) {
|
12731
|
+
const startTone = 50.0;
|
12732
|
+
let smallestDeltaHct = Hct.from(hue, chroma, startTone);
|
12733
|
+
let smallestDelta = Math.abs(smallestDeltaHct.chroma - chroma);
|
12734
|
+
// Starting from T50, check T+/-delta to see if they match the requested
|
12735
|
+
// chroma.
|
12736
|
+
//
|
12737
|
+
// Starts from T50 because T50 has the most chroma available, on
|
12738
|
+
// average. Thus it is most likely to have a direct answer and minimize
|
12739
|
+
// iteration.
|
12740
|
+
for (let delta = 1.0; delta < 50.0; delta += 1.0) {
|
12741
|
+
// Termination condition rounding instead of minimizing delta to avoid
|
12742
|
+
// case where requested chroma is 16.51, and the closest chroma is 16.49.
|
12743
|
+
// Error is minimized, but when rounded and displayed, requested chroma
|
12744
|
+
// is 17, key color's chroma is 16.
|
12745
|
+
if (Math.round(chroma) === Math.round(smallestDeltaHct.chroma)) {
|
12746
|
+
return smallestDeltaHct;
|
12747
|
+
}
|
12748
|
+
const hctAdd = Hct.from(hue, chroma, startTone + delta);
|
12749
|
+
const hctAddDelta = Math.abs(hctAdd.chroma - chroma);
|
12750
|
+
if (hctAddDelta < smallestDelta) {
|
12751
|
+
smallestDelta = hctAddDelta;
|
12752
|
+
smallestDeltaHct = hctAdd;
|
12753
|
+
}
|
12754
|
+
const hctSubtract = Hct.from(hue, chroma, startTone - delta);
|
12755
|
+
const hctSubtractDelta = Math.abs(hctSubtract.chroma - chroma);
|
12756
|
+
if (hctSubtractDelta < smallestDelta) {
|
12757
|
+
smallestDelta = hctSubtractDelta;
|
12758
|
+
smallestDeltaHct = hctSubtract;
|
12759
|
+
}
|
12760
|
+
}
|
12761
|
+
return smallestDeltaHct;
|
12762
|
+
}
|
12763
|
+
/**
|
12764
|
+
* @param tone HCT tone, measured from 0 to 100.
|
12765
|
+
* @return ARGB representation of a color with that tone.
|
12766
|
+
*/
|
12767
|
+
tone(tone) {
|
12768
|
+
let argb = this.cache.get(tone);
|
12769
|
+
if (argb === undefined) {
|
12770
|
+
argb = Hct.from(this.hue, this.chroma, tone).toInt();
|
12771
|
+
this.cache.set(tone, argb);
|
12772
|
+
}
|
12773
|
+
return argb;
|
12774
|
+
}
|
12775
|
+
/**
|
12776
|
+
* @param tone HCT tone.
|
12777
|
+
* @return HCT representation of a color with that tone.
|
12778
|
+
*/
|
12779
|
+
getHct(tone) {
|
12780
|
+
return Hct.fromInt(this.tone(tone));
|
12781
|
+
}
|
12782
|
+
}
|
12783
|
+
|
12784
|
+
/**
|
12785
|
+
* @license
|
12786
|
+
* Copyright 2021 Google LLC
|
12787
|
+
*
|
12788
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
12789
|
+
* you may not use this file except in compliance with the License.
|
12790
|
+
* You may obtain a copy of the License at
|
12791
|
+
*
|
12792
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
12793
|
+
*
|
12794
|
+
* Unless required by applicable law or agreed to in writing, software
|
12795
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
12796
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12797
|
+
* See the License for the specific language governing permissions and
|
12798
|
+
* limitations under the License.
|
12799
|
+
*/
|
12800
|
+
/**
|
12801
|
+
* An intermediate concept between the key color for a UI theme, and a full
|
12802
|
+
* color scheme. 5 sets of tones are generated, all except one use the same hue
|
12803
|
+
* as the key color, and all vary in chroma.
|
12804
|
+
*/
|
12805
|
+
class CorePalette {
|
12806
|
+
/**
|
12807
|
+
* @param argb ARGB representation of a color
|
12808
|
+
*/
|
12809
|
+
static of(argb) {
|
12810
|
+
return new CorePalette(argb, false);
|
12811
|
+
}
|
12812
|
+
/**
|
12813
|
+
* @param argb ARGB representation of a color
|
12814
|
+
*/
|
12815
|
+
static contentOf(argb) {
|
12816
|
+
return new CorePalette(argb, true);
|
12817
|
+
}
|
12818
|
+
/**
|
12819
|
+
* Create a [CorePalette] from a set of colors
|
12820
|
+
*/
|
12821
|
+
static fromColors(colors) {
|
12822
|
+
return CorePalette.createPaletteFromColors(false, colors);
|
12823
|
+
}
|
12824
|
+
/**
|
12825
|
+
* Create a content [CorePalette] from a set of colors
|
12826
|
+
*/
|
12827
|
+
static contentFromColors(colors) {
|
12828
|
+
return CorePalette.createPaletteFromColors(true, colors);
|
12829
|
+
}
|
12830
|
+
static createPaletteFromColors(content, colors) {
|
12831
|
+
const palette = new CorePalette(colors.primary, content);
|
12832
|
+
if (colors.secondary) {
|
12833
|
+
const p = new CorePalette(colors.secondary, content);
|
12834
|
+
palette.a2 = p.a1;
|
12835
|
+
}
|
12836
|
+
if (colors.tertiary) {
|
12837
|
+
const p = new CorePalette(colors.tertiary, content);
|
12838
|
+
palette.a3 = p.a1;
|
12839
|
+
}
|
12840
|
+
if (colors.error) {
|
12841
|
+
const p = new CorePalette(colors.error, content);
|
12842
|
+
palette.error = p.a1;
|
12843
|
+
}
|
12844
|
+
if (colors.neutral) {
|
12845
|
+
const p = new CorePalette(colors.neutral, content);
|
12846
|
+
palette.n1 = p.n1;
|
12847
|
+
}
|
12848
|
+
if (colors.neutralVariant) {
|
12849
|
+
const p = new CorePalette(colors.neutralVariant, content);
|
12850
|
+
palette.n2 = p.n2;
|
12851
|
+
}
|
12852
|
+
return palette;
|
12853
|
+
}
|
12854
|
+
constructor(argb, isContent) {
|
12855
|
+
const hct = Hct.fromInt(argb);
|
12856
|
+
const hue = hct.hue;
|
12857
|
+
const chroma = hct.chroma;
|
12858
|
+
if (isContent) {
|
12859
|
+
this.a1 = TonalPalette.fromHueAndChroma(hue, chroma);
|
12860
|
+
this.a2 = TonalPalette.fromHueAndChroma(hue, chroma / 3);
|
12861
|
+
this.a3 = TonalPalette.fromHueAndChroma(hue + 60, chroma / 2);
|
12862
|
+
this.n1 = TonalPalette.fromHueAndChroma(hue, Math.min(chroma / 12, 4));
|
12863
|
+
this.n2 = TonalPalette.fromHueAndChroma(hue, Math.min(chroma / 6, 8));
|
12864
|
+
}
|
12865
|
+
else {
|
12866
|
+
this.a1 = TonalPalette.fromHueAndChroma(hue, Math.max(48, chroma));
|
12867
|
+
this.a2 = TonalPalette.fromHueAndChroma(hue, 16);
|
12868
|
+
this.a3 = TonalPalette.fromHueAndChroma(hue + 60, 24);
|
12869
|
+
this.n1 = TonalPalette.fromHueAndChroma(hue, 4);
|
12870
|
+
this.n2 = TonalPalette.fromHueAndChroma(hue, 8);
|
12871
|
+
}
|
12872
|
+
this.error = TonalPalette.fromHueAndChroma(25, 84);
|
12873
|
+
}
|
12874
|
+
}
|
12875
|
+
|
12876
|
+
/**
|
12877
|
+
* @license
|
12878
|
+
* Copyright 2021 Google LLC
|
12879
|
+
*
|
12880
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
12881
|
+
* you may not use this file except in compliance with the License.
|
12882
|
+
* You may obtain a copy of the License at
|
12883
|
+
*
|
12884
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
12885
|
+
*
|
12886
|
+
* Unless required by applicable law or agreed to in writing, software
|
12887
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
12888
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12889
|
+
* See the License for the specific language governing permissions and
|
12890
|
+
* limitations under the License.
|
12891
|
+
*/
|
12892
|
+
/**
|
12893
|
+
* Provides conversions needed for K-Means quantization. Converting input to
|
12894
|
+
* points, and converting the final state of the K-Means algorithm to colors.
|
12895
|
+
*/
|
12896
|
+
class LabPointProvider {
|
12897
|
+
/**
|
12898
|
+
* Convert a color represented in ARGB to a 3-element array of L*a*b*
|
12899
|
+
* coordinates of the color.
|
12900
|
+
*/
|
12901
|
+
fromInt(argb) {
|
12902
|
+
return labFromArgb(argb);
|
12903
|
+
}
|
12904
|
+
/**
|
12905
|
+
* Convert a 3-element array to a color represented in ARGB.
|
12906
|
+
*/
|
12907
|
+
toInt(point) {
|
12908
|
+
return argbFromLab(point[0], point[1], point[2]);
|
12909
|
+
}
|
12910
|
+
/**
|
12911
|
+
* Standard CIE 1976 delta E formula also takes the square root, unneeded
|
12912
|
+
* here. This method is used by quantization algorithms to compare distance,
|
12913
|
+
* and the relative ordering is the same, with or without a square root.
|
12914
|
+
*
|
12915
|
+
* This relatively minor optimization is helpful because this method is
|
12916
|
+
* called at least once for each pixel in an image.
|
12917
|
+
*/
|
12918
|
+
distance(from, to) {
|
12919
|
+
const dL = from[0] - to[0];
|
12920
|
+
const dA = from[1] - to[1];
|
12921
|
+
const dB = from[2] - to[2];
|
12922
|
+
return dL * dL + dA * dA + dB * dB;
|
12923
|
+
}
|
12924
|
+
}
|
12925
|
+
|
12926
|
+
/**
|
12927
|
+
* @license
|
12928
|
+
* Copyright 2021 Google LLC
|
12929
|
+
*
|
12930
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
12931
|
+
* you may not use this file except in compliance with the License.
|
12932
|
+
* You may obtain a copy of the License at
|
12933
|
+
*
|
12934
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
12935
|
+
*
|
12936
|
+
* Unless required by applicable law or agreed to in writing, software
|
12937
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
12938
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12939
|
+
* See the License for the specific language governing permissions and
|
12940
|
+
* limitations under the License.
|
12941
|
+
*/
|
12942
|
+
const MAX_ITERATIONS = 10;
|
12943
|
+
const MIN_MOVEMENT_DISTANCE = 3.0;
|
12944
|
+
/**
|
12945
|
+
* An image quantizer that improves on the speed of a standard K-Means algorithm
|
12946
|
+
* by implementing several optimizations, including deduping identical pixels
|
12947
|
+
* and a triangle inequality rule that reduces the number of comparisons needed
|
12948
|
+
* to identify which cluster a point should be moved to.
|
12949
|
+
*
|
12950
|
+
* Wsmeans stands for Weighted Square Means.
|
12951
|
+
*
|
12952
|
+
* This algorithm was designed by M. Emre Celebi, and was found in their 2011
|
12953
|
+
* paper, Improving the Performance of K-Means for Color Quantization.
|
12954
|
+
* https://arxiv.org/abs/1101.0395
|
12955
|
+
*/
|
12956
|
+
// material_color_utilities is designed to have a consistent API across
|
12957
|
+
// platforms and modular components that can be moved around easily. Using a
|
12958
|
+
// class as a namespace facilitates this.
|
12959
|
+
//
|
12960
|
+
// tslint:disable-next-line:class-as-namespace
|
12961
|
+
class QuantizerWsmeans {
|
12962
|
+
/**
|
12963
|
+
* @param inputPixels Colors in ARGB format.
|
12964
|
+
* @param startingClusters Defines the initial state of the quantizer. Passing
|
12965
|
+
* an empty array is fine, the implementation will create its own initial
|
12966
|
+
* state that leads to reproducible results for the same inputs.
|
12967
|
+
* Passing an array that is the result of Wu quantization leads to higher
|
12968
|
+
* quality results.
|
12969
|
+
* @param maxColors The number of colors to divide the image into. A lower
|
12970
|
+
* number of colors may be returned.
|
12971
|
+
* @return Colors in ARGB format.
|
12972
|
+
*/
|
12973
|
+
static quantize(inputPixels, startingClusters, maxColors) {
|
12974
|
+
const pixelToCount = new Map();
|
12975
|
+
const points = new Array();
|
12976
|
+
const pixels = new Array();
|
12977
|
+
const pointProvider = new LabPointProvider();
|
12978
|
+
let pointCount = 0;
|
12979
|
+
for (let i = 0; i < inputPixels.length; i++) {
|
12980
|
+
const inputPixel = inputPixels[i];
|
12981
|
+
const pixelCount = pixelToCount.get(inputPixel);
|
12982
|
+
if (pixelCount === undefined) {
|
12983
|
+
pointCount++;
|
12984
|
+
points.push(pointProvider.fromInt(inputPixel));
|
12985
|
+
pixels.push(inputPixel);
|
12986
|
+
pixelToCount.set(inputPixel, 1);
|
12987
|
+
}
|
12988
|
+
else {
|
12989
|
+
pixelToCount.set(inputPixel, pixelCount + 1);
|
12990
|
+
}
|
12991
|
+
}
|
12992
|
+
const counts = new Array();
|
12993
|
+
for (let i = 0; i < pointCount; i++) {
|
12994
|
+
const pixel = pixels[i];
|
12995
|
+
const count = pixelToCount.get(pixel);
|
12996
|
+
if (count !== undefined) {
|
12997
|
+
counts[i] = count;
|
12998
|
+
}
|
12999
|
+
}
|
13000
|
+
let clusterCount = Math.min(maxColors, pointCount);
|
13001
|
+
if (startingClusters.length > 0) {
|
13002
|
+
clusterCount = Math.min(clusterCount, startingClusters.length);
|
13003
|
+
}
|
13004
|
+
const clusters = new Array();
|
13005
|
+
for (let i = 0; i < startingClusters.length; i++) {
|
13006
|
+
clusters.push(pointProvider.fromInt(startingClusters[i]));
|
13007
|
+
}
|
13008
|
+
const additionalClustersNeeded = clusterCount - clusters.length;
|
13009
|
+
if (startingClusters.length === 0 && additionalClustersNeeded > 0) {
|
13010
|
+
for (let i = 0; i < additionalClustersNeeded; i++) {
|
13011
|
+
const l = Math.random() * 100.0;
|
13012
|
+
const a = Math.random() * (100.0 - (-100.0) + 1) + -100;
|
13013
|
+
const b = Math.random() * (100.0 - (-100.0) + 1) + -100;
|
13014
|
+
clusters.push(new Array(l, a, b));
|
13015
|
+
}
|
13016
|
+
}
|
13017
|
+
const clusterIndices = new Array();
|
13018
|
+
for (let i = 0; i < pointCount; i++) {
|
13019
|
+
clusterIndices.push(Math.floor(Math.random() * clusterCount));
|
13020
|
+
}
|
13021
|
+
const indexMatrix = new Array();
|
13022
|
+
for (let i = 0; i < clusterCount; i++) {
|
13023
|
+
indexMatrix.push(new Array());
|
13024
|
+
for (let j = 0; j < clusterCount; j++) {
|
13025
|
+
indexMatrix[i].push(0);
|
13026
|
+
}
|
13027
|
+
}
|
13028
|
+
const distanceToIndexMatrix = new Array();
|
13029
|
+
for (let i = 0; i < clusterCount; i++) {
|
13030
|
+
distanceToIndexMatrix.push(new Array());
|
13031
|
+
for (let j = 0; j < clusterCount; j++) {
|
13032
|
+
distanceToIndexMatrix[i].push(new DistanceAndIndex());
|
13033
|
+
}
|
13034
|
+
}
|
13035
|
+
const pixelCountSums = new Array();
|
13036
|
+
for (let i = 0; i < clusterCount; i++) {
|
13037
|
+
pixelCountSums.push(0);
|
13038
|
+
}
|
13039
|
+
for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
13040
|
+
for (let i = 0; i < clusterCount; i++) {
|
13041
|
+
for (let j = i + 1; j < clusterCount; j++) {
|
13042
|
+
const distance = pointProvider.distance(clusters[i], clusters[j]);
|
13043
|
+
distanceToIndexMatrix[j][i].distance = distance;
|
13044
|
+
distanceToIndexMatrix[j][i].index = i;
|
13045
|
+
distanceToIndexMatrix[i][j].distance = distance;
|
13046
|
+
distanceToIndexMatrix[i][j].index = j;
|
13047
|
+
}
|
13048
|
+
distanceToIndexMatrix[i].sort();
|
13049
|
+
for (let j = 0; j < clusterCount; j++) {
|
13050
|
+
indexMatrix[i][j] = distanceToIndexMatrix[i][j].index;
|
13051
|
+
}
|
13052
|
+
}
|
13053
|
+
let pointsMoved = 0;
|
13054
|
+
for (let i = 0; i < pointCount; i++) {
|
13055
|
+
const point = points[i];
|
13056
|
+
const previousClusterIndex = clusterIndices[i];
|
13057
|
+
const previousCluster = clusters[previousClusterIndex];
|
13058
|
+
const previousDistance = pointProvider.distance(point, previousCluster);
|
13059
|
+
let minimumDistance = previousDistance;
|
13060
|
+
let newClusterIndex = -1;
|
13061
|
+
for (let j = 0; j < clusterCount; j++) {
|
13062
|
+
if (distanceToIndexMatrix[previousClusterIndex][j].distance >=
|
13063
|
+
4 * previousDistance) {
|
13064
|
+
continue;
|
13065
|
+
}
|
13066
|
+
const distance = pointProvider.distance(point, clusters[j]);
|
13067
|
+
if (distance < minimumDistance) {
|
13068
|
+
minimumDistance = distance;
|
13069
|
+
newClusterIndex = j;
|
13070
|
+
}
|
13071
|
+
}
|
13072
|
+
if (newClusterIndex !== -1) {
|
13073
|
+
const distanceChange = Math.abs((Math.sqrt(minimumDistance) - Math.sqrt(previousDistance)));
|
13074
|
+
if (distanceChange > MIN_MOVEMENT_DISTANCE) {
|
13075
|
+
pointsMoved++;
|
13076
|
+
clusterIndices[i] = newClusterIndex;
|
13077
|
+
}
|
13078
|
+
}
|
13079
|
+
}
|
13080
|
+
if (pointsMoved === 0 && iteration !== 0) {
|
13081
|
+
break;
|
13082
|
+
}
|
13083
|
+
const componentASums = new Array(clusterCount).fill(0);
|
13084
|
+
const componentBSums = new Array(clusterCount).fill(0);
|
13085
|
+
const componentCSums = new Array(clusterCount).fill(0);
|
13086
|
+
for (let i = 0; i < clusterCount; i++) {
|
13087
|
+
pixelCountSums[i] = 0;
|
13088
|
+
}
|
13089
|
+
for (let i = 0; i < pointCount; i++) {
|
13090
|
+
const clusterIndex = clusterIndices[i];
|
13091
|
+
const point = points[i];
|
13092
|
+
const count = counts[i];
|
13093
|
+
pixelCountSums[clusterIndex] += count;
|
13094
|
+
componentASums[clusterIndex] += (point[0] * count);
|
13095
|
+
componentBSums[clusterIndex] += (point[1] * count);
|
13096
|
+
componentCSums[clusterIndex] += (point[2] * count);
|
13097
|
+
}
|
13098
|
+
for (let i = 0; i < clusterCount; i++) {
|
13099
|
+
const count = pixelCountSums[i];
|
13100
|
+
if (count === 0) {
|
13101
|
+
clusters[i] = [0.0, 0.0, 0.0];
|
13102
|
+
continue;
|
13103
|
+
}
|
13104
|
+
const a = componentASums[i] / count;
|
13105
|
+
const b = componentBSums[i] / count;
|
13106
|
+
const c = componentCSums[i] / count;
|
13107
|
+
clusters[i] = [a, b, c];
|
13108
|
+
}
|
13109
|
+
}
|
13110
|
+
const argbToPopulation = new Map();
|
13111
|
+
for (let i = 0; i < clusterCount; i++) {
|
13112
|
+
const count = pixelCountSums[i];
|
13113
|
+
if (count === 0) {
|
13114
|
+
continue;
|
13115
|
+
}
|
13116
|
+
const possibleNewCluster = pointProvider.toInt(clusters[i]);
|
13117
|
+
if (argbToPopulation.has(possibleNewCluster)) {
|
13118
|
+
continue;
|
13119
|
+
}
|
13120
|
+
argbToPopulation.set(possibleNewCluster, count);
|
13121
|
+
}
|
13122
|
+
return argbToPopulation;
|
13123
|
+
}
|
13124
|
+
}
|
13125
|
+
/**
|
13126
|
+
* A wrapper for maintaining a table of distances between K-Means clusters.
|
13127
|
+
*/
|
13128
|
+
class DistanceAndIndex {
|
13129
|
+
constructor() {
|
13130
|
+
this.distance = -1;
|
13131
|
+
this.index = -1;
|
13132
|
+
}
|
13133
|
+
}
|
13134
|
+
|
13135
|
+
/**
|
13136
|
+
* @license
|
13137
|
+
* Copyright 2021 Google LLC
|
13138
|
+
*
|
13139
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
13140
|
+
* you may not use this file except in compliance with the License.
|
13141
|
+
* You may obtain a copy of the License at
|
13142
|
+
*
|
13143
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
13144
|
+
*
|
13145
|
+
* Unless required by applicable law or agreed to in writing, software
|
13146
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
13147
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13148
|
+
* See the License for the specific language governing permissions and
|
13149
|
+
* limitations under the License.
|
13150
|
+
*/
|
13151
|
+
/**
|
13152
|
+
* Quantizes an image into a map, with keys of ARGB colors, and values of the
|
13153
|
+
* number of times that color appears in the image.
|
13154
|
+
*/
|
13155
|
+
// material_color_utilities is designed to have a consistent API across
|
13156
|
+
// platforms and modular components that can be moved around easily. Using a
|
13157
|
+
// class as a namespace facilitates this.
|
13158
|
+
//
|
13159
|
+
// tslint:disable-next-line:class-as-namespace
|
13160
|
+
class QuantizerMap {
|
13161
|
+
/**
|
13162
|
+
* @param pixels Colors in ARGB format.
|
13163
|
+
* @return A Map with keys of ARGB colors, and values of the number of times
|
13164
|
+
* the color appears in the image.
|
13165
|
+
*/
|
13166
|
+
static quantize(pixels) {
|
13167
|
+
const countByColor = new Map();
|
13168
|
+
for (let i = 0; i < pixels.length; i++) {
|
13169
|
+
const pixel = pixels[i];
|
13170
|
+
const alpha = alphaFromArgb(pixel);
|
13171
|
+
if (alpha < 255) {
|
13172
|
+
continue;
|
13173
|
+
}
|
13174
|
+
countByColor.set(pixel, (countByColor.get(pixel) ?? 0) + 1);
|
13175
|
+
}
|
13176
|
+
return countByColor;
|
13177
|
+
}
|
13178
|
+
}
|
13179
|
+
|
13180
|
+
/**
|
13181
|
+
* @license
|
13182
|
+
* Copyright 2021 Google LLC
|
13183
|
+
*
|
13184
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
13185
|
+
* you may not use this file except in compliance with the License.
|
13186
|
+
* You may obtain a copy of the License at
|
13187
|
+
*
|
13188
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
13189
|
+
*
|
13190
|
+
* Unless required by applicable law or agreed to in writing, software
|
13191
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
13192
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13193
|
+
* See the License for the specific language governing permissions and
|
13194
|
+
* limitations under the License.
|
13195
|
+
*/
|
13196
|
+
const INDEX_BITS = 5;
|
13197
|
+
const SIDE_LENGTH = 33; // ((1 << INDEX_INDEX_BITS) + 1)
|
13198
|
+
const TOTAL_SIZE = 35937; // SIDE_LENGTH * SIDE_LENGTH * SIDE_LENGTH
|
13199
|
+
const directions = {
|
13200
|
+
RED: 'red',
|
13201
|
+
GREEN: 'green',
|
13202
|
+
BLUE: 'blue',
|
13203
|
+
};
|
13204
|
+
/**
|
13205
|
+
* An image quantizer that divides the image's pixels into clusters by
|
13206
|
+
* recursively cutting an RGB cube, based on the weight of pixels in each area
|
13207
|
+
* of the cube.
|
13208
|
+
*
|
13209
|
+
* The algorithm was described by Xiaolin Wu in Graphic Gems II, published in
|
13210
|
+
* 1991.
|
13211
|
+
*/
|
13212
|
+
class QuantizerWu {
|
13213
|
+
constructor(weights = [], momentsR = [], momentsG = [], momentsB = [], moments = [], cubes = []) {
|
13214
|
+
this.weights = weights;
|
13215
|
+
this.momentsR = momentsR;
|
13216
|
+
this.momentsG = momentsG;
|
13217
|
+
this.momentsB = momentsB;
|
13218
|
+
this.moments = moments;
|
13219
|
+
this.cubes = cubes;
|
13220
|
+
}
|
13221
|
+
/**
|
13222
|
+
* @param pixels Colors in ARGB format.
|
13223
|
+
* @param maxColors The number of colors to divide the image into. A lower
|
13224
|
+
* number of colors may be returned.
|
13225
|
+
* @return Colors in ARGB format.
|
13226
|
+
*/
|
13227
|
+
quantize(pixels, maxColors) {
|
13228
|
+
this.constructHistogram(pixels);
|
13229
|
+
this.computeMoments();
|
13230
|
+
const createBoxesResult = this.createBoxes(maxColors);
|
13231
|
+
const results = this.createResult(createBoxesResult.resultCount);
|
13232
|
+
return results;
|
13233
|
+
}
|
13234
|
+
constructHistogram(pixels) {
|
13235
|
+
this.weights = Array.from({ length: TOTAL_SIZE }).fill(0);
|
13236
|
+
this.momentsR = Array.from({ length: TOTAL_SIZE }).fill(0);
|
13237
|
+
this.momentsG = Array.from({ length: TOTAL_SIZE }).fill(0);
|
13238
|
+
this.momentsB = Array.from({ length: TOTAL_SIZE }).fill(0);
|
13239
|
+
this.moments = Array.from({ length: TOTAL_SIZE }).fill(0);
|
13240
|
+
const countByColor = QuantizerMap.quantize(pixels);
|
13241
|
+
for (const [pixel, count] of countByColor.entries()) {
|
13242
|
+
const red = redFromArgb(pixel);
|
13243
|
+
const green = greenFromArgb(pixel);
|
13244
|
+
const blue = blueFromArgb(pixel);
|
13245
|
+
const bitsToRemove = 8 - INDEX_BITS;
|
13246
|
+
const iR = (red >> bitsToRemove) + 1;
|
13247
|
+
const iG = (green >> bitsToRemove) + 1;
|
13248
|
+
const iB = (blue >> bitsToRemove) + 1;
|
13249
|
+
const index = this.getIndex(iR, iG, iB);
|
13250
|
+
this.weights[index] = (this.weights[index] ?? 0) + count;
|
13251
|
+
this.momentsR[index] += count * red;
|
13252
|
+
this.momentsG[index] += count * green;
|
13253
|
+
this.momentsB[index] += count * blue;
|
13254
|
+
this.moments[index] += count * (red * red + green * green + blue * blue);
|
13255
|
+
}
|
13256
|
+
}
|
13257
|
+
computeMoments() {
|
13258
|
+
for (let r = 1; r < SIDE_LENGTH; r++) {
|
13259
|
+
const area = Array.from({ length: SIDE_LENGTH }).fill(0);
|
13260
|
+
const areaR = Array.from({ length: SIDE_LENGTH }).fill(0);
|
13261
|
+
const areaG = Array.from({ length: SIDE_LENGTH }).fill(0);
|
13262
|
+
const areaB = Array.from({ length: SIDE_LENGTH }).fill(0);
|
13263
|
+
const area2 = Array.from({ length: SIDE_LENGTH }).fill(0.0);
|
13264
|
+
for (let g = 1; g < SIDE_LENGTH; g++) {
|
13265
|
+
let line = 0;
|
13266
|
+
let lineR = 0;
|
13267
|
+
let lineG = 0;
|
13268
|
+
let lineB = 0;
|
13269
|
+
let line2 = 0.0;
|
13270
|
+
for (let b = 1; b < SIDE_LENGTH; b++) {
|
13271
|
+
const index = this.getIndex(r, g, b);
|
13272
|
+
line += this.weights[index];
|
13273
|
+
lineR += this.momentsR[index];
|
13274
|
+
lineG += this.momentsG[index];
|
13275
|
+
lineB += this.momentsB[index];
|
13276
|
+
line2 += this.moments[index];
|
13277
|
+
area[b] += line;
|
13278
|
+
areaR[b] += lineR;
|
13279
|
+
areaG[b] += lineG;
|
13280
|
+
areaB[b] += lineB;
|
13281
|
+
area2[b] += line2;
|
13282
|
+
const previousIndex = this.getIndex(r - 1, g, b);
|
13283
|
+
this.weights[index] = this.weights[previousIndex] + area[b];
|
13284
|
+
this.momentsR[index] = this.momentsR[previousIndex] + areaR[b];
|
13285
|
+
this.momentsG[index] = this.momentsG[previousIndex] + areaG[b];
|
13286
|
+
this.momentsB[index] = this.momentsB[previousIndex] + areaB[b];
|
13287
|
+
this.moments[index] = this.moments[previousIndex] + area2[b];
|
13288
|
+
}
|
13289
|
+
}
|
13290
|
+
}
|
13291
|
+
}
|
13292
|
+
createBoxes(maxColors) {
|
13293
|
+
this.cubes =
|
13294
|
+
Array.from({ length: maxColors }).fill(0).map(() => new Box());
|
13295
|
+
const volumeVariance = Array.from({ length: maxColors }).fill(0.0);
|
13296
|
+
this.cubes[0].r0 = 0;
|
13297
|
+
this.cubes[0].g0 = 0;
|
13298
|
+
this.cubes[0].b0 = 0;
|
13299
|
+
this.cubes[0].r1 = SIDE_LENGTH - 1;
|
13300
|
+
this.cubes[0].g1 = SIDE_LENGTH - 1;
|
13301
|
+
this.cubes[0].b1 = SIDE_LENGTH - 1;
|
13302
|
+
let generatedColorCount = maxColors;
|
13303
|
+
let next = 0;
|
13304
|
+
for (let i = 1; i < maxColors; i++) {
|
13305
|
+
if (this.cut(this.cubes[next], this.cubes[i])) {
|
13306
|
+
volumeVariance[next] =
|
13307
|
+
this.cubes[next].vol > 1 ? this.variance(this.cubes[next]) : 0.0;
|
13308
|
+
volumeVariance[i] =
|
13309
|
+
this.cubes[i].vol > 1 ? this.variance(this.cubes[i]) : 0.0;
|
13310
|
+
}
|
13311
|
+
else {
|
13312
|
+
volumeVariance[next] = 0.0;
|
13313
|
+
i--;
|
13314
|
+
}
|
13315
|
+
next = 0;
|
13316
|
+
let temp = volumeVariance[0];
|
13317
|
+
for (let j = 1; j <= i; j++) {
|
13318
|
+
if (volumeVariance[j] > temp) {
|
13319
|
+
temp = volumeVariance[j];
|
13320
|
+
next = j;
|
13321
|
+
}
|
13322
|
+
}
|
13323
|
+
if (temp <= 0.0) {
|
13324
|
+
generatedColorCount = i + 1;
|
13325
|
+
break;
|
13326
|
+
}
|
13327
|
+
}
|
13328
|
+
return new CreateBoxesResult(maxColors, generatedColorCount);
|
13329
|
+
}
|
13330
|
+
createResult(colorCount) {
|
13331
|
+
const colors = [];
|
13332
|
+
for (let i = 0; i < colorCount; ++i) {
|
13333
|
+
const cube = this.cubes[i];
|
13334
|
+
const weight = this.volume(cube, this.weights);
|
13335
|
+
if (weight > 0) {
|
13336
|
+
const r = Math.round(this.volume(cube, this.momentsR) / weight);
|
13337
|
+
const g = Math.round(this.volume(cube, this.momentsG) / weight);
|
13338
|
+
const b = Math.round(this.volume(cube, this.momentsB) / weight);
|
13339
|
+
const color = (255 << 24) | ((r & 0x0ff) << 16) | ((g & 0x0ff) << 8) |
|
13340
|
+
(b & 0x0ff);
|
13341
|
+
colors.push(color);
|
13342
|
+
}
|
13343
|
+
}
|
13344
|
+
return colors;
|
13345
|
+
}
|
13346
|
+
variance(cube) {
|
13347
|
+
const dr = this.volume(cube, this.momentsR);
|
13348
|
+
const dg = this.volume(cube, this.momentsG);
|
13349
|
+
const db = this.volume(cube, this.momentsB);
|
13350
|
+
const xx = this.moments[this.getIndex(cube.r1, cube.g1, cube.b1)] -
|
13351
|
+
this.moments[this.getIndex(cube.r1, cube.g1, cube.b0)] -
|
13352
|
+
this.moments[this.getIndex(cube.r1, cube.g0, cube.b1)] +
|
13353
|
+
this.moments[this.getIndex(cube.r1, cube.g0, cube.b0)] -
|
13354
|
+
this.moments[this.getIndex(cube.r0, cube.g1, cube.b1)] +
|
13355
|
+
this.moments[this.getIndex(cube.r0, cube.g1, cube.b0)] +
|
13356
|
+
this.moments[this.getIndex(cube.r0, cube.g0, cube.b1)] -
|
13357
|
+
this.moments[this.getIndex(cube.r0, cube.g0, cube.b0)];
|
13358
|
+
const hypotenuse = dr * dr + dg * dg + db * db;
|
13359
|
+
const volume = this.volume(cube, this.weights);
|
13360
|
+
return xx - hypotenuse / volume;
|
13361
|
+
}
|
13362
|
+
cut(one, two) {
|
13363
|
+
const wholeR = this.volume(one, this.momentsR);
|
13364
|
+
const wholeG = this.volume(one, this.momentsG);
|
13365
|
+
const wholeB = this.volume(one, this.momentsB);
|
13366
|
+
const wholeW = this.volume(one, this.weights);
|
13367
|
+
const maxRResult = this.maximize(one, directions.RED, one.r0 + 1, one.r1, wholeR, wholeG, wholeB, wholeW);
|
13368
|
+
const maxGResult = this.maximize(one, directions.GREEN, one.g0 + 1, one.g1, wholeR, wholeG, wholeB, wholeW);
|
13369
|
+
const maxBResult = this.maximize(one, directions.BLUE, one.b0 + 1, one.b1, wholeR, wholeG, wholeB, wholeW);
|
13370
|
+
let direction;
|
13371
|
+
const maxR = maxRResult.maximum;
|
13372
|
+
const maxG = maxGResult.maximum;
|
13373
|
+
const maxB = maxBResult.maximum;
|
13374
|
+
if (maxR >= maxG && maxR >= maxB) {
|
13375
|
+
if (maxRResult.cutLocation < 0) {
|
13376
|
+
return false;
|
13377
|
+
}
|
13378
|
+
direction = directions.RED;
|
13379
|
+
}
|
13380
|
+
else if (maxG >= maxR && maxG >= maxB) {
|
13381
|
+
direction = directions.GREEN;
|
13382
|
+
}
|
13383
|
+
else {
|
13384
|
+
direction = directions.BLUE;
|
13385
|
+
}
|
13386
|
+
two.r1 = one.r1;
|
13387
|
+
two.g1 = one.g1;
|
13388
|
+
two.b1 = one.b1;
|
13389
|
+
switch (direction) {
|
13390
|
+
case directions.RED:
|
13391
|
+
one.r1 = maxRResult.cutLocation;
|
13392
|
+
two.r0 = one.r1;
|
13393
|
+
two.g0 = one.g0;
|
13394
|
+
two.b0 = one.b0;
|
13395
|
+
break;
|
13396
|
+
case directions.GREEN:
|
13397
|
+
one.g1 = maxGResult.cutLocation;
|
13398
|
+
two.r0 = one.r0;
|
13399
|
+
two.g0 = one.g1;
|
13400
|
+
two.b0 = one.b0;
|
13401
|
+
break;
|
13402
|
+
case directions.BLUE:
|
13403
|
+
one.b1 = maxBResult.cutLocation;
|
13404
|
+
two.r0 = one.r0;
|
13405
|
+
two.g0 = one.g0;
|
13406
|
+
two.b0 = one.b1;
|
13407
|
+
break;
|
13408
|
+
default:
|
13409
|
+
throw new Error('unexpected direction ' + direction);
|
13410
|
+
}
|
13411
|
+
one.vol = (one.r1 - one.r0) * (one.g1 - one.g0) * (one.b1 - one.b0);
|
13412
|
+
two.vol = (two.r1 - two.r0) * (two.g1 - two.g0) * (two.b1 - two.b0);
|
13413
|
+
return true;
|
13414
|
+
}
|
13415
|
+
maximize(cube, direction, first, last, wholeR, wholeG, wholeB, wholeW) {
|
13416
|
+
const bottomR = this.bottom(cube, direction, this.momentsR);
|
13417
|
+
const bottomG = this.bottom(cube, direction, this.momentsG);
|
13418
|
+
const bottomB = this.bottom(cube, direction, this.momentsB);
|
13419
|
+
const bottomW = this.bottom(cube, direction, this.weights);
|
13420
|
+
let max = 0.0;
|
13421
|
+
let cut = -1;
|
13422
|
+
let halfR = 0;
|
13423
|
+
let halfG = 0;
|
13424
|
+
let halfB = 0;
|
13425
|
+
let halfW = 0;
|
13426
|
+
for (let i = first; i < last; i++) {
|
13427
|
+
halfR = bottomR + this.top(cube, direction, i, this.momentsR);
|
13428
|
+
halfG = bottomG + this.top(cube, direction, i, this.momentsG);
|
13429
|
+
halfB = bottomB + this.top(cube, direction, i, this.momentsB);
|
13430
|
+
halfW = bottomW + this.top(cube, direction, i, this.weights);
|
13431
|
+
if (halfW === 0) {
|
13432
|
+
continue;
|
13433
|
+
}
|
13434
|
+
let tempNumerator = (halfR * halfR + halfG * halfG + halfB * halfB) * 1.0;
|
13435
|
+
let tempDenominator = halfW * 1.0;
|
13436
|
+
let temp = tempNumerator / tempDenominator;
|
13437
|
+
halfR = wholeR - halfR;
|
13438
|
+
halfG = wholeG - halfG;
|
13439
|
+
halfB = wholeB - halfB;
|
13440
|
+
halfW = wholeW - halfW;
|
13441
|
+
if (halfW === 0) {
|
13442
|
+
continue;
|
13443
|
+
}
|
13444
|
+
tempNumerator = (halfR * halfR + halfG * halfG + halfB * halfB) * 1.0;
|
13445
|
+
tempDenominator = halfW * 1.0;
|
13446
|
+
temp += tempNumerator / tempDenominator;
|
13447
|
+
if (temp > max) {
|
13448
|
+
max = temp;
|
13449
|
+
cut = i;
|
13450
|
+
}
|
13451
|
+
}
|
13452
|
+
return new MaximizeResult(cut, max);
|
13453
|
+
}
|
13454
|
+
volume(cube, moment) {
|
13455
|
+
return (moment[this.getIndex(cube.r1, cube.g1, cube.b1)] -
|
13456
|
+
moment[this.getIndex(cube.r1, cube.g1, cube.b0)] -
|
13457
|
+
moment[this.getIndex(cube.r1, cube.g0, cube.b1)] +
|
13458
|
+
moment[this.getIndex(cube.r1, cube.g0, cube.b0)] -
|
13459
|
+
moment[this.getIndex(cube.r0, cube.g1, cube.b1)] +
|
13460
|
+
moment[this.getIndex(cube.r0, cube.g1, cube.b0)] +
|
13461
|
+
moment[this.getIndex(cube.r0, cube.g0, cube.b1)] -
|
13462
|
+
moment[this.getIndex(cube.r0, cube.g0, cube.b0)]);
|
13463
|
+
}
|
13464
|
+
bottom(cube, direction, moment) {
|
13465
|
+
switch (direction) {
|
13466
|
+
case directions.RED:
|
13467
|
+
return (-moment[this.getIndex(cube.r0, cube.g1, cube.b1)] +
|
13468
|
+
moment[this.getIndex(cube.r0, cube.g1, cube.b0)] +
|
13469
|
+
moment[this.getIndex(cube.r0, cube.g0, cube.b1)] -
|
13470
|
+
moment[this.getIndex(cube.r0, cube.g0, cube.b0)]);
|
13471
|
+
case directions.GREEN:
|
13472
|
+
return (-moment[this.getIndex(cube.r1, cube.g0, cube.b1)] +
|
13473
|
+
moment[this.getIndex(cube.r1, cube.g0, cube.b0)] +
|
13474
|
+
moment[this.getIndex(cube.r0, cube.g0, cube.b1)] -
|
13475
|
+
moment[this.getIndex(cube.r0, cube.g0, cube.b0)]);
|
13476
|
+
case directions.BLUE:
|
13477
|
+
return (-moment[this.getIndex(cube.r1, cube.g1, cube.b0)] +
|
13478
|
+
moment[this.getIndex(cube.r1, cube.g0, cube.b0)] +
|
13479
|
+
moment[this.getIndex(cube.r0, cube.g1, cube.b0)] -
|
13480
|
+
moment[this.getIndex(cube.r0, cube.g0, cube.b0)]);
|
13481
|
+
default:
|
13482
|
+
throw new Error('unexpected direction $direction');
|
13483
|
+
}
|
13484
|
+
}
|
13485
|
+
top(cube, direction, position, moment) {
|
13486
|
+
switch (direction) {
|
13487
|
+
case directions.RED:
|
13488
|
+
return (moment[this.getIndex(position, cube.g1, cube.b1)] -
|
13489
|
+
moment[this.getIndex(position, cube.g1, cube.b0)] -
|
13490
|
+
moment[this.getIndex(position, cube.g0, cube.b1)] +
|
13491
|
+
moment[this.getIndex(position, cube.g0, cube.b0)]);
|
13492
|
+
case directions.GREEN:
|
13493
|
+
return (moment[this.getIndex(cube.r1, position, cube.b1)] -
|
13494
|
+
moment[this.getIndex(cube.r1, position, cube.b0)] -
|
13495
|
+
moment[this.getIndex(cube.r0, position, cube.b1)] +
|
13496
|
+
moment[this.getIndex(cube.r0, position, cube.b0)]);
|
13497
|
+
case directions.BLUE:
|
13498
|
+
return (moment[this.getIndex(cube.r1, cube.g1, position)] -
|
13499
|
+
moment[this.getIndex(cube.r1, cube.g0, position)] -
|
13500
|
+
moment[this.getIndex(cube.r0, cube.g1, position)] +
|
13501
|
+
moment[this.getIndex(cube.r0, cube.g0, position)]);
|
13502
|
+
default:
|
13503
|
+
throw new Error('unexpected direction $direction');
|
13504
|
+
}
|
13505
|
+
}
|
13506
|
+
getIndex(r, g, b) {
|
13507
|
+
return (r << (INDEX_BITS * 2)) + (r << (INDEX_BITS + 1)) + r +
|
13508
|
+
(g << INDEX_BITS) + g + b;
|
13509
|
+
}
|
13510
|
+
}
|
13511
|
+
/**
|
13512
|
+
* Keeps track of the state of each box created as the Wu quantization
|
13513
|
+
* algorithm progresses through dividing the image's pixels as plotted in RGB.
|
13514
|
+
*/
|
13515
|
+
class Box {
|
13516
|
+
constructor(r0 = 0, r1 = 0, g0 = 0, g1 = 0, b0 = 0, b1 = 0, vol = 0) {
|
13517
|
+
this.r0 = r0;
|
13518
|
+
this.r1 = r1;
|
13519
|
+
this.g0 = g0;
|
13520
|
+
this.g1 = g1;
|
13521
|
+
this.b0 = b0;
|
13522
|
+
this.b1 = b1;
|
13523
|
+
this.vol = vol;
|
13524
|
+
}
|
13525
|
+
}
|
13526
|
+
/**
|
13527
|
+
* Represents final result of Wu algorithm.
|
13528
|
+
*/
|
13529
|
+
class CreateBoxesResult {
|
13530
|
+
/**
|
13531
|
+
* @param requestedCount how many colors the caller asked to be returned from
|
13532
|
+
* quantization.
|
13533
|
+
* @param resultCount the actual number of colors achieved from quantization.
|
13534
|
+
* May be lower than the requested count.
|
13535
|
+
*/
|
13536
|
+
constructor(requestedCount, resultCount) {
|
13537
|
+
this.requestedCount = requestedCount;
|
13538
|
+
this.resultCount = resultCount;
|
13539
|
+
}
|
13540
|
+
}
|
13541
|
+
/**
|
13542
|
+
* Represents the result of calculating where to cut an existing box in such
|
13543
|
+
* a way to maximize variance between the two new boxes created by a cut.
|
13544
|
+
*/
|
13545
|
+
class MaximizeResult {
|
13546
|
+
constructor(cutLocation, maximum) {
|
13547
|
+
this.cutLocation = cutLocation;
|
13548
|
+
this.maximum = maximum;
|
13549
|
+
}
|
13550
|
+
}
|
13551
|
+
|
13552
|
+
/**
|
13553
|
+
* @license
|
13554
|
+
* Copyright 2021 Google LLC
|
13555
|
+
*
|
13556
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
13557
|
+
* you may not use this file except in compliance with the License.
|
13558
|
+
* You may obtain a copy of the License at
|
13559
|
+
*
|
13560
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
13561
|
+
*
|
13562
|
+
* Unless required by applicable law or agreed to in writing, software
|
13563
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
13564
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13565
|
+
* See the License for the specific language governing permissions and
|
13566
|
+
* limitations under the License.
|
13567
|
+
*/
|
13568
|
+
/**
|
13569
|
+
* An image quantizer that improves on the quality of a standard K-Means
|
13570
|
+
* algorithm by setting the K-Means initial state to the output of a Wu
|
13571
|
+
* quantizer, instead of random centroids. Improves on speed by several
|
13572
|
+
* optimizations, as implemented in Wsmeans, or Weighted Square Means, K-Means
|
13573
|
+
* with those optimizations.
|
13574
|
+
*
|
13575
|
+
* This algorithm was designed by M. Emre Celebi, and was found in their 2011
|
13576
|
+
* paper, Improving the Performance of K-Means for Color Quantization.
|
13577
|
+
* https://arxiv.org/abs/1101.0395
|
13578
|
+
*/
|
13579
|
+
// material_color_utilities is designed to have a consistent API across
|
13580
|
+
// platforms and modular components that can be moved around easily. Using a
|
13581
|
+
// class as a namespace facilitates this.
|
13582
|
+
//
|
13583
|
+
// tslint:disable-next-line:class-as-namespace
|
13584
|
+
class QuantizerCelebi {
|
13585
|
+
/**
|
13586
|
+
* @param pixels Colors in ARGB format.
|
13587
|
+
* @param maxColors The number of colors to divide the image into. A lower
|
13588
|
+
* number of colors may be returned.
|
13589
|
+
* @return Map with keys of colors in ARGB format, and values of number of
|
13590
|
+
* pixels in the original image that correspond to the color in the
|
13591
|
+
* quantized image.
|
13592
|
+
*/
|
13593
|
+
static quantize(pixels, maxColors) {
|
13594
|
+
const wu = new QuantizerWu();
|
13595
|
+
const wuResult = wu.quantize(pixels, maxColors);
|
13596
|
+
return QuantizerWsmeans.quantize(pixels, wuResult, maxColors);
|
13597
|
+
}
|
13598
|
+
}
|
13599
|
+
|
13600
|
+
/**
|
13601
|
+
* @license
|
13602
|
+
* Copyright 2021 Google LLC
|
13603
|
+
*
|
13604
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
13605
|
+
* you may not use this file except in compliance with the License.
|
13606
|
+
* You may obtain a copy of the License at
|
13607
|
+
*
|
13608
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
13609
|
+
*
|
13610
|
+
* Unless required by applicable law or agreed to in writing, software
|
13611
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
13612
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13613
|
+
* See the License for the specific language governing permissions and
|
13614
|
+
* limitations under the License.
|
13615
|
+
*/
|
13616
|
+
/**
|
13617
|
+
* Represents a Material color scheme, a mapping of color roles to colors.
|
13618
|
+
*/
|
13619
|
+
class Scheme {
|
13620
|
+
get primary() {
|
13621
|
+
return this.props.primary;
|
13622
|
+
}
|
13623
|
+
get onPrimary() {
|
13624
|
+
return this.props.onPrimary;
|
13625
|
+
}
|
13626
|
+
get primaryContainer() {
|
13627
|
+
return this.props.primaryContainer;
|
13628
|
+
}
|
13629
|
+
get onPrimaryContainer() {
|
13630
|
+
return this.props.onPrimaryContainer;
|
13631
|
+
}
|
13632
|
+
get secondary() {
|
13633
|
+
return this.props.secondary;
|
13634
|
+
}
|
13635
|
+
get onSecondary() {
|
13636
|
+
return this.props.onSecondary;
|
13637
|
+
}
|
13638
|
+
get secondaryContainer() {
|
13639
|
+
return this.props.secondaryContainer;
|
13640
|
+
}
|
13641
|
+
get onSecondaryContainer() {
|
13642
|
+
return this.props.onSecondaryContainer;
|
13643
|
+
}
|
13644
|
+
get tertiary() {
|
13645
|
+
return this.props.tertiary;
|
13646
|
+
}
|
13647
|
+
get onTertiary() {
|
13648
|
+
return this.props.onTertiary;
|
13649
|
+
}
|
13650
|
+
get tertiaryContainer() {
|
13651
|
+
return this.props.tertiaryContainer;
|
13652
|
+
}
|
13653
|
+
get onTertiaryContainer() {
|
13654
|
+
return this.props.onTertiaryContainer;
|
13655
|
+
}
|
13656
|
+
get error() {
|
13657
|
+
return this.props.error;
|
13658
|
+
}
|
13659
|
+
get onError() {
|
13660
|
+
return this.props.onError;
|
13661
|
+
}
|
13662
|
+
get errorContainer() {
|
13663
|
+
return this.props.errorContainer;
|
13664
|
+
}
|
13665
|
+
get onErrorContainer() {
|
13666
|
+
return this.props.onErrorContainer;
|
13667
|
+
}
|
13668
|
+
get background() {
|
13669
|
+
return this.props.background;
|
13670
|
+
}
|
13671
|
+
get onBackground() {
|
13672
|
+
return this.props.onBackground;
|
13673
|
+
}
|
13674
|
+
get surface() {
|
13675
|
+
return this.props.surface;
|
13676
|
+
}
|
13677
|
+
get onSurface() {
|
13678
|
+
return this.props.onSurface;
|
13679
|
+
}
|
13680
|
+
get surfaceVariant() {
|
13681
|
+
return this.props.surfaceVariant;
|
13682
|
+
}
|
13683
|
+
get onSurfaceVariant() {
|
13684
|
+
return this.props.onSurfaceVariant;
|
13685
|
+
}
|
13686
|
+
get outline() {
|
13687
|
+
return this.props.outline;
|
13688
|
+
}
|
13689
|
+
get outlineVariant() {
|
13690
|
+
return this.props.outlineVariant;
|
13691
|
+
}
|
13692
|
+
get shadow() {
|
13693
|
+
return this.props.shadow;
|
13694
|
+
}
|
13695
|
+
get scrim() {
|
13696
|
+
return this.props.scrim;
|
13697
|
+
}
|
13698
|
+
get inverseSurface() {
|
13699
|
+
return this.props.inverseSurface;
|
13700
|
+
}
|
13701
|
+
get inverseOnSurface() {
|
13702
|
+
return this.props.inverseOnSurface;
|
13703
|
+
}
|
13704
|
+
get inversePrimary() {
|
13705
|
+
return this.props.inversePrimary;
|
13706
|
+
}
|
13707
|
+
/**
|
13708
|
+
* @param argb ARGB representation of a color.
|
13709
|
+
* @return Light Material color scheme, based on the color's hue.
|
13710
|
+
*/
|
13711
|
+
static light(argb) {
|
13712
|
+
return Scheme.lightFromCorePalette(CorePalette.of(argb));
|
13713
|
+
}
|
13714
|
+
/**
|
13715
|
+
* @param argb ARGB representation of a color.
|
13716
|
+
* @return Dark Material color scheme, based on the color's hue.
|
13717
|
+
*/
|
13718
|
+
static dark(argb) {
|
13719
|
+
return Scheme.darkFromCorePalette(CorePalette.of(argb));
|
13720
|
+
}
|
13721
|
+
/**
|
13722
|
+
* @param argb ARGB representation of a color.
|
13723
|
+
* @return Light Material content color scheme, based on the color's hue.
|
13724
|
+
*/
|
13725
|
+
static lightContent(argb) {
|
13726
|
+
return Scheme.lightFromCorePalette(CorePalette.contentOf(argb));
|
13727
|
+
}
|
13728
|
+
/**
|
13729
|
+
* @param argb ARGB representation of a color.
|
13730
|
+
* @return Dark Material content color scheme, based on the color's hue.
|
13731
|
+
*/
|
13732
|
+
static darkContent(argb) {
|
13733
|
+
return Scheme.darkFromCorePalette(CorePalette.contentOf(argb));
|
13734
|
+
}
|
13735
|
+
/**
|
13736
|
+
* Light scheme from core palette
|
13737
|
+
*/
|
13738
|
+
static lightFromCorePalette(core) {
|
13739
|
+
return new Scheme({
|
13740
|
+
primary: core.a1.tone(40),
|
13741
|
+
onPrimary: core.a1.tone(100),
|
13742
|
+
primaryContainer: core.a1.tone(90),
|
13743
|
+
onPrimaryContainer: core.a1.tone(10),
|
13744
|
+
secondary: core.a2.tone(40),
|
13745
|
+
onSecondary: core.a2.tone(100),
|
13746
|
+
secondaryContainer: core.a2.tone(90),
|
13747
|
+
onSecondaryContainer: core.a2.tone(10),
|
13748
|
+
tertiary: core.a3.tone(40),
|
13749
|
+
onTertiary: core.a3.tone(100),
|
13750
|
+
tertiaryContainer: core.a3.tone(90),
|
13751
|
+
onTertiaryContainer: core.a3.tone(10),
|
13752
|
+
error: core.error.tone(40),
|
13753
|
+
onError: core.error.tone(100),
|
13754
|
+
errorContainer: core.error.tone(90),
|
13755
|
+
onErrorContainer: core.error.tone(10),
|
13756
|
+
background: core.n1.tone(99),
|
13757
|
+
onBackground: core.n1.tone(10),
|
13758
|
+
surface: core.n1.tone(99),
|
13759
|
+
onSurface: core.n1.tone(10),
|
13760
|
+
surfaceVariant: core.n2.tone(90),
|
13761
|
+
onSurfaceVariant: core.n2.tone(30),
|
13762
|
+
outline: core.n2.tone(50),
|
13763
|
+
outlineVariant: core.n2.tone(80),
|
13764
|
+
shadow: core.n1.tone(0),
|
13765
|
+
scrim: core.n1.tone(0),
|
13766
|
+
inverseSurface: core.n1.tone(20),
|
13767
|
+
inverseOnSurface: core.n1.tone(95),
|
13768
|
+
inversePrimary: core.a1.tone(80)
|
13769
|
+
});
|
13770
|
+
}
|
13771
|
+
/**
|
13772
|
+
* Dark scheme from core palette
|
13773
|
+
*/
|
13774
|
+
static darkFromCorePalette(core) {
|
13775
|
+
return new Scheme({
|
13776
|
+
primary: core.a1.tone(80),
|
13777
|
+
onPrimary: core.a1.tone(20),
|
13778
|
+
primaryContainer: core.a1.tone(30),
|
13779
|
+
onPrimaryContainer: core.a1.tone(90),
|
13780
|
+
secondary: core.a2.tone(80),
|
13781
|
+
onSecondary: core.a2.tone(20),
|
13782
|
+
secondaryContainer: core.a2.tone(30),
|
13783
|
+
onSecondaryContainer: core.a2.tone(90),
|
13784
|
+
tertiary: core.a3.tone(80),
|
13785
|
+
onTertiary: core.a3.tone(20),
|
13786
|
+
tertiaryContainer: core.a3.tone(30),
|
13787
|
+
onTertiaryContainer: core.a3.tone(90),
|
13788
|
+
error: core.error.tone(80),
|
13789
|
+
onError: core.error.tone(20),
|
13790
|
+
errorContainer: core.error.tone(30),
|
13791
|
+
onErrorContainer: core.error.tone(80),
|
13792
|
+
background: core.n1.tone(10),
|
13793
|
+
onBackground: core.n1.tone(90),
|
13794
|
+
surface: core.n1.tone(10),
|
13795
|
+
onSurface: core.n1.tone(90),
|
13796
|
+
surfaceVariant: core.n2.tone(30),
|
13797
|
+
onSurfaceVariant: core.n2.tone(80),
|
13798
|
+
outline: core.n2.tone(60),
|
13799
|
+
outlineVariant: core.n2.tone(30),
|
13800
|
+
shadow: core.n1.tone(0),
|
13801
|
+
scrim: core.n1.tone(0),
|
13802
|
+
inverseSurface: core.n1.tone(90),
|
13803
|
+
inverseOnSurface: core.n1.tone(20),
|
13804
|
+
inversePrimary: core.a1.tone(40)
|
13805
|
+
});
|
13806
|
+
}
|
13807
|
+
constructor(props) {
|
13808
|
+
this.props = props;
|
13809
|
+
}
|
13810
|
+
toJSON() {
|
13811
|
+
return {
|
13812
|
+
...this.props
|
13813
|
+
};
|
13814
|
+
}
|
13815
|
+
}
|
13816
|
+
|
13817
|
+
/**
|
13818
|
+
* @license
|
13819
|
+
* Copyright 2021 Google LLC
|
13820
|
+
*
|
13821
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
13822
|
+
* you may not use this file except in compliance with the License.
|
13823
|
+
* You may obtain a copy of the License at
|
13824
|
+
*
|
13825
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
13826
|
+
*
|
13827
|
+
* Unless required by applicable law or agreed to in writing, software
|
13828
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
13829
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13830
|
+
* See the License for the specific language governing permissions and
|
13831
|
+
* limitations under the License.
|
13832
|
+
*/
|
13833
|
+
const SCORE_OPTION_DEFAULTS = {
|
13834
|
+
desired: 4,
|
13835
|
+
fallbackColorARGB: 0xff4285f4,
|
13836
|
+
filter: true, // Avoid unsuitable colors.
|
13837
|
+
};
|
13838
|
+
function compare(a, b) {
|
13839
|
+
if (a.score > b.score) {
|
13840
|
+
return -1;
|
13841
|
+
}
|
13842
|
+
else if (a.score < b.score) {
|
13843
|
+
return 1;
|
13844
|
+
}
|
13845
|
+
return 0;
|
13846
|
+
}
|
13847
|
+
/**
|
13848
|
+
* Given a large set of colors, remove colors that are unsuitable for a UI
|
13849
|
+
* theme, and rank the rest based on suitability.
|
13850
|
+
*
|
13851
|
+
* Enables use of a high cluster count for image quantization, thus ensuring
|
13852
|
+
* colors aren't muddied, while curating the high cluster count to a much
|
13853
|
+
* smaller number of appropriate choices.
|
13854
|
+
*/
|
13855
|
+
class Score {
|
13856
|
+
constructor() { }
|
13857
|
+
/**
|
13858
|
+
* Given a map with keys of colors and values of how often the color appears,
|
13859
|
+
* rank the colors based on suitability for being used for a UI theme.
|
13860
|
+
*
|
13861
|
+
* @param colorsToPopulation map with keys of colors and values of how often
|
13862
|
+
* the color appears, usually from a source image.
|
13863
|
+
* @param {ScoreOptions} options optional parameters.
|
13864
|
+
* @return Colors sorted by suitability for a UI theme. The most suitable
|
13865
|
+
* color is the first item, the least suitable is the last. There will
|
13866
|
+
* always be at least one color returned. If all the input colors
|
13867
|
+
* were not suitable for a theme, a default fallback color will be
|
13868
|
+
* provided, Google Blue.
|
13869
|
+
*/
|
13870
|
+
static score(colorsToPopulation, options) {
|
13871
|
+
const { desired, fallbackColorARGB, filter } = { ...SCORE_OPTION_DEFAULTS, ...options };
|
13872
|
+
// Get the HCT color for each Argb value, while finding the per hue count and
|
13873
|
+
// total count.
|
13874
|
+
const colorsHct = [];
|
13875
|
+
const huePopulation = new Array(360).fill(0);
|
13876
|
+
let populationSum = 0;
|
13877
|
+
for (const [argb, population] of colorsToPopulation.entries()) {
|
13878
|
+
const hct = Hct.fromInt(argb);
|
13879
|
+
colorsHct.push(hct);
|
13880
|
+
const hue = Math.floor(hct.hue);
|
13881
|
+
huePopulation[hue] += population;
|
13882
|
+
populationSum += population;
|
13883
|
+
}
|
13884
|
+
// Hues with more usage in neighboring 30 degree slice get a larger number.
|
13885
|
+
const hueExcitedProportions = new Array(360).fill(0.0);
|
13886
|
+
for (let hue = 0; hue < 360; hue++) {
|
13887
|
+
const proportion = huePopulation[hue] / populationSum;
|
13888
|
+
for (let i = hue - 14; i < hue + 16; i++) {
|
13889
|
+
const neighborHue = sanitizeDegreesInt(i);
|
13890
|
+
hueExcitedProportions[neighborHue] += proportion;
|
13891
|
+
}
|
13892
|
+
}
|
13893
|
+
// Scores each HCT color based on usage and chroma, while optionally
|
13894
|
+
// filtering out values that do not have enough chroma or usage.
|
13895
|
+
const scoredHct = new Array();
|
13896
|
+
for (const hct of colorsHct) {
|
13897
|
+
const hue = sanitizeDegreesInt(Math.round(hct.hue));
|
13898
|
+
const proportion = hueExcitedProportions[hue];
|
13899
|
+
if (filter && (hct.chroma < Score.CUTOFF_CHROMA || proportion <= Score.CUTOFF_EXCITED_PROPORTION)) {
|
13900
|
+
continue;
|
13901
|
+
}
|
13902
|
+
const proportionScore = proportion * 100.0 * Score.WEIGHT_PROPORTION;
|
13903
|
+
const chromaWeight = hct.chroma < Score.TARGET_CHROMA ? Score.WEIGHT_CHROMA_BELOW : Score.WEIGHT_CHROMA_ABOVE;
|
13904
|
+
const chromaScore = (hct.chroma - Score.TARGET_CHROMA) * chromaWeight;
|
13905
|
+
const score = proportionScore + chromaScore;
|
13906
|
+
scoredHct.push({ hct, score });
|
13907
|
+
}
|
13908
|
+
// Sorted so that colors with higher scores come first.
|
13909
|
+
scoredHct.sort(compare);
|
13910
|
+
// Iterates through potential hue differences in degrees in order to select
|
13911
|
+
// the colors with the largest distribution of hues possible. Starting at
|
13912
|
+
// 90 degrees(maximum difference for 4 colors) then decreasing down to a
|
13913
|
+
// 15 degree minimum.
|
13914
|
+
const chosenColors = [];
|
13915
|
+
for (let differenceDegrees$1 = 90; differenceDegrees$1 >= 15; differenceDegrees$1--) {
|
13916
|
+
chosenColors.length = 0;
|
13917
|
+
for (const { hct } of scoredHct) {
|
13918
|
+
const duplicateHue = chosenColors.find(chosenHct => {
|
13919
|
+
return differenceDegrees(hct.hue, chosenHct.hue) < differenceDegrees$1;
|
13920
|
+
});
|
13921
|
+
if (!duplicateHue) {
|
13922
|
+
chosenColors.push(hct);
|
13923
|
+
}
|
13924
|
+
if (chosenColors.length >= desired)
|
13925
|
+
break;
|
13926
|
+
}
|
13927
|
+
if (chosenColors.length >= desired)
|
13928
|
+
break;
|
13929
|
+
}
|
13930
|
+
const colors = [];
|
13931
|
+
if (chosenColors.length === 0) {
|
13932
|
+
colors.push(fallbackColorARGB);
|
13933
|
+
}
|
13934
|
+
for (const chosenHct of chosenColors) {
|
13935
|
+
colors.push(chosenHct.toInt());
|
13936
|
+
}
|
13937
|
+
return colors;
|
13938
|
+
}
|
13939
|
+
}
|
13940
|
+
Score.TARGET_CHROMA = 48.0; // A1 Chroma
|
13941
|
+
Score.WEIGHT_PROPORTION = 0.7;
|
13942
|
+
Score.WEIGHT_CHROMA_ABOVE = 0.3;
|
13943
|
+
Score.WEIGHT_CHROMA_BELOW = 0.1;
|
13944
|
+
Score.CUTOFF_CHROMA = 5.0;
|
13945
|
+
Score.CUTOFF_EXCITED_PROPORTION = 0.01;
|
13946
|
+
|
13947
|
+
/**
|
13948
|
+
* @license
|
13949
|
+
* Copyright 2021 Google LLC
|
13950
|
+
*
|
13951
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
13952
|
+
* you may not use this file except in compliance with the License.
|
13953
|
+
* You may obtain a copy of the License at
|
13954
|
+
*
|
13955
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
13956
|
+
*
|
13957
|
+
* Unless required by applicable law or agreed to in writing, software
|
13958
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
13959
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13960
|
+
* See the License for the specific language governing permissions and
|
13961
|
+
* limitations under the License.
|
13962
|
+
*/
|
13963
|
+
/**
|
13964
|
+
* Utility methods for hexadecimal representations of colors.
|
13965
|
+
*/
|
13966
|
+
/**
|
13967
|
+
* @param argb ARGB representation of a color.
|
13968
|
+
* @return Hex string representing color, ex. #ff0000 for red.
|
13969
|
+
*/
|
13970
|
+
function hexFromArgb(argb) {
|
13971
|
+
const r = redFromArgb(argb);
|
13972
|
+
const g = greenFromArgb(argb);
|
13973
|
+
const b = blueFromArgb(argb);
|
13974
|
+
const outParts = [r.toString(16), g.toString(16), b.toString(16)];
|
13975
|
+
// Pad single-digit output values
|
13976
|
+
for (const [i, part] of outParts.entries()) {
|
13977
|
+
if (part.length === 1) {
|
13978
|
+
outParts[i] = '0' + part;
|
13979
|
+
}
|
13980
|
+
}
|
13981
|
+
return '#' + outParts.join('');
|
13982
|
+
}
|
13983
|
+
|
13984
|
+
/**
|
13985
|
+
* @license
|
13986
|
+
* Copyright 2021 Google LLC
|
13987
|
+
*
|
13988
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
13989
|
+
* you may not use this file except in compliance with the License.
|
13990
|
+
* You may obtain a copy of the License at
|
13991
|
+
*
|
13992
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
13993
|
+
*
|
13994
|
+
* Unless required by applicable law or agreed to in writing, software
|
13995
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
13996
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13997
|
+
* See the License for the specific language governing permissions and
|
13998
|
+
* limitations under the License.
|
13999
|
+
*/
|
14000
|
+
/**
|
14001
|
+
* Get the source color from an image.
|
14002
|
+
*
|
14003
|
+
* @param image The image element
|
14004
|
+
* @return Source color - the color most suitable for creating a UI theme
|
14005
|
+
*/
|
14006
|
+
async function sourceColorFromImage(image) {
|
14007
|
+
// Convert Image data to Pixel Array
|
14008
|
+
const imageBytes = await new Promise((resolve, reject) => {
|
14009
|
+
const canvas = document.createElement('canvas');
|
14010
|
+
const context = canvas.getContext('2d');
|
14011
|
+
if (!context) {
|
14012
|
+
reject(new Error('Could not get canvas context'));
|
14013
|
+
return;
|
14014
|
+
}
|
14015
|
+
const callback = () => {
|
14016
|
+
canvas.width = image.width;
|
14017
|
+
canvas.height = image.height;
|
14018
|
+
context.drawImage(image, 0, 0);
|
14019
|
+
let rect = [0, 0, image.width, image.height];
|
14020
|
+
const area = image.dataset['area'];
|
14021
|
+
if (area && /^\d+(\s*,\s*\d+){3}$/.test(area)) {
|
14022
|
+
rect = area.split(/\s*,\s*/).map(s => {
|
14023
|
+
// tslint:disable-next-line:ban
|
14024
|
+
return parseInt(s, 10);
|
14025
|
+
});
|
14026
|
+
}
|
14027
|
+
const [sx, sy, sw, sh] = rect;
|
14028
|
+
resolve(context.getImageData(sx, sy, sw, sh).data);
|
14029
|
+
};
|
14030
|
+
if (image.complete) {
|
14031
|
+
callback();
|
14032
|
+
}
|
14033
|
+
else {
|
14034
|
+
image.onload = callback;
|
14035
|
+
}
|
14036
|
+
});
|
14037
|
+
// Convert Image data to Pixel Array
|
14038
|
+
const pixels = [];
|
14039
|
+
for (let i = 0; i < imageBytes.length; i += 4) {
|
14040
|
+
const r = imageBytes[i];
|
14041
|
+
const g = imageBytes[i + 1];
|
14042
|
+
const b = imageBytes[i + 2];
|
14043
|
+
const a = imageBytes[i + 3];
|
14044
|
+
if (a < 255) {
|
14045
|
+
continue;
|
14046
|
+
}
|
14047
|
+
const argb = argbFromRgb(r, g, b);
|
14048
|
+
pixels.push(argb);
|
14049
|
+
}
|
14050
|
+
// Convert Pixels to Material Colors
|
14051
|
+
const result = QuantizerCelebi.quantize(pixels, 128);
|
14052
|
+
const ranked = Score.score(result);
|
14053
|
+
const top = ranked[0];
|
14054
|
+
return top;
|
14055
|
+
}
|
14056
|
+
|
14057
|
+
/**
|
14058
|
+
* @license
|
14059
|
+
* Copyright 2021 Google LLC
|
14060
|
+
*
|
14061
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
14062
|
+
* you may not use this file except in compliance with the License.
|
14063
|
+
* You may obtain a copy of the License at
|
14064
|
+
*
|
14065
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
14066
|
+
*
|
14067
|
+
* Unless required by applicable law or agreed to in writing, software
|
14068
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
14069
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14070
|
+
* See the License for the specific language governing permissions and
|
14071
|
+
* limitations under the License.
|
14072
|
+
*/
|
14073
|
+
/**
|
14074
|
+
* Generate a theme from a source color
|
14075
|
+
*
|
14076
|
+
* @param source Source color
|
14077
|
+
* @param customColors Array of custom colors
|
14078
|
+
* @return Theme object
|
14079
|
+
*/
|
14080
|
+
function themeFromSourceColor(source, customColors = []) {
|
14081
|
+
const palette = CorePalette.of(source);
|
14082
|
+
return {
|
14083
|
+
source,
|
14084
|
+
schemes: {
|
14085
|
+
light: Scheme.light(source),
|
14086
|
+
dark: Scheme.dark(source),
|
14087
|
+
},
|
14088
|
+
palettes: {
|
14089
|
+
primary: palette.a1,
|
14090
|
+
secondary: palette.a2,
|
14091
|
+
tertiary: palette.a3,
|
14092
|
+
neutral: palette.n1,
|
14093
|
+
neutralVariant: palette.n2,
|
14094
|
+
error: palette.error,
|
14095
|
+
},
|
14096
|
+
customColors: customColors.map((c) => customColor(source, c)),
|
14097
|
+
};
|
14098
|
+
}
|
14099
|
+
/**
|
14100
|
+
* Generate a theme from an image source
|
14101
|
+
*
|
14102
|
+
* @param image Image element
|
14103
|
+
* @param customColors Array of custom colors
|
14104
|
+
* @return Theme object
|
14105
|
+
*/
|
14106
|
+
async function themeFromImage(image, customColors = []) {
|
14107
|
+
const source = await sourceColorFromImage(image);
|
14108
|
+
return themeFromSourceColor(source, customColors);
|
14109
|
+
}
|
14110
|
+
/**
|
14111
|
+
* Generate custom color group from source and target color
|
14112
|
+
*
|
14113
|
+
* @param source Source color
|
14114
|
+
* @param color Custom color
|
14115
|
+
* @return Custom color group
|
14116
|
+
*
|
14117
|
+
* @link https://m3.material.io/styles/color/the-color-system/color-roles
|
14118
|
+
*/
|
14119
|
+
function customColor(source, color) {
|
14120
|
+
let value = color.value;
|
14121
|
+
const from = value;
|
14122
|
+
const to = source;
|
14123
|
+
if (color.blend) {
|
14124
|
+
value = Blend.harmonize(from, to);
|
14125
|
+
}
|
14126
|
+
const palette = CorePalette.of(value);
|
14127
|
+
const tones = palette.a1;
|
14128
|
+
return {
|
14129
|
+
color,
|
14130
|
+
value,
|
14131
|
+
light: {
|
14132
|
+
color: tones.tone(40),
|
14133
|
+
onColor: tones.tone(100),
|
14134
|
+
colorContainer: tones.tone(90),
|
14135
|
+
onColorContainer: tones.tone(10),
|
14136
|
+
},
|
14137
|
+
dark: {
|
14138
|
+
color: tones.tone(80),
|
14139
|
+
onColor: tones.tone(20),
|
14140
|
+
colorContainer: tones.tone(30),
|
14141
|
+
onColorContainer: tones.tone(90),
|
14142
|
+
},
|
14143
|
+
};
|
14144
|
+
}
|
14145
|
+
|
14146
|
+
const dynamicColor = async src => {
|
14147
|
+
const imageElement = document.createElement("img");
|
14148
|
+
imageElement.src = src;
|
14149
|
+
const dynamicPalette = await themeFromImage(imageElement);
|
14150
|
+
const extractHctInstance = color => {
|
14151
|
+
const {
|
14152
|
+
hue,
|
14153
|
+
chroma,
|
14154
|
+
tone
|
14155
|
+
} = color.keyColor;
|
14156
|
+
return hexFromArgb(Hct.from(hue, chroma, tone).toInt());
|
14157
|
+
};
|
14158
|
+
const {
|
14159
|
+
primary,
|
14160
|
+
secondary
|
14161
|
+
} = dynamicPalette.palettes;
|
14162
|
+
const {
|
14163
|
+
dark,
|
14164
|
+
light
|
14165
|
+
} = dynamicPalette.schemes;
|
14166
|
+
const primaryMain = extractHctInstance(primary);
|
14167
|
+
const secondaryMain = extractHctInstance(secondary);
|
14168
|
+
const primaryLight = hexFromArgb(dark.primary);
|
14169
|
+
const primaryDark = hexFromArgb(light.primary);
|
14170
|
+
const secondaryLight = hexFromArgb(dark.secondary);
|
14171
|
+
const secondaryDark = hexFromArgb(light.secondary);
|
14172
|
+
const primaryColor = {
|
14173
|
+
main: primaryMain,
|
14174
|
+
light: primaryLight,
|
14175
|
+
dark: primaryDark,
|
14176
|
+
contrastText: "#ffffff"
|
14177
|
+
};
|
14178
|
+
const secondaryColor = {
|
14179
|
+
main: secondaryMain,
|
14180
|
+
light: secondaryLight,
|
14181
|
+
dark: secondaryDark,
|
14182
|
+
contrastText: "#ffffff"
|
14183
|
+
};
|
14184
|
+
const backgroundColor = {
|
14185
|
+
default: alpha(primaryMain, 0.1),
|
14186
|
+
paper: "#fff"
|
14187
|
+
};
|
14188
|
+
return {
|
14189
|
+
primaryColor,
|
14190
|
+
secondaryColor,
|
14191
|
+
backgroundColor
|
14192
|
+
};
|
14193
|
+
};
|
14194
|
+
|
14195
|
+
const useDynamicColor = url => {
|
14196
|
+
const [primary, setPrimary] = useState(SincoTheme.palette.primary);
|
14197
|
+
const [secondary, setSecondary] = useState(SincoTheme.palette.secondary);
|
14198
|
+
const [background, setBackground] = useState(SincoTheme.palette.background);
|
14199
|
+
const [loading, setLoading] = useState(false);
|
14200
|
+
useEffect(() => {
|
14201
|
+
dynamicColor(url).then(({
|
14202
|
+
primaryColor,
|
14203
|
+
secondaryColor,
|
14204
|
+
backgroundColor
|
14205
|
+
}) => {
|
14206
|
+
setPrimary(primaryColor);
|
14207
|
+
setSecondary(secondaryColor);
|
14208
|
+
setBackground(backgroundColor);
|
14209
|
+
setLoading(true);
|
14210
|
+
});
|
14211
|
+
}, [url]);
|
14212
|
+
SincoTheme.palette.primary = primary;
|
14213
|
+
SincoTheme.palette.secondary = secondary;
|
14214
|
+
SincoTheme.palette.background = background;
|
14215
|
+
return {
|
14216
|
+
loading
|
14217
|
+
};
|
14218
|
+
};
|
14219
|
+
|
14220
|
+
export { SincoTheme, useDynamicColor };
|