Skip to content

Commit 96acbef

Browse files
Add files via upload
1 parent 675c8bb commit 96acbef

File tree

4 files changed

+297
-465
lines changed

4 files changed

+297
-465
lines changed

coi-serviceworker.js

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */
2+
let coepCredentialless = false;
3+
if (typeof window === 'undefined') {
4+
self.addEventListener("install", () => self.skipWaiting());
5+
self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim()));
6+
7+
self.addEventListener("message", (ev) => {
8+
if (!ev.data) {
9+
return;
10+
} else if (ev.data.type === "deregister") {
11+
self.registration
12+
.unregister()
13+
.then(() => {
14+
return self.clients.matchAll();
15+
})
16+
.then(clients => {
17+
clients.forEach((client) => client.navigate(client.url));
18+
});
19+
} else if (ev.data.type === "coepCredentialless") {
20+
coepCredentialless = ev.data.value;
21+
}
22+
});
23+
24+
self.addEventListener("fetch", function (event) {
25+
const r = event.request;
26+
if (r.cache === "only-if-cached" && r.mode !== "same-origin") {
27+
return;
28+
}
29+
30+
const request = (coepCredentialless && r.mode === "no-cors")
31+
? new Request(r, {
32+
credentials: "omit",
33+
})
34+
: r;
35+
event.respondWith(
36+
fetch(request)
37+
.then((response) => {
38+
if (response.status === 0) {
39+
return response;
40+
}
41+
42+
const newHeaders = new Headers(response.headers);
43+
newHeaders.set("Cross-Origin-Embedder-Policy",
44+
coepCredentialless ? "credentialless" : "require-corp"
45+
);
46+
if (!coepCredentialless) {
47+
newHeaders.set("Cross-Origin-Resource-Policy", "cross-origin");
48+
}
49+
newHeaders.set("Cross-Origin-Opener-Policy", "same-origin");
50+
51+
return new Response(response.body, {
52+
status: response.status,
53+
statusText: response.statusText,
54+
headers: newHeaders,
55+
});
56+
})
57+
.catch((e) => console.error(e))
58+
);
59+
});
60+
61+
} else {
62+
(() => {
63+
const reloadedBySelf = window.sessionStorage.getItem("coiReloadedBySelf");
64+
window.sessionStorage.removeItem("coiReloadedBySelf");
65+
const coepDegrading = (reloadedBySelf == "coepdegrade");
66+
67+
// You can customize the behavior of this script through a global `coi` variable.
68+
const coi = {
69+
shouldRegister: () => !reloadedBySelf,
70+
shouldDeregister: () => false,
71+
coepCredentialless: () => true,
72+
coepDegrade: () => true,
73+
doReload: () => window.location.reload(),
74+
quiet: false,
75+
...window.coi
76+
};
77+
78+
const n = navigator;
79+
const controlling = n.serviceWorker && n.serviceWorker.controller;
80+
81+
// Record the failure if the page is served by serviceWorker.
82+
if (controlling && !window.crossOriginIsolated) {
83+
window.sessionStorage.setItem("coiCoepHasFailed", "true");
84+
}
85+
const coepHasFailed = window.sessionStorage.getItem("coiCoepHasFailed");
86+
87+
if (controlling) {
88+
// Reload only on the first failure.
89+
const reloadToDegrade = coi.coepDegrade() && !(
90+
coepDegrading || window.crossOriginIsolated
91+
);
92+
n.serviceWorker.controller.postMessage({
93+
type: "coepCredentialless",
94+
value: (reloadToDegrade || coepHasFailed && coi.coepDegrade())
95+
? false
96+
: coi.coepCredentialless(),
97+
});
98+
if (reloadToDegrade) {
99+
!coi.quiet && console.log("Reloading page to degrade COEP.");
100+
window.sessionStorage.setItem("coiReloadedBySelf", "coepdegrade");
101+
coi.doReload("coepdegrade");
102+
}
103+
104+
if (coi.shouldDeregister()) {
105+
n.serviceWorker.controller.postMessage({ type: "deregister" });
106+
}
107+
}
108+
109+
// If we're already coi: do nothing. Perhaps it's due to this script doing its job, or COOP/COEP are
110+
// already set from the origin server. Also if the browser has no notion of crossOriginIsolated, just give up here.
111+
if (window.crossOriginIsolated !== false || !coi.shouldRegister()) return;
112+
113+
if (!window.isSecureContext) {
114+
!coi.quiet && console.log("COOP/COEP Service Worker not registered, a secure context is required.");
115+
return;
116+
}
117+
118+
// In some environments (e.g. Firefox private mode) this won't be available
119+
if (!n.serviceWorker) {
120+
!coi.quiet && console.error("COOP/COEP Service Worker not registered, perhaps due to private mode.");
121+
return;
122+
}
123+
124+
n.serviceWorker.register(window.document.currentScript.src).then(
125+
(registration) => {
126+
!coi.quiet && console.log("COOP/COEP Service Worker registered", registration.scope);
127+
128+
registration.addEventListener("updatefound", () => {
129+
!coi.quiet && console.log("Reloading page to make use of updated COOP/COEP Service Worker.");
130+
window.sessionStorage.setItem("coiReloadedBySelf", "updatefound");
131+
coi.doReload();
132+
});
133+
134+
// If the registration is active, but it's not controlling the page
135+
if (registration.active && !n.serviceWorker.controller) {
136+
!coi.quiet && console.log("Reloading page to make use of COOP/COEP Service Worker.");
137+
window.sessionStorage.setItem("coiReloadedBySelf", "notcontrolling");
138+
coi.doReload();
139+
}
140+
},
141+
(err) => {
142+
!coi.quiet && console.error("COOP/COEP Service Worker failed to register:", err);
143+
}
144+
);
145+
})();
146+
}

index.html

Lines changed: 28 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,28 @@
1-
<!DOCTYPE html>
2-
<html>
3-
<head>
4-
<meta charset="UTF-8">
5-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6-
<link rel="stylesheet" type="text/css" href="style.css">
7-
8-
<link rel="preconnect" href="https://fonts.googleapis.com">
9-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10-
<link href="https://fonts.googleapis.com/css2?family=Permanent+Marker&display=swap" rel="stylesheet">
11-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" integrity="sha512-9usAa10IRO0HhonpyAIVpjrylPvoDwiPUiKdWk5t3PyolY1cOd4DSE0Ga+ri4AuTroPR5aQvXU9xC6qOPnzFeg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
12-
13-
<meta property="og:title" content="Colliding Scopes: kaleidoscope animations" />
14-
<meta property="og:description" content="Turn photos into kaleidoscope animations" />
15-
<meta property="og:type" content="website" />
16-
<meta property="og:url" content="https://collidingscopes.github.io/" />
17-
<meta property="og:image" content="https://collidingscopes.github.io/images/siteOGImage.png">
18-
<meta property="og:image:type" content="image/png" >
19-
<meta property="og:image:width" content="800" >
20-
<meta property="og:image:height" content="800" >
21-
22-
<link rel="icon" href="images/siteFavicon2.png">
23-
<link rel="apple-touch-icon" href="images/siteFavicon2.png">
24-
25-
</head>
26-
<body>
27-
28-
<div id="coverScreen" class="hidden">
29-
</div>
30-
31-
<div id="introDiv">
32-
<span id="siteNameText">Colliding Scopes</span>
33-
<span id="subtitleText">Turn photos into kaleidoscope animations</span>
34-
</div>
35-
36-
<div id="toolDiv">
37-
38-
<canvas id="animation"></canvas>
39-
40-
<table id="inputTable">
41-
<tr>
42-
<td>
43-
<label for="imageInput" class="custom-file-upload">
44-
<i class="fa fa-cloud-upload"></i> Select Image
45-
</label>
46-
<input type="file" id="imageInput" accept="image/*">
47-
</td>
48-
<td>
49-
<pre><span class="tableText">Animation Speed</span><br><input class="input-number-noSpinners" type="range" id="speedInput" value="4" min="1" max="10"></pre>
50-
</td>
51-
<td><button id="recordVideoButton" class="recordButton">Record Video (r)</button></td>
52-
<td><span class="tableText">Seconds: </span><input type="number" id="videoDurationInput" class="input-number-noSpinners" value="10" min="1" max="120"></td>
53-
<!--
54-
<td><button id="pauseAnimationButton">Pause/Play (p)</button></td>
55-
<td><button id="save-image-button">Screenshot (s)</button></td>
56-
-->
57-
</tr>
58-
59-
</table>
60-
61-
62-
<div id="videoRecordingMessageDiv" class="hidden">
63-
</div>
64-
<button id="downloadButton" class="hidden">Download video</button>
65-
66-
<div id="imageContainer" class="hidden">
67-
<img id="originalImg" src="images/HK400px.jpg">
68-
</div>
69-
<div id="newImageContainer" class="hidden">
70-
<img id="flippedImg" src="images/HK400pxFlipped.jpg">
71-
</div>
72-
73-
</div>
74-
75-
76-
<div id="notesDiv">
77-
<div id="textBox">
78-
79-
<h2 id="aboutText">About</h2>
80-
81-
<p>This web tool is completely free, open source, without any paywalls or premium options. You are welcome to use it for personal or commercial purposes.</p>
82-
<p>If you found this tool useful, feel free to buy me a coffee. This would be much appreciated during late-night coding sessions!</p>
83-
84-
<a href="https://www.buymeacoffee.com/stereoDrift" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/yellow_img.png" alt="Buy Me A Coffee"></a>
85-
86-
<p>Enormous thanks and credits to Luke Hannam, whose <a href="https://www.pepperoni.blog/canvas-kaleidoscope/" target="_blank" rel="noopener">blog post</a> explained the code and mechanics for creating kaleidoscope animations in javascript.</p>
87-
<p>I made only a few tweaks to Luke's original algorithm, with my main work being to add the front-end user interface allowing users to upload their own photos, control variables like animation speed, and easily export the canvas animation to video.</p>
88-
<p>There are a few hotkeys which can speed up using the tool:</p>
89-
<ul>
90-
<li>Press <b>"r"</b> to start recording a video of the animation. You can specify the length of the video in seconds. An mp4 video file will be exported to your downloads folder afterwards</li>
91-
<li>Press <b>"p"</b> to pause / play the animation. This lets you stop at an interesting point of the animation</li>
92-
<li>Press <b>"s"</b> to save a screenshot of the current state of the animation (png image)</li>
93-
</ul>
94-
<p>This project is coded using Javascript, HTML, and CSS (see github repo linked below). Video creation and encoding is done using mp4 muxer.</p>
95-
<p>I do not have access to any of the images that you upload here, as all processing is done "client-side" (i.e., <b>no images are saved or stored by me — they stay on your computer only</b>).</p>
96-
<p>Feel free to reach out to discuss, ask questions, or to share your creations! The animations can be easily uploaded to instagram or otherwise -- you can tag me <a href="https://www.instagram.com/stereo.drift/" target="_blank" rel="noopener">@stereo.drift</a> :)</p>
97-
</div>
98-
</div>
99-
100-
<div id="linksDiv">
101-
<table id="infoMenuTable">
102-
<tr>
103-
<td><button id="gitHubButton"class="socialMediaButton"><a href="https://github.com/collidingScopes/collidingScopes.github.io" target="_blank" rel="noopener"><i class="fa-brands fa-github"></i></a></button></td>
104-
<td><button id="coffeeButton" class="socialMediaButton"><a href="https://www.buymeacoffee.com/stereoDrift" target="_blank" rel="noopener"><i class="fa-solid fa-heart"></i></a></button></td>
105-
<td><button id="instagramButton" class="socialMediaButton"><a href="https://www.instagram.com/stereo.drift/" target="_blank" rel="noopener"><i class="fa-brands fa-instagram"></i></a></button></td>
106-
<td><button id="emailButton" class="socialMediaButton"><a href="mailto:[email protected]" target="_blank" rel="noopener"><i class="fa-solid fa-envelope"></i></a></button></td>
107-
</tr>
108-
</table>
109-
</div>
110-
111-
112-
</body>
113-
114-
<script src="kaleidoscope.js"></script>
115-
<script src="mp4-muxer-main/build/mp4-muxer.js"></script>
116-
117-
</html>
1+
<!doctype html>
2+
<head>
3+
<title>CANVAS TO VIDEO FAILS ON SAFARI</title>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
</head>
7+
<body>
8+
<header>
9+
<h1>CANVAS TO VIDEO FAILS ON SAFARI</h1>
10+
<p>Record canvas to video</p>
11+
<p>This example renders white noise in a <code>canvas</code> for three seconds and records the output in a video clip, using <code>canvas.captureStream</code> and <code>MediaRecorder</code></p>
12+
</header>
13+
<main class="row">
14+
<figure>
15+
<canvas id="canvas" width="800" height="800"></canvas><br>
16+
<caption>This is a <code>canvas</code> element</caption>
17+
</figure>
18+
<!--
19+
<figure>
20+
<video id="video" controls loop></video><br>
21+
<caption>This is a <code>video</code> element</caption>
22+
</figure>
23+
-->
24+
<button id="recordButton">Record 10 second video</button>
25+
<button id="downloadButton">Download video</button>
26+
</main>
27+
</body>
28+
<script src="saveCanvas.js"></script>

saveCanvas.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// This example gets a video stream from a canvas on which we will draw
2+
// black and white noise, and captures it to a video
3+
//
4+
// The relevant functions in use are:
5+
//
6+
// requestAnimationFrame -> to create a render loop (better than setTimeout)
7+
// canvas.captureStream -> to get a stream from a canvas
8+
// context.getImageData -> to get access to the canvas pixels
9+
// URL.createObjectURL -> to create a URL from a stream so we can use it as src
10+
11+
var finishedBlob;
12+
var downloadButton = document.getElementById("downloadButton");
13+
downloadButton.addEventListener("click",downloadBlob);
14+
15+
var recordButton = document.getElementById("recordButton");
16+
recordButton.addEventListener("click",startRecording);
17+
var videoDuration = 10000; //milliseconds
18+
19+
var canvas = document.getElementById('canvas');
20+
var width = canvas.width;
21+
var height = canvas.height;
22+
var capturing = false;
23+
24+
// We need the 2D context to individually manipulate pixel data
25+
var ctx = canvas.getContext('2d');
26+
27+
// Start with a black background
28+
ctx.fillStyle = '#000';
29+
ctx.fillRect(0, 0, width, height);
30+
31+
// Since we're continuously accessing and overwriting the pixels
32+
// object, we'll request it once and reuse it across calls to draw()
33+
// for best performance (we don't need to create ImageData objects
34+
// on every frame)
35+
var pixels = ctx.getImageData(0, 0, width, height);
36+
var data = pixels.data;
37+
var numPixels = data.length;
38+
39+
var fps = 24;
40+
var stream = canvas.captureStream(fps);
41+
var recorder = new MediaRecorder(stream, { 'type': 'video/mp4' });
42+
recorder.addEventListener('dataavailable', finishCapturing);
43+
44+
//main method
45+
draw();
46+
47+
function startRecording(){
48+
recorder.start(); //moved here
49+
capturing = true;
50+
51+
setTimeout(function() {
52+
recorder.stop();
53+
}, videoDuration);
54+
}
55+
56+
function finishCapturing(e) {
57+
//capturing = false;
58+
var videoData = [ e.data ];
59+
finishedBlob = new Blob(videoData, { 'type': 'video/mp4' });
60+
console.log(finishedBlob);
61+
downloadBlob(finishedBlob);
62+
//var videoURL = URL.createObjectURL(finishedBlob);
63+
//video.src = videoURL;
64+
//video.play();
65+
//downloadBlob(finishedBlob);
66+
}
67+
68+
function draw() {
69+
// We don't want to render again if we're not capturing
70+
//requestAnimationFrame(draw);
71+
setInterval(drawWhiteNoise,1000/fps);
72+
//drawWhiteNoise();
73+
}
74+
75+
76+
function drawWhiteNoise() {
77+
78+
var offset = 0;
79+
for(var i = 0; i < numPixels; i++) {
80+
var grey = Math.round(Math.random() * 255);
81+
data[offset++] = grey;
82+
data[offset++] = grey;
83+
data[offset++] = grey;
84+
offset++;
85+
}
86+
87+
// And tell the context to draw the updated pixels in the canvas
88+
ctx.putImageData(pixels, 0, 0);
89+
}
90+
91+
92+
function downloadBlob() {
93+
let url = window.URL.createObjectURL(finishedBlob);
94+
let a = document.createElement("a");
95+
a.style.display = "none";
96+
a.href = url;
97+
const date = new Date();
98+
const filename = `video_${date.toLocaleDateString()}_${date.toLocaleTimeString()}.mp4`;
99+
a.download = filename;
100+
document.body.appendChild(a);
101+
a.click();
102+
window.URL.revokeObjectURL(url);
103+
}

0 commit comments

Comments
 (0)