@m3e/web 2.5.2 → 2.5.3

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.
Files changed (41) hide show
  1. package/dist/all.js +1026 -4
  2. package/dist/all.js.map +1 -1
  3. package/dist/all.min.js +143 -30
  4. package/dist/all.min.js.map +1 -1
  5. package/dist/calendar.js +12 -0
  6. package/dist/calendar.js.map +1 -1
  7. package/dist/calendar.min.js +5 -5
  8. package/dist/calendar.min.js.map +1 -1
  9. package/dist/css-custom-data.json +279 -279
  10. package/dist/custom-elements.json +4591 -4548
  11. package/dist/datepicker.js +6 -0
  12. package/dist/datepicker.js.map +1 -1
  13. package/dist/datepicker.min.js +1 -1
  14. package/dist/datepicker.min.js.map +1 -1
  15. package/dist/html-custom-data.json +169 -169
  16. package/dist/list.js +1 -1
  17. package/dist/list.js.map +1 -1
  18. package/dist/list.min.js +1 -1
  19. package/dist/list.min.js.map +1 -1
  20. package/dist/nav-bar.js +1 -1
  21. package/dist/nav-bar.js.map +1 -1
  22. package/dist/nav-bar.min.js +1 -1
  23. package/dist/nav-bar.min.js.map +1 -1
  24. package/dist/src/calendar/CalendarElement.d.ts.map +1 -1
  25. package/dist/src/datepicker/DatepickerElement.d.ts.map +1 -1
  26. package/dist/src/list/ListItemButtonElement.d.ts +0 -5
  27. package/dist/src/list/ListItemButtonElement.d.ts.map +1 -1
  28. package/dist/src/nav-bar/NavItemElement.d.ts.map +1 -1
  29. package/dist/src/theme/getColorFromImage.d.ts +12 -0
  30. package/dist/src/theme/getColorFromImage.d.ts.map +1 -0
  31. package/dist/src/theme/index.d.ts +1 -0
  32. package/dist/src/theme/index.d.ts.map +1 -1
  33. package/dist/theme.js +1005 -1
  34. package/dist/theme.js.map +1 -1
  35. package/dist/theme.min.js +133 -20
  36. package/dist/theme.min.js.map +1 -1
  37. package/dist/toc.js +1 -1
  38. package/dist/toc.js.map +1 -1
  39. package/dist/toc.min.js +1 -1
  40. package/dist/toc.min.js.map +1 -1
  41. package/package.json +1 -1
package/dist/theme.js CHANGED
@@ -78,6 +78,19 @@ function clampDouble(min, max, input) {
78
78
  }
79
79
  return input;
80
80
  }
81
+ /**
82
+ * Sanitizes a degree measure as an integer.
83
+ *
84
+ * @return a degree measure between 0 (inclusive) and 360
85
+ * (exclusive).
86
+ */
87
+ function sanitizeDegreesInt(degrees) {
88
+ degrees = degrees % 360;
89
+ if (degrees < 0) {
90
+ degrees = degrees + 360;
91
+ }
92
+ return degrees;
93
+ }
81
94
  /**
82
95
  * Sanitizes a degree measure as a floating-point number.
83
96
  *
@@ -91,6 +104,12 @@ function sanitizeDegreesDouble(degrees) {
91
104
  }
92
105
  return degrees;
93
106
  }
107
+ /**
108
+ * Distance of two points on a circle, represented using degrees.
109
+ */
110
+ function differenceDegrees(a, b) {
111
+ return 180.0 - Math.abs(Math.abs(a - b) - 180.0);
112
+ }
94
113
  /**
95
114
  * Multiplies a 1x3 row vector with a 3x3 matrix.
96
115
  */
@@ -142,6 +161,12 @@ function argbFromLinrgb(linrgb) {
142
161
  const b = delinearized(linrgb[2]);
143
162
  return argbFromRgb(r, g, b);
144
163
  }
164
+ /**
165
+ * Returns the alpha component of a color in ARGB format.
166
+ */
167
+ function alphaFromArgb(argb) {
168
+ return argb >> 24 & 255;
169
+ }
145
170
  /**
146
171
  * Returns the red component of a color in ARGB format.
147
172
  */
@@ -182,6 +207,50 @@ function xyzFromArgb(argb) {
182
207
  const b = linearized(blueFromArgb(argb));
183
208
  return matrixMultiply([r, g, b], SRGB_TO_XYZ);
184
209
  }
210
+ /**
211
+ * Converts a color represented in Lab color space into an ARGB
212
+ * integer.
213
+ */
214
+ function argbFromLab(l, a, b) {
215
+ const whitePoint = WHITE_POINT_D65;
216
+ const fy = (l + 16.0) / 116.0;
217
+ const fx = a / 500.0 + fy;
218
+ const fz = fy - b / 200.0;
219
+ const xNormalized = labInvf(fx);
220
+ const yNormalized = labInvf(fy);
221
+ const zNormalized = labInvf(fz);
222
+ const x = xNormalized * whitePoint[0];
223
+ const y = yNormalized * whitePoint[1];
224
+ const z = zNormalized * whitePoint[2];
225
+ return argbFromXyz(x, y, z);
226
+ }
227
+ /**
228
+ * Converts a color from ARGB representation to L*a*b*
229
+ * representation.
230
+ *
231
+ * @param argb the ARGB representation of a color
232
+ * @return a Lab object representing the color
233
+ */
234
+ function labFromArgb(argb) {
235
+ const linearR = linearized(redFromArgb(argb));
236
+ const linearG = linearized(greenFromArgb(argb));
237
+ const linearB = linearized(blueFromArgb(argb));
238
+ const matrix = SRGB_TO_XYZ;
239
+ const x = matrix[0][0] * linearR + matrix[0][1] * linearG + matrix[0][2] * linearB;
240
+ const y = matrix[1][0] * linearR + matrix[1][1] * linearG + matrix[1][2] * linearB;
241
+ const z = matrix[2][0] * linearR + matrix[2][1] * linearG + matrix[2][2] * linearB;
242
+ const whitePoint = WHITE_POINT_D65;
243
+ const xNormalized = x / whitePoint[0];
244
+ const yNormalized = y / whitePoint[1];
245
+ const zNormalized = z / whitePoint[2];
246
+ const fx = labF(xNormalized);
247
+ const fy = labF(yNormalized);
248
+ const fz = labF(zNormalized);
249
+ const l = 116.0 * fy - 16;
250
+ const a = 500.0 * (fx - fy);
251
+ const b = 200.0 * (fy - fz);
252
+ return [l, a, b];
253
+ }
185
254
  /**
186
255
  * Converts an L* value to an ARGB representation.
187
256
  *
@@ -2889,6 +2958,712 @@ class CorePalette {
2889
2958
  }
2890
2959
  }
2891
2960
 
2961
+ /**
2962
+ * @license
2963
+ * Copyright 2021 Google LLC
2964
+ *
2965
+ * Licensed under the Apache License, Version 2.0 (the "License");
2966
+ * you may not use this file except in compliance with the License.
2967
+ * You may obtain a copy of the License at
2968
+ *
2969
+ * http://www.apache.org/licenses/LICENSE-2.0
2970
+ *
2971
+ * Unless required by applicable law or agreed to in writing, software
2972
+ * distributed under the License is distributed on an "AS IS" BASIS,
2973
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2974
+ * See the License for the specific language governing permissions and
2975
+ * limitations under the License.
2976
+ */
2977
+ /**
2978
+ * Provides conversions needed for K-Means quantization. Converting input to
2979
+ * points, and converting the final state of the K-Means algorithm to colors.
2980
+ */
2981
+ class LabPointProvider {
2982
+ /**
2983
+ * Convert a color represented in ARGB to a 3-element array of L*a*b*
2984
+ * coordinates of the color.
2985
+ */
2986
+ fromInt(argb) {
2987
+ return labFromArgb(argb);
2988
+ }
2989
+ /**
2990
+ * Convert a 3-element array to a color represented in ARGB.
2991
+ */
2992
+ toInt(point) {
2993
+ return argbFromLab(point[0], point[1], point[2]);
2994
+ }
2995
+ /**
2996
+ * Standard CIE 1976 delta E formula also takes the square root, unneeded
2997
+ * here. This method is used by quantization algorithms to compare distance,
2998
+ * and the relative ordering is the same, with or without a square root.
2999
+ *
3000
+ * This relatively minor optimization is helpful because this method is
3001
+ * called at least once for each pixel in an image.
3002
+ */
3003
+ distance(from, to) {
3004
+ const dL = from[0] - to[0];
3005
+ const dA = from[1] - to[1];
3006
+ const dB = from[2] - to[2];
3007
+ return dL * dL + dA * dA + dB * dB;
3008
+ }
3009
+ }
3010
+
3011
+ /**
3012
+ * @license
3013
+ * Copyright 2021 Google LLC
3014
+ *
3015
+ * Licensed under the Apache License, Version 2.0 (the "License");
3016
+ * you may not use this file except in compliance with the License.
3017
+ * You may obtain a copy of the License at
3018
+ *
3019
+ * http://www.apache.org/licenses/LICENSE-2.0
3020
+ *
3021
+ * Unless required by applicable law or agreed to in writing, software
3022
+ * distributed under the License is distributed on an "AS IS" BASIS,
3023
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3024
+ * See the License for the specific language governing permissions and
3025
+ * limitations under the License.
3026
+ */
3027
+ const MAX_ITERATIONS = 10;
3028
+ const MIN_MOVEMENT_DISTANCE = 3.0;
3029
+ /**
3030
+ * An image quantizer that improves on the speed of a standard K-Means algorithm
3031
+ * by implementing several optimizations, including deduping identical pixels
3032
+ * and a triangle inequality rule that reduces the number of comparisons needed
3033
+ * to identify which cluster a point should be moved to.
3034
+ *
3035
+ * Wsmeans stands for Weighted Square Means.
3036
+ *
3037
+ * This algorithm was designed by M. Emre Celebi, and was found in their 2011
3038
+ * paper, Improving the Performance of K-Means for Color Quantization.
3039
+ * https://arxiv.org/abs/1101.0395
3040
+ */
3041
+ // material_color_utilities is designed to have a consistent API across
3042
+ // platforms and modular components that can be moved around easily. Using a
3043
+ // class as a namespace facilitates this.
3044
+ //
3045
+ // tslint:disable-next-line:class-as-namespace
3046
+ class QuantizerWsmeans {
3047
+ /**
3048
+ * @param inputPixels Colors in ARGB format.
3049
+ * @param startingClusters Defines the initial state of the quantizer. Passing
3050
+ * an empty array is fine, the implementation will create its own initial
3051
+ * state that leads to reproducible results for the same inputs.
3052
+ * Passing an array that is the result of Wu quantization leads to higher
3053
+ * quality results.
3054
+ * @param maxColors The number of colors to divide the image into. A lower
3055
+ * number of colors may be returned.
3056
+ * @return Colors in ARGB format.
3057
+ */
3058
+ static quantize(inputPixels, startingClusters, maxColors) {
3059
+ const pixelToCount = new Map();
3060
+ const points = new Array();
3061
+ const pixels = new Array();
3062
+ const pointProvider = new LabPointProvider();
3063
+ let pointCount = 0;
3064
+ for (let i = 0; i < inputPixels.length; i++) {
3065
+ const inputPixel = inputPixels[i];
3066
+ const pixelCount = pixelToCount.get(inputPixel);
3067
+ if (pixelCount === undefined) {
3068
+ pointCount++;
3069
+ points.push(pointProvider.fromInt(inputPixel));
3070
+ pixels.push(inputPixel);
3071
+ pixelToCount.set(inputPixel, 1);
3072
+ } else {
3073
+ pixelToCount.set(inputPixel, pixelCount + 1);
3074
+ }
3075
+ }
3076
+ const counts = new Array();
3077
+ for (let i = 0; i < pointCount; i++) {
3078
+ const pixel = pixels[i];
3079
+ const count = pixelToCount.get(pixel);
3080
+ if (count !== undefined) {
3081
+ counts[i] = count;
3082
+ }
3083
+ }
3084
+ let clusterCount = Math.min(maxColors, pointCount);
3085
+ if (startingClusters.length > 0) {
3086
+ clusterCount = Math.min(clusterCount, startingClusters.length);
3087
+ }
3088
+ const clusters = new Array();
3089
+ for (let i = 0; i < startingClusters.length; i++) {
3090
+ clusters.push(pointProvider.fromInt(startingClusters[i]));
3091
+ }
3092
+ const additionalClustersNeeded = clusterCount - clusters.length;
3093
+ if (startingClusters.length === 0 && additionalClustersNeeded > 0) {
3094
+ for (let i = 0; i < additionalClustersNeeded; i++) {
3095
+ const l = Math.random() * 100.0;
3096
+ const a = Math.random() * (100.0 - -100 + 1) + -100;
3097
+ const b = Math.random() * (100.0 - -100 + 1) + -100;
3098
+ clusters.push(new Array(l, a, b));
3099
+ }
3100
+ }
3101
+ const clusterIndices = new Array();
3102
+ for (let i = 0; i < pointCount; i++) {
3103
+ clusterIndices.push(Math.floor(Math.random() * clusterCount));
3104
+ }
3105
+ const indexMatrix = new Array();
3106
+ for (let i = 0; i < clusterCount; i++) {
3107
+ indexMatrix.push(new Array());
3108
+ for (let j = 0; j < clusterCount; j++) {
3109
+ indexMatrix[i].push(0);
3110
+ }
3111
+ }
3112
+ const distanceToIndexMatrix = new Array();
3113
+ for (let i = 0; i < clusterCount; i++) {
3114
+ distanceToIndexMatrix.push(new Array());
3115
+ for (let j = 0; j < clusterCount; j++) {
3116
+ distanceToIndexMatrix[i].push(new DistanceAndIndex());
3117
+ }
3118
+ }
3119
+ const pixelCountSums = new Array();
3120
+ for (let i = 0; i < clusterCount; i++) {
3121
+ pixelCountSums.push(0);
3122
+ }
3123
+ for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
3124
+ for (let i = 0; i < clusterCount; i++) {
3125
+ for (let j = i + 1; j < clusterCount; j++) {
3126
+ const distance = pointProvider.distance(clusters[i], clusters[j]);
3127
+ distanceToIndexMatrix[j][i].distance = distance;
3128
+ distanceToIndexMatrix[j][i].index = i;
3129
+ distanceToIndexMatrix[i][j].distance = distance;
3130
+ distanceToIndexMatrix[i][j].index = j;
3131
+ }
3132
+ distanceToIndexMatrix[i].sort();
3133
+ for (let j = 0; j < clusterCount; j++) {
3134
+ indexMatrix[i][j] = distanceToIndexMatrix[i][j].index;
3135
+ }
3136
+ }
3137
+ let pointsMoved = 0;
3138
+ for (let i = 0; i < pointCount; i++) {
3139
+ const point = points[i];
3140
+ const previousClusterIndex = clusterIndices[i];
3141
+ const previousCluster = clusters[previousClusterIndex];
3142
+ const previousDistance = pointProvider.distance(point, previousCluster);
3143
+ let minimumDistance = previousDistance;
3144
+ let newClusterIndex = -1;
3145
+ for (let j = 0; j < clusterCount; j++) {
3146
+ if (distanceToIndexMatrix[previousClusterIndex][j].distance >= 4 * previousDistance) {
3147
+ continue;
3148
+ }
3149
+ const distance = pointProvider.distance(point, clusters[j]);
3150
+ if (distance < minimumDistance) {
3151
+ minimumDistance = distance;
3152
+ newClusterIndex = j;
3153
+ }
3154
+ }
3155
+ if (newClusterIndex !== -1) {
3156
+ const distanceChange = Math.abs(Math.sqrt(minimumDistance) - Math.sqrt(previousDistance));
3157
+ if (distanceChange > MIN_MOVEMENT_DISTANCE) {
3158
+ pointsMoved++;
3159
+ clusterIndices[i] = newClusterIndex;
3160
+ }
3161
+ }
3162
+ }
3163
+ if (pointsMoved === 0 && iteration !== 0) {
3164
+ break;
3165
+ }
3166
+ const componentASums = new Array(clusterCount).fill(0);
3167
+ const componentBSums = new Array(clusterCount).fill(0);
3168
+ const componentCSums = new Array(clusterCount).fill(0);
3169
+ for (let i = 0; i < clusterCount; i++) {
3170
+ pixelCountSums[i] = 0;
3171
+ }
3172
+ for (let i = 0; i < pointCount; i++) {
3173
+ const clusterIndex = clusterIndices[i];
3174
+ const point = points[i];
3175
+ const count = counts[i];
3176
+ pixelCountSums[clusterIndex] += count;
3177
+ componentASums[clusterIndex] += point[0] * count;
3178
+ componentBSums[clusterIndex] += point[1] * count;
3179
+ componentCSums[clusterIndex] += point[2] * count;
3180
+ }
3181
+ for (let i = 0; i < clusterCount; i++) {
3182
+ const count = pixelCountSums[i];
3183
+ if (count === 0) {
3184
+ clusters[i] = [0.0, 0.0, 0.0];
3185
+ continue;
3186
+ }
3187
+ const a = componentASums[i] / count;
3188
+ const b = componentBSums[i] / count;
3189
+ const c = componentCSums[i] / count;
3190
+ clusters[i] = [a, b, c];
3191
+ }
3192
+ }
3193
+ const argbToPopulation = new Map();
3194
+ for (let i = 0; i < clusterCount; i++) {
3195
+ const count = pixelCountSums[i];
3196
+ if (count === 0) {
3197
+ continue;
3198
+ }
3199
+ const possibleNewCluster = pointProvider.toInt(clusters[i]);
3200
+ if (argbToPopulation.has(possibleNewCluster)) {
3201
+ continue;
3202
+ }
3203
+ argbToPopulation.set(possibleNewCluster, count);
3204
+ }
3205
+ return argbToPopulation;
3206
+ }
3207
+ }
3208
+ /**
3209
+ * A wrapper for maintaining a table of distances between K-Means clusters.
3210
+ */
3211
+ class DistanceAndIndex {
3212
+ constructor() {
3213
+ this.distance = -1;
3214
+ this.index = -1;
3215
+ }
3216
+ }
3217
+
3218
+ /**
3219
+ * @license
3220
+ * Copyright 2021 Google LLC
3221
+ *
3222
+ * Licensed under the Apache License, Version 2.0 (the "License");
3223
+ * you may not use this file except in compliance with the License.
3224
+ * You may obtain a copy of the License at
3225
+ *
3226
+ * http://www.apache.org/licenses/LICENSE-2.0
3227
+ *
3228
+ * Unless required by applicable law or agreed to in writing, software
3229
+ * distributed under the License is distributed on an "AS IS" BASIS,
3230
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3231
+ * See the License for the specific language governing permissions and
3232
+ * limitations under the License.
3233
+ */
3234
+ /**
3235
+ * Quantizes an image into a map, with keys of ARGB colors, and values of the
3236
+ * number of times that color appears in the image.
3237
+ */
3238
+ // material_color_utilities is designed to have a consistent API across
3239
+ // platforms and modular components that can be moved around easily. Using a
3240
+ // class as a namespace facilitates this.
3241
+ //
3242
+ // tslint:disable-next-line:class-as-namespace
3243
+ class QuantizerMap {
3244
+ /**
3245
+ * @param pixels Colors in ARGB format.
3246
+ * @return A Map with keys of ARGB colors, and values of the number of times
3247
+ * the color appears in the image.
3248
+ */
3249
+ static quantize(pixels) {
3250
+ const countByColor = new Map();
3251
+ for (let i = 0; i < pixels.length; i++) {
3252
+ const pixel = pixels[i];
3253
+ const alpha = alphaFromArgb(pixel);
3254
+ if (alpha < 255) {
3255
+ continue;
3256
+ }
3257
+ countByColor.set(pixel, (countByColor.get(pixel) ?? 0) + 1);
3258
+ }
3259
+ return countByColor;
3260
+ }
3261
+ }
3262
+
3263
+ /**
3264
+ * @license
3265
+ * Copyright 2021 Google LLC
3266
+ *
3267
+ * Licensed under the Apache License, Version 2.0 (the "License");
3268
+ * you may not use this file except in compliance with the License.
3269
+ * You may obtain a copy of the License at
3270
+ *
3271
+ * http://www.apache.org/licenses/LICENSE-2.0
3272
+ *
3273
+ * Unless required by applicable law or agreed to in writing, software
3274
+ * distributed under the License is distributed on an "AS IS" BASIS,
3275
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3276
+ * See the License for the specific language governing permissions and
3277
+ * limitations under the License.
3278
+ */
3279
+ const INDEX_BITS = 5;
3280
+ const SIDE_LENGTH = 33; // ((1 << INDEX_INDEX_BITS) + 1)
3281
+ const TOTAL_SIZE = 35937; // SIDE_LENGTH * SIDE_LENGTH * SIDE_LENGTH
3282
+ const directions = {
3283
+ RED: 'red',
3284
+ GREEN: 'green',
3285
+ BLUE: 'blue'
3286
+ };
3287
+ /**
3288
+ * An image quantizer that divides the image's pixels into clusters by
3289
+ * recursively cutting an RGB cube, based on the weight of pixels in each area
3290
+ * of the cube.
3291
+ *
3292
+ * The algorithm was described by Xiaolin Wu in Graphic Gems II, published in
3293
+ * 1991.
3294
+ */
3295
+ class QuantizerWu {
3296
+ constructor(weights = [], momentsR = [], momentsG = [], momentsB = [], moments = [], cubes = []) {
3297
+ this.weights = weights;
3298
+ this.momentsR = momentsR;
3299
+ this.momentsG = momentsG;
3300
+ this.momentsB = momentsB;
3301
+ this.moments = moments;
3302
+ this.cubes = cubes;
3303
+ }
3304
+ /**
3305
+ * @param pixels Colors in ARGB format.
3306
+ * @param maxColors The number of colors to divide the image into. A lower
3307
+ * number of colors may be returned.
3308
+ * @return Colors in ARGB format.
3309
+ */
3310
+ quantize(pixels, maxColors) {
3311
+ this.constructHistogram(pixels);
3312
+ this.computeMoments();
3313
+ const createBoxesResult = this.createBoxes(maxColors);
3314
+ const results = this.createResult(createBoxesResult.resultCount);
3315
+ return results;
3316
+ }
3317
+ constructHistogram(pixels) {
3318
+ this.weights = Array.from({
3319
+ length: TOTAL_SIZE
3320
+ }).fill(0);
3321
+ this.momentsR = Array.from({
3322
+ length: TOTAL_SIZE
3323
+ }).fill(0);
3324
+ this.momentsG = Array.from({
3325
+ length: TOTAL_SIZE
3326
+ }).fill(0);
3327
+ this.momentsB = Array.from({
3328
+ length: TOTAL_SIZE
3329
+ }).fill(0);
3330
+ this.moments = Array.from({
3331
+ length: TOTAL_SIZE
3332
+ }).fill(0);
3333
+ const countByColor = QuantizerMap.quantize(pixels);
3334
+ for (const [pixel, count] of countByColor.entries()) {
3335
+ const red = redFromArgb(pixel);
3336
+ const green = greenFromArgb(pixel);
3337
+ const blue = blueFromArgb(pixel);
3338
+ const bitsToRemove = 8 - INDEX_BITS;
3339
+ const iR = (red >> bitsToRemove) + 1;
3340
+ const iG = (green >> bitsToRemove) + 1;
3341
+ const iB = (blue >> bitsToRemove) + 1;
3342
+ const index = this.getIndex(iR, iG, iB);
3343
+ this.weights[index] = (this.weights[index] ?? 0) + count;
3344
+ this.momentsR[index] += count * red;
3345
+ this.momentsG[index] += count * green;
3346
+ this.momentsB[index] += count * blue;
3347
+ this.moments[index] += count * (red * red + green * green + blue * blue);
3348
+ }
3349
+ }
3350
+ computeMoments() {
3351
+ for (let r = 1; r < SIDE_LENGTH; r++) {
3352
+ const area = Array.from({
3353
+ length: SIDE_LENGTH
3354
+ }).fill(0);
3355
+ const areaR = Array.from({
3356
+ length: SIDE_LENGTH
3357
+ }).fill(0);
3358
+ const areaG = Array.from({
3359
+ length: SIDE_LENGTH
3360
+ }).fill(0);
3361
+ const areaB = Array.from({
3362
+ length: SIDE_LENGTH
3363
+ }).fill(0);
3364
+ const area2 = Array.from({
3365
+ length: SIDE_LENGTH
3366
+ }).fill(0.0);
3367
+ for (let g = 1; g < SIDE_LENGTH; g++) {
3368
+ let line = 0;
3369
+ let lineR = 0;
3370
+ let lineG = 0;
3371
+ let lineB = 0;
3372
+ let line2 = 0.0;
3373
+ for (let b = 1; b < SIDE_LENGTH; b++) {
3374
+ const index = this.getIndex(r, g, b);
3375
+ line += this.weights[index];
3376
+ lineR += this.momentsR[index];
3377
+ lineG += this.momentsG[index];
3378
+ lineB += this.momentsB[index];
3379
+ line2 += this.moments[index];
3380
+ area[b] += line;
3381
+ areaR[b] += lineR;
3382
+ areaG[b] += lineG;
3383
+ areaB[b] += lineB;
3384
+ area2[b] += line2;
3385
+ const previousIndex = this.getIndex(r - 1, g, b);
3386
+ this.weights[index] = this.weights[previousIndex] + area[b];
3387
+ this.momentsR[index] = this.momentsR[previousIndex] + areaR[b];
3388
+ this.momentsG[index] = this.momentsG[previousIndex] + areaG[b];
3389
+ this.momentsB[index] = this.momentsB[previousIndex] + areaB[b];
3390
+ this.moments[index] = this.moments[previousIndex] + area2[b];
3391
+ }
3392
+ }
3393
+ }
3394
+ }
3395
+ createBoxes(maxColors) {
3396
+ this.cubes = Array.from({
3397
+ length: maxColors
3398
+ }).fill(0).map(() => new Box());
3399
+ const volumeVariance = Array.from({
3400
+ length: maxColors
3401
+ }).fill(0.0);
3402
+ this.cubes[0].r0 = 0;
3403
+ this.cubes[0].g0 = 0;
3404
+ this.cubes[0].b0 = 0;
3405
+ this.cubes[0].r1 = SIDE_LENGTH - 1;
3406
+ this.cubes[0].g1 = SIDE_LENGTH - 1;
3407
+ this.cubes[0].b1 = SIDE_LENGTH - 1;
3408
+ let generatedColorCount = maxColors;
3409
+ let next = 0;
3410
+ for (let i = 1; i < maxColors; i++) {
3411
+ if (this.cut(this.cubes[next], this.cubes[i])) {
3412
+ volumeVariance[next] = this.cubes[next].vol > 1 ? this.variance(this.cubes[next]) : 0.0;
3413
+ volumeVariance[i] = this.cubes[i].vol > 1 ? this.variance(this.cubes[i]) : 0.0;
3414
+ } else {
3415
+ volumeVariance[next] = 0.0;
3416
+ i--;
3417
+ }
3418
+ next = 0;
3419
+ let temp = volumeVariance[0];
3420
+ for (let j = 1; j <= i; j++) {
3421
+ if (volumeVariance[j] > temp) {
3422
+ temp = volumeVariance[j];
3423
+ next = j;
3424
+ }
3425
+ }
3426
+ if (temp <= 0.0) {
3427
+ generatedColorCount = i + 1;
3428
+ break;
3429
+ }
3430
+ }
3431
+ return new CreateBoxesResult(maxColors, generatedColorCount);
3432
+ }
3433
+ createResult(colorCount) {
3434
+ const colors = [];
3435
+ for (let i = 0; i < colorCount; ++i) {
3436
+ const cube = this.cubes[i];
3437
+ const weight = this.volume(cube, this.weights);
3438
+ if (weight > 0) {
3439
+ const r = Math.round(this.volume(cube, this.momentsR) / weight);
3440
+ const g = Math.round(this.volume(cube, this.momentsG) / weight);
3441
+ const b = Math.round(this.volume(cube, this.momentsB) / weight);
3442
+ const color = 255 << 24 | (r & 0x0ff) << 16 | (g & 0x0ff) << 8 | b & 0x0ff;
3443
+ colors.push(color);
3444
+ }
3445
+ }
3446
+ return colors;
3447
+ }
3448
+ variance(cube) {
3449
+ const dr = this.volume(cube, this.momentsR);
3450
+ const dg = this.volume(cube, this.momentsG);
3451
+ const db = this.volume(cube, this.momentsB);
3452
+ const xx = this.moments[this.getIndex(cube.r1, cube.g1, cube.b1)] - this.moments[this.getIndex(cube.r1, cube.g1, cube.b0)] - this.moments[this.getIndex(cube.r1, cube.g0, cube.b1)] + this.moments[this.getIndex(cube.r1, cube.g0, cube.b0)] - this.moments[this.getIndex(cube.r0, cube.g1, cube.b1)] + this.moments[this.getIndex(cube.r0, cube.g1, cube.b0)] + this.moments[this.getIndex(cube.r0, cube.g0, cube.b1)] - this.moments[this.getIndex(cube.r0, cube.g0, cube.b0)];
3453
+ const hypotenuse = dr * dr + dg * dg + db * db;
3454
+ const volume = this.volume(cube, this.weights);
3455
+ return xx - hypotenuse / volume;
3456
+ }
3457
+ cut(one, two) {
3458
+ const wholeR = this.volume(one, this.momentsR);
3459
+ const wholeG = this.volume(one, this.momentsG);
3460
+ const wholeB = this.volume(one, this.momentsB);
3461
+ const wholeW = this.volume(one, this.weights);
3462
+ const maxRResult = this.maximize(one, directions.RED, one.r0 + 1, one.r1, wholeR, wholeG, wholeB, wholeW);
3463
+ const maxGResult = this.maximize(one, directions.GREEN, one.g0 + 1, one.g1, wholeR, wholeG, wholeB, wholeW);
3464
+ const maxBResult = this.maximize(one, directions.BLUE, one.b0 + 1, one.b1, wholeR, wholeG, wholeB, wholeW);
3465
+ let direction;
3466
+ const maxR = maxRResult.maximum;
3467
+ const maxG = maxGResult.maximum;
3468
+ const maxB = maxBResult.maximum;
3469
+ if (maxR >= maxG && maxR >= maxB) {
3470
+ if (maxRResult.cutLocation < 0) {
3471
+ return false;
3472
+ }
3473
+ direction = directions.RED;
3474
+ } else if (maxG >= maxR && maxG >= maxB) {
3475
+ direction = directions.GREEN;
3476
+ } else {
3477
+ direction = directions.BLUE;
3478
+ }
3479
+ two.r1 = one.r1;
3480
+ two.g1 = one.g1;
3481
+ two.b1 = one.b1;
3482
+ switch (direction) {
3483
+ case directions.RED:
3484
+ one.r1 = maxRResult.cutLocation;
3485
+ two.r0 = one.r1;
3486
+ two.g0 = one.g0;
3487
+ two.b0 = one.b0;
3488
+ break;
3489
+ case directions.GREEN:
3490
+ one.g1 = maxGResult.cutLocation;
3491
+ two.r0 = one.r0;
3492
+ two.g0 = one.g1;
3493
+ two.b0 = one.b0;
3494
+ break;
3495
+ case directions.BLUE:
3496
+ one.b1 = maxBResult.cutLocation;
3497
+ two.r0 = one.r0;
3498
+ two.g0 = one.g0;
3499
+ two.b0 = one.b1;
3500
+ break;
3501
+ default:
3502
+ throw new Error('unexpected direction ' + direction);
3503
+ }
3504
+ one.vol = (one.r1 - one.r0) * (one.g1 - one.g0) * (one.b1 - one.b0);
3505
+ two.vol = (two.r1 - two.r0) * (two.g1 - two.g0) * (two.b1 - two.b0);
3506
+ return true;
3507
+ }
3508
+ maximize(cube, direction, first, last, wholeR, wholeG, wholeB, wholeW) {
3509
+ const bottomR = this.bottom(cube, direction, this.momentsR);
3510
+ const bottomG = this.bottom(cube, direction, this.momentsG);
3511
+ const bottomB = this.bottom(cube, direction, this.momentsB);
3512
+ const bottomW = this.bottom(cube, direction, this.weights);
3513
+ let max = 0.0;
3514
+ let cut = -1;
3515
+ let halfR = 0;
3516
+ let halfG = 0;
3517
+ let halfB = 0;
3518
+ let halfW = 0;
3519
+ for (let i = first; i < last; i++) {
3520
+ halfR = bottomR + this.top(cube, direction, i, this.momentsR);
3521
+ halfG = bottomG + this.top(cube, direction, i, this.momentsG);
3522
+ halfB = bottomB + this.top(cube, direction, i, this.momentsB);
3523
+ halfW = bottomW + this.top(cube, direction, i, this.weights);
3524
+ if (halfW === 0) {
3525
+ continue;
3526
+ }
3527
+ let tempNumerator = (halfR * halfR + halfG * halfG + halfB * halfB) * 1.0;
3528
+ let tempDenominator = halfW * 1.0;
3529
+ let temp = tempNumerator / tempDenominator;
3530
+ halfR = wholeR - halfR;
3531
+ halfG = wholeG - halfG;
3532
+ halfB = wholeB - halfB;
3533
+ halfW = wholeW - halfW;
3534
+ if (halfW === 0) {
3535
+ continue;
3536
+ }
3537
+ tempNumerator = (halfR * halfR + halfG * halfG + halfB * halfB) * 1.0;
3538
+ tempDenominator = halfW * 1.0;
3539
+ temp += tempNumerator / tempDenominator;
3540
+ if (temp > max) {
3541
+ max = temp;
3542
+ cut = i;
3543
+ }
3544
+ }
3545
+ return new MaximizeResult(cut, max);
3546
+ }
3547
+ volume(cube, moment) {
3548
+ return moment[this.getIndex(cube.r1, cube.g1, cube.b1)] - moment[this.getIndex(cube.r1, cube.g1, cube.b0)] - moment[this.getIndex(cube.r1, cube.g0, cube.b1)] + moment[this.getIndex(cube.r1, cube.g0, cube.b0)] - moment[this.getIndex(cube.r0, cube.g1, cube.b1)] + moment[this.getIndex(cube.r0, cube.g1, cube.b0)] + moment[this.getIndex(cube.r0, cube.g0, cube.b1)] - moment[this.getIndex(cube.r0, cube.g0, cube.b0)];
3549
+ }
3550
+ bottom(cube, direction, moment) {
3551
+ switch (direction) {
3552
+ case directions.RED:
3553
+ return -moment[this.getIndex(cube.r0, cube.g1, cube.b1)] + moment[this.getIndex(cube.r0, cube.g1, cube.b0)] + moment[this.getIndex(cube.r0, cube.g0, cube.b1)] - moment[this.getIndex(cube.r0, cube.g0, cube.b0)];
3554
+ case directions.GREEN:
3555
+ return -moment[this.getIndex(cube.r1, cube.g0, cube.b1)] + moment[this.getIndex(cube.r1, cube.g0, cube.b0)] + moment[this.getIndex(cube.r0, cube.g0, cube.b1)] - moment[this.getIndex(cube.r0, cube.g0, cube.b0)];
3556
+ case directions.BLUE:
3557
+ return -moment[this.getIndex(cube.r1, cube.g1, cube.b0)] + moment[this.getIndex(cube.r1, cube.g0, cube.b0)] + moment[this.getIndex(cube.r0, cube.g1, cube.b0)] - moment[this.getIndex(cube.r0, cube.g0, cube.b0)];
3558
+ default:
3559
+ throw new Error('unexpected direction $direction');
3560
+ }
3561
+ }
3562
+ top(cube, direction, position, moment) {
3563
+ switch (direction) {
3564
+ case directions.RED:
3565
+ return moment[this.getIndex(position, cube.g1, cube.b1)] - moment[this.getIndex(position, cube.g1, cube.b0)] - moment[this.getIndex(position, cube.g0, cube.b1)] + moment[this.getIndex(position, cube.g0, cube.b0)];
3566
+ case directions.GREEN:
3567
+ return moment[this.getIndex(cube.r1, position, cube.b1)] - moment[this.getIndex(cube.r1, position, cube.b0)] - moment[this.getIndex(cube.r0, position, cube.b1)] + moment[this.getIndex(cube.r0, position, cube.b0)];
3568
+ case directions.BLUE:
3569
+ return moment[this.getIndex(cube.r1, cube.g1, position)] - moment[this.getIndex(cube.r1, cube.g0, position)] - moment[this.getIndex(cube.r0, cube.g1, position)] + moment[this.getIndex(cube.r0, cube.g0, position)];
3570
+ default:
3571
+ throw new Error('unexpected direction $direction');
3572
+ }
3573
+ }
3574
+ getIndex(r, g, b) {
3575
+ return (r << INDEX_BITS * 2) + (r << INDEX_BITS + 1) + r + (g << INDEX_BITS) + g + b;
3576
+ }
3577
+ }
3578
+ /**
3579
+ * Keeps track of the state of each box created as the Wu quantization
3580
+ * algorithm progresses through dividing the image's pixels as plotted in RGB.
3581
+ */
3582
+ class Box {
3583
+ constructor(r0 = 0, r1 = 0, g0 = 0, g1 = 0, b0 = 0, b1 = 0, vol = 0) {
3584
+ this.r0 = r0;
3585
+ this.r1 = r1;
3586
+ this.g0 = g0;
3587
+ this.g1 = g1;
3588
+ this.b0 = b0;
3589
+ this.b1 = b1;
3590
+ this.vol = vol;
3591
+ }
3592
+ }
3593
+ /**
3594
+ * Represents final result of Wu algorithm.
3595
+ */
3596
+ class CreateBoxesResult {
3597
+ /**
3598
+ * @param requestedCount how many colors the caller asked to be returned from
3599
+ * quantization.
3600
+ * @param resultCount the actual number of colors achieved from quantization.
3601
+ * May be lower than the requested count.
3602
+ */
3603
+ constructor(requestedCount, resultCount) {
3604
+ this.requestedCount = requestedCount;
3605
+ this.resultCount = resultCount;
3606
+ }
3607
+ }
3608
+ /**
3609
+ * Represents the result of calculating where to cut an existing box in such
3610
+ * a way to maximize variance between the two new boxes created by a cut.
3611
+ */
3612
+ class MaximizeResult {
3613
+ constructor(cutLocation, maximum) {
3614
+ this.cutLocation = cutLocation;
3615
+ this.maximum = maximum;
3616
+ }
3617
+ }
3618
+
3619
+ /**
3620
+ * @license
3621
+ * Copyright 2021 Google LLC
3622
+ *
3623
+ * Licensed under the Apache License, Version 2.0 (the "License");
3624
+ * you may not use this file except in compliance with the License.
3625
+ * You may obtain a copy of the License at
3626
+ *
3627
+ * http://www.apache.org/licenses/LICENSE-2.0
3628
+ *
3629
+ * Unless required by applicable law or agreed to in writing, software
3630
+ * distributed under the License is distributed on an "AS IS" BASIS,
3631
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3632
+ * See the License for the specific language governing permissions and
3633
+ * limitations under the License.
3634
+ */
3635
+ /**
3636
+ * An image quantizer that improves on the quality of a standard K-Means
3637
+ * algorithm by setting the K-Means initial state to the output of a Wu
3638
+ * quantizer, instead of random centroids. Improves on speed by several
3639
+ * optimizations, as implemented in Wsmeans, or Weighted Square Means, K-Means
3640
+ * with those optimizations.
3641
+ *
3642
+ * This algorithm was designed by M. Emre Celebi, and was found in their 2011
3643
+ * paper, Improving the Performance of K-Means for Color Quantization.
3644
+ * https://arxiv.org/abs/1101.0395
3645
+ */
3646
+ // material_color_utilities is designed to have a consistent API across
3647
+ // platforms and modular components that can be moved around easily. Using a
3648
+ // class as a namespace facilitates this.
3649
+ //
3650
+ // tslint:disable-next-line:class-as-namespace
3651
+ class QuantizerCelebi {
3652
+ /**
3653
+ * @param pixels Colors in ARGB format.
3654
+ * @param maxColors The number of colors to divide the image into. A lower
3655
+ * number of colors may be returned.
3656
+ * @return Map with keys of colors in ARGB format, and values of number of
3657
+ * pixels in the original image that correspond to the color in the
3658
+ * quantized image.
3659
+ */
3660
+ static quantize(pixels, maxColors) {
3661
+ const wu = new QuantizerWu();
3662
+ const wuResult = wu.quantize(pixels, maxColors);
3663
+ return QuantizerWsmeans.quantize(pixels, wuResult, maxColors);
3664
+ }
3665
+ }
3666
+
2892
3667
  /**
2893
3668
  * @license
2894
3669
  * Copyright 2022 Google LLC
@@ -2990,6 +3765,145 @@ SchemeVibrant.secondaryRotations = [18.0, 15.0, 10.0, 12.0, 15.0, 18.0, 15.0, 12
2990
3765
  */
2991
3766
  SchemeVibrant.tertiaryRotations = [35.0, 30.0, 20.0, 25.0, 30.0, 35.0, 30.0, 25.0, 25.0];
2992
3767
 
3768
+ /**
3769
+ * @license
3770
+ * Copyright 2021 Google LLC
3771
+ *
3772
+ * Licensed under the Apache License, Version 2.0 (the "License");
3773
+ * you may not use this file except in compliance with the License.
3774
+ * You may obtain a copy of the License at
3775
+ *
3776
+ * http://www.apache.org/licenses/LICENSE-2.0
3777
+ *
3778
+ * Unless required by applicable law or agreed to in writing, software
3779
+ * distributed under the License is distributed on an "AS IS" BASIS,
3780
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3781
+ * See the License for the specific language governing permissions and
3782
+ * limitations under the License.
3783
+ */
3784
+ const SCORE_OPTION_DEFAULTS = {
3785
+ desired: 4,
3786
+ fallbackColorARGB: 0xff4285f4,
3787
+ filter: true // Avoid unsuitable colors.
3788
+ };
3789
+ function compare(a, b) {
3790
+ if (a.score > b.score) {
3791
+ return -1;
3792
+ } else if (a.score < b.score) {
3793
+ return 1;
3794
+ }
3795
+ return 0;
3796
+ }
3797
+ /**
3798
+ * Given a large set of colors, remove colors that are unsuitable for a UI
3799
+ * theme, and rank the rest based on suitability.
3800
+ *
3801
+ * Enables use of a high cluster count for image quantization, thus ensuring
3802
+ * colors aren't muddied, while curating the high cluster count to a much
3803
+ * smaller number of appropriate choices.
3804
+ */
3805
+ class Score {
3806
+ constructor() {}
3807
+ /**
3808
+ * Given a map with keys of colors and values of how often the color appears,
3809
+ * rank the colors based on suitability for being used for a UI theme.
3810
+ *
3811
+ * @param colorsToPopulation map with keys of colors and values of how often
3812
+ * the color appears, usually from a source image.
3813
+ * @param {ScoreOptions} options optional parameters.
3814
+ * @return Colors sorted by suitability for a UI theme. The most suitable
3815
+ * color is the first item, the least suitable is the last. There will
3816
+ * always be at least one color returned. If all the input colors
3817
+ * were not suitable for a theme, a default fallback color will be
3818
+ * provided, Google Blue.
3819
+ */
3820
+ static score(colorsToPopulation, options) {
3821
+ const {
3822
+ desired,
3823
+ fallbackColorARGB,
3824
+ filter
3825
+ } = {
3826
+ ...SCORE_OPTION_DEFAULTS,
3827
+ ...options
3828
+ };
3829
+ // Get the HCT color for each Argb value, while finding the per hue count and
3830
+ // total count.
3831
+ const colorsHct = [];
3832
+ const huePopulation = new Array(360).fill(0);
3833
+ let populationSum = 0;
3834
+ for (const [argb, population] of colorsToPopulation.entries()) {
3835
+ const hct = Hct.fromInt(argb);
3836
+ colorsHct.push(hct);
3837
+ const hue = Math.floor(hct.hue);
3838
+ huePopulation[hue] += population;
3839
+ populationSum += population;
3840
+ }
3841
+ // Hues with more usage in neighboring 30 degree slice get a larger number.
3842
+ const hueExcitedProportions = new Array(360).fill(0.0);
3843
+ for (let hue = 0; hue < 360; hue++) {
3844
+ const proportion = huePopulation[hue] / populationSum;
3845
+ for (let i = hue - 14; i < hue + 16; i++) {
3846
+ const neighborHue = sanitizeDegreesInt(i);
3847
+ hueExcitedProportions[neighborHue] += proportion;
3848
+ }
3849
+ }
3850
+ // Scores each HCT color based on usage and chroma, while optionally
3851
+ // filtering out values that do not have enough chroma or usage.
3852
+ const scoredHct = new Array();
3853
+ for (const hct of colorsHct) {
3854
+ const hue = sanitizeDegreesInt(Math.round(hct.hue));
3855
+ const proportion = hueExcitedProportions[hue];
3856
+ if (filter && (hct.chroma < Score.CUTOFF_CHROMA || proportion <= Score.CUTOFF_EXCITED_PROPORTION)) {
3857
+ continue;
3858
+ }
3859
+ const proportionScore = proportion * 100.0 * Score.WEIGHT_PROPORTION;
3860
+ const chromaWeight = hct.chroma < Score.TARGET_CHROMA ? Score.WEIGHT_CHROMA_BELOW : Score.WEIGHT_CHROMA_ABOVE;
3861
+ const chromaScore = (hct.chroma - Score.TARGET_CHROMA) * chromaWeight;
3862
+ const score = proportionScore + chromaScore;
3863
+ scoredHct.push({
3864
+ hct,
3865
+ score
3866
+ });
3867
+ }
3868
+ // Sorted so that colors with higher scores come first.
3869
+ scoredHct.sort(compare);
3870
+ // Iterates through potential hue differences in degrees in order to select
3871
+ // the colors with the largest distribution of hues possible. Starting at
3872
+ // 90 degrees(maximum difference for 4 colors) then decreasing down to a
3873
+ // 15 degree minimum.
3874
+ const chosenColors = [];
3875
+ for (let differenceDegrees$1 = 90; differenceDegrees$1 >= 15; differenceDegrees$1--) {
3876
+ chosenColors.length = 0;
3877
+ for (const {
3878
+ hct
3879
+ } of scoredHct) {
3880
+ const duplicateHue = chosenColors.find(chosenHct => {
3881
+ return differenceDegrees(hct.hue, chosenHct.hue) < differenceDegrees$1;
3882
+ });
3883
+ if (!duplicateHue) {
3884
+ chosenColors.push(hct);
3885
+ }
3886
+ if (chosenColors.length >= desired) break;
3887
+ }
3888
+ if (chosenColors.length >= desired) break;
3889
+ }
3890
+ const colors = [];
3891
+ if (chosenColors.length === 0) {
3892
+ colors.push(fallbackColorARGB);
3893
+ }
3894
+ for (const chosenHct of chosenColors) {
3895
+ colors.push(chosenHct.toInt());
3896
+ }
3897
+ return colors;
3898
+ }
3899
+ }
3900
+ Score.TARGET_CHROMA = 48.0; // A1 Chroma
3901
+ Score.WEIGHT_PROPORTION = 0.7;
3902
+ Score.WEIGHT_CHROMA_ABOVE = 0.3;
3903
+ Score.WEIGHT_CHROMA_BELOW = 0.1;
3904
+ Score.CUTOFF_CHROMA = 5.0;
3905
+ Score.CUTOFF_EXCITED_PROPORTION = 0.01;
3906
+
2993
3907
  /**
2994
3908
  * @license
2995
3909
  * Copyright 2021 Google LLC
@@ -3063,6 +3977,96 @@ function parseIntHex(value) {
3063
3977
  return parseInt(value, 16);
3064
3978
  }
3065
3979
 
3980
+ /**
3981
+ * @license
3982
+ * Copyright 2021 Google LLC
3983
+ *
3984
+ * Licensed under the Apache License, Version 2.0 (the "License");
3985
+ * you may not use this file except in compliance with the License.
3986
+ * You may obtain a copy of the License at
3987
+ *
3988
+ * http://www.apache.org/licenses/LICENSE-2.0
3989
+ *
3990
+ * Unless required by applicable law or agreed to in writing, software
3991
+ * distributed under the License is distributed on an "AS IS" BASIS,
3992
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3993
+ * See the License for the specific language governing permissions and
3994
+ * limitations under the License.
3995
+ */
3996
+ /**
3997
+ * Get the source color from an image.
3998
+ *
3999
+ * @param image The image element
4000
+ * @return Source color - the color most suitable for creating a UI theme
4001
+ */
4002
+ async function sourceColorFromImage(image) {
4003
+ // Convert Image data to Pixel Array
4004
+ const imageBytes = await new Promise((resolve, reject) => {
4005
+ const canvas = document.createElement('canvas');
4006
+ const context = canvas.getContext('2d');
4007
+ if (!context) {
4008
+ reject(new Error('Could not get canvas context'));
4009
+ return;
4010
+ }
4011
+ const loadCallback = () => {
4012
+ canvas.width = image.width;
4013
+ canvas.height = image.height;
4014
+ context.drawImage(image, 0, 0);
4015
+ let rect = [0, 0, image.width, image.height];
4016
+ const area = image.dataset['area'];
4017
+ if (area && /^\d+(\s*,\s*\d+){3}$/.test(area)) {
4018
+ rect = area.split(/\s*,\s*/).map(s => {
4019
+ // tslint:disable-next-line:ban
4020
+ return parseInt(s, 10);
4021
+ });
4022
+ }
4023
+ const [sx, sy, sw, sh] = rect;
4024
+ resolve(context.getImageData(sx, sy, sw, sh).data);
4025
+ };
4026
+ const errorCallback = () => {
4027
+ reject(new Error('Image load failed'));
4028
+ };
4029
+ if (image.complete) {
4030
+ loadCallback();
4031
+ } else {
4032
+ image.onload = loadCallback;
4033
+ image.onerror = errorCallback;
4034
+ }
4035
+ });
4036
+ // Convert Image data to Pixel Array
4037
+ const pixels = [];
4038
+ for (let i = 0; i < imageBytes.length; i += 4) {
4039
+ const r = imageBytes[i];
4040
+ const g = imageBytes[i + 1];
4041
+ const b = imageBytes[i + 2];
4042
+ const a = imageBytes[i + 3];
4043
+ if (a < 255) {
4044
+ continue;
4045
+ }
4046
+ const argb = argbFromRgb(r, g, b);
4047
+ pixels.push(argb);
4048
+ }
4049
+ // Convert Pixels to Material Colors
4050
+ const result = QuantizerCelebi.quantize(pixels, 128);
4051
+ const ranked = Score.score(result);
4052
+ const top = ranked[0];
4053
+ return top;
4054
+ }
4055
+
4056
+ /**
4057
+ * Extracts the Material source color from an image and returns it as a hex string.
4058
+ * @param {HTMLImageElement} image - A fully loaded image element. Pixel data is sampled to derive the source color.
4059
+ * @returns {Promise<string>} A hex color string (`#RRGGBB`) representing the extracted source color.
4060
+ *
4061
+ * @example
4062
+ * const img = document.querySelector("img");
4063
+ * const color = await getColorFromImage(img);
4064
+ * // "#6750A4"
4065
+ */
4066
+ async function getColorFromImage(image) {
4067
+ return hexFromArgb(await sourceColorFromImage(image));
4068
+ }
4069
+
3066
4070
  var _M3eThemeElement_instances, _M3eThemeElement_styleSheet, _M3eThemeElement_firstUpdated, _M3eThemeElement_light, _M3eThemeElement_dark, _M3eThemeElement_forcedColor, _M3eThemeElement_colorSchemeChangeHandler, _M3eThemeElement_apply, _M3eThemeElement_getContrastLevel;
3067
4071
  /**
3068
4072
  * A non-visual element responsible for application-level theming.
@@ -3301,5 +4305,5 @@ __decorate([property({
3301
4305
  __decorate([property()], M3eThemeElement.prototype, "motion", void 0);
3302
4306
  M3eThemeElement = __decorate([customElement("m3e-theme")], M3eThemeElement);
3303
4307
 
3304
- export { M3eThemeElement };
4308
+ export { M3eThemeElement, getColorFromImage };
3305
4309
  //# sourceMappingURL=theme.js.map