mirror of
https://github.com/Harshit-Dhanwalkar/AsciiCam.git
synced 2026-06-21 10:58:05 +02:00
Add Windows supports
This commit is contained in:
parent
f531035887
commit
af6841fcac
5 changed files with 507 additions and 9 deletions
30
C/Makefile
30
C/Makefile
|
|
@ -1,8 +1,9 @@
|
|||
# AsciiCam cross-platform build
|
||||
# Supports:
|
||||
# Linux x86_64 (gcc, nolibc, V4L2)
|
||||
# macOS ARM64 (clang, system libc, AVFoundation)
|
||||
# macOS x86_64 (clang, system libc, AVFoundation)
|
||||
# Linux x86_64 (gcc, nolibc, V4L2)
|
||||
# macOS ARM64 (clang, system libc, AVFoundation)
|
||||
# macOS x86_64 (clang, system libc, AVFoundation)
|
||||
# Windows (gcc, ststem libc, MinGW-w64)
|
||||
|
||||
UNAME_S := $(shell uname -s)
|
||||
UNAME_M := $(shell uname -m)
|
||||
|
|
@ -16,6 +17,10 @@ else ifeq ($(UNAME_S),Darwin)
|
|||
CC := clang
|
||||
LD := clang
|
||||
OBJC := clang
|
||||
else ifneq (,$(filter MINGW% MSYS%,$(UNAME_S)))
|
||||
PLATFORM := windows
|
||||
CC := gcc
|
||||
LD := gcc
|
||||
else
|
||||
$(error Unsupported platform: $(UNAME_S))
|
||||
endif
|
||||
|
|
@ -84,6 +89,25 @@ else ifeq ($(PLATFORM),macos)
|
|||
# Plugin shared objects
|
||||
SO_EXT := dylib
|
||||
SO_FLAGS := -dynamiclib
|
||||
else ifeq ($(PLATFORM),windows)
|
||||
# Windows: system libc (MSVCRT via MinGW), Media Foundation.
|
||||
CFLAGS := $(CFLAGS_COMMON) -DPLATFORM_WINDOWS -DWIN32_LEAN_AND_MEAN \
|
||||
-DUNICODE -D_UNICODE \
|
||||
-msse4.1
|
||||
CFLAGS := $(filter-out -ffreestanding -fno-builtin,$(CFLAGS))
|
||||
|
||||
LDFLAGS := -lmfplat -lmf -lmfreadwrite -lmfuuid -lole32 \
|
||||
-loleaut32 -luuid -lstrmiids
|
||||
|
||||
# TODO: Update nolibc for windows support instead of system system libc, currently :
|
||||
# No nolibc on Windows, uses system libc
|
||||
LIBSRCS :=
|
||||
|
||||
PLAT_SRC := $(SRCDIR)/capture_windows.c
|
||||
|
||||
# Plugin shared objects
|
||||
SO_EXT := dll
|
||||
SO_FLAGS := -shared
|
||||
endif
|
||||
|
||||
CORE_SRCS := $(SRCDIR)/ascii.c \
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@
|
|||
#define PLATFORM_LINUX 1
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
#define PLATFORM_MACOS 1
|
||||
#elif defined(_WIN32)
|
||||
#define PLATFORM_WINDOWS 1
|
||||
#else
|
||||
#error "Unsupported platform (only Linux and macOS are supported)"
|
||||
#error "Unsupported platform (only Linux, macOS, and Windows are supported)"
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// NOTE:
|
||||
// SIMD paths:
|
||||
// - x86_64 Linux/macOS: SSE2 via <immintrin.h>
|
||||
// - ARM64 macOS: NEON via <arm_neon.h>
|
||||
// - x86_64 Linux/macOS: SSE2 via <immintrin.h>
|
||||
// - ARM64 macOS: NEON via <arm_neon.h>
|
||||
// - TODO: Windows:
|
||||
|
||||
#include "nolibc.h"
|
||||
|
||||
|
|
|
|||
410
C/src/capture_windows.c
Normal file
410
C/src/capture_windows.c
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
#ifdef PLATFORM_WINDOWS
|
||||
#include "capture.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// Media Foundation is the modern (post-Vista) successor to DirectShow for
|
||||
// webcam capture on Windows; DirectShow is still around mainly for legacy
|
||||
// filter graphs and is on Microsoft's "prefer Media Foundation for new code"
|
||||
// list, so that's what this backend uses. It needs COM initialized and
|
||||
// links against mfplat.lib, mf.lib, mfreadwrite.lib, mfuuid.lib, ole32.lib
|
||||
// (see the Windows section added to the Makefile).
|
||||
#include <mfapi.h>
|
||||
#include <mferror.h>
|
||||
#include <mfidl.h>
|
||||
#include <mfreadwrite.h>
|
||||
#include <mfobjects.h>
|
||||
|
||||
struct webcam_impl {
|
||||
IMFMediaSource *source;
|
||||
IMFSourceReader *reader;
|
||||
|
||||
// The frame fetched by webcam_wait_frame() and consumed by
|
||||
// webcam_capture_frame(). Media Foundation's ReadSample() call is itself
|
||||
// blocking/synchronous when no async callback is configured, which maps
|
||||
// naturally onto this project's wait/capture split: the actual blocking
|
||||
// read happens in wait_frame, and capture_frame just drains whatever
|
||||
// wait_frame already fetched.
|
||||
IMFSample *pending_sample;
|
||||
|
||||
int com_initialized;
|
||||
int mf_started;
|
||||
};
|
||||
|
||||
#define SAFE_RELEASE(p) \
|
||||
do { \
|
||||
if (p) { \
|
||||
(p)->lpVtbl->Release(p); \
|
||||
(p) = NULL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static void release_pending_sample(struct webcam_impl *im) {
|
||||
if (im->pending_sample) {
|
||||
im->pending_sample->lpVtbl->Release(im->pending_sample);
|
||||
im->pending_sample = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Enumerate video capture devices and pick the one whose friendly name
|
||||
// matches `device` (case-insensitive substring match), or the first device
|
||||
// found if `device` is NULL. Mirrors capture_macos.c's localizedName match.
|
||||
static IMFActivate *find_device(const char *device) {
|
||||
IMFAttributes *attrs = NULL;
|
||||
IMFActivate **devices = NULL;
|
||||
UINT32 count = 0;
|
||||
IMFActivate *chosen = NULL;
|
||||
|
||||
if (FAILED(MFCreateAttributes(&attrs, 1)))
|
||||
return NULL;
|
||||
if (FAILED(attrs->lpVtbl->SetGUID(attrs, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
|
||||
&MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP))) {
|
||||
SAFE_RELEASE(attrs);
|
||||
return NULL;
|
||||
}
|
||||
if (FAILED(MFEnumDeviceSources(attrs, &devices, &count)) || count == 0) {
|
||||
SAFE_RELEASE(attrs);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (UINT32 i = 0; i < count; i++) {
|
||||
if (chosen)
|
||||
break;
|
||||
if (!device) {
|
||||
chosen = devices[i];
|
||||
continue;
|
||||
}
|
||||
WCHAR *name = NULL;
|
||||
UINT32 name_len = 0;
|
||||
if (SUCCEEDED(devices[i]->lpVtbl->GetAllocatedString(
|
||||
devices[i], &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name,
|
||||
&name_len))) {
|
||||
char narrow[256];
|
||||
int n = WideCharToMultiByte(CP_UTF8, 0, name, -1, narrow,
|
||||
sizeof(narrow), NULL, NULL);
|
||||
(void)n;
|
||||
CoTaskMemFree(name);
|
||||
if (strstr(narrow, device) != NULL)
|
||||
chosen = devices[i];
|
||||
}
|
||||
}
|
||||
if (!chosen && count > 0)
|
||||
chosen = devices[0]; // fall back to first device if name didn't match
|
||||
|
||||
if (chosen)
|
||||
chosen->lpVtbl->AddRef(chosen);
|
||||
for (UINT32 i = 0; i < count; i++)
|
||||
SAFE_RELEASE(devices[i]);
|
||||
CoTaskMemFree(devices);
|
||||
SAFE_RELEASE(attrs);
|
||||
return chosen;
|
||||
}
|
||||
|
||||
// Walk the native media types on stream 0 and pick whichever is closest to
|
||||
// the requested width/height, same "closest match" approach
|
||||
// capture_macos.c uses for AVCaptureDeviceFormat. Webcams almost always
|
||||
// offer NV12 natively; we ask for that explicitly via SetCurrentMediaType so
|
||||
// the Y-plane copy below can assume it.
|
||||
static int negotiate_format(IMFSourceReader *reader, int want_w, int want_h,
|
||||
int *out_w, int *out_h) {
|
||||
IMFMediaType *best = NULL;
|
||||
double best_diff = 1e18;
|
||||
int best_w = 0, best_h = 0;
|
||||
|
||||
for (DWORD i = 0;; i++) {
|
||||
IMFMediaType *type = NULL;
|
||||
HRESULT hr = reader->lpVtbl->GetNativeMediaType(
|
||||
reader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, i, &type);
|
||||
if (hr == MF_E_NO_MORE_TYPES || FAILED(hr))
|
||||
break;
|
||||
|
||||
UINT64 packed_size = 0;
|
||||
if (SUCCEEDED(
|
||||
type->lpVtbl->GetUINT64(type, &MF_MT_FRAME_SIZE, &packed_size))) {
|
||||
UINT32 w = (UINT32)(packed_size >> 32);
|
||||
UINT32 h = (UINT32)(packed_size & 0xFFFFFFFF);
|
||||
double diff = (double)(w - want_w) * (w - want_w) +
|
||||
(double)(h - want_h) * (h - want_h);
|
||||
if (diff < best_diff) {
|
||||
best_diff = diff;
|
||||
SAFE_RELEASE(best);
|
||||
best = type;
|
||||
best->lpVtbl->AddRef(best);
|
||||
best_w = (int)w;
|
||||
best_h = (int)h;
|
||||
}
|
||||
}
|
||||
SAFE_RELEASE(type);
|
||||
}
|
||||
|
||||
if (!best)
|
||||
return -1;
|
||||
|
||||
// Force NV12 output regardless of the native subtype the camera reports;
|
||||
// the source reader's built-in video processor (MF_SOURCE_READER_ENABLE_
|
||||
// VIDEO_PROCESSING) handles the conversion for us if the native format
|
||||
// is something else (e.g. MJPG).
|
||||
IMFMediaType *want_type = NULL;
|
||||
if (FAILED(MFCreateMediaType(&want_type))) {
|
||||
SAFE_RELEASE(best);
|
||||
return -1;
|
||||
}
|
||||
want_type->lpVtbl->SetGUID(want_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video);
|
||||
want_type->lpVtbl->SetGUID(want_type, &MF_MT_SUBTYPE, &MFVideoFormat_NV12);
|
||||
want_type->lpVtbl->SetUINT64(
|
||||
want_type, &MF_MT_FRAME_SIZE,
|
||||
((UINT64)best_w << 32) | (UINT32)best_h);
|
||||
|
||||
HRESULT hr = reader->lpVtbl->SetCurrentMediaType(
|
||||
reader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, want_type);
|
||||
|
||||
SAFE_RELEASE(want_type);
|
||||
SAFE_RELEASE(best);
|
||||
if (FAILED(hr))
|
||||
return -1;
|
||||
|
||||
*out_w = best_w;
|
||||
*out_h = best_h;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int webcam_init(webcam_t *cam, const char *device, int width, int height) {
|
||||
struct webcam_impl *im = calloc(1, sizeof(struct webcam_impl));
|
||||
if (!im)
|
||||
return -1;
|
||||
|
||||
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
// RPC_E_CHANGED_MODE means COM was already init'd on this thread in a
|
||||
// different concurrency mode by something else in the process -- that's
|
||||
// fine, we just don't own un-initializing it later in that case.
|
||||
im->com_initialized = (SUCCEEDED(hr) && hr != RPC_E_CHANGED_MODE);
|
||||
|
||||
if (FAILED(MFStartup(MF_VERSION, MFSTARTUP_LITE)))
|
||||
goto fail;
|
||||
im->mf_started = 1;
|
||||
|
||||
IMFActivate *activate = find_device(device);
|
||||
if (!activate)
|
||||
goto fail;
|
||||
|
||||
hr = activate->lpVtbl->ActivateObject(activate, &IID_IMFMediaSource,
|
||||
(void **)&im->source);
|
||||
SAFE_RELEASE(activate);
|
||||
if (FAILED(hr) || !im->source)
|
||||
goto fail;
|
||||
|
||||
IMFAttributes *reader_attrs = NULL;
|
||||
if (FAILED(MFCreateAttributes(&reader_attrs, 1)))
|
||||
goto fail;
|
||||
// Let MF's built-in video processor handle MJPG/YUY2/etc -> NV12
|
||||
// conversion for us, so this backend only ever has to deal with NV12.
|
||||
reader_attrs->lpVtbl->SetUINT32(
|
||||
reader_attrs, &MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, TRUE);
|
||||
|
||||
hr = MFCreateSourceReaderFromMediaSource(im->source, reader_attrs,
|
||||
&im->reader);
|
||||
SAFE_RELEASE(reader_attrs);
|
||||
if (FAILED(hr) || !im->reader)
|
||||
goto fail;
|
||||
|
||||
int got_w = 0, got_h = 0;
|
||||
if (negotiate_format(im->reader, width, height, &got_w, &got_h) < 0) {
|
||||
// fall back to whatever's already current rather than failing outright
|
||||
got_w = width;
|
||||
got_h = height;
|
||||
}
|
||||
|
||||
cam->width = got_w;
|
||||
cam->height = got_h;
|
||||
cam->impl = im;
|
||||
cam->fd = -1;
|
||||
cam->buffer = NULL;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (im->reader)
|
||||
SAFE_RELEASE(im->reader);
|
||||
if (im->source) {
|
||||
im->source->lpVtbl->Shutdown(im->source);
|
||||
SAFE_RELEASE(im->source);
|
||||
}
|
||||
if (im->mf_started)
|
||||
MFShutdown();
|
||||
if (im->com_initialized)
|
||||
CoUninitialize();
|
||||
free(im);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int webcam_wait_frame(webcam_t *cam, int timeout_ms) {
|
||||
struct webcam_impl *im = cam->impl;
|
||||
// The synchronous reader doesn't take a timeout directly; ReadSample()
|
||||
// blocks until a sample, error, or end-of-stream arrives. `timeout_ms` is
|
||||
// accepted for interface parity with the V4L2/select()-based backend but
|
||||
// isn't enforced here -- in practice a UVC camera that's actively
|
||||
// streaming delivers samples well within any timeout this app uses.
|
||||
(void)timeout_ms;
|
||||
|
||||
release_pending_sample(im);
|
||||
|
||||
DWORD stream_index = 0, stream_flags = 0;
|
||||
LONGLONG timestamp = 0;
|
||||
IMFSample *sample = NULL;
|
||||
|
||||
HRESULT hr = im->reader->lpVtbl->ReadSample(
|
||||
im->reader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0,
|
||||
&stream_index, &stream_flags, ×tamp, &sample);
|
||||
|
||||
if (FAILED(hr) || !sample || (stream_flags & MF_SOURCE_READERF_ENDOFSTREAM))
|
||||
return -1;
|
||||
|
||||
im->pending_sample = sample; // ownership transferred to impl
|
||||
return 0;
|
||||
}
|
||||
|
||||
int webcam_capture_frame(webcam_t *cam, uint8_t *gray_buffer) {
|
||||
struct webcam_impl *im = cam->impl;
|
||||
if (!im->pending_sample)
|
||||
return -1;
|
||||
|
||||
IMFMediaBuffer *buffer = NULL;
|
||||
if (FAILED(im->pending_sample->lpVtbl->ConvertToContiguousBuffer(
|
||||
im->pending_sample, &buffer)))
|
||||
return -1;
|
||||
|
||||
BYTE *data = NULL;
|
||||
DWORD max_len = 0, cur_len = 0;
|
||||
if (FAILED(buffer->lpVtbl->Lock(buffer, &data, &max_len, &cur_len))) {
|
||||
SAFE_RELEASE(buffer);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// NV12: the Y (luma) plane comes first, tightly packed at cam->width
|
||||
// stride for the formats this app negotiates -- if a given driver hands
|
||||
// back a padded stride, IMF2DBuffer::Lock2D below would be the correct
|
||||
// way to get the real stride, but plain Lock() with width-sized rows
|
||||
// covers the overwhelming majority of UVC cameras.
|
||||
size_t plane_size = (size_t)cam->width * (size_t)cam->height;
|
||||
if ((size_t)cur_len >= plane_size)
|
||||
memcpy(gray_buffer, data, plane_size);
|
||||
|
||||
buffer->lpVtbl->Unlock(buffer);
|
||||
SAFE_RELEASE(buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int webcam_requeue_buffer(webcam_t *cam) {
|
||||
(void)cam;
|
||||
return 0; // Media Foundation manages its own sample lifetime
|
||||
}
|
||||
|
||||
void webcam_cleanup(webcam_t *cam) {
|
||||
struct webcam_impl *im = cam->impl;
|
||||
if (!im)
|
||||
return;
|
||||
|
||||
release_pending_sample(im);
|
||||
SAFE_RELEASE(im->reader);
|
||||
if (im->source) {
|
||||
im->source->lpVtbl->Shutdown(im->source);
|
||||
SAFE_RELEASE(im->source);
|
||||
}
|
||||
if (im->mf_started)
|
||||
MFShutdown();
|
||||
if (im->com_initialized)
|
||||
CoUninitialize();
|
||||
free(im);
|
||||
|
||||
cam->impl = NULL;
|
||||
cam->fd = -1;
|
||||
cam->buffer = NULL;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hardware controls: not implemented on Windows yet.
|
||||
//
|
||||
// The real equivalents exist (IAMCameraControl for exposure, IAMVideoProcAmp
|
||||
// for contrast/white-balance, both reachable off the IMFMediaSource via
|
||||
// IMFGetService::GetService(..., MF_PROXY_PLAYER, IID_IAMCameraControl,...)-
|
||||
// style queries since these are DirectShow-era COM interfaces that MF
|
||||
// sources still expose for backward compatibility), but that's a separate
|
||||
// chunk of COM plumbing from frame capture, so left as a follow-up rather
|
||||
// than bolted on here speculatively/untested.
|
||||
// TODO: implement via IAMCameraControl / IAMVideoProcAmp.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int webcam_set_auto_exposure(webcam_t *cam, int enable) {
|
||||
(void)cam;
|
||||
(void)enable;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int webcam_set_auto_white_balance(webcam_t *cam, int enable) {
|
||||
(void)cam;
|
||||
(void)enable;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int webcam_adjust_exposure(webcam_t *cam, int delta, int *out_value) {
|
||||
(void)cam;
|
||||
(void)delta;
|
||||
(void)out_value;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int webcam_adjust_contrast(webcam_t *cam, int delta, int *out_value) {
|
||||
(void)cam;
|
||||
(void)delta;
|
||||
(void)out_value;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int webcam_adjust_white_balance(webcam_t *cam, int delta, int *out_value) {
|
||||
(void)cam;
|
||||
(void)delta;
|
||||
(void)out_value;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int webcam_get_exposure(webcam_t *cam, int *value) {
|
||||
(void)cam;
|
||||
(void)value;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int webcam_get_contrast(webcam_t *cam, int *value) {
|
||||
(void)cam;
|
||||
(void)value;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int webcam_get_white_balance(webcam_t *cam, int *value) {
|
||||
(void)cam;
|
||||
(void)value;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int webcam_get_exposure_range(webcam_t *cam, int *min, int *max) {
|
||||
(void)cam;
|
||||
(void)min;
|
||||
(void)max;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int webcam_get_contrast_range(webcam_t *cam, int *min, int *max) {
|
||||
(void)cam;
|
||||
(void)min;
|
||||
(void)max;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int webcam_get_white_balance_range(webcam_t *cam, int *min, int *max) {
|
||||
(void)cam;
|
||||
(void)min;
|
||||
(void)max;
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif /* PLATFORM_WINDOWS */
|
||||
67
C/src/main.c
67
C/src/main.c
|
|
@ -73,6 +73,9 @@ static void print_usage(const char *prog) {
|
|||
" x / X cycle edge detection mode forward / backward \n"
|
||||
" n / N cycle loaded charset forward / backward \n"
|
||||
" p / o increase / decrease depth-pop strength \n"
|
||||
" e / E hw exposure down / up (V4L2, Linux only) \n"
|
||||
" w / W hw white-balance down / up (V4L2, Linux only) \n"
|
||||
" c / C hw contrast down / up (V4L2, Linux only) \n"
|
||||
" up/down select plugin [ ] +-1 { } +-10 r reset \n"
|
||||
" q quit \n",
|
||||
prog, DEFAULT_CAPTURE_WIDTH, DEFAULT_CAPTURE_HEIGHT, DEFAULT_FPS,
|
||||
|
|
@ -120,7 +123,8 @@ 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, const ascii_opts_t *opts,
|
||||
const charset_registry_t *charsets) {
|
||||
const charset_registry_t *charsets, int hw_exposure,
|
||||
int hw_contrast, int hw_wb) {
|
||||
char buf[1024];
|
||||
int n, base_row = ascii_h + 1; // 1-indexed panel row
|
||||
|
||||
|
|
@ -167,10 +171,44 @@ static void overlay_panel(int ascii_h, double fps, plugin_loader_t *plugins,
|
|||
if (n > 0 && n < (int)sizeof(buf))
|
||||
(void)write(STDOUT_FILENO, buf, (size_t)n);
|
||||
|
||||
// Hardware (V4L2) camera control row -- "n/a" fields when unsupported
|
||||
// (macOS/Windows, or a driver that doesn't expose that control).
|
||||
n = nl_snprintf(buf, sizeof(buf), "\033[%d;1H\033[K", base_row + 2);
|
||||
if (n > 0)
|
||||
(void)write(STDOUT_FILENO, buf, (size_t)n);
|
||||
|
||||
char exp_buf[16], con_buf[16], wb_buf[16];
|
||||
if (hw_exposure >= 0)
|
||||
nl_snprintf(exp_buf, sizeof(exp_buf), "%d", hw_exposure);
|
||||
else
|
||||
nl_snprintf(exp_buf, sizeof(exp_buf), "n/a");
|
||||
if (hw_contrast >= 0)
|
||||
nl_snprintf(con_buf, sizeof(con_buf), "%d", hw_contrast);
|
||||
else
|
||||
nl_snprintf(con_buf, sizeof(con_buf), "n/a");
|
||||
if (hw_wb >= 0)
|
||||
nl_snprintf(wb_buf, sizeof(wb_buf), "%dK", hw_wb);
|
||||
else
|
||||
nl_snprintf(wb_buf, sizeof(wb_buf), "n/a");
|
||||
|
||||
if (color) {
|
||||
n = nl_snprintf(buf, sizeof(buf),
|
||||
"\033[38;2;220;160;0m hw exposure: %s (e/E) hw contrast: "
|
||||
"%s (c/C) hw white-balance: %s (w/W)\033[0m\033[K",
|
||||
exp_buf, con_buf, wb_buf);
|
||||
} else {
|
||||
n = nl_snprintf(buf, sizeof(buf),
|
||||
" hw exposure: %s hw contrast: %s hw white-balance: "
|
||||
"%s\033[K",
|
||||
exp_buf, con_buf, wb_buf);
|
||||
}
|
||||
if (n > 0 && n < (int)sizeof(buf))
|
||||
(void)write(STDOUT_FILENO, buf, (size_t)n);
|
||||
|
||||
n = nl_snprintf(buf, sizeof(buf), "\033[%d;1H\033[K", base_row + 3);
|
||||
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"
|
||||
|
|
@ -363,6 +401,11 @@ int main(int argc, char *argv[]) {
|
|||
opts.edges != EDGE_OFF ? " | edges" : "",
|
||||
opts.dither ? " | dither" : "");
|
||||
|
||||
int hw_exposure = -1, hw_contrast = -1, hw_wb = -1;
|
||||
webcam_get_exposure(&cam, &hw_exposure);
|
||||
webcam_get_contrast(&cam, &hw_contrast);
|
||||
webcam_get_white_balance(&cam, &hw_wb);
|
||||
|
||||
// Pixel buffers allocation
|
||||
int cam_pixels = cam.width * cam.height;
|
||||
uint8_t *gray = malloc(cam_pixels);
|
||||
|
|
@ -523,6 +566,24 @@ int main(int argc, char *argv[]) {
|
|||
case 'v':
|
||||
opts.depth_invert = !opts.depth_invert;
|
||||
break;
|
||||
case 'e':
|
||||
webcam_adjust_exposure(&cam, -10, &hw_exposure);
|
||||
break;
|
||||
case 'E':
|
||||
webcam_adjust_exposure(&cam, 10, &hw_exposure);
|
||||
break;
|
||||
case 'w':
|
||||
webcam_adjust_white_balance(&cam, -100, &hw_wb);
|
||||
break;
|
||||
case 'W':
|
||||
webcam_adjust_white_balance(&cam, 100, &hw_wb);
|
||||
break;
|
||||
case 'c':
|
||||
webcam_adjust_contrast(&cam, -5, &hw_contrast);
|
||||
break;
|
||||
case 'C':
|
||||
webcam_adjust_contrast(&cam, 5, &hw_contrast);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!keep_running)
|
||||
|
|
@ -579,7 +640,6 @@ int main(int argc, char *argv[]) {
|
|||
break;
|
||||
}
|
||||
|
||||
// Process frame mapping using dynamically calculated bounds
|
||||
int len = grayscale_to_ascii(gray, rgb, cam.width, cam.height, subpixel_w,
|
||||
subpixel_h, out_buf, out_size, &opts);
|
||||
if (len > (int)out_size) {
|
||||
|
|
@ -589,7 +649,8 @@ int main(int argc, char *argv[]) {
|
|||
if (len > 0) {
|
||||
(void)write(STDOUT_FILENO, out_buf, (size_t)len);
|
||||
overlay_panel(ascii_h, current_fps, plugins, plugin_params, plugin_count,
|
||||
selected, opts.color, &opts, &charsets);
|
||||
selected, opts.color, &opts, &charsets, hw_exposure,
|
||||
hw_contrast, hw_wb);
|
||||
}
|
||||
|
||||
if (webcam_requeue_buffer(&cam) < 0) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue