├── README.md
├── console.snapshot.js
└── index.html
/README.md:
--------------------------------------------------------------------------------
1 | # console.snapshot( _canvas_ )
2 | ### A simple canvas profiler
3 |
4 | `console.snapshot` takes and inputted _<canvas>_ element and outputs a snapshot of it into the console. It makes debugging the canvas a little less dramatic. See [this demo](http://adriancooney.github.io/console.snapshot). `console.snapshot` is a fork of the [`console.image`](http://github.com/adriancooney/console.image) and actually does something useful.
5 |
6 | 
7 |
8 | ## Usage
9 | ### console.snapshot( _<canvas>_ )
10 | `console.snapshot` profiles a canvas for 1 iteration in the browser render loop or for one `requestAnimationFrame` tick and outputs the canvas context call stack and state changes to the console.
11 |
12 | ```js
13 | var canvas = document.getElementById("canvas");
14 |
15 | console.snapshot(canvas);
16 | ```
17 |
18 | ;
19 |
20 | ### console.screenshot( _<canvas>_ [, _<scale>_ ] )
21 | `console.screenshot` takes in a `HTMLCanvasElement`, base64 encodes it using `toDataURL` and then outputs it to the canvas using `console.image`. You can also pass along an optional scale factor to scale down the outputted image for better viewing within the Dev tools.
22 |
23 | ```js
24 | var canvas = document.createElement("canvas"),
25 | ctx = canvas.getContext("2d");
26 |
27 | // ...
28 | //draw
29 | // ...
30 |
31 | console.screenshot(canvas);
32 | console.screenshot(canvas, 0.8); //Snapshot it and scale the output to 80% of the original size
33 | ```
34 |
35 | 
36 |
37 | ### console.image( _<url>_ )
38 | `console.image` outputs an image from a url into the console. See [console.image](http://github.com/adriancooney/console.image).
39 |
40 | ```js
41 | console.image("http://i.imgur.com/wWPQK.gif");
42 | ```
43 |
44 | ## Troubleshooting
45 | ##### I'm getting "Uncaught Error: SecurityError: DOM Exception 18" when I try to snapshot the canvas.
46 | This is caused by printing a non CORS-enabled image on the canvas. The browser blocks the `toDataURL` function which is what `console.image` depends on to print the canvas. To fix this, see [this tutorial on HTML5Rocks](http://www.html5rocks.com/en/tutorials/cors/) or consider passing your image through a CORS proxy such as [corsproxy.com](http://corsproxy.com).
47 |
48 | License: MIT
49 |
--------------------------------------------------------------------------------
/console.snapshot.js:
--------------------------------------------------------------------------------
1 | /**
2 | * An actual useful fork of dunxrion/console.image
3 | * Created by Adrian Cooney
4 | * http://dunxrion.github.io
5 | */
6 |
7 | (function(console) {
8 | "use strict";
9 |
10 | /**
11 | * Since the console.log doesn't respond to the `display` style,
12 | * setting a width and height has no effect. In fact, the only styles
13 | * I've found it responds to is font-size, background-image and color.
14 | * To combat the image repeating, we have to get a create a font bounding
15 | * box so to speak with the unicode box characters. EDIT: See Readme.md
16 | *
17 | * @param {int} width The height of the box
18 | * @param {int} height The width of the box
19 | * @return {object} {string, css}
20 | */
21 | function getBox(width, height) {
22 | return {
23 | string: "+",
24 | style: "font-size: 1px; padding: " + Math.floor(height/2) + "px " + Math.floor(width/2) + "px; line-height: " + height + "px;"
25 | }
26 | }
27 |
28 | /**
29 | * Profile class for a canvas
30 | * @param {CanvasRenderingContext2D} ctx The context to profile
31 | * @param {HTMLCanvasElement} canvas The canvas element
32 | */
33 | var Profile = function(ctx, canvas) {
34 | this.ctx = ctx;
35 | this.canvas = canvas;
36 | this.stack = [];
37 | this.startTime = (new Date()).getTime();
38 | this.collectFPS = true;
39 | this.frames = 0;
40 |
41 | //Add the initial state
42 | this.addState(this.returnState(ctx));
43 |
44 | //Add a sort of man in the middle/proxy for all the
45 | //context render functions and tell the profiler
46 | //when a function call happens. We send along the
47 | //function name, the arguments, and the context state
48 | var that = this;
49 | for(var fn in ctx) { //Move the native data to a namespace
50 | if(typeof ctx[fn] == "function") {
51 | ctx["_CF_" + fn] = ctx[fn];
52 | (function(fn) { //Create the closure
53 | ctx[fn] = function() {
54 | that.addState(that.returnState(ctx));
55 | that.addFunctionCall(fn, Array.prototype.slice.call(arguments));
56 | ctx["_CF_" + fn].apply(ctx, arguments);
57 | }
58 | })(fn);
59 | }
60 | }
61 |
62 | //Start collecting the frames
63 | (function tick() {
64 | if(that.collectFPS) that.frames++, requestAnimationFrame(tick);
65 | })();
66 | };
67 |
68 | /**
69 | * Stop profiling
70 | * @return {null}
71 | */
72 | Profile.prototype.end = function() {
73 | this.collectFPS = false;
74 | this.duration = ((new Date()).getTime()) - this.startTime;
75 | this.FPS = (this.frames * 1000)/this.duration; //TODO: fix this formula
76 |
77 | //Remove the man in the middle
78 | var ctx = this.ctx;
79 | for(var fn in ctx) {
80 | if(fn.match(/_CF_/)) {
81 | fn = fn.replace("_CF_", "");
82 | ctx[fn] = ctx["_CF_" + fn];
83 | delete ctx["_CF_" + fn]; //And remove the cache
84 | }
85 | }
86 | }
87 |
88 | /**
89 | * Return the useful data from the context
90 | * to represent the state of the context
91 | * Only adds the properties and not functions
92 | * @param {CanvasRenderContext2D} ctx The context to make a state from
93 | * @return {Object} State object
94 | */
95 | Profile.prototype.returnState = function(ctx) {
96 | var obj = {};
97 | if(!this._stateKeys) {
98 | this._stateKeys = [];
99 | for(var key in ctx) {
100 | if(typeof ctx[key] == "string" || typeof ctx[key] == "number") this._stateKeys.push(key), obj[key] = ctx[key];
101 | }
102 | } else {
103 | for (var i = this._stateKeys.length - 1; i >= 0; i--) {
104 | var key = this._stateKeys[i];
105 | obj[key] = ctx[key];
106 | };
107 | }
108 |
109 | return obj;
110 | };
111 |
112 | /**
113 | * Add a state object to the current profile
114 | * @param {StateObject} state See Profile#returnState
115 | */
116 | Profile.prototype.addState = function(state) {
117 | this.stack.push(["state", state]);
118 | };
119 |
120 | /**
121 | * Add a function call to the current profile
122 | * @param {string} fn The function name
123 | * @param {array} args The array of arguments passed to the function
124 | */
125 | Profile.prototype.addFunctionCall = function(fn, args) {
126 | this.stack.push(["functionCall", fn, args]);
127 | };
128 |
129 | /**
130 | * Output the profile to the console in a nice, readable way.
131 | * @return {null}
132 | */
133 | Profile.prototype.outputToConsole = function() {
134 | var prevState = [], group = 1, scope = 1;
135 | var callCount = 0, stateChanges = 0;
136 |
137 | //console.group is a synchronous so this led to some
138 | //messy state saving and changing but it works in the end
139 | console.group("Canvas snapshot");
140 | //console.log("%cFPS: %c" + this.FPS, "color: green", "color: #000"); Fix the formula!
141 |
142 | console.group("Rendering");
143 | this.stack.forEach(function(item, i) {
144 | switch(item[0]) {
145 | case "functionCall":
146 | callCount++;
147 |
148 | if(item[1] == "save") console.groupCollapsed("Saved Draw State #" + group), group++, scope++;
149 | console.log("%c" + item[1] + "%c(" + item[2].join(", ") + ")", "color: blue; font-style: italic", "color: #000");
150 | if(item[1] == "restore") console.groupEnd(), scope--;
151 | break;
152 |
153 | case "state":
154 | var state = item[1];
155 |
156 | if(!prevState.length) {
157 | console.groupCollapsed("Initial state");
158 | for(var key in state) console.log(key + " = " + state[key]);
159 | console.groupEnd();
160 | } else {
161 | for(var key in state)
162 | if(prevState[scope] && prevState[scope][key] !== state[key])
163 | console.log("%c" + key + " %c= " + state[key], "color: purple; font-style: italic", "color: #000"), stateChanges++;
164 | }
165 |
166 | prevState[scope] = state;
167 | break;
168 | }
169 | });
170 | console.groupEnd();
171 |
172 | //Add the screenshot
173 | console.groupCollapsed("Screenshot");
174 | console.screenshot(this.canvas, function() {
175 | console.groupEnd(); //End the screenshot group
176 |
177 | console.group("Statistics"); //Stats group
178 | console.log("Function call count: ", callCount);
179 | console.log("State change count: ", stateChanges);
180 | console.groupEnd(); //End stats group
181 |
182 | console.groupEnd(); //End the major group
183 | });
184 | };
185 |
186 | /**
187 | * Display an image in the console.
188 | * @param {string} url The url of the image.
189 | * @param {int} scale Scale factor on the image
190 | * @return {null}
191 | */
192 | console.image = function(url, scale, callback) {
193 | if(typeof scale == "function") callback = scale, scale = 1;
194 | if(!scale) scale = 1;
195 |
196 | var img = new Image();
197 |
198 | img.onload = function() {
199 | var dim = getBox(this.width * scale, this.height * scale);
200 | console.log("%c" + dim.string, dim.style + "background-image: url(" + url + "); background-size: " + (this.width * scale) + "px " + (this.height * scale) + "px; color: transparent;");
201 | if(callback) callback();
202 | };
203 |
204 | img.src = url;
205 | img.style.background = "url(" + url + ")"; //Preload it again..
206 | };
207 |
208 | /**
209 | * Snapshot a canvas context and output it to the console.
210 | * @param {HTMLCanvasElement} canvas The canvas element
211 | * @return {null}
212 | */
213 | console.screenshot = function(canvas, scale) {
214 | var url = canvas.toDataURL(),
215 | width = canvas.width,
216 | height = canvas.height,
217 | scale = scale || 1,
218 | dim = getBox(width * scale, height * scale);
219 |
220 | console.log("%c" + dim.string, dim.style + "background-image: url(" + url + "); background-size: " + (width * scale) + "px " + (height * scale) + "px; color: transparent;");
221 | };
222 |
223 | /**
224 | * Snapshot/Profile a canvas element
225 | * @param {HTMLCanvasElement} canvas The canvas element to profile
226 | * @return {null}
227 | */
228 | console.snapshot = function(canvas) {
229 | //Start the profiling.
230 | var profile = new Profile(canvas.getContext("2d"), canvas);
231 | requestAnimationFrame(function() {
232 | profile.end();
233 | profile.outputToConsole();
234 | });
235 | }
236 | })(console);
237 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | console.snapshot
5 |
78 |
79 |
80 |
81 |
82 | Github
83 | console.snapshot( canvas )
84 |
85 |
86 |
Snapshot a canvas context and output it to the console.
87 |
88 |
89 |
90 |
91 |
Pop open the Chrome Dev tools (Ctrl/CMD + Shift + J
) and click the button below to snapshot the canvas to the left and see the output within the console.
92 |
93 | Snapshot
94 | Screenshot
95 |
96 |
97 |
98 |
This is the brainchild of a bit of idiocy and some insightful comments on a Hacker News thread . Created by @dunxrion .
99 |
100 |
101 |
102 |
103 |
196 |
--------------------------------------------------------------------------------