/** Ren'Py uses the mask movie's red channel as opacity, not its alpha channel. */ export function applyMovieMask(picture: Uint8ClampedArray, mask: Uint8ClampedArray) { if (picture.length !== mask.length) throw new Error("Размеры кадров маски не совпадают"); for (let i = 3; i < picture.length; i += 4) picture[i] = Math.round(picture[i] * mask[i - 3] / 255); } export function movieDisplayable(source: string, mask: string | null, loop = true): HTMLElement { const video = document.createElement("video"); video.src = source; video.autoplay = true; video.loop = loop; video.muted = true; video.playsInline = true; if (!mask) return video; const alpha = document.createElement("video"); alpha.src = mask; alpha.loop = loop; alpha.muted = true; alpha.playsInline = true; const canvas = document.createElement("canvas"); canvas.className = "pg-masked-movie"; const maskCanvas = document.createElement("canvas"); const ctx = canvas.getContext("2d", { willReadFrequently: true }); const maskCtx = maskCanvas.getContext("2d", { willReadFrequently: true }); if (!ctx || !maskCtx) throw new Error("Не удалось создать поверхность для видео с маской"); let last = -1; let failed = false; const draw = () => { if (failed) return; if (!canvas.isConnected) { video.pause(); alpha.pause(); return; } if (video.readyState >= 2 && alpha.readyState >= 2 && video.videoWidth && video.currentTime !== last) { if (Math.abs(alpha.currentTime - video.currentTime) > 0.06) alpha.currentTime = video.currentTime; const w = video.videoWidth, h = video.videoHeight; if (canvas.width !== w || canvas.height !== h) { canvas.width = maskCanvas.width = w; canvas.height = maskCanvas.height = h; } ctx.clearRect(0, 0, w, h); ctx.drawImage(video, 0, 0, w, h); maskCtx.drawImage(alpha, 0, 0, w, h); const pixels = ctx.getImageData(0, 0, w, h); applyMovieMask(pixels.data, maskCtx.getImageData(0, 0, w, h).data); ctx.putImageData(pixels, 0, 0); last = video.currentTime; } if (video.ended && !loop) { video.pause(); alpha.pause(); return; } requestAnimationFrame(draw); }; video.onerror = alpha.onerror = () => { failed = true; canvas.dataset.error = "Не удалось загрузить видео или его маску"; video.pause(); alpha.pause(); }; void video.play().catch(() => {}); void alpha.play().catch(() => {}); requestAnimationFrame(draw); return canvas; }