@vibrant/quantizer-mmcq
Advanced tools
Comparing version 3.0.0 to 3.1.0-0
@@ -8,9 +8,9 @@ export interface PQueueComparator<T> { | ||
private _comparator; | ||
private _sort(); | ||
private _sort; | ||
constructor(comparator: PQueueComparator<T>); | ||
push(item: T): void; | ||
peek(index?: number): T; | ||
pop(): T; | ||
pop(): T | undefined; | ||
size(): number; | ||
map<U>(mapper: (item: T, index: number) => any): U[]; | ||
} |
@@ -86,3 +86,3 @@ "use strict"; | ||
~~(mult * (g1 + g2 + 1) / 2), | ||
~~(mult * (b1 + b2 + 1) / 2), | ||
~~(mult * (b1 + b2 + 1) / 2) | ||
]; | ||
@@ -89,0 +89,0 @@ } |
{ | ||
"name": "@vibrant/quantizer-mmcq", | ||
"version": "3.0.0", | ||
"version": "3.1.0-0", | ||
"description": "MMCQ quantzier for vibrant", | ||
"scripts": { | ||
"build:module": "tsc", | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
@@ -11,4 +10,11 @@ }, | ||
"types": "lib/index.d.ts", | ||
"author": "", | ||
"license": "ISC", | ||
"author": { | ||
"name": "akfish", | ||
"email": "akfish@gmail.com" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/akfish/node-vibrant/issues" | ||
}, | ||
"homepage": "https://github.com/akfish/node-vibrant", | ||
"license": "MIT", | ||
"dependencies": { | ||
@@ -20,3 +26,3 @@ "@vibrant/color": "^3.0.0", | ||
"devDependencies": { | ||
"typescript": "latest" | ||
"typescript": "^3.2.2" | ||
}, | ||
@@ -23,0 +29,0 @@ "publishConfig": { |
import { Quantizer, QuantizerOptions } from '@vibrant/quantizer' | ||
import { | ||
Pixels | ||
Pixels | ||
} from '@vibrant/image' | ||
@@ -11,61 +11,61 @@ import { Filter, Swatch } from '@vibrant/color' | ||
function _splitBoxes(pq: PQueue<VBox>, target: number): void { | ||
let lastSize = pq.size() | ||
while (pq.size() < target) { | ||
let vbox = pq.pop() | ||
function _splitBoxes (pq: PQueue<VBox>, target: number): void { | ||
let lastSize = pq.size() | ||
while (pq.size() < target) { | ||
let vbox = pq.pop() | ||
if (vbox && vbox.count() > 0) { | ||
let [vbox1, vbox2] = vbox.split() | ||
if (vbox && vbox.count() > 0) { | ||
let [vbox1, vbox2] = vbox.split() | ||
pq.push(vbox1) | ||
if (vbox2 && vbox2.count() > 0) pq.push(vbox2) | ||
pq.push(vbox1) | ||
if (vbox2 && vbox2.count() > 0) pq.push(vbox2) | ||
// No more new boxes, converged | ||
if (pq.size() === lastSize) { | ||
break | ||
} else { | ||
lastSize = pq.size() | ||
} | ||
} else { | ||
break | ||
} | ||
// No more new boxes, converged | ||
if (pq.size() === lastSize) { | ||
break | ||
} else { | ||
lastSize = pq.size() | ||
} | ||
} else { | ||
break | ||
} | ||
} | ||
} | ||
const MMCQ = (pixels: Pixels, opts: QuantizerOptions): Array<Swatch> => { | ||
if (pixels.length === 0 || opts.colorCount < 2 || opts.colorCount > 256) { | ||
throw new Error('Wrong MMCQ parameters') | ||
} | ||
if (pixels.length === 0 || opts.colorCount < 2 || opts.colorCount > 256) { | ||
throw new Error('Wrong MMCQ parameters') | ||
} | ||
let vbox = VBox.build(pixels) | ||
let colorCount = vbox.histogram.colorCount | ||
let pq = new PQueue<VBox>((a, b) => a.count() - b.count()) | ||
let vbox = VBox.build(pixels) | ||
let colorCount = vbox.histogram.colorCount | ||
let pq = new PQueue<VBox>((a, b) => a.count() - b.count()) | ||
pq.push(vbox) | ||
pq.push(vbox) | ||
// first set of colors, sorted by population | ||
_splitBoxes(pq, fractByPopulations * opts.colorCount) | ||
// first set of colors, sorted by population | ||
_splitBoxes(pq, fractByPopulations * opts.colorCount) | ||
// Re-order | ||
let pq2 = new PQueue<VBox>((a, b) => a.count() * a.volume() - b.count() * b.volume()) | ||
pq2.contents = pq.contents | ||
// Re-order | ||
let pq2 = new PQueue<VBox>((a, b) => a.count() * a.volume() - b.count() * b.volume()) | ||
pq2.contents = pq.contents | ||
// next set - generate the median cuts using the (npix * vol) sorting. | ||
_splitBoxes(pq2, opts.colorCount - pq2.size()) | ||
// next set - generate the median cuts using the (npix * vol) sorting. | ||
_splitBoxes(pq2, opts.colorCount - pq2.size()) | ||
// calculate the actual colors | ||
return generateSwatches(pq2) | ||
// calculate the actual colors | ||
return generateSwatches(pq2) | ||
} | ||
function generateSwatches(pq: PQueue<VBox>) { | ||
let swatches: Swatch[] = [] | ||
while (pq.size()) { | ||
let v = pq.pop() | ||
let color = v.avg() | ||
let [r, g, b] = color | ||
swatches.push(new Swatch(color, v.count())) | ||
} | ||
return swatches | ||
function generateSwatches (pq: PQueue<VBox>) { | ||
let swatches: Swatch[] = [] | ||
while (pq.size()) { | ||
let v = pq.pop()! | ||
let color = v.avg() | ||
let [r, g, b] = color | ||
swatches.push(new Swatch(color, v.count())) | ||
} | ||
return swatches | ||
} | ||
export default MMCQ | ||
export default MMCQ |
export interface PQueueComparator<T> { | ||
(a: T, b: T): number | ||
(a: T, b: T): number | ||
} | ||
export default class PQueue<T> { | ||
contents: T[] | ||
private _sorted: boolean | ||
private _comparator: PQueueComparator<T> | ||
private _sort(): void { | ||
if (!this._sorted) { | ||
this.contents.sort(this._comparator) | ||
this._sorted = true | ||
} | ||
contents: T[] | ||
private _sorted: boolean | ||
private _comparator: PQueueComparator<T> | ||
private _sort (): void { | ||
if (!this._sorted) { | ||
this.contents.sort(this._comparator) | ||
this._sorted = true | ||
} | ||
} | ||
constructor(comparator: PQueueComparator<T>) { | ||
this._comparator = comparator | ||
this.contents = [] | ||
this._sorted = false | ||
} | ||
push(item: T): void { | ||
this.contents.push(item) | ||
this._sorted = false | ||
} | ||
peek(index?: number): T { | ||
this._sort() | ||
index = typeof index === 'number' ? index : this.contents.length - 1 | ||
return this.contents[index] | ||
} | ||
pop(): T { | ||
this._sort() | ||
return this.contents.pop() | ||
} | ||
size(): number { | ||
return this.contents.length | ||
} | ||
map<U>(mapper: (item: T, index: number) => any): U[] { | ||
this._sort() | ||
return this.contents.map(mapper) | ||
} | ||
} | ||
constructor (comparator: PQueueComparator<T>) { | ||
this._comparator = comparator | ||
this.contents = [] | ||
this._sorted = false | ||
} | ||
push (item: T): void { | ||
this.contents.push(item) | ||
this._sorted = false | ||
} | ||
peek (index?: number): T { | ||
this._sort() | ||
index = typeof index === 'number' ? index : this.contents.length - 1 | ||
return this.contents[index] | ||
} | ||
pop () { | ||
this._sort() | ||
return this.contents.pop() | ||
} | ||
size (): number { | ||
return this.contents.length | ||
} | ||
map<U> (mapper: (item: T, index: number) => any): U[] { | ||
this._sort() | ||
return this.contents.map(mapper) | ||
} | ||
} |
389
src/vbox.ts
@@ -5,9 +5,9 @@ import { Vec3, Filter } from '@vibrant/color' | ||
export interface Dimension { | ||
r1: number | ||
r2: number | ||
g1: number | ||
g2: number | ||
b1: number | ||
b2: number | ||
[d: string]: number | ||
r1: number | ||
r2: number | ||
g1: number | ||
g2: number | ||
b1: number | ||
b2: number | ||
[d: string]: number | ||
} | ||
@@ -19,222 +19,219 @@ | ||
export default class VBox { | ||
static build(pixels: Pixels): VBox { | ||
let h = new Histogram(pixels, { sigBits: SIGBITS }) | ||
let { rmin, rmax, gmin, gmax, bmin, bmax } = h | ||
return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, h) | ||
} | ||
static build (pixels: Pixels): VBox { | ||
let h = new Histogram(pixels, { sigBits: SIGBITS }) | ||
let { rmin, rmax, gmin, gmax, bmin, bmax } = h | ||
return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, h) | ||
} | ||
dimension: Dimension | ||
dimension: Dimension | ||
private _volume = -1 | ||
private _avg: Vec3 | ||
private _count = -1 | ||
private _volume = -1 | ||
private _avg: Vec3 | null | ||
private _count = -1 | ||
constructor( | ||
r1: number, r2: number, | ||
g1: number, g2: number, | ||
b1: number, b2: number, | ||
public histogram: Histogram | ||
) { | ||
// NOTE: dimension will be mutated by split operation. | ||
// It must be specified explicitly, not from histogram | ||
this.dimension = { r1, r2, g1, g2, b1, b2 } | ||
} | ||
constructor ( | ||
r1: number, r2: number, | ||
g1: number, g2: number, | ||
b1: number, b2: number, | ||
public histogram: Histogram | ||
) { | ||
// NOTE: dimension will be mutated by split operation. | ||
// It must be specified explicitly, not from histogram | ||
this.dimension = { r1, r2, g1, g2, b1, b2 } | ||
} | ||
invalidate(): void { | ||
this._volume = this._count = -1 | ||
this._avg = null | ||
} | ||
invalidate (): void { | ||
this._volume = this._count = -1 | ||
this._avg = null | ||
} | ||
volume(): number { | ||
if (this._volume < 0) { | ||
let { r1, r2, g1, g2, b1, b2 } = this.dimension | ||
this._volume = (r2 - r1 + 1) * (g2 - g1 + 1) * (b2 - b1 + 1) | ||
} | ||
return this._volume | ||
volume (): number { | ||
if (this._volume < 0) { | ||
let { r1, r2, g1, g2, b1, b2 } = this.dimension | ||
this._volume = (r2 - r1 + 1) * (g2 - g1 + 1) * (b2 - b1 + 1) | ||
} | ||
return this._volume | ||
} | ||
count(): number { | ||
if (this._count < 0) { | ||
let { hist, getColorIndex } = this.histogram | ||
let { r1, r2, g1, g2, b1, b2 } = this.dimension | ||
let c = 0 | ||
count (): number { | ||
if (this._count < 0) { | ||
let { hist, getColorIndex } = this.histogram | ||
let { r1, r2, g1, g2, b1, b2 } = this.dimension | ||
let c = 0 | ||
for (let r = r1; r <= r2; r++) { | ||
for (let g = g1; g <= g2; g++) { | ||
for (let b = b1; b <= b2; b++) { | ||
let index = getColorIndex(r, g, b) | ||
c += hist[index] | ||
} | ||
} | ||
} | ||
this._count = c | ||
for (let r = r1; r <= r2; r++) { | ||
for (let g = g1; g <= g2; g++) { | ||
for (let b = b1; b <= b2; b++) { | ||
let index = getColorIndex(r, g, b) | ||
c += hist[index] | ||
} | ||
} | ||
return this._count | ||
} | ||
this._count = c | ||
} | ||
return this._count | ||
} | ||
clone(): VBox { | ||
let { histogram } = this | ||
let { r1, r2, g1, g2, b1, b2 } = this.dimension | ||
return new VBox(r1, r2, g1, g2, b1, b2, histogram) | ||
} | ||
clone (): VBox { | ||
let { histogram } = this | ||
let { r1, r2, g1, g2, b1, b2 } = this.dimension | ||
return new VBox(r1, r2, g1, g2, b1, b2, histogram) | ||
} | ||
avg(): Vec3 { | ||
if (!this._avg) { | ||
let { hist, getColorIndex } = this.histogram | ||
let { r1, r2, g1, g2, b1, b2 } = this.dimension | ||
let ntot = 0 | ||
let mult = 1 << (8 - SIGBITS) | ||
let rsum: number | ||
let gsum: number | ||
let bsum: number | ||
rsum = gsum = bsum = 0 | ||
avg (): Vec3 { | ||
if (!this._avg) { | ||
let { hist, getColorIndex } = this.histogram | ||
let { r1, r2, g1, g2, b1, b2 } = this.dimension | ||
let ntot = 0 | ||
let mult = 1 << (8 - SIGBITS) | ||
let rsum: number | ||
let gsum: number | ||
let bsum: number | ||
rsum = gsum = bsum = 0 | ||
for (let r = r1; r <= r2; r++) { | ||
for (let g = g1; g <= g2; g++) { | ||
for (let b = b1; b <= b2; b++) { | ||
var index = getColorIndex(r, g, b); | ||
var h = hist[index]; | ||
ntot += h; | ||
rsum += (h * (r + 0.5) * mult); | ||
gsum += (h * (g + 0.5) * mult); | ||
bsum += (h * (b + 0.5) * mult); | ||
} | ||
} | ||
} | ||
if (ntot) { | ||
this._avg = [ | ||
~~(rsum / ntot), | ||
~~(gsum / ntot), | ||
~~(bsum / ntot) | ||
] | ||
} else { | ||
this._avg = [ | ||
~~(mult * (r1 + r2 + 1) / 2), | ||
~~(mult * (g1 + g2 + 1) / 2), | ||
~~(mult * (b1 + b2 + 1) / 2), | ||
] | ||
} | ||
for (let r = r1; r <= r2; r++) { | ||
for (let g = g1; g <= g2; g++) { | ||
for (let b = b1; b <= b2; b++) { | ||
let index = getColorIndex(r, g, b) | ||
let h = hist[index] | ||
ntot += h | ||
rsum += (h * (r + 0.5) * mult) | ||
gsum += (h * (g + 0.5) * mult) | ||
bsum += (h * (b + 0.5) * mult) | ||
} | ||
} | ||
} | ||
if (ntot) { | ||
this._avg = [ | ||
~~(rsum / ntot), | ||
~~(gsum / ntot), | ||
~~(bsum / ntot) | ||
] | ||
} else { | ||
this._avg = [ | ||
~~(mult * (r1 + r2 + 1) / 2), | ||
~~(mult * (g1 + g2 + 1) / 2), | ||
~~(mult * (b1 + b2 + 1) / 2) | ||
] | ||
} | ||
} | ||
return this._avg | ||
} | ||
return this._avg | ||
} | ||
contains(rgb: Vec3): boolean { | ||
let [r, g, b] = rgb | ||
let { r1, r2, g1, g2, b1, b2 } = this.dimension | ||
r >>= RSHIFT | ||
g >>= RSHIFT | ||
b >>= RSHIFT | ||
contains (rgb: Vec3): boolean { | ||
let [r, g, b] = rgb | ||
let { r1, r2, g1, g2, b1, b2 } = this.dimension | ||
r >>= RSHIFT | ||
g >>= RSHIFT | ||
b >>= RSHIFT | ||
return r >= r1 && r <= r2 | ||
&& g >= g1 && g <= g2 | ||
&& b >= b1 && b <= b2 | ||
} | ||
return r >= r1 && r <= r2 | ||
&& g >= g1 && g <= g2 | ||
&& b >= b1 && b <= b2 | ||
} | ||
split(): VBox[] { | ||
let { hist, getColorIndex } = this.histogram | ||
let { r1, r2, g1, g2, b1, b2 } = this.dimension | ||
let count = this.count() | ||
if (!count) return [] | ||
if (count === 1) return [this.clone()] | ||
let rw = r2 - r1 + 1 | ||
let gw = g2 - g1 + 1 | ||
let bw = b2 - b1 + 1 | ||
split (): VBox[] { | ||
let { hist, getColorIndex } = this.histogram | ||
let { r1, r2, g1, g2, b1, b2 } = this.dimension | ||
let count = this.count() | ||
if (!count) return [] | ||
if (count === 1) return [this.clone()] | ||
let rw = r2 - r1 + 1 | ||
let gw = g2 - g1 + 1 | ||
let bw = b2 - b1 + 1 | ||
let maxw = Math.max(rw, gw, bw) | ||
let accSum: Uint32Array = null | ||
let sum: number | ||
let total: number | ||
sum = total = 0 | ||
let maxw = Math.max(rw, gw, bw) | ||
let accSum: Uint32Array | null = null | ||
let sum: number | ||
let total: number | ||
sum = total = 0 | ||
let maxd: 'r' | 'g' | 'b' = null | ||
let maxd: 'r' | 'g' | 'b' | null = null | ||
if (maxw === rw) { | ||
maxd = 'r' | ||
accSum = new Uint32Array(r2 + 1) | ||
for (let r = r1; r <= r2; r++) { | ||
sum = 0 | ||
for (let g = g1; g <= g2; g++) { | ||
for (let b = b1; b <= b2; b++) { | ||
let index = getColorIndex(r, g, b); | ||
sum += hist[index]; | ||
} | ||
} | ||
total += sum; | ||
accSum[r] = total; | ||
} | ||
} else if (maxw === gw) { | ||
maxd = 'g' | ||
accSum = new Uint32Array(g2 + 1) | ||
for (let g = g1; g <= g2; g++) { | ||
sum = 0 | ||
for (let r = r1; r <= r2; r++) { | ||
for (let b = b1; b <= b2; b++) { | ||
let index = getColorIndex(r, g, b); | ||
sum += hist[index]; | ||
} | ||
} | ||
total += sum; | ||
accSum[g] = total; | ||
} | ||
} else { | ||
maxd = 'b' | ||
accSum = new Uint32Array(b2 + 1) | ||
for (let b = b1; b <= b2; b++) { | ||
sum = 0 | ||
for (let r = r1; r <= r2; r++) { | ||
for (let g = g1; g <= g2; g++) { | ||
let index = getColorIndex(r, g, b); | ||
sum += hist[index]; | ||
} | ||
} | ||
total += sum; | ||
accSum[b] = total; | ||
} | ||
if (maxw === rw) { | ||
maxd = 'r' | ||
accSum = new Uint32Array(r2 + 1) | ||
for (let r = r1; r <= r2; r++) { | ||
sum = 0 | ||
for (let g = g1; g <= g2; g++) { | ||
for (let b = b1; b <= b2; b++) { | ||
let index = getColorIndex(r, g, b) | ||
sum += hist[index] | ||
} | ||
} | ||
let splitPoint = -1 | ||
let reverseSum = new Uint32Array(accSum.length) | ||
for (let i = 0; i < accSum.length; i++) { | ||
let d = accSum[i]; | ||
if (splitPoint < 0 && d > total / 2) splitPoint = i | ||
reverseSum[i] = total - d | ||
total += sum | ||
accSum[r] = total | ||
} | ||
} else if (maxw === gw) { | ||
maxd = 'g' | ||
accSum = new Uint32Array(g2 + 1) | ||
for (let g = g1; g <= g2; g++) { | ||
sum = 0 | ||
for (let r = r1; r <= r2; r++) { | ||
for (let b = b1; b <= b2; b++) { | ||
let index = getColorIndex(r, g, b) | ||
sum += hist[index] | ||
} | ||
} | ||
total += sum | ||
accSum[g] = total | ||
} | ||
} else { | ||
maxd = 'b' | ||
accSum = new Uint32Array(b2 + 1) | ||
for (let b = b1; b <= b2; b++) { | ||
sum = 0 | ||
for (let r = r1; r <= r2; r++) { | ||
for (let g = g1; g <= g2; g++) { | ||
let index = getColorIndex(r, g, b) | ||
sum += hist[index] | ||
} | ||
} | ||
total += sum | ||
accSum[b] = total | ||
} | ||
} | ||
let vbox = this | ||
let splitPoint = -1 | ||
let reverseSum = new Uint32Array(accSum.length) | ||
for (let i = 0; i < accSum.length; i++) { | ||
let d = accSum[i] | ||
if (splitPoint < 0 && d > total / 2) splitPoint = i | ||
reverseSum[i] = total - d | ||
} | ||
function doCut(d: string): VBox[] { | ||
let dim1 = d + '1' | ||
let dim2 = d + '2' | ||
let d1 = vbox.dimension[dim1] | ||
let d2 = vbox.dimension[dim2] | ||
let vbox1 = vbox.clone() | ||
let vbox2 = vbox.clone() | ||
let left = splitPoint - d1 | ||
let right = d2 - splitPoint | ||
if (left <= right) { | ||
d2 = Math.min(d2 - 1, ~~(splitPoint + right / 2)) | ||
d2 = Math.max(0, d2) | ||
} | ||
else { | ||
d2 = Math.max(d1, ~~(splitPoint - 1 - left / 2)) | ||
d2 = Math.min(vbox.dimension[dim2], d2) | ||
} | ||
let vbox = this | ||
function doCut (d: string): VBox[] { | ||
let dim1 = d + '1' | ||
let dim2 = d + '2' | ||
let d1 = vbox.dimension[dim1] | ||
let d2 = vbox.dimension[dim2] | ||
let vbox1 = vbox.clone() | ||
let vbox2 = vbox.clone() | ||
let left = splitPoint - d1 | ||
let right = d2 - splitPoint | ||
if (left <= right) { | ||
d2 = Math.min(d2 - 1, ~~(splitPoint + right / 2)) | ||
d2 = Math.max(0, d2) | ||
} else { | ||
d2 = Math.max(d1, ~~(splitPoint - 1 - left / 2)) | ||
d2 = Math.min(vbox.dimension[dim2], d2) | ||
} | ||
while (!accSum[d2]) d2++ | ||
while (!accSum![d2]) d2++ | ||
let c2 = reverseSum[d2] | ||
while (!c2 && accSum![d2 - 1]) c2 = reverseSum[--d2] | ||
let c2 = reverseSum[d2] | ||
while (!c2 && accSum[d2 - 1]) c2 = reverseSum[--d2] | ||
vbox1.dimension[dim2] = d2 | ||
vbox2.dimension[dim1] = d2 + 1 | ||
vbox1.dimension[dim2] = d2 | ||
vbox2.dimension[dim1] = d2 + 1 | ||
return [vbox1, vbox2] | ||
} | ||
return [vbox1, vbox2] | ||
} | ||
return doCut(maxd) | ||
} | ||
return doCut(maxd) | ||
} | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
0
34438
15
647