Implementing my own C libs

This commit is contained in:
Harshit-Dhanwalkar 2026-06-08 13:36:51 +05:30
parent c0ddc27b5d
commit 5f98c5b633
24 changed files with 1245 additions and 381 deletions

View file

@ -2,42 +2,39 @@
#include <immintrin.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nolibc.h"
// Helpers
static inline uint8_t clamp_u8(int v) {
return (v < 0) ? 0 : (v > 255) ? 255 : (uint8_t)v;
}
static inline int my_abs(int x) {
return x < 0 ? -x : x;
}
// Image conversion
// void yuyv_to_gray(const uint8_t *yuyv, uint8_t *gray, int width, int height) {
// int n = width * height;
// for (int i = 0; i < n; i++)
// gray[i] = yuyv[i * 2]; // Y sample at even bytes in YUYV format
// }
void yuyv_to_gray_simd(const uint8_t *yuyv, uint8_t *gray, int width, int height) {
int total_pixels = width * height;
void yuyv_to_gray_simd(const uint8_t *yuyv, uint8_t *gray, int width,
int height) {
int total_pixels = width * height;
// Mask to extract every even byte (Y samples)
__m128i mask = _mm_set1_epi16(0x00FF);
// Mask to extract every even byte (Y samples)
__m128i mask = _mm_set1_epi16(0x00FF);
int i = 0;
for (; i + 16 <= total_pixels; i += 16) {
// Load 32 bytes of YUYV (= 16 pixels)
__m128i lo = _mm_loadu_si128((__m128i *)(yuyv + i*2));
__m128i hi = _mm_loadu_si128((__m128i *)(yuyv + i*2 + 16));
// low byte of each 16-bit word (the Y sample)
lo = _mm_and_si128(lo, mask);
hi = _mm_and_si128(hi, mask);
// Pack 16-bit
__m128i result = _mm_packus_epi16(lo, hi);
_mm_storeu_si128((__m128i *)(gray + i), result);
}
for (; i < total_pixels; i++)
gray[i] = yuyv[i * 2];
int i = 0;
for (; i + 16 <= total_pixels; i += 16) {
// Load 32 bytes of YUYV (= 16 pixels)
__m128i lo = _mm_loadu_si128((__m128i *)(yuyv + i * 2));
__m128i hi = _mm_loadu_si128((__m128i *)(yuyv + i * 2 + 16));
// low byte of each 16-bit word (the Y sample)
lo = _mm_and_si128(lo, mask);
hi = _mm_and_si128(hi, mask);
// Pack 16-bit
__m128i result = _mm_packus_epi16(lo, hi);
_mm_storeu_si128((__m128i *)(gray + i), result);
}
for (; i < total_pixels; i++)
gray[i] = yuyv[i * 2];
}
void yuyv_to_rgb(const uint8_t *yuyv, uint8_t *rgb, int width, int height) {
@ -59,49 +56,6 @@ void yuyv_to_rgb(const uint8_t *yuyv, uint8_t *rgb, int width, int height) {
}
}
}
// void yuyv_to_rgb_simd(const uint8_t *yuyv, uint8_t *rgb, int width, int height) {
// int total_bytes = width * height * 2;
// int i = 0;
//
// // Vectors of constants for color weights shifted up by 8 bits
// __m128i r_v_weight = _mm_set1_epi16(409);
// __m128i g_u_weight = _mm_set1_epi16(-100);
// __m128i g_v_weight = _mm_set1_epi16(-208);
// __m128i b_u_weight = _mm_set1_epi16(516);
// __m128i const_128 = _mm_set1_epi16(128);
//
// for (; i + 8 <= total_bytes; i += 8) {
// // Load 8 bytes of YUYV = [Y0, U0, Y1, V0, Y2, U1, Y3, V1]
// // Cast directly into a 64-bit load to avoid polluting register spaces
// long long chunk = *(const long long *)(yuyv + i);
// __m128i raw = _mm_cvtsi64_si128(chunk);
//
// // Unpack bytes to 16-bit integers to hold intermediate precision math
// __m128i pixels = _mm_unpacklo_epi8(raw, _mm_setzero_si128());
//
// // Extract channels across structural layouts
// int16_t y0 = _mm_extract_epi16(pixels, 0) - 16;
// int16_t u0 = _mm_extract_epi16(pixels, 1) - 128;
// int16_t y1 = _mm_extract_epi16(pixels, 2) - 16;
// int16_t v0 = _mm_extract_epi16(pixels, 3) - 128;
//
// int16_t y2 = _mm_extract_epi16(pixels, 4) - 16;
// int16_t u1 = _mm_extract_epi16(pixels, 5) - 128;
// int16_t y3 = _mm_extract_epi16(pixels, 6) - 16;
// int16_t v1 = _mm_extract_epi16(pixels, 7) - 128;
//
// // Perform fixed-point math approximations using scalar elements or smaller vector groupings
// // Example for first pixel pair layout:
// int32_t r0 = clamp_u8((298 * y0 + 409 * v0 + 128) >> 8);
// int32_t g0 = clamp_u8((298 * y0 - 100 * u0 - 208 * v0 + 128) >> 8);
// int32_t b0 = clamp_u8((298 * y0 + 516 * u0 + 128) >> 8);
//
// // Write sequentially to interleaved pointer layout
// int rgb_offset = (i / 4) * 6;
// rgb[rgb_offset + 0] = r0; rgb[rgb_offset + 1] = g0; rgb[rgb_offset + 2] = b0;
// // Continue similarly down line iterations...
// }
// }
// Buffer sizing
size_t ascii_out_size(int dst_w, int dst_h, int color) {
@ -118,16 +72,8 @@ size_t ascii_out_size(int dst_w, int dst_h, int color) {
// Sobel edge detection (kernel convolution)
static void sobel(const uint8_t *in, uint8_t *out, int w, int h) {
static const int Gx[3][3] = {
{-1, 0, 1},
{-2, 0, 2},
{-1, 0, 1}
};
static const int Gy[3][3] = {
{-1, -2, -1},
{0, 0, 0},
{1, 2, 1}
};
static const int Gx[3][3] = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}};
static const int Gy[3][3] = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};
for (int y = 1; y < h - 1; y++) {
for (int x = 1; x < w - 1; x++) {
@ -141,7 +87,7 @@ static void sobel(const uint8_t *in, uint8_t *out, int w, int h) {
}
// TEST: test both L1 and L2 normalisations
int mag = abs(gx) + abs(gy); // L1 normalisation
int mag = my_abs(gx) + my_abs(gy); // L1 normalisation
// int mag = sqrt(gx * gx + gy * gy); // L2 normalisation
out[y * w + x] = (uint8_t)(mag > 255 ? 255 : mag);
@ -153,22 +99,23 @@ static void sobel(const uint8_t *in, uint8_t *out, int w, int h) {
int grayscale_to_ascii(const uint8_t *gray, const uint8_t *rgb, int src_w,
int src_h, int dst_w, int dst_h, char *out,
size_t out_size, const ascii_opts_t *opts) {
const char *charset = (opts && opts->charset) ? opts->charset : ASCII_CHARS_DEFAULT;
int nchars = (int)strlen(charset);
int brightness = opts ? opts->brightness : 0;
int contrast = opts ? opts->contrast : 100;
int invert = opts ? opts->invert : 0;
int do_color = opts && opts->color && (rgb != NULL);
int do_edges = opts ? opts->edges : 0;
int do_dither = opts ? opts->dither : 0;
const char *charset =
(opts && opts->charset) ? opts->charset : ASCII_CHARS_DEFAULT;
int nchars = (int)strlen(charset);
int brightness = opts ? opts->brightness : 0;
int contrast = opts ? opts->contrast : 100;
int invert = opts ? opts->invert : 0;
int do_color = opts && opts->color && (rgb != NULL);
int do_edges = opts ? opts->edges : 0;
int do_dither = opts ? opts->dither : 0;
// Blocking width and height pixels in source pixels
double bw = (double)src_w / dst_w;
double bh = (double)src_h / dst_h;
double bw = (double)src_w / dst_w;
double bh = (double)src_h / dst_h;
// Downsample to (dst_w x dst_h)
uint8_t *small_g = malloc(dst_w * dst_h);
uint8_t *small_rgb = do_color ? malloc(dst_w * dst_h * 3) : NULL;
uint8_t *small_g = malloc(dst_w * dst_h);
uint8_t *small_rgb = do_color ? malloc(dst_w * dst_h * 3) : NULL;
if (!small_g || (do_color && !small_rgb)) {
free(small_g);
@ -328,22 +275,25 @@ int grayscale_to_ascii(const uint8_t *gray, const uint8_t *rgb, int src_w,
return out_idx;
}
// FPS overlay
void overlay_fps_box(int dst_w, double fps, int color_enabled) {
char buf[80];
int col = (dst_w - 13) / 2 + 1;
if (col < 1) col = 1;
int col = (dst_w - 13) / 2 + 1;
if (col < 1)
col = 1;
int n;
if (color_enabled) {
n = snprintf(buf, sizeof(buf),
"\033[1;%dH\033[38;2;0;255;0m\033[48;2;30;30;30m[ FPS: %4.1f ]\033[0m",
col, fps);
char fpsbuf[10];
nl_fmt_fps(fpsbuf, sizeof(fpsbuf), fps);
n = snprintf(
buf, sizeof(buf),
"\033[1;%dH\033[38;2;0;255;0m\033[48;2;30;30;30m[ FPS: %s ]\033[0m",
col, fpsbuf);
} else {
n = snprintf(buf, sizeof(buf),
"\033[1;%dH[ FPS: %4.1f ]",
col, fps);
char fpsbuf[10];
nl_fmt_fps(fpsbuf, sizeof(fpsbuf), fps);
n = snprintf(buf, sizeof(buf), "\033[1;%dH[ FPS: %s ]", col, fpsbuf);
}
if (n > 0 && n < (int)sizeof(buf))
(void)write(STDOUT_FILENO, buf, (size_t)n);

View file

@ -1,138 +1,135 @@
#include "capture.h"
#include "ascii.h"
#include <errno.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/select.h>
#include <unistd.h>
#include "nolibc.h"
int webcam_init(webcam_t *cam, const char *device, int width, int height) {
// Open device (nonblocking for select usage)
cam->fd = open(device, O_RDWR | O_NONBLOCK);
if (cam->fd < 0) return -1;
// Open device (nonblocking for select usage)
cam->fd = open(device, O_RDWR | O_NONBLOCK, 0);
if (cam->fd < 0)
return -1;
// Set format
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_NONE;
// Set format
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_NONE;
if (ioctl(cam->fd, VIDIOC_S_FMT, &fmt) < 0) {
close(cam->fd);
return -1;
}
if (ioctl(cam->fd, VIDIOC_S_FMT, &fmt) < 0) {
close(cam->fd);
return -1;
}
// Read back actual resolution
cam->width = fmt.fmt.pix.width;
cam->height = fmt.fmt.pix.height;
// Read back actual resolution
cam->width = fmt.fmt.pix.width;
cam->height = fmt.fmt.pix.height;
// Request one mmap buffer
struct v4l2_requestbuffers req = {0};
req.count = 1;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
// Request one mmap buffer
struct v4l2_requestbuffers req = {0};
req.count = 1;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(cam->fd, VIDIOC_REQBUFS, &req) < 0) {
close(cam->fd);
return -1;
}
if (ioctl(cam->fd, VIDIOC_REQBUFS, &req) < 0) {
close(cam->fd);
return -1;
}
// Query buffer info
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = 0;
// Query buffer info
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = 0;
if (ioctl(cam->fd, VIDIOC_QUERYBUF, &buf) < 0) {
close(cam->fd);
return -1;
}
if (ioctl(cam->fd, VIDIOC_QUERYBUF, &buf) < 0) {
close(cam->fd);
return -1;
}
// mmap
cam->buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,
MAP_SHARED, cam->fd, buf.m.offset);
if (cam->buffer == MAP_FAILED) {
close(cam->fd);
return -1;
}
// mmap
cam->buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
cam->fd, buf.m.offset);
if (cam->buffer == MAP_FAILED) {
close(cam->fd);
return -1;
}
// Store buffer info for later munmap and requeue
cam->buf_info = buf;
// Store buffer info for later munmap and requeue
cam->buf_info = buf;
// Queue the buffer
if (ioctl(cam->fd, VIDIOC_QBUF, &buf) < 0) {
munmap(cam->buffer, buf.length);
close(cam->fd);
return -1;
}
// Queue the buffer
if (ioctl(cam->fd, VIDIOC_QBUF, &buf) < 0) {
munmap(cam->buffer, buf.length);
close(cam->fd);
return -1;
}
// Start streaming
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(cam->fd, VIDIOC_STREAMON, &type) < 0) {
munmap(cam->buffer, buf.length);
close(cam->fd);
return -1;
}
// Start streaming
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(cam->fd, VIDIOC_STREAMON, &type) < 0) {
munmap(cam->buffer, buf.length);
close(cam->fd);
return -1;
}
return 0;
return 0;
}
int webcam_wait_frame(webcam_t *cam, int timeout_ms) {
fd_set fds;
struct timeval tv;
FD_ZERO(&fds);
FD_SET(cam->fd, &fds);
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
nl_fd_set fds;
struct nl_timeval tv;
NL_FD_ZERO(&fds);
NL_FD_SET(cam->fd, &fds);
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
int ret = select(cam->fd + 1, &fds, NULL, NULL, &tv);
if (ret <= 0) return -1; // timeout or error
return 0;
int ret = nl_select(cam->fd + 1, &fds, (nl_fd_set *)0, (nl_fd_set *)0, &tv);
if (ret <= 0)
return -1; // timeout or error
return 0;
}
int webcam_capture_frame(webcam_t *cam, uint8_t *gray_buffer) {
// Dequeue buffer
struct v4l2_buffer buf = cam->buf_info;
if (ioctl(cam->fd, VIDIOC_DQBUF, &buf) < 0) return -1;
// Dequeue buffer
struct v4l2_buffer buf = cam->buf_info;
if (ioctl(cam->fd, VIDIOC_DQBUF, &buf) < 0)
return -1;
// Convert YUYV -> grayscale (Y component)
// uint8_t *yuyv = (uint8_t *)cam->buffer;
// for (int i = 0, j = 0; i < cam->width * cam->height * 2; i += 2, j++) {
// gray_buffer[j] = yuyv[i];
// }
int w = cam->width;
int h = cam->height;
yuyv_to_gray_simd((uint8_t *)cam->buffer, gray_buffer, w, h);
// Convert YUYV -> grayscale (Y component)
// uint8_t *yuyv = (uint8_t *)cam->buffer;
// for (int i = 0, j = 0; i < cam->width * cam->height * 2; i += 2, j++) {
// gray_buffer[j] = yuyv[i];
// }
int w = cam->width;
int h = cam->height;
yuyv_to_gray_simd((uint8_t *)cam->buffer, gray_buffer, w, h);
// Store updated buffer info for requeue
cam->buf_info = buf;
return 0;
// Store updated buffer info for requeue
cam->buf_info = buf;
return 0;
}
int webcam_requeue_buffer(webcam_t *cam) {
if (ioctl(cam->fd, VIDIOC_QBUF, &cam->buf_info) < 0) return -1;
return 0;
if (ioctl(cam->fd, VIDIOC_QBUF, &cam->buf_info) < 0)
return -1;
return 0;
}
void webcam_cleanup(webcam_t *cam) {
if (cam->fd >= 0) {
// Stop streaming
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(cam->fd, VIDIOC_STREAMOFF, &type);
// Unmap and close
if (cam->buffer != MAP_FAILED)
munmap(cam->buffer, cam->buf_info.length);
close(cam->fd);
}
cam->fd = -1;
cam->buffer = MAP_FAILED;
if (cam->fd >= 0) {
// Stop streaming
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(cam->fd, VIDIOC_STREAMOFF, &type);
// Unmap and close
if (cam->buffer != MAP_FAILED)
munmap(cam->buffer, cam->buf_info.length);
close(cam->fd);
}
cam->fd = -1;
cam->buffer = MAP_FAILED;
}

View file

@ -4,27 +4,26 @@
#include "thread_sharing.h"
#include "timing.h"
#include <dlfcn.h>
#include <getopt.h>
#include <pthread.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/mman.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#include "nolibc.h"
typedef int sig_atomic_t;
#define MAP_FAILED ((void *)-1)
#define PROT_READ 1
#define PROT_WRITE 2
#define MAP_SHARED 1
// Defaults
#define DEFAULT_ASCII_WIDTH 80
#define DEFAULT_ASCII_HEIGHT 40
#define DEFAULT_CAPTURE_WIDTH 160
#define DEFAULT_ASCII_WIDTH 80
#define DEFAULT_ASCII_HEIGHT 40
#define DEFAULT_CAPTURE_WIDTH 160
#define DEFAULT_CAPTURE_HEIGHT 120
#define DEFAULT_FPS 20
#define MAX_PLUGINS 8
#define DEFAULT_FPS 20
#define MAX_PLUGINS 8
// Signal handling
volatile sig_atomic_t keep_running = 1;
@ -35,6 +34,17 @@ void handle_signal(int sig) {
static struct termios orig_terminal;
static int my_atoi(const char *s) {
int n = 0, neg = 0;
if (*s == '-') {
neg = 1;
s++;
}
while (*s >= '0' && *s <= '9')
n = n * 10 + (*s++ - '0');
return neg ? -n : n;
}
// Usage
static void print_usage(const char *prog) {
fprintf(
@ -60,8 +70,7 @@ static void print_usage(const char *prog) {
" -e enable Sobel edge detection \n"
" -C ANSI truecolor output \n"
" -D Floyd-Steinberg dithering \n",
prog,
DEFAULT_CAPTURE_WIDTH, DEFAULT_CAPTURE_HEIGHT, DEFAULT_FPS,
prog, DEFAULT_CAPTURE_WIDTH, DEFAULT_CAPTURE_HEIGHT, DEFAULT_FPS,
DEFAULT_ASCII_WIDTH, DEFAULT_ASCII_HEIGHT, ASCII_CHARS_DEFAULT);
}
@ -69,82 +78,71 @@ static void print_usage(const char *prog) {
void term_raw_mode(void) {
tcgetattr(STDIN_FILENO, &orig_terminal); // save stdin state
struct termios raw = orig_terminal;
raw.c_lflag &= ~(ICANON | ECHO); // no line buffering or no echo
raw.c_cc[VMIN] = 0; // non-blocking read
raw.c_cc[VTIME] = 0;
raw.c_lflag &= ~(ICANON | ECHO); // no line buffering or no echo
raw.c_cc[VMIN] = 0; // non-blocking read
raw.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
void term_restore(void) { tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_terminal); }
static void overlay_panel(int ascii_h, double fps,
plugin_loader_t *plugins, int *plugin_params,
int count, int selected, int color) {
static void overlay_panel(int ascii_h, double fps, plugin_loader_t *plugins,
int *plugin_params, int count, int selected,
int color) {
char buf[1024];
int n, base_row = ascii_h + 1; // 1-indexed panel row
// const char *name = pl->plugin ? pl->plugin->name : "none";
// const char *status = pl->status_msg[0] ? pl->status_msg : "ok";
//
// if (color) {
// n = snprintf(buf, sizeof(buf),
// "\033[%d;1H\033[38;2;180;180;0m\033[48;2;18;18;18m"
// " plugin: %-14s | %s | param: %3d ([ ] ±1 { } ±10 r=reset) "
// "\033[0m\033[K",
// row, name, status, plugin_param);
// } else {
// n = snprintf(buf, sizeof(buf),
// "\033[%d;1H"
// " plugin: %-14s | %s | param: %3d ([ ] ±1 { } ±10 r=reset)"
// "\033[K",
// row, name, status, plugin_param);
// }
// if (n > 0 && n < (int)sizeof(buf))
// (void)write(STDOUT_FILENO, buf, (size_t)n);
// FPS + hint bar
char fpsbuf[10];
nl_fmt_fps(fpsbuf, sizeof(fpsbuf), fps);
if (color) {
n = snprintf(buf, sizeof(buf),
"\033[%d;1H\033[38;2;0;220;0m\033[48;2;18;18;18m"
" FPS: %4.1f │ ↑↓ select [ ] ±1 { } ±10 r reset q quit "
"\033[0m\033[K", base_row, fps);
n = nl_snprintf(buf, sizeof(buf),
"\033[%d;1H\033[38;2;0;220;0m\033[48;2;18;18;18m"
" FPS: %s │ ↑↓ select [ ] ±1 { } ±10 r reset q quit "
"\033[0m\033[K",
base_row, fpsbuf);
} else {
n = snprintf(buf, sizeof(buf),
"\033[%d;1H FPS: %4.1f | up/dn select [ ] +-1 { } +-10 r reset q quit\033[K",
base_row, fps);
n = nl_snprintf(buf, sizeof(buf),
"\033[%d;1H FPS: %s | up/dn select [ ] +-1 { } +-10 r "
"reset q quit\033[K",
base_row, fpsbuf);
}
if (n > 0 && n < (int)sizeof(buf))
(void)write(STDOUT_FILENO, buf, (size_t)n);
n = snprintf(buf, sizeof(buf), "\033[%d;1H\033[K", base_row + 1);
if (n > 0) (void)write(STDOUT_FILENO, buf, (size_t)n);
n = nl_snprintf(buf, sizeof(buf), "\033[%d;1H\033[K", base_row + 1);
if (n > 0)
(void)write(STDOUT_FILENO, buf, (size_t)n);
// Plugin cells
if (count == 0) {
const char *msg = color
? "\033[38;2;120;120;120m no plugins loaded \033[0m"
: " no plugins loaded";
const char *msg = color ? "\033[38;2;120;120;120m no plugins loaded \033[0m"
: " no plugins loaded";
(void)write(STDOUT_FILENO, msg, strlen(msg));
return;
}
for (int i = 0; i < count; i++) {
const char *name = plugins[i].plugin ? plugins[i].plugin->name : "???";
int param = plugin_params[i];
int is_sel = (i == selected);
int param = plugin_params[i];
int is_sel = (i == selected);
if (color) {
// Selected: bright yellow text on dark blue bg; others: dim
if (is_sel)
n = snprintf(buf, sizeof(buf),
"\033[38;2;255;220;0m\033[48;2;0;40;80m"
" ▶ %s [%3d] \033[0m ", name, param);
else
n = snprintf(buf, sizeof(buf),
"\033[38;2;140;140;140m\033[48;2;18;18;18m"
" %s [%3d] \033[0m ", name, param);
if (is_sel) {
n = nl_snprintf(buf, sizeof(buf),
"\033[38;2;255;220;0m\033[48;2;0;40;80m"
" ▶ %s [%3d] \033[0m ",
name, param);
} else {
n = nl_snprintf(buf, sizeof(buf),
"\033[38;2;140;140;140m\033[48;2;18;18;18m"
" %s [%3d] \033[0m ",
name, param);
}
} else {
n = snprintf(buf, sizeof(buf),
is_sel ? " *%s[%3d] " : " %s[%3d] ", name, param);
n = nl_snprintf(buf, sizeof(buf), is_sel ? " *%s[%3d] " : " %s[%3d] ",
name, param);
}
if (n > 0 && n < (int)sizeof(buf))
(void)write(STDOUT_FILENO, buf, (size_t)n);
@ -155,68 +153,75 @@ fps_counter_t fps_calc = {0};
// Main
int main(int argc, char *argv[]) {
signal(SIGINT, handle_signal);
signal(SIGTERM, handle_signal);
for (int i = 1; i < argc; i++) {
if (nl_strcmp(argv[i], "--help") == 0) {
print_usage(argv[0]);
return 0;
}
}
nl_signal(SIGINT, handle_signal);
nl_signal(SIGTERM, handle_signal);
// Config
char *device = "/dev/video0";
int ascii_w = DEFAULT_ASCII_WIDTH;
int ascii_h = DEFAULT_ASCII_HEIGHT;
int cap_w = DEFAULT_CAPTURE_WIDTH;
int cap_h = DEFAULT_CAPTURE_HEIGHT;
int fps = DEFAULT_FPS;
int ascii_w = DEFAULT_ASCII_WIDTH;
int ascii_h = DEFAULT_ASCII_HEIGHT;
int cap_w = DEFAULT_CAPTURE_WIDTH;
int cap_h = DEFAULT_CAPTURE_HEIGHT;
int fps = DEFAULT_FPS;
ascii_opts_t opts = {
.brightness = 0,
.contrast = 100,
.invert = 0,
.color = 0,
.edges = 0,
.dither = 0,
.charset = NULL,
.contrast = 100,
.invert = 0,
.color = 0,
.edges = 0,
.dither = 0,
.charset = NULL,
};
// Plugins
const char *plugin_paths[MAX_PLUGINS];
int plugin_path_count = 0;
int plugin_path_count = 0;
// CLI parsing
int opt;
while ((opt = getopt(argc, argv, "d:W:H:w:h:f:b:c:iCDs:p:")) != -1) {
while ((opt = nl_getopt(argc, argv, "d:W:H:w:h:f:b:c:iCDes:p:")) != -1)
switch (opt) {
case 'd':
device = optarg;
break;
case 'W':
ascii_w = atoi(optarg);
ascii_w = my_atoi(optarg);
if (ascii_w <= 0)
ascii_w = DEFAULT_ASCII_WIDTH;
break;
case 'H':
ascii_h = atoi(optarg);
ascii_h = my_atoi(optarg);
if (ascii_h <= 0)
ascii_h = DEFAULT_ASCII_HEIGHT;
break;
case 'w':
cap_w = atoi(optarg);
cap_w = my_atoi(optarg);
if (cap_w <= 0)
cap_w = DEFAULT_CAPTURE_WIDTH;
break;
case 'h':
cap_h = atoi(optarg);
cap_h = my_atoi(optarg);
if (cap_h <= 0)
cap_h = DEFAULT_CAPTURE_HEIGHT;
break;
case 'f':
fps = atoi(optarg);
fps = my_atoi(optarg);
if (fps <= 0)
fps = DEFAULT_FPS;
break;
case 'b':
opts.brightness = atoi(optarg);
opts.brightness = my_atoi(optarg);
break;
case 'c':
opts.contrast = atoi(optarg);
opts.contrast = my_atoi(optarg);
if (opts.contrast <= 0)
opts.contrast = 100;
break;
@ -239,26 +244,26 @@ int main(int argc, char *argv[]) {
if (plugin_path_count < MAX_PLUGINS)
plugin_paths[plugin_path_count++] = optarg;
else
fprintf(stderr, "Warning: max %d plugins, ignoring %s\n", MAX_PLUGINS, optarg);
fprintf(stderr, "Warning: max %d plugins, ignoring %s\n", MAX_PLUGINS,
optarg);
break;
default:
print_usage(argv[0]);
return 1;
}
}
timing_init(fps);
// Initialize plugins
plugin_loader_t plugins[MAX_PLUGINS];
int plugin_params[MAX_PLUGINS];
int plugin_count = 0;
int plugin_params[MAX_PLUGINS];
int plugin_count = 0;
for (int i = 0; i < plugin_path_count; i++) {
memset(&plugins[i], 0, sizeof(plugin_loader_t));
plugins[i].inotify_fd = -1;
plugin_params[i] = 128; // default
plugin_params[i] = 128; // default
if (plugin_load(&plugins[i], plugin_paths[i]) == 0) {
plugin_watch_init(&plugins[i], plugin_paths[i]);
plugin_count++;
@ -266,7 +271,7 @@ int main(int argc, char *argv[]) {
fprintf(stderr, "Failed to load plugin: %s\n", plugin_paths[i]);
}
}
int selected = 0;
// Open webcam
@ -275,15 +280,15 @@ int main(int argc, char *argv[]) {
perror("webcam_init");
return 1;
}
fprintf(stderr, "Device: %s | capture %dx%d | ASCII %dx%d | %d fps | %d plugin(s)%s%s%s%s\n",
fprintf(stderr,
"Device: %s | capture %dx%d | ASCII %dx%d | %d fps | %d "
"plugin(s)%s%s%s%s\n",
device, cam.width, cam.height, ascii_w, ascii_h, fps, plugin_count,
opts.color ? " | color" : "",
opts.edges ? " | edges" : "",
opts.dither ? " | dither" : "",
opts.invert ? " | inverted" : "");
opts.color ? " | color" : "", opts.edges ? " | edges" : "",
opts.dither ? " | dither" : "", opts.invert ? " | inverted" : "");
// Pixel buffers allocation
int cam_pixels = cam.width * cam.height;
int cam_pixels = cam.width * cam.height;
uint8_t *gray = malloc(cam_pixels);
uint8_t *rgb = opts.color ? malloc(cam_pixels * 3) : NULL;
@ -296,7 +301,7 @@ int main(int argc, char *argv[]) {
// Allocate output string buffer
size_t out_size = ascii_out_size(ascii_w, ascii_h, opts.color);
char *out_buf = malloc(out_size);
char *out_buf = malloc(out_size);
if (!out_buf) {
perror("malloc out_buf");
@ -338,12 +343,12 @@ int main(int argc, char *argv[]) {
clock_gettime(CLOCK_MONOTONIC, &frame_start);
long frame_diff_ns =
(frame_start.tv_sec - last_frame_time.tv_sec) * 1000000000L + // Seconds
(frame_start.tv_nsec - last_frame_time.tv_nsec); // Nano seconds
(frame_start.tv_sec - last_frame_time.tv_sec) * 1000000000L + // Seconds
(frame_start.tv_nsec - last_frame_time.tv_nsec); // Nano seconds
if (frame_diff_ns > 0)
fps_push(&fps_calc, frame_diff_ns);
last_frame_time = frame_start;
last_frame_time = frame_start;
double current_fps = fps_get(&fps_calc);
// Keypress handling
@ -367,16 +372,35 @@ int main(int argc, char *argv[]) {
}
continue;
}
// adjust plugin param if selected
int *p = (plugin_count > 0) ? &plugin_params[selected] : NULL;
switch (ch) {
case 'q': case 'Q': keep_running = 0; break;
case ']' : if (p && *p < 255) (*p)++; break;
case '[' : if (p && *p > 0) (*p)--; break;
case '}' : if (p) *p = (*p + 10 > 255) ? 255 : *p + 10; break;
case '{' : if (p) *p = (*p - 10 < 0) ? 0 : *p - 10; break;
case 'r': case 'R': if (p) *p = 128; break;
case 'q':
case 'Q':
keep_running = 0;
break;
case ']':
if (p && *p < 255)
(*p)++;
break;
case '[':
if (p && *p > 0)
(*p)--;
break;
case '}':
if (p)
*p = (*p + 10 > 255) ? 255 : *p + 10;
break;
case '{':
if (p)
*p = (*p - 10 < 0) ? 0 : *p - 10;
break;
case 'r':
case 'R':
if (p)
*p = 128;
break;
}
}
if (!keep_running)
@ -386,7 +410,7 @@ int main(int argc, char *argv[]) {
for (int i = 0; i < plugin_count; i++)
plugin_check_reload(&plugins[i]);
// Frame capture
// Frame capture
if (webcam_wait_frame(&cam, 1000) < 0)
continue; // timeout, retry
@ -398,7 +422,8 @@ int main(int argc, char *argv[]) {
// Run all plugins in order
for (int i = 0; i < plugin_count; i++) {
if (plugins[i].plugin)
plugins[i].plugin->process(gray, cam.width, cam.height, &plugin_params[i]);
plugins[i].plugin->process(gray, cam.width, cam.height,
&plugin_params[i]);
}
if (opts.color && rgb)

View file

@ -1,13 +1,9 @@
#include "plugins.h"
#include <dlfcn.h>
#include <fcntl.h>
#include <libgen.h> // dirname()
#include <stdio.h>
#include <string.h>
#include <sys/inotify.h>
#include <time.h>
#include <unistd.h>
#include "nolibc.h"
static int copy_file(const char *src, const char *dst) {
int fd_src = open(src, O_RDONLY);
@ -90,16 +86,19 @@ int plugin_load(plugin_loader_t *pl, const char *path) {
}
pl->plugin = get_plugin();
snprintf(pl->status_msg, sizeof(pl->status_msg),
"loaded: %s", pl->plugin->name);
snprintf(pl->status_msg, sizeof(pl->status_msg), "loaded: %s",
pl->plugin->name);
// FIX: Temporary file leak in plugin_load() when dlsym fails
// unlink(pl->tmp_path)
return 0;
}
void plugin_watch_init(plugin_loader_t *pl, const char *path) {
strncpy(pl->path, path, sizeof(pl->path) - 1);
nl_strncpy_safe(pl->path, path, sizeof(pl->path) - 1);
char dir_copy[256];
strncpy(dir_copy, path, sizeof(dir_copy) - 1);
nl_strncpy_safe(dir_copy, path, sizeof(dir_copy) - 1);
const char *dir = dirname(dir_copy);
pl->inotify_fd = inotify_init1(IN_NONBLOCK);
@ -127,7 +126,7 @@ void plugin_check_reload(plugin_loader_t *pl) {
return; // No new filesystem modifications detected
char path_copy[256];
strncpy(path_copy, pl->path, sizeof(path_copy) - 1);
nl_strncpy_safe(path_copy, pl->path, sizeof(path_copy) - 1);
const char *soname = basename(path_copy);
int relevant = 0;
@ -144,11 +143,12 @@ void plugin_check_reload(plugin_loader_t *pl) {
if (!relevant)
return;
// TODO: Replace with inotify event coalescing for more deterministic reload.
usleep(100000); // 0.1s delay for linker
if (plugin_load(pl, pl->path) == 0) {
snprintf(pl->status_msg, sizeof(pl->status_msg),
"hot-swapped -> %s", pl->plugin->name);
snprintf(pl->status_msg, sizeof(pl->status_msg), "hot-swapped -> %s",
pl->plugin->name);
} else {
snprintf(pl->status_msg, sizeof(pl->status_msg),
"hot-swap FAILED - old filter retained");

View file

@ -1,11 +1,15 @@
/*
Still uses pthread functions, TODO: replace them with raw futex syscalls and clone()
*/
#include "ascii.h"
#include "capture.h"
#include "thread_sharing.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdint.h>
#include "nolibc.h"
// Producer thread for capturing frames
void *capture_thread(void *arg) {

View file

@ -1,7 +1,9 @@
#include "timing.h"
#include <errno.h>
#include <time.h>
#include "nolibc.h"
static long frame_duration_ns = 0; // nanoseconds per frame
void timing_init(int fps) { frame_duration_ns = 1000000000L / fps; }
@ -15,7 +17,7 @@ void timing_sleep(struct timespec *start_time) {
long sleep_ns = frame_duration_ns - elapsed_ns;
if (sleep_ns > 0) {
struct timespec ts = {sleep_ns / 1000000000L, sleep_ns % 1000000000L};
while (nanosleep(&ts, &ts) == -1 && errno == EINTR)
while (nl_nanosleep(&ts, &ts) == -1 && errno == EINTR)
;
}
}