Skip to content

Commit ddbf4b6

Browse files
Add license, readMe
1 parent 5698d24 commit ddbf4b6

File tree

8 files changed

+102
-139
lines changed

8 files changed

+102
-139
lines changed

LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Alan Ang
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.MD

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1-
This is a generative art project which turns images into "force-field" particle animations in real-time. The animation effect uses a force repulsion / self-healing effect.
1+
Video-to-Pixel-Art
2+
=====
23

3-
Live demo: https://collidingscopes.github.io/forcefield/
4-
5-
Upload your own image, open the GUI controls to change the animation parameters, and then use your mouse or touchscreen to activate the animation.
6-
7-
This project is open source (offered under MIT license), so feel free to use it however you wish.
8-
9-
If you liked this and are feeling generous, feel free to buy me a coffee. This would be much appreciated during late-night coding sessions!
4+
<p>Turn videos into pixel art! Use your webcam feed or upload a video, then use the controls to adjust the color palette and pixel size.</p>
5+
<img src="assets/ipodAd.gif">
6+
<p>All processing is done in real-time within your browser, using javascript / webgl shaders / html canvas (see github repo linked below).</p>
7+
<p>You can export your creations as images or videos to save / share your work.</p>
8+
<p>This tool is completely free, open source (MIT license), without any paywalls or premium options. You are welcome to use it for personal or commercial purposes.</p>
9+
<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>
1010

1111
<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>
1212

13-
This project is coded using Javascript, HTML, and CSS. Video creation and encoding is done using mp4 muxer.
14-
15-
Enormous thanks and credit to the project <a href="https://aijs.io/project?user=Tezumie&project=1-Million-Particles" target="_blank" rel="noopener">"1 million particles" by Tezumie</a>, which provided the code foundation for the particle repulsion animation.
16-
17-
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> :)
13+
<p>If the video export feature does not work for you, please try a free screen-recording tool such as OBS Studio.</p>
14+
<p>I do not have access to any of the videos that you upload here, as all processing is done "client-side" (i.e., <b>no videos or images are saved/stored by me — they stay on your computer only</b>).</p>
15+
<p>If you enjoyed this, you may be interested in my other free / open source art projects:</p>
16+
<ul>
17+
<li><a href="https://collidingScopes.github.io/ascii" target="_blank" rel="noopener">Video-to-ASCII</a>: turn videos into ASCII pixel art</li>
18+
<li><a href="https://collidingScopes.github.io/shimmer" target="_blank" rel="noopener">Shape Shimmer</a>: turn photos into funky wave animations</li>
19+
<li><a href="https://collidingScopes.github.io" target="_blank" rel="noopener">Colliding Scopes</a>: turn photos into kaleidoscope animations</li>
20+
<li><a href="https://collidingScopes.github.io/forcefield" target="_blank" rel="noopener">Force-Field Animation</a>: turn photos into particle animations</li>
21+
</ul>
22+
23+
<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>
24+
25+
License
26+
=======
27+
This is an open source project provided under an <a href="https://opensource.org/license/MIT">MIT license</a>

assets/ipodAd.gif

3.36 MB
Loading

assets/tokyoTrain.mp4

2.56 MB
Binary file not shown.

canvasVideoExport.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ function saveImage(){
6060
}
6161

6262
function toggleVideoRecord(){
63+
64+
userVideo.currentTime = 0;
65+
defaultVideo.currentTime = 0;
66+
6367
if(recordVideoState == false){
6468
recordVideoState = true;
6569
chooseRecordingFunction();

index.html

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,19 @@
2121
<link rel="icon" href="assets/siteFavicon3.png">
2222
<link rel="apple-touch-icon" href="assets/siteFavicon3.png">
2323

24+
<link rel="preconnect" href="https://fonts.googleapis.com">
25+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
26+
<link href="https://fonts.googleapis.com/css2?family=Tiny5&display=swap" rel="stylesheet">
27+
2428
</head>
2529
<body>
2630
<div class="container">
2731
<div id="gui"></div>
2832

2933
<canvas id="canvas"></canvas>
3034

31-
<video id="defaultVideo" class="hidden" width="480px" height="848px" loop muted playsinline>
32-
<source src="assets/waves.mp4" type="video/mp4" />
35+
<video id="defaultVideo" class="hidden" width="900px" height="504px" loop muted playsinline>
36+
<source src="assets/tokyoTrain.mp4" type="video/mp4" />
3337
</video>
3438

3539
<video id="userVideo" class="hidden" loop muted playsinline></video>
@@ -58,29 +62,34 @@
5862
<div id="notesDiv">
5963
<div id="textBox">
6064

61-
<h2 id="aboutText">About</h2>
65+
<h2 id="aboutText">VIDEO-TO-PIXEL-ART</h2>
6266

63-
<p>Turn videos into ASCII pixel art! Use your webcam feed or upload a video, then use the controls to adjust the colors, resolution, text style, etc...</p>
64-
<p>You can create a video export to save and/or share your animation afterwards.</p>
67+
<p>Turn videos into pixel art! Use your webcam feed or upload a video, then use the controls to adjust the color palette and pixel size.</p>
68+
<p>All processing is done in real-time within your browser, using javascript / webgl shaders / html canvas (see github repo linked below).</p>
69+
<p>You can export your creations as images or videos to save / share your work.</p>
6570
<p>This tool is completely free, open source (MIT license), without any paywalls or premium options. You are welcome to use it for personal or commercial purposes.</p>
6671
<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>
6772

6873
<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>
6974

70-
<p>The code used to create the animation is quite resource-intensive, so it may run with some lag if your computer doesn't have enough computing power, too many tabs open, low battery, etc.</p>
7175
<p>If the video export feature does not work for you, please try a free screen-recording tool such as OBS Studio.</p>
72-
73-
<p>This project is coded using Javascript, HTML canvas, and CSS (see github repo linked below). Video creation and encoding is done using mp4 muxer.</p>
74-
<p>Enormous thanks and credits to <a href="https://medium.com/@sasha.kub95/webcam-stream-to-ascii-art-with-javascript-2a2f9a39befb" target="_blank" rel="noopener">Aleksandr Kubarskii</a>, whose code served as the foundation for this project.</p>
7576
<p>I do not have access to any of the videos that you upload here, as all processing is done "client-side" (i.e., <b>no videos or images are saved/stored by me — they stay on your computer only</b>).</p>
77+
<p>If you enjoyed this, you may be interested in my other free / open source art projects:</p>
78+
<ul>
79+
<li><a href="https://collidingScopes.github.io/ascii" target="_blank" rel="noopener">Video-to-ASCII</a>: turn videos into ASCII pixel art</li>
80+
<li><a href="https://collidingScopes.github.io/shimmer" target="_blank" rel="noopener">Shape Shimmer</a>: turn photos into funky wave animations</li>
81+
<li><a href="https://collidingScopes.github.io" target="_blank" rel="noopener">Colliding Scopes</a>: turn photos into kaleidoscope animations</li>
82+
<li><a href="https://collidingScopes.github.io/forcefield" target="_blank" rel="noopener">Force-Field Animation</a>: turn photos into particle animations</li>
83+
</ul>
84+
7685
<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>
7786
</div>
7887
</div>
7988

8089
<div id="linksDiv">
8190
<table id="infoMenuTable">
8291
<tr>
83-
<td><button id="gitHubButton"class="socialMediaButton"><a href="https://github.com/collidingScopes/ascii" target="_blank" rel="noopener"><i class="fa-brands fa-github"></i></a></button></td>
92+
<td><button id="gitHubButton"class="socialMediaButton"><a href="https://github.com/collidingScopes/video-to-pixel-art" target="_blank" rel="noopener"><i class="fa-brands fa-github"></i></a></button></td>
8493
<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>
8594
<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>
8695
<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>

pixelShader.js

Lines changed: 33 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
/*
22
To do:
3-
Review entire code base to understand functionality
4-
Choose better default video (Tokyo train video -- downsize and optimize first)
5-
Add more color palettes
63
Write about section, footer section, site OG tags
7-
Clean up code / remove commented out code
8-
Review CSS / page layout / margins
9-
Fix upload video on mobile (follow example from ASCII)
104
Rename page title, github repo, etc...
11-
Upon video record, rewind user video and default video to 0 time (see ASCII example)
125
*/
136

147
// DOM Elements
@@ -25,57 +18,19 @@ console.log("Mobile?: "+isMobileFlag);
2518

2619
let userVideo = document.getElementById('userVideo');
2720
let defaultVideo = document.getElementById('defaultVideo');
28-
let defaultVideoWidth = 480;
29-
let defaultVideoHeight = 848;
21+
let defaultVideoWidth = 900;
22+
let defaultVideoHeight = 504;
23+
let maxCanvasWidth = 1200;
3024

3125
if (!gl) {
3226
alert('WebGL not supported');
3327
throw new Error('WebGL not supported');
3428
}
3529

36-
// Event listeners for controls
37-
fileInput.addEventListener('change', (e) => {
38-
cleanupVideoSource();
39-
if (e.target.files && e.target.files[0]) {
40-
const file = e.target.files[0];
41-
// document.getElementById('fileName').textContent = file.name;
42-
//handleVideoUpload(file);
43-
44-
const url = URL.createObjectURL(file);
45-
userVideo.src = url;
46-
userVideo.addEventListener('loadedmetadata', () => {
47-
48-
userVideo.width = userVideo.videoWidth;
49-
userVideo.height = userVideo.videoHeight;
50-
console.log("user video width/height: "+userVideo.width+", "+userVideo.height);
51-
52-
canvas.width = userVideo.width;
53-
canvas.height = userVideo.height;
54-
gl.viewport(0, 0, canvas.width, canvas.height);
55-
56-
});
57-
58-
// // Wait for video to be loaded before playing
59-
// userVideo.oncanplay = () => {
60-
// userVideo.play();
61-
// currentVideo = userVideo;
62-
// // animationRequest = render(currentVideo);
63-
// render();
64-
// };
65-
66-
setTimeout(function(){
67-
userVideo.play();
68-
currentVideo = userVideo;
69-
render();
70-
},1000);
71-
}
72-
73-
});
74-
7530
//add gui
7631
let obj = {
77-
pixelSize: 8,
78-
colorPalette: "field",
32+
pixelSize: 5,
33+
colorPalette: "underwater",
7934
};
8035

8136
let gui = new dat.gui.GUI( { autoPlace: false } );
@@ -435,10 +390,6 @@ function render() {
435390
}
436391

437392
function drawScene(){
438-
// if (!currentVideo || currentVideo.readyState < currentVideo.HAVE_CURRENT_DATA) {
439-
// // animationRequest = requestAnimationFrame(() => render(video));
440-
// return;
441-
// }
442393

443394
if (!currentVideo.paused) {
444395
animationPlayToggle = true;
@@ -513,69 +464,37 @@ function useWebcam(){
513464
});
514465
}
515466

516-
function handleVideoUpload(file) {
467+
fileInput.addEventListener('change', (e) => {
517468
cleanupVideoSource();
469+
if (e.target.files && e.target.files[0]) {
470+
const file = e.target.files[0];
471+
const url = URL.createObjectURL(file);
472+
userVideo.src = url;
473+
userVideo.addEventListener('loadedmetadata', () => {
474+
475+
userVideo.width = userVideo.videoWidth;
476+
userVideo.height = userVideo.videoHeight;
477+
console.log("user video width/height: "+userVideo.width+", "+userVideo.height);
478+
479+
let canvasWidth = Math.min(userVideo.videoWidth, maxCanvasWidth);
480+
let canvasHeight = Math.floor(canvasWidth * (userVideo.videoHeight / userVideo.videoWidth));
518481

519-
const video = document.createElement('video');
520-
video.setAttribute('playsinline', '');
521-
video.setAttribute('webkit-playsinline', '');
522-
video.setAttribute('crossorigin', 'anonymous');
523-
524-
// Create object URL for the uploaded file
525-
const objectURL = URL.createObjectURL(file);
526-
video.src = objectURL;
527-
video.loop = true;
528-
529-
// Set up video loading handlers
530-
video.onloadedmetadata = () => {
531-
canvas.width = video.videoWidth;
532-
canvas.height = video.videoHeight;
533-
gl.viewport(0, 0, canvas.width, canvas.height);
534-
};
535-
536-
// Wait for video to be loaded before playing
537-
video.oncanplay = () => {
538-
video.play();
539-
currentVideo = video;
540-
// animationRequest = render(currentVideo);
541-
render();
542-
};
543-
}
482+
canvas.width = canvasWidth;
483+
canvas.height = canvasHeight;
484+
console.log("canvas width/height: "+canvas.width+", "+canvas.height);
544485

545-
function useDefaultVideo() {
546-
cleanupVideoSource();
547-
console.log("start default video");
548-
// video.setAttribute('playsinline', 'playsinline');
549-
// video.setAttribute('webkit-playsinline', 'webkit-playsinline');
550-
defaultVideo.setAttribute('crossorigin', 'anonymous');
486+
gl.viewport(0, 0, canvas.width, canvas.height);
551487

552-
if(isMobileFlag){
553-
video.setAttribute('playsinline', ''); // Required for iOS
554-
video.setAttribute('webkit-playsinline', '');
555-
video.setAttribute('autoplay', '');
556-
video.style.transform = 'scaleX(-1)'; // Mirror the video
557-
}
558-
559-
// Create object URL for the uploaded file
560-
// const objectURL = URL.createObjectURL(file);
561-
// video.src = objectURL;
562-
// defaultVideo.loop = true;
563-
564-
// Set up video loading handlers
565-
defaultVideo.onloadedmetadata = () => {
566-
canvas.width = defaultVideo.videoWidth;
567-
canvas.height = defaultVideo.videoHeight;
568-
gl.viewport(0, 0, canvas.width, canvas.height);
569-
defaultVideo.play();
570-
currentVideo = defaultVideo;
571-
render();
572-
};
573-
574-
// Wait for video to be loaded before playing
575-
// defaultVideo.oncanplay = () => {
488+
});
576489

577-
// };
578-
}
490+
setTimeout(function(){
491+
userVideo.play();
492+
currentVideo = userVideo;
493+
render();
494+
},1000);
495+
}
496+
497+
});
579498

580499
function startDefaultVideo(){
581500
if(animationPlayToggle==true){
@@ -584,8 +503,8 @@ function startDefaultVideo(){
584503
console.log("cancel animation");
585504
}
586505

587-
canvasWidth = defaultVideoWidth;
588-
canvasHeight = defaultVideoHeight;
506+
let canvasWidth = defaultVideoWidth;
507+
let canvasHeight = defaultVideoHeight;
589508
canvas.width = canvasWidth;
590509
canvas.height = canvasHeight;
591510

styles.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ canvas {
7070
}
7171

7272
#aboutText{
73-
font-size: 32px;
74-
font-family: "Permanent Marker";
73+
font-size: 40px;
74+
font-family: "Tiny5", sans-serif;
7575
font-weight: 400;
7676
font-style: normal;
7777
display:block;

0 commit comments

Comments
 (0)