// Third party resources: // COLOURlovers API - A colour palette API by the folks at COLOURlovers.com http://www.colourlovers.com/api // crayola.json - A list of Crayola colours by Darius Kazemi https://github.com/dariusk/corpora/blob/master/data/colors/crayola.json function bot() { this.marbleImage = new MarbleImage(); // make this true once image has been drawn this.have_drawn = false; // return true if image has been drawn this.isDone = function() { return this.have_drawn; } this.preload = function() { this.marbleImage.loadColorPalette(); this.marbleImage.loadMarble(); this.marbleImage.loadJson(); } this.setup = function() { noSmooth(); this.marbleImage.sampleMarble(); } this.respond = function() { if (this.marbleImage.finished) { image(this.marbleImage.marble); this.marbleImage.applyColourPaletteToMarble(); this.marbleImage.getColourName(); // set have_drawn to true since we have completed this.have_drawn = true; } // return the message return this.marbleImage.message(); } } function MarbleImage() { // Marble images to sample from this.marbleImages = [ 'z_1.png', 'z_2.png', 'z_3.png', 'z_4.png' ]; // The colour table (not including background) of each marbleImage (ordered darkest to lightest) this.marblePalettes = { 'z_1.png': [ [150, 141, 136].toString(), [190, 181, 179].toString(), [216, 210, 210].toString(), [235, 232, 234].toString() ], 'z_2.png': [ [136, 133, 129].toString(), [178, 173, 169].toString(), [201, 196, 193].toString(), [224, 221, 221].toString() ], 'z_3.png': [ [12, 12, 10].toString(), [147, 165, 162].toString(), [119, 229, 226].toString(), [183, 229, 232].toString() ], 'z_4.png': [ [67, 140, 167].toString(), [134, 168, 184].toString(), [10, 93, 138].toString(), [192, 203, 212].toString() ] }; this.marblePalette = []; this.colourPaletteUrl = 'http://www.colourlovers.com/api/palettes/top?format=json&numResults=1&resultOffset='; this.colourPalette = []; this.crayolaColours; this.artNames; this.dominantColour; this.closestColourName; this.marble; this.finished = false; var self = this; // Send JSONP request to src URL // p5.js loadJSON() doesn't work with COLOURlovers API this.jsonp = function(src, options) { var callback_name = options.callbackName || 'callback'; var on_success = options.onSuccess || function(){}; var on_timeout = options.onTimeout || function(){}; var timeout = options.timeout || 10; // sec var timeout_trigger = window.setTimeout(function(){ window[callback_name] = function(){}; on_timeout(); }, timeout * 1000); window[callback_name] = function(data){ window.clearTimeout(timeout_trigger); on_success(data); } var script = document.createElement('script'); script.type = 'text/javascript'; script.async = true; script.src = src; document.getElementsByTagName('head')[0].appendChild(script); } // Extract colour palette from API data this.handleColourPaletteData = function(data) { // Set colourPalette with the 5 colours var colours = data[0].colors; for (var i = 0; i < colours.length; i++) { this.colourPalette[i] = colours[i]; } this.sortColourPalette(); } // Get a random popular colour palette from COLOURlovers API this.loadColorPalette = function() { // Add a random results page offset (1-50) to get different results every time var rand = ceil(random(50)); this.colourPaletteUrl += rand; // Add callback for JSONP var callbackName = 'callback'; this.colourPaletteUrl += '&jsonCallback=' + callbackName; var timeout = 5; // Send GET request to load JSONP this.jsonp(this.colourPaletteUrl, { callbackName: callbackName, timeout: timeout, onSuccess: function(data) { console.log('Successfully retreived API data'); self.handleColourPaletteData(data); }, onTimeout: function() { console.info('Timeout: failed to retrieve data from API after ' + timeout + ' seconds. Using local colour palette'); self.handleColourPaletteData(data); } }); } // Sort colour palette from (perceptual) darkest to lightest colours this.sortColourPalette = function() { this.colourPalette.sort(function (a, b) { var rgbA = self.hexToRgb(a); rgbA = [rgbA['r'], rgbA['g'], rgbA['b']]; var rgbB = self.hexToRgb(b); rgbB = [rgbB['r'], rgbB['g'], rgbB['b']]; return self.sumColour(rgbA) > self.sumColour(rgbB); }); // MarbleImage is done and it's image can be used now this.finished = true; } // Colour sorting code by Bas Dirks from // http://stackoverflow.com/questions/27960722/sort-array-with-rgb-color-on-javascript this.sumColour = function(rgb) { // To calculate relative luminance under sRGB and RGB colorspaces that use Rec. 709: return 0.2126*rgb[0] + 0.7152*rgb[1] + 0.0722*rgb[2]; } // Load a random marble image this.loadMarble = function() { var image = random(this.marbleImages); this.marblePalette = this.marblePalettes[image]; this.marble = loadImage(image); } // Load all JSON files this.loadJson = function() { this.crayolaColours = loadJSON('crayola.json'); this.artNames = loadJSON('art.json'); } // Sample a random section of marble image this.sampleMarble = function() { var sampleWidth = width * 1.5; var sampleHeight = height * 1.5; var sampleX = random(this.marble.width - sampleWidth); var sampleY = random(this.marble.height - sampleHeight); image(this.marble, sampleX, sampleY, sampleWidth, sampleHeight, 0, 0, width, height); var sampled = get(); this.marble = sampled; } // Convert a hex colour to RGB as an object this.hexToRgb = function(hex) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } // Replace colours of marble image from colours in colourPalette // Also set the dominant colour as a hex value this.applyColourPaletteToMarble = function() { var colourCount = []; for (var i = 0; i < this.colourPalette.length; i++) { colourCount[i] = { 'colour': this.colourPalette[i], 'count': 0 }; } loadPixels(); var d = pixelDensity(); var imageRes = 4 * (width * d) * (height * d); for (var i = 0; i < imageRes; i += 4) { var rgb = [pixels[i], pixels[i+1], pixels[i+2]].toString(); switch (rgb) { case this.marblePalette[0]: var colour = this.hexToRgb(this.colourPalette[0]); pixels[i] = colour['r']; pixels[i+1] = colour['g']; pixels[i+2] = colour['b']; colourCount[0]['count']++; break; case this.marblePalette[1]: var colour = this.hexToRgb(this.colourPalette[1]); pixels[i] = colour['r']; pixels[i+1] = colour['g']; pixels[i+2] = colour['b']; colourCount[1]['count']++; break; case this.marblePalette[2]: var colour = this.hexToRgb(this.colourPalette[2]); pixels[i] = colour['r']; pixels[i+1] = colour['g']; pixels[i+2] = colour['b']; colourCount[2]['count']++; break; case this.marblePalette[3]: var colour = this.hexToRgb(this.colourPalette[3]); pixels[i] = colour['r']; pixels[i+1] = colour['g']; pixels[i+2] = colour['b']; colourCount[3]['count']++; break; default: break; } } updatePixels(); var highestCountColour = colourCount[0]; for (var i = 1; i < colourCount.length; i++) { if (colourCount[i]['count'] > highestCountColour['count']) { highestCountColour = colourCount[i]; } } this.dominantColour = highestCountColour['colour']; } // Compare colours from an array and return the closest matching colour // (Modified) closest hex color compare code by Andrew Clark // http://stackoverflow.com/questions/17175664/get-the-closest-color-name-depending-on-an-hex-color this.closestColour = (function () { function dist(s, t) { if (!s.length || !t.length) return 0; return dist(s.slice(2), t.slice(2)) + Math.abs(parseInt(s.slice(0, 2), 16) - parseInt(t.slice(0, 2), 16)); } return function (arr, str) { var min = 0xffffff; var best, current, i; for (i = 0; i < arr.length; i++) { var crayolaHex = arr[i]['hex'].replace('#', ''); var compareHex = str.replace('#', ''); current = dist(crayolaHex, compareHex); if (current < min) { min = current best = arr[i]; } } return best; }; }()); // Find the closest colour in crayola.json to the dominant colour this.getColourName = function() { var closestColour = this.closestColour(this.crayolaColours['colors'], this.dominantColour); this.closestColourName = closestColour['color']; } // Combine the closest dominant colour name and an art description name as the message this.message = function() { if (!this.finished) { return 'Loading colour palette'; } return this.closestColourName + ' ' + random(this.artNames['names']); } }