@thi.ng/pixel-analysis 1.1.1 → 2.0.0

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-27T16:56:27Z
3
+ - **Last updated**: 2025-07-30T22:32:35Z
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,19 @@ 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
+ # [2.0.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/pixel-analysis@2.0.0) (2025-07-30)
15
+
16
+ #### 🛑 Breaking changes
17
+
18
+ - update color analysis result types, add aggregation ([8c70504](https://github.com/thi-ng/umbrella/commit/8c70504))
19
+ - BREAKING CHANGE: update color analysis result types, add aggregation
20
+ - add `BaseColorAnalysisResult`, `AggregatedColorAnalysisResult`
21
+ - update `ColorAnalysisResult`, use `Metrics`
22
+ - add `aggregateColorResults()`
23
+ - update `analyzeColors()`, `deriveColorResults()`
24
+ - rename `computeHueRange()` => `hueRange()`
25
+ - update deps
26
+
14
27
  ### [1.1.1](https://github.com/thi-ng/umbrella/tree/@thi.ng/pixel-analysis@1.1.1) (2025-07-27)
15
28
 
16
29
  #### 🩹 Bug fixes
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![Mastodon Follow](https://img.shields.io/mastodon/follow/109331703950160316?domain=https%3A%2F%2Fmastodon.thi.ng&style=social)](https://mastodon.thi.ng/@toxi)
8
8
 
9
9
  > [!NOTE]
10
- > This is one of 209 standalone projects, maintained as part
10
+ > This is one of 210 standalone projects, maintained as part
11
11
  > of the [@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo
12
12
  > and anti-framework.
13
13
  >
@@ -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.89 KB
84
+ Package sizes (brotli'd, pre-treeshake): ESM: 1.87 KB
85
85
 
86
86
  ## Dependencies
87
87
 
@@ -90,6 +90,7 @@ Package sizes (brotli'd, pre-treeshake): ESM: 1.89 KB
90
90
  - [@thi.ng/color](https://github.com/thi-ng/umbrella/tree/develop/packages/color)
91
91
  - [@thi.ng/compare](https://github.com/thi-ng/umbrella/tree/develop/packages/compare)
92
92
  - [@thi.ng/math](https://github.com/thi-ng/umbrella/tree/develop/packages/math)
93
+ - [@thi.ng/metrics](https://github.com/thi-ng/umbrella/tree/develop/packages/metrics)
93
94
  - [@thi.ng/pixel](https://github.com/thi-ng/umbrella/tree/develop/packages/pixel)
94
95
  - [@thi.ng/pixel-convolve](https://github.com/thi-ng/umbrella/tree/develop/packages/pixel-convolve)
95
96
  - [@thi.ng/pixel-dominant-colors](https://github.com/thi-ng/umbrella/tree/develop/packages/pixel-dominant-colors)
@@ -1,6 +1,6 @@
1
1
  import { FloatBuffer } from "@thi.ng/pixel/float";
2
2
  import { IntBuffer } from "@thi.ng/pixel/int";
3
- import type { ColorAnalysisOpts, ColorAnalysisResult } from "./api.js";
3
+ import type { AggregatedColorAnalysisResult, BaseColorAnalysisResult, ColorAnalysisOpts, ColorAnalysisResult } from "./api.js";
4
4
  import { type DEFAULT_TEMPERATURE_COEFFS } from "./hues.js";
5
5
  /**
6
6
  * Performs a set of image/color analyses on provided pixel buffer.
@@ -10,12 +10,15 @@ import { type DEFAULT_TEMPERATURE_COEFFS } from "./hues.js";
10
10
  */
11
11
  export declare const analyzeColors: (img: FloatBuffer | IntBuffer, opts?: Partial<ColorAnalysisOpts>) => ColorAnalysisResult;
12
12
  /**
13
- * Computes a number of metrics (partial {@link ColorAnalysisResult}) derived from
14
- * given raw SRGB colors. Helper function for {@link analyzeColors}.
13
+ * Computes a number of metrics (partial {@link ColorAnalysisResult}) derived
14
+ * from given raw SRGB colors and their (normalized) areas. Helper function for
15
+ * {@link analyzeColors}.
15
16
  *
16
17
  * @param colors
18
+ * @param areas
17
19
  * @param minSat
18
20
  * @param tempCoeffs
19
21
  */
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">;
22
+ export declare const deriveColorResults: (colors: number[][], areas?: number[], minSat?: number, tempCoeffs?: typeof DEFAULT_TEMPERATURE_COEFFS) => BaseColorAnalysisResult;
23
+ export declare const aggregateColorResults: (results: Omit<ColorAnalysisResult, "img" | "imgGray" | "imgHsv">[], numColors?: number) => AggregatedColorAnalysisResult;
21
24
  //# sourceMappingURL=analyze-colors.d.ts.map
package/analyze-colors.js CHANGED
@@ -6,8 +6,16 @@ import { oklch } from "@thi.ng/color/oklch/oklch";
6
6
  import { srgb } from "@thi.ng/color/srgb/srgb";
7
7
  import { compareByKey } from "@thi.ng/compare/keys";
8
8
  import { compareNumDesc } from "@thi.ng/compare/numeric";
9
- import { TAU } from "@thi.ng/math/api";
10
9
  import { fit } from "@thi.ng/math/fit";
10
+ import {
11
+ aggregateCircularMetrics,
12
+ aggregateMetrics,
13
+ aggregateWeightedMetrics,
14
+ defCircularMetric,
15
+ defMetric,
16
+ defWeightedMetric
17
+ } from "@thi.ng/metrics/metrics";
18
+ import { dominantColorsMeanCut } from "@thi.ng/pixel-dominant-colors";
11
19
  import { dominantColorsKmeans } from "@thi.ng/pixel-dominant-colors/kmeans";
12
20
  import { FloatBuffer } from "@thi.ng/pixel/float";
13
21
  import { FLOAT_GRAY } from "@thi.ng/pixel/format/float-gray";
@@ -16,20 +24,11 @@ import { FLOAT_RGBA } from "@thi.ng/pixel/format/float-rgba";
16
24
  import { IntBuffer } from "@thi.ng/pixel/int";
17
25
  import { map } from "@thi.ng/transducers/map";
18
26
  import { max } from "@thi.ng/transducers/max";
19
- import { minMax } from "@thi.ng/transducers/min-max";
20
27
  import { permutations } from "@thi.ng/transducers/permutations";
21
- import { reduce } from "@thi.ng/transducers/reduce";
22
28
  import { transduce } from "@thi.ng/transducers/transduce";
23
- import { circularMean, circularSD } from "@thi.ng/vectors/circular";
24
- import { dot } from "@thi.ng/vectors/dot";
25
- import { vmean } from "@thi.ng/vectors/mean";
26
- import { mulN } from "@thi.ng/vectors/muln";
27
29
  import { roundN } from "@thi.ng/vectors/roundn";
28
- import { sd } from "@thi.ng/vectors/variance";
29
- import {
30
- computeHueRange,
31
- temperature
32
- } from "./hues.js";
30
+ import { ones } from "@thi.ng/vectors/setn";
31
+ import { temperature } from "./hues.js";
33
32
  const analyzeColors = (img, opts) => {
34
33
  let $img = img.format !== FLOAT_RGBA ? img.as(FLOAT_RGBA) : img;
35
34
  if (opts?.size) $img = __resize($img, opts.size);
@@ -37,68 +36,46 @@ const analyzeColors = (img, opts) => {
37
36
  const imgHsv = $img.as(FLOAT_HSVA);
38
37
  const colors = __dominantColors($img, opts);
39
38
  const colorAreas = colors.map((x) => x.area);
40
- const derived = derivedColorResults(
39
+ const derived = deriveColorResults(
41
40
  colors.map((x) => x.color),
41
+ colorAreas,
42
42
  opts?.minSat,
43
43
  opts?.tempCoeffs
44
44
  );
45
- const lumaRangeImg = reduce(minMax(), imgGray.data);
46
- const weightedLuma = dot(derived.srgb.map(luminanceSrgb), colorAreas);
47
- const weightedChroma = dot(
48
- derived.oklch.map((x) => x[1]),
49
- colorAreas
50
- );
51
- const weightedSat = dot(
52
- derived.hsv.map((x) => x[1]),
53
- colorAreas
54
- );
45
+ const lumImg = defMetric(imgGray.data);
55
46
  return {
47
+ ...derived,
56
48
  img: $img,
57
49
  imgGray,
58
50
  imgHsv,
59
- ...derived,
60
- area: colorAreas,
51
+ lumImg,
61
52
  temperature: temperature(imgHsv, opts?.minSat, opts?.tempCoeffs),
62
- contrastImg: lumaRangeImg[1] - lumaRangeImg[0],
63
- lumaRangeImg,
64
- weightedSat,
65
- weightedLuma,
66
- weightedChroma
53
+ contrastImg: lumImg.max - lumImg.min
67
54
  };
68
55
  };
69
- const derivedColorResults = (colors, minSat, tempCoeffs) => {
56
+ const deriveColorResults = (colors, areas = ones(colors.length), minSat, tempCoeffs) => {
70
57
  const dominantLuma = colors.map((x) => luminanceSrgb(x));
71
58
  const dominantSrgb = colors.map((x) => srgb(x));
72
59
  const dominantHsv = dominantSrgb.map((x) => hsv(x));
73
60
  const dominantOklch = dominantSrgb.map((x) => oklch(x));
74
61
  const dominantCss = dominantSrgb.map((x) => css(x));
75
62
  const hues = dominantHsv.map((x) => x[0]);
76
- const huesRad = mulN([], hues, TAU);
77
63
  const sats = dominantHsv.map((x) => x[1]);
78
- const meanHue = circularMean(huesRad) / TAU;
79
- const hueRange = computeHueRange(hues, meanHue);
80
- const lumaRange = reduce(minMax(), dominantLuma);
64
+ const lum = defWeightedMetric(dominantLuma, areas);
81
65
  return {
82
66
  css: dominantCss,
83
67
  srgb: dominantSrgb,
84
68
  hsv: dominantHsv,
85
69
  oklch: dominantOklch,
86
- hue: {
87
- mean: meanHue,
88
- range: hueRange,
89
- sd: circularSD(huesRad) / TAU
90
- },
91
- sat: {
92
- mean: vmean(sats),
93
- range: reduce(minMax(), sats),
94
- sd: sd(sats)
95
- },
96
- luma: {
97
- mean: vmean(dominantLuma),
98
- range: lumaRange,
99
- sd: sd(dominantLuma)
100
- },
101
- contrast: lumaRange[1] - lumaRange[0],
70
+ hue: defCircularMetric(hues),
71
+ sat: defWeightedMetric(sats, areas),
72
+ chroma: defWeightedMetric(
73
+ dominantOklch.map((x) => x[1]),
74
+ areas
75
+ ),
76
+ lum,
77
+ areas,
78
+ contrast: lum.max - lum.min,
102
79
  colorContrast: fit(
103
80
  transduce(
104
81
  map((pair) => contrastWCAG(...pair)),
@@ -113,6 +90,33 @@ const derivedColorResults = (colors, minSat, tempCoeffs) => {
113
90
  temperature: temperature(dominantHsv, minSat, tempCoeffs)
114
91
  };
115
92
  };
93
+ const aggregateColorResults = (results, numColors = 4) => {
94
+ const dominant = dominantColorsMeanCut(
95
+ results.flatMap((res) => res.srgb),
96
+ numColors
97
+ ).map((x) => srgb(x.color));
98
+ return {
99
+ srgb: dominant,
100
+ css: dominant.map((x) => css(x)),
101
+ hsv: dominant.map((x) => hsv(x)),
102
+ oklch: dominant.map((x) => oklch(x)),
103
+ hue: aggregateCircularMetrics(results.map((x) => x.hue)),
104
+ sat: aggregateWeightedMetrics(results.map((x) => x.sat)),
105
+ chroma: aggregateWeightedMetrics(results.map((x) => x.chroma)),
106
+ lum: aggregateWeightedMetrics(results.map((x) => x.lum)),
107
+ lumImg: aggregateMetrics(results.map((x) => x.lumImg)),
108
+ contrast: defMetric(results.map((x) => x.contrast)),
109
+ contrastImg: defMetric(results.map((x) => x.contrastImg)),
110
+ colorContrast: defMetric(results.map((x) => x.colorContrast)),
111
+ temperature: {
112
+ meanHue: defCircularMetric(
113
+ results.map((x) => x.temperature.meanHue)
114
+ ),
115
+ temp: defMetric(results.map((x) => x.temperature.temp)),
116
+ areaTemp: defMetric(results.map((x) => x.temperature.areaTemp))
117
+ }
118
+ };
119
+ };
116
120
  const __dominantColors = (img, {
117
121
  dominantFn = dominantColorsKmeans,
118
122
  numColors = 4,
@@ -126,6 +130,7 @@ const __resize = ($img, size) => {
126
130
  return $img.resize(w, h);
127
131
  };
128
132
  export {
133
+ aggregateColorResults,
129
134
  analyzeColors,
130
- derivedColorResults
135
+ deriveColorResults
131
136
  };
@@ -12,9 +12,9 @@ const { sqrt } = Math;
12
12
  const FMT_EDGE = FLOAT_GRAY_RANGE(-24, 24);
13
13
  const FMT_SOBEL = FLOAT_GRAY_RANGE(-4, 4);
14
14
  const analyzeFeatures = (img) => {
15
- const $img = img.format !== FLOAT_GRAY ? img.as(FLOAT_GRAY) : img;
16
- const { width, height } = $img;
15
+ const { width, height } = img;
17
16
  const numPixels = width * height;
17
+ const $img = img.format !== FLOAT_GRAY ? img.as(FLOAT_GRAY) : img;
18
18
  const imgEdge = convolveImage($img, { kernel: EDGE5 });
19
19
  imgEdge.format = FMT_EDGE;
20
20
  const imgSobelX = convolveImage($img, { kernel: SOBEL_X });
package/api.d.ts CHANGED
@@ -1,12 +1,9 @@
1
1
  import type { Fn2 } from "@thi.ng/api";
2
2
  import type { HSV, Oklch, SRGB } from "@thi.ng/color";
3
+ import type { Metric, WeightedMetric } from "@thi.ng/metrics";
3
4
  import type { FloatBuffer } from "@thi.ng/pixel";
4
5
  import type { DominantColor } from "@thi.ng/pixel-dominant-colors";
5
6
  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
7
  export interface ColorAnalysisOpts {
11
8
  /**
12
9
  * Max. number of dominant colors.
@@ -37,22 +34,7 @@ export interface ColorAnalysisOpts {
37
34
  */
38
35
  prec: number;
39
36
  }
40
- /**
41
- * Result data structure returned by {@link analyzeColors}.
42
- */
43
- export interface ColorAnalysisResult {
44
- /**
45
- * Input image, possibly converted to float RGBA & resized.
46
- */
47
- img: FloatBuffer;
48
- /**
49
- * Float grayscale version of {@link ColorAnalysisResult.img}.
50
- */
51
- imgGray: FloatBuffer;
52
- /**
53
- * Float HSV version of {@link ColorAnalysisResult.img}.
54
- */
55
- imgHsv: FloatBuffer;
37
+ export interface BaseColorAnalysisResult {
56
38
  /**
57
39
  * Dominant colors as CSS (hex) strings
58
40
  */
@@ -70,58 +52,31 @@ export interface ColorAnalysisResult {
70
52
  */
71
53
  oklch: Oklch[];
72
54
  /**
73
- * Normalized areas of dominant color clusters
74
- */
75
- area: number[];
76
- hue: {
77
- /**
78
- * Normalized mean hue (using circular mean).
79
- */
80
- mean: number;
81
- /**
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}.
86
- */
87
- range: Range;
88
- /**
89
- * Circular standard deviation of normalized hues.
90
- */
91
- sd: number;
92
- };
93
- sat: {
94
- /**
95
- * Mean saturation
96
- */
97
- mean: number;
98
- /**
99
- * Min/max HSV saturation range of dominant colors
100
- */
101
- range: Range;
102
- /**
103
- * Standard deviation of normalized saturation.
104
- */
105
- sd: number;
106
- };
107
- luma: {
108
- /**
109
- * Mean luminance
110
- */
111
- mean: number;
112
- /**
113
- * Min/max luminance range of dominant colors (obtained from SRGB)
114
- */
115
- range: Range;
116
- /**
117
- * Standard deviation of normalized luminance.
118
- */
119
- sd: number;
120
- };
55
+ * HSV hue metrics of dominant colors.
56
+ *
57
+ * @remarks
58
+ * The mean and standard deviation are computed using circular versions. In
59
+ * case of circular overflow (1.0 => 0, aka 360 => 0 degrees), the min hue
60
+ * WILL be greater than the max hue (e.g. a hue range of `[0.8, 0.2]`
61
+ * indicates the hue range from magenta -> orange).
62
+ */
63
+ hue: Metric;
121
64
  /**
122
- * Min/max luminance range of entire grayscale image (obtained from SRGB)
65
+ * HSV saturation metrics of dominant colors.
123
66
  */
124
- lumaRangeImg: Range;
67
+ sat: WeightedMetric;
68
+ /**
69
+ * Oklch chroma metrics of dominant colors.
70
+ */
71
+ chroma: WeightedMetric;
72
+ /**
73
+ * sRGB-based luminance metrics of dominant colors.
74
+ */
75
+ lum: WeightedMetric;
76
+ /**
77
+ * Normalized areas of dominant colors
78
+ */
79
+ areas: number[];
125
80
  /**
126
81
  * Comprehensive {@link TemperatureResult} as produced by
127
82
  * {@link temperature}. Also see {@link ColorAnalysisOpts.minSat} and
@@ -133,28 +88,60 @@ export interface ColorAnalysisResult {
133
88
  * {@link ColorAnalysisResult.lumaRange}).
134
89
  */
135
90
  contrast: number;
136
- /**
137
- * Luminance contrast of entire grayscale image (i.e. delta of
138
- * {@link ColorAnalysisResult.lumaRangeImg}).
139
- */
140
- contrastImg: number;
141
91
  /**
142
92
  * Max. normalized WCAG color contrast of dominant colors. The original WCAG
143
93
  * result range [1,21] is rescaled to [0,1].
144
94
  */
145
95
  colorContrast: number;
96
+ }
97
+ /**
98
+ * Result data structure returned by {@link analyzeColors}.
99
+ */
100
+ export interface ColorAnalysisResult extends BaseColorAnalysisResult {
101
+ /**
102
+ * Input image, possibly converted to float RGBA & resized.
103
+ */
104
+ img: FloatBuffer;
105
+ /**
106
+ * Float grayscale version of {@link ColorAnalysisResult.img}.
107
+ */
108
+ imgGray: FloatBuffer;
146
109
  /**
147
- * Average luminance of dominant colors, weighted by area.
110
+ * Float HSV version of {@link ColorAnalysisResult.img}.
148
111
  */
149
- weightedLuma: number;
112
+ imgHsv: FloatBuffer;
150
113
  /**
151
- * Average HSV saturation of dominant colors, weighted by area.
114
+ * Min/max luminance range of entire grayscale image (obtained from SRGB)
152
115
  */
153
- weightedSat: number;
116
+ lumImg: Metric;
154
117
  /**
155
- * Average Oklch chroma of dominant colors, weighted by area.
118
+ * Luminance contrast of entire grayscale image (i.e. delta of
119
+ * {@link ColorAnalysisResult.lumaRangeImg}).
156
120
  */
157
- weightedChroma: number;
121
+ contrastImg: number;
122
+ }
123
+ /**
124
+ * Aggregated version of {@link BaseColorAnalysisResult}. Return type of
125
+ * {@link aggregateColorResults}.
126
+ */
127
+ export interface AggregatedColorAnalysisResult {
128
+ css: string[];
129
+ srgb: SRGB[];
130
+ hsv: HSV[];
131
+ oklch: Oklch[];
132
+ hue: Metric;
133
+ sat: WeightedMetric;
134
+ chroma: WeightedMetric;
135
+ lum: WeightedMetric;
136
+ lumImg: Metric;
137
+ contrast: Metric;
138
+ contrastImg: Metric;
139
+ colorContrast: Metric;
140
+ temperature: {
141
+ meanHue: Metric;
142
+ temp: Metric;
143
+ areaTemp: Metric;
144
+ };
158
145
  }
159
146
  /**
160
147
  * Result type for {@link temparature}.
package/hues.d.ts CHANGED
@@ -87,15 +87,14 @@ export declare const hueRangeAreaIntensity: (colors: Iterable<ReadonlyVec>, hueR
87
87
  */
88
88
  export declare const meanIntensity: (colors: Iterable<ReadonlyVec>) => number;
89
89
  /**
90
- * Constructs the min/max range of given normalized `hues` around given mean hue
90
+ * Constructs a min/max range of given normalized `hues` around given `mean` hue
91
91
  * (also normalized), considering circular wrap-around. If `min > max` in the
92
- * result `[min,max]`, then the hue range is crossing 0 degrees.
92
+ * result `[min,max]`, then the hue range is crossing zero.
93
93
  *
94
94
  * @param hues
95
- * @param range
96
95
  * @param mean
97
96
  */
98
- export declare const computeHueRange: (hues: number[], mean: number) => [number, number];
97
+ export declare const hueRange: (values: import("@thi.ng/api").NumericArray, mean: number) => import("@thi.ng/metrics").Range;
99
98
  /**
100
99
  * Computes an abstract measure of a normalized "color temperature" of the given
101
100
  * HSV `colors` (also normalized in [0,1] range). Results closer to 1.0 indicate
package/hues.js CHANGED
@@ -1,15 +1,14 @@
1
1
  import { ensureArray } from "@thi.ng/arrays/ensure-array";
2
2
  import { compareByKey } from "@thi.ng/compare";
3
3
  import { TAU } from "@thi.ng/math/api";
4
+ import { roundTo } from "@thi.ng/math/prec";
5
+ import { smoothStep } from "@thi.ng/math/step";
6
+ import { circularRange } from "@thi.ng/metrics/metrics";
4
7
  import { normFrequenciesAuto, repeat, transduce } from "@thi.ng/transducers";
5
8
  import { map } from "@thi.ng/transducers/map";
6
9
  import { mapcat } from "@thi.ng/transducers/mapcat";
7
10
  import { mean } from "@thi.ng/transducers/mean";
8
- import { minMax } from "@thi.ng/transducers/min-max";
9
- import { reduce } from "@thi.ng/transducers/reduce";
10
11
  import { circularMean } from "@thi.ng/vectors/circular";
11
- import { fract, roundTo } from "@thi.ng/math/prec";
12
- import { smoothStep } from "@thi.ng/math/step";
13
12
  function* selectHueRange(colors, minHue, maxHue, minSat) {
14
13
  const pred = __hueSelector(minHue, maxHue);
15
14
  for (let col of colors) {
@@ -64,29 +63,7 @@ const meanIntensity = (colors) => transduce(
64
63
  mean(),
65
64
  colors
66
65
  );
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
- };
66
+ const hueRange = circularRange;
90
67
  const temperature = (colors, minSat = 0.2, coeffs) => {
91
68
  const $colors = ensureArray(colors);
92
69
  const filtered = $colors.filter((x) => x[1] >= minSat);
@@ -121,8 +98,8 @@ const DEFAULT_TEMPERATURE_COEFFS = [
121
98
  const hueTemperature = (hue, [a, b, c, d] = DEFAULT_TEMPERATURE_COEFFS) => 2 * (hue < 2 / 3 ? smoothStep(b, a, hue) : smoothStep(c, d, hue)) - 1;
122
99
  export {
123
100
  DEFAULT_TEMPERATURE_COEFFS,
124
- computeHueRange,
125
101
  countHueRange,
102
+ hueRange,
126
103
  hueRangeArea,
127
104
  hueRangeAreaIntensity,
128
105
  hueTemperature,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/pixel-analysis",
3
- "version": "1.1.1",
3
+ "version": "2.0.0",
4
4
  "description": "Image color & feature analysis utilities",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -39,16 +39,17 @@
39
39
  "tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
40
40
  },
41
41
  "dependencies": {
42
- "@thi.ng/api": "^8.11.32",
43
- "@thi.ng/arrays": "^2.13.5",
44
- "@thi.ng/color": "^5.7.52",
45
- "@thi.ng/compare": "^2.4.24",
46
- "@thi.ng/math": "^5.11.32",
47
- "@thi.ng/pixel": "^7.5.4",
48
- "@thi.ng/pixel-convolve": "^1.1.4",
49
- "@thi.ng/pixel-dominant-colors": "^2.0.7",
50
- "@thi.ng/transducers": "^9.6.3",
51
- "@thi.ng/vectors": "^8.5.1"
42
+ "@thi.ng/api": "^8.11.33",
43
+ "@thi.ng/arrays": "^2.13.6",
44
+ "@thi.ng/color": "^5.7.53",
45
+ "@thi.ng/compare": "^2.4.25",
46
+ "@thi.ng/math": "^5.11.33",
47
+ "@thi.ng/metrics": "^0.1.0",
48
+ "@thi.ng/pixel": "^7.5.5",
49
+ "@thi.ng/pixel-convolve": "^1.1.5",
50
+ "@thi.ng/pixel-dominant-colors": "^2.0.8",
51
+ "@thi.ng/transducers": "^9.6.4",
52
+ "@thi.ng/vectors": "^8.6.0"
52
53
  },
53
54
  "devDependencies": {
54
55
  "esbuild": "^0.25.8",
@@ -112,5 +113,5 @@
112
113
  "status": "beta",
113
114
  "year": 2024
114
115
  },
115
- "gitHead": "77f9f42528ac3c6209429fb798e3be69f52fcff2\n"
116
+ "gitHead": "1df8a3a9061257ea73ccf1ab9cdf63c81a9f519f\n"
116
117
  }