@thi.ng/pixel-analysis 1.0.2 → 1.1.1

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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2025-07-26T12:33:06Z
3
+ - **Last updated**: 2025-07-27T16:56:27Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
@@ -11,6 +11,27 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
11
11
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
12
12
  and/or version bumps of transitive dependencies.
13
13
 
14
+ ### [1.1.1](https://github.com/thi-ng/umbrella/tree/@thi.ng/pixel-analysis@1.1.1) (2025-07-27)
15
+
16
+ #### 🩹 Bug fixes
17
+
18
+ - update/fix weightedLuma result ([19d818c](https://github.com/thi-ng/umbrella/commit/19d818c))
19
+
20
+ ## [1.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/pixel-analysis@1.1.0) (2025-07-27)
21
+
22
+ #### 🚀 Features
23
+
24
+ - make temperature()/hueTemperature() parametric ([f61955e](https://github.com/thi-ng/umbrella/commit/f61955e))
25
+ - add coefficients as optional arg
26
+ - add `DEFAULT_TEMPERATURE_COEFFS`
27
+ - update analyzeColors() & ColorAnalysisResult ([595064d](https://github.com/thi-ng/umbrella/commit/595064d))
28
+ - update/rename `AnalysisOpts` => `ColorAnalysisOpts`
29
+ - add `ColorAnalysisOpts.tempCoeffs`
30
+ - rename `AnalyzedImage` => `ColorAnalysisResult`
31
+ - update `ColorAnalysisResult` internal structure
32
+ - add `computeHueRange()`
33
+ - update `analyzeFeatures()`, add `FeatureAnalysisResult` ([e276eb8](https://github.com/thi-ng/umbrella/commit/e276eb8))
34
+
14
35
  ### [1.0.1](https://github.com/thi-ng/umbrella/tree/@thi.ng/pixel-analysis@1.0.1) (2025-07-24)
15
36
 
16
37
  #### 🩹 Bug fixes
package/README.md CHANGED
@@ -81,7 +81,7 @@ For Node.js REPL:
81
81
  const pa = await import("@thi.ng/pixel-analysis");
82
82
  ```
83
83
 
84
- Package sizes (brotli'd, pre-treeshake): ESM: 1.72 KB
84
+ Package sizes (brotli'd, pre-treeshake): ESM: 1.89 KB
85
85
 
86
86
  ## Dependencies
87
87
 
@@ -1,18 +1,21 @@
1
1
  import { FloatBuffer } from "@thi.ng/pixel/float";
2
2
  import { IntBuffer } from "@thi.ng/pixel/int";
3
- import type { AnalysisOpts, AnalyzedImage } from "./api.js";
3
+ import type { ColorAnalysisOpts, ColorAnalysisResult } from "./api.js";
4
+ import { type DEFAULT_TEMPERATURE_COEFFS } from "./hues.js";
4
5
  /**
5
6
  * Performs a set of image/color analyses on provided pixel buffer.
6
7
  *
7
8
  * @param img
8
9
  * @param opts
9
10
  */
10
- export declare const analyzeColors: (img: FloatBuffer | IntBuffer, opts?: Partial<AnalysisOpts>) => AnalyzedImage;
11
+ export declare const analyzeColors: (img: FloatBuffer | IntBuffer, opts?: Partial<ColorAnalysisOpts>) => ColorAnalysisResult;
11
12
  /**
12
- * Computes a number of metrics (partial {@link AnalyzedImage}) derived from
13
+ * Computes a number of metrics (partial {@link ColorAnalysisResult}) derived from
13
14
  * given raw SRGB colors. Helper function for {@link analyzeColors}.
14
15
  *
15
16
  * @param colors
17
+ * @param minSat
18
+ * @param tempCoeffs
16
19
  */
17
- export declare const derivedColorsResult: (colors: number[][], minSat?: number) => Pick<AnalyzedImage, "css" | "srgb" | "hsv" | "oklch" | "mean" | "sd" | "range" | "contrast" | "colorContrast" | "temperature">;
20
+ export declare const derivedColorResults: (colors: number[][], minSat?: number, tempCoeffs?: typeof DEFAULT_TEMPERATURE_COEFFS) => Pick<ColorAnalysisResult, "css" | "srgb" | "hsv" | "oklch" | "hue" | "sat" | "luma" | "contrast" | "colorContrast" | "temperature">;
18
21
  //# sourceMappingURL=analyze-colors.d.ts.map
package/analyze-colors.js CHANGED
@@ -18,15 +18,18 @@ import { map } from "@thi.ng/transducers/map";
18
18
  import { max } from "@thi.ng/transducers/max";
19
19
  import { minMax } from "@thi.ng/transducers/min-max";
20
20
  import { permutations } from "@thi.ng/transducers/permutations";
21
- import { pluck } from "@thi.ng/transducers/pluck";
22
21
  import { reduce } from "@thi.ng/transducers/reduce";
23
22
  import { transduce } from "@thi.ng/transducers/transduce";
24
23
  import { circularMean, circularSD } from "@thi.ng/vectors/circular";
25
24
  import { dot } from "@thi.ng/vectors/dot";
26
25
  import { vmean } from "@thi.ng/vectors/mean";
26
+ import { mulN } from "@thi.ng/vectors/muln";
27
27
  import { roundN } from "@thi.ng/vectors/roundn";
28
28
  import { sd } from "@thi.ng/vectors/variance";
29
- import { temperature } from "./hues.js";
29
+ import {
30
+ computeHueRange,
31
+ temperature
32
+ } from "./hues.js";
30
33
  const analyzeColors = (img, opts) => {
31
34
  let $img = img.format !== FLOAT_RGBA ? img.as(FLOAT_RGBA) : img;
32
35
  if (opts?.size) $img = __resize($img, opts.size);
@@ -34,9 +37,13 @@ const analyzeColors = (img, opts) => {
34
37
  const imgHsv = $img.as(FLOAT_HSVA);
35
38
  const colors = __dominantColors($img, opts);
36
39
  const colorAreas = colors.map((x) => x.area);
37
- const derived = derivedColorsResult(colors.map((x) => x.color));
40
+ const derived = derivedColorResults(
41
+ colors.map((x) => x.color),
42
+ opts?.minSat,
43
+ opts?.tempCoeffs
44
+ );
38
45
  const lumaRangeImg = reduce(minMax(), imgGray.data);
39
- const weightedLuma = dot(derived.range.luma, colorAreas);
46
+ const weightedLuma = dot(derived.srgb.map(luminanceSrgb), colorAreas);
40
47
  const weightedChroma = dot(
41
48
  derived.oklch.map((x) => x[1]),
42
49
  colorAreas
@@ -51,7 +58,7 @@ const analyzeColors = (img, opts) => {
51
58
  imgHsv,
52
59
  ...derived,
53
60
  area: colorAreas,
54
- temperature: temperature(imgHsv, opts?.minSat),
61
+ temperature: temperature(imgHsv, opts?.minSat, opts?.tempCoeffs),
55
62
  contrastImg: lumaRangeImg[1] - lumaRangeImg[0],
56
63
  lumaRangeImg,
57
64
  weightedSat,
@@ -59,37 +66,37 @@ const analyzeColors = (img, opts) => {
59
66
  weightedChroma
60
67
  };
61
68
  };
62
- const derivedColorsResult = (colors, minSat) => {
69
+ const derivedColorResults = (colors, minSat, tempCoeffs) => {
63
70
  const dominantLuma = colors.map((x) => luminanceSrgb(x));
64
71
  const dominantSrgb = colors.map((x) => srgb(x));
65
72
  const dominantHsv = dominantSrgb.map((x) => hsv(x));
66
73
  const dominantOklch = dominantSrgb.map((x) => oklch(x));
67
74
  const dominantCss = dominantSrgb.map((x) => css(x));
68
- const $hueRange = transduce(pluck(0), minMax(), dominantHsv);
69
- const hues = dominantHsv.map((x) => x[0] * TAU);
75
+ const hues = dominantHsv.map((x) => x[0]);
76
+ const huesRad = mulN([], hues, TAU);
70
77
  const sats = dominantHsv.map((x) => x[1]);
71
- const meanHue = circularMean(hues) / TAU;
72
- const hueRange = meanHue > $hueRange[1] || meanHue < $hueRange[0] ? [$hueRange[1], $hueRange[0]] : $hueRange;
78
+ const meanHue = circularMean(huesRad) / TAU;
79
+ const hueRange = computeHueRange(hues, meanHue);
73
80
  const lumaRange = reduce(minMax(), dominantLuma);
74
81
  return {
75
82
  css: dominantCss,
76
83
  srgb: dominantSrgb,
77
84
  hsv: dominantHsv,
78
85
  oklch: dominantOklch,
79
- mean: {
80
- hue: meanHue,
81
- sat: vmean(sats),
82
- luma: vmean(dominantLuma)
86
+ hue: {
87
+ mean: meanHue,
88
+ range: hueRange,
89
+ sd: circularSD(huesRad) / TAU
83
90
  },
84
- sd: {
85
- hue: circularSD(hues) / TAU,
86
- sat: sd(sats),
87
- luma: sd(dominantLuma)
91
+ sat: {
92
+ mean: vmean(sats),
93
+ range: reduce(minMax(), sats),
94
+ sd: sd(sats)
88
95
  },
89
- range: {
90
- hue: hueRange,
91
- sat: reduce(minMax(), sats),
92
- luma: lumaRange
96
+ luma: {
97
+ mean: vmean(dominantLuma),
98
+ range: lumaRange,
99
+ sd: sd(dominantLuma)
93
100
  },
94
101
  contrast: lumaRange[1] - lumaRange[0],
95
102
  colorContrast: fit(
@@ -103,7 +110,7 @@ const derivedColorsResult = (colors, minSat) => {
103
110
  0,
104
111
  1
105
112
  ),
106
- temperature: temperature(dominantHsv, minSat)
113
+ temperature: temperature(dominantHsv, minSat, tempCoeffs)
107
114
  };
108
115
  };
109
116
  const __dominantColors = (img, {
@@ -120,5 +127,5 @@ const __resize = ($img, size) => {
120
127
  };
121
128
  export {
122
129
  analyzeColors,
123
- derivedColorsResult
130
+ derivedColorResults
124
131
  };
@@ -1,10 +1,5 @@
1
1
  import { type FloatBuffer } from "@thi.ng/pixel/float";
2
2
  import type { IntBuffer } from "@thi.ng/pixel/int";
3
- export declare const analyzeFeatures: (img: FloatBuffer | IntBuffer) => {
4
- imgEdge: FloatBuffer;
5
- imgSobelX: FloatBuffer;
6
- imgSobelY: FloatBuffer;
7
- scoreEdge: number;
8
- scoreSobel: number;
9
- };
3
+ import type { FeatureAnalysisResult } from "./api.ts";
4
+ export declare const analyzeFeatures: (img: FloatBuffer | IntBuffer) => FeatureAnalysisResult;
10
5
  //# sourceMappingURL=analyze-features.d.ts.map
@@ -7,6 +7,8 @@ import {
7
7
  import {} from "@thi.ng/pixel/float";
8
8
  import { FLOAT_GRAY } from "@thi.ng/pixel/format/float-gray";
9
9
  import { FLOAT_GRAY_RANGE } from "@thi.ng/pixel/format/float-gray-range";
10
+ import { magSq } from "@thi.ng/vectors/magsq";
11
+ const { sqrt } = Math;
10
12
  const FMT_EDGE = FLOAT_GRAY_RANGE(-24, 24);
11
13
  const FMT_SOBEL = FLOAT_GRAY_RANGE(-4, 4);
12
14
  const analyzeFeatures = (img) => {
@@ -19,15 +21,20 @@ const analyzeFeatures = (img) => {
19
21
  imgSobelX.format = FMT_SOBEL;
20
22
  const imgSobelY = convolveImage($img, { kernel: SOBEL_Y });
21
23
  imgSobelY.format = FMT_SOBEL;
22
- const magSq = (img2) => img2.data.reduce((acc, x) => acc + x * x);
23
- const scoreEdge = Math.sqrt(magSq(imgEdge)) / numPixels;
24
- const scoreSobel = Math.sqrt(magSq(imgSobelX) + magSq(imgSobelY)) / numPixels;
24
+ const edge = sqrt(magSq(imgEdge.data)) / numPixels;
25
+ const sobX = magSq(imgSobelX.data);
26
+ const sobY = magSq(imgSobelY.data);
27
+ const sobelX = sqrt(sobX) / numPixels;
28
+ const sobelY = sqrt(sobY) / numPixels;
29
+ const sobel = sqrt(sobX + sobY) / numPixels;
25
30
  return {
26
31
  imgEdge,
27
32
  imgSobelX,
28
33
  imgSobelY,
29
- scoreEdge,
30
- scoreSobel
34
+ edge,
35
+ sobelX,
36
+ sobelY,
37
+ sobel
31
38
  };
32
39
  };
33
40
  export {
package/api.d.ts CHANGED
@@ -2,7 +2,12 @@ import type { Fn2 } from "@thi.ng/api";
2
2
  import type { HSV, Oklch, SRGB } from "@thi.ng/color";
3
3
  import type { FloatBuffer } from "@thi.ng/pixel";
4
4
  import type { DominantColor } from "@thi.ng/pixel-dominant-colors";
5
- export interface AnalysisOpts {
5
+ import type { DEFAULT_TEMPERATURE_COEFFS } from "./hues.js";
6
+ /**
7
+ * 2-tuple representing a closed [min,max] value range/interval
8
+ */
9
+ export type Range = [number, number];
10
+ export interface ColorAnalysisOpts {
6
11
  /**
7
12
  * Max. number of dominant colors.
8
13
  */
@@ -20,6 +25,11 @@ export interface AnalysisOpts {
20
25
  * Min. saturation to consider for computing {@link temperature}.
21
26
  */
22
27
  minSat: number;
28
+ /**
29
+ * Temperature curve hues/coefficients for computing {@link temparature}.
30
+ * See {@link DEFAULT_TEMPERATURE_COEFFS} for details.
31
+ */
32
+ tempCoeffs: typeof DEFAULT_TEMPERATURE_COEFFS;
23
33
  /**
24
34
  * Channel precision for dominant colors.
25
35
  *
@@ -27,17 +37,20 @@ export interface AnalysisOpts {
27
37
  */
28
38
  prec: number;
29
39
  }
30
- export interface AnalyzedImage {
40
+ /**
41
+ * Result data structure returned by {@link analyzeColors}.
42
+ */
43
+ export interface ColorAnalysisResult {
31
44
  /**
32
45
  * Input image, possibly converted to float RGBA & resized.
33
46
  */
34
47
  img: FloatBuffer;
35
48
  /**
36
- * Float grayscale version of {@link AnalyzedImage.img}.
49
+ * Float grayscale version of {@link ColorAnalysisResult.img}.
37
50
  */
38
51
  imgGray: FloatBuffer;
39
52
  /**
40
- * Float HSV version of {@link AnalyzedImage.img}.
53
+ * Float HSV version of {@link ColorAnalysisResult.img}.
41
54
  */
42
55
  imgHsv: FloatBuffer;
43
56
  /**
@@ -60,72 +73,74 @@ export interface AnalyzedImage {
60
73
  * Normalized areas of dominant color clusters
61
74
  */
62
75
  area: number[];
63
- mean: {
76
+ hue: {
64
77
  /**
65
78
  * Normalized mean hue (using circular mean).
66
79
  */
67
- hue: number;
80
+ mean: number;
68
81
  /**
69
- * Mean saturation
82
+ * Min/max HSV hue range of dominant colors. IMPORTANT: In case of
83
+ * circular overflow (360 => 0 degrees), the min hue WILL be greater
84
+ * than the max hue (e.g. a hue range of `[0.8, 0.2]` indicates the hue
85
+ * range from magenta -> orange). Also see {@link ColorAnalysisResult.hue.mean}.
70
86
  */
71
- sat: number;
87
+ range: Range;
72
88
  /**
73
- * Mean luminance
89
+ * Circular standard deviation of normalized hues.
74
90
  */
75
- luma: number;
91
+ sd: number;
76
92
  };
77
- sd: {
93
+ sat: {
78
94
  /**
79
- * Circular standard deviation of normalized hues.
95
+ * Mean saturation
80
96
  */
81
- hue: number;
97
+ mean: number;
82
98
  /**
83
- * Standard deviation of normalized saturation.
99
+ * Min/max HSV saturation range of dominant colors
84
100
  */
85
- sat: number;
101
+ range: Range;
86
102
  /**
87
- * Standard deviation of normalized luminance.
103
+ * Standard deviation of normalized saturation.
88
104
  */
89
- luma: number;
105
+ sd: number;
90
106
  };
91
- range: {
107
+ luma: {
92
108
  /**
93
- * Min/max HSV hue range of dominant colors. IMPORTANT: In case of
94
- * circular overflow (360 => 0 degrees), the min hue WILL be greater
95
- * than the max hue (e.g. a hue range of `[0.8, 0.2]` indicates the hue
96
- * range from magenta -> orange). Also see {@link AnalyzedImage.mean.hue}.
109
+ * Mean luminance
97
110
  */
98
- hue: [number, number];
111
+ mean: number;
99
112
  /**
100
- * Min/max HSV saturation range of dominant colors
113
+ * Min/max luminance range of dominant colors (obtained from SRGB)
101
114
  */
102
- sat: [number, number];
115
+ range: Range;
103
116
  /**
104
- * Min/max luminance range of dominant colors (obtained from SRGB)
117
+ * Standard deviation of normalized luminance.
105
118
  */
106
- luma: [number, number];
119
+ sd: number;
107
120
  };
108
121
  /**
109
122
  * Min/max luminance range of entire grayscale image (obtained from SRGB)
110
123
  */
111
- lumaRangeImg: [number, number];
124
+ lumaRangeImg: Range;
112
125
  /**
113
126
  * Comprehensive {@link TemperatureResult} as produced by
114
- * {@link temperature}. Also see {@link AnalysisOpts.minSat}.
127
+ * {@link temperature}. Also see {@link ColorAnalysisOpts.minSat} and
128
+ * {@link ColorAnalysisOpts.tempCoeffs}.
115
129
  */
116
130
  temperature: TemperatureResult;
117
131
  /**
118
132
  * Luminance contrast of dominant colors (i.e. delta of
119
- * {@link AnalyzedImage.lumaRange}).
133
+ * {@link ColorAnalysisResult.lumaRange}).
120
134
  */
121
135
  contrast: number;
122
136
  /**
123
137
  * Luminance contrast of entire grayscale image (i.e. delta of
124
- * {@link AnalyzedImage.lumaRangeImg}).
138
+ * {@link ColorAnalysisResult.lumaRangeImg}).
125
139
  */
126
140
  contrastImg: number;
127
141
  /**
128
- * Max. normalized WCAG color contrast of dominant colors.
142
+ * Max. normalized WCAG color contrast of dominant colors. The original WCAG
143
+ * result range [1,21] is rescaled to [0,1].
129
144
  */
130
145
  colorContrast: number;
131
146
  /**
@@ -171,4 +186,13 @@ export interface TemperatureResult {
171
186
  */
172
187
  area: number;
173
188
  }
189
+ export interface FeatureAnalysisResult {
190
+ imgEdge: FloatBuffer;
191
+ imgSobelX: FloatBuffer;
192
+ imgSobelY: FloatBuffer;
193
+ edge: number;
194
+ sobel: number;
195
+ sobelX: number;
196
+ sobelY: number;
197
+ }
174
198
  //# sourceMappingURL=api.d.ts.map
package/hues.d.ts CHANGED
@@ -86,10 +86,20 @@ export declare const hueRangeAreaIntensity: (colors: Iterable<ReadonlyVec>, hueR
86
86
  * @param colors
87
87
  */
88
88
  export declare const meanIntensity: (colors: Iterable<ReadonlyVec>) => number;
89
+ /**
90
+ * Constructs the min/max range of given normalized `hues` around given mean hue
91
+ * (also normalized), considering circular wrap-around. If `min > max` in the
92
+ * result `[min,max]`, then the hue range is crossing 0 degrees.
93
+ *
94
+ * @param hues
95
+ * @param range
96
+ * @param mean
97
+ */
98
+ export declare const computeHueRange: (hues: number[], mean: number) => [number, number];
89
99
  /**
90
100
  * Computes an abstract measure of a normalized "color temperature" of the given
91
101
  * HSV `colors` (also normalized in [0,1] range). Results closer to 1.0 indicate
92
- * a prevalence of warmer colors, results closer to -1.0 mean more
102
+ * a prevalence of warmer (red/orange) colors, results closer to -1.0 mean more
93
103
  * colder/blue-ish colors present.
94
104
  *
95
105
  * @remarks
@@ -109,8 +119,24 @@ export declare const meanIntensity: (colors: Iterable<ReadonlyVec>) => number;
109
119
  *
110
120
  * @param colors
111
121
  * @param minSat
122
+ * @param coeffs
123
+ */
124
+ export declare const temperature: (colors: Iterable<ReadonlyVec>, minSat?: number, coeffs?: typeof DEFAULT_TEMPERATURE_COEFFS) => TemperatureResult;
125
+ /**
126
+ * Default temperature curve hues/coefficients for {@link hueTemperature}.
127
+ *
128
+ * @remarks
129
+ * The four values (all in [0,1] range) are to be giving in this order:
130
+ *
131
+ * - A: start hue of warm falloff
132
+ * - B: end hue of warm falloff
133
+ * - C: start hue of warm rise
134
+ * - D: end hue of warm rise
135
+ *
136
+ * Note: The hot point is fixed at 0 (aka red) and the cold point is fixed at
137
+ * 2/3 (= 0.666... aka blue), meaning C & D MUST be greater.
112
138
  */
113
- export declare const temperature: (colors: Iterable<ReadonlyVec>, minSat?: number) => TemperatureResult;
139
+ export declare const DEFAULT_TEMPERATURE_COEFFS: [number, number, number, number];
114
140
  /**
115
141
  * Computes an abstract measure of a normalized "color temperature" ([-1,1]
116
142
  * range) for the given `hue` (in [0,1] range). Red/orange/yellow hues produce
@@ -122,6 +148,7 @@ export declare const temperature: (colors: Iterable<ReadonlyVec>, minSat?: numbe
122
148
  * input hue.
123
149
  *
124
150
  * @param hue
151
+ * @param coeffs
125
152
  */
126
- export declare const hueTemperature: (hue: number) => number;
153
+ export declare const hueTemperature: (hue: number, [a, b, c, d]?: [number, number, number, number]) => number;
127
154
  //# sourceMappingURL=hues.d.ts.map
package/hues.js CHANGED
@@ -1,11 +1,15 @@
1
1
  import { ensureArray } from "@thi.ng/arrays/ensure-array";
2
2
  import { compareByKey } from "@thi.ng/compare";
3
- import { roundTo, smoothStep, TAU } from "@thi.ng/math";
3
+ import { TAU } from "@thi.ng/math/api";
4
4
  import { normFrequenciesAuto, repeat, transduce } from "@thi.ng/transducers";
5
5
  import { map } from "@thi.ng/transducers/map";
6
6
  import { mapcat } from "@thi.ng/transducers/mapcat";
7
7
  import { mean } from "@thi.ng/transducers/mean";
8
+ import { minMax } from "@thi.ng/transducers/min-max";
9
+ import { reduce } from "@thi.ng/transducers/reduce";
8
10
  import { circularMean } from "@thi.ng/vectors/circular";
11
+ import { fract, roundTo } from "@thi.ng/math/prec";
12
+ import { smoothStep } from "@thi.ng/math/step";
9
13
  function* selectHueRange(colors, minHue, maxHue, minSat) {
10
14
  const pred = __hueSelector(minHue, maxHue);
11
15
  for (let col of colors) {
@@ -60,7 +64,30 @@ const meanIntensity = (colors) => transduce(
60
64
  mean(),
61
65
  colors
62
66
  );
63
- const temperature = (colors, minSat = 0.2) => {
67
+ const computeHueRange = (hues, mean2) => {
68
+ const range = reduce(minMax(), hues);
69
+ const [min, max] = range;
70
+ if (mean2 < min || mean2 > max) {
71
+ return [
72
+ hues.reduce(
73
+ (acc, x) => {
74
+ const d = fract(mean2 - x);
75
+ return d < 0.5 && d > acc[1] ? [x, d] : acc;
76
+ },
77
+ [max, fract(mean2 - max)]
78
+ )[0],
79
+ hues.reduce(
80
+ (acc, x) => {
81
+ const d = fract(x - mean2);
82
+ return d < 0.5 && d > acc[1] ? [x, d] : acc;
83
+ },
84
+ [min, fract(min - mean2)]
85
+ )[0]
86
+ ];
87
+ }
88
+ return range;
89
+ };
90
+ const temperature = (colors, minSat = 0.2, coeffs) => {
64
91
  const $colors = ensureArray(colors);
65
92
  const filtered = $colors.filter((x) => x[1] >= minSat);
66
93
  const area = filtered.length / $colors.length;
@@ -81,12 +108,20 @@ const temperature = (colors, minSat = 0.2) => {
81
108
  return { hues, meanHue: 0, temp: 0, areaTemp: 0, area: 0 };
82
109
  }
83
110
  const meanHue = circularMean(angles) / TAU;
84
- const temp = hueTemperature(meanHue);
111
+ const temp = hueTemperature(meanHue, coeffs);
85
112
  const areaTemp = temp * area;
86
113
  return { hues, meanHue, temp, areaTemp, area };
87
114
  };
88
- const hueTemperature = (hue) => 2 * (hue < 2 / 3 ? smoothStep(0.6, 0.1, hue) : smoothStep(0.72, 0.92, hue)) - 1;
115
+ const DEFAULT_TEMPERATURE_COEFFS = [
116
+ 0.1,
117
+ 0.6,
118
+ 0.72,
119
+ 0.92
120
+ ];
121
+ const hueTemperature = (hue, [a, b, c, d] = DEFAULT_TEMPERATURE_COEFFS) => 2 * (hue < 2 / 3 ? smoothStep(b, a, hue) : smoothStep(c, d, hue)) - 1;
89
122
  export {
123
+ DEFAULT_TEMPERATURE_COEFFS,
124
+ computeHueRange,
90
125
  countHueRange,
91
126
  hueRangeArea,
92
127
  hueRangeAreaIntensity,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/pixel-analysis",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "description": "Image color & feature analysis utilities",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -112,5 +112,5 @@
112
112
  "status": "beta",
113
113
  "year": 2024
114
114
  },
115
- "gitHead": "b138ae2fb8567acfe724cc675654878f60d26631\n"
115
+ "gitHead": "77f9f42528ac3c6209429fb798e3be69f52fcff2\n"
116
116
  }