Skip to content

Instantly share code, notes, and snippets.

@brookemckim
Last active December 15, 2015 07:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brookemckim/5224547 to your computer and use it in GitHub Desktop.
Save brookemckim/5224547 to your computer and use it in GitHub Desktop.
// Generated by CoffeeScript 1.3.3
$(document).ready(function() {
$(".playlist").addClass("closed hidden transition transition-height");
return $(".cover-picker").on("click", "a", function(event) {
var albumSelected, canvas, coverActive, coverAnchor, coverSelected, image, indicatorPosition, playlistHeight, switchPlaylistToAlbum, togglePlaylistForAlbum,
_this = this;
coverAnchor = this;
coverSelected = coverAnchor.parentNode;
coverActive = $(".cover-picker .active");
albumSelected = coverAnchor.href.split('#')[1];
indicatorPosition = coverSelected.offsetLeft + (coverSelected.offsetWidth / 2) - 15;
playlistHeight = $(".playlist-inner")[0].getBoundingClientRect().height;
function loadAlbumInfo(album) {
var selectedAlbum = TRACK_INFO[album];
$('.album-tracks').empty();
for(var i = 0; i < selectedAlbum.tracks.length; i++) {
var track = selectedAlbum.tracks[i],
trackListItem = $('<li>'),
trackTitleItem = $('<h5>').addClass('track-title').text(track.title),
trackRatingsItem = $('<span>').addClass('track-ratings'),
trackPlaytimeItem = $('<span>').addClass('track-playtime').text(track.playtime);
trackListItem.append(trackTitleItem).append(trackRatingsItem).append(trackPlaytimeItem);
trackListItem.appendTo('.album-tracks');
}
$('.album-title').text(selectedAlbum.title);
$('.album-artist .artist-name').text(selectedAlbum.artist);
$('.album-artist .released-on').text("(" + selectedAlbum.release + ")");
(function launchColorTunesLibrary() {
var canvas = $('#album-artwork')[0],
image = new Image();
image.src = selectedAlbum.cover;
ColorTunes.launch(image, canvas);
})();
};
togglePlaylistForAlbum = function(album) {
var isExpanding, targetHeight;
isExpanding = $(".playlist").hasClass("closed");
targetHeight = isExpanding ? 400 : 0;
if (isExpanding) {
$(".playlist").removeClass("hidden");
$(".playlist-indicator").removeClass("hidden").css("left", indicatorPosition);
} else {
$(".playlist").on("webkitTransitionEnd", function() {
$(".playlist-indicator").addClass("hidden");
return $(".playlist").addClass("hidden").off("webkitTransitionEnd");
});
}
$(coverSelected).toggleClass("active");
return $(".playlist").toggleClass("closed expanded").height(targetHeight);
};
switchPlaylistToAlbum = function(album) {
$(coverActive).removeClass("active");
$(coverSelected).addClass("active");
return $(".playlist-indicator").css("left", indicatorPosition);
};
loadAlbumInfo(albumSelected);
if (($(coverActive).length === 0) || (coverSelected === coverActive[0])) {
togglePlaylistForAlbum(albumSelected);
} else if ($(coverActive).length > 0) {
switchPlaylistToAlbum(albumSelected);
}
coverAnchor.blur();
event.preventDefault();
});
});
// Generated by CoffeeScript 1.3.3
var ColorTunes;
ColorTunes = (function() {
function ColorTunes() {}
ColorTunes.getColorMap = function(canvas, sx, sy, w, h, nc) {
var index, indexBase, pdata, pixels, x, y, _i, _j, _ref, _ref1;
if (nc == null) {
nc = 8;
}
pdata = canvas.getContext("2d").getImageData(sx, sy, w, h).data;
pixels = [];
for (y = _i = sy, _ref = sy + h; _i < _ref; y = _i += 1) {
indexBase = y * w * 4;
for (x = _j = sx, _ref1 = sx + w; _j < _ref1; x = _j += 1) {
index = indexBase + (x * 4);
pixels.push([pdata[index], pdata[index + 1], pdata[index + 2]]);
}
}
return (new MMCQ).quantize(pixels, nc);
};
ColorTunes.colorDist = function(a, b) {
var square;
square = function(n) {
return n * n;
};
return square(a[0] - b[0]) + square(a[1] - b[1]) + square(a[2] - b[2]);
};
ColorTunes.fadeout = function(canvas, width, height, opa, color) {
var idata, idx, pdata, x, y, _i, _j;
if (opa == null) {
opa = 0.5;
}
if (color == null) {
color = [0, 0, 0];
}
idata = canvas.getContext("2d").getImageData(0, 0, width, height);
pdata = idata.data;
for (y = _i = 0; 0 <= height ? _i < height : _i > height; y = 0 <= height ? ++_i : --_i) {
for (x = _j = 0; 0 <= width ? _j < width : _j > width; x = 0 <= width ? ++_j : --_j) {
idx = (y * width + x) * 4;
pdata[idx + 0] = opa * pdata[idx + 0] + (1 - opa) * color[0];
pdata[idx + 1] = opa * pdata[idx + 1] + (1 - opa) * color[1];
pdata[idx + 2] = opa * pdata[idx + 2] + (1 - opa) * color[2];
}
}
return canvas.getContext("2d").putImageData(idata, 0, 0);
};
ColorTunes.feathering = function(canvas, width, height, size, color) {
var conv, dist, idata, p, pdata, x, y, _i, _j, _k, _l, _ref;
if (size == null) {
size = 50;
}
if (color == null) {
color = [0, 0, 0];
}
idata = canvas.getContext("2d").getImageData(0, 0, width, height);
pdata = idata.data;
conv = function(x, y, p) {
var idx;
if (p < 0) {
p = 0;
}
if (p > 1) {
p = 1;
}
idx = (y * width + x) * 4;
pdata[idx + 0] = p * pdata[idx + 0] + (1 - p) * color[0];
pdata[idx + 1] = p * pdata[idx + 1] + (1 - p) * color[1];
return pdata[idx + 2] = p * pdata[idx + 2] + (1 - p) * color[2];
};
dist = function(xa, ya, xb, yb) {
return Math.sqrt((xb - xa) * (xb - xa) + (yb - ya) * (yb - ya));
};
for (x = _i = 0; _i < width; x = _i += 1) {
for (y = _j = 0; _j < size; y = _j += 1) {
p = y / size;
if (x < size) {
p = 1 - dist(x, y, size, size) / size;
}
conv(x, y, p);
}
}
for (y = _k = _ref = 0 + size; _k < height; y = _k += 1) {
for (x = _l = 0; _l < size; x = _l += 1) {
p = x / size;
conv(x, y, p);
}
}
return canvas.getContext("2d").putImageData(idata, 0, 0);
};
ColorTunes.mirror = function(canvas, sy, height, color) {
var idata, idx, idxu, p, pdata, width, x, y, _i, _j, _ref;
if (color == null) {
color = [0, 0, 0];
}
width = canvas.width;
idata = canvas.getContext("2d").getImageData(0, sy - height, width, height * 2);
pdata = idata.data;
for (y = _i = height, _ref = height * 2; _i < _ref; y = _i += 1) {
for (x = _j = 0; _j < width; x = _j += 1) {
idx = (y * width + x) * 4;
idxu = ((height * 2 - y) * width + x) * 4;
p = (y - height) / height + 0.33;
if (p > 1) {
p = 1;
}
pdata[idx + 0] = (1 - p) * pdata[idxu + 0] + p * color[0];
pdata[idx + 1] = (1 - p) * pdata[idxu + 1] + p * color[1];
pdata[idx + 2] = (1 - p) * pdata[idxu + 2] + p * color[2];
pdata[idx + 3] = 255;
}
}
return canvas.getContext("2d").putImageData(idata, 0, sy - height);
};
ColorTunes.launch = function(image, canvas) {
return $(image).on("load", function() {
var bgColor, bgColorMap, bgPalette, color, dist, fgColor, fgColor2, fgColorMap, fgPalette, maxDist, rgbToCssString, _i, _j, _len, _len1;
image.height = Math.round(image.height * (300 / image.width));
image.width = 300;
canvas.width = image.width;
canvas.height = image.height + 150;
canvas.getContext("2d").drawImage(image, 0, 0, image.width, image.height);
bgColorMap = ColorTunes.getColorMap(canvas, 0, 0, image.width * 0.5, image.height, 4);
bgPalette = bgColorMap.cboxes.map(function(cbox) {
return {
count: cbox.cbox.count(),
rgb: cbox.color
};
});
bgPalette.sort(function(a, b) {
return b.count - a.count;
});
bgColor = bgPalette[0].rgb;
fgColorMap = ColorTunes.getColorMap(canvas, 0, 0, image.width, image.height, 10);
fgPalette = fgColorMap.cboxes.map(function(cbox) {
return {
count: cbox.cbox.count(),
rgb: cbox.color
};
});
fgPalette.sort(function(a, b) {
return b.count - a.count;
});
maxDist = 0;
for (_i = 0, _len = fgPalette.length; _i < _len; _i++) {
color = fgPalette[_i];
dist = ColorTunes.colorDist(bgColor, color.rgb);
if (dist > maxDist) {
maxDist = dist;
fgColor = color.rgb;
}
}
maxDist = 0;
for (_j = 0, _len1 = fgPalette.length; _j < _len1; _j++) {
color = fgPalette[_j];
dist = ColorTunes.colorDist(bgColor, color.rgb);
if (dist > maxDist && color.rgb !== fgColor) {
maxDist = dist;
fgColor2 = color.rgb;
}
}
ColorTunes.fadeout(canvas, image.width, image.height, 0.5, bgColor);
ColorTunes.feathering(canvas, image.width, image.height, 60, bgColor);
ColorTunes.mirror(canvas, image.height - 1, 150, bgColor);
rgbToCssString = function(color) {
return "rgb(" + color[0] + ", " + color[1] + ", " + color[2] + ")";
};
$(".playlist").css("background-color", "" + (rgbToCssString(bgColor)));
$(".playlist-indicator").css("border-bottom-color", "" + (rgbToCssString(bgColor)));
$(".album-title, .track-title").css("color", "" + (rgbToCssString(fgColor)));
return $(".album-artist, .album-tracks").css("color", "" + (rgbToCssString(fgColor2)));
});
};
return ColorTunes;
})();
// quantize.js, Copyright 2012 Shao-Chung Chen.
// Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php)
// Basic CoffeeScript port of the (MMCQ) Modified Media Cut Quantization
// algorithm from the Leptonica library (http://www.leptonica.com/).
// Return a color map you can use to map original pixels to the reduced palette.
//
// Rewritten from the JavaScript port (http://gist.github.com/1104622)
// developed by Nick Rabinowitz under the MIT license.
// Generated by CoffeeScript 1.3.3
var MMCQ, PriorityQueue;
PriorityQueue = (function() {
function PriorityQueue(comparator) {
this.comparator = comparator;
this.contents = [];
this.sorted = false;
}
PriorityQueue.prototype.sort = function() {
this.contents.sort(this.comparator);
return this.sotred = true;
};
PriorityQueue.prototype.push = function(obj) {
this.contents.push(obj);
return this.sorted = false;
};
PriorityQueue.prototype.peek = function(index) {
if (index == null) {
index = this.contents.length - 1;
}
if (!this.sorted) {
this.sort();
}
return this.contents[index];
};
PriorityQueue.prototype.pop = function() {
if (!this.sorted) {
this.sort();
}
return this.contents.pop();
};
PriorityQueue.prototype.size = function() {
return this.contents.length;
};
PriorityQueue.prototype.map = function(func) {
return this.contents.map(func);
};
return PriorityQueue;
})();
MMCQ = (function() {
var ColorBox, ColorMap, cboxFromPixels, getColorIndex, getHisto, medianCutApply,
_this = this;
MMCQ.sigbits = 5;
MMCQ.rshift = 8 - MMCQ.sigbits;
function MMCQ() {
this.maxIterations = 1000;
this.fractByPopulations = 0.75;
}
getColorIndex = function(r, g, b) {
return (r << (2 * MMCQ.sigbits)) + (g << MMCQ.sigbits) + b;
};
ColorBox = (function() {
function ColorBox(r1, r2, g1, g2, b1, b2, histo) {
this.r1 = r1;
this.r2 = r2;
this.g1 = g1;
this.g2 = g2;
this.b1 = b1;
this.b2 = b2;
this.histo = histo;
}
ColorBox.prototype.volume = function(forced) {
if (!this._volume || forced) {
this._volume = (this.r2 - this.r1 + 1) * (this.g2 - this.g1 + 1) * (this.b2 - this.b1 + 1);
}
return this._volume;
};
ColorBox.prototype.count = function(forced) {
var b, g, index, numpix, r, _i, _j, _k, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
if (!this._count_set || forced) {
numpix = 0;
for (r = _i = _ref = this.r1, _ref1 = this.r2; _i <= _ref1; r = _i += 1) {
for (g = _j = _ref2 = this.g1, _ref3 = this.g2; _j <= _ref3; g = _j += 1) {
for (b = _k = _ref4 = this.b1, _ref5 = this.b2; _k <= _ref5; b = _k += 1) {
index = getColorIndex(r, g, b);
numpix += this.histo[index] || 0;
}
}
}
this._count_set = true;
this._count = numpix;
}
return this._count;
};
ColorBox.prototype.copy = function() {
return new ColorBox(this.r1, this.r2, this.g1, this.g2, this.b1, this.b2, this.histo);
};
ColorBox.prototype.average = function(forced) {
var b, bsum, g, gsum, hval, index, mult, r, rsum, total, _i, _j, _k, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
if (!this._average || forced) {
mult = 1 << (8 - MMCQ.sigbits);
total = 0;
rsum = 0;
gsum = 0;
bsum = 0;
for (r = _i = _ref = this.r1, _ref1 = this.r2; _i <= _ref1; r = _i += 1) {
for (g = _j = _ref2 = this.g1, _ref3 = this.g2; _j <= _ref3; g = _j += 1) {
for (b = _k = _ref4 = this.b1, _ref5 = this.b2; _k <= _ref5; b = _k += 1) {
index = getColorIndex(r, g, b);
hval = this.histo[index] || 0;
total += hval;
rsum += hval * (r + 0.5) * mult;
gsum += hval * (g + 0.5) * mult;
bsum += hval * (b + 0.5) * mult;
}
}
}
if (total) {
this._average = [~~(rsum / total), ~~(gsum / total), ~~(bsum / total)];
} else {
this._average = [~~(mult * (this.r1 + this.r2 + 1) / 2), ~~(mult * (this.g1 + this.g2 + 1) / 2), ~~(mult * (this.b1 + this.b2 + 1) / 2)];
}
}
return this._average;
};
ColorBox.prototype.contains = function(pixel) {
var b, g, r;
r = pixel[0] >> MMCQ.rshift;
g = pixel[1] >> MMCQ.rshift;
b = pixel[2] >> MMCQ.rshift;
return ((this.r1 <= r && r <= this.r2)) && ((this.g1 <= g && g <= this.g2)) && ((this.b1 <= b && b <= this.b2));
};
return ColorBox;
})();
ColorMap = (function() {
function ColorMap() {
this.cboxes = new PriorityQueue(function(a, b) {
var va, vb;
va = a.count() * a.volume();
vb = b.count() * b.volume();
if (va > vb) {
return 1;
} else if (va < vb) {
return -1;
} else {
return 0;
}
});
}
ColorMap.prototype.push = function(cbox) {
return this.cboxes.push({
cbox: cbox,
color: cbox.average()
});
};
ColorMap.prototype.palette = function() {
return this.cboxes.map(function(cbox) {
return cbox.color;
});
};
ColorMap.prototype.size = function() {
return this.cboxes.size();
};
ColorMap.prototype.map = function(color) {
var i, _i, _ref;
for (i = _i = 0, _ref = this.cboxes.size(); _i < _ref; i = _i += 1) {
if (this.cboxes.peek(i).cbox.contains(color)) {
return this.cboxes.peek(i).color;
}
return this.nearest(color);
}
};
ColorMap.prototype.cboxes = function() {
return this.cboxes;
};
ColorMap.prototype.nearest = function(color) {
var dist, i, minDist, retColor, square, _i, _ref;
square = function(n) {
return n * n;
};
minDist = 1e9;
for (i = _i = 0, _ref = this.cboxes.size(); _i < _ref; i = _i += 1) {
dist = Math.sqrt(square(color[0] - this.cboxes.peek(i).color[0]) + square(color[1] - this.cboxes.peek(i).color[1]) + square(color[2] - this.cboxes.peek(i).color[2]));
if (dist < minDist) {
minDist = dist;
retColor = this.cboxes.peek(i).color;
}
}
return retColor;
};
return ColorMap;
})();
getHisto = function(pixels) {
var b, g, histo, histosize, index, pixel, r, _i, _len;
histosize = 1 << (3 * MMCQ.sigbits);
histo = new Array(histosize);
for (_i = 0, _len = pixels.length; _i < _len; _i++) {
pixel = pixels[_i];
r = pixel[0] >> MMCQ.rshift;
g = pixel[1] >> MMCQ.rshift;
b = pixel[2] >> MMCQ.rshift;
index = getColorIndex(r, g, b);
histo[index] = (histo[index] || 0) + 1;
}
return histo;
};
cboxFromPixels = function(pixels, histo) {
var b, bmax, bmin, g, gmax, gmin, pixel, r, rmax, rmin, _i, _len;
rmin = 1e6;
rmax = 0;
gmin = 1e6;
gmax = 0;
bmin = 1e6;
bmax = 0;
for (_i = 0, _len = pixels.length; _i < _len; _i++) {
pixel = pixels[_i];
r = pixel[0] >> MMCQ.rshift;
g = pixel[1] >> MMCQ.rshift;
b = pixel[2] >> MMCQ.rshift;
if (r < rmin) {
rmin = r;
} else if (r > rmax) {
rmax = r;
}
if (g < gmin) {
gmin = g;
} else if (g > gmax) {
gmax = g;
}
if (b < bmin) {
bmin = b;
} else if (b > bmax) {
bmax = b;
}
}
return new ColorBox(rmin, rmax, gmin, gmax, bmin, bmax, histo);
};
medianCutApply = function(histo, cbox) {
var b, bw, doCut, g, gw, index, lookaheadsum, maxw, partialsum, r, rw, sum, total, _i, _j, _k, _l, _m, _n, _o, _p, _q, _ref, _ref1, _ref10, _ref11, _ref12, _ref13, _ref14, _ref15, _ref16, _ref17, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9;
if (!cbox.count()) {
return;
}
if (cbox.count() === 1) {
return [cbox.copy()];
}
rw = cbox.r2 - cbox.r1 + 1;
gw = cbox.g2 - cbox.g1 + 1;
bw = cbox.b2 - cbox.b1 + 1;
maxw = Math.max(rw, gw, bw);
total = 0;
partialsum = [];
lookaheadsum = [];
if (maxw === rw) {
for (r = _i = _ref = cbox.r1, _ref1 = cbox.r2; _i <= _ref1; r = _i += 1) {
sum = 0;
for (g = _j = _ref2 = cbox.g1, _ref3 = cbox.g2; _j <= _ref3; g = _j += 1) {
for (b = _k = _ref4 = cbox.b1, _ref5 = cbox.b2; _k <= _ref5; b = _k += 1) {
index = getColorIndex(r, g, b);
sum += histo[index] || 0;
}
}
total += sum;
partialsum[r] = total;
}
} else if (maxw === gw) {
for (g = _l = _ref6 = cbox.g1, _ref7 = cbox.g2; _l <= _ref7; g = _l += 1) {
sum = 0;
for (r = _m = _ref8 = cbox.r1, _ref9 = cbox.r2; _m <= _ref9; r = _m += 1) {
for (b = _n = _ref10 = cbox.b1, _ref11 = cbox.b2; _n <= _ref11; b = _n += 1) {
index = getColorIndex(r, g, b);
sum += histo[index] || 0;
}
}
total += sum;
partialsum[g] = total;
}
} else {
for (b = _o = _ref12 = cbox.b1, _ref13 = cbox.b2; _o <= _ref13; b = _o += 1) {
sum = 0;
for (r = _p = _ref14 = cbox.r1, _ref15 = cbox.r2; _p <= _ref15; r = _p += 1) {
for (g = _q = _ref16 = cbox.g1, _ref17 = cbox.g2; _q <= _ref17; g = _q += 1) {
index = getColorIndex(r, g, b);
sum += histo[index] || 0;
}
}
total += sum;
partialsum[b] = total;
}
}
partialsum.forEach(function(d, i) {
return lookaheadsum[i] = total - d;
});
doCut = function(color) {
var cbox1, cbox2, count2, d2, dim1, dim2, i, left, right, _r, _ref18, _ref19;
dim1 = color + '1';
dim2 = color + '2';
for (i = _r = _ref18 = cbox[dim1], _ref19 = cbox[dim2]; _r <= _ref19; i = _r += 1) {
if (partialsum[i] > (total / 2)) {
cbox1 = cbox.copy();
cbox2 = cbox.copy();
left = i - cbox[dim1];
right = cbox[dim2] - i;
if (left <= right) {
d2 = Math.min(cbox[dim2] - 1, ~~(i + right / 2));
} else {
d2 = Math.max(cbox[dim1], ~~(i - 1 - left / 2));
}
while (!partialsum[d2]) {
d2++;
}
count2 = lookaheadsum[d2];
while (!count2 && partialsum[d2 - 1]) {
count2 = lookaheadsum[--d2];
}
cbox1[dim2] = d2;
cbox2[dim1] = cbox1[dim2] + 1;
console.log("cbox counts: " + (cbox.count()) + ", " + (cbox1.count()) + ", " + (cbox2.count()));
return [cbox1, cbox2];
}
}
};
if (maxw === rw) {
return doCut("r");
}
if (maxw === gw) {
return doCut("g");
}
if (maxw === bw) {
return doCut("b");
}
};
MMCQ.prototype.quantize = function(pixels, maxcolors) {
var cbox, cmap, histo, iter, pq, pq2,
_this = this;
if ((!pixels.length) || (maxcolors < 2) || (maxcolors > 256)) {
console.log("invalid arguments");
return false;
}
histo = getHisto(pixels);
cbox = cboxFromPixels(pixels, histo);
pq = new PriorityQueue(function(a, b) {
var va, vb;
va = a.count();
vb = b.count();
if (va > vb) {
return 1;
} else if (va < vb) {
return -1;
} else {
return 0;
}
});
pq.push(cbox);
iter = function(lh, target) {
var cbox1, cbox2, cboxes, ncolors, niters;
ncolors = 1;
niters = 0;
while (niters < _this.maxIterations) {
cbox = lh.pop();
if (!cbox.count()) {
lh.push(cbox);
niters++;
continue;
}
cboxes = medianCutApply(histo, cbox);
cbox1 = cboxes[0];
cbox2 = cboxes[1];
if (!cbox1) {
console.log("cbox1 not defined; shouldn't happen");
return;
}
lh.push(cbox1);
if (cbox2) {
lh.push(cbox2);
ncolors++;
}
if (ncolors >= target) {
return;
}
if ((niters++) > _this.maxIterations) {
console.log("infinite loop; perhaps too few pixels");
return;
}
}
};
iter(pq, this.fractByPopulations * maxcolors);
pq2 = new PriorityQueue(function(a, b) {
var va, vb;
va = a.count() * a.volume();
vb = b.count() * b.volume();
if (va > vb) {
return 1;
} else if (va < vb) {
return -1;
} else {
return 0;
}
});
while (pq.size()) {
pq2.push(pq.pop());
}
iter(pq2, maxcolors - pq2.size());
cmap = new ColorMap;
while (pq2.size()) {
cmap.push(pq2.pop());
}
return cmap;
};
return MMCQ;
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment