block by sxywu 2aead353c3446ee9acca

Image processing fun #6

Full Screen

Here, I took my twitter profile picture and used @enjalot‘s image downscaling tool to reduce the picture to 50x50 pixels. I then applied Atkinson Dithering, and mapped one of my tweets (sorted chronologically) to each filled pixel.

Built with blockbuilder.org

forked from sxywu‘s block: Image processing fun #1

forked from sxywu‘s block: Image processing fun #2

forked from sxywu‘s block: Image processing fun #3

forked from sxywu‘s block: Image processing fun #4

forked from sxywu‘s block: Image processing fun #5

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
  <style>
  </style>
</head>

<body>
  <img src="https://pbs.twimg.com/profile_images/3775316964/fe2ba0b0a4f0fb30aaef37c089553a8d.jpeg" width="400" />
  <canvas id="canvas"></canvas>
  <script>
    d3.json('twitter_profile3.json', function(image) {
      d3.json('tweets.json', function(tweets) {
        
        // first process the tweets
        var minOpacity = _.min(tweets, function(tweet) {
          return tweet.stats.favorites;
        });
        minOpacity = minOpacity.stats.favorites + 1;
        var maxOpacity = _.max(tweets, function(tweet) {
          return tweet.stats.favorites;
        });
        maxOpacity = maxOpacity.stats.favorites + 1;
        var opacityScale = d3.scale.log()
          .domain([minOpacity, maxOpacity])
          .range([25, 255]);
        var tweetColors = {
          'reply': [248,148,6], // orange
          'retweet': [81,163,81], // green
          'tweet': [0,136,204] // blue
        };
        tweets = _.sortBy(tweets, function(tweet) {
          tweet.date = new Date(tweet.created_at);
          tweet.opacity = opacityScale(tweet.stats.favorites + 1);
          if (tweet.retweet || tweet.quote) {
            tweet.type = 'retweet';
          } else if (tweet.in_reply_to) {
            tweet.type = 'reply';
          } else {
            tweet.type = 'tweet';
          }
          return tweet.date;
        });
        
        // some defaults for the image
        var imageSize = Math.sqrt(image.length);
        var scaleFactor = Math.floor(500 / imageSize);
        var canvas = document.getElementById('canvas');
        canvas.width = imageSize * scaleFactor;
        canvas.height = imageSize * scaleFactor;
        var ctx = canvas.getContext('2d');
        var data = [];
        var threshold = 122.5;

        // turn it grayscale first
        _.each(image, function(pixel) {
          data.push(Math.max(pixel[0], pixel[1], pixel[2]));
        });
        // Atkinson dithering
        var tweetIndex = 0;
        var tweetMap = {};
        _.each(data, function(oldPixel, i) {
          var newPixel = oldPixel > threshold ? 255 : 0;
          var error = (oldPixel - newPixel) >> 3;
          
          data[i] = newPixel;
          data[i + 1] += error;
          data[i + 1] += error;
          data[i + imageSize - 1] += error;
          data[i + imageSize] += error;
          data[i + imageSize + 1] += error;
          data[i + imageSize + 2] += error;
          
          if (!newPixel) {
            // if the pixel is black, then keep track of
            // its corresponding tweet
            tweetMap[i] = tweets[tweetIndex];
            tweetIndex += 1;
          }
        });
        data = data.slice(0, imageSize * imageSize);

        var imageData = ctx.getImageData(0,0,imageSize * scaleFactor, imageSize * scaleFactor);
        _.each(data, function(pixel, i) {
          var tweet = tweetMap[i];
          _.times(scaleFactor, function(x) {
            _.times(scaleFactor, function(y) {
              // first calculate row, then column
              var index = Math.floor(i / imageSize) * (imageSize * Math.pow(scaleFactor, 2)) +
                  (y * imageSize * scaleFactor) +
                  ((i % imageSize) * scaleFactor + x);
              index *= 4;

              imageData.data[index] = pixel || tweetColors[tweet.type][0];
              imageData.data[index + 1] = pixel || tweetColors[tweet.type][1];
              imageData.data[index + 2] = pixel || tweetColors[tweet.type][2];
              imageData.data[index + 3] = !tweet ? 255 : tweet.opacity;
            });
          });
        });
        ctx.putImageData(imageData, 0, 0);
      });
    });
  </script>
</body>