indexpost archiveatom feed syndication feed icon

Result: Scraped Color Palettes

2016-03-24

A small culmination of my recent efforts to compile a collection of color palettes from trendy sites.

Munge Some Data

Using the same process as last time, utilizing imagemagick to generate a histogram of the top colors in an image (scraped from a "design inspiration" site this time). The one minor change here is using tr to compress it down to a CSV file with the filename included for later reference. I had intended to do this part in Python using Wand but it turns out to be surprisingly difficult to find good documentation on how to use the histogram API. I eventually conceded that it would be faster to do it this way.

for i in *.jpg; do 
    convert $i \
            -colorspace LAB \
            -colors 4 \
            -format %c histogram:info:- \
    | gsed 's/.*\(#[0-9A-F]\{6\}\).*/\1/' \
    | tr '\n' ',' >> output.csv; echo $i >> output.csv;
done
#888073,#B1B1A5,#C3C3B7,#E7E7E2,1.jpg
#303C37,#6B8CAB,#737A82,#B3BFC9,10.jpg
#000000,#56554F,#848475,#DCDBD7,100.jpg
#523E76,#BCBBBC,#C2C2C2,#CF566A,101.jpg
#302924,#EBB79E,#FBC4A5,#FCF9F4,102.jpg
...

Template the Results

I thought one page per image would be a logical way to present the generated palettes with their corresponding image. It also eases bandwidth requirements so that I don't force everyone to pull down 70MB of photos (unless you browse every page).

Accomplishing this was a pleasant surprise using Python and Jinja2, I pulled in the CSV module to write a dictionary per line and then pass that into a template and substitute the four color values into style attributes and the image file name to an image tag src.

  import csv
  import sys
  from jinja2 import Environment, FileSystemLoader

  env = Environment(loader=FileSystemLoader('template'))
  template = env.get_template('template.html')

  def _parse_palette_csv(csv_input):
      with open(csv_input) as f:
          header = ['first', 'second', 'third', 'fourth', 'image_filename']
          reader = csv.DictReader(f, fieldnames=header)
          for palette_dict in reader:
              yield palette_dict

  def generate_pages(csv_input):
      palettes =_parse_palette_csv(csv_input)
      for i, palette_dict in enumerate(palettes):
          rendered_html = template.render(content=palette_dict)
          with open("./rendered/{}.html".format(i), 'wb') as out:
              out.write(rendered_html.encode('utf-8'))

  if __name__ == '__main__':
      generate_pages(sys.argv[1])

Where my template is (omitting some styling here):

  <body>
    <div>
      <div>
        <img src="./images/{{content.image_filename}}"></img>
      </div>
      <div>
        <div style="background-color:{{content.first}}"></div>
        <div style="background-color:{{content.second}}"></div>
        <div style="background-color:{{content.third}}"></div>
        <div style="background-color:{{content.fourth}}"></div>
      </div>
    </div>
    <div>random</div>
  </body>

Python and Jinja2 fly through it and generate over 500 pages in under 2 seconds. I opted out of designing navigation between pages or an index of them all and instead wrote a tiny bit of JavaScript to pick a page at random:

  var random = document.querySelector('.next-a416b');

  nextPage = Math.floor(Math.random() * 527) // total pages hard-coded, super good
  random.addEventListener("click", function() {
      window.location.href = nextPage + '.html';
  })

For a final bit of flair I decided to give each page a palette specific background based on the dominant color. A tiny bit more JavaScript and I'm finished:

  var body = document.querySelector('body');

  function hexToRgb(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;
  }

  rgb = hexToRgb("{{content.first}}");
  body.style.backgroundColor = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ', 0.5)';

I'm converting the hex to rgba so that I can apply an alpha value of 0.5 so that the backgrounds aren't over-powering. Sometimes it looks a little odd, but in general I think it works well enough.

You can check out the result here.