@chromaprint ?= {}@chromaprint ?= {}Creating images comes down to choosing a ‘shade of grey’ from here.
GRAYCODE = [ 0, 1, 3, 2 ]
@chromaprint.GRAYCODE = GRAYCODEChromaprint does its work on “images” - 2 dimensional arrays which are calculated from an input stream.
integral = (image) ->
result = []
image.forEach (row, i) ->
result[i] ?= []
row.forEach (n, j) ->
result[i][j] = n +
(result[i ]?[j-1] ? 0) +
(result[i-1]?[j ] ? 0) -
(result[i-1]?[j-1] ? 0)
result
area = (image, x1, y1, x2, y2) ->
x1 ?= 0
y1 ?= 0
x2 ?= image.length
y2 ?= image[x2]?.length
result = image[x2]?[y2]
return 0 if x2 < x1 or y2 < y1
if x1 > 0
result -= image[x1-1][y2]
result += image[x1-1][y1-1] if y1 > 0
result -= image[x2][y1-1] if y1 > 0
result
measure = (image) ->
h: image.length
w: image[0].length ? 0
@chromaprint.Image = { integral, area, measure }A filter in chromaprint modifies a portion of an image. There are 5
different filters available. The filter function takes as arguments:
filter = (type, y, w, h, cmp) ->
cmp ?= filter.compare.subtractLog
f = (img, x) -> filter[type](img, x, y, w, h, cmp)
f.w = w
f.h = h
f.y = y
f
filter[0] = (img, x, y, w, h, cmp) -> # oooooooooooo
a = area(img, x, y, x + w - 1, y + h - 1) # oooooooooooo
b = 0 # oooooooooooo
cmp(a, b) # oooooooooooooooooooooooo oooooooooooo
filter[1] = (img, x, y, w, h, cmp) -> # ............
h2 = h / 2 # ............
a = area(img, x, y + h2, x + w - 1, y + h - 1) # ............
b = area(img, x, y , x + w - 1, y + h2 - 1) # oooooooooooo
cmp(a, b) # oooooooooooooooooooooooo
filter[2] = (img, x, y, w, h, cmp) -> # ......oooooo
w2 = w / 2 # ......oooooo
a = area(img, x + w2, y, x + w - 1, y + h - 1) # ......oooooo
b = area(img, x , y, x + w2 - 1, y + y - 1) # ......oooooo
cmp(a, b) # ......oooooo……oooooo
filter[3] = (img, x, y, w, h, cmp) -> # ......oooooo
[w2, h2] = [w / 2, h / 2] # ......oooooo
a = area(img, x , y + h2, x + w2 - 1, y + h - 1) + # ......oooooo
area(img, x + w2, y , x + w - 1, y + h2 - 1) # oooooo......
b = area(img, x , y , x + w2 - 1, y + h2 - 1) + # oooooo......
area(img, x + w2, y + h2, x + w - 1, y + h - 1) # oooooo......
cmp(a, b)
filter[4] = (img, x, y, w, h, cmp) -> # ............
h3 = h / 3 # ............
a = area(img, x, y + h3, x + w - 1, y + 2 * h3 - 1) # oooooooooooo
b = area(img, x, y , x + w - 1, y + h3 - 1) + # oooooooooooo
area(img, x, y + 2 * h3, x + w - 1, y + h - 1) # ............
cmp(a, b) # ............
filter[5] = (img, x, y, w, h, cmp) -> # ....oooo....
w3 = w / 3 # ....oooo....
a = area(img, x + w3, y, x + 2 * w3 - 1, y + h - 1) # ....oooo....
b = area(img, x , y, x + w3 - 1, y + h - 1) + # ....oooo....
area(img, x + 2 * w3, y, x + w - 1, y + h -1) # ....oooo....
cmp(a, b) # ....oooo....
filter.compare =
subtract: (a, b) -> a - b
subtractLog: (a, b) -> Math.log(1 + a) - Math.log(1 + b)
@chromaprint.filter = filterMakes a very simple function to return 0, 1, 2, or 3 depending on where the value falls within the three values given.
quantizer = (t0, t1, t2) ->
(value) ->
if value < t1
return 0 if value < t0
return 1
return 2 if value < t2
3
@chromaprint.quantizer = quantizerclassifier is a higher order function that makes a filter and a quantizer work together on an image.
classifier = (coefficients) ->
f = filter(coefficients.f...)
q = quantizer(coefficients.q...)
fn = (image, offset) -> q(f(image, offset))
fn.filterWidth = f.w
fn
@chromaprint.classifier = classifierThe calculator actually calculates the fingerprint of an image, by creating the right classifier, and passing the image into the resulting function in chunks.
calculator = (coefficients...) ->
classifiers = coefficients.map (cc) ->
classifier(cc)
max = classifiers.reduce ((w, c) -> Math.max(w, c.filterWidth)), 0
subfingerprint = (image, offset) ->
classify = (b, classifier) -> (b << 2) | GRAYCODE[classifier(image, offset)]
classifiers.reduce classify, 0
(image) ->
length = measure(image).h - max + 1
img = integral(image)
subfingerprint(img, offset) for offset in [0...length]
@chromaprint.calculator = calculator