mirror of
https://github.com/janishutz/color-thief.git
synced 2025-11-25 05:44:24 +00:00
median cut palette algorithm workingls still need to get dominant color.
This commit is contained in:
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Compiled source #
|
||||||
|
###################
|
||||||
|
*.com
|
||||||
|
*.class
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
*.o
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Packages #
|
||||||
|
############
|
||||||
|
# it's better to unpack these files and commit the raw source
|
||||||
|
# git has its own built in compression methods
|
||||||
|
*.7z
|
||||||
|
*.dmg
|
||||||
|
*.gz
|
||||||
|
*.iso
|
||||||
|
*.jar
|
||||||
|
*.rar
|
||||||
|
*.tar
|
||||||
|
*.zip
|
||||||
|
|
||||||
|
# Logs and databases #
|
||||||
|
######################
|
||||||
|
*.log
|
||||||
|
*.sql
|
||||||
|
*.sqlite
|
||||||
|
|
||||||
|
# OS generated files #
|
||||||
|
######################
|
||||||
|
.DS_Store*
|
||||||
|
ehthumbs.db
|
||||||
|
Icon?
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# App Files #
|
||||||
|
#############
|
||||||
|
*.tmproj
|
||||||
BIN
.sass-cache/1a2431077de703832e834851e7bd1b7a1f21919c/_base.sassc
Normal file
BIN
.sass-cache/1a2431077de703832e834851e7bd1b7a1f21919c/_base.sassc
Normal file
Binary file not shown.
BIN
.sass-cache/1a2431077de703832e834851e7bd1b7a1f21919c/app.sassc
Normal file
BIN
.sass-cache/1a2431077de703832e834851e7bd1b7a1f21919c/app.sassc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.sass-cache/2407a1964da03a4863cad18bcb66f55c2df23671/_box.scssc
Normal file
BIN
.sass-cache/2407a1964da03a4863cad18bcb66f55c2df23671/_box.scssc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.sass-cache/5976d8a8a45a4a27d1a01fb6e3c16d1849f18b81/_css3.scssc
Normal file
BIN
.sass-cache/5976d8a8a45a4a27d1a01fb6e3c16d1849f18b81/_css3.scssc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
115
css/app.css
Normal file
115
css/app.css
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
/* line 17, ../../../../.rvm/gems/ruby-1.9.2-p290/gems/compass-0.11.5/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
html, body, div, span, applet, object, iframe,
|
||||||
|
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||||
|
a, abbr, acronym, address, big, cite, code,
|
||||||
|
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||||
|
small, strike, strong, sub, sup, tt, var,
|
||||||
|
b, u, i, center,
|
||||||
|
dl, dt, dd, ol, ul, li,
|
||||||
|
fieldset, form, label, legend,
|
||||||
|
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||||
|
article, aside, canvas, details, embed,
|
||||||
|
figure, figcaption, footer, header, hgroup,
|
||||||
|
menu, nav, output, ruby, section, summary,
|
||||||
|
time, mark, audio, video {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font-size: 100%;
|
||||||
|
font: inherit;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 20, ../../../../.rvm/gems/ruby-1.9.2-p290/gems/compass-0.11.5/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
body {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 22, ../../../../.rvm/gems/ruby-1.9.2-p290/gems/compass-0.11.5/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
ol, ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 24, ../../../../.rvm/gems/ruby-1.9.2-p290/gems/compass-0.11.5/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 26, ../../../../.rvm/gems/ruby-1.9.2-p290/gems/compass-0.11.5/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
caption, th, td {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: normal;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 28, ../../../../.rvm/gems/ruby-1.9.2-p290/gems/compass-0.11.5/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
q, blockquote {
|
||||||
|
quotes: none;
|
||||||
|
}
|
||||||
|
/* line 101, ../../../../.rvm/gems/ruby-1.9.2-p290/gems/compass-0.11.5/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
q:before, q:after, blockquote:before, blockquote:after {
|
||||||
|
content: "";
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 30, ../../../../.rvm/gems/ruby-1.9.2-p290/gems/compass-0.11.5/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
a img {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 114, ../../../../.rvm/gems/ruby-1.9.2-p290/gems/compass-0.11.5/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 3, ../sass/app.sass */
|
||||||
|
body {
|
||||||
|
background: #222222;
|
||||||
|
color: white;
|
||||||
|
font-family: "lucida grande", sans-serif;
|
||||||
|
line-height: 1.625em;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 10, ../sass/app.sass */
|
||||||
|
h1 {
|
||||||
|
font-size: 36px;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 17, ../sass/app.sass */
|
||||||
|
.imageSection {
|
||||||
|
overflow: hidden;
|
||||||
|
*zoom: 1;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
/* line 20, ../sass/app.sass */
|
||||||
|
.imageSection img {
|
||||||
|
float: left;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
/* line 23, ../sass/app.sass */
|
||||||
|
.imageSection .colors {
|
||||||
|
width: 400px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
/* line 26, ../sass/app.sass */
|
||||||
|
.imageSection .colors .function {
|
||||||
|
overflow: hidden;
|
||||||
|
*zoom: 1;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
/* line 29, ../sass/app.sass */
|
||||||
|
.imageSection .colors .function .swatch {
|
||||||
|
width: 40px;
|
||||||
|
height: 20px;
|
||||||
|
margin: 0 4px 4px 0;
|
||||||
|
background: #dddddd;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 36, ../sass/app.sass */
|
||||||
|
canvas {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
BIN
img/6.jpeg
Normal file
BIN
img/6.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
131
index.html
Executable file
131
index.html
Executable file
@@ -0,0 +1,131 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html class="no-js" lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
|
||||||
|
<title>Image Palette</title>
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="author" content="">
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="css/app.css">
|
||||||
|
|
||||||
|
<script src="js/libs/modernizr-2.0.6.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="container">
|
||||||
|
<header>
|
||||||
|
<h1>Image Palette</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id="main" role="main">
|
||||||
|
|
||||||
|
<div class="imageSection">
|
||||||
|
<img src="img/1.jpg" />
|
||||||
|
<div class="colors">
|
||||||
|
<div class="function averageRGB">
|
||||||
|
<h3>getAverageRGB()</h3>
|
||||||
|
</div>
|
||||||
|
<div class="function areaBasedPalette">
|
||||||
|
<h3>createAreaBasedPalette()</h3>
|
||||||
|
</div>
|
||||||
|
<div class="function medianCutPalette">
|
||||||
|
<h3>createMedianCutPalette()</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="imageSection">
|
||||||
|
<img src="img/2.jpg" />
|
||||||
|
<div class="colors">
|
||||||
|
<div class="function averageRGB">
|
||||||
|
<h3>getAverageRGB()</h3>
|
||||||
|
</div>
|
||||||
|
<div class="function areaBasedPalette">
|
||||||
|
<h3>createAreaBasedPalette()</h3>
|
||||||
|
</div>
|
||||||
|
<div class="function medianCutPalette">
|
||||||
|
<h3>createMedianCutPalette()</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="imageSection">
|
||||||
|
<img src="img/3.jpg" />
|
||||||
|
<div class="colors">
|
||||||
|
<div class="function averageRGB">
|
||||||
|
<h3>getAverageRGB()</h3>
|
||||||
|
</div>
|
||||||
|
<div class="function areaBasedPalette">
|
||||||
|
<h3>createAreaBasedPalette()</h3>
|
||||||
|
</div>
|
||||||
|
<div class="function medianCutPalette">
|
||||||
|
<h3>createMedianCutPalette()</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="imageSection">
|
||||||
|
<img src="img/4.png" />
|
||||||
|
<div class="colors">
|
||||||
|
<div class="function averageRGB">
|
||||||
|
<h3>getAverageRGB()</h3>
|
||||||
|
</div>
|
||||||
|
<div class="function areaBasedPalette">
|
||||||
|
<h3>createAreaBasedPalette()</h3>
|
||||||
|
</div>
|
||||||
|
<div class="function medianCutPalette">
|
||||||
|
<h3>createMedianCutPalette()</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="imageSection">
|
||||||
|
<img src="img/5.png" />
|
||||||
|
<div class="colors">
|
||||||
|
<div class="function averageRGB">
|
||||||
|
<h3>getAverageRGB()</h3>
|
||||||
|
</div>
|
||||||
|
<div class="function areaBasedPalette">
|
||||||
|
<h3>createAreaBasedPalette()</h3>
|
||||||
|
</div>
|
||||||
|
<div class="function medianCutPalette">
|
||||||
|
<h3>createMedianCutPalette()</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="imageSection">
|
||||||
|
<img src="img/6.jpeg" />
|
||||||
|
<div class="colors">
|
||||||
|
<div class="function averageRGB">
|
||||||
|
<h3>getAverageRGB()</h3>
|
||||||
|
</div>
|
||||||
|
<div class="function areaBasedPalette">
|
||||||
|
<h3>createAreaBasedPalette()</h3>
|
||||||
|
</div>
|
||||||
|
<div class="function medianCutPalette">
|
||||||
|
<h3>createMedianCutPalette()</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
</div> <!--! end of #container -->
|
||||||
|
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
|
||||||
|
<script>window.jQuery || document.write('<script src="js/libs/jquery-1.6.2.min.js"><\/script>')</script>
|
||||||
|
|
||||||
|
<script src="js/quantize.js"></script>
|
||||||
|
|
||||||
|
<script src="js/canvasimage.js"></script>
|
||||||
|
<script src="js/functions.js"></script>
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
39
js/app.js
Normal file
39
js/app.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
$(document).ready(function(){
|
||||||
|
|
||||||
|
$('img').each(function(index){
|
||||||
|
|
||||||
|
var averageRGB = getAverageRGB(this);
|
||||||
|
var areaPalette = createAreaBasedPalette(this, 9);
|
||||||
|
var medianPalette = createMedianCutPalette(this, 10);
|
||||||
|
|
||||||
|
var imageSection = $(this).closest('.imageSection'),
|
||||||
|
swatchEl;
|
||||||
|
|
||||||
|
swatchEl = $('<div>', {
|
||||||
|
'class': 'swatch'
|
||||||
|
}).css('background-color','rgba('+averageRGB.r+','+averageRGB.g+ ','+averageRGB.b+', 1)');
|
||||||
|
|
||||||
|
imageSection.find('.averageRGB').append(swatchEl);
|
||||||
|
|
||||||
|
var areaBasedPalette = imageSection.find('.areaBasedPalette');
|
||||||
|
|
||||||
|
$.each(areaPalette, function(index, value){
|
||||||
|
swatchEl = $('<div>', {
|
||||||
|
'class': 'swatch'
|
||||||
|
}).css('background-color','rgba('+value.r+','+value.g+ ','+value.b+', 1)');
|
||||||
|
areaBasedPalette.append(swatchEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
var medianCutPalette = imageSection.find('.medianCutPalette');
|
||||||
|
|
||||||
|
$.each(medianPalette, function(index, value){
|
||||||
|
swatchEl = $('<div>', {
|
||||||
|
'class': 'swatch'
|
||||||
|
}).css('background-color','rgba('+value[0]+','+value[1]+ ','+value[2]+', 1)');
|
||||||
|
medianCutPalette.append(swatchEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
37
js/canvasimage.js
Normal file
37
js/canvasimage.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
Class that wraps the html image element and canvas.
|
||||||
|
It also simplifies some of the canvas context manipulation
|
||||||
|
with a set of helper functions.
|
||||||
|
*/
|
||||||
|
var CanvasImage = function(image){
|
||||||
|
// If jquery object is passed in, get html element
|
||||||
|
this.imgEl = (image.jquery)? image[0]: image;
|
||||||
|
|
||||||
|
this.canvas = document.createElement('canvas'),
|
||||||
|
this.context = this.canvas.getContext('2d');
|
||||||
|
|
||||||
|
document.body.appendChild(this.canvas);
|
||||||
|
|
||||||
|
this.width = this.canvas.width = $(this.imgEl).width(),
|
||||||
|
this.height = this.canvas.height = $(this.imgEl).height();
|
||||||
|
|
||||||
|
this.context.drawImage(this.imgEl, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
CanvasImage.prototype.clear = function() {
|
||||||
|
this.context.clearRect(0, 0, this.width, this.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
CanvasImage.prototype.update = function(imageData) {
|
||||||
|
console.log('worked');
|
||||||
|
this.context.putImageData(imageData, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
CanvasImage.prototype.getPixelCount = function() {
|
||||||
|
return this.width * this.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
CanvasImage.prototype.getImageData = function() {
|
||||||
|
return this.context.getImageData(0, 0, this.width, this.height);
|
||||||
|
}
|
||||||
|
|
||||||
120
js/functions.js
Normal file
120
js/functions.js
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
|
||||||
|
function getAverageRGB(sourceImage) {
|
||||||
|
// Config
|
||||||
|
var sampleSize = 10;
|
||||||
|
|
||||||
|
// Create custom CanvasImage object
|
||||||
|
var image = new CanvasImage(sourceImage),
|
||||||
|
imageData = image.getImageData(),
|
||||||
|
pixels = imageData.data,
|
||||||
|
pixelCount = image.getPixelCount();
|
||||||
|
|
||||||
|
// Reset vars
|
||||||
|
var i = 0,
|
||||||
|
count = 0,
|
||||||
|
rgb = {r:0,g:0,b:0};
|
||||||
|
|
||||||
|
// Loop through every # pixels. (# is set in Config above via the blockSize var)
|
||||||
|
// Add all the red values together, repeat for blue and green.
|
||||||
|
// Last step, divide by the number of pixels checked to get average.
|
||||||
|
while ( (i += sampleSize * 4) < pixelCount ) {
|
||||||
|
++count;
|
||||||
|
rgb.r += pixels[i];
|
||||||
|
rgb.g += pixels[i+1];
|
||||||
|
rgb.b += pixels[i+2];
|
||||||
|
}
|
||||||
|
|
||||||
|
rgb.r = Math.floor(rgb.r/count);
|
||||||
|
rgb.g = Math.floor(rgb.g/count);
|
||||||
|
rgb.b = Math.floor(rgb.b/count);
|
||||||
|
|
||||||
|
return rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function createAreaBasedPalette(sourceImage, colorCount){
|
||||||
|
|
||||||
|
var palette = [];
|
||||||
|
|
||||||
|
// Create custom CanvasImage object
|
||||||
|
var image = new CanvasImage(sourceImage),
|
||||||
|
imageData = image.getImageData(),
|
||||||
|
pixels = imageData.data,
|
||||||
|
pixelCount = image.getPixelCount();
|
||||||
|
|
||||||
|
|
||||||
|
// How big a pixel area does each palette color get
|
||||||
|
var rowCount = colCount = Math.round(Math.sqrt(colorCount)),
|
||||||
|
colWidth = Math.round(image.width / colCount),
|
||||||
|
rowHeight = Math.round(image.height / rowCount);
|
||||||
|
|
||||||
|
var count = offset = rowOffset = vertOffset = horizOffset = 0,
|
||||||
|
rgb = {r:0,g:0,b:0};
|
||||||
|
|
||||||
|
for(var i=0; i<rowCount; i++){
|
||||||
|
vertOffset = i * rowHeight * image.width * 4;
|
||||||
|
|
||||||
|
for(var j=0; j<colCount; j++){
|
||||||
|
horizOffset = j * colWidth * 4;
|
||||||
|
|
||||||
|
for( var k = 0; k < rowHeight; k++){
|
||||||
|
rowOffset = k * image.width * 4;
|
||||||
|
|
||||||
|
for( var l = 0; l < colWidth; l++){
|
||||||
|
offset = vertOffset + horizOffset + rowOffset + (l * 4);
|
||||||
|
rgb.r += pixels[offset];
|
||||||
|
rgb.g += pixels[offset+1];
|
||||||
|
rgb.b += pixels[offset+2];
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
rgb.r = Math.floor(rgb.r/count);
|
||||||
|
rgb.g = Math.floor(rgb.g/count);
|
||||||
|
rgb.b = Math.floor(rgb.b/count);
|
||||||
|
palette.push(rgb);
|
||||||
|
|
||||||
|
// reset before next section
|
||||||
|
rgb = {r:0,g:0,b:0};
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return palette;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createMedianCutPalette(sourceImage, colorCount){
|
||||||
|
|
||||||
|
var palette = [];
|
||||||
|
|
||||||
|
// Create custom CanvasImage object
|
||||||
|
var image = new CanvasImage(sourceImage),
|
||||||
|
imageData = image.getImageData(),
|
||||||
|
pixels = imageData.data,
|
||||||
|
pixelCount = image.getPixelCount();
|
||||||
|
|
||||||
|
var pixelArray = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < pixelCount; i++) {
|
||||||
|
pixelArray.push( [pixels[i*4], pixels[i*4+1], pixels[i*4+2]]);
|
||||||
|
};
|
||||||
|
|
||||||
|
var cmap = MMCQ.quantize(pixelArray, colorCount);
|
||||||
|
var newPalette = cmap.palette();
|
||||||
|
|
||||||
|
return newPalette;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
for (var i = 0; i < pixelCount; i++) {
|
||||||
|
pixels[i*4] = pixels[i*4]; // Red
|
||||||
|
pixels[i*4+1] = pixels[i*4+1]; // Green
|
||||||
|
pixels[i*4+2] = pixels[i*4+2]; // Blue
|
||||||
|
};
|
||||||
|
|
||||||
|
image.clear();
|
||||||
|
imageData.data = pixels;
|
||||||
|
image.update(imageData);
|
||||||
|
*/
|
||||||
18
js/libs/jquery-1.6.2.min.js
vendored
Executable file
18
js/libs/jquery-1.6.2.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
4
js/libs/modernizr-2.0.6.min.js
vendored
Executable file
4
js/libs/modernizr-2.0.6.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
471
js/quantize.js
Normal file
471
js/quantize.js
Normal file
@@ -0,0 +1,471 @@
|
|||||||
|
/*!
|
||||||
|
* quantize.js Copyright 2008 Nick Rabinowitz.
|
||||||
|
* Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
// fill out a couple protovis dependencies
|
||||||
|
/*!
|
||||||
|
* Block below copied from Protovis: http://mbostock.github.com/protovis/
|
||||||
|
* Copyright 2010 Stanford Visualization Group
|
||||||
|
* Licensed under the BSD License: http://www.opensource.org/licenses/bsd-license.php
|
||||||
|
*/
|
||||||
|
if (!pv) {
|
||||||
|
var pv = {
|
||||||
|
map: function(array, f) {
|
||||||
|
var o = {};
|
||||||
|
return f
|
||||||
|
? array.map(function(d, i) { o.index = i; return f.call(o, d); })
|
||||||
|
: array.slice();
|
||||||
|
},
|
||||||
|
naturalOrder: function(a, b) {
|
||||||
|
return (a < b) ? -1 : ((a > b) ? 1 : 0);
|
||||||
|
},
|
||||||
|
sum: function(array, f) {
|
||||||
|
var o = {};
|
||||||
|
return array.reduce(f
|
||||||
|
? function(p, d, i) { o.index = i; return p + f.call(o, d); }
|
||||||
|
: function(p, d) { return p + d; }, 0);
|
||||||
|
},
|
||||||
|
max: function(array, f) {
|
||||||
|
return Math.max.apply(null, f ? pv.map(array, f) : array);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic Javascript port of the MMCQ (modified median cut quantization)
|
||||||
|
* algorithm from the Leptonica library (http://www.leptonica.com/).
|
||||||
|
* Returns a color map you can use to map original pixels to the reduced
|
||||||
|
* palette. Still a work in progress.
|
||||||
|
*
|
||||||
|
* @author Nick Rabinowitz
|
||||||
|
* @example
|
||||||
|
|
||||||
|
// array of pixels as [R,G,B] arrays
|
||||||
|
var myPixels = [[190,197,190], [202,204,200], [207,214,210], [211,214,211], [205,207,207]
|
||||||
|
// etc
|
||||||
|
];
|
||||||
|
var maxColors = 4;
|
||||||
|
|
||||||
|
var cmap = MMCQ.quantize(myPixels, maxColors);
|
||||||
|
var newPalette = cmap.palette();
|
||||||
|
var newPixels = myPixels.map(function(p) {
|
||||||
|
return cmap.map(p);
|
||||||
|
});
|
||||||
|
|
||||||
|
*/
|
||||||
|
var MMCQ = (function() {
|
||||||
|
// private constants
|
||||||
|
var sigbits = 5,
|
||||||
|
rshift = 8 - sigbits,
|
||||||
|
maxIterations = 1000,
|
||||||
|
fractByPopulations = 0.75;
|
||||||
|
|
||||||
|
// get reduced-space color index for a pixel
|
||||||
|
function getColorIndex(r, g, b) {
|
||||||
|
return (r << (2 * sigbits)) + (g << sigbits) + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple priority queue
|
||||||
|
function PQueue(comparator) {
|
||||||
|
var contents = [],
|
||||||
|
sorted = false;
|
||||||
|
|
||||||
|
function sort() {
|
||||||
|
contents.sort(comparator);
|
||||||
|
sorted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
push: function(o) {
|
||||||
|
contents.push(o);
|
||||||
|
sorted = false;
|
||||||
|
},
|
||||||
|
peek: function(index) {
|
||||||
|
if (!sorted) sort();
|
||||||
|
if (index===undefined) index = contents.length - 1;
|
||||||
|
return contents[index];
|
||||||
|
},
|
||||||
|
pop: function() {
|
||||||
|
if (!sorted) sort();
|
||||||
|
return contents.pop();
|
||||||
|
},
|
||||||
|
size: function() {
|
||||||
|
return contents.length;
|
||||||
|
},
|
||||||
|
map: function(f) {
|
||||||
|
return contents.map(f);
|
||||||
|
},
|
||||||
|
debug: function() {
|
||||||
|
if (!sorted) sort();
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3d color space box
|
||||||
|
function VBox(r1, r2, g1, g2, b1, b2, histo) {
|
||||||
|
var vbox = this;
|
||||||
|
vbox.r1 = r1;
|
||||||
|
vbox.r2 = r2;
|
||||||
|
vbox.g1 = g1;
|
||||||
|
vbox.g2 = g2;
|
||||||
|
vbox.b1 = b1;
|
||||||
|
vbox.b2 = b2;
|
||||||
|
vbox.histo = histo;
|
||||||
|
}
|
||||||
|
VBox.prototype = {
|
||||||
|
volume: function(force) {
|
||||||
|
var vbox = this;
|
||||||
|
if (!vbox._volume || force) {
|
||||||
|
vbox._volume = ((vbox.r2 - vbox.r1 + 1) * (vbox.g2 - vbox.g1 + 1) * (vbox.b2 - vbox.b1 + 1));
|
||||||
|
}
|
||||||
|
return vbox._volume;
|
||||||
|
},
|
||||||
|
count: function(force) {
|
||||||
|
var vbox = this,
|
||||||
|
histo = vbox.histo;
|
||||||
|
if (!vbox._count_set || force) {
|
||||||
|
var npix = 0,
|
||||||
|
i, j, k;
|
||||||
|
for (i = vbox.r1; i <= vbox.r2; i++) {
|
||||||
|
for (j = vbox.g1; j <= vbox.g2; j++) {
|
||||||
|
for (k = vbox.b1; k <= vbox.b2; k++) {
|
||||||
|
index = getColorIndex(i,j,k);
|
||||||
|
npix += (histo[index] || 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vbox._count = npix;
|
||||||
|
vbox._count_set = true;
|
||||||
|
}
|
||||||
|
return vbox._count;
|
||||||
|
},
|
||||||
|
copy: function() {
|
||||||
|
var vbox = this;
|
||||||
|
return new VBox(vbox.r1, vbox.r2, vbox.g1, vbox.g2, vbox.b1, vbox.b2, vbox.histo);
|
||||||
|
},
|
||||||
|
avg: function(force) {
|
||||||
|
var vbox = this,
|
||||||
|
histo = vbox.histo;
|
||||||
|
if (!vbox._avg || force) {
|
||||||
|
var ntot = 0,
|
||||||
|
mult = 1 << (8 - sigbits),
|
||||||
|
rsum = 0,
|
||||||
|
gsum = 0,
|
||||||
|
bsum = 0,
|
||||||
|
hval,
|
||||||
|
i, j, k, histoindex;
|
||||||
|
for (i = vbox.r1; i <= vbox.r2; i++) {
|
||||||
|
for (j = vbox.g1; j <= vbox.g2; j++) {
|
||||||
|
for (k = vbox.b1; k <= vbox.b2; k++) {
|
||||||
|
histoindex = getColorIndex(i,j,k);
|
||||||
|
hval = histo[histoindex] || 0;
|
||||||
|
ntot += hval;
|
||||||
|
rsum += (hval * (i + 0.5) * mult);
|
||||||
|
gsum += (hval * (j + 0.5) * mult);
|
||||||
|
bsum += (hval * (k + 0.5) * mult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ntot) {
|
||||||
|
vbox._avg = [~~(rsum/ntot), ~~(gsum/ntot), ~~(bsum/ntot)];
|
||||||
|
} else {
|
||||||
|
// console.log('empty box');
|
||||||
|
vbox._avg = [
|
||||||
|
~~(mult * (vbox.r1 + vbox.r2 + 1) / 2),
|
||||||
|
~~(mult * (vbox.g1 + vbox.g2 + 1) / 2),
|
||||||
|
~~(mult * (vbox.b1 + vbox.b2 + 1) / 2)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vbox._avg;
|
||||||
|
},
|
||||||
|
contains: function(pixel) {
|
||||||
|
var vbox = this,
|
||||||
|
rval = pixel[0] >> rshift;
|
||||||
|
gval = pixel[1] >> rshift;
|
||||||
|
bval = pixel[2] >> rshift;
|
||||||
|
return (rval >= vbox.r1 && rval <= vbox.r2 &&
|
||||||
|
gval >= vbox.g1 && rval <= vbox.g2 &&
|
||||||
|
bval >= vbox.b1 && rval <= vbox.b2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Color map
|
||||||
|
function CMap() {
|
||||||
|
this.vboxes = new PQueue(function(a,b) {
|
||||||
|
return pv.naturalOrder(
|
||||||
|
a.vbox.count()*a.vbox.volume(),
|
||||||
|
b.vbox.count()*b.vbox.volume()
|
||||||
|
)
|
||||||
|
});;
|
||||||
|
}
|
||||||
|
CMap.prototype = {
|
||||||
|
push: function(vbox) {
|
||||||
|
this.vboxes.push({
|
||||||
|
vbox: vbox,
|
||||||
|
color: vbox.avg()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
palette: function() {
|
||||||
|
return this.vboxes.map(function(vb) { return vb.color });
|
||||||
|
},
|
||||||
|
size: function() {
|
||||||
|
return this.vboxes.size();
|
||||||
|
},
|
||||||
|
map: function(color) {
|
||||||
|
var vboxes = this.vboxes;
|
||||||
|
for (var i=0; i<vboxes.size(); i++) {
|
||||||
|
if (vboxes.peek(i).vbox.contains(color)) {
|
||||||
|
return vboxes.peek(i).color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.nearest(color);
|
||||||
|
},
|
||||||
|
nearest: function(color) {
|
||||||
|
var vboxes = this.vboxes,
|
||||||
|
d1, d2, pColor;
|
||||||
|
for (var i=0; i<vboxes.size(); i++) {
|
||||||
|
d2 = Math.sqrt(
|
||||||
|
Math.pow(color[0] - vboxes.peek(i).color[0], 2) +
|
||||||
|
Math.pow(color[1] - vboxes.peek(i).color[1], 2) +
|
||||||
|
Math.pow(color[1] - vboxes.peek(i).color[1], 2)
|
||||||
|
);
|
||||||
|
if (d2 < d1 || d1 === undefined) {
|
||||||
|
d1 = d2;
|
||||||
|
pColor = vboxes.peek(i).color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pColor;
|
||||||
|
},
|
||||||
|
forcebw: function() {
|
||||||
|
// XXX: won't work yet
|
||||||
|
var vboxes = this.vboxes;
|
||||||
|
vboxes.sort(function(a,b) { return pv.naturalOrder(pv.sum(a.color), pv.sum(b.color) )});
|
||||||
|
|
||||||
|
// force darkest color to black if everything < 5
|
||||||
|
var lowest = vboxes[0].color;
|
||||||
|
if (lowest[0] < 5 && lowest[1] < 5 && lowest[2] < 5)
|
||||||
|
vboxes[0].color = [0,0,0];
|
||||||
|
|
||||||
|
// force lightest color to white if everything > 251
|
||||||
|
var idx = vboxes.length-1,
|
||||||
|
highest = vboxes[idx].color;
|
||||||
|
if (highest[0] > 251 && highest[1] > 251 && highest[2] > 251)
|
||||||
|
vboxes[idx].color = [255,255,255];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// histo (1-d array, giving the number of pixels in
|
||||||
|
// each quantized region of color space), or null on error
|
||||||
|
function getHisto(pixels) {
|
||||||
|
var histosize = 1 << (3 * sigbits),
|
||||||
|
histo = new Array(histosize),
|
||||||
|
index, rval, gval, bval;
|
||||||
|
pixels.forEach(function(pixel) {
|
||||||
|
rval = pixel[0] >> rshift;
|
||||||
|
gval = pixel[1] >> rshift;
|
||||||
|
bval = pixel[2] >> rshift;
|
||||||
|
index = getColorIndex(rval, gval, bval);
|
||||||
|
histo[index] = (histo[index] || 0) + 1;
|
||||||
|
});
|
||||||
|
return histo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function vboxFromPixels(pixels, histo) {
|
||||||
|
var rmin=1000000, rmax=0,
|
||||||
|
gmin=1000000, gmax=0,
|
||||||
|
bmin=1000000, bmax=0,
|
||||||
|
rval, gval, bval;
|
||||||
|
// find min/max
|
||||||
|
pixels.forEach(function(pixel) {
|
||||||
|
rval = pixel[0] >> rshift;
|
||||||
|
gval = pixel[1] >> rshift;
|
||||||
|
bval = pixel[2] >> rshift;
|
||||||
|
if (rval < rmin) rmin = rval;
|
||||||
|
else if (rval > rmax) rmax = rval;
|
||||||
|
if (gval < gmin) gmin = gval;
|
||||||
|
else if (gval > gmax) gmax = gval;
|
||||||
|
if (bval < bmin) bmin = bval;
|
||||||
|
else if (bval > bmax) bmax = bval;
|
||||||
|
});
|
||||||
|
return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo);
|
||||||
|
}
|
||||||
|
|
||||||
|
function medianCutApply(histo, vbox) {
|
||||||
|
if (!vbox.count()) return;
|
||||||
|
|
||||||
|
var rw = vbox.r2 - vbox.r1 + 1,
|
||||||
|
gw = vbox.g2 - vbox.g1 + 1,
|
||||||
|
bw = vbox.b2 - vbox.b1 + 1,
|
||||||
|
maxw = pv.max([rw, gw, bw]);
|
||||||
|
// only one pixel, no split
|
||||||
|
if (vbox.count() == 1) {
|
||||||
|
return [vbox.copy()]
|
||||||
|
}
|
||||||
|
/* Find the partial sum arrays along the selected axis. */
|
||||||
|
var total = 0,
|
||||||
|
partialsum = [],
|
||||||
|
lookaheadsum = [],
|
||||||
|
i, j, k, sum, index;
|
||||||
|
if (maxw == rw) {
|
||||||
|
for (i = vbox.r1; i <= vbox.r2; i++) {
|
||||||
|
sum = 0;
|
||||||
|
for (j = vbox.g1; j <= vbox.g2; j++) {
|
||||||
|
for (k = vbox.b1; k <= vbox.b2; k++) {
|
||||||
|
index = getColorIndex(i,j,k);
|
||||||
|
sum += (histo[index] || 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total += sum;
|
||||||
|
partialsum[i] = total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (maxw == gw) {
|
||||||
|
for (i = vbox.g1; i <= vbox.g2; i++) {
|
||||||
|
sum = 0;
|
||||||
|
for (j = vbox.r1; j <= vbox.r2; j++) {
|
||||||
|
for (k = vbox.b1; k <= vbox.b2; k++) {
|
||||||
|
index = getColorIndex(j,i,k);
|
||||||
|
sum += (histo[index] || 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total += sum;
|
||||||
|
partialsum[i] = total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { /* maxw == bw */
|
||||||
|
for (i = vbox.b1; i <= vbox.b2; i++) {
|
||||||
|
sum = 0;
|
||||||
|
for (j = vbox.r1; j <= vbox.r2; j++) {
|
||||||
|
for (k = vbox.g1; k <= vbox.g2; k++) {
|
||||||
|
index = getColorIndex(j,k,i);
|
||||||
|
sum += (histo[index] || 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total += sum;
|
||||||
|
partialsum[i] = total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
partialsum.forEach(function(d,i) {
|
||||||
|
lookaheadsum[i] = total-d
|
||||||
|
});
|
||||||
|
function doCut(color) {
|
||||||
|
var dim1 = color + '1',
|
||||||
|
dim2 = color + '2',
|
||||||
|
left, right, vbox1, vbox2, d2, count2=0;
|
||||||
|
for (i = vbox[dim1]; i <= vbox[dim2]; i++) {
|
||||||
|
if (partialsum[i] > total / 2) {
|
||||||
|
vbox1 = vbox.copy();
|
||||||
|
vbox2 = vbox.copy();
|
||||||
|
left = i - vbox[dim1];
|
||||||
|
right = vbox[dim2] - i;
|
||||||
|
if (left <= right)
|
||||||
|
d2 = Math.min(vbox[dim2] - 1, ~~(i + right / 2));
|
||||||
|
else d2 = Math.max(vbox[dim1], ~~(i - 1 - left / 2));
|
||||||
|
// avoid 0-count boxes
|
||||||
|
while (!partialsum[d2]) d2++;
|
||||||
|
count2 = lookaheadsum[d2];
|
||||||
|
while (!count2 && partialsum[d2-1]) count2 = lookaheadsum[--d2];
|
||||||
|
// set dimensions
|
||||||
|
vbox1[dim2] = d2;
|
||||||
|
vbox2[dim1] = vbox1[dim2] + 1;
|
||||||
|
// console.log('vbox counts:', vbox.count(), vbox1.count(), vbox2.count());
|
||||||
|
return [vbox1, vbox2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// determine the cut planes
|
||||||
|
return maxw == rw ? doCut('r') :
|
||||||
|
maxw == gw ? doCut('g') :
|
||||||
|
doCut('b');
|
||||||
|
}
|
||||||
|
|
||||||
|
function quantize(pixels, maxcolors) {
|
||||||
|
// short-circuit
|
||||||
|
if (!pixels.length || maxcolors < 2 || maxcolors > 256) {
|
||||||
|
// console.log('wrong number of maxcolors');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: check color content and convert to grayscale if insufficient
|
||||||
|
|
||||||
|
var histo = getHisto(pixels),
|
||||||
|
histosize = 1 << (3 * sigbits);
|
||||||
|
|
||||||
|
// check that we aren't below maxcolors already
|
||||||
|
var nColors = 0;
|
||||||
|
histo.forEach(function() { nColors++ });
|
||||||
|
if (nColors <= maxcolors) {
|
||||||
|
// XXX: generate the new colors from the histo and return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the beginning vbox from the colors
|
||||||
|
var vbox = vboxFromPixels(pixels, histo),
|
||||||
|
pq = new PQueue(function(a,b) { return pv.naturalOrder(a.count(), b.count()) });
|
||||||
|
pq.push(vbox);
|
||||||
|
|
||||||
|
// inner function to do the iteration
|
||||||
|
function iter(lh, target) {
|
||||||
|
var ncolors = 1,
|
||||||
|
niters = 0,
|
||||||
|
vbox;
|
||||||
|
while (niters < maxIterations) {
|
||||||
|
vbox = lh.pop();
|
||||||
|
if (!vbox.count()) { /* just put it back */
|
||||||
|
lh.push(vbox);
|
||||||
|
niters++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// do the cut
|
||||||
|
var vboxes = medianCutApply(histo, vbox),
|
||||||
|
vbox1 = vboxes[0],
|
||||||
|
vbox2 = vboxes[1];
|
||||||
|
|
||||||
|
if (!vbox1) {
|
||||||
|
// console.log("vbox1 not defined; shouldn't happen!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lh.push(vbox1);
|
||||||
|
if (vbox2) { /* vbox2 can be null */
|
||||||
|
lh.push(vbox2);
|
||||||
|
ncolors++;
|
||||||
|
}
|
||||||
|
if (ncolors >= target) return;
|
||||||
|
if (niters++ > maxIterations) {
|
||||||
|
// console.log("infinite loop; perhaps too few pixels!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// first set of colors, sorted by population
|
||||||
|
iter(pq, fractByPopulations * maxcolors);
|
||||||
|
// console.log(pq.size(), pq.debug().length, pq.debug().slice());
|
||||||
|
|
||||||
|
// Re-sort by the product of pixel occupancy times the size in color space.
|
||||||
|
var pq2 = new PQueue(function(a,b) {
|
||||||
|
return pv.naturalOrder(a.count()*a.volume(), b.count()*b.volume())
|
||||||
|
});
|
||||||
|
while (pq.size()) {
|
||||||
|
pq2.push(pq.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
// next set - generate the median cuts using the (npix * vol) sorting.
|
||||||
|
iter(pq2, maxcolors - pq2.size());
|
||||||
|
|
||||||
|
// calculate the actual colors
|
||||||
|
var cmap = new CMap();
|
||||||
|
while (pq2.size()) {
|
||||||
|
cmap.push(pq2.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
quantize: quantize
|
||||||
|
}
|
||||||
|
})();
|
||||||
12
sass/_base.sass
Normal file
12
sass/_base.sass
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
$color: #555
|
||||||
|
$headingColor: #222
|
||||||
|
$altColor: scale-color($color, $lightness: 50%)
|
||||||
|
$blue: #09a1ec
|
||||||
|
$orange: #e83
|
||||||
|
$green: #6bb445
|
||||||
|
|
||||||
|
$radius: 4px
|
||||||
|
|
||||||
|
@import "compass/reset"
|
||||||
|
@import "compass/utilities/general/clearfix"
|
||||||
|
@import "compass/css3"
|
||||||
38
sass/app.sass
Normal file
38
sass/app.sass
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
@import "base"
|
||||||
|
|
||||||
|
body
|
||||||
|
background: #222
|
||||||
|
color: #fff
|
||||||
|
font-family: 'lucida grande', sans-serif
|
||||||
|
line-height: 1.625em
|
||||||
|
padding: 40px
|
||||||
|
|
||||||
|
h1
|
||||||
|
font-size: 36px
|
||||||
|
margin-bottom: .5em
|
||||||
|
|
||||||
|
h3
|
||||||
|
|
||||||
|
|
||||||
|
.imageSection
|
||||||
|
+clearfix
|
||||||
|
margin-bottom: 40px
|
||||||
|
img
|
||||||
|
float: left
|
||||||
|
margin-right: 20px
|
||||||
|
.colors
|
||||||
|
width: 400px
|
||||||
|
float: left
|
||||||
|
.function
|
||||||
|
+clearfix
|
||||||
|
margin-bottom: 10px
|
||||||
|
.swatch
|
||||||
|
width: 40px
|
||||||
|
height: 20px
|
||||||
|
margin: 0 4px 4px 0
|
||||||
|
background: #ddd
|
||||||
|
float: left
|
||||||
|
|
||||||
|
canvas
|
||||||
|
display: none
|
||||||
|
|
||||||
Reference in New Issue
Block a user