Files
fsae41.de/public/led/index.html
BolkeDerBaer 038910e9f0 Add service worker for push notifications, create calendar layout, and implement WLAN QR code page
- Implemented a service worker (sw.js) to handle push notifications with dynamic options and notification click events.
- Created a calendar layout in test.html with a grid system for displaying events across days and times.
- Developed a visually engaging WLAN QR code page (wlan.html) with animated backgrounds, particle effects, and tips for connecting to the network.
2026-02-22 00:50:22 +01:00

264 lines
8.5 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Bild skalieren & anzeigen</title>
<style>
/* Canvas sichtbar skalieren */
#outputCanvas {
width: 33%;
/* Bildschirmbreite */
height: auto;
/* Höhe automatisch */
border: 1px solid black;
image-rendering: pixelated;
/* WICHTIG: Pixel bleiben scharf */
}
</style>
</head>
<body>
<h2>Bild auswählen, skalieren und im Canvas anzeigen</h2>
<label>Breite: </label>
<input type="number" id="newWidth" value="8"><br>
<label>Höhe: </label>
<input type="number" id="newHeight" value="8"><br><br>
<button onclick="myFunction()">Copy text</button>
<script>
function myFunction() {
// Copy the text inside the text field
navigator.clipboard.writeText(AnimationData.getPy());
}
</script>
<input type="file" id="imageInput" accept="image/*,video/*"><br><br>
<!-- Canvas zur Anzeige -->
<canvas id="outputCanvas"></canvas>
<script>
// Pixel-Klasse
class Pixel {
constructor(x, y, r, g, b, a) {
this.x = x;
this.y = y;
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
}
class Frame {
constructor(file) {
this.file = URL.createObjectURL(file);
this.pixels = [];
this.updateSize(8, 8);
}
updateSize(w, h) {
const img = new Image();
img.onload = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
// Interne Pixelgröße des Canvas (NICHT sichtbare Größe)
canvas.width = w;
canvas.height = h;
// Bild intern auf dieser Pixelgröße zeichnen
ctx.drawImage(img, 0, 0, w, h);
// Pixel auslesen
const imageData = ctx.getImageData(0, 0, w, h);
const data = imageData.data;
this.pixels = [];
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const i = (y * w + x) * 4;
this.pixels.push(new Pixel(x, y, data[i], data[i + 1], data[i + 2], data[i + 3]));
}
}
};
img.src = this.file;
}
}
class Animation {
constructor(frames, fps = 1) {
this.frames = frames;
this.fps = fps;
this.currentFrameIndex = 0;
}
updateSize(w, h) {
this.frames.forEach(f => f.updateSize(w, h));
}
getPy() {
let string = "";
this.frames.forEach((frame, index) => {
string += `frame${index} = [`;
frame.pixels.forEach((pixel, i) => {
const prevPixel = index > 0 ? this.frames[index - 1].pixels[i] : null;
if (!prevPixel || prevPixel.r !== pixel.r || prevPixel.g !== pixel.g || prevPixel.b !== pixel.b || prevPixel.a !== pixel.a) {
string += `(${i}, ${pixel.r}, ${pixel.g}, ${pixel.b}, ${pixel.a}),`;
}
});
string += "]\n";
});
string += "\nFRAMES = [\n";
this.frames.forEach((frame, index) => {
string += ` frame${index},\n`;
});
return string;
}
getFrame() {
return this.frames[this.currentFrameIndex];
}
nextFrame() {
this.currentFrameIndex = (this.currentFrameIndex + 1) % this.frames.length;
}
}
let AnimationData = new Animation([]);
document.getElementById("imageInput").addEventListener("change", function (event) {
const file = event.target.files[0];
if (!file) return;
if (file.type.startsWith("image/")) {
AnimationData = new Animation([new Frame(file)], 10);
AnimationData.frames[0].updateSize(
parseInt(document.getElementById("newWidth").value),
parseInt(document.getElementById("newHeight").value)
);
requestAnimationFrame(loop);
}
if (file.type.startsWith("video/")) {
let targetFPS = 24;
let frames = videoDecoder(file, targetFPS);
AnimationData = new Animation(frames, targetFPS);
setTimeout(() => {
requestAnimationFrame(loop);
}, 10000);
}
});
let accumulator = 0;
let lastTime = 0;
function videoDecoder(file, fps) {
let video = document.createElement("video");
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
let frames = [];
function seek(videoEl, time) {
return new Promise((resolve, reject) => {
function cleanup() {
videoEl.removeEventListener('seeked', onSeeked);
videoEl.removeEventListener('error', onError);
}
function onSeeked() { cleanup(); resolve(); }
function onError(e) { cleanup(); reject(e); }
videoEl.addEventListener('seeked', onSeeked);
videoEl.addEventListener('error', onError);
videoEl.currentTime = Math.min(Math.max(time, 0), videoEl.duration || time);
});
}
video.addEventListener('loadeddata', async () => {
canvas.width = video.videoWidth || 640;
canvas.height = video.videoHeight || 360;
let delta = 1 / fps;
let currentTime = 0;
for (let currentTime = 0; currentTime < video.duration; currentTime += delta) {
await seek(video, currentTime);
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
canvas.toBlob(blob => {
if (!blob) return;
const url = URL.createObjectURL(blob);
let frame = new Frame(blob);
document.body.appendChild(document.createTextNode(`Frame at ${currentTime.toFixed(2)}s`));
let img = document.createElement("img");
img.src = url;
//document.body.appendChild(img);
document.body.appendChild(document.createElement("br"));
frames.push(frame);
}, 'image/png');
}
});
video.src = URL.createObjectURL(file);
video.load();
return frames;
}
function loop(time) {
const deltaTime = (time - lastTime) / 1000; // in seconds
lastTime = time;
accumulator += deltaTime;
const FRAME_TIME = 1 / AnimationData.fps; // ~0.0167 seconds for 60 FPS
// Generate frames only when enough time has passed
if (accumulator >= FRAME_TIME) {
const canvas = document.getElementById("outputCanvas");
const ctx = canvas.getContext("2d");
const frame = AnimationData.getFrame();
AnimationData.nextFrame();
canvas.width = frame.pixels.reduce((max, p) => Math.max(max, p.x), 0) + 1;
canvas.height = frame.pixels.reduce((max, p) => Math.max(max, p.y), 0) + 1;
for (const pixel of frame.pixels) {
ctx.fillStyle = `rgba(${pixel.r}, ${pixel.g}, ${pixel.b}, ${pixel.a / 255})`;
ctx.fillRect(pixel.x, pixel.y, 1, 1);
}
accumulator -= FRAME_TIME;
}
requestAnimationFrame(loop);
}
</script>
</body>
</html>