2024-02-25 12:18:36 -05:00
|
|
|
{
|
|
|
|
|
let currentVideoFrames = [];
|
|
|
|
|
let videoFrame = 0;
|
|
|
|
|
let processed = 0;
|
|
|
|
|
let started = false;
|
|
|
|
|
let videoWidth = 0;
|
|
|
|
|
let videoHeight = 0;
|
|
|
|
|
let FPS = tps / 2;
|
|
|
|
|
|
|
|
|
|
const splitHex = (hex) => hex.slice(1).match(/../g).map(a => Math.floor(parseInt(a, 16)));
|
|
|
|
|
const hexify = (rgb) => "#" + rgb.map(a => Math.floor(a).toString(16).padStart(2, "0")).join("");
|
|
|
|
|
function colorLerp(color_, color2_, t) {
|
|
|
|
|
const color = splitHex(color_);
|
|
|
|
|
const color2 = splitHex(color2_);
|
|
|
|
|
const r = (1 - t) * color[0] + t * color2[0];
|
|
|
|
|
const g = (1 - t) * color[1] + t * color2[1];
|
|
|
|
|
const b = (1 - t) * color[2] + t * color2[2];
|
|
|
|
|
return hexify([r, g, b]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elements.video_pixel = {
|
|
|
|
|
color: "#ffffff",
|
|
|
|
|
hidden: true,
|
|
|
|
|
category: "special",
|
|
|
|
|
canPlace: false,
|
|
|
|
|
tool: () => {},
|
|
|
|
|
tick: (pixel) => {
|
|
|
|
|
if (started && pixelTicks % (tps / FPS) == 0) {
|
|
|
|
|
processed++;
|
|
|
|
|
if (processed >= videoWidth * videoHeight) {
|
|
|
|
|
videoFrame++;
|
|
|
|
|
processed = 0;
|
|
|
|
|
}
|
|
|
|
|
if (videoFrame >= currentVideoFrames.length) {
|
|
|
|
|
started = false;
|
|
|
|
|
processed = 0;
|
|
|
|
|
currentVideoFrames = [];
|
|
|
|
|
videoFrame = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pixel.color = currentVideoFrames[videoFrame % currentVideoFrames.length][pixel.y][pixel.x - Math.floor((width - videoWidth) / 2)];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const chunk = (arr, size) => arr.map((_, i) => i % size == 0 ? arr.slice(i, i + size) : null).filter(a => a)
|
|
|
|
|
|
|
|
|
|
elements.video = {
|
|
|
|
|
color: ["#78bbff","#5bb81a"],
|
|
|
|
|
onSelect: () => {
|
|
|
|
|
if (!localStorage.getItem("video.js/tutorial")) {
|
|
|
|
|
alert("Videos might take a while (up to a few minutes with larger videos) to load. During that time, do not delete any video pixels");
|
|
|
|
|
localStorage.setItem("video.js/tutorial", true);
|
|
|
|
|
}
|
|
|
|
|
const file = document.createElement("input");
|
|
|
|
|
file.type = "file";
|
|
|
|
|
file.accept = "video/*";
|
|
|
|
|
file.click();
|
|
|
|
|
file.onchange = () => {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
const video = document.createElement("video");
|
|
|
|
|
video.preload = "auto";
|
|
|
|
|
const url = URL.createObjectURL(file.files[0]);
|
|
|
|
|
video.src = url;
|
|
|
|
|
video.load();
|
|
|
|
|
|
|
|
|
|
let canvas = document.createElement("canvas", {
|
|
|
|
|
willReadFrequently: true
|
|
|
|
|
});
|
|
|
|
|
let ctx = canvas.getContext("2d");
|
|
|
|
|
video.onloadeddata = async () => {
|
|
|
|
|
const w = video.videoWidth;
|
|
|
|
|
const h = video.videoHeight;
|
|
|
|
|
const newHeight = height;
|
|
|
|
|
const newWidth = Math.floor((w / h) * newHeight);
|
|
|
|
|
canvas.width = video.videoWidth;
|
|
|
|
|
canvas.height = video.videoHeight;
|
|
|
|
|
videoWidth = newWidth;
|
|
|
|
|
videoHeight = newHeight;
|
|
|
|
|
|
|
|
|
|
const videoLength = video.duration * FPS;
|
|
|
|
|
const seekBy = video.duration / videoLength;
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < newWidth; i++) {
|
|
|
|
|
for (let y = 0; y < newHeight; y++) {
|
|
|
|
|
const x = Math.floor((width - newWidth) / 2) + i;
|
|
|
|
|
if (pixelMap[x][y]) deletePixel(x, y);
|
|
|
|
|
createPixel("video_pixel", x, y);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-03-21 19:45:41 -04:00
|
|
|
currentVideoFrames = [];
|
2024-02-25 12:18:36 -05:00
|
|
|
video.currentTime = 0;
|
|
|
|
|
|
|
|
|
|
video.onseeked = () => {
|
|
|
|
|
ctx.drawImage(video, 0, 0, newWidth, newHeight);
|
|
|
|
|
const imageData = chunk(chunk(Array.from(ctx.getImageData(0, 0, newWidth, newHeight).data), 4), newWidth);
|
|
|
|
|
const frame = [];
|
|
|
|
|
for (let y = 0; y < newHeight; y++) {
|
|
|
|
|
frame[y] = [];
|
|
|
|
|
for (let x = 0; x < newWidth; x++) {
|
|
|
|
|
frame[y][x] = `#${imageData[y][x].slice(0, 3).map(a => a.toString(16).padStart(2, "0")).join("")}`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
currentVideoFrames.push(frame);
|
|
|
|
|
|
|
|
|
|
if (currentVideoFrames.length >= videoLength) {
|
|
|
|
|
const audio = new Audio();
|
|
|
|
|
audio.src = url;
|
|
|
|
|
audio.play();
|
|
|
|
|
|
|
|
|
|
started = true;
|
|
|
|
|
videoFrame = 0;
|
|
|
|
|
} else {
|
|
|
|
|
video.currentTime += seekBy;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, 500);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
tool: () => {},
|
|
|
|
|
category: "special"
|
|
|
|
|
}
|
|
|
|
|
}
|