block by dribnet 374e9004f80b623328c2f373b9f3158d

Marble Bot

Full Screen

Twitter bot

This is part of a Twitter bot which generates marble images. It’s the creative component which creates the image and message which the bot posts. It takes a sample from a marble image, gets a colour palette from the COLOURlovers API, and applies the colours to the marble image. Every output is unique with a different image and colour palette every time.

The message is generated with two components. Firstly, the coverage of each colour is tracked. The dominant colour is compared to a list of Crayola colours and the closest matching Crayola colour is found. The name of the Crayola colour is the first part of the message. The second component is a randomly chosen name which describes the form of art.

What did I make?

The artefact of my generator is a uniquely coloured marble image. The concrete properties (good qualities) of each output are a unique beautiful colour palette and an interesting marble-like look (swirly with bands of colour).

The constraints (bad artefacts) are blank/empty images, a boring or ugly colour palette, smudgy large areas of colour, and images which don’t look like marble.

To ensure my generator produces a wide range of artefacts, I sample my marble from large images which has a mix of areas that are interesting and areas that are blank, boring, or ugly. To ensure it produces artefacts with more good properties than bad, the majority of the marble images it sample are interesting and are marble-like.

To ensure I get a good colour palette, I find a random popular colour palette in their ‘top’ category. There is enough results that it would take a long time (or luck) to get repeating colour palettes. Even if the palette repeats, the marble should be different.

Making the closed bot a green bot

In order to make the closed bot a green bot, I needed to get a colour palette from an API. I found COLOURlovers offered a nice API and implemented that. For my closed bot, I was extracting a colour palette from an image which was tedious. It also gave me problems with Cross Origin security errors when I tried to get images from an API. So I changed the way I get colour palettes to directly getting them online. This is a much more elegant solution. It also guarantees I will get nice colours because they are curated.

index.html

<head>
    <script src="//cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.3/p5.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.3/addons/p5.dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.0/seedrandom.min.js"></script>
    <script src="https://d3js.org/d3-random.v1.min.js"></script>
    <script language="javascript" type="text/javascript" src="focusedRandom.js"></script>
    <script language="javascript" type="text/javascript" src=".purview_helper.js"></script>
    <script language="javascript" type="text/javascript" src="bot.js"></script>
    <script language="javascript" type="text/javascript" src="sketch.js"></script>
    <style>
    body {padding: 0; margin: 0;}
    ul li {
      list-style:none;
      overflow:hidden;
      border:1px solid #dedede;
      margin:5px;
      padding:5px;
	}
    .media img {
      max-width:440px;
      max-height:220px;
    }
    </style>
</head>

<body style="background-color:white">
    <div id="canvasContainer"></div>
    <pre>
        <p id="tweet_text">
        </p>
    </pre>
    <hr>
    <div id="tweetExamples"></div>

</body>

art.json

{
  "names": [
    "swirl",
    "Ebru",
    "marble",
    "Suminagashi",
    "ink",
    "art"
  ]
}

bot.js

// 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']);
  }
}

bot.json

{
}

crayola.json

{
    "description": "List of Crayola crayon standard colors",
    "colors": [
        {
            "color": "Almond",
            "hex": "#EFDECD"
        },
        {
            "color": "Antique Brass",
            "hex": "#CD9575"
        },
        {
            "color": "Apricot",
            "hex": "#FDD9B5"
        },
        {
            "color": "Aquamarine",
            "hex": "#78DBE2"
        },
        {
            "color": "Asparagus",
            "hex": "#87A96B"
        },
        {
            "color": "Atomic Tangerine",
            "hex": "#FFA474"
        },
        {
            "color": "Banana Mania",
            "hex": "#FAE7B5"
        },
        {
            "color": "Beaver",
            "hex": "#9F8170"
        },
        {
            "color": "Bittersweet",
            "hex": "#FD7C6E"
        },
        {
            "color": "Black",
            "hex": "#000000"
        },
        {
            "color": "Blue",
            "hex": "#1F75FE"
        },
        {
            "color": "Blue Bell",
            "hex": "#A2A2D0"
        },
        {
            "color": "Blue Green",
            "hex": "#0D98BA"
        },
        {
            "color": "Blue Violet",
            "hex": "#7366BD"
        },
        {
            "color": "Blush",
            "hex": "#DE5D83"
        },
        {
            "color": "Brick Red",
            "hex": "#CB4154"
        },
        {
            "color": "Brown",
            "hex": "#B4674D"
        },
        {
            "color": "Burnt Orange",
            "hex": "#FF7F49"
        },
        {
            "color": "Burnt Sienna",
            "hex": "#EA7E5D"
        },
        {
            "color": "Cadet Blue",
            "hex": "#B0B7C6"
        },
        {
            "color": "Canary",
            "hex": "#FFFF99"
        },
        {
            "color": "Caribbean Green",
            "hex": "#00CC99"
        },
        {
            "color": "Carnation Pink",
            "hex": "#FFAACC"
        },
        {
            "color": "Cerise",
            "hex": "#DD4492"
        },
        {
            "color": "Cerulean",
            "hex": "#1DACD6"
        },
        {
            "color": "Chestnut",
            "hex": "#BC5D58"
        },
        {
            "color": "Copper",
            "hex": "#DD9475"
        },
        {
            "color": "Cornflower",
            "hex": "#9ACEEB"
        },
        {
            "color": "Cotton Candy",
            "hex": "#FFBCD9"
        },
        {
            "color": "Dandelion",
            "hex": "#FDDB6D"
        },
        {
            "color": "Denim",
            "hex": "#2B6CC4"
        },
        {
            "color": "Desert Sand",
            "hex": "#EFCDB8"
        },
        {
            "color": "Eggplant",
            "hex": "#6E5160"
        },
        {
            "color": "Electric Lime",
            "hex": "#CEFF1D"
        },
        {
            "color": "Fern",
            "hex": "#71BC78"
        },
        {
            "color": "Forest Green",
            "hex": "#6DAE81"
        },
        {
            "color": "Fuchsia",
            "hex": "#C364C5"
        },
        {
            "color": "Fuzzy Wuzzy",
            "hex": "#CC6666"
        },
        {
            "color": "Gold",
            "hex": "#E7C697"
        },
        {
            "color": "Goldenrod",
            "hex": "#FCD975"
        },
        {
            "color": "Granny Smith Apple",
            "hex": "#A8E4A0"
        },
        {
            "color": "Gray",
            "hex": "#95918C"
        },
        {
            "color": "Green",
            "hex": "#1CAC78"
        },
        {
            "color": "Green Yellow",
            "hex": "#F0E891"
        },
        {
            "color": "Hot Magenta",
            "hex": "#FF1DCE"
        },
        {
            "color": "Inchworm",
            "hex": "#B2EC5D"
        },
        {
            "color": "Indigo",
            "hex": "#5D76CB"
        },
        {
            "color": "Jazzberry Jam",
            "hex": "#CA3767"
        },
        {
            "color": "Jungle Green",
            "hex": "#3BB08F"
        },
        {
            "color": "Laser Lemon",
            "hex": "#FEFE22"
        },
        {
            "color": "Lavender",
            "hex": "#FCB4D5"
        },
        {
            "color": "Macaroni and Cheese",
            "hex": "#FFBD88"
        },
        {
            "color": "Magenta",
            "hex": "#F664AF"
        },
        {
            "color": "Mahogany",
            "hex": "#CD4A4C"
        },
        {
            "color": "Manatee",
            "hex": "#979AAA"
        },
        {
            "color": "Mango Tango",
            "hex": "#FF8243"
        },
        {
            "color": "Maroon",
            "hex": "#C8385A"
        },
        {
            "color": "Mauvelous",
            "hex": "#EF98AA"
        },
        {
            "color": "Melon",
            "hex": "#FDBCB4"
        },
        {
            "color": "Midnight Blue",
            "hex": "#1A4876"
        },
        {
            "color": "Mountain Meadow",
            "hex": "#30BA8F"
        },
        {
            "color": "Navy Blue",
            "hex": "#1974D2"
        },
        {
            "color": "Neon Carrot",
            "hex": "#FFA343"
        },
        {
            "color": "Olive Green",
            "hex": "#BAB86C"
        },
        {
            "color": "Orange",
            "hex": "#FF7538"
        },
        {
            "color": "Orchid",
            "hex": "#E6A8D7"
        },
        {
            "color": "Outer Space",
            "hex": "#414A4C"
        },
        {
            "color": "Outrageous Orange",
            "hex": "#FF6E4A"
        },
        {
            "color": "Pacific Blue",
            "hex": "#1CA9C9"
        },
        {
            "color": "Peach",
            "hex": "#FFCFAB"
        },
        {
            "color": "Periwinkle",
            "hex": "#C5D0E6"
        },
        {
            "color": "Piggy Pink",
            "hex": "#FDDDE6"
        },
        {
            "color": "Pine Green",
            "hex": "#158078"
        },
        {
            "color": "Pink Flamingo",
            "hex": "#FC74FD"
        },
        {
            "color": "Pink Sherbert",
            "hex": "#F78FA7"
        },
        {
            "color": "Plum",
            "hex": "#8E4585"
        },
        {
            "color": "Purple Heart",
            "hex": "#7442C8"
        },
        {
            "color": "Purple Mountain's Majesty",
            "hex": "#9D81BA"
        },
        {
            "color": "Purple Pizzazz",
            "hex": "#FE4EDA"
        },
        {
            "color": "Radical Red",
            "hex": "#FF496C"
        },
        {
            "color": "Raw Sienna",
            "hex": "#D68A59"
        },
        {
            "color": "Razzle Dazzle Rose",
            "hex": "#FF48D0"
        },
        {
            "color": "Razzmatazz",
            "hex": "#E3256B"
        },
        {
            "color": "Red",
            "hex": "#EE204D"
        },
        {
            "color": "Red Orange",
            "hex": "#FF5349"
        },
        {
            "color": "Red Violet",
            "hex": "#C0448F"
        },
        {
            "color": "Robin's Egg Blue",
            "hex": "#1FCECB"
        },
        {
            "color": "Royal Purple",
            "hex": "#7851A9"
        },
        {
            "color": "Salmon",
            "hex": "#FF9BAA"
        },
        {
            "color": "Scarlet",
            "hex": "#FC2847"
        },
        {
            "color": "Screamin' Green",
            "hex": "#76FF7A"
        },
        {
            "color": "Sea Green",
            "hex": "#93DFB8"
        },
        {
            "color": "Sepia",
            "hex": "#A5694F"
        },
        {
            "color": "Shadow",
            "hex": "#8A795D"
        },
        {
            "color": "Shamrock",
            "hex": "#45CEA2"
        },
        {
            "color": "Shocking Pink",
            "hex": "#FB7EFD"
        },
        {
            "color": "Silver",
            "hex": "#CDC5C2"
        },
        {
            "color": "Sky Blue",
            "hex": "#80DAEB"
        },
        {
            "color": "Spring Green",
            "hex": "#ECEABE"
        },
        {
            "color": "Sunglow",
            "hex": "#FFCF48"
        },
        {
            "color": "Sunset Orange",
            "hex": "#FD5E53"
        },
        {
            "color": "Tan",
            "hex": "#FAA76C"
        },
        {
            "color": "Tickle Me Pink",
            "hex": "#FC89AC"
        },
        {
            "color": "Timberwolf",
            "hex": "#DBD7D2"
        },
        {
            "color": "Tropical Rain Forest",
            "hex": "#17806D"
        },
        {
            "color": "Tumbleweed",
            "hex": "#DEAA88"
        },
        {
            "color": "Turquoise Blue",
            "hex": "#77DDE7"
        },
        {
            "color": "Unmellow Yellow",
            "hex": "#FFFF66"
        },
        {
            "color": "Violet",
            "hex": "#926EAE"
        },
        {
            "color": "Violet Red",
            "hex": "#F75394"
        },
        {
            "color": "Vivid Tangerine",
            "hex": "#FFA089"
        },
        {
            "color": "Vivid Violet",
            "hex": "#8F509D"
        },
        {
            "color": "White",
            "hex": "#FFFFFF"
        },
        {
            "color": "Wild Blue Yonder",
            "hex": "#A2ADD0"
        },
        {
            "color": "Wild Strawberry",
            "hex": "#FF43A4"
        },
        {
            "color": "Wild Watermelon",
            "hex": "#FC6C85"
        },
        {
            "color": "Wisteria",
            "hex": "#CDA4DE"
        },
        {
            "color": "Yellow",
            "hex": "#FCE883"
        },
        {
            "color": "Yellow Green",
            "hex": "#C5E384"
        },
        {
            "color": "Yellow Orange",
            "hex": "#FFAE42"
        }
    ]
}

focusedRandom.js

function resetFocusedRandom() {
  return Math.seedrandom(arguments);
}

function focusedRandom(min, max, focus, mean) {
  // console.log("hello")
  if(max === undefined) {
    max = min;
    min = 0;
  }
  if(focus === undefined) {
    focus = 1.0;
  }
  if(mean === undefined) {
    mean = (min + max) / 2.0;
  }
  if(focus == 0) {
    return d3.randomUniform(min, max)();
  }
  else if(focus < 0) {
    focus = -1 / focus;
  }
  sigma = (max - mean) / focus;
  val = d3.randomNormal(mean, sigma)();
  if (val > min && val < max) {
    return val;
  }
  return d3.randomUniform(min, max)();
}

sketch.js

var rndSeed;

var bot;
var renderReady = false;

function preload() {
  bot = new bot();
  bot.preload();
}

function setup () {
  var main_canvas = createCanvas(440, 220);
  main_canvas.parent('canvasContainer');

  rndSeed = random(1024);
  bot.setup();
}

function keyTyped() {
  if (key == '!') {
    saveBlocksImages();
  }
  else if (key == '@') {
    saveBlocksImages(true);
  }
}

function reportRenderReady() {
  finalDiv = createDiv('(render ready)');  
  finalDiv.id("render_ready")
}

function draw() {
  background(204);
  // randomSeed(0);
  resetFocusedRandom(rndSeed);
  message = bot.respond();
  var text = select('#tweet_text');
  text.html(message);
  if(renderReady == false) {
    if(bot.isDone()) {
      reportRenderReady();
      renderReady = true;
    }
  }
}