UNPKG

image-in-browser

Version:

Package for encoding / decoding images, transforming images, applying filters, drawing primitives on images on the client side (no need for server Node.js)

208 lines 6.46 kB
import { ColorRgb8 } from '../color/color-rgb8.js'; import { HeapNode } from './heap-node.js'; import { MemoryImage } from './image.js'; import { OctreeNode } from './octree-node.js'; import { PaletteUint8 } from './palette-uint8.js'; export class OctreeQuantizer { get palette() { return this._palette; } constructor(image, numberOfColors = 256) { this._root = new OctreeNode(0, 0); const heap = new HeapNode(); for (const p of image) { const r = Math.trunc(p.r); const g = Math.trunc(p.g); const b = Math.trunc(p.b); this.heapAdd(heap, this.nodeInsert(this._root, r, g, b)); } const nc = numberOfColors + 1; while (heap.n > nc) { this.heapAdd(heap, this.nodeFold(this.popHeap(heap))); } for (let i = 1; i < heap.n; i++) { const got = heap.buf[i]; const c = got.count; got.r = Math.round(got.r / c); got.g = Math.round(got.g / c); got.b = Math.round(got.b / c); } const nodes = []; this.getNodes(nodes, this._root); this._palette = new PaletteUint8(nodes.length, 3); const l = nodes.length; for (let i = 0; i < l; ++i) { nodes[i].paletteIndex = i; const n = nodes[i]; this._palette.setRgb(i, n.r, n.g, n.b); } } nodeInsert(root, r, g, b) { let _root = root; let depth = 0; for (let bit = 1 << 7; ++depth < 8; bit >>>= 1) { const i = ((g & bit) !== 0 ? 1 : 0) * 4 + ((r & bit) !== 0 ? 1 : 0) * 2 + ((b & bit) !== 0 ? 1 : 0); if (_root.children[i] === undefined) { _root.children[i] = new OctreeNode(i, depth, _root); } _root = _root.children[i]; } _root.r += r; _root.g += g; _root.b += b; _root.count++; return _root; } popHeap(h) { if (h.n <= 1) { return undefined; } const ret = h.buf[1]; h.buf[1] = h.buf.pop(); h.buf[1].heapIndex = 1; this.downHeap(h, h.buf[1]); return ret; } heapAdd(h, p) { if ((p.flags & OctreeQuantizer._inHeap) !== 0) { this.downHeap(h, p); this.upHeap(h, p); return; } p.flags |= OctreeQuantizer._inHeap; p.heapIndex = h.n; h.buf.push(p); this.upHeap(h, p); } downHeap(h, p) { let n = p.heapIndex; while (true) { let m = n * 2; if (m >= h.n) { break; } if (m + 1 < h.n && this.compareNode(h.buf[m], h.buf[m + 1]) > 0) { m++; } if (this.compareNode(p, h.buf[m]) <= 0) { break; } h.buf[n] = h.buf[m]; h.buf[n].heapIndex = n; n = m; } h.buf[n] = p; p.heapIndex = n; } upHeap(h, p) { let n = p.heapIndex; let prev = undefined; while (n > 1) { prev = h.buf[Math.trunc(n / 2)]; if (this.compareNode(p, prev) >= 0) { break; } h.buf[n] = prev; prev.heapIndex = n; n = Math.trunc(n / 2); } h.buf[n] = p; p.heapIndex = n; } nodeFold(p) { if (p.childCount > 0) { return undefined; } const q = p.parent; q.count += p.count; q.r += p.r; q.g += p.g; q.b += p.b; q.childCount--; q.children[p.childIndex] = undefined; return q; } compareNode(a, b) { if (a.childCount < b.childCount) { return -1; } if (a.childCount > b.childCount) { return 1; } const ac = a.count >>> a.depth; const bc = b.count >>> b.depth; return ac < bc ? -1 : ac > bc ? 1 : 0; } getNodes(nodes, node) { if (node.childCount === 0) { nodes.push(node); return; } for (const n of node.children) { if (n !== undefined) { this.getNodes(nodes, n); } } } getColorIndex(c) { return this.getColorIndexRgb(Math.trunc(c.r), Math.trunc(c.g), Math.trunc(c.b)); } getColorIndexRgb(r, g, b) { var _a; let root = this._root; for (let bit = 1 << 7; bit !== 0; bit >>>= 1) { const i = ((g & bit) !== 0 ? 1 : 0) * 4 + ((r & bit) !== 0 ? 1 : 0) * 2 + ((b & bit) !== 0 ? 1 : 0); if (root.children[i] === undefined) { break; } root = root.children[i]; } return (_a = root === null || root === void 0 ? void 0 : root.paletteIndex) !== null && _a !== void 0 ? _a : 0; } getQuantizedColor(c) { let r = Math.trunc(c.r); let g = Math.trunc(c.g); let b = Math.trunc(c.b); let root = this._root; for (let bit = 1 << 7; bit !== 0; bit >>>= 1) { const i = ((g & bit) !== 0 ? 1 : 0) * 4 + ((r & bit) !== 0 ? 1 : 0) * 2 + ((b & bit) !== 0 ? 1 : 0); if (root.children[i] === undefined) { break; } root = root.children[i]; } r = root.r; g = root.g; b = root.b; return new ColorRgb8(r, g, b); } getIndexImage(image) { const target = new MemoryImage({ width: image.width, height: image.height, numChannels: 1, palette: this.palette, }); target.frameIndex = image.frameIndex; target.frameType = image.frameType; target.frameDuration = image.frameDuration; const imageIt = image[Symbol.iterator](); const targetIt = target[Symbol.iterator](); let imageItRes = undefined; let targetItRes = undefined; while ((((imageItRes = imageIt.next()), (targetItRes = targetIt.next())), !imageItRes.done && !targetItRes.done)) { const t = targetItRes.value; t.setChannel(0, this.getColorIndex(imageItRes.value)); } return target; } } OctreeQuantizer._inHeap = 1; //# sourceMappingURL=octree-quantizer.js.map