-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathconsole.snapshot.js
More file actions
236 lines (207 loc) · 7 KB
/
Copy pathconsole.snapshot.js
File metadata and controls
236 lines (207 loc) · 7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/**
* An actual useful fork of dunxrion/console.image
* Created by Adrian Cooney
* http://dunxrion.github.io
*/
(function(console) {
"use strict";
/**
* Since the console.log doesn't respond to the `display` style,
* setting a width and height has no effect. In fact, the only styles
* I've found it responds to is font-size, background-image and color.
* To combat the image repeating, we have to get a create a font bounding
* box so to speak with the unicode box characters. EDIT: See Readme.md
*
* @param {int} width The height of the box
* @param {int} height The width of the box
* @return {object} {string, css}
*/
function getBox(width, height) {
return {
string: "+",
style: "font-size: 1px; padding: " + Math.floor(height/2) + "px " + Math.floor(width/2) + "px; line-height: " + height + "px;"
}
}
/**
* Profile class for a canvas
* @param {CanvasRenderingContext2D} ctx The context to profile
* @param {HTMLCanvasElement} canvas The canvas element
*/
var Profile = function(ctx, canvas) {
this.ctx = ctx;
this.canvas = canvas;
this.stack = [];
this.startTime = (new Date()).getTime();
this.collectFPS = true;
this.frames = 0;
//Add the initial state
this.addState(this.returnState(ctx));
//Add a sort of man in the middle/proxy for all the
//context render functions and tell the profiler
//when a function call happens. We send along the
//function name, the arguments, and the context state
var that = this;
for(var fn in ctx) { //Move the native data to a namespace
if(typeof ctx[fn] == "function") {
ctx["_CF_" + fn] = ctx[fn];
(function(fn) { //Create the closure
ctx[fn] = function() {
that.addState(that.returnState(ctx));
that.addFunctionCall(fn, Array.prototype.slice.call(arguments));
ctx["_CF_" + fn].apply(ctx, arguments);
}
})(fn);
}
}
//Start collecting the frames
(function tick() {
if(that.collectFPS) that.frames++, requestAnimationFrame(tick);
})();
};
/**
* Stop profiling
* @return {null}
*/
Profile.prototype.end = function() {
this.collectFPS = false;
this.duration = ((new Date()).getTime()) - this.startTime;
this.FPS = (this.frames * 1000)/this.duration; //TODO: fix this formula
//Remove the man in the middle
var ctx = this.ctx;
for(var fn in ctx) {
if(fn.match(/_CF_/)) {
fn = fn.replace("_CF_", "");
ctx[fn] = ctx["_CF_" + fn];
delete ctx["_CF_" + fn]; //And remove the cache
}
}
}
/**
* Return the useful data from the context
* to represent the state of the context
* Only adds the properties and not functions
* @param {CanvasRenderContext2D} ctx The context to make a state from
* @return {Object} State object
*/
Profile.prototype.returnState = function(ctx) {
var obj = {};
if(!this._stateKeys) {
this._stateKeys = [];
for(var key in ctx) {
if(typeof ctx[key] == "string" || typeof ctx[key] == "number") this._stateKeys.push(key), obj[key] = ctx[key];
}
} else {
for (var i = this._stateKeys.length - 1; i >= 0; i--) {
var key = this._stateKeys[i];
obj[key] = ctx[key];
};
}
return obj;
};
/**
* Add a state object to the current profile
* @param {StateObject} state See Profile#returnState
*/
Profile.prototype.addState = function(state) {
this.stack.push(["state", state]);
};
/**
* Add a function call to the current profile
* @param {string} fn The function name
* @param {array} args The array of arguments passed to the function
*/
Profile.prototype.addFunctionCall = function(fn, args) {
this.stack.push(["functionCall", fn, args]);
};
/**
* Output the profile to the console in a nice, readable way.
* @return {null}
*/
Profile.prototype.outputToConsole = function() {
var prevState = [], group = 1, scope = 1;
var callCount = 0, stateChanges = 0;
//console.group is a synchronous so this led to some
//messy state saving and changing but it works in the end
console.group("Canvas snapshot");
//console.log("%cFPS: %c" + this.FPS, "color: green", "color: #000"); Fix the formula!
console.group("Rendering");
this.stack.forEach(function(item, i) {
switch(item[0]) {
case "functionCall":
callCount++;
if(item[1] == "save") console.groupCollapsed("Saved Draw State #" + group), group++, scope++;
console.log("%c" + item[1] + "%c(" + item[2].join(", ") + ")", "color: blue; font-style: italic", "color: #000");
if(item[1] == "restore") console.groupEnd(), scope--;
break;
case "state":
var state = item[1];
if(!prevState.length) {
console.groupCollapsed("Initial state");
for(var key in state) console.log(key + " = " + state[key]);
console.groupEnd();
} else {
for(var key in state)
if(prevState[scope] && prevState[scope][key] !== state[key])
console.log("%c" + key + " %c= " + state[key], "color: purple; font-style: italic", "color: #000"), stateChanges++;
}
prevState[scope] = state;
break;
}
});
console.groupEnd();
//Add the screenshot
console.groupCollapsed("Screenshot");
console.screenshot(this.canvas, function() {
console.groupEnd(); //End the screenshot group
console.group("Statistics"); //Stats group
console.log("Function call count: ", callCount);
console.log("State change count: ", stateChanges);
console.groupEnd(); //End stats group
console.groupEnd(); //End the major group
});
};
/**
* Display an image in the console.
* @param {string} url The url of the image.
* @param {int} scale Scale factor on the image
* @return {null}
*/
console.image = function(url, scale, callback) {
if(typeof scale == "function") callback = scale, scale = 1;
if(!scale) scale = 1;
var img = new Image();
img.onload = function() {
var dim = getBox(this.width * scale, this.height * scale);
console.log("%c" + dim.string, dim.style + "background-image: url(" + url + "); background-size: " + (this.width * scale) + "px " + (this.height * scale) + "px; color: transparent;");
if(callback) callback();
};
img.src = url;
img.style.background = "url(" + url + ")"; //Preload it again..
};
/**
* Snapshot a canvas context and output it to the console.
* @param {HTMLCanvasElement} canvas The canvas element
* @return {null}
*/
console.screenshot = function(canvas, scale) {
var url = canvas.toDataURL(),
width = canvas.width,
height = canvas.height,
scale = scale || 1,
dim = getBox(width * scale, height * scale);
console.log("%c" + dim.string, dim.style + "background-image: url(" + url + "); background-size: " + (width * scale) + "px " + (height * scale) + "px; color: transparent;");
};
/**
* Snapshot/Profile a canvas element
* @param {HTMLCanvasElement} canvas The canvas element to profile
* @return {null}
*/
console.snapshot = function(canvas) {
//Start the profiling.
var profile = new Profile(canvas.getContext("2d"), canvas);
requestAnimationFrame(function() {
profile.end();
profile.outputToConsole();
});
}
})(console);