UNPKG

@thi.ng/geom-isoline

Version:

Fast 2D contour line extraction / generation

144 lines (143 loc) 3.14 kB
import { range2d } from "@thi.ng/transducers/range2d"; const EDGE_INDEX = [ -1, -1, 4, 0, 2, 0, 2, 0, 0, 0, -1, -1, 0, 0, 0, 0, 6, 0, 4, 0, -1, -1, 2, 0, 6, 0, 4, 0, 6, 0, -1, -1 ]; const NEXT_EDGES = [0, -1, 1, 0, 0, 1, -1, 0]; const S5 = [4, 8, 0, 2, 0, 26, 4, 14]; const S10 = [6, 4, 2, 16, 6, 22, 2, 28]; const setBorder = (src, w, h, val) => { const w1 = w - 1; const h1 = h - 1; const idxH1 = h1 * w; for (let x = 0; x < w; x++) { src[x] = src[idxH1 + x] = val; } for (let y = 0; y < h; y++) { const yy = y * w; src[yy] = src[w1 + yy] = val; } return src; }; const __encodeCrossings = (src, w, h, iso) => { const out = new Uint8Array(src.length); const w1 = w - 1; const h1 = h - 1; for (let y = 0, i = 0; y < h1; y++) { for (let x = 0; x < w1; i++, x++) { out[i] = (src[i] < iso ? 16 : 0) | (src[i + 1] < iso ? 8 : 0) | (src[i + 1 + w] < iso ? 4 : 0) | (src[i + w] < iso ? 2 : 0); } i++; } return out; }; const __cellValue = (src, w, idx) => { return (src[idx] + src[idx + 1] + src[idx + w] + src[idx + w + 1]) * 0.25; }; const __mix = (src, w, x1, y1, x2, y2, iso) => { const a = src[y1 * w + x1]; const b = src[y2 * w + x2]; return a === b ? 0 : (a - iso) / (a - b); }; const __contourVertex = [ (src, w, x, y, iso) => [x + __mix(src, w, x, y, x + 1, y, iso), y], (src, w, x, y, iso) => [x + 1, y + __mix(src, w, x + 1, y, x + 1, y + 1, iso)], (src, w, x, y, iso) => [x + __mix(src, w, x, y + 1, x + 1, y + 1, iso), y + 1], (src, w, x, y, iso) => [x, y + __mix(src, w, x, y, x, y + 1, iso)] ]; function* isolines(src, w, h, iso, scale = 1) { const coded = __encodeCrossings(src, w, h, iso); let curr = []; let from; let to = -1; let clear; let x; let y; const w1 = w - 1; const h1 = h - 1; const [sx, sy] = typeof scale === "number" ? [scale, scale] : scale; const cells = range2d(h, w); let next = true; let idx; while (true) { from = to; if (next) { const c = cells.next(); if (c.done) break; [y, x] = c.value; next = false; } if (x >= w1 || y >= h1) { next = true; continue; } const i = y * w + x; const id = coded[i]; if (id === 10) { idx = (__cellValue(src, w, i) > iso ? 0 : 4) + (from === 6 ? 0 : 2); to = S5[idx]; clear = S5[idx + 1]; } else if (id === 20) { idx = __cellValue(src, w, i) > iso ? from === 0 ? 0 : 2 : from === 4 ? 4 : 6; to = S10[idx]; clear = S10[idx + 1]; } else { to = EDGE_INDEX[id]; clear = EDGE_INDEX[id + 1]; } if (from === -1 && to > -1 && curr.length > 0) { yield curr; curr = []; } if (clear !== -1) { coded[i] = clear; } if (to >= 0) { const p = __contourVertex[to >> 1](src, w, x, y, iso); p[0] = (p[0] + 0.5) * sx; p[1] = (p[1] + 0.5) * sy; curr.push(p); x += NEXT_EDGES[to]; y += NEXT_EDGES[to + 1]; } else { next = true; } } if (curr.length > 0) { yield curr; } } export { isolines, setBorder };