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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 };