-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
285b5c4
commit 3d3206c
Showing
12 changed files
with
290 additions
and
185 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,74 @@ | ||
# React + Vite | ||
|
||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. | ||
# Video to ASCII Converter | ||
|
||
Currently, two official plugins are available: | ||
## Overview | ||
|
||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh | ||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh | ||
This project is a web application that captures video from the camera and converts it into ASCII art in real-time. The application is built using React and uses context for processing video frames. | ||
|
||
## Project Structure | ||
|
||
### main.jsx | ||
|
||
The main entry point of the application. Here, the application is rendered into the DOM using React's `StrictMode`. This file also includes the styles and the context for video frame processing. | ||
|
||
### App.jsx | ||
|
||
The `App` component is responsible for connecting the `AsciiArt` and `VideoStream` components. It receives the `ProcessVideoFrameContext` through props and uses it to pass necessary functions and refs to the child components. | ||
|
||
### components/index.js | ||
|
||
The export file for components. It exports `AsciiArt` and `VideoStream` components for use in other parts of the application. | ||
|
||
### AsciiArt/AsciiArt.jsx | ||
|
||
The `AsciiArt` component is responsible for displaying the ASCII art. It contains a hidden canvas for processing video frames and a `<pre>` element for rendering the ASCII art. | ||
|
||
### VideoStream/VideoStream.jsx | ||
|
||
The `VideoStream` component captures video from the camera using the `getUserMedia` API. The video is displayed in a mirrored format, and each frame is passed to the `processVideoFrame` function for ASCII conversion. | ||
|
||
### context/ProcessVideoFrameProvider.jsx | ||
|
||
The context component that provides the `processVideoFrame` function along with refs for the canvas and ASCII art output. The `processVideoFrame` function processes video frames, converts them to grayscale, and then to ASCII characters. | ||
|
||
### styles/index.min.css | ||
|
||
The stylesheet for the application, defining the basic styles. | ||
|
||
## Usage | ||
|
||
1. Ensure that you have Node.js installed. | ||
2. Clone the repository. | ||
3. Install the dependencies using `npm install`. | ||
4. Start the application using `npm start`. | ||
|
||
## Components and Contexts | ||
|
||
### AsciiArt | ||
|
||
Component for displaying ASCII art. | ||
|
||
- **props**: | ||
- `canvasRef`: a reference to the hidden canvas element. | ||
- `outputRef`: a reference to the `<pre>` element for rendering ASCII art. | ||
|
||
### VideoStream | ||
|
||
Component for capturing video from the camera. | ||
|
||
- **props**: | ||
- `processVideoFrame`: function for processing each video frame. | ||
|
||
### ProcessVideoFrameProvider | ||
|
||
Context provider that supplies functions and refs for video frame processing. | ||
|
||
- **children**: child components that will be provided with the context. | ||
|
||
### Key Parameters | ||
|
||
- **originalWidth**: original width of the video. | ||
- **originalHeight**: original height of the video. | ||
- **width**: width of the canvas for video processing (half of the original width). | ||
- **height**: height of the canvas for video processing (half of the original height). | ||
- **asciiCharacters**: string of ASCII characters used for image conversion. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,13 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Camera ASCII Transducer</title> | ||
</head> | ||
<body> | ||
<!-- Created by Alexandr Priadchenko: https://github.com/eternalvision --> | ||
<div id="root"></div> | ||
<script type="module" src="/src/main.jsx"></script> | ||
</body> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Camera ASCII Transducer</title> | ||
<script async defer type="module" src="./src/main.jsx"></script> | ||
</head> | ||
<body> | ||
<!-- Created by https://github.com/eternalvision --> | ||
<div id="root"></div> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,12 @@ | ||
import PropTypes from "prop-types"; | ||
|
||
export const AsciiArt = (props) => { | ||
const { outputRef, canvasRef } = props; | ||
|
||
return ( | ||
<> | ||
<canvas ref={canvasRef} style={{ display: "none" }}></canvas> | ||
<pre ref={outputRef}></pre> | ||
</> | ||
); | ||
}; | ||
const { outputRef, canvasRef } = props; | ||
|
||
AsciiArt.propTypes = { | ||
canvasRef: PropTypes.object.isRequired, | ||
outputRef: PropTypes.object.isRequired, | ||
return ( | ||
<> | ||
<canvas ref={canvasRef} style={{ display: "none" }}></canvas> | ||
<div className="PreFather"> | ||
<pre ref={outputRef}></pre> | ||
</div> | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,33 @@ | ||
import { useEffect, useRef } from "react"; | ||
import PropTypes from "prop-types"; | ||
|
||
export const VideoStream = ({ processVideoFrame }) => { | ||
const videoRef = useRef(null); | ||
const videoRef = useRef(null); | ||
|
||
useEffect(() => { | ||
if (navigator.mediaDevices.getUserMedia) { | ||
navigator.mediaDevices | ||
.getUserMedia({ video: true }) | ||
.then((stream) => { | ||
if (videoRef.current) { | ||
videoRef.current.srcObject = stream; | ||
videoRef.current.style.transform = "scaleX(-1)"; | ||
videoRef.current | ||
.play() | ||
.then(() => { | ||
requestAnimationFrame(() => | ||
processVideoFrame(videoRef.current), | ||
); | ||
}) | ||
.catch((err) => { | ||
console.error( | ||
"Ошибка воспроизведения видео: ", | ||
err, | ||
); | ||
}); | ||
} | ||
}) | ||
.catch((err) => { | ||
console.error("Ошибка доступа к камере: ", err); | ||
}); | ||
} | ||
}, [processVideoFrame]); | ||
useEffect(() => { | ||
if (navigator.mediaDevices.getUserMedia) { | ||
navigator.mediaDevices | ||
.getUserMedia({ video: true }) | ||
.then((stream) => { | ||
if (videoRef.current) { | ||
videoRef.current.srcObject = stream; | ||
videoRef.current.style.transform = "scaleX(-1)"; | ||
videoRef.current | ||
.play() | ||
.then(() => { | ||
requestAnimationFrame(() => | ||
processVideoFrame(videoRef.current), | ||
); | ||
}) | ||
.catch((err) => { | ||
console.error(err); | ||
}); | ||
} | ||
}) | ||
.catch((err) => { | ||
console.error(err); | ||
}); | ||
} | ||
}, [processVideoFrame]); | ||
|
||
return <video ref={videoRef}></video>; | ||
}; | ||
|
||
VideoStream.propTypes = { | ||
processVideoFrame: PropTypes.func.isRequired, | ||
return <video ref={videoRef}></video>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,73 +1,63 @@ | ||
import { createContext, useCallback, useRef } from "react"; | ||
import PropTypes from "prop-types"; | ||
|
||
export const ProcessVideoFrameContext = createContext(); | ||
|
||
const originalWidth = 1160; | ||
const originalHeight = 400; | ||
const originalWidth = 1650; | ||
const originalHeight = 450; | ||
const width = originalWidth / 2; | ||
const height = originalHeight / 2; | ||
const asciiCharacters = | ||
"@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/|()1{}[]?-_+~<>i!lI;:,\"^`'. "; | ||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; | ||
|
||
export const ProcessVideoFrameProvider = ({ children }) => { | ||
const canvasRef = useRef(null); | ||
const outputRef = useRef(null); | ||
|
||
const processVideoFrame = useCallback( | ||
(videoElement) => { | ||
const canvas = canvasRef.current; | ||
const output = outputRef.current; | ||
if (canvas && output && videoElement) { | ||
canvas.width = width; | ||
canvas.height = height; | ||
|
||
const ctx = canvas.getContext("2d", { | ||
willReadFrequently: true, | ||
}); | ||
ctx.translate(width, 0); | ||
ctx.scale(-1, 1); | ||
ctx.drawImage(videoElement, 0, 0, width, height); | ||
|
||
const imageData = ctx.getImageData(0, 0, width, height); | ||
let asciiImage = ""; | ||
const charLength = asciiCharacters.length; | ||
|
||
for (let i = 0; i < imageData.data.length; i += 4) { | ||
const gray = | ||
(imageData.data[i] + | ||
imageData.data[i + 1] + | ||
imageData.data[i + 2]) / | ||
3; | ||
const index = Math.floor((gray * charLength) / 255); | ||
asciiImage += asciiCharacters.charAt(index); | ||
|
||
if (i % (width * 4) === 0) { | ||
asciiImage += "\n"; | ||
} | ||
} | ||
|
||
output.textContent = asciiImage; | ||
} | ||
setTimeout( | ||
() => | ||
requestAnimationFrame(() => | ||
processVideoFrame(videoElement), | ||
), | ||
60, | ||
); | ||
}, | ||
[canvasRef, outputRef], | ||
); | ||
|
||
return ( | ||
<ProcessVideoFrameContext.Provider | ||
value={{ processVideoFrame, canvasRef, outputRef }}> | ||
{children} | ||
</ProcessVideoFrameContext.Provider> | ||
); | ||
}; | ||
|
||
ProcessVideoFrameProvider.propTypes = { | ||
children: PropTypes.any.isRequired, | ||
const canvasRef = useRef(null); | ||
const outputRef = useRef(null); | ||
|
||
const processVideoFrame = useCallback( | ||
(videoElement) => { | ||
const canvas = canvasRef.current; | ||
const output = outputRef.current; | ||
if (canvas && output && videoElement) { | ||
canvas.width = width; | ||
canvas.height = height; | ||
|
||
const ctx = canvas.getContext("2d", { | ||
willReadFrequently: true, | ||
}); | ||
ctx.translate(width, 0); | ||
ctx.scale(-1, 1); | ||
ctx.drawImage(videoElement, 0, 0, width, height); | ||
|
||
const imageData = ctx.getImageData(0, 0, width, height); | ||
let asciiImage = ""; | ||
const charLength = asciiCharacters.length; | ||
|
||
for (let i = 0; i < imageData.data.length; i += 4) { | ||
const gray = | ||
(imageData.data[i] + | ||
imageData.data[i + 1] + | ||
imageData.data[i + 2]) / | ||
3; | ||
const index = Math.floor((gray * charLength) / 255); | ||
asciiImage += asciiCharacters.charAt(index); | ||
|
||
if (i % (width * 4) === 0) asciiImage += "\n"; | ||
} | ||
|
||
output.textContent = asciiImage; | ||
} | ||
setTimeout( | ||
() => requestAnimationFrame(() => processVideoFrame(videoElement)), | ||
60, | ||
); | ||
}, | ||
[canvasRef, outputRef], | ||
); | ||
|
||
return ( | ||
<ProcessVideoFrameContext.Provider | ||
value={{ processVideoFrame, canvasRef, outputRef }}> | ||
{children} | ||
</ProcessVideoFrameContext.Provider> | ||
); | ||
}; |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.