PageSourceSearch

https://odysseus-lxcq.onrender.com/static/js/editor/fx/histogram.js

js odysseus-lxcq.onrender.com collected 2026-10-03 11:44:47 UTC 2,512 bytes, 67 lines download raw bytes

1/**
2 * Draw a luminance histogram of a layer's pixels onto the given
3 * canvas. Sampling is capped at ~400×400 so the call stays cheap on
4 * very large images.
5 *
6 * If the layer has a staged Levels adjustment
7 * (`layer._stagedAdj.params` with `inBlack` / `inWhite`), the two
8 * endpoint markers are drawn over the bars.
9 *
10 * @param {HTMLCanvasElement} canvas  The histogram canvas to render into.
11 * @param {{
12 *   canvas: HTMLCanvasElement,
13 *   _stagedAdj?: {params?: {inBlack?: number, inWhite?: number}}
14 * }} layer                            Source layer.
15 */
16export function drawHistogram(canvas, layer) {
17  if (!canvas) return;
18  const w = canvas.width, h = canvas.height;
19  const ctx = canvas.getContext('2d');
20  ctx.clearRect(0, 0, w, h);
21
22  // Down-sample huge images so the histogram stays interactive on 8k+
23  // photos. ~400×400 is enough to characterise the distribution.
24  const src = layer.canvas;
25  const sw = src.width, sh = src.height;
26  const maxSamples = 400;
27  const sampleW = Math.min(maxSamples, sw);
28  const sampleH = Math.min(maxSamples, sh);
29  const tmp = document.createElement('canvas');
30  tmp.width = sampleW; tmp.height = sampleH;
31  const tctx = tmp.getContext('2d');
32  tctx.drawImage(src, 0, 0, sampleW, sampleH);
33  const img = tctx.getImageData(0, 0, sampleW, sampleH).data;
34
35  const hist = new Uint32Array(256);
36  for (let i = 0; i < img.length; i += 4) {
37    if (img[i + 3] < 8) continue; // skip near-transparent
38    // Rec. 709 luminance — common choice for histograms in photo editors.
39    const Y = (0.2126 * img[i] + 0.7152 * img[i + 1] + 0.0722 * img[i + 2]) | 0;
40    hist[Math.min(255, Y)]++;
41  }
42  let peak = 1;
43  for (let i = 0; i < 256; i++) if (hist[i] > peak) peak = hist[i];
44
45  // Background.
46  ctx.fillStyle = 'rgba(255,255,255,0.05)';
47  ctx.fillRect(0, 0, w, h);
48
49  // Bars. sqrt-scaled so the long tails (specular highlights, deep
50  // shadows) stay visible even when the central mass dominates.
51  ctx.fillStyle = 'rgba(255,255,255,0.55)';
52  for (let i = 0; i < 256; i++) {
53    const x = (i / 256) * w;
54    const bh = Math.pow(hist[i] / peak, 0.5) * h;
55    ctx.fillRect(x, h - bh, w / 256 + 0.5, bh);
56  }
57
58  // Endpoint markers (input black / input white) from a staged Levels
59  // adjustment, if one is in flight.
60  const p = layer._stagedAdj?.params;
61  if (p) {
62    ctx.fillStyle = 'rgba(0,0,0,0.9)';
63    ctx.fillRect((p.inBlack / 256) * w, 0, 1, h);
64    ctx.fillStyle = 'rgba(255,255,255,0.9)';
65    ctx.fillRect((p.inWhite / 256) * w, 0, 1, h);
66  }
67}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.