Skip to content

Instantly share code, notes, and snippets.

@unconed
Last active December 8, 2021 17:37
Show Gist options
  • Save unconed/4370822 to your computer and use it in GitHub Desktop.
Save unconed/4370822 to your computer and use it in GitHub Desktop.
How to capture WebGL/Canvas by writing out PNGs to a sandboxed filesystem

How to capture WebGL/Canvas by writing out PNGs to a sandboxed filesystem

Summary: Write .PNGs to the sandboxed file system in Chrome. Then sneak in and move them out.

1) Set up a sandboxed file system

// Request 40 GB (should be enough for ~5 minutes of 1080p)
var bytes = 1024*1024*1024*40;
window.webkitStorageInfo.requestQuota(PERSISTENT, bytes, function(grantedBytes) {
  console.log('Got storage', grantedBytes);
  window.webkitRequestFileSystem(PERSISTENT, grantedBytes, function (fs) {
    window.fs = fs;
    console.log("Got filesystem");
  });
}, function(e) {
  console.log('Storage error', e);
});

2) Override requestAnimationFrame to capture N frames and write them to PNGs

var frames = 1000;

var raf = window.requestAnimationFrame;
var next = null;
var hold = false;
window.requestAnimationFrame = function rafOverride(callback) {
  // Find canvas
  var canvas = document.querySelector('canvas');
  if (canvas && window.fs) {
    // Done capturing?
    if (frames < 0) {
      window.requestAnimationFrame = raf;
      return raf(callback);
    }

    // Hold rendering until screenshot is done
    if (!hold) {
      hold = true;
      frames--;
      setTimeout(function () {
        callback();
        capture(canvas, function () {
          // Resume rendering
          hold = false;
          rafOverride(next);
        });
      }, 66);
    }
    else {
      next = callback;
    }
  }
  else {
    // Canvas not created yet?
    return raf(callback);
  }
}

function capture(canvas, callback) {
  var name = Math.random(); // File name doesn't matter
  var image = canvas.toDataURL('image/png').slice(22);
  fs.root.getFile(name, {create: true}, function (entry) {
    entry.createWriter(function (writer) {
      // Convert base64 to binary without UTF-8 mangling.
      var data = atob(image);
      var buf = new Uint8Array(data.length);
      for (var i = 0; i < data.length; ++i) {
        buf[i] = data.charCodeAt(i);
      }

      // Write data
      var blob = new Blob([buf], {});
      writer.seek(0);
      writer.write(blob);

      console.log('Writing file', frames, blob.size);

      setTimeout(function () {
        // Resume rendering
        callback();
      }, 66);
    });
  }, function () { console.log('File error', arguments); });
}

3) Find where the files are stored and move them out

The reason the filenames are random in capture() is because they get written out as sequential 00000000, 00000001, etc. files, somewhere buried deep inside Chrome's profile.

On a Mac, I used the following node.js script to get them out. The specific location will differ, but you're looking for a directory full of 00, 01, 02, 03 subdirectories, with 100 numbered files inside each.

var fs = require('fs');
var path = "/Users/steven/Library/Application Support/Google/Chrome/Default/File System/009/p/";
var first = 0;
var last = 9899;
var j = 0;

for (var i = first; i <= last; ++i) {
  var subdir = ("00" + Math.floor(i / 100)).slice(-2);
  var file = path + subdir + '/' + ("000000000" + i).slice(-8);
  var target = './file' + ("00000000" + (++j)).slice(-8);
  console.log(file, target + '.png');
  fs.renameSync(file, target + '.png');
}

4) Assemble the PNGs into a movie with your favorite tool.

I used the old school Quicktime Player 7, as it still has the capability to open numbered image sequences. Then I used the x264 QuickTime plug-in to encode it (it's both faster and better than Apple's H.264 encoder).

5) Clear your Google Chrome cache to clean up the file system.

@iandanforth
Copy link

You might also pipe them through a websocket and write directly to a location of your choice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment