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.