├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── src
├── checkers.svg
├── favicon.png
├── giphy-arts-logo.svg
├── giphy-logo.svg
├── index.html
├── marks.js
├── next.svg
├── og-image.png
├── pause.svg
├── play.svg
├── sketch.js
├── style.css
├── timeline.js
├── ui.js
└── vendor
│ ├── gif.js
│ ├── gif.worker.js
│ ├── p5.js
│ └── p5.min.js
└── todo-20180312.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | .DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sketch Machine
2 |
3 | The Sketch Machine (www.sketchmachine.net) wants you to draw; it's waiting for you. The Sketch Machine is a quick and simple way to create short, looping animations and to turn them into GIFs to share through the web.
4 |
5 | Sketch Machine was created by Casey REAS (http://caesuras.net/) in 2018, with help from GIPHY. It was created within the tradition of “direct animation” works by Len Lye and Stan Brakhage and exploratory drawing software like TURUX.
6 |
7 | * [Sketch Machine Notes](https://github.com/REAS/sketchmachine/wiki/Sketch-Machine-Notes)
8 | * [Drawing/Animation/Coding Systems](https://github.com/REAS/sketchmachine/wiki/Drawing,-Animation,-Coding-Systems-(DACS))
9 | * [Weird Drawing Software](https://github.com/REAS/sketchmachine/wiki/Weird-Drawing-Software)
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sketchmachine",
3 | "version": "1.0.0",
4 | "description": "",
5 | "now": {
6 | "alias": [
7 | "sketchmachine.net",
8 | "www.sketchmachine.net"
9 | ]
10 | },
11 | "scripts": {
12 | "start": "serve src",
13 | "dev": "browser-sync start --server 'src' --files 'src' --no-notify"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/REAS/sketchmachine.git"
18 | },
19 | "author": "Casey REAS",
20 | "license": "GPL-2.0",
21 | "bugs": {
22 | "url": "https://github.com/REAS/sketchmachine/issues"
23 | },
24 | "homepage": "https://github.com/REAS/sketchmachine#readme",
25 | "devDependencies": {
26 | "browser-sync": "^2.18.13"
27 | },
28 | "dependencies": {
29 | "serve": "latest"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/checkers.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/REAS/sketchmachine/54769a949ed4a4859e3e95e9343c4324865275e1/src/favicon.png
--------------------------------------------------------------------------------
/src/giphy-arts-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/giphy-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Sketch Machine
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
34 |
35 |
36 |
89 |
110 |
111 |
Options
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | Speed
120 |
121 |
122 |
123 |
124 |
128 |
129 |
130 |
131 |
137 |
138 |
139 |
140 |
Export
141 |
142 |
143 |
144 |
147 |
![]()
148 |
149 |
152 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
197 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
--------------------------------------------------------------------------------
/src/marks.js:
--------------------------------------------------------------------------------
1 | // POINTS
2 |
3 | let pastPoints = [];
4 | for (let i = 0; i < markers.length; i += 1) {
5 | pastPoints.push({
6 | lastPoint: {
7 | color: undefined,
8 | thickness: undefined,
9 | x: undefined,
10 | y: undefined,
11 | },
12 | lastPointFrames: []
13 | })
14 | }
15 |
16 | /*
17 | function mark1(sketch, i, color, thickness) {
18 |
19 | thickness /= 4;
20 |
21 | // if (smoothing) {
22 | // mx += (targetX - mx) * easing;
23 | // my += (targetY - my) * easing;
24 | // }
25 |
26 | if (pmx !== mx || pmy !== my) {
27 | markerFrames[i].strokeCap(sketch.ROUND);
28 | markerFrames[i].noFill();
29 | markerFrames[i].stroke(color);
30 | if (speedSize) {
31 | let varThick = sketch.map(thickness, 1, 100, 0.25, 3.0);
32 | let diameter = sketch.dist(pmx, pmy, mx, my) * varThick;
33 | markerFrames[i].strokeWeight(diameter);
34 | } else {
35 | markerFrames[i].strokeWeight(thickness + 1);
36 | }
37 |
38 | let x1 = mx + rx;
39 | let y1 = my + ry;
40 | markerFrames[i].point(x1, y1);
41 | }
42 | }
43 | */
44 |
45 | function mark1(sketch, i, color, thickness) {
46 |
47 | thickness /= 4;
48 |
49 | /*
50 | if (smoothing) {
51 | mx += (targetX - mx) * easing;
52 | my += (targetY - my) * easing;
53 | }
54 | */
55 |
56 | markerFrames[i].strokeCap(sketch.ROUND);
57 | markerFrames[i].stroke(color);
58 |
59 | if (speedSize) {
60 | let varThick = sketch.map(thickness, 1, 100, 0.25, 2.0);
61 | let diameter = sketch.dist(pmx, pmy, mx, my) * varThick;
62 | markerFrames[i].strokeWeight(diameter);
63 | } else {
64 | markerFrames[i].strokeWeight(thickness);
65 | }
66 |
67 | let x = mx + rx;
68 | let y = my + ry;
69 |
70 | let lastPoint = pastPoints[i].lastPoint;
71 | let lastPointFrames = pastPoints[i].lastPointFrames;
72 |
73 | let pointChanged = lastPoint.color !== color ||
74 | lastPoint.thickness !== thickness ||
75 | lastPoint.x !== x ||
76 | lastPoint.y !== y;
77 |
78 | // markerFrames[i].drawingContext.globalCompositeOperation = 'copy';
79 |
80 | if (pointChanged) {
81 | markerFrames[i].point(x, y);
82 | pastPoints[i].lastPointFrames = [currentFrame]
83 | } else if (lastPointFrames.includes(currentFrame) === false) {
84 | lastPointFrames.push(currentFrame);
85 | markerFrames[i].point(x, y);
86 | }
87 |
88 | // markerFrames[i].drawingContext.globalCompositeOperation = 'source-over';
89 |
90 | pastPoints[i].lastPoint = {
91 | color: color,
92 | thickness: thickness,
93 | x: x,
94 | y: y,
95 | }
96 | }
97 |
98 |
99 | // LINES
100 |
101 | let pastLines = [];
102 | for (let i = 0; i < markers.length; i += 1) {
103 | pastLines.push({
104 | lastLine: {
105 | color: undefined,
106 | thickness: undefined,
107 | x1: undefined,
108 | y1: undefined,
109 | x2: undefined,
110 | y2: undefined
111 | },
112 | lastLineFrames: []
113 | })
114 | }
115 |
116 | function mark2(sketch, i, color, thickness) {
117 |
118 | /*
119 | if (smoothing) {
120 | mx += (targetX - mx) * easing;
121 | my += (targetY - my) * easing;
122 | }
123 | */
124 |
125 | markerFrames[i].strokeCap(sketch.ROUND);
126 | markerFrames[i].strokeJoin(sketch.ROUND);
127 | markerFrames[i].stroke(color);
128 | markerFrames[i].noFill();
129 | if (speedSize) {
130 | let varThick = sketch.map(thickness, 1, 100, 0.25, 2.0);
131 | let diameter = sketch.dist(pmx, pmy, mx, my) * varThick;
132 | markerFrames[i].strokeWeight(diameter);
133 | } else {
134 | markerFrames[i].strokeWeight(thickness);
135 | }
136 |
137 | let x0 = ppmx + pprx;
138 | let y0 = ppmy + ppry;
139 | let x1 = pmx + prx;
140 | let y1 = pmy + pry;
141 | let x2 = mx + rx;
142 | let y2 = my + ry;
143 |
144 | let lastLine = pastLines[i].lastLine;
145 | let lastLineFrames = pastLines[i].lastLineFrames;
146 |
147 | let lineChanged = lastLine.color !== color ||
148 | lastLine.thickness !== thickness ||
149 | lastLine.x1 !== x1 ||
150 | lastLine.y1 !== y1 ||
151 | lastLine.x2 !== x2 ||
152 | lastLine.y2 !== y2;
153 |
154 | let points
155 |
156 | if (lineChanged) {
157 | points = curvePoints(x0, y0, x1, y1, x2, y2);
158 | pastLines[i].lastLineFrames = [currentFrame];
159 | } else if (lastLineFrames.includes(currentFrame) === false) {
160 | lastLineFrames.push(currentFrame);
161 | points = curvePoints(x0, y0, x1, y1, x2, y2);
162 | }
163 |
164 | if (points) {
165 | if (points.length === 2 && points[0][0] === points[1][0] && points[0][1] === points[1][1]) {
166 | markerFrames[i].point(...points[0])
167 | } else if (points.length === 0) {
168 | markerFrames[i].point(x2, y2);
169 | } else {
170 | markerFrames[i].beginShape();
171 | points.forEach((p) => { markerFrames[i].vertex(...p); });
172 | markerFrames[i].endShape();
173 | }
174 | }
175 |
176 | pastLines[i].lastLine = {
177 | color: color,
178 | thickness: thickness,
179 | x1: x1,
180 | y1: y1,
181 | x2: x2,
182 | y2: y2,
183 | }
184 | }
185 |
186 | // QUADS
187 |
188 | function mark3(sketch, i, color, thickness) {
189 | /*
190 | if (smoothing) {
191 | mx += (targetX - mx) * easing;
192 | my += (targetY - my) * easing;
193 | }
194 | */
195 |
196 | if (pmx !== mx || pmy !== my) {
197 | markerFrames[i].strokeCap(sketch.SQUARE);
198 | markerFrames[i].noFill();
199 | markerFrames[i].stroke(color);
200 | if (speedSize) {
201 | let varThick = sketch.map(thickness, 1, 100, 0.25, 3.0);
202 | let diameter = sketch.dist(pmx, pmy, mx, my) * varThick;
203 | markerFrames[i].strokeWeight(diameter);
204 | } else {
205 | markerFrames[i].strokeWeight(thickness + 1);
206 | }
207 |
208 | let x1 = pmx + prx;
209 | let y1 = pmy + pry;
210 | let x2 = mx + rx;
211 | let y2 = my + ry;
212 | markerFrames[i].line(x1, y1, x2, y2);
213 | }
214 | }
215 |
216 |
217 | // Utility functions for curves
218 |
219 | function curvePoints(x0, y0, x1, y1, x2, y2) {
220 | // Update pastPast, past, and current points.
221 | let midp1 = midp([], [x0, y0], [x1, y1]);
222 | let midp2 = midp([], [x1, y1], [x2, y2]);
223 |
224 | let flow = 1;
225 |
226 | // Make a low res (flattened) quadratic bezier curve from three control points, so that a completely new curve is generated each time a mouse coordinate gets added.
227 | // A: Midpoint of past and pastPast points.
228 | // B: pastPoint
229 | // C: Midpoint of pastPoint and current point.
230 | let dist = Math.hypot(midp2[0] - midp1[0], midp2[1] - midp1[1]);
231 | let segmentCount = 1 + Math.floor(dist / flow);
232 | let step = 1 / segmentCount;
233 | let bezierPoints = [];
234 | for (let i = 0; i <= segmentCount; i += 1) {
235 | let t = i*step;
236 | bezierPoints.push(quadBez(t, midp1, [x1, y1], midp2));
237 | }
238 |
239 | return bezierPoints
240 | }
241 |
242 | function quadBez (t, p1, p2, p3) {
243 | return [quadBezXY(t, [p1[0], p2[0], p3[0]]), quadBezXY(t, [p1[1], p2[1], p3[1]])]
244 | }
245 |
246 | function quadBezXY(t, w) {
247 | t2 = t * t;
248 | mt = 1 - t;
249 | mt2 = mt * mt;
250 | return w[0] * mt2 + w[1] * 2 * mt * t + w[2] * t2
251 | }
252 |
253 | function midp (out, a, b) {
254 | out[0] = (a[0] + b[0]) / 2;
255 | out[1] = (a[1] + b[1]) / 2;
256 | return out
257 | }
258 |
--------------------------------------------------------------------------------
/src/next.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
--------------------------------------------------------------------------------
/src/og-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/REAS/sketchmachine/54769a949ed4a4859e3e95e9343c4324865275e1/src/og-image.png
--------------------------------------------------------------------------------
/src/pause.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
--------------------------------------------------------------------------------
/src/play.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
--------------------------------------------------------------------------------
/src/sketch.js:
--------------------------------------------------------------------------------
1 | let canvas;
2 | let frames = [];
3 | let backgroundFrame;
4 | let markerFrames = [];
5 | let compositeFrame;
6 | let exportFrame;
7 |
8 | let numFrames = 36; // 48
9 | let numMarkerFrames = 5;
10 | let firstFrame = 12; //18;
11 | let lastFrame = 24; //30;
12 | let currentFrame = firstFrame;
13 |
14 | let timelineRangeSelected = false;
15 | let timelineRangeLock = false;
16 | let arrowLock = false;
17 |
18 | let numToLeft = 0;
19 | let numToRight = 0;
20 |
21 | let jitterOn = false;
22 |
23 | let frameDim = 512;
24 | let surfaceDim = frameDim;
25 | if (window.screen.availWidth >= 1024) {
26 | surfaceDim = Math.min(1024, 512 * window.devicePixelRatio);
27 | }
28 | let resInt = parseInt(window.location.search.replace('?res=', ''));
29 | if (resInt) { surfaceDim = Math.min(1024, resInt); }
30 | let surfaceBorder = 4;
31 | let frameSurfaceRatio = frameDim / surfaceDim;
32 |
33 | let lastTime = 0;
34 | let timeStep = 500; // In milliseconds
35 |
36 | let pause = false;
37 | let startDrawing = false;
38 | let didDrawAnything = false;
39 |
40 | let mx = 0;
41 | let my = 0;
42 | let pmx = 0
43 | let pmy = 0;
44 | let ppmx = 0;
45 | let ppmy = 0;
46 |
47 | let rx = 0;
48 | let ry = 0;
49 | let prx = 0
50 | let pry = 0;
51 | let pprx = 0;
52 | let ppry = 0;
53 |
54 | let targetX = 0;
55 | let targetY = 0;
56 |
57 | let currentColor = "#FFFFFF";
58 | let currentColorSelection = 1;
59 |
60 | const ui = document.querySelector('#ui');
61 | const sketchContainer = document.querySelector('#sketch-container');
62 | const animationDummy = document.querySelector('#animation-dummy');
63 | const speedSlider = document.querySelector('#speed');
64 |
65 | const colorSelector = document.querySelector('#color-selector');
66 | const backgroundColorSelector = document.querySelector('#background-color-selector');
67 | const backgroundColorButton = document.querySelector('#background-color-button');
68 |
69 | let speedSize = false;
70 | let onionSkin = false;
71 | let smoothing = false;
72 | let easingSlider = document.querySelector("#easing-slider");
73 | let easing = 0.0;
74 |
75 | // TIMELINE
76 | //let overFrame = new Array(numFrames).fill(false);
77 | //let overMarker = new Array(numFrames).fill(false);
78 | let onFrame = new Array(numFrames).fill(false);
79 | //let firstClick = false;
80 | //let addMode = true; // Add or remove active frames
81 |
82 | let playbackDirection = 1;
83 |
84 | const REVERSE = 0;
85 | const FORWARD = 1;
86 | const BACKANDFORTH = 2;
87 |
88 | let playbackMode = FORWARD;
89 | const fpsOptions = [20, 30, 40, 60, 80, 120, 250, 500, 1000];
90 |
91 | // MARKERS
92 | const marker1Select = document.querySelector("#b1-select");
93 | const marker1Tools = document.querySelector("#b1-tools");
94 | const marker1Slider = document.querySelector("#b1-slider");
95 | const marker1ColorButton = document.querySelector("#b1-color");
96 | let marker1Color = 0;
97 |
98 | const marker2Select = document.querySelector("#b2-select");
99 | const marker2Tools = document.querySelector("#b2-tools");
100 | const marker2Slider = document.querySelector("#b2-slider");
101 | const marker2ColorButton = document.querySelector("#b2-color");
102 | let marker2Color = 0;
103 |
104 | const marker3Select = document.querySelector("#b3-select");
105 | const marker3Tools = document.querySelector("#b3-tools");
106 | const marker3Slider = document.querySelector("#b3-slider");
107 | const marker3ColorButton = document.querySelector("#b3-color");
108 | let marker3Color = 0;
109 |
110 | const marker4Select = document.querySelector("#b4-select");
111 | const marker4Tools = document.querySelector("#b4-tools");
112 | const marker4Slider = document.querySelector("#b4-slider");
113 | const marker4ColorButton = document.querySelector("#b4-color");
114 | let marker4Color = 0;
115 |
116 | const marker5Select = document.querySelector("#b5-select");
117 | const marker5Tools = document.querySelector("#b5-tools");
118 | const marker5Slider = document.querySelector("#b5-slider");
119 | const marker5ColorButton = document.querySelector("#b5-color");
120 | let marker5Color = 0;
121 |
122 | let backgroundColor = 0;
123 | let backgroundEnabled = true;
124 | let randomXY = document.querySelector("#randomXY");
125 | let markers = [false, false, false, false, false];
126 |
127 | let timelineCanvas;
128 |
129 | const animationSketch = new p5(function (sketch) {
130 | sketch.setup = function() {
131 | sketch.pixelDensity(window.devicePixelRatio);
132 | canvas = sketch.createCanvas(frameDim, frameDim);
133 |
134 | if (surfaceDim <= frameDim) { sketch.noSmooth() }
135 |
136 | /*
137 | for (let i = firstFrame; i < lastFrame; i++) {
138 | onFrame[i] = true;
139 | }
140 | */
141 |
142 | canvas.id('animation');
143 |
144 | animationDummy.remove();
145 | sketchContainer.prepend(canvas.elt);
146 | canvas.background(204);
147 |
148 | marker1Color = web216[sketch.int(sketch.random(web216.length))]; //"#FFFFFF";
149 | marker2Color = web216[sketch.int(sketch.random(web216.length))];
150 | marker3Color = web216[sketch.int(sketch.random(web216.length))];
151 | marker4Color = web216[sketch.int(sketch.random(web216.length))];
152 | marker5Color = web216[sketch.int(sketch.random(web216.length))];
153 |
154 | marker1ColorButton.style.backgroundColor = marker1Color;
155 | marker2ColorButton.style.backgroundColor = marker2Color;
156 | marker3ColorButton.style.backgroundColor = marker3Color;
157 | marker4ColorButton.style.backgroundColor = marker4Color;
158 | marker5ColorButton.style.backgroundColor = marker5Color;
159 |
160 | backgroundColor = web216[sketch.int(sketch.random(web216.length))]; //"#000000"; //
161 | backgroundColorButton.style.backgroundColor = backgroundColor;
162 |
163 | sketch.pixelDensity(1);
164 |
165 | for (let i = 0; i < numFrames; i++) {
166 | frames[i] = sketch.createGraphics(surfaceDim, surfaceDim);
167 | }
168 |
169 | for (let i = 0; i < numMarkerFrames; i++) {
170 | markerFrames[i] = sketch.createGraphics(surfaceDim, surfaceDim);
171 | }
172 |
173 | compositeFrame = sketch.createGraphics(surfaceDim, surfaceDim);
174 | exportFrame = sketch.createGraphics(surfaceDim, surfaceDim);
175 |
176 | backgroundFrame = sketch.createGraphics(surfaceDim, surfaceDim);
177 | backgroundFrame.background(backgroundColor);
178 |
179 | sketch.pixelDensity(window.devicePixelRatio);
180 |
181 | lastTime = sketch.millis();
182 | };
183 |
184 | sketch.draw = function () {
185 | if (startDrawing) {
186 |
187 | if (smoothing) {
188 | targetX = Math.floor((sketch.mouseX - surfaceBorder) / frameSurfaceRatio)
189 | targetY = Math.floor((sketch.mouseY - surfaceBorder) / frameSurfaceRatio)
190 | } else {
191 | mx = Math.floor((sketch.mouseX - surfaceBorder) / frameSurfaceRatio)
192 | my = Math.floor((sketch.mouseY - surfaceBorder) / frameSurfaceRatio)
193 | }
194 | }
195 |
196 | // TIMELINE
197 | timeStep = fpsOptions[parseInt(speedSlider.value)-1];
198 |
199 | // MARKERS
200 | markers[0] = marker1Select.checked;
201 | markers[1] = marker2Select.checked;
202 | markers[2] = marker3Select.checked;
203 | markers[3] = marker4Select.checked;
204 | markers[4] = marker5Select.checked;
205 |
206 | let whichTool;
207 |
208 | let markFunctions = [ mark1, mark2, mark3 ];
209 |
210 | let rxy = parseInt(randomXY.value);
211 |
212 | rx = 0;
213 | ry = 0;
214 |
215 | let tempEasing = parseInt(easingSlider.value);
216 | if (tempEasing > 0) {
217 | easing = sketch.map(tempEasing, 0, 100, 0.1, 0.02);
218 | //smoothing = true;
219 | } else {
220 | //smoothing = false;
221 | }
222 |
223 | //if (rxy !== 0) {
224 | if (jitterOn) {
225 | rx = sketch.random(-rxy, rxy);
226 | ry = sketch.random(-rxy, rxy);
227 | }
228 | //}
229 |
230 | if (startDrawing) {
231 | if (smoothing) {
232 | mx += (targetX - mx) * easing;
233 | my += (targetY - my) * easing;
234 | }
235 | if (markers[0]) {
236 | whichTool = parseInt(marker1Tools.value);
237 | markFunctions[whichTool - 1](sketch, 0, marker1Color, calculateThickness(marker1Slider.value));
238 | didDrawAnything = true
239 | }
240 | if (markers[1]) {
241 | whichTool = parseInt(marker2Tools.value);
242 | markFunctions[whichTool - 1](sketch, 1, marker2Color, calculateThickness(marker2Slider.value));
243 | didDrawAnything = true
244 | }
245 | if (markers[2]) {
246 | whichTool = parseInt(marker3Tools.value);
247 | markFunctions[whichTool - 1](sketch, 2, marker3Color, calculateThickness(marker3Slider.value));
248 | didDrawAnything = true
249 | }
250 | if (markers[3]) {
251 | whichTool = parseInt(marker4Tools.value);
252 | markFunctions[whichTool - 1](sketch, 3, marker4Color, calculateThickness(marker4Slider.value));
253 | didDrawAnything = true
254 | }
255 | if (markers[4]) {
256 | whichTool = parseInt(marker5Tools.value);
257 | markFunctions[whichTool - 1](sketch, 4, marker5Color, calculateThickness(marker5Slider.value));
258 | didDrawAnything = true
259 | }
260 | }
261 |
262 | // Now, finally, draw the animation to the screen
263 |
264 | if (!pause || startDrawing) {
265 | displayFrame(sketch)
266 | }
267 |
268 | if (startDrawing) {
269 | ppmx = pmx;
270 | ppmy = pmy;
271 | pprx = prx;
272 | ppry = pry;
273 | pmx = mx;
274 | pmy = my;
275 | prx = rx;
276 | pry = ry;
277 | }
278 |
279 | if (!pause) {
280 | if (sketch.millis() > lastTime + timeStep) {
281 | writeMarkersIntoFrames();
282 |
283 | if (playbackMode === FORWARD) {
284 | currentFrame++; // Go to the next frame
285 | if (currentFrame >= lastFrame) {
286 | currentFrame = firstFrame;
287 | }
288 | } else if (playbackMode === REVERSE) {
289 | currentFrame--; // Go to the next frame
290 | if (currentFrame < firstFrame) {
291 | currentFrame = lastFrame - 1;
292 | }
293 | } else if (playbackMode === BACKANDFORTH) {
294 | currentFrame += playbackDirection; // Go to the next frame
295 | if (currentFrame >= lastFrame - 1 || currentFrame <= firstFrame) {
296 | playbackDirection *= -1;
297 | if (currentFrame >= lastFrame - 1) { currentFrame = lastFrame - 1 }
298 | if (currentFrame <= firstFrame) { currentFrame = firstFrame }
299 | }
300 | }
301 | lastTime = sketch.millis();
302 | }
303 | }
304 | };
305 |
306 | // Define an abstract pointer device
307 | function pointerPressed (e) {
308 |
309 | // Cancel event if not clicking inside a sketch.
310 | if (e.target !== canvas.elt && e.target !== timelineCanvas.elt) {
311 | return
312 | }
313 |
314 | // If click in animation area
315 | if (sketch.mouseX > 0 && sketch.mouseX < sketch.width && sketch.mouseY > 0 && sketch.mouseY < sketch.height) {
316 | startDrawing = true;
317 | pastLines.forEach((line) => { line.lastLineFrames = [] });
318 | pastPoints.forEach((point) => { point.lastPointFrames = [] });
319 | pmx = Math.floor((sketch.mouseX - surfaceBorder) / frameSurfaceRatio);
320 | pmy = Math.floor((sketch.mouseY - surfaceBorder) / frameSurfaceRatio);
321 | ppmx = pmx;
322 | ppmy = pmy;
323 | if (smoothing) {
324 | mx = pmx;
325 | my = pmy;
326 | }
327 | }
328 | }
329 |
330 | function pointerReleased() {
331 | startDrawing = false;
332 | arrowLock = false;
333 | selectFirstFrame = false;
334 | selectLastFrame = false;
335 | timelineRangeLock = false;
336 | writeMarkersIntoFrames();
337 | }
338 |
339 | sketch.mousePressed = (e) => { pointerPressed(e) };
340 | sketch.mouseReleased = () => { pointerReleased() };
341 |
342 | sketch.touchStarted = (e) => { pointerPressed(e) };
343 |
344 | sketch.touchMoved = (e) => {
345 |
346 | if (e.touches && e.touches.length === 2) {
347 | e.preventDefault()
348 | }
349 |
350 | if (startDrawing && e.touches && e.touches.length === 1) { // Prevent pan gesture on mobile
351 | e.preventDefault();
352 | }
353 | };
354 |
355 | sketch.touchReleased = () => { pointerReleased() };
356 | });
357 |
358 | const timelineSketch = new p5(function (sketch) {
359 | sketch.setup = function () {
360 | sketch.pixelDensity(window.devicePixelRatio);
361 | timelineCanvas = sketch.createCanvas(frameDim, 110);
362 | timelineCanvas.id('timeline');
363 | sketchContainer.append(timelineCanvas.elt);
364 | sketch.noSmooth();
365 | }
366 |
367 | sketch.draw = function () {
368 | if (!pause || sketch.mouseIsPressed) {
369 | displayTimeline(sketch)
370 | }
371 | }
372 |
373 | sketch.mouseMoved = function (e) {
374 | if (timelineCanvas && e.target !== timelineCanvas.elt) { return }
375 | if (sketch.mouseX > 0 && sketch.mouseX < sketch.width && sketch.mouseY > 0 && sketch.mouseY < sketch.height) {
376 | displayTimeline(sketch)
377 | }
378 | }
379 |
380 | sketch.mouseDragged = function (e) {
381 | if (sketch.mouseX > 0 && sketch.mouseX < sketch.width && sketch.mouseY > 0 && sketch.mouseY < sketch.height) {
382 | displayFrame(animationSketch)
383 | }
384 | }
385 |
386 | sketch.mousePressed = function (e) {
387 | if (sketch.mouseX > 0 && sketch.mouseX < sketch.width && sketch.mouseY > 0 && sketch.mouseY < sketch.height) {
388 | displayFrame(animationSketch)
389 | }
390 | }
391 |
392 | });
393 |
394 | function displayFrame (sketch) {
395 | canvas.drawingContext.clearRect(0, 0, sketch.width, sketch.height);
396 | if (backgroundEnabled === true) {
397 | backgroundFrame.background(backgroundColor);
398 | sketch.drawingContext.drawImage(backgroundFrame.canvas, 0, 0, frameDim, frameDim)
399 | }
400 | sketch.drawingContext.drawImage(frames[currentFrame].canvas, 0, 0, frameDim, frameDim);
401 | for (let i = numMarkerFrames-1; i >= 0; i--) {
402 | sketch.drawingContext.drawImage(markerFrames[i].canvas, 0, 0, frameDim, frameDim);
403 | }
404 |
405 | if (pause && onionSkin) {
406 | sketch.drawingContext.globalAlpha = 0.5;
407 | if (currentFrame > firstFrame) {
408 | sketch.drawingContext.drawImage(frames[currentFrame - 1].canvas, 0, 0, frameDim, frameDim);
409 | } else if (currentFrame === firstFrame) {
410 | sketch.drawingContext.drawImage(frames[lastFrame - 1].canvas, 0, 0, frameDim, frameDim);
411 | }
412 | sketch.drawingContext.globalAlpha = 1.0
413 | }
414 | }
415 |
416 | function writeMarkersIntoFrames () {
417 | // Composite each layer into one, then erase in turn
418 | for (let i = numMarkerFrames - 1; i >= 0; i--) {
419 | if (markers[i]) {
420 | compositeFrame.drawingContext.drawImage(markerFrames[i].canvas, 0, 0, surfaceDim, surfaceDim)
421 | markerFrames[i].drawingContext.clearRect(0, 0, surfaceDim, surfaceDim)
422 | }
423 | }
424 |
425 | // Write all "marker frames" composites into the selected frames
426 | for (let i = firstFrame; i < lastFrame; i++) {
427 | if (onFrame[i] || i === currentFrame) {
428 | frames[i].drawingContext.drawImage(compositeFrame.canvas, 0, 0, surfaceDim, surfaceDim)
429 | }
430 | }
431 | compositeFrame.drawingContext.clearRect(0, 0, surfaceDim, surfaceDim);
432 | }
433 |
434 | function eraseFrame() {
435 | if (window.confirm("Clear this frame?")) {
436 | frames[currentFrame].clear();
437 | displayFrame(animationSketch)
438 | }
439 | }
440 |
441 | function eraseAllFrames() {
442 | if (window.confirm("Are you sure you want to clear everything?")) {
443 | for (let i = 0; i < numFrames; i++) {
444 | frames[i].clear();
445 | }
446 | displayFrame(animationSketch)
447 | }
448 | }
449 |
450 | function eraseSelectedFrames() {
451 | if (window.confirm("Are you sure you want to clear the selected frames?")) {
452 | frames[currentFrame].clear();
453 | for (let i = 0; i < numFrames; i++) {
454 | if (onFrame[i]) {
455 | frames[i].clear();
456 | }
457 | }
458 | displayFrame(animationSketch)
459 | }
460 | }
461 |
462 | window.addEventListener('keydown', (e) => {
463 |
464 | if(e.altKey || e.shiftKey) {
465 | return
466 | }
467 |
468 | if (e.key === 'f' || e.key === 'F') {
469 | eraseFrame();
470 | }
471 |
472 | if (e.key === 'a' || e.key === 'A') {
473 | eraseAllFrames();
474 | }
475 |
476 | if (e.key === 's' || e.key === 'S') {
477 | eraseSelectedFrames();
478 | }
479 |
480 | if (e.key === 'p' || e.key === 'P') {
481 | clickPlay();
482 | }
483 |
484 | if (e.keyCode === 37) { // Left arrow
485 | if (!pause) { clickPlay() } else {
486 | clickBack()
487 | }
488 | e.preventDefault();
489 | }
490 | if (e.keyCode === 39) { // Right arrow
491 | if (!pause) { clickPlay() } else {
492 | clickNext()
493 | }
494 | e.preventDefault();
495 | }
496 |
497 | /*
498 | if (e.key === 'u' || e.key === 'U') {
499 | onFrame.fill(false);
500 | }
501 | if (e.key === 'a' || e.key === 'A') {
502 | onFrame.fill(true);
503 | }
504 | */
505 | })
506 |
507 | function calculateThickness(t) {
508 | return Math.max(1, Math.floor(parseInt(t) / frameSurfaceRatio))
509 | }
510 |
511 | // GIF Export
512 | const exportButton = document.getElementById('export-button');
513 | const exportOverlay = document.getElementById('export-overlay');
514 | const exportedGIFSpinner = document.getElementById('exported-gif-spinner');
515 | const exportedGIFImg = document.getElementById('exported-gif-img');
516 | const exportControls = document.getElementById('export-controls');
517 | const exportProgress = document.getElementById('export-progress');
518 |
519 | function renderFrameGIF (gif, i) {
520 | if (backgroundEnabled === true) {
521 | exportFrame.drawingContext.drawImage(backgroundFrame.canvas, 0, 0, surfaceDim, surfaceDim);
522 | } else {
523 | exportFrame.drawingContext.clearRect(0, 0, surfaceDim, surfaceDim);
524 | }
525 | exportFrame.drawingContext.drawImage(frames[i].canvas, 0, 0, surfaceDim, surfaceDim);
526 | gif.addFrame(exportFrame.canvas, {delay: timeStep, copy: true});
527 | }
528 |
529 | let checkInterval;
530 | let gifBlob;
531 |
532 | function exportGIF () {
533 | if (didDrawAnything === false) {
534 | alert("You haven't drawn anything to export.");
535 | return
536 | }
537 |
538 | if(!pause) { clickPlay() }
539 |
540 | exportOverlay.classList.add('active');
541 | exportButton.classList.add('active');
542 | document.body.classList.add('noscroll');
543 |
544 | let isTransparent = null;
545 | if (backgroundEnabled === false) { isTransparent = 0x000000 }
546 |
547 | const gif = new GIF({
548 | workers: 4,
549 | quality: 10,
550 | background: backgroundColor,
551 | transparent: isTransparent,
552 | workerScript: "vendor/gif.worker.js"
553 | });
554 |
555 | switch(playbackMode) {
556 | case FORWARD:
557 | for (let i = firstFrame; i < lastFrame; i += 1) { renderFrameGIF(gif, i) }
558 | break;
559 | case REVERSE:
560 | for (let i = lastFrame - 1; i > firstFrame; i -= 1) { renderFrameGIF(gif, i) }
561 | break;
562 | case BACKANDFORTH:
563 | for (let i = firstFrame; i < lastFrame; i += 1) { renderFrameGIF(gif, i) }
564 | for (let i = lastFrame - 2; i >= firstFrame + 1; i -= 1) { renderFrameGIF(gif, i) }
565 | break;
566 | }
567 |
568 | let totalFrames = gif.frames.length;
569 |
570 | checkInterval = window.setInterval(() => {
571 | let progress = gif.finishedFrames / totalFrames;
572 | exportedGIFSpinner.style.borderRadius = ((1 - progress) * 50) + '%';
573 | exportProgress.innerText = Math.round(progress * 100) + '%'
574 | }, 150);
575 |
576 | gif.on('finished', function(blob) {
577 | if (exportOverlay.classList.contains('active')) {
578 | window.clearInterval(checkInterval);
579 | exportedGIFImg.onload = function () {
580 | exportedGIFImg.classList.add('active');
581 | exportControls.classList.add('active');
582 | exportedGIFSpinner.classList.add('hidden')
583 | }
584 | gifBlob = blob;
585 | exportedGIFImg.src = URL.createObjectURL(blob);
586 | }
587 | });
588 |
589 | gif.render()
590 | }
591 |
592 | exportOverlay.onclick = (e) => {
593 | if (exportedGIFImg.classList.contains('active') === false) {
594 | cancelOrCloseGIF()
595 | }
596 | }
597 |
598 | function cancelOrCloseGIF () {
599 | window.clearInterval(checkInterval);
600 | gifBlob = undefined;
601 | exportedGIFSpinner.removeAttribute('style');
602 | exportProgress.innerText = '0%';
603 | exportOverlay.classList.remove('active');
604 | exportButton.classList.remove('active');
605 | exportedGIFSpinner.classList.remove('hidden');
606 | exportedGIFImg.classList.remove('active');
607 | exportControls.classList.remove('active');
608 | document.body.classList.remove('noscroll');
609 | exportUploadToGiphy.classList.remove('hidden');
610 | giphyUploadOverlay.classList.remove('active');
611 | }
612 |
613 | const exportUploadToGiphy = document.getElementById('export-upload-to-giphy');
614 | const giphyUploadOverlay = document.getElementById('giphy-upload-overlay');
615 |
616 | function uploadToGiphy () {
617 |
618 | exportUploadToGiphy.classList.add('hidden');
619 | giphyUploadOverlay.classList.add('active');
620 |
621 | let username = 'sketchmachine';
622 | let apiKey = 'ul0HNk8uS4G92IEFad7Y9XabW2pOBfK9';
623 |
624 | let formData = new FormData();
625 | formData.append('file', gifBlob, 'sketchmachine.gif');
626 | formData.append('username', username);
627 | formData.append('api_key', apiKey);
628 | formData.append('tags', 'sketchmachine');
629 | formData.append('source_post_url', 'https://sketchmachine.net');
630 |
631 | fetch('https://upload.giphy.com/v1/gifs', {
632 | method: 'POST',
633 | body: formData,
634 | mode: 'cors'
635 | }).then((response) => {
636 | return response.json()
637 | })
638 | .catch((error) => { console.error('Error:', error) })
639 | .then((res) => {
640 | if (res.meta && res.meta.status === 200) {
641 | let id = res.data.id;
642 | let url = 'https://giphy.com/gifs/' + username + '-' + id;
643 | window.open(url);
644 | giphyUploadOverlay.classList.remove('active');
645 | } else {
646 | console.error('Upload failed.');
647 | exportUploadToGiphy.classList.remove('hidden');
648 | giphyUploadOverlay.classList.remove('active');
649 | }
650 | });
651 | }
652 |
653 | // Leave page warning
654 | window.onbeforeunload = (e) => {
655 | if (didDrawAnything) {
656 | return true
657 | }
658 | }
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | html {
2 | touch-action: manipulation;
3 | }
4 |
5 | body {
6 | margin: 0;
7 | background-color: #CCC;
8 | user-select: none;
9 | -webkit-text-size-adjust: none;
10 | }
11 |
12 | body::after {
13 | position:absolute; width:0; height:0; overflow:hidden; z-index:-1;
14 | content:url(play.svg);
15 | }
16 |
17 | body.noscroll {
18 | height: 100%;
19 | overflow: hidden;
20 | }
21 |
22 | /* Override p5.js sizing to fit viewport. */
23 | #animation, #animation-dummy {
24 | width: calc(100% - 8px) !important;
25 | height: auto !important;
26 | cursor: crosshair;
27 | background: url(checkers.svg);
28 | background-size: 16px;
29 | border: 4px inset;
30 | }
31 |
32 | #timeline {
33 | margin-left: 4px;
34 | max-width: 512px;
35 | width: calc(100% - 8px) !important;
36 | height: auto !important;
37 | }
38 |
39 | input, button {
40 | -webkit-tap-highlight-color: rgba(0,0,0,0);
41 | }
42 |
43 | input[type="button"], button {
44 | -webkit-appearance: none;
45 | }
46 |
47 | #color-selector {
48 | width: 100%;
49 | height: 100%;
50 | background-color: black;
51 | display: none;
52 | position: fixed;
53 | top: 0;
54 | left: 0;
55 | }
56 |
57 | #colors {
58 | font-size: 0;
59 | height: 100%;
60 | overflow-y: scroll;
61 | -webkit-overflow-scrolling: touch;
62 | }
63 |
64 | #colors button {
65 | width: 12.5%;
66 | padding: 12.5% 0 0 0;
67 | border: 0;
68 | /*outline: 0;*/
69 | cursor: pointer;
70 | margin: 0;
71 | }
72 |
73 | #colors button:active {
74 | filter: invert(1);
75 | }
76 |
77 | #color-selector.active {
78 | display: block;
79 | }
80 |
81 | #background-color-selector.active {
82 | display: block;
83 | }
84 |
85 | #ui {
86 | padding: 21px 10px 10px 10px;
87 | box-sizing: border-box;
88 | margin: auto;
89 | display: flex;
90 | flex-direction: column;
91 | position: relative;
92 | user-select: none;
93 | }
94 |
95 | #column {
96 | max-width: 400px;
97 | /*background-color: pink;*/
98 | }
99 |
100 | #macro-ui {
101 | margin: auto;
102 | display: flex;
103 | flex-direction: column;
104 | position: relative;
105 | }
106 |
107 | #etc {
108 | font-family: monospace;
109 | font-size: 12px;
110 | color: #666666;
111 | box-sizing: border-box;
112 | width: 100%;
113 | display: flex;
114 | flex-direction: column;
115 | position: relative;
116 | max-width: 520px;
117 | margin: auto;
118 | }
119 |
120 | footer {
121 | padding: 10px;
122 | }
123 |
124 | #etc a, #etc a:visited {
125 | color: #666666;
126 | }
127 |
128 | .current-resolution {
129 | color: #111 !important;
130 | }
131 |
132 | #fine-print, #fine-print a, #fine-print a:visited {
133 | /*color: #AAA;*/
134 | }
135 |
136 | hr {
137 | margin-top: 10px;
138 | margin-bottom: 0px;
139 | width: 100%;
140 | border-left: none;
141 | border-right: none;
142 | }
143 |
144 | #sketch-container {
145 | position: relative;
146 | max-width: 520px;
147 | max-height: 680px; /*662*/
148 | display: flex;
149 | flex-direction: column;
150 | }
151 |
152 | #buttons {
153 | height: 50px;
154 | width: 100%;
155 | padding-top: 4px;
156 | margin: 2px auto 0 auto;
157 | display: flex;
158 | vertical-align: middle;
159 | }
160 |
161 | #playback-buttons {
162 | margin-left: 4px;
163 | height: 50px;
164 | }
165 |
166 | #playback-buttons input {
167 | cursor: pointer;
168 | }
169 |
170 | .colorButton {
171 | width: 24px;
172 | height: 24px;
173 | color: white;
174 | padding: 2px 2px 2px 2px;
175 | display: inline-block;
176 | border-radius: 50%;
177 | border: 1px solid #FFFFFF;
178 | cursor: pointer;
179 | }
180 |
181 | .play {
182 | background: url("pause.svg") no-repeat top left;
183 | background-size: contain;
184 | width: 50px;
185 | height: 50px;
186 | display: inline-block;
187 | border-radius: 50%;
188 | border: 1px inset;
189 | vertical-align: middle;
190 | line-height: 50px;
191 | }
192 |
193 | .back-next {
194 | background: url("next.svg") no-repeat top left;
195 | background-size: contain;
196 | background-color: #333333;
197 | width: 32px;
198 | height: 32px;
199 | color: #FFFFFF;
200 | padding: 3px 2px 2px 2px;
201 | display: inline-block;
202 | border: 1px inset;
203 | font-size: 1.25em;
204 | vertical-align: middle;
205 | }
206 |
207 | #buttons input {
208 | outline: 0;
209 | }
210 |
211 | #buttons input:active {
212 | background-color: #555;
213 | }
214 |
215 | #keyboard-shortcuts {
216 | padding: 0;
217 | list-style-type: none;
218 | }
219 |
220 | #keyboard-shortcuts li {
221 | line-height: 1.75em;
222 | display: inline-block;
223 | padding-bottom: 0.5em;
224 | }
225 |
226 | .keyboard-shortcut {
227 | min-width: 1em;
228 | height: 1.5em;
229 | display: inline-block;
230 | font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
231 | background-color: #eee;
232 | border: 0.25em outset #c3c3c3;
233 | padding: 0 0.2em;
234 | vertical-align: middle;
235 | text-align: center;
236 | color: grey;
237 | }
238 |
239 | .mirror {
240 | display: inline-block;
241 | transform: scaleX(-1);
242 | }
243 |
244 | #select-buttons {
245 | margin-left: auto;
246 | height: 50px;
247 | display: flex;
248 | margin-right: 4px;
249 | }
250 |
251 | #select-buttons input {
252 | margin: auto;
253 | height: 32px;
254 | margin-left: 4px;
255 | cursor: pointer;
256 | }
257 |
258 | .selector {
259 | display: inline-block;
260 | font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
261 | letter-spacing: 0.5px;
262 | font-size: 0.7em;
263 | background-color: #333333;
264 | color: #FFFFFF;
265 | height: 32px;
266 | border: 1px inset;
267 | box-sizing: border-box;
268 | padding: 0 8px 0 4px;
269 | margin: auto;
270 | }
271 |
272 | .clear-frames {
273 | display: inline-block;
274 | font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
275 | letter-spacing: 0.5px;
276 | font-size: 0.7em;
277 | background-color: #333333;
278 | color: #FFFFFF;
279 | vertical-align: middle;
280 | border: 1px inset;
281 | padding: 2px 10px 2px 10px;
282 | }
283 |
284 | .selector input, .selector .selector-label, .selector label {
285 | vertical-align: middle;
286 | }
287 |
288 | select option {
289 | font-family: monospace;
290 | }
291 |
292 | .sm-title {
293 | font-weight: normal;
294 | font-size: 1.3em;
295 | margin: 0;
296 | padding-left: 0;
297 | display: inline-block;
298 | }
299 |
300 | .sketch-title {
301 | font-family: "Times New Roman", Times, serif;
302 | }
303 |
304 | .machine-title {
305 | font-family: monospace;
306 | font-size: 1.25em;
307 | }
308 |
309 | #controls {
310 | margin-top: 1.5em;
311 | font-family: "Times New Roman", Times, serif;
312 | color: #333;
313 | }
314 |
315 | #controls h2 {
316 | font-size: 1em;
317 | font-weight: normal;
318 | color: #777;
319 | margin: 0 0 6px 0;
320 | }
321 |
322 | .brush, .setting {
323 | background-color: #BBB;
324 | display: inline-block;
325 | /*border-radius: 4px;*/
326 | margin-bottom: 5px;
327 | border: 1px inset;
328 | vertical-align: middle;
329 | height: 30px; /*40*/
330 | line-height: 32px; /*40*/
331 | padding-left: 6px;
332 | padding-right: 11px;
333 | padding-bottom: 2px;
334 | }
335 |
336 | .brush input, .brush select, .setting input, .setting .setting-label, .setting label {
337 | vertical-align: middle;
338 | }
339 |
340 | #engorge-label, #onion-label, #background-label, #select-label {
341 | cursor: pointer;
342 | }
343 |
344 | #select-label {
345 | padding-left: 4px;
346 | }
347 |
348 | #background-label {
349 | padding-right: 4px;
350 | }
351 |
352 | #tools {
353 | /*margin-top: 0.75em;*/
354 | margin-bottom: 0.75em;
355 | }
356 |
357 | #tool-modifiers {
358 | margin-bottom: 0.75em;
359 | }
360 |
361 | #options {
362 | margin-bottom: 0.75em;
363 | }
364 |
365 | #playback-options label {
366 | padding: 3px;
367 | color: #666;
368 | display: inline-block;
369 | cursor: pointer;
370 | font-size: 1.25em;
371 | line-height: 18px;
372 | height: 20px;
373 | width: 20px;
374 | background: #999;
375 | }
376 |
377 |
378 | #playback-options .setting-label {
379 | padding-right: 4px;
380 | }
381 |
382 | #playback-options {
383 | padding-right: 6px;
384 | }
385 |
386 | #playback-options input[type="radio"] {
387 | display: none;
388 | }
389 |
390 | #playback-options input[type="radio"]:checked+label {
391 | color: #222;
392 | background: #eee;
393 | }
394 |
395 | #export-options {
396 | background-color: #BBB;
397 | padding: 8px;
398 | display: inline-block;
399 | border: 1px inset;
400 | }
401 |
402 | #export-overlay {
403 | position: fixed;
404 | z-index: 1;
405 | top: 0;
406 | left: 0;
407 | width: 100%;
408 | height: 100%;
409 | background-color: rgba(0, 0, 0, 0.75);
410 | visibility: hidden;
411 | display: flex;
412 | overflow-y: scroll;
413 | -webkit-overflow-scrolling: touch;
414 | }
415 |
416 | #export-overlay.active,
417 | #exported-gif-img.active,
418 | #export-controls.active {
419 | visibility: visible;
420 | }
421 |
422 | #exported-gif {
423 | margin: auto;
424 | }
425 |
426 | #exported-gif-spinner {
427 | position: absolute;
428 | top: 50%;
429 | left: 50%;
430 | width: 64px;
431 | height: 64px;
432 | margin-top: -32px;
433 | margin-left: -32px;
434 | background: white;
435 | border-radius: 50%;
436 | animation: spinner 1s infinite;
437 | pointer-events: none;
438 | transition: 0.5s border-radius;
439 | display: flex;
440 | }
441 |
442 | #export-progress {
443 | font-family: monospace;
444 | font-size: 1.5em;
445 | margin: auto;
446 | }
447 |
448 | #exported-gif-spinner.hidden {
449 | display: none;
450 | }
451 |
452 | @keyframes spinner {
453 | 0% {
454 | transform: scale(1);
455 | }
456 | 50% {
457 | transform: scale(1.5);
458 | }
459 | 100% {
460 | transform: scale(1);
461 | }
462 | }
463 |
464 | #exported-gif-img {
465 | margin: 8px;
466 | max-width: calc(100% - 24px);
467 | width: 512px;
468 | border: 4px outset;
469 | visibility: hidden;
470 | cursor: context-menu;
471 | image-rendering: pixelated;
472 | background: url(checkers.svg);
473 | background-size: 16px;
474 | }
475 |
476 | #export-controls {
477 | display: flex;
478 | visibility: hidden;
479 | margin: 0px 8px 8px 8px;
480 | }
481 |
482 | #export-upload-to-giphy {
483 | margin-left: auto;
484 | }
485 |
486 | #export-upload-to-giphy.hidden {
487 | visibility: hidden;
488 | }
489 |
490 | #export-upload-to-giphy, #export-close {
491 | display: block;
492 | font-size: 1.25em;
493 | color: black;
494 | font-weight: bold;
495 | background-color: #ddd;
496 | padding: 6px 8px 4px 8px;
497 | text-transform: uppercase;
498 | border: 4px outset;
499 | cursor: pointer;
500 | outline: none;
501 | }
502 |
503 | #export-upload-to-giphy:active, #export-close:active {
504 | border: 4px inset;
505 | padding: 7px 7px 3px 9px;
506 | background-color: #bbb;
507 | }
508 |
509 | #giphy-upload-overlay {
510 | visibility: hidden;
511 | z-index: 2;
512 | position: fixed;
513 | top: 0;
514 | left: 0;
515 | width: 100%;
516 | height: 100%;
517 | background: rgba(0, 0, 0, 0.8);
518 | }
519 |
520 | #giphy-upload-overlay.active {
521 | visibility: visible;
522 | }
523 |
524 | #giphy-logo {
525 | width: 100px;
526 | vertical-align: top;
527 | padding-left: 4px;
528 | padding-top: 1px;
529 | }
530 |
531 | #export-button {
532 | display: block;
533 | width: 77px;
534 | height: 77px;
535 | font-size: 1.5em;
536 | font-weight: bold;
537 | background-color: #dcdcdc;
538 | outline: none;
539 | color: #555;
540 | cursor: pointer;
541 | border: 0;
542 | border-radius: 0;
543 | font-family: "Times New Roman", Times, serif;
544 | padding: 0;
545 | }
546 |
547 | #export-button:active, #export-button.active {
548 | background-color: #aaa;
549 | color: #888;
550 | }
551 |
552 | #speed-slider {
553 | }
554 |
555 | #next {
556 | visibility: hidden;
557 | }
558 |
559 | #back {
560 | visibility: hidden;
561 | }
562 |
563 | #giphy-arts-logo {
564 | width: 100px;
565 | filter: grayscale(1);
566 | transition: 0.25s filter;
567 | }
568 |
569 | #giphy-arts-logo:hover, #giphy-arts-logo:active {
570 | filter: none;
571 | }
572 |
573 | @media all and (min-width: 940px) {
574 | body {
575 | }
576 |
577 | #ui {
578 | flex-direction: row;
579 | }
580 |
581 | #etc {
582 | flex-direction: column;
583 | padding: 10px;
584 | margin-left: 0;
585 | }
586 |
587 | #controls {
588 | padding-top: 0px;
589 | padding-left: 40px; /*16*/
590 | margin-top: 0;
591 | }
592 |
593 | h1 {
594 | padding-left: 10px;
595 | }
596 |
597 | #colors button {
598 | width: 6.25%;
599 | padding: 6.25% 0 0 0;
600 | border: 0;
601 | /*outline: 0;*/
602 | cursor: pointer;
603 | margin: 0;
604 | }
605 | }
606 |
607 | .dev * {
608 | background-color: rgba(0, 0, 255, 0.1);
609 | }
--------------------------------------------------------------------------------
/src/timeline.js:
--------------------------------------------------------------------------------
1 | let selectLastFrame = false;
2 | let selectFirstFrame = false;
3 | let masterSelect = false;
4 |
5 | function selectRange (sketch) {
6 |
7 | masterSelect = !masterSelect;
8 |
9 | if (!masterSelect) {
10 | // First, deselect all
11 | deselect();
12 | if (pause) {
13 | displayTimeline(timelineSketch);
14 | }
15 | } else {
16 | // First, deselect all
17 | deselect();
18 | // Second, select the new range
19 | for (let i = firstFrame; i < lastFrame; i++) {
20 | onFrame[i] = true;
21 | }
22 | if (pause) {
23 | displayTimeline(timelineSketch);
24 | }
25 | }
26 | }
27 |
28 | function deselect (sketch) {
29 | for (let i = 0; i < numFrames; i++) {
30 | onFrame[i] = false;
31 | }
32 | }
33 |
34 | function manageSelection (sketch) {
35 | if (masterSelect) {
36 | deselect();
37 | for (let i = firstFrame; i < lastFrame; i++) {
38 | onFrame[i] = true;
39 | }
40 | }
41 | }
42 |
43 |
44 | function displayTimeline (sketch) {
45 |
46 | sketch.drawingContext.clearRect(0, 0, sketch.width, sketch.height);
47 |
48 | let tw = frameDim / numFrames;
49 | let ty = 16; // Gap from the top of the canvas
50 | let tlh = 40; // Height of the time line -- Increased for better touch on mobile
51 | let th = 40; // Height of the selection arrows
52 |
53 | let tx = sketch.map(currentFrame, 0, numFrames, 0, sketch.width);
54 |
55 | if (!startDrawing) {
56 | for (let x = firstFrame; x < lastFrame; x++) {
57 | let xx = sketch.map(x, 0, numFrames, 0, sketch.width);
58 | if ((sketch.mouseX > xx && sketch.mouseX < xx + tw && sketch.mouseY > ty && sketch.mouseY < ty + tlh)) {
59 | if (sketch.mouseIsPressed && sketch.mouseY >= ty && sketch.mouseY <= ty + tlh && !selectFirstFrame && !selectLastFrame && !timelineRangeLock) {
60 | if (!pause) {
61 | clickPlay()
62 | }
63 | currentFrame = x;
64 | displayFrame(animationSketch);
65 | arrowLock = true;
66 | }
67 | }
68 | }
69 | }
70 |
71 | // DRAW FRAMES THAT ARE "ON", THAT ARE CURRENTLY BEING DRAWING INTO
72 | for (let i = firstFrame; i < lastFrame; i++) {
73 | if (onFrame[i]) {
74 | let tempx = sketch.map(i, 0, numFrames, 0, sketch.width);
75 | sketch.noStroke();
76 | //sketch.fill(126, 126, 126);
77 | sketch.fill(0, 0, 255);
78 | sketch.rect(tempx, ty, tw, tlh + 1);
79 | }
80 | }
81 |
82 | // CURRENT FRAME MARKER IN BRIGHT BLUE
83 | sketch.noStroke();
84 | //if (!masterSelect) {
85 | sketch.fill(0, 0, 255);
86 | //} else {
87 | // sketch.fill(255);
88 | //}
89 | sketch.rect(tx, ty, tw, tlh+1);
90 |
91 | // RANGE OF FRAMES, FIRST TO LAST
92 | let tty = ty+tlh+6;
93 | //let tty = ty;
94 | let ffx = firstFrame * tw;
95 | let lfx = (lastFrame - 1) * tw;
96 |
97 | // CONNECT IN AND OUT MARKERS
98 | sketch.stroke(102);
99 | sketch.line(firstFrame * tw + tw - 1, tty + th / 2, (lastFrame - 1) * tw, tty + th / 2);
100 |
101 | // IN MARKER
102 | sketch.fill(51); // Default color overwritten with blue if mouse is over
103 | sketch.noStroke();
104 | if (sketch.mouseX > ffx && sketch.mouseX < ffx + tw && sketch.mouseY > tty && sketch.mouseY < tty + th) {
105 | if (!startDrawing) {
106 | if (sketch.mouseIsPressed && !selectLastFrame && !arrowLock && !timelineRangeLock) {
107 | selectFirstFrame = true; // Goes "false" in mouseReleased
108 | //
109 | }
110 | if (!selectLastFrame && !arrowLock) {
111 | sketch.fill(0, 0, 255);
112 | }
113 | }
114 | }
115 | if (selectFirstFrame) {
116 | firstFrame = sketch.floor(sketch.mouseX / tw);
117 | firstFrame = sketch.constrain(firstFrame, 0, lastFrame - 2);
118 | if (currentFrame < firstFrame) {
119 | currentFrame = firstFrame;
120 | }
121 | sketch.fill(0, 0, 255);
122 | manageSelection();
123 | }
124 | sketch.triangle(firstFrame * tw, tty, firstFrame * tw, tty + th, (firstFrame + 1) * tw, tty + th / 2);
125 |
126 | // OUT MARKER
127 | sketch.fill(51);
128 | if (sketch.mouseX > lfx && sketch.mouseX < lfx + tw && sketch.mouseY > tty && sketch.mouseY < tty + th) {
129 | if (!startDrawing) {
130 | if (sketch.mouseIsPressed && !selectFirstFrame && !arrowLock && !timelineRangeLock) {
131 | selectLastFrame = true; // Goes "false" in mouseReleased
132 | }
133 | if (!selectFirstFrame && !arrowLock){
134 | sketch.fill(0, 0, 255);
135 | }
136 | }
137 | }
138 | if (selectLastFrame) {
139 | lastFrame = sketch.ceil(sketch.mouseX / tw);
140 | lastFrame = sketch.constrain(lastFrame, firstFrame + 2, numFrames);
141 | if (currentFrame > lastFrame - 1) {
142 | currentFrame = lastFrame - 1;
143 | }
144 | sketch.fill(0, 0, 255);
145 | manageSelection();
146 | }
147 | sketch.triangle(lastFrame * tw, tty, lastFrame * tw, tty + th, (lastFrame - 1) * tw, tty + th / 2);
148 |
149 | // BETWEEN THE IN AND OUT MARKER
150 | if (sketch.mouseX > ffx + tw && sketch.mouseX < lfx && sketch.mouseY > tty && sketch.mouseY < tty + th && !selectFirstFrame && !selectLastFrame) {
151 | if (!startDrawing && !arrowLock) {
152 | timelineRangeSelected = true;
153 | if (sketch.mouseIsPressed && !timelineRangeLock) {
154 | timelineRangeLock = true;
155 | let currentX = sketch.ceil(sketch.map(sketch.mouseX, 0, sketch.width, 0, numFrames));
156 | numToLeft = currentX-firstFrame;
157 | numToRight = lastFrame-currentX;
158 | }
159 | }
160 | } else {
161 | timelineRangeSelected = false;
162 | }
163 |
164 | if (timelineRangeSelected || timelineRangeLock) {
165 | sketch.fill(153, 153, 153);
166 | sketch.noStroke();
167 | sketch.rect(ffx, tty, lfx-ffx+tw, th);
168 | // Hack to draw the blue arrows on top, as well as the connecting line
169 | sketch.fill(0, 0, 255);
170 | sketch.triangle(lastFrame * tw, tty, lastFrame * tw, tty + th, (lastFrame - 1) * tw, tty + th / 2);
171 | sketch.triangle(firstFrame * tw, tty, firstFrame * tw, tty + th, (firstFrame + 1) * tw, tty + th / 2);
172 | sketch.stroke(102);
173 | sketch.line(firstFrame * tw + tw - 1, tty + th / 2, (lastFrame - 1) * tw, tty + th / 2);
174 | }
175 |
176 | // Calculate the "range" change if it's selected and locked
177 | if (timelineRangeLock) {
178 |
179 | //manageSelection();
180 |
181 | let currentX = sketch.ceil(sketch.map(sketch.mouseX, 0, sketch.width, 0, numFrames));
182 | let newX = sketch.map(currentX, 0, numFrames, 0, sketch.width);
183 |
184 | firstFrame = currentX - numToLeft;
185 | lastFrame = currentX + numToRight;
186 |
187 | manageSelection();
188 |
189 | firstFrame = sketch.constrain(firstFrame, 0, numFrames-2);
190 | lastFrame = sketch.constrain(lastFrame, 2, numFrames);
191 |
192 | if (currentFrame < firstFrame) {
193 | currentFrame = firstFrame;
194 | }
195 |
196 | if (currentFrame > lastFrame-1) {
197 | currentFrame = lastFrame-1;
198 | }
199 |
200 | //sketch.print(currentX, firstFrame, lastFrame, numToLeft, numToRight);
201 |
202 | // Comment this bit out when it's working
203 | //sketch.stroke(255, 0, 0);
204 | //sketch.line(sketch.mouseX, 0, sketch.mouseX, sketch.height);
205 | //sketch.stroke(0, 255, 0);
206 | //sketch.line(newX, 0, newX, sketch.height);
207 | }
208 |
209 | // TICK MARKS, THE GRID OF FRAMES
210 | let tickMiddle = ty+tlh/2;
211 | for (let x = 0; x <= numFrames; x++) {
212 |
213 | let xx = sketch.map(x, 0, numFrames, 0, sketch.width);
214 | if (x === numFrames) {
215 | xx = xx-1;
216 | }
217 | if (x < firstFrame || x > lastFrame) {
218 | sketch.stroke(102);
219 | sketch.line(xx, tickMiddle-2, xx, tickMiddle+2);
220 | } else {
221 | sketch.stroke(0);
222 | sketch.line(xx, ty, xx, ty + tlh);
223 | }
224 | }
225 |
226 | }
227 |
--------------------------------------------------------------------------------
/src/ui.js:
--------------------------------------------------------------------------------
1 | function speedSizeToggle() {
2 | speedSize = !speedSize;
3 | }
4 |
5 | function jitterToggle() {
6 | jitterOn = !jitterOn;
7 | }
8 |
9 | function sluggishToggle() {
10 | smoothing = !smoothing;
11 | }
12 |
13 | function onionToggle() {
14 | onionSkin = !onionSkin;
15 | displayFrame(animationSketch);
16 | }
17 |
18 | function backgroundToggle() {
19 | backgroundEnabled = !backgroundEnabled;
20 | displayFrame(animationSketch);
21 | }
22 |
23 | function openColorSelector (n) {
24 | document.body.classList.add('noscroll');
25 | colorSelector.classList.add('active');
26 | currentColorSelection = n;
27 | }
28 |
29 | function closeColorSelector (e) {
30 | currentColor = e.target.dataset.color
31 |
32 | if (currentColorSelection === 0) {
33 | backgroundColor = currentColor;
34 | displayFrame(animationSketch);
35 | backgroundColorButton.style.backgroundColor = currentColor;
36 | } else if (currentColorSelection === 1) {
37 | marker1Color = currentColor;
38 | marker1ColorButton.style.backgroundColor = marker1Color;
39 | } else if (currentColorSelection === 2) {
40 | marker2Color = currentColor;
41 | marker2ColorButton.style.backgroundColor = marker2Color;
42 | } else if (currentColorSelection === 3) {
43 | marker3Color = currentColor;
44 | marker3ColorButton.style.backgroundColor = marker3Color;
45 | } else if (currentColorSelection === 4) {
46 | marker4Color = currentColor;
47 | marker4ColorButton.style.backgroundColor = marker4Color;
48 | } else if (currentColorSelection === 5) {
49 | marker5Color = currentColor;
50 | marker5ColorButton.style.backgroundColor = marker5Color;
51 | }
52 | colorSelector.classList.remove('active');
53 | document.body.classList.remove('noscroll');
54 | }
55 |
56 | function clickPlay() {
57 | pause = !pause;
58 | if (pause) {
59 | displayFrame(animationSketch)
60 | //document.getElementById("play").value = "▶︎";
61 | document.getElementById("play").style.backgroundImage = "url('play.svg')";
62 | document.getElementById("next").style.visibility = "visible";
63 | document.getElementById("back").style.visibility = "visible";
64 | } else {
65 | //document.getElementById("play").value = "◼︎";
66 | document.getElementById("play").style.backgroundImage = "url('pause.svg')";
67 | document.getElementById("next").style.visibility = "hidden";
68 | document.getElementById("back").style.visibility = "hidden";
69 | if (playbackMode === BACKANDFORTH && currentFrame === lastFrame-1) {
70 | playbackDirection = -1;
71 | }
72 | }
73 | }
74 |
75 | function clickBack() {
76 | currentFrame--;
77 | if (currentFrame < firstFrame) {
78 | currentFrame = lastFrame - 1;
79 | }
80 | displayFrame(animationSketch);
81 | displayTimeline(timelineSketch);
82 | }
83 |
84 | function clickNext() {
85 | currentFrame++;
86 | if (currentFrame >= lastFrame) {
87 | currentFrame = firstFrame;
88 | }
89 | displayFrame(animationSketch);
90 | displayTimeline(timelineSketch);
91 | }
92 |
93 | function clickReverse() {
94 | playbackMode = REVERSE;
95 | }
96 |
97 | function clickForward() {
98 | playbackMode = FORWARD;
99 | }
100 |
101 | function clickBackAndForth() {
102 | if (playbackMode === REVERSE) {
103 | playbackDirection = -1
104 | } else if (playbackMode === FORWARD) {
105 | playbackDirection = 1
106 | }
107 | playbackMode = BACKANDFORTH;
108 | }
109 |
110 | function componentToHex(c) {
111 | let hex = c.toString(16);
112 | return hex.length === 1 ? "0" + hex : hex;
113 | }
114 |
115 | function rgbToHex(r, g, b) {
116 | return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
117 | }
118 |
119 | const web216 = [
120 | '#FFFFFF',
121 | '#000000',
122 | '#333333',
123 | '#666666',
124 | '#999999',
125 | '#CCCCCC',
126 | '#000033',
127 | '#000066',
128 | '#000099',
129 | '#0000CC',
130 | '#0000FF',
131 | '#003300',
132 | '#003333',
133 | '#003366',
134 | '#003399',
135 | '#0033CC',
136 | '#0033FF',
137 | '#006600',
138 | '#006633',
139 | '#006666',
140 | '#006699',
141 | '#0066CC',
142 | '#0066FF',
143 | '#009900',
144 | '#009933',
145 | '#009966',
146 | '#009999',
147 | '#0099CC',
148 | '#0099FF',
149 | '#00CC00',
150 | '#00CC33',
151 | '#00CC66',
152 | '#00CC99',
153 | '#00CCCC',
154 | '#00CCFF',
155 | '#00FF00',
156 | '#00FF33',
157 | '#00FF66',
158 | '#00FF99',
159 | '#00FFCC',
160 | '#00FFFF',
161 | '#330000',
162 | '#330033',
163 | '#330066',
164 | '#330099',
165 | '#3300CC',
166 | '#3300FF',
167 | '#333300',
168 | '#333366',
169 | '#333399',
170 | '#3333CC',
171 | '#3333FF',
172 | '#336600',
173 | '#336633',
174 | '#336666',
175 | '#336699',
176 | '#3366CC',
177 | '#3366FF',
178 | '#339900',
179 | '#339933',
180 | '#339966',
181 | '#339999',
182 | '#3399CC',
183 | '#3399FF',
184 | '#33CC00',
185 | '#33CC33',
186 | '#33CC66',
187 | '#33CC99',
188 | '#33CCCC',
189 | '#33CCFF',
190 | '#33FF00',
191 | '#33FF33',
192 | '#33FF66',
193 | '#33FF99',
194 | '#33FFCC',
195 | '#33FFFF',
196 | '#660000',
197 | '#660033',
198 | '#660066',
199 | '#660099',
200 | '#6600CC',
201 | '#6600FF',
202 | '#663300',
203 | '#663333',
204 | '#663366',
205 | '#663399',
206 | '#6633CC',
207 | '#6633FF',
208 | '#666600',
209 | '#666633',
210 | '#666699',
211 | '#6666CC',
212 | '#6666FF',
213 | '#669900',
214 | '#669933',
215 | '#669966',
216 | '#669999',
217 | '#6699CC',
218 | '#6699FF',
219 | '#66CC00',
220 | '#66CC33',
221 | '#66CC66',
222 | '#66CC99',
223 | '#66CCCC',
224 | '#66CCFF',
225 | '#66FF00',
226 | '#66FF33',
227 | '#66FF66',
228 | '#66FF99',
229 | '#66FFCC',
230 | '#66FFFF',
231 | '#990000',
232 | '#990033',
233 | '#990066',
234 | '#990099',
235 | '#9900CC',
236 | '#9900FF',
237 | '#993300',
238 | '#993333',
239 | '#993366',
240 | '#993399',
241 | '#9933CC',
242 | '#9933FF',
243 | '#996600',
244 | '#996633',
245 | '#996666',
246 | '#996699',
247 | '#9966CC',
248 | '#9966FF',
249 | '#999900',
250 | '#999933',
251 | '#999966',
252 | '#9999CC',
253 | '#9999FF',
254 | '#99CC00',
255 | '#99CC33',
256 | '#99CC66',
257 | '#99CC99',
258 | '#99CCCC',
259 | '#99CCFF',
260 | '#99FF00',
261 | '#99FF33',
262 | '#99FF66',
263 | '#99FF99',
264 | '#99FFCC',
265 | '#99FFFF',
266 | '#CC0000',
267 | '#CC0033',
268 | '#CC0066',
269 | '#CC0099',
270 | '#CC00CC',
271 | '#CC00FF',
272 | '#CC3300',
273 | '#CC3333',
274 | '#CC3366',
275 | '#CC3399',
276 | '#CC33CC',
277 | '#CC33FF',
278 | '#CC6600',
279 | '#CC6633',
280 | '#CC6666',
281 | '#CC6699',
282 | '#CC66CC',
283 | '#CC66FF',
284 | '#CC9900',
285 | '#CC9933',
286 | '#CC9966',
287 | '#CC9999',
288 | '#CC99CC',
289 | '#CC99FF',
290 | '#CCCC00',
291 | '#CCCC33',
292 | '#CCCC66',
293 | '#CCCC99',
294 | '#CCCCFF',
295 | '#CCFF00',
296 | '#CCFF33',
297 | '#CCFF66',
298 | '#CCFF99',
299 | '#CCFFCC',
300 | '#CCFFFF',
301 | '#FF0000',
302 | '#FF0033',
303 | '#FF0066',
304 | '#FF0099',
305 | '#FF00CC',
306 | '#FF00FF',
307 | '#FF3300',
308 | '#FF3333',
309 | '#FF3366',
310 | '#FF3399',
311 | '#FF33CC',
312 | '#FF33FF',
313 | '#FF6600',
314 | '#FF6633',
315 | '#FF6666',
316 | '#FF6699',
317 | '#FF66CC',
318 | '#FF66FF',
319 | '#FF9900',
320 | '#FF9933',
321 | '#FF9966',
322 | '#FF9999',
323 | '#FF99CC',
324 | '#FF99FF',
325 | '#FFCC00',
326 | '#FFCC33',
327 | '#FFCC66',
328 | '#FFCC99',
329 | '#FFCCCC',
330 | '#FFCCFF',
331 | '#FFFF00',
332 | '#FFFF33',
333 | '#FFFF66',
334 | '#FFFF99',
335 | '#FFFFCC'
336 | ];
337 |
338 | // Populate color picker with buttons.
339 |
340 | const colors = document.querySelector('#colors');
341 |
342 | web216.forEach((c) => {
343 | let btn = document.createElement('button');
344 | btn.onclick = closeColorSelector;
345 | btn.dataset.color = c;
346 | btn.style.backgroundColor = c;
347 | colors.append(btn)
348 | });
349 |
350 | // Resolution links
351 |
352 | document.querySelectorAll('a.res').forEach((el) => {
353 | if (el.href === window.location.href || surfaceDim === parseInt(el.innerText)) {
354 | el.classList.add('current-resolution');
355 | }
356 | });
357 |
358 |
--------------------------------------------------------------------------------
/src/vendor/gif.js:
--------------------------------------------------------------------------------
1 | // gif.js 0.2.0 - https://github.com/jnordberg/gif.js
2 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.GIF=f()}})(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o0&&this._events[type].length>m){this._events[type].warned=true;console.error("(node) warning: possible EventEmitter memory "+"leak detected. %d listeners added. "+"Use emitter.setMaxListeners() to increase limit.",this._events[type].length);if(typeof console.trace==="function"){console.trace()}}}return this};EventEmitter.prototype.on=EventEmitter.prototype.addListener;EventEmitter.prototype.once=function(type,listener){if(!isFunction(listener))throw TypeError("listener must be a function");var fired=false;function g(){this.removeListener(type,g);if(!fired){fired=true;listener.apply(this,arguments)}}g.listener=listener;this.on(type,g);return this};EventEmitter.prototype.removeListener=function(type,listener){var list,position,length,i;if(!isFunction(listener))throw TypeError("listener must be a function");if(!this._events||!this._events[type])return this;list=this._events[type];length=list.length;position=-1;if(list===listener||isFunction(list.listener)&&list.listener===listener){delete this._events[type];if(this._events.removeListener)this.emit("removeListener",type,listener)}else if(isObject(list)){for(i=length;i-- >0;){if(list[i]===listener||list[i].listener&&list[i].listener===listener){position=i;break}}if(position<0)return this;if(list.length===1){list.length=0;delete this._events[type]}else{list.splice(position,1)}if(this._events.removeListener)this.emit("removeListener",type,listener)}return this};EventEmitter.prototype.removeAllListeners=function(type){var key,listeners;if(!this._events)return this;if(!this._events.removeListener){if(arguments.length===0)this._events={};else if(this._events[type])delete this._events[type];return this}if(arguments.length===0){for(key in this._events){if(key==="removeListener")continue;this.removeAllListeners(key)}this.removeAllListeners("removeListener");this._events={};return this}listeners=this._events[type];if(isFunction(listeners)){this.removeListener(type,listeners)}else if(listeners){while(listeners.length)this.removeListener(type,listeners[listeners.length-1])}delete this._events[type];return this};EventEmitter.prototype.listeners=function(type){var ret;if(!this._events||!this._events[type])ret=[];else if(isFunction(this._events[type]))ret=[this._events[type]];else ret=this._events[type].slice();return ret};EventEmitter.prototype.listenerCount=function(type){if(this._events){var evlistener=this._events[type];if(isFunction(evlistener))return 1;else if(evlistener)return evlistener.length}return 0};EventEmitter.listenerCount=function(emitter,type){return emitter.listenerCount(type)};function isFunction(arg){return typeof arg==="function"}function isNumber(arg){return typeof arg==="number"}function isObject(arg){return typeof arg==="object"&&arg!==null}function isUndefined(arg){return arg===void 0}},{}],2:[function(require,module,exports){var UA,browser,mode,platform,ua;ua=navigator.userAgent.toLowerCase();platform=navigator.platform.toLowerCase();UA=ua.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/)||[null,"unknown",0];mode=UA[1]==="ie"&&document.documentMode;browser={name:UA[1]==="version"?UA[3]:UA[1],version:mode||parseFloat(UA[1]==="opera"&&UA[4]?UA[4]:UA[2]),platform:{name:ua.match(/ip(?:ad|od|hone)/)?"ios":(ua.match(/(?:webos|android)/)||platform.match(/mac|win|linux/)||["other"])[0]}};browser[browser.name]=true;browser[browser.name+parseInt(browser.version,10)]=true;browser.platform[browser.platform.name]=true;module.exports=browser},{}],3:[function(require,module,exports){var EventEmitter,GIF,browser,extend=function(child,parent){for(var key in parent){if(hasProp.call(parent,key))child[key]=parent[key]}function ctor(){this.constructor=child}ctor.prototype=parent.prototype;child.prototype=new ctor;child.__super__=parent.prototype;return child},hasProp={}.hasOwnProperty,indexOf=[].indexOf||function(item){for(var i=0,l=this.length;iref;i=0<=ref?++j:--j){results.push(null)}return results}.call(this);numWorkers=this.spawnWorkers();if(this.options.globalPalette===true){this.renderNextFrame()}else{for(i=j=0,ref=numWorkers;0<=ref?jref;i=0<=ref?++j:--j){this.renderNextFrame()}}this.emit("start");return this.emit("progress",0)};GIF.prototype.abort=function(){var worker;while(true){worker=this.activeWorkers.shift();if(worker==null){break}this.log("killing active worker");worker.terminate()}this.running=false;return this.emit("abort")};GIF.prototype.spawnWorkers=function(){var j,numWorkers,ref,results;numWorkers=Math.min(this.options.workers,this.frames.length);(function(){results=[];for(var j=ref=this.freeWorkers.length;ref<=numWorkers?jnumWorkers;ref<=numWorkers?j++:j--){results.push(j)}return results}).apply(this).forEach(function(_this){return function(i){var worker;_this.log("spawning worker "+i);worker=new Worker(_this.options.workerScript);worker.onmessage=function(event){_this.activeWorkers.splice(_this.activeWorkers.indexOf(worker),1);_this.freeWorkers.push(worker);return _this.frameFinished(event.data)};return _this.freeWorkers.push(worker)}}(this));return numWorkers};GIF.prototype.frameFinished=function(frame){var i,j,ref;this.log("frame "+frame.index+" finished - "+this.activeWorkers.length+" active");this.finishedFrames++;this.emit("progress",this.finishedFrames/this.frames.length);this.imageParts[frame.index]=frame;if(this.options.globalPalette===true){this.options.globalPalette=frame.globalPalette;this.log("global palette analyzed");if(this.frames.length>2){for(i=j=1,ref=this.freeWorkers.length;1<=ref?jref;i=1<=ref?++j:--j){this.renderNextFrame()}}}if(indexOf.call(this.imageParts,null)>=0){return this.renderNextFrame()}else{return this.finishRendering()}};GIF.prototype.finishRendering=function(){var data,frame,i,image,j,k,l,len,len1,len2,len3,offset,page,ref,ref1,ref2;len=0;ref=this.imageParts;for(j=0,len1=ref.length;j=this.frames.length){return}frame=this.frames[this.nextFrame++];worker=this.freeWorkers.shift();task=this.getTask(frame);this.log("starting frame "+(task.index+1)+" of "+this.frames.length);this.activeWorkers.push(worker);return worker.postMessage(task)};GIF.prototype.getContextData=function(ctx){return ctx.getImageData(0,0,this.options.width,this.options.height).data};GIF.prototype.getImageData=function(image){var ctx;if(this._canvas==null){this._canvas=document.createElement("canvas");this._canvas.width=this.options.width;this._canvas.height=this.options.height}ctx=this._canvas.getContext("2d");ctx.setFill=this.options.background;ctx.fillRect(0,0,this.options.width,this.options.height);ctx.drawImage(image,0,0);return this.getContextData(ctx)};GIF.prototype.getTask=function(frame){var index,task;index=this.frames.indexOf(frame);task={index:index,last:index===this.frames.length-1,delay:frame.delay,transparent:frame.transparent,width:this.options.width,height:this.options.height,quality:this.options.quality,dither:this.options.dither,globalPalette:this.options.globalPalette,repeat:this.options.repeat,canTransfer:browser.name==="chrome"};if(frame.data!=null){task.data=frame.data}else if(frame.context!=null){task.data=this.getContextData(frame.context)}else if(frame.image!=null){task.data=this.getImageData(frame.image)}else{throw new Error("Invalid frame")}return task};GIF.prototype.log=function(){var args;args=1<=arguments.length?slice.call(arguments,0):[];if(!this.options.debug){return}return console.log.apply(console,args)};return GIF}(EventEmitter);module.exports=GIF},{"./browser.coffee":2,events:1}]},{},[3])(3)});
3 | //# sourceMappingURL=gif.js.map
4 |
--------------------------------------------------------------------------------
/src/vendor/gif.worker.js:
--------------------------------------------------------------------------------
1 | // gif.worker.js 0.2.0 - https://github.com/jnordberg/gif.js
2 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o=ByteArray.pageSize)this.newPage();this.pages[this.page][this.cursor++]=val};ByteArray.prototype.writeUTFBytes=function(string){for(var l=string.length,i=0;i=0)this.dispose=disposalCode};GIFEncoder.prototype.setRepeat=function(repeat){this.repeat=repeat};GIFEncoder.prototype.setTransparent=function(color){this.transparent=color};GIFEncoder.prototype.addFrame=function(imageData){this.image=imageData;this.colorTab=this.globalPalette&&this.globalPalette.slice?this.globalPalette:null;this.getImagePixels();this.analyzePixels();if(this.globalPalette===true)this.globalPalette=this.colorTab;if(this.firstFrame){this.writeLSD();this.writePalette();if(this.repeat>=0){this.writeNetscapeExt()}}this.writeGraphicCtrlExt();this.writeImageDesc();if(!this.firstFrame&&!this.globalPalette)this.writePalette();this.writePixels();this.firstFrame=false};GIFEncoder.prototype.finish=function(){this.out.writeByte(59)};GIFEncoder.prototype.setQuality=function(quality){if(quality<1)quality=1;this.sample=quality};GIFEncoder.prototype.setDither=function(dither){if(dither===true)dither="FloydSteinberg";this.dither=dither};GIFEncoder.prototype.setGlobalPalette=function(palette){this.globalPalette=palette};GIFEncoder.prototype.getGlobalPalette=function(){return this.globalPalette&&this.globalPalette.slice&&this.globalPalette.slice(0)||this.globalPalette};GIFEncoder.prototype.writeHeader=function(){this.out.writeUTFBytes("GIF89a")};GIFEncoder.prototype.analyzePixels=function(){if(!this.colorTab){this.neuQuant=new NeuQuant(this.pixels,this.sample);this.neuQuant.buildColormap();this.colorTab=this.neuQuant.getColormap()}if(this.dither){this.ditherPixels(this.dither.replace("-serpentine",""),this.dither.match(/-serpentine/)!==null)}else{this.indexPixels()}this.pixels=null;this.colorDepth=8;this.palSize=7;if(this.transparent!==null){this.transIndex=this.findClosest(this.transparent,true)}};GIFEncoder.prototype.indexPixels=function(imgq){var nPix=this.pixels.length/3;this.indexedPixels=new Uint8Array(nPix);var k=0;for(var j=0;j=0&&x1+x=0&&y1+y>16,(c&65280)>>8,c&255,used)};GIFEncoder.prototype.findClosestRGB=function(r,g,b,used){if(this.colorTab===null)return-1;if(this.neuQuant&&!used){return this.neuQuant.lookupRGB(r,g,b)}var c=b|g<<8|r<<16;var minpos=0;var dmin=256*256*256;var len=this.colorTab.length;for(var i=0,index=0;i=0){disp=dispose&7}disp<<=2;this.out.writeByte(0|disp|0|transp);this.writeShort(this.delay);this.out.writeByte(this.transIndex);this.out.writeByte(0)};GIFEncoder.prototype.writeImageDesc=function(){this.out.writeByte(44);this.writeShort(0);this.writeShort(0);this.writeShort(this.width);this.writeShort(this.height);if(this.firstFrame||this.globalPalette){this.out.writeByte(0)}else{this.out.writeByte(128|0|0|0|this.palSize)}};GIFEncoder.prototype.writeLSD=function(){this.writeShort(this.width);this.writeShort(this.height);this.out.writeByte(128|112|0|this.palSize);this.out.writeByte(0);this.out.writeByte(0)};GIFEncoder.prototype.writeNetscapeExt=function(){this.out.writeByte(33);this.out.writeByte(255);this.out.writeByte(11);this.out.writeUTFBytes("NETSCAPE2.0");this.out.writeByte(3);this.out.writeByte(1);this.writeShort(this.repeat);this.out.writeByte(0)};GIFEncoder.prototype.writePalette=function(){this.out.writeBytes(this.colorTab);var n=3*256-this.colorTab.length;for(var i=0;i>8&255)};GIFEncoder.prototype.writePixels=function(){var enc=new LZWEncoder(this.width,this.height,this.indexedPixels,this.colorDepth);enc.encode(this.out)};GIFEncoder.prototype.stream=function(){return this.out};module.exports=GIFEncoder},{"./LZWEncoder.js":2,"./TypedNeuQuant.js":3}],2:[function(require,module,exports){var EOF=-1;var BITS=12;var HSIZE=5003;var masks=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535];function LZWEncoder(width,height,pixels,colorDepth){var initCodeSize=Math.max(2,colorDepth);var accum=new Uint8Array(256);var htab=new Int32Array(HSIZE);var codetab=new Int32Array(HSIZE);var cur_accum,cur_bits=0;var a_count;var free_ent=0;var maxcode;var clear_flg=false;var g_init_bits,ClearCode,EOFCode;function char_out(c,outs){accum[a_count++]=c;if(a_count>=254)flush_char(outs)}function cl_block(outs){cl_hash(HSIZE);free_ent=ClearCode+2;clear_flg=true;output(ClearCode,outs)}function cl_hash(hsize){for(var i=0;i=0){disp=hsize_reg-i;if(i===0)disp=1;do{if((i-=disp)<0)i+=hsize_reg;if(htab[i]===fcode){ent=codetab[i];continue outer_loop}}while(htab[i]>=0)}output(ent,outs);ent=c;if(free_ent<1<0){outs.writeByte(a_count);outs.writeBytes(accum,0,a_count);a_count=0}}function MAXCODE(n_bits){return(1<0)cur_accum|=code<=8){char_out(cur_accum&255,outs);cur_accum>>=8;cur_bits-=8}if(free_ent>maxcode||clear_flg){if(clear_flg){maxcode=MAXCODE(n_bits=g_init_bits);clear_flg=false}else{++n_bits;if(n_bits==BITS)maxcode=1<0){char_out(cur_accum&255,outs);cur_accum>>=8;cur_bits-=8}flush_char(outs)}}this.encode=encode}module.exports=LZWEncoder},{}],3:[function(require,module,exports){var ncycles=100;var netsize=256;var maxnetpos=netsize-1;var netbiasshift=4;var intbiasshift=16;var intbias=1<>betashift;var betagamma=intbias<>3;var radiusbiasshift=6;var radiusbias=1<>3);var i,v;for(i=0;i>=netbiasshift;network[i][1]>>=netbiasshift;network[i][2]>>=netbiasshift;network[i][3]=i}}function altersingle(alpha,i,b,g,r){network[i][0]-=alpha*(network[i][0]-b)/initalpha;network[i][1]-=alpha*(network[i][1]-g)/initalpha;network[i][2]-=alpha*(network[i][2]-r)/initalpha}function alterneigh(radius,i,b,g,r){var lo=Math.abs(i-radius);var hi=Math.min(i+radius,netsize);var j=i+1;var k=i-1;var m=1;var p,a;while(jlo){a=radpower[m++];if(jlo){p=network[k--];p[0]-=a*(p[0]-b)/alpharadbias;p[1]-=a*(p[1]-g)/alpharadbias;p[2]-=a*(p[2]-r)/alpharadbias}}}function contest(b,g,r){var bestd=~(1<<31);var bestbiasd=bestd;var bestpos=-1;var bestbiaspos=bestpos;var i,n,dist,biasdist,betafreq;for(i=0;i>intbiasshift-netbiasshift);if(biasdist>betashift;freq[i]-=betafreq;bias[i]+=betafreq<>1;for(j=previouscol+1;j>1;for(j=previouscol+1;j<256;j++)netindex[j]=maxnetpos}function inxsearch(b,g,r){var a,p,dist;var bestd=1e3;var best=-1;var i=netindex[g];var j=i-1;while(i=0){if(i=bestd)i=netsize;else{i++;if(dist<0)dist=-dist;a=p[0]-b;if(a<0)a=-a;dist+=a;if(dist=0){p=network[j];dist=g-p[1];if(dist>=bestd)j=-1;else{j--;if(dist<0)dist=-dist;a=p[0]-b;if(a<0)a=-a;dist+=a;if(dist>radiusbiasshift;if(rad<=1)rad=0;for(i=0;i=lengthcount)pix-=lengthcount;i++;if(delta===0)delta=1;if(i%delta===0){alpha-=alpha/alphadec;radius-=radius/radiusdec;rad=radius>>radiusbiasshift;if(rad<=1)rad=0;for(j=0;j