From 4272252db9e9365a6eb64101c2e3e1b5924d53eb Mon Sep 17 00:00:00 2001 From: Lokesh Dhakar Date: Sun, 23 Jun 2013 18:26:36 -0700 Subject: [PATCH] Drag'n'drop working again. Appending quantize.js code into color-thief.js --- css/app.css | 337 ++++++++++++++++++-------------- demo.js | 236 +++++++++++------------ index.html | 70 ++++--- js/color-thief.js | 478 +++++++++++++++++++++++++++++++++++++++++++++- sass/app.sass | 118 ++++++++---- 5 files changed, 896 insertions(+), 343 deletions(-) diff --git a/css/app.css b/css/app.css index dff483a..477f944 100644 --- a/css/app.css +++ b/css/app.css @@ -63,96 +63,127 @@ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, display: block; } -/* line 29, ../sass/app.sass */ +/* line 30, ../sass/app.sass */ body, input, textarea { - margin: 40px; + margin: 40px 40px 80px 40px; color: #aaaaaa; background: #444444; font-size: 18px; font-family: "Karla", "lucida grande", sans-serif; } -/* line 36, ../sass/app.sass */ +/* line 37, ../sass/app.sass */ h1, h2, h3, h4, h5, h6 { color: white; line-height: 1.2em; font-weight: 500; - margin-bottom: 0.5em; } /* line 42, ../sass/app.sass */ -h2 { - font-size: 36px; - line-height: 1.2em; +h1 { + font-size: 72px; + line-height: 1em; } /* line 46, ../sass/app.sass */ -h3 { - font-size: 16px; - letter-spacing: 0.1em; - text-transform: uppercase; +h2 { + font-size: 36px; + line-height: 1.2em; + margin-bottom: 0.2em; } /* line 51, ../sass/app.sass */ +h3 { + font-size: 16px; + letter-spacing: 0.1em; + margin-bottom: 0.2em; + text-transform: uppercase; +} + +/* line 57, ../sass/app.sass */ h4 { font-size: 20px; - margin-bottom: 1.25em; + margin-bottom: 1em; } -/* line 55, ../sass/app.sass */ +/* line 61, ../sass/app.sass */ p { - line-height: 1.5em; - margin-bottom: 1.25em; + line-height: 1.4em; + margin-bottom: 1em; } -/* line 59, ../sass/app.sass */ +/* line 65, ../sass/app.sass */ strong { font-weight: bold; } +/* line 68, ../sass/app.sass */ +code, +kbd { + font: 12px "Andale Mono", "DejaVu Sans Mono", monospace; + color: #848484; +} + /* Links */ -/* line 63, ../sass/app.sass */ +/* line 74, ../sass/app.sass */ a { color: #00bfa8; text-decoration: none; } -/* line 66, ../sass/app.sass */ +/* line 77, ../sass/app.sass */ a:hover { color: #59ffeb; } +/* Images */ +/* line 81, ../sass/app.sass */ +img { + display: block; +} + /* -- Layout ------------------------------------------------------------------ */ -/* line 71, ../sass/app.sass */ +/* line 86, ../sass/app.sass */ .wrapper { - text-align: center; max-width: 600px; margin: 0 auto; } -/* line 76, ../sass/app.sass */ +/* line 90, ../sass/app.sass */ +.section-header { + text-align: center; +} + +/* line 93, ../sass/app.sass */ +.intro { + text-align: center; + margin-bottom: 2em; +} + +/* line 97, ../sass/app.sass */ .description { max-width: 450px; margin-right: auto; margin-left: auto; } -/* line 81, ../sass/app.sass */ +/* line 102, ../sass/app.sass */ +.read-more-links { + font-size: 16px; +} + +/* line 105, ../sass/app.sass */ .image-section { margin-bottom: 80px; background: #2b2b2b; } -/* line 84, ../sass/app.sass */ +/* line 108, ../sass/app.sass */ .image-section .image-wrap { position: relative; background: #444444; } -/* line 89, ../sass/app.sass */ -.target-image { - width: 100%; -} - -/* line 92, ../sass/app.sass */ +/* -- Image examples ------------------------------------------------------------------ */ +/* line 115, ../sass/app.sass */ .run-functions-button { position: absolute; top: 50%; @@ -167,17 +198,18 @@ a:hover { -ms-border-radius: 50%; -o-border-radius: 50%; border-radius: 50%; - color: #817702; + color: #686002; background-color: #fdf485; font-size: 24px; + font-weight: 500; cursor: pointer; } -/* line 106, ../sass/app.sass */ +/* line 130, ../sass/app.sass */ .run-functions-button:hover { background-color: #fef9b7; - color: #9a8f03; + color: #4f4901; } -/* line 109, ../sass/app.sass */ +/* line 133, ../sass/app.sass */ .run-functions-button:active { -webkit-transform: scale(0.9, 0.9); -moz-transform: scale(0.9, 0.9); @@ -185,171 +217,157 @@ a:hover { -o-transform: scale(0.9, 0.9); transform: scale(0.9, 0.9); } - -/* line 112, ../sass/app.sass */ -.color-thief-output { - margin-top: 20px; +/* line 135, ../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; + top: 100%; + -webkit-transform: scale(0, 0); + -moz-transform: scale(0, 0); + -ms-transform: scale(0, 0); + -o-transform: scale(0, 0); + transform: scale(0, 0); } -/* line 115, ../sass/app.sass */ +/* line 140, ../sass/app.sass */ +.target-image { + width: 100%; +} + +/* line 143, ../sass/app.sass */ +.color-thief-output { + display: none; + padding: 20px; + background-color: #eeeeee; +} +/* line 147, ../sass/app.sass */ +.color-thief-output .function-title { + color: #444444; + font-weight: bold; +} + +/* line 151, ../sass/app.sass */ .function { - clear: left; margin-bottom: 10px; +} + +/* line 154, ../sass/app.sass */ +.swatches { *zoom: 1; } /* line 38, ../../../../.rvm/gems/ruby-1.9.2-p320/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/utilities/general/_clearfix.scss */ -.function:after { +.swatches:after { content: ""; display: table; clear: both; } -/* line 120, ../sass/app.sass */ +/* line 157, ../sass/app.sass */ .swatch { width: 60px; height: 30px; - margin-right: 2px; + margin: 0 2px 2px 0; background: #dddddd; float: left; - -webkit-transition: scale 0.5s; - -moz-transition: scale 0.5s; - -o-transition: scale 0.5s; - transition: scale 0.5s; -} -/* line 127, ../sass/app.sass */ -.swatch:hover { - -webkit-transition: none; - -moz-transition: none; - -o-transition: none; - transition: none; - -webkit-transform: scale(1.1, 1.1); - -moz-transform: scale(1.1, 1.1); - -ms-transform: scale(1.1, 1.1); - -o-transform: scale(1.1, 1.1); - transform: scale(1.1, 1.1); - -webkit-box-shadow: 0 1px 10px black; - -moz-box-shadow: 0 1px 10px black; - box-shadow: 0 1px 10px black; } -/* Hardcoding the offset for the FB icon example */ -/* line 133, ../sass/app.sass */ -.fbIcon .imageWrap .targetImage { - -webkit-border-radius: 0; - -moz-border-radius: 0; - -ms-border-radius: 0; - -o-border-radius: 0; - border-radius: 0; - position: relative; - top: 142px; - left: 192px; -} - -/* line 139, ../sass/app.sass */ +/* line 164, ../sass/app.sass */ canvas { display: none; } /* -- Animated logo ------------------------------------------------------------------ */ -/* line 143, ../sass/app.sass */ -h1 { - font-size: 72px; - line-height: 0.5em; - margin-bottom: 0.3em; -} -/* line 147, ../sass/app.sass */ -h1 small { - font-size: 20px; -} -/* line 150, ../sass/app.sass */ -h1 .char1 { +/* line 169, ../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 152, ../sass/app.sass */ -h1 .char2 { +/* line 171, ../sass/app.sass */ +.logo .char2 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.05s; -moz-transition: color 0.1s 0.05s; -o-transition: color 0.1s 0.05s; transition: color 0.1s 0.05s; } -/* line 154, ../sass/app.sass */ -h1 .char3 { +/* line 173, ../sass/app.sass */ +.logo .char3 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.1s; -moz-transition: color 0.1s 0.1s; -o-transition: color 0.1s 0.1s; transition: color 0.1s 0.1s; } -/* line 156, ../sass/app.sass */ -h1 .char4 { +/* line 175, ../sass/app.sass */ +.logo .char4 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.15s; -moz-transition: color 0.1s 0.15s; -o-transition: color 0.1s 0.15s; transition: color 0.1s 0.15s; } -/* line 158, ../sass/app.sass */ -h1 .char5 { +/* line 177, ../sass/app.sass */ +.logo .char5 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.2s; -moz-transition: color 0.1s 0.2s; -o-transition: color 0.1s 0.2s; transition: color 0.1s 0.2s; } -/* line 161, ../sass/app.sass */ -h1 .char7 { +/* line 180, ../sass/app.sass */ +.logo .char7 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.3s; -moz-transition: color 0.1s 0.3s; -o-transition: color 0.1s 0.3s; transition: color 0.1s 0.3s; } -/* line 163, ../sass/app.sass */ -h1 .char8 { +/* line 182, ../sass/app.sass */ +.logo .char8 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.35s; -moz-transition: color 0.1s 0.35s; -o-transition: color 0.1s 0.35s; transition: color 0.1s 0.35s; } -/* line 165, ../sass/app.sass */ -h1 .char9 { +/* line 184, ../sass/app.sass */ +.logo .char9 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.4s; -moz-transition: color 0.1s 0.4s; -o-transition: color 0.1s 0.4s; transition: color 0.1s 0.4s; } -/* line 167, ../sass/app.sass */ -h1 .char10 { +/* line 186, ../sass/app.sass */ +.logo .char10 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.45s; -moz-transition: color 0.1s 0.45s; -o-transition: color 0.1s 0.45s; transition: color 0.1s 0.45s; } -/* line 169, ../sass/app.sass */ -h1 .char11 { +/* line 188, ../sass/app.sass */ +.logo .char11 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.5s; -moz-transition: color 0.1s 0.5s; -o-transition: color 0.1s 0.5s; transition: color 0.1s 0.5s; } -/* line 172, ../sass/app.sass */ -h1:hover .char1 { +/* line 191, ../sass/app.sass */ +.logo:hover .char1 { -webkit-transition: color 0.1s; -moz-transition: color 0.1s; -o-transition: color 0.1s; transition: color 0.1s; color: #ff4040; } -/* line 175, ../sass/app.sass */ -h1:hover .char2 { +/* line 194, ../sass/app.sass */ +.logo:hover .char2 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.05s; -moz-transition: color 0.1s 0.05s; @@ -357,8 +375,8 @@ h1:hover .char2 { transition: color 0.1s 0.05s; color: #ff8000; } -/* line 178, ../sass/app.sass */ -h1:hover .char3 { +/* line 197, ../sass/app.sass */ +.logo:hover .char3 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.1s; -moz-transition: color 0.1s 0.1s; @@ -366,8 +384,8 @@ h1:hover .char3 { transition: color 0.1s 0.1s; color: #fdf485; } -/* line 181, ../sass/app.sass */ -h1:hover .char4 { +/* line 200, ../sass/app.sass */ +.logo:hover .char4 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.15s; -moz-transition: color 0.1s 0.15s; @@ -375,8 +393,8 @@ h1:hover .char4 { transition: color 0.1s 0.15s; color: #00bfa8; } -/* line 184, ../sass/app.sass */ -h1:hover .char5 { +/* line 203, ../sass/app.sass */ +.logo:hover .char5 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.2s; -moz-transition: color 0.1s 0.2s; @@ -384,8 +402,8 @@ h1:hover .char5 { transition: color 0.1s 0.2s; color: #0096df; } -/* line 188, ../sass/app.sass */ -h1:hover .char7 { +/* line 207, ../sass/app.sass */ +.logo:hover .char7 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.3s; -moz-transition: color 0.1s 0.3s; @@ -393,8 +411,8 @@ h1:hover .char7 { transition: color 0.1s 0.3s; color: #ff4040; } -/* line 191, ../sass/app.sass */ -h1:hover .char8 { +/* line 210, ../sass/app.sass */ +.logo:hover .char8 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.35s; -moz-transition: color 0.1s 0.35s; @@ -402,8 +420,8 @@ h1:hover .char8 { transition: color 0.1s 0.35s; color: #ff8000; } -/* line 194, ../sass/app.sass */ -h1:hover .char9 { +/* line 213, ../sass/app.sass */ +.logo:hover .char9 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.4s; -moz-transition: color 0.1s 0.4s; @@ -411,8 +429,8 @@ h1:hover .char9 { transition: color 0.1s 0.4s; color: #fdf485; } -/* line 197, ../sass/app.sass */ -h1:hover .char10 { +/* line 216, ../sass/app.sass */ +.logo:hover .char10 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.45s; -moz-transition: color 0.1s 0.45s; @@ -420,8 +438,8 @@ h1:hover .char10 { transition: color 0.1s 0.45s; color: #00bfa8; } -/* line 200, ../sass/app.sass */ -h1:hover .char11 { +/* line 219, ../sass/app.sass */ +.logo:hover .char11 { -webkit-transition: color 0.1s; -webkit-transition-delay: 0.5s; -moz-transition: color 0.1s 0.5s; @@ -431,53 +449,84 @@ h1:hover .char11 { } /* -- Drag and drop ------------------------------------------------------------------ */ -/* line 207, ../sass/app.sass */ -.drag-and-drop { +/* line 226, ../sass/app.sass */ +.drag-drop { display: none; } -/* line 210, ../sass/app.sass */ -.dropZone { - height: 200px; - margin-bottom: 60px; - background: url("../img/drag_placeholder.png"); +/* line 229, ../sass/app.sass */ +.drop-zone { + height: 400px; + margin-bottom: 80px; + background: url("../img/dark_checkered_bg.png"); } -/* line 214, ../sass/app.sass */ -.dropZone.dragging { - -webkit-box-shadow: inset 0 1px 10px #09a1ec; - -moz-box-shadow: inset 0 1px 10px #09a1ec; - box-shadow: inset 0 1px 10px #09a1ec; +/* line 233, ../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 */ +.drop-zone.dragging .default-label { + display: none; +} +/* line 237, ../sass/app.sass */ +.drop-zone.dragging .dragging-label { + display: block; } -/* line 216, ../sass/app.sass */ -.droppedImage { +/* line 240, ../sass/app.sass */ +.drop-zone-label { + position: relative; + top: 170px; + padding: 10px; + margin: 0 100px; + border: 4px solid #fdf485; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + -ms-border-radius: 4px; + -o-border-radius: 4px; + border-radius: 4px; + color: #fdf485; + font-size: 24px; + text-align: center; + pointer-events: none; +} + +/* line 252, ../sass/app.sass */ +.dragging-label { + display: none; +} + +/* line 256, ../sass/app.sass */ +.dropped-image .run-functions-button { display: none; } @media only screen and (max-width: 480px) { - /* line 223, ../sass/app.sass */ + /* line 263, ../sass/app.sass */ body { margin: 40px 0; font-size: 14px; } - /* line 226, ../sass/app.sass */ + /* line 266, ../sass/app.sass */ .intro { padding-left: 20px; padding-right: 20px; } - /* line 229, ../sass/app.sass */ + /* line 269, ../sass/app.sass */ h1 { font-size: 48px; } - /* line 231, ../sass/app.sass */ + /* line 271, ../sass/app.sass */ h2 { font-size: 24px; } - /* line 233, ../sass/app.sass */ + /* line 273, ../sass/app.sass */ h3 { font-size: 14px; } diff --git a/demo.js b/demo.js index feea73f..17465bc 100644 --- a/demo.js +++ b/demo.js @@ -1,148 +1,126 @@ $(document).ready(function () { - // Uses mustache.js templating to create layout - var imageArray = {images: [ - {"file": "img/photo1.jpg"}, - {"file": "img/photo2.jpg"}, - {"file": "img/photo3.jpg"} - ]}; + // Use lettering.js to generate spans for each letter in the logo. + // This is used to create the on hover animated rainbow effect. + $('.logo').lettering(); - var displayColors = function(image) { - var $image = $(image); - var imageSection = $image.closest('.image-section'); - var appendColors = function(colors, root) { - $.each(colors, function(index, value) { - var swatchEl = $('
', {'class': 'swatch'}) - .css('background-color', 'rgba('+ value +', 1)'); - root.append(swatchEl); - }); - }; - var colorThief = new ColorThief(); + // --------------------- + // Color Thief demo code + // --------------------- + var imageArray = {images: [ + {'file': 'img/photo1.jpg'}, + {'file': 'img/photo2.jpg'}, + {'file': 'img/photo3.jpg'} + ]}; - // Dominant Color - var dominantColor = colorThief.getColor(image); - var dominantSwatch = imageSection.find('.get-color .swatches'); - appendColors([dominantColor], dominantSwatch); + // Render example images + var examplesHTML = Mustache.to_html($('#image-section-template').html(), imageArray); + $('#examples').append(examplesHTML); - // Palette - var colorCount = $image.attr('data-colorcount') ? $image.data('colorcount') : 10; - var medianPalette = colorThief.getPalette(image, colorCount); - var medianCutPalette = imageSection.find('.get-palette .swatches'); - appendColors(medianPalette, medianCutPalette); + // Event handlers + $('.run-functions-button').on('click', function(event) { + var $this = $(this); + $this.text('...'); + var $imageSection = $this.closest('.image-section'); + var $colorThiefOutput = $imageSection.find('.color-thief-output'); + var $targetimage = $imageSection.find('.target-image'); + showColorsForImage($targetimage, $imageSection); + }); + + var colorThief = new ColorThief(); + + var PALETTE_COLOR_COUNT = 10; + + // Run Color Thief functions and display results below image. + // We also log execution time of functions for display. + var showColorsForImage = function($image, $imageSection ) { + var image = $image[0]; + var start = Date.now(); + 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 colorThiefOutput = { + color: color, + palette: palette, + elapsedTimeForGetColor: elapsedTimeForGetColor, + elapsedTimeForGetPalette: elapsedTimeForGetPalette }; + var colorThiefOuputHTML = Mustache.to_html($('#color-thief-output-template').html(), colorThiefOutput); + $imageSection.find('.run-functions-button').addClass('hide'); + $imageSection.find('.color-thief-output').append(colorThiefOuputHTML).slideDown(); - - // Setup the drag and drop behavior if supported - if (typeof window.FileReader === 'function') { - $('#dragDrop').show(); - - var $dropZone = $('#dropZone'); - - var handleDragEnter = function(event){ - $dropZone.addClass('dragging'); - return false; - }; - - var handleDragLeave = function(event){ - $dropZone.removeClass('dragging'); - return false; - }; - - var handleDragOver = function(event){ - return false; - }; - - var handleDrop = function(event){ - $dropZone.removeClass('dragging'); - - var dt = event.originalEvent.dataTransfer; - var files = dt.files; - - handleFiles(files); - - return false; - }; - - $dropZone - .on('dragenter', handleDragEnter) - .on('dragleave', handleDragLeave) - .on('dragover', handleDragOver) - .on('drop', handleDrop); + // If the color-thief-output div is not in the viewport or cut off, scroll down. + var windowHeight = $(window).height(); + var currentScrollPosition = $('body').scrollTop() + var outputOffsetTop = $imageSection.find('.color-thief-output').offset().top + if ((currentScrollPosition < outputOffsetTop) && (currentScrollPosition + windowHeight - 250 < outputOffsetTop)) { + $('body').animate({scrollTop: outputOffsetTop - windowHeight + 200 + "px"}); } + }; - function handleFiles( files ) { - var imageType = /image.*/; - var fileCount = files.length; + // Drag'n'drop demo + // Thanks to Nathan Spady (http://nspady.com/) who did the bulk of the drag'n'drop work. - for (var i = 0; i < fileCount; i++) { - var file = files[i]; + // Setup the drag and drop behavior if supported + if (typeof window.FileReader === 'function') { + $('#drag-drop').show(); + var $dropZone = $('#drop-zone'); + var handleDragEnter = function(event){ + $dropZone.addClass('dragging'); + return false; + }; + var handleDragLeave = function(event){ + $dropZone.removeClass('dragging'); + return false; + }; + var handleDragOver = function(event){ + return false; + }; + var handleDrop = function(event){ + $dropZone.removeClass('dragging'); + handleFiles(event.originalEvent.dataTransfer.files); + return false; + }; + $dropZone + .on('dragenter', handleDragEnter) + .on('dragleave', handleDragLeave) + .on('dragover', handleDragOver) + .on('drop', handleDrop); + } - if ( file.type.match(imageType) ) { - var reader = new FileReader(); - reader.onload = function( e ) { - imageInfo = { images: [ - {'class': 'droppedImage', file: e.target.result} - ]}; + function handleFiles(files) { + var $draggedImages = $('#dragged-images'); + var imageType = /image.*/; + var fileCount = files.length; - var html = Mustache.to_html($('#template').html(), imageInfo); - $('#draggedImages').prepend( html ); + for (var i = 0; i < fileCount; i++) { + var file = files[i]; - var img = $('.droppedImage .targetImage').get(0); + if (file.type.match(imageType)) { + var reader = new FileReader(); + reader.onload = function(event) { + 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); - // Must wait for image to load in DOM, not just load from FileReader - $(img).on('load', function() { - displayColors( img ); - util.centerImg( img, 400, 300); + var $imageSection = $draggedImages.find('.image-section').first(); + var $image = $('.dropped-image .target-image'); - $('.droppedImage').slideDown(); - }); - }; - reader.readAsDataURL(file); - } else { - alert('File must be a supported image type.'); - } + // Must wait for image to load in DOM, not just load from FileReader + $image.on('load', function() { + showColorsForImage($image, $imageSection); + }); + }; + reader.readAsDataURL(file); + } else { + alert('File must be a supported image type.'); } } - - // Use lettering.js to give letter by letter styling control for the h1 title - $("h1").lettering(); - - - var html = Mustache.to_html($('#image-section-template').html(), imageArray); - $('#examples').append(html); - - - // For each image: - // Once image is loaded, get dominant color and palette and display them. - // $('img').bind('load', function (event) { - // var image = event.target; - // displayColors(image); - // }); + } }); - -var util = { - centerImg: function( img, containerWidth, containerHeight) { - var $img = $(img); - var imgWidth = $img.get(0).width; - var imgHeight = $img.get(0).height; - var imgAspectRatio = imgHeight/imgWidth; - - if ( imgHeight > containerHeight ) { - imgWidth = containerHeight / imgAspectRatio; - $img.css('width', ( imgWidth + "px" )); - } - if ( imgWidth > containerWidth ) { - $img.css('width', ( containerWidth + "px" )); - imgHeight = imgAspectRatio * containerWidth; - } - if ( imgWidth < containerWidth ) { - var hOffset = ( containerWidth - imgWidth )/2; - $img.css('margin-left', ( hOffset + "px" )); - } - if ( imgHeight < containerHeight ) { - var vOffset = ( containerHeight - imgHeight )/2; - $img.css('margin-top', ( vOffset + "px" )); - } - } -}; diff --git a/index.html b/index.html index cd46e0f..648353b 100755 --- a/index.html +++ b/index.html @@ -20,71 +20,81 @@
-

Color Thief

+
+

Color Thief

+

by Lokesh Dhakar

-

A script for grabbing the color palette from an image. Uses Javascript and the canvas tag to make it happen.

-

Read more about it on my blog or get the code on Github -

+
-
-

Examples

+
+
+

Examples

+
- -

Try it yourself

-
+
+
+

Try it yourself

+
+
+
Drag an image here
+
Drop it here!
-
+
-
+
+ - - - + + - + + diff --git a/js/color-thief.js b/js/color-thief.js index f628cf4..15b4ecd 100644 --- a/js/color-thief.js +++ b/js/color-thief.js @@ -116,4 +116,480 @@ ColorThief.prototype.getPalette = function(sourceImage, colorCount) { image.removeCanvas(); return palette; -} \ No newline at end of file +} + + + + +/*! + * 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 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); + + // 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 + } +})(); \ No newline at end of file diff --git a/sass/app.sass b/sass/app.sass index 0a8d722..6f315a7 100644 --- a/sass/app.sass +++ b/sass/app.sass @@ -17,6 +17,7 @@ $linkColor: $green $fontSize: 18px $columnWidth: 600px +$radius: 4px // $codeColor: #eff // $inputColor: #555 @@ -27,7 +28,7 @@ $columnWidth: 600px body, input, textarea - margin: 40px + margin: 40px 40px 80px 40px color: $color background: $bgColor font-size: $fontSize @@ -37,28 +38,38 @@ h1, h2, h3, h4, h5, h6 color: white line-height: 1.2em font-weight: 500 - margin-bottom: 0.5em + +h1 + font-size: 72px + line-height: 1em h2 font-size: 36px line-height: 1.2em + margin-bottom: 0.2em h3 font-size: 16px letter-spacing: 0.1em + margin-bottom: 0.2em text-transform: uppercase h4 font-size: 20px - margin-bottom: 1.25em + margin-bottom: 1em p - line-height: 1.5em - margin-bottom: 1.25em + line-height: 1.4em + margin-bottom: 1em strong font-weight: bold +code, +kbd + font: $fontSize - 6 "Andale Mono", "DejaVu Sans Mono", monospace + color: darken($color, 15%) + /* Links */ a color: $linkColor @@ -66,18 +77,31 @@ a &:hover color: lighten($linkColor, 30%) +/* Images */ +img + display: block + /* -- Layout ------------------------------------------------------------------ */ .wrapper - text-align: center max-width: $columnWidth margin: 0 auto +.section-header + text-align: center + +.intro + text-align: center + margin-bottom: 2em + .description max-width: 450px margin-right: auto margin-left: auto +.read-more-links + font-size: $fontSize - 2 + .image-section margin-bottom: 80px background: darken($bgColor, 10%) @@ -86,8 +110,7 @@ a background: #444 // background: url(../img/dark_checkered_bg.png) -.target-image - width: 100% +/* -- Image examples ------------------------------------------------------------------ */ .run-functions-button position: absolute @@ -99,54 +122,50 @@ a margin-left: -50px border: none +border-radius(50%) - color: darken($yellow, 50%) + color: darken($yellow, 55%) background-color: $yellow font-size: 24px + font-weight: 500 cursor: pointer &:hover background-color: lighten($yellow, 10%) - color: darken($yellow, 45%) + color: darken($yellow, 60%) &:active +scale(0.9) + &.hide + +transition(all .4s) + top: 100% + +scale(0) + +.target-image + width: 100% .color-thief-output - margin-top: 20px + display: none + padding: 20px + background-color: #eee + .function-title + color: $bgColor + font-weight: bold .function - clear: left margin-bottom: 10px + +.swatches +pie-clearfix .swatch width: 60px height: 30px - margin-right: 2px + margin: 0 2px 2px 0 background: #dddddd float: left - +transition(scale .5s) - &:hover - +transition(none) - +scale(1.1, 1.1) - +box-shadow( 0 1px 10px black) - -/* Hardcoding the offset for the FB icon example */ -.fbIcon .imageWrap .targetImage - +border-radius(0) - position: relative - top: 142px - left: 192px canvas display: none /* -- Animated logo ------------------------------------------------------------------ */ -h1 - font-size: 72px - line-height: 0.5em - margin-bottom: 0.3em - small - font-size: 20px - +.logo .char1 +transition( color .1s) .char2 @@ -204,17 +223,38 @@ h1 /* -- Drag and drop ------------------------------------------------------------------ */ -.drag-and-drop +.drag-drop display: none -.dropZone - height: 200px - margin-bottom: 60px - background: url("../img/drag_placeholder.png") +.drop-zone + height: 400px + margin-bottom: 80px + background: url("../img/dark_checkered_bg.png") &.dragging - +box-shadow(inset 0 1px 10px #09A1EC) -.droppedImage + +box-shadow(inset 0 0 0 4px $linkColor) + .default-label + display: none + .dragging-label + display: block + +.drop-zone-label + position: relative + top: 170px + padding: 10px + margin: 0 100px + border: 4px solid $yellow + +border-radius($radius) + color: $yellow + font-size: 24px + text-align: center + pointer-events: none + +.dragging-label display: none + +.dropped-image + .run-functions-button + display: none .targetImage // width: 100%