From 53ad022bbac738285e2d1a760945e9b77a88715d Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Mon, 19 Jan 2026 22:25:41 +0100 Subject: [PATCH] worst init ever --- .clang-format | 37 ++++ .gitattributes | 1 + .gitignore | 10 + README.md | 20 ++ bad_apple.mp4 | 3 + src/main.cpp | 499 +++++++++++++++++++++++++++++++++++++++++++++++++ xmake.lua | 83 ++++++++ 7 files changed, 653 insertions(+) create mode 100644 .clang-format create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 README.md create mode 100644 bad_apple.mp4 create mode 100644 src/main.cpp create mode 100644 xmake.lua diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..e993027 --- /dev/null +++ b/.clang-format @@ -0,0 +1,37 @@ +Language: Cpp +BasedOnStyle: LLVM + +AlignAfterOpenBracket: DontAlign +BreakConstructorInitializers: AfterColon +ConstructorInitializerAllOnOneLineOrOnePerLine: true +PointerAlignment: Left +SortIncludes: true +SpacesBeforeTrailingComments: 2 +UseTab: Always +MaxEmptyLinesToKeep: 5 + +TabWidth: 4 +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +IndentWidth: 4 +IndentCaseLabels: true + +ColumnLimit: 135 +AlwaysBreakTemplateDeclarations: Yes + +AllowShortFunctionsOnASingleLine: Empty +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3f1946b --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +bad_apple.mp4 filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c12020 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Xmake cache +.xmake/ +build/ + +# MacOS Cache +.DS_Store + + +*.mp4 +*.mkv \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d3b96e7 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# VideoToAscii + +## Usage + +Have a file called bad_apple.mp4 in the root. +Then, + +```sh +xmake run +``` + +## Install libs + +```sh +apt install libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev libpostproc-dev libswresample-dev libswscale-dev +``` + +## Future + +I'm too lazy right now to continue this. \ No newline at end of file diff --git a/bad_apple.mp4 b/bad_apple.mp4 new file mode 100644 index 0000000..9b24dfb --- /dev/null +++ b/bad_apple.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c40324dc5832e3bb32abf1a1903f52bc2deb35aa2a620a614e996248501c570 +size 9119448 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..42013fa --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,499 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static constexpr const char vertexShader[] = R"( + #version 330 + + // Input vertex attributes + in vec2 vertexPosition; + in vec2 vertexTexCoord; + + // Output vertex attributes (to fragment shader) + out vec2 fragTexCoord; + + // NOTE: Add your custom variables here + + void main() + { + // Send vertex attributes to fragment shader + fragTexCoord = vertexTexCoord; + + // Calculate final vertex position + gl_Position = vec4(vertexPosition, 0.0, 1.0); + } +)"; + +static constexpr const char fragmentShader[] = R"( + #version 330 + + // Input from the vertex shader + in vec2 fragTexCoord; + + // Output color for the screen + out vec4 finalColor; + + uniform sampler2D texture0; + uniform vec2 resolution; + + // Fontsize less then 9 may be not complete + uniform float fontSize; + + float GreyScale(in vec3 col) + { + return dot(col, vec3(0.2126, 0.7152, 0.0722)); + } + + float GetCharacter(int n, vec2 p) + { + p = floor(p*vec2(-4.0, 4.0) + 2.5); + + // Check if the coordinate is inside the 5x5 grid (0 to 4) + if (clamp(p.x, 0.0, 4.0) == p.x && clamp(p.y, 0.0, 4.0) == p.y) + { + int a = int(round(p.x) + 5.0*round(p.y)); + if (((n >> a) & 1) == 1) + { + return 1.0; + } + } + + return 0.0; // The bit is off, or we are outside the grid + } + + // ----------------------------------------------------------------------------- + // Main shader logic + // ----------------------------------------------------------------------------- + + void main() + { + vec2 charPixelSize = vec2(fontSize, fontSize); + vec2 uvCellSize = charPixelSize/resolution; + + // The cell size is based on the fontSize set by application + vec2 cellUV = floor(fragTexCoord/uvCellSize)*uvCellSize; + + vec3 cellColor = texture(texture0, cellUV).rgb; + + // Gray is used to define what character will be selected to draw + float gray = GreyScale(cellColor); + + int n = 4096; + + // Character set from https://www.shadertoy.com/view/lssGDj + // Create new bitmaps https://thrill-project.com/archiv/coding/bitmap/ + // limited character set + // if (gray > 0.2) n = 65600; // : + // if (gray > 0.3) n = 163153; // * + // if (gray > 0.4) n = 15255086; // o + // if (gray > 0.5) n = 13121101; // & + // if (gray > 0.6) n = 15252014; // 8 + // if (gray > 0.7) n = 13195790; // @ + // if (gray > 0.8) n = 11512810; // # + + if (gray > 0.0233) n = 4096; + if (gray > 0.0465) n = 131200; + if (gray > 0.0698) n = 4329476; + if (gray > 0.0930) n = 459200; + if (gray > 0.1163) n = 4591748; + if (gray > 0.1395) n = 12652620; + if (gray > 0.1628) n = 14749828; + if (gray > 0.1860) n = 18393220; + if (gray > 0.2093) n = 15239300; + if (gray > 0.2326) n = 17318431; + if (gray > 0.2558) n = 32641156; + if (gray > 0.2791) n = 18393412; + if (gray > 0.3023) n = 18157905; + if (gray > 0.3256) n = 17463428; + if (gray > 0.3488) n = 14954572; + if (gray > 0.3721) n = 13177118; + if (gray > 0.3953) n = 6566222; + if (gray > 0.4186) n = 16269839; + if (gray > 0.4419) n = 18444881; + if (gray > 0.4651) n = 18400814; + if (gray > 0.4884) n = 33061392; + if (gray > 0.5116) n = 15255086; + if (gray > 0.5349) n = 32045584; + if (gray > 0.5581) n = 18405034; + if (gray > 0.5814) n = 15022158; + if (gray > 0.6047) n = 15018318; + if (gray > 0.6279) n = 16272942; + if (gray > 0.6512) n = 18415153; + if (gray > 0.6744) n = 32641183; + if (gray > 0.6977) n = 32540207; + if (gray > 0.7209) n = 18732593; + if (gray > 0.7442) n = 18667121; + if (gray > 0.7674) n = 16267326; + if (gray > 0.7907) n = 32575775; + if (gray > 0.8140) n = 15022414; + if (gray > 0.8372) n = 15255537; + if (gray > 0.8605) n = 32032318; + if (gray > 0.8837) n = 32045617; + if (gray > 0.9070) n = 33081316; + if (gray > 0.9302) n = 32045630; + if (gray > 0.9535) n = 33061407; + if (gray > 0.9767) n = 11512810; + + vec2 localUV = (fragTexCoord - cellUV)/uvCellSize; // Range [0.0, 1.0] + + vec2 p = localUV*2.0 - 1.0; // Range [-1.0, 1.0] + + vec3 color = cellColor*GetCharacter(n, p); + + finalColor = vec4(color, 1.0); + + // finalColor = texture(texture0, fragTexCoord); + } +)"; + +static unsigned int vertexShaderID, fragmentShaderID, programID; +static unsigned int location_resolution, location_fontSize; +static unsigned int location_texture0; + +static constexpr float vertexData[] = { + -1, + -1, + 0, + 0, + 1, + -1, + 1, + 0, + -1, + 1, + 0, + 1, + + 1, + 1, + 1, + 1, + -1, + 1, + 0, + 1, + 1, + -1, + 1, + 0, +}; + + +static const EGLint configAttribs[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, + EGL_DEPTH_SIZE, 8, + + // Uncomment the following to enable MSAA + // EGL_SAMPLE_BUFFERS, 1, // <-- Must be set to 1 to enable multisampling! + // EGL_SAMPLES, 4, // <-- Number of samples + + // Uncomment the following to enable stencil buffer + EGL_STENCIL_SIZE, 1, + + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE}; + +static const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + +static const char* eglGetErrorStr() { + switch (eglGetError()) { + case EGL_SUCCESS: + return "The last function succeeded without error."; + case EGL_NOT_INITIALIZED: + return "EGL is not initialized, or could not be initialized, for the " + "specified EGL display connection."; + case EGL_BAD_ACCESS: + return "EGL cannot access a requested resource (for example a context " + "is bound in another thread)."; + case EGL_BAD_ALLOC: + return "EGL failed to allocate resources for the requested operation."; + case EGL_BAD_ATTRIBUTE: + return "An unrecognized attribute or attribute value was passed in the " + "attribute list."; + case EGL_BAD_CONTEXT: + return "An EGLContext argument does not name a valid EGL rendering " + "context."; + case EGL_BAD_CONFIG: + return "An EGLConfig argument does not name a valid EGL frame buffer " + "configuration."; + case EGL_BAD_CURRENT_SURFACE: + return "The current surface of the calling thread is a window, pixel " + "buffer or pixmap that is no longer valid."; + case EGL_BAD_DISPLAY: + return "An EGLDisplay argument does not name a valid EGL display " + "connection."; + case EGL_BAD_SURFACE: + return "An EGLSurface argument does not name a valid surface (window, " + "pixel buffer or pixmap) configured for GL rendering."; + case EGL_BAD_MATCH: + return "Arguments are inconsistent (for example, a valid context " + "requires buffers not supplied by a valid surface)."; + case EGL_BAD_PARAMETER: + return "One or more argument values are invalid."; + case EGL_BAD_NATIVE_PIXMAP: + return "A NativePixmapType argument does not refer to a valid native " + "pixmap."; + case EGL_BAD_NATIVE_WINDOW: + return "A NativeWindowType argument does not refer to a valid native " + "window."; + case EGL_CONTEXT_LOST: + return "A power management event has occurred. The application must " + "destroy all contexts and reinitialise OpenGL ES state and " + "objects to continue rendering."; + default: + break; + } + return "Unknown error!"; +} + +static GLint width, height; + +static EGLContext context; +static EGLDisplay display; +static EGLSurface surface; + +bool createContext(const int& viewport_width, const int& viewport_height) { + width = viewport_width; + height = viewport_height; + + int major, minor; + + if ((display = eglGetDisplay(EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY) { + fprintf(stderr, "Failed to get EGL display! Error: %s\n", eglGetErrorStr()); + return false; + } + + if (eglInitialize(display, &major, &minor) == EGL_FALSE) { + fprintf(stderr, "Failed to get EGL version! Error: %s\n", eglGetErrorStr()); + eglTerminate(display); + return false; + } + + printf("Initialized EGL version: %d.%d\n", major, minor); + + EGLint numConfigs; + EGLConfig config; + if (!eglChooseConfig(display, configAttribs, &config, 1, &numConfigs)) { + fprintf(stderr, "Failed to get EGL config! Error: %s\n", eglGetErrorStr()); + eglTerminate(display); + return false; + } + //---------------------------------------------- + EGLint pbufferAttribs[] = { + EGL_WIDTH, + width, + EGL_HEIGHT, + height, + EGL_NONE, + }; + surface = eglCreatePbufferSurface(display, config, pbufferAttribs); + if (surface == EGL_NO_SURFACE) { + fprintf(stderr, "Failed to create EGL surface! Error: %s\n", eglGetErrorStr()); + eglTerminate(display); + return false; + } + + eglBindAPI(EGL_OPENGL_API); + + context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); + if (context == EGL_NO_CONTEXT) { + fprintf(stderr, "Failed to create EGL context! Error: %s\n", eglGetErrorStr()); + eglDestroySurface(display, surface); + eglTerminate(display); + return false; + } + + eglMakeCurrent(display, surface, surface, context); + + // Set GL Viewport size, always needed! + glViewport(0, 0, width, height); + + // Get GL Viewport size and test if it is correct. + // NOTE! DO NOT UPDATE EGL LIBRARY ON RASPBERRY PI AS IT WILL INSTALL FAKE + // EGL! If you have fake/faulty EGL library, the glViewport and + // glGetIntegerv won't work! The following piece of code checks if the gl + // functions are working as intended! + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + + // viewport[2] and viewport[3] are viewport width and height respectively + printf("GL Viewport size: %dx%d\n", viewport[2], viewport[3]); + + // Test if the desired width and height match the one returned by + // glGetIntegerv + if (width != viewport[2] || height != viewport[3]) { + fprintf(stderr, "Error! The glViewport/glGetIntegerv are not working! " + "EGL might be faulty!\n"); + return false; + } + return true; +} + +unsigned char* getRawPixels() { + // Create buffer to hold entire front buffer pixels + // We multiply width and height by 4 to because we use RGBA! + const int comp = 3; + const int dataSize = width * height * comp; + unsigned char* buffer = new unsigned char[dataSize]; + + glReadPixels(0, 0, width, height, GL_BGR, GL_UNSIGNED_BYTE, buffer); + + /*if (flipped){ + unsigned char flipPixels[dataSize]; + for (int i = 0; i < width; ++i){ + for (int j = 0; j < height; ++j){ + for (int k = 0; k < comp; ++k){ + flipPixels[(i + j * width) * comp + k] = buffer[(i + + (height - 1 - j) * width) * comp + k]; + } + } + } + std::copy(flipPixels, flipPixels + dataSize, buffer); + }*/ + return buffer; +} + +void destroyContext() { + // Cleanup + eglDestroyContext(display, context); + eglDestroySurface(display, surface); + eglTerminate(display); +} + +unsigned int initShader(GLenum type, const char* shader, int size) { + unsigned int shaderID = glCreateShader(type); + + glShaderSource(shaderID, 1, &shader, &size); // @suppress("Function cannot be resolved") + glCompileShader(shaderID); + GLint compilesuccessful; + glGetShaderiv(shaderID, GL_COMPILE_STATUS, &compilesuccessful); + if (compilesuccessful == (int)GL_FALSE) { + std::cout << "Could not compile shader !" << std::endl; + GLsizei size; + glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &size); + char error[size]; + glGetShaderInfoLog(shaderID, size, &size, error); + std::cout << error; + } + return shaderID; +} + +void initShaders(int width, int height) { + programID = glCreateProgram(); + vertexShaderID = initShader(GL_VERTEX_SHADER, vertexShader, sizeof(vertexShader)); + fragmentShaderID = initShader(GL_FRAGMENT_SHADER, fragmentShader, sizeof(fragmentShader)); + glAttachShader(programID, vertexShaderID); + glAttachShader(programID, fragmentShaderID); + glBindAttribLocation(vertexShaderID, 0, "vertexPosition"); + glBindAttribLocation(vertexShaderID, 1, "vertexTexCoord"); + glLinkProgram(programID); + glValidateProgram(programID); + location_texture0 = glGetUniformLocation(programID, "texture0"); + location_resolution = glGetUniformLocation(programID, "resolution"); + location_fontSize = glGetUniformLocation(programID, "fontSize"); + glUseProgram(programID); + glUniform2f(location_resolution, width, height); + glUniform1f(location_fontSize, 6.0f); +} + +unsigned int vboID; + +void initGL(int width, int height) { + if (!glewInit()) { + std::cout << "Failed to initialize glew!\n"; + } + + initShaders(width, height); + glCreateBuffers(1, &vboID); + glBindBuffer(GL_ARRAY_BUFFER, vboID); + glVertexAttribPointer(0, 2, GL_FLOAT, false, 4 * sizeof(float), (void*)0); + glVertexAttribPointer(1, 2, GL_FLOAT, false, 4 * sizeof(float), (void*)8); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW); +} + +void loadTextureImage(int textureID, int width, int height, const cv::Mat& image) { + glBindTexture(GL_TEXTURE_2D, textureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + // This prevents having weird edges + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, image.ptr()); +} + + +void draw() { + + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + + glBindBuffer(GL_ARRAY_BUFFER, vboID); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + + glDrawArrays(GL_TRIANGLES, 0, 6); + + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(0); +} + + +int main() { + cv::VideoCapture video("bad_apple.mp4"); + int width = video.get(cv::VideoCaptureProperties::CAP_PROP_FRAME_WIDTH); + int height = video.get(cv::VideoCaptureProperties::CAP_PROP_FRAME_HEIGHT); + int frameCount = video.get(cv::VideoCaptureProperties::CAP_PROP_FRAME_COUNT); + int fps = video.get(cv::VideoCaptureProperties::CAP_PROP_FPS); + + cv::VideoWriter output("outcpp.mkv", cv::VideoWriter::fourcc('H', '2', '6', '4'), fps, cv::Size(width, height)); + createContext(width, height); + + initGL(width, height); + + int currentFrame = 0; + + unsigned int texture0; + glGenTextures(1, &texture0); + + glEnable(GL_TEXTURE_2D); + + glUseProgram(programID); + + int writtenFrame = 0; + + std::cout << "Converting...\n"; + + auto start = std::chrono::system_clock::now(); + + for (int i = 0; i < frameCount; i++) { + std::cout << "\rFrame " << i << " / " << frameCount; + cv::Mat image; + video >> image; + + glActiveTexture(GL_TEXTURE0); + loadTextureImage(texture0, width, height, image); + + writtenFrame++; + + draw(); + unsigned char* pixels = getRawPixels(); + cv::Mat pixelArray(cv::Size(width, height), CV_8UC3, cv::Scalar(0, 0, 0)); + pixelArray.data = pixels; + output << pixelArray; + pixelArray.release(); + delete pixels; + writtenFrame++; + + image.release(); + } + + std::cout << std::endl; + + auto end = std::chrono::system_clock::now(); + std::cout << "Done in " << std::chrono::duration_cast(end - start).count() << "s!\n"; + + destroyContext(); +} \ No newline at end of file diff --git a/xmake.lua b/xmake.lua new file mode 100644 index 0000000..868929a --- /dev/null +++ b/xmake.lua @@ -0,0 +1,83 @@ +add_rules("mode.debug", "mode.release") + +add_requires("ffmpeg", {system = true}) +add_requires("opencv", "glew") + +add_requires("raylib") + +target("VideoToAscii") + set_kind("binary") + add_packages("opencv", "glew") + add_links("EGL") + add_files("src/main.cpp") + set_rundir(".") + +-- +-- If you want to known more usage about xmake, please see https://xmake.io +-- +-- ## FAQ +-- +-- You can enter the project directory firstly before building project. +-- +-- $ cd projectdir +-- +-- 1. How to build project? +-- +-- $ xmake +-- +-- 2. How to configure project? +-- +-- $ xmake f -p [macosx|linux|iphoneos ..] -a [x86_64|i386|arm64 ..] -m [debug|release] +-- +-- 3. Where is the build output directory? +-- +-- The default output directory is `./build` and you can configure the output directory. +-- +-- $ xmake f -o outputdir +-- $ xmake +-- +-- 4. How to run and debug target after building project? +-- +-- $ xmake run [targetname] +-- $ xmake run -d [targetname] +-- +-- 5. How to install target to the system directory or other output directory? +-- +-- $ xmake install +-- $ xmake install -o installdir +-- +-- 6. Add some frequently-used compilation flags in xmake.lua +-- +-- @code +-- -- add debug and release modes +-- add_rules("mode.debug", "mode.release") +-- +-- -- add macro definition +-- add_defines("NDEBUG", "_GNU_SOURCE=1") +-- +-- -- set warning all as error +-- set_warnings("all", "error") +-- +-- -- set language: c99, c++11 +-- set_languages("c99", "c++11") +-- +-- -- set optimization: none, faster, fastest, smallest +-- set_optimize("fastest") +-- +-- -- add include search directories +-- add_includedirs("/usr/include", "/usr/local/include") +-- +-- -- add link libraries and search directories +-- add_links("tbox") +-- add_linkdirs("/usr/local/lib", "/usr/lib") +-- +-- -- add system link libraries +-- add_syslinks("z", "pthread") +-- +-- -- add compilation and link flags +-- add_cxflags("-stdnolib", "-fno-strict-aliasing") +-- add_ldflags("-L/usr/local/lib", "-lpthread", {force = true}) +-- +-- @endcode +-- +