- Removed jquery dependency for color-thief.js

- Updated jquery and modernizr for demo page.
This commit is contained in:
Lokesh Dhakar
2013-06-23 22:18:58 -07:00
parent 4272252db9
commit b088790b8d
10 changed files with 292 additions and 562 deletions

View File

@@ -89,7 +89,7 @@ h1 {
h2 {
font-size: 36px;
line-height: 1.2em;
margin-bottom: 0.2em;
margin-bottom: 0.3em;
}
/* line 51, ../sass/app.sass */
@@ -182,8 +182,52 @@ img {
background: #444444;
}
/* line 114, ../sass/app.sass */
.examples .image-section .target-image {
-moz-border-radius-bottomleft: 4px;
-webkit-border-bottom-left-radius: 4px;
border-bottom-left-radius: 4px;
-moz-border-radius-bottomright: 4px;
-webkit-border-bottom-right-radius: 4px;
border-bottom-right-radius: 4px;
-webkit-box-shadow: 0 4px 0 #333333;
-moz-box-shadow: 0 4px 0 #333333;
box-shadow: 0 4px 0 #333333;
}
/* line 117, ../sass/app.sass */
.examples .color-thief-output {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
/* line 120, ../sass/app.sass */
.examples .image-section.with-color-thief-output .target-image {
-moz-border-radius-bottomleft: 0;
-webkit-border-bottom-left-radius: 0;
border-bottom-left-radius: 0;
-moz-border-radius-bottomright: 0;
-webkit-border-bottom-right-radius: 0;
border-bottom-right-radius: 0;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
/* line 123, ../sass/app.sass */
.examples .image-section.with-color-thief-output .color-thief-output {
-webkit-box-shadow: 0 4px 0 #333333;
-moz-box-shadow: 0 4px 0 #333333;
box-shadow: 0 4px 0 #333333;
}
/* line 126, ../sass/app.sass */
.sharing {
position: fixed;
top: 20px;
right: 0;
}
/* -- Image examples ------------------------------------------------------------------ */
/* line 115, ../sass/app.sass */
/* line 133, ../sass/app.sass */
.run-functions-button {
position: absolute;
top: 50%;
@@ -200,16 +244,17 @@ img {
border-radius: 50%;
color: #686002;
background-color: #fdf485;
border-bottom: 2px solid #4f4901;
font-size: 24px;
font-weight: 500;
cursor: pointer;
}
/* line 130, ../sass/app.sass */
/* line 149, ../sass/app.sass */
.run-functions-button:hover {
background-color: #fef9b7;
color: #4f4901;
}
/* line 133, ../sass/app.sass */
/* line 152, ../sass/app.sass */
.run-functions-button:active {
-webkit-transform: scale(0.9, 0.9);
-moz-transform: scale(0.9, 0.9);
@@ -217,12 +262,12 @@ img {
-o-transform: scale(0.9, 0.9);
transform: scale(0.9, 0.9);
}
/* line 135, ../sass/app.sass */
/* line 154, ../sass/app.sass */
.run-functions-button.hide {
-webkit-transition: all 0.4s;
-moz-transition: all 0.4s;
-o-transition: all 0.4s;
transition: all 0.4s;
-webkit-transition: -webkit-transform 0.4s, top 0.4s;
-moz-transition: -moz-transform 0.4s, top 0.4s;
-o-transition: -o-transform 0.4s, top 0.4s;
transition: transform 0.4s, top 0.4s;
top: 100%;
-webkit-transform: scale(0, 0);
-moz-transform: scale(0, 0);
@@ -231,29 +276,62 @@ img {
transform: scale(0, 0);
}
/* line 140, ../sass/app.sass */
.target-image {
width: 100%;
/* line 161, ../sass/app.sass */
.touch .touch-label {
display: inline;
}
/* line 163, ../sass/app.sass */
.touch .no-touch-label {
display: none;
}
/* line 143, ../sass/app.sass */
/* line 166, ../sass/app.sass */
.no-touch .touch-label {
display: none;
}
/* line 168, ../sass/app.sass */
.no-touch .no-touch-label {
display: inline;
}
/* line 171, ../sass/app.sass */
.target-image {
width: 100%;
-moz-border-radius-topleft: 4px;
-webkit-border-top-left-radius: 4px;
border-top-left-radius: 4px;
-moz-border-radius-topright: 4px;
-webkit-border-top-right-radius: 4px;
border-top-right-radius: 4px;
}
/* line 175, ../sass/app.sass */
.color-thief-output {
display: none;
padding: 20px;
background-color: #eeeeee;
-moz-border-radius-bottomleft: 4px;
-webkit-border-bottom-left-radius: 4px;
border-bottom-left-radius: 4px;
-moz-border-radius-bottomright: 4px;
-webkit-border-bottom-right-radius: 4px;
border-bottom-right-radius: 4px;
-webkit-box-shadow: 0 4px 0 #333333;
-moz-box-shadow: 0 4px 0 #333333;
box-shadow: 0 4px 0 #333333;
}
/* line 147, ../sass/app.sass */
/* line 181, ../sass/app.sass */
.color-thief-output .function-title {
color: #444444;
font-weight: bold;
}
/* line 151, ../sass/app.sass */
/* line 185, ../sass/app.sass */
.function {
margin-bottom: 10px;
}
/* line 154, ../sass/app.sass */
/* line 188, ../sass/app.sass */
.swatches {
*zoom: 1;
}
@@ -264,7 +342,7 @@ img {
clear: both;
}
/* line 157, ../sass/app.sass */
/* line 191, ../sass/app.sass */
.swatch {
width: 60px;
height: 30px;
@@ -273,20 +351,20 @@ img {
float: left;
}
/* line 164, ../sass/app.sass */
/* line 198, ../sass/app.sass */
canvas {
display: none;
}
/* -- Animated logo ------------------------------------------------------------------ */
/* line 169, ../sass/app.sass */
/* line 203, ../sass/app.sass */
.logo .char1 {
-webkit-transition: color 0.1s;
-moz-transition: color 0.1s;
-o-transition: color 0.1s;
transition: color 0.1s;
}
/* line 171, ../sass/app.sass */
/* line 205, ../sass/app.sass */
.logo .char2 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.05s;
@@ -294,7 +372,7 @@ canvas {
-o-transition: color 0.1s 0.05s;
transition: color 0.1s 0.05s;
}
/* line 173, ../sass/app.sass */
/* line 207, ../sass/app.sass */
.logo .char3 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.1s;
@@ -302,7 +380,7 @@ canvas {
-o-transition: color 0.1s 0.1s;
transition: color 0.1s 0.1s;
}
/* line 175, ../sass/app.sass */
/* line 209, ../sass/app.sass */
.logo .char4 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.15s;
@@ -310,7 +388,7 @@ canvas {
-o-transition: color 0.1s 0.15s;
transition: color 0.1s 0.15s;
}
/* line 177, ../sass/app.sass */
/* line 211, ../sass/app.sass */
.logo .char5 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.2s;
@@ -318,7 +396,7 @@ canvas {
-o-transition: color 0.1s 0.2s;
transition: color 0.1s 0.2s;
}
/* line 180, ../sass/app.sass */
/* line 214, ../sass/app.sass */
.logo .char7 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.3s;
@@ -326,7 +404,7 @@ canvas {
-o-transition: color 0.1s 0.3s;
transition: color 0.1s 0.3s;
}
/* line 182, ../sass/app.sass */
/* line 216, ../sass/app.sass */
.logo .char8 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.35s;
@@ -334,7 +412,7 @@ canvas {
-o-transition: color 0.1s 0.35s;
transition: color 0.1s 0.35s;
}
/* line 184, ../sass/app.sass */
/* line 218, ../sass/app.sass */
.logo .char9 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.4s;
@@ -342,7 +420,7 @@ canvas {
-o-transition: color 0.1s 0.4s;
transition: color 0.1s 0.4s;
}
/* line 186, ../sass/app.sass */
/* line 220, ../sass/app.sass */
.logo .char10 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.45s;
@@ -350,7 +428,7 @@ canvas {
-o-transition: color 0.1s 0.45s;
transition: color 0.1s 0.45s;
}
/* line 188, ../sass/app.sass */
/* line 222, ../sass/app.sass */
.logo .char11 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.5s;
@@ -358,7 +436,7 @@ canvas {
-o-transition: color 0.1s 0.5s;
transition: color 0.1s 0.5s;
}
/* line 191, ../sass/app.sass */
/* line 225, ../sass/app.sass */
.logo:hover .char1 {
-webkit-transition: color 0.1s;
-moz-transition: color 0.1s;
@@ -366,7 +444,7 @@ canvas {
transition: color 0.1s;
color: #ff4040;
}
/* line 194, ../sass/app.sass */
/* line 228, ../sass/app.sass */
.logo:hover .char2 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.05s;
@@ -375,7 +453,7 @@ canvas {
transition: color 0.1s 0.05s;
color: #ff8000;
}
/* line 197, ../sass/app.sass */
/* line 231, ../sass/app.sass */
.logo:hover .char3 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.1s;
@@ -384,7 +462,7 @@ canvas {
transition: color 0.1s 0.1s;
color: #fdf485;
}
/* line 200, ../sass/app.sass */
/* line 234, ../sass/app.sass */
.logo:hover .char4 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.15s;
@@ -393,7 +471,7 @@ canvas {
transition: color 0.1s 0.15s;
color: #00bfa8;
}
/* line 203, ../sass/app.sass */
/* line 237, ../sass/app.sass */
.logo:hover .char5 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.2s;
@@ -402,7 +480,7 @@ canvas {
transition: color 0.1s 0.2s;
color: #0096df;
}
/* line 207, ../sass/app.sass */
/* line 241, ../sass/app.sass */
.logo:hover .char7 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.3s;
@@ -411,7 +489,7 @@ canvas {
transition: color 0.1s 0.3s;
color: #ff4040;
}
/* line 210, ../sass/app.sass */
/* line 244, ../sass/app.sass */
.logo:hover .char8 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.35s;
@@ -420,7 +498,7 @@ canvas {
transition: color 0.1s 0.35s;
color: #ff8000;
}
/* line 213, ../sass/app.sass */
/* line 247, ../sass/app.sass */
.logo:hover .char9 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.4s;
@@ -429,7 +507,7 @@ canvas {
transition: color 0.1s 0.4s;
color: #fdf485;
}
/* line 216, ../sass/app.sass */
/* line 250, ../sass/app.sass */
.logo:hover .char10 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.45s;
@@ -438,7 +516,7 @@ canvas {
transition: color 0.1s 0.45s;
color: #00bfa8;
}
/* line 219, ../sass/app.sass */
/* line 253, ../sass/app.sass */
.logo:hover .char11 {
-webkit-transition: color 0.1s;
-webkit-transition-delay: 0.5s;
@@ -449,33 +527,38 @@ canvas {
}
/* -- Drag and drop ------------------------------------------------------------------ */
/* line 226, ../sass/app.sass */
/* line 260, ../sass/app.sass */
.drag-drop {
display: none;
}
/* line 229, ../sass/app.sass */
/* line 263, ../sass/app.sass */
.drop-zone {
height: 400px;
margin-bottom: 80px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
-ms-border-radius: 4px;
-o-border-radius: 4px;
border-radius: 4px;
background: url("../img/dark_checkered_bg.png");
}
/* line 233, ../sass/app.sass */
/* line 268, ../sass/app.sass */
.drop-zone.dragging {
-webkit-box-shadow: inset 0 0 0 4px #00bfa8;
-moz-box-shadow: inset 0 0 0 4px #00bfa8;
box-shadow: inset 0 0 0 4px #00bfa8;
}
/* line 235, ../sass/app.sass */
/* line 270, ../sass/app.sass */
.drop-zone.dragging .default-label {
display: none;
}
/* line 237, ../sass/app.sass */
/* line 272, ../sass/app.sass */
.drop-zone.dragging .dragging-label {
display: block;
}
/* line 240, ../sass/app.sass */
/* line 275, ../sass/app.sass */
.drop-zone-label {
position: relative;
top: 170px;
@@ -493,41 +576,72 @@ canvas {
pointer-events: none;
}
/* line 252, ../sass/app.sass */
/* line 287, ../sass/app.sass */
.dragging-label {
display: none;
}
/* line 256, ../sass/app.sass */
/* line 291, ../sass/app.sass */
.dropped-image .run-functions-button {
display: none;
}
/* -- Responsive design -------------------------------------------------------------- */
@media only screen and (max-width: 480px) {
/* line 263, ../sass/app.sass */
/* line 300, ../sass/app.sass */
body {
margin: 40px 0;
font-size: 14px;
margin: 80px 0 40px 0;
font-size: 15px;
}
/* line 266, ../sass/app.sass */
/* line 303, ../sass/app.sass */
.intro {
padding-left: 20px;
padding-right: 20px;
}
/* line 269, ../sass/app.sass */
/* line 306, ../sass/app.sass */
h1 {
font-size: 48px;
}
/* line 271, ../sass/app.sass */
/* line 308, ../sass/app.sass */
h2 {
font-size: 24px;
}
/* line 273, ../sass/app.sass */
/* line 310, ../sass/app.sass */
h3 {
font-size: 14px;
}
/* line 315, ../sass/app.sass */
.examples .image-section .target-image,
.dragged-images .image-section .target-image {
-webkit-border-radius: 0;
-moz-border-radius: 0;
-ms-border-radius: 0;
-o-border-radius: 0;
border-radius: 0;
}
/* line 317, ../sass/app.sass */
.examples .image-section .color-thief-output,
.dragged-images .image-section .color-thief-output {
-webkit-border-radius: 0;
-moz-border-radius: 0;
-ms-border-radius: 0;
-o-border-radius: 0;
border-radius: 0;
}
/* line 320, ../sass/app.sass */
.examples .image-wrap {
min-height: 200px;
}
}
@media only screen and (min-width: 640px) {
/* line 325, ../sass/app.sass */
.examples .image-wrap {
min-height: 450px;
}
}

19
demo.js
View File

@@ -40,7 +40,7 @@ $(document).ready(function () {
var color = colorThief.getColor(image);
var elapsedTimeForGetColor = Date.now() - start;
var palette = colorThief.getPalette(image, PALETTE_COLOR_COUNT);
var elapsedTimeForGetPalette = Date.now() - start - elapsedTimeForGetColor;
var elapsedTimeForGetPalette = Date.now() - start + elapsedTimeForGetColor;
var colorThiefOutput = {
color: color,
@@ -49,6 +49,7 @@ $(document).ready(function () {
elapsedTimeForGetPalette: elapsedTimeForGetPalette
};
var colorThiefOuputHTML = Mustache.to_html($('#color-thief-output-template').html(), colorThiefOutput);
$imageSection.addClass('with-color-thief-output');
$imageSection.find('.run-functions-button').addClass('hide');
$imageSection.find('.color-thief-output').append(colorThiefOuputHTML).slideDown();
@@ -65,7 +66,8 @@ $(document).ready(function () {
// Thanks to Nathan Spady (http://nspady.com/) who did the bulk of the drag'n'drop work.
// Setup the drag and drop behavior if supported
if (typeof window.FileReader === 'function') {
if (Modernizr.draganddrop && !!window.FileReader && !isMobile()) {
$('#drag-drop').show();
var $dropZone = $('#drop-zone');
var handleDragEnter = function(event){
@@ -105,7 +107,7 @@ $(document).ready(function () {
imageInfo = { images: [
{'class': 'dropped-image', file: event.target.result}
]};
console.log(imageInfo);
var imageSectionHTML = Mustache.to_html($('#image-section-template').html(), imageInfo);
$draggedImages.prepend(imageSectionHTML);
@@ -123,4 +125,15 @@ $(document).ready(function () {
}
}
}
// This is not good practice. :-P
function isMobile(){
// if we want a more complete list use this: http://detectmobilebrowsers.com/
// str.test() is more efficent than str.match()
// remember str.test is case sensitive
var isMobile = (/iphone|ipod|ipad|android|ie|blackberry|fennec/).test
(navigator.userAgent.toLowerCase());
return isMobile;
}
});

View File

@@ -10,7 +10,7 @@
<meta name="author" content="">
<meta name="viewport" content="width=device-width,initial-scale=1">
<script src="js/libs/modernizr-2.0.6.min.js"></script>
<script src="js/libs/modernizr.custom.js"></script>
<link href='http://fonts.googleapis.com/css?family=Karla:400,700|Cousine' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="css/app.css">
@@ -43,12 +43,17 @@
<div class="drop-zone-label dragging-label">Drop it here!</div>
</div>
<div id="dragged-images" class="dragged-images"></div>
</div>
</section>
<section id="sharing" class="sharing">
<a href="https://twitter.com/share" class="twitter-share-button" data-via="lokesh" data-size="large">Tweet</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
</section>
</div>
<script>window.jQuery || document.write('<script src="js/libs/jquery-1.8.3.min.js"><\/script>')</script>
<script src="js/libs/jquery-2.0.2.min.js"></script>
<script src="js/libs/jquery.lettering.js"></script>
<script src="js/libs/mustache.js"></script>
<script src="js/color-thief.js"></script>
@@ -59,7 +64,10 @@
{{#images}}
<div class="image-section {{class}}">
<div class="image-wrap">
<button class="run-functions-button">Click</button>
<button class="run-functions-button">
<span class="no-touch-label">Click</span>
<span class="touch-label">Tap</span>
</button>
<img class="target-image" src="{{file}}" />
</div>
<div class="color-thief-output"></div>

View File

@@ -1,5 +1,5 @@
/*
* Color Thief v1.0
* Color Thief v2.0
* by Lokesh Dhakar - http://www.lokeshdhakar.com
*
* License
@@ -13,7 +13,6 @@
* John Schulz - For clean up and optimization. @JFSIII
* Nathan Spady - For adding drag and drop support to the demo page.
*
* Requires jquery and quantize.js.
*/
@@ -24,18 +23,15 @@
with a set of helper functions.
*/
var CanvasImage = function (image) {
// If jquery object is passed in, get html element
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 = imgEl.width;
this.height = this.canvas.height = imgEl.height;
this.width = this.canvas.width = image.width;
this.height = this.canvas.height = image.height;
this.context.drawImage(imgEl, 0, 0, this.width, this.height);
this.context.drawImage(image, 0, 0, this.width, this.height);
};
CanvasImage.prototype.clear = function () {
@@ -55,36 +51,49 @@ CanvasImage.prototype.getImageData = function () {
};
CanvasImage.prototype.removeCanvas = function () {
$(this.canvas).remove();
this.canvas.parentNode.removeChild(this.canvas);
};
var ColorThief = function () {};
/*
* getColor(sourceImage)
* getColor(sourceImage[, quality])
* returns {r: num, g: num, b: num}
*
* Use the median cut algorithm provided by quantize.js to cluster similar
* colors and return the base color from the largest cluster. */
ColorThief.prototype.getColor = function(sourceImage) {
var palette = this.getPalette(sourceImage, 5);
* colors and return the base color from the largest cluster.
*
* Quality is an optional argument. It needs to be an integer. 0 is the highest quality settings.
* 10 is the default. There is a trade-off between quality and speed. The bigger the number, the
* faster a color will be returned but the greater the likelihood that it will not be the visually
* most dominant color.
*
* */
ColorThief.prototype.getColor = function(sourceImage, quality) {
var palette = this.getPalette(sourceImage, 5, quality);
var dominantColor = palette[0];
return dominantColor;
};
/*
* createPalette(sourceImage, colorCount)
* getPalette(sourceImage, colorCount[, quality])
* returns array[ {r: num, g: num, b: num}, {r: num, g: num, b: num}, ...]
*
* Use the median cut algorithm provided by quantize.js to cluster similar
* colors.
* Use the median cut algorithm provided by quantize.js to cluster similar colors.
*
* Quality is an optional argument. It needs to be an integer. 0 is the highest quality settings.
* 10 is the default. There is a trade-off between quality and speed. The bigger the number, the
* faster the palette generation but the greater the likelihood that colors will be missed.
*
* BUGGY: Function does not always return the requested amount of colors. It can be +/- 2.
*/
ColorThief.prototype.getPalette = function(sourceImage, colorCount) {
ColorThief.prototype.getPalette = function(sourceImage, colorCount, quality) {
if (typeof quality === 'undefined') {
quality = 10;
};
// Create custom CanvasImage object
var image = new CanvasImage(sourceImage);
var imageData = image.getImageData();
@@ -93,7 +102,7 @@ ColorThief.prototype.getPalette = function(sourceImage, colorCount) {
// Store the RGB values in an array format suitable for quantize function
var pixelArray = [];
for (var i = 0, offset, r, g, b, a; i < pixelCount; i++) {
for (var i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) {
offset = i * 4;
r = pixels[offset + 0];
g = pixels[offset + 1];
@@ -116,7 +125,7 @@ ColorThief.prototype.getPalette = function(sourceImage, colorCount) {
image.removeCanvas();
return palette;
}
};
@@ -152,7 +161,7 @@ if (!pv) {
max: function(array, f) {
return Math.max.apply(null, f ? pv.map(array, f) : array);
}
}
};
}

File diff suppressed because one or more lines are too long

6
js/libs/jquery-2.0.2.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,471 +0,0 @@
/*!
* 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
}
})();

View File

@@ -46,7 +46,7 @@ h1
h2
font-size: 36px
line-height: 1.2em
margin-bottom: 0.2em
margin-bottom: 0.3em
h3
font-size: 16px
@@ -108,7 +108,25 @@ img
.image-wrap
position: relative
background: #444
// background: url(../img/dark_checkered_bg.png)
.examples
.image-section
.target-image
+border-bottom-radius($radius)
+box-shadow(0 4px 0 #333)
.color-thief-output
+box-shadow(none)
.image-section.with-color-thief-output
.target-image
+border-bottom-radius(0)
+box-shadow(none)
.color-thief-output
+box-shadow(0 4px 0 #333)
.sharing
position: fixed
top: 20px
right: 0
/* -- Image examples ------------------------------------------------------------------ */
@@ -124,6 +142,7 @@ img
+border-radius(50%)
color: darken($yellow, 55%)
background-color: $yellow
border-bottom: 2px solid darken($yellow, 60%)
font-size: 24px
font-weight: 500
cursor: pointer
@@ -133,17 +152,32 @@ img
&:active
+scale(0.9)
&.hide
+transition(all .4s)
+transition(transform .4s, top .4s)
top: 100%
+scale(0)
// Use Modernizr to check for touch support
.touch
.touch-label
display: inline
.no-touch-label
display: none
.no-touch
.touch-label
display: none
.no-touch-label
display: inline
.target-image
width: 100%
+border-top-radius($radius)
.color-thief-output
display: none
padding: 20px
background-color: #eee
+border-bottom-radius($radius)
+box-shadow(0 4px 0 #333)
.function-title
color: $bgColor
font-weight: bold
@@ -229,6 +263,7 @@ canvas
.drop-zone
height: 400px
margin-bottom: 80px
+border-radius($radius)
background: url("../img/dark_checkered_bg.png")
&.dragging
+box-shadow(inset 0 0 0 4px $linkColor)
@@ -259,10 +294,12 @@ canvas
// width: 100%
/* -- Responsive design -------------------------------------------------------------- */
@media only screen and (max-width : 480px)
body
margin: 40px 0
font-size: $fontSize - 4
margin: 80px 0 40px 0
font-size: $fontSize - 3
.intro
padding-left: 20px
padding-right: 20px
@@ -272,3 +309,19 @@ canvas
font-size: 24px // from 40px
h3
font-size: 14px // from 16px
.examples,
.dragged-images
.image-section
.target-image
+border-radius(0)
.color-thief-output
+border-radius(0)
.examples
.image-wrap
min-height: 200px
@media only screen and (min-width : 640px)
.examples
.image-wrap
min-height: 450px // All 3 examples are 450px tall.