import React, {useRef, useEffect, useState, createRef} from 'react'; import {asciiChars} from '../constants/pixel-ascii'; import { calculateAndSetFontSize, canvasImgToUrl, getAsciiFromImage, getAsciiFromImageColor, lineHeight, lineSpacing, videoImgToUrl, } from '../canvas-handler/video-canvas-ascii'; export enum ArtTypeEnum { ASCII = 'ASCII', ASCII_COLOR = 'ASCII_COLOR', ASCII_COLOR_BG_IMAGE = 'ASCII_COLOR_BG_IMAGE', } type Props = { videoStreaming: HTMLVideoElement; parentRef: React.RefObject; charsPerLine: number; charsPerColumn: number; fontColor: string; backgroundColor: string; artType: ArtTypeEnum; flipY?: boolean; preTagRef?: React.RefObject; }; const defaultProps = { flipY: false, preTagRef: createRef(), }; export const VideoAscii = (props: Props) => { // Merge the props with the default props const mergedProps = {...defaultProps, ...props}; // Set the local variables const canvasVideoBufferRef = useRef(null); const [asciiText, setAsciiText] = useState(''); // UseEffect to calculate the font size and set the resize observer (to resize the canvas and the font size, when the parent element is resized) useEffect(() => { calculateAndSetFontSize(mergedProps.preTagRef.current!, mergedProps.charsPerLine, mergedProps.charsPerColumn, mergedProps.parentRef.current!.clientWidth, mergedProps.parentRef.current!.clientHeight); // Set a resize observer to the parent element to resize the canvas and the font size const resizeObserver = new ResizeObserver(entries => { const {width, height} = entries[0].contentRect; calculateAndSetFontSize(mergedProps.preTagRef.current!, mergedProps.charsPerLine, mergedProps.charsPerColumn, width, height); }); if (mergedProps.parentRef.current) { resizeObserver.observe(mergedProps.parentRef.current); } // Stop the resize observer when the component is unmounted return () => { resizeObserver.disconnect(); }; }, [mergedProps.videoStreaming, mergedProps.parentRef, mergedProps.charsPerLine, mergedProps.charsPerColumn, mergedProps.artType]); // UseEffect to draw the video to the canvas buffer and get the ascii from the canvas buffer on every frame useEffect(() => { const canvas = canvasVideoBufferRef.current!; const context = canvas.getContext('2d', {willReadFrequently: true})!; // Animation frame id let animationFrameId: number; // Refresh the ascii art text every frame const updateAscii = () => { // Draw video to canvas buffer context.drawImage(mergedProps.videoStreaming, 0, 0, canvas.width, canvas.height); const imageData = context.getImageData(0, 0, canvas.width, canvas.height); switch (mergedProps.artType) { case ArtTypeEnum.ASCII: setAsciiText(getAsciiFromImage(imageData, asciiChars)); break; case ArtTypeEnum.ASCII_COLOR: setAsciiText(getAsciiFromImageColor(imageData, asciiChars)); break; case ArtTypeEnum.ASCII_COLOR_BG_IMAGE: setAsciiText(getAsciiFromImage(imageData, asciiChars)); // Set the background image of the pre tag to the resized canvas mergedProps.preTagRef.current!.style.backgroundImage = `url(${canvasImgToUrl(canvas).src})`; // // Set the background image of the pre tag to the original dimensions video // mergedProps.preTagRef.current!.style.backgroundImage = `url(${videoImgToUrl(mergedProps.videoStreaming).src})`; break; default: break; } // Schedule the next frame animationFrameId = requestAnimationFrame(updateAscii); }; // Start the animation loop when the component mounts updateAscii(); // Stop the animation loop when the component unmounts return () => { cancelAnimationFrame(animationFrameId); }; }, [mergedProps.videoStreaming, mergedProps.artType]); return (
{ (() => { switch (mergedProps.artType) { case ArtTypeEnum.ASCII: return (
									{asciiText}
								
); case ArtTypeEnum.ASCII_COLOR: return (

							);
						case ArtTypeEnum.ASCII_COLOR_BG_IMAGE:
							return (
								
									{
										/*
                                        This span is important for the browser, it helps differentiate
                                        the other pre tag from the one with the background image when
                                        toggling the artType. If the pre tag is not present, the browser
                                        might think that the change of pre tag is an update not a replace
                                         */
									}
									
										{asciiText}
									
); default: return (

ERROR

); } })() }
); };