Image sequence diffing

I wrote this script to analyze dominant frame colors for two films in An over-analytical analysis of style. This script runs through a generated sequence of frame images and visualizes the most prominent color in each image.

Instructions

  1. Copy the code below into a file on your machine. Let’s name it visualize-frame-colors.js
  2. Install Node.js
  3. Fire up a Terminal window and cd to the directory where you saved the visualize-frame-colors.js file
  4. This script requires Color Thief. To install, cd into your project’s directory and run npm i colorthief --save-dev in your terminal. I try to avoid using third-party libraries, but I ended up taking a shortcut for this project.
  5. Edit all constants to fit your needs
  6. Run node visualize-frame-colors.js in your terminal
  7. Read out the results returned in the terminal
            
import fs from 'fs'
import ColorThief from 'colorthief';

// Toggle whether diff data should be generated and exported to JSON. This is an expensive operation, so it's helpful to set this to false if you're only tuning the visuals
const EXPORT_JSON = true
// The directory where the frame images exist 
const SEQUENCE_DIRECTORY_PATH = '../../../../../Movies/the-dark-knight'
// The naming of each frame (including separator). The script depends on a sequential number for each image with no leading zeros. Example: `frame_1, frame_2, frame_3`
const SEQUENCE_IMAGE_FILE_PREFIX = 'the_dark_knight_'
// The image type. I've only tested this with jpg images
const SEQUENCE_IMAGE_EXTENSION = '.jpg'
// The frame to start with. This can be helpful if you want to isolate the visualization to specific areas of a sequence
const START_FRAME = 1
// The frame to end with. If set to `-1`, the script will run until there are no more frames to load
const END_FRAME = 95
// The name/location for the generated JSON file containing all frame diff values. This JSON file is used to generate the final visualization
const EXPORT_JSON_PATH = './colors.json'

// The length of each sampled frame
const SAMPLED_FRAME_HEIGHT = 200
// The name/location for the generated SVG file
const EXPORT_SVG_PATH = './colors.svg'


async function sampleColors(imagePath, imageName, startFrame, endFrame, imageExtension, jsonFilePath) {
  let colors = []
  let frame = startFrame
  let img = imagePath + '/' + imageName + frame + imageExtension;
  while((frame < endFrame || endFrame === -1) && fs.existsSync(img)) {
    console.log(frame)
    var color = await getFrameColor(img)
    colors.push(color)
    frame++
    img = imagePath + '/' + imageName + frame + imageExtension;
  }
  
  fs.writeFileSync(jsonFilePath, JSON.stringify(colors))
  
}

function visualizeColors(colorData, frameHeight, svgFilePath) {
 
  let svg = ''
  for(var i = 0; i < colorData.length; i++) {
    let color = colorData[i]
    svg += '<rect x="' + i + '" width="1" height="' + frameHeight + '" fill="' + rgbToHex(color[0], color[1], color[2]) + '"/>'   
  }

  svg = '<svg width="' + colorData.length + '" height="' + frameHeight + '" preserveAspectRatio="none" viewBox="0 0 ' + colorData.length + ' ' + frameHeight + '" fill="#000" xmlns="http://www.w3.org/2000/svg">' + svg + '</svg>'
  fs.writeFileSync(svgFilePath, svg)
}

async function getFrameColor(img) {
  var color = await ColorThief.getColor(img)
  return color;
}

function rgbToHex(r, g, b) {
  return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}

async function generateColorTimeline(exportJSON, sequenceDirectory, sequenceFileName, startFrame, endFrame, imageExtension, jsonFilePath, frameHeight, svgFilePath) {
  if(exportJSON) {
    await sampleColors(sequenceDirectory, sequenceFileName, startFrame, endFrame, imageExtension, jsonFilePath)
  }

  let colorData = JSON.parse(fs.readFileSync(jsonFilePath))
  visualizeColors(colorData, frameHeight, svgFilePath)

}

generateColorTimeline(
  EXPORT_JSON, 
  SEQUENCE_DIRECTORY_PATH, 
  SEQUENCE_IMAGE_FILE_PREFIX, 
  START_FRAME, 
  END_FRAME,
  SEQUENCE_IMAGE_EXTENSION,
  EXPORT_JSON_PATH,
  SAMPLED_FRAME_HEIGHT,
  EXPORT_SVG_PATH
)