@sinco/react 1.0.7-rc.2 → 1.0.7-rc.3

Sign up to get free protection for your applications and to get access to all the features.
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
- export { SincoTheme };
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 };