ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
video/rgba/image.c
Go to the documentation of this file.
1
7#include <stdbool.h>
8#include <stddef.h>
9#include <stdint.h>
10#include <limits.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <math.h>
15#ifndef _WIN32
16#include <unistd.h>
17#endif
18
19#include <ascii-chat/common.h>
26
29#include <ascii-chat/buffer_pool.h> // For buffer pool allocation functions
33
34// NOTE: luminance_palette is now passed as parameter to functions instead of using global cache
35
36// ansi_fast functions are declared in ansi_fast.h (already included)
37
38asciichat_error_t image_render_test_pattern(image_t *image, unsigned int *frame_counter) {
39 if (!image || !image->pixels || image->w <= 0 || image->h <= 0 || !frame_counter) {
40 return SET_ERRNO(ERROR_INVALID_PARAM, "Test pattern requires a valid image and frame counter");
41 }
42
43 // Advance every generated frame. Discovery's FPS indicator measures visual
44 // changes, so holding each test pattern phase for two frames makes a 60 Hz
45 // source appear to run at only 30 FPS.
46 unsigned int animation_phase = *frame_counter;
47 *frame_counter += 5; // Speed up animation by a factor of five.
48
49 for (int y = 0; y < image->h; y++) {
50 for (int x = 0; x < image->w; x++) {
51 rgb_pixel_t *pixel = &image->pixels[y * image->w + x];
52
53 // Animated color bars that shift based on frame counter
54 int animated_x = (x + animation_phase) % image->w;
55 int grid_x = animated_x / 40;
56
57 // Base pattern: color bars that animate horizontally
58 switch (grid_x % 3) {
59 case 0: // Red
60 pixel->r = 255;
61 pixel->g = 0;
62 pixel->b = 0;
63 break;
64 case 1: // Green
65 pixel->r = 0;
66 pixel->g = 255;
67 pixel->b = 0;
68 break;
69 case 2: // Blue
70 default:
71 pixel->r = 0;
72 pixel->g = 0;
73 pixel->b = 255;
74 break;
75 }
76
77 // Add animated grid lines. The phase alternates the cell pattern each
78 // generated frame so a downsampled terminal client still has a visible
79 // frame-to-frame change when --test-pattern is used for cadence tests.
80 if (animated_x % 40 == 0 || y % 30 == 0 || (((x / 10) + (y / 10) + animation_phase) & 1) == 0) {
81 pixel->r = 0;
82 pixel->g = 0;
83 pixel->b = 0;
84 }
85 }
86 }
87
88 return ASCIICHAT_OK;
89}
90
91image_t *image_new(size_t width, size_t height) {
92 image_t *p;
93
94 p = SAFE_MALLOC(sizeof(image_t), image_t *);
95
96 // Validate dimensions are non-zero and within bounds
97 if (image_validate_dimensions(width, height) != ASCIICHAT_OK) {
98 SET_ERRNO(ERROR_INVALID_PARAM, "Image dimensions invalid or too large: %zu x %zu", width, height);
99 SAFE_FREE(p);
100 return NULL;
101 }
102
103 // Calculate pixel count with overflow checking
104 size_t total_pixels;
105 if (checked_size_mul(width, height, &total_pixels) != ASCIICHAT_OK) {
106 SET_ERRNO(ERROR_INVALID_PARAM, "Image dimensions would cause overflow: %zu x %zu", width, height);
107 SAFE_FREE(p);
108 return NULL;
109 }
110
111 // Calculate total pixel buffer size with overflow checking
112 size_t pixels_size;
113 if (checked_size_mul(total_pixels, sizeof(rgb_pixel_t), &pixels_size) != ASCIICHAT_OK) {
114 SET_ERRNO(ERROR_INVALID_PARAM, "Image pixel buffer size would cause overflow");
115 SAFE_FREE(p);
116 return NULL;
117 }
118
119 if (pixels_size > IMAGE_MAX_PIXELS_SIZE) {
120 SET_ERRNO(ERROR_INVALID_PARAM, "Image size exceeds maximum allowed: %zu x %zu (%zu bytes)", width, height,
121 pixels_size);
122 SAFE_FREE(p);
123 return NULL;
124 }
125
126 // Use SIMD-aligned allocation for optimal NEON/AVX performance with vld3q_u8
127 p->pixels = SAFE_MALLOC_SIMD(pixels_size, rgb_pixel_t *);
128 if (!p->pixels) {
129 SET_ERRNO(ERROR_MEMORY, "Failed to allocate image pixels: %zu bytes", pixels_size);
130 SAFE_FREE(p);
131 return NULL;
132 }
133
134 p->w = (int)width;
135 p->h = (int)height;
136 p->alloc_method = IMAGE_ALLOC_SIMD; // Track allocation method for correct deallocation
137 return p;
138}
139
141 if (!p) {
142 SET_ERRNO(ERROR_INVALID_PARAM, "image_destroy: p is NULL");
143 return;
144 }
145
146 // Check allocation method and deallocate appropriately
147 if (p->alloc_method == IMAGE_ALLOC_POOL) {
148 // Pool-allocated images: free entire contiguous buffer (image + pixels)
149 // Validate dimensions before calculating size (guard against corruption)
150 if (p->w <= 0 || p->h <= 0) {
151 SET_ERRNO(ERROR_INVALID_PARAM, "image_destroy: invalid dimensions %dx%d (pool-allocated)", p->w, p->h);
152 return;
153 }
154
155 // Calculate original allocation size for proper buffer pool free
156 size_t w = (size_t)p->w;
157 size_t h = (size_t)p->h;
158 size_t pixels_size;
159 if (checked_size_mul3(w, h, sizeof(rgb_pixel_t), &pixels_size) != ASCIICHAT_OK) {
160 SET_ERRNO(ERROR_INVALID_STATE, "image_destroy: dimensions would overflow: %dx%d", p->w, p->h);
161 return;
162 }
163 size_t total_size;
164 if (checked_size_add(sizeof(image_t), pixels_size, &total_size) != ASCIICHAT_OK) {
165 SET_ERRNO(ERROR_INVALID_STATE, "image_destroy: total size would overflow: %dx%d", p->w, p->h);
166 return;
167 }
168
169 // Free the entire contiguous buffer back to pool
170 buffer_pool_free(NULL, p, total_size);
171 } else {
172 // SIMD-allocated images: free pixels and structure separately
173 // SAFE_MALLOC_SIMD allocates with aligned allocation (posix_memalign on macOS, aligned_alloc on Linux)
174 // These can be freed with standard free() / SAFE_FREE()
175 SAFE_FREE(p->pixels);
176 SAFE_FREE(p);
177 }
178}
179
180// Buffer pool allocation for video pipeline (consistent memory management)
181image_t *image_new_from_pool(size_t width, size_t height) {
182 if (width == 0 || height == 0) {
183 SET_ERRNO(ERROR_INVALID_PARAM, "image_new_from_pool: invalid dimensions %zux%zu", width, height);
184 return NULL;
185 }
186
187 if (width > IMAGE_MAX_WIDTH || height > IMAGE_MAX_HEIGHT) {
188 SET_ERRNO(ERROR_INVALID_PARAM, "image_new_from_pool: dimensions %zux%zu exceed maximum %ux%u", width, height,
190 return NULL;
191 }
192
193 // Calculate total allocation size (structure + pixel data in single buffer)
194 // Check for integer overflow before multiplication
195 size_t pixels_size;
196 if (checked_size_mul3(width, height, sizeof(rgb_pixel_t), &pixels_size) != ASCIICHAT_OK) {
197 SET_ERRNO(ERROR_INVALID_PARAM, "image_new_from_pool: dimensions would overflow: %zux%zu", width, height);
198 return NULL;
199 }
200
201 size_t total_size;
202 if (checked_size_add(sizeof(image_t), pixels_size, &total_size) != ASCIICHAT_OK) {
203 SET_ERRNO(ERROR_INVALID_PARAM, "image_new_from_pool: total size would overflow");
204 return NULL;
205 }
206
207 // Allocate from buffer pool as single contiguous block
208 void *buffer = buffer_pool_alloc(NULL, total_size);
209 if (!buffer) {
210 SET_ERRNO(ERROR_MEMORY, "image_new_from_pool: buffer pool allocation failed for %zu bytes (%zux%zu)", total_size,
211 width, height);
212 return NULL;
213 }
214
215 // Set up image structure at start of buffer
216 image_t *image = (image_t *)buffer;
217 image->w = (int)width;
218 image->h = (int)height;
219 // Pixel data immediately follows the image structure
220 image->pixels = (rgb_pixel_t *)((uint8_t *)buffer + sizeof(image_t));
221 image->alloc_method = IMAGE_ALLOC_POOL; // Track allocation method for correct deallocation
222
223 return image;
224}
225
227 if (!image) {
228 SET_ERRNO(ERROR_INVALID_PARAM, "image_destroy_to_pool: image is NULL");
229 return;
230 }
231
232 // Validate dimensions before calculating size (guard against corruption)
233 if (image->w <= 0 || image->h <= 0) {
234 SET_ERRNO(ERROR_INVALID_PARAM, "image_destroy_to_pool: invalid dimensions %dx%d", image->w, image->h);
235 return;
236 }
237
238 // Calculate original allocation size for proper buffer pool free
239 // Check for overflow (defensive - should match original allocation)
240 size_t w = (size_t)image->w;
241 size_t h = (size_t)image->h;
242 size_t pixels_size;
243 if (checked_size_mul3(w, h, sizeof(rgb_pixel_t), &pixels_size) != ASCIICHAT_OK) {
244 SET_ERRNO(ERROR_INVALID_STATE, "image_destroy_to_pool: dimensions would overflow: %dx%d", image->w, image->h);
245 return;
246 }
247 size_t total_size;
248 if (checked_size_add(sizeof(image_t), pixels_size, &total_size) != ASCIICHAT_OK) {
249 SET_ERRNO(ERROR_INVALID_STATE, "image_destroy_to_pool: total size would overflow: %dx%d", image->w, image->h);
250 return;
251 }
252
253 // Free the entire contiguous buffer back to pool
254 buffer_pool_free(NULL, image, total_size);
255 // Note: Don't set image = NULL here since caller owns the pointer
256}
257
259 if (!p || !p->pixels) {
260 SET_ERRNO(ERROR_INVALID_PARAM, "image_clear: p or p->pixels is NULL");
261 return;
262 }
263 // Check for integer overflow before multiplication.
264 unsigned long w_ul = (unsigned long)p->w;
265 unsigned long h_ul = (unsigned long)p->h;
266 if (w_ul > 0 && h_ul > ULONG_MAX / w_ul) {
267 SET_ERRNO(ERROR_INVALID_PARAM, "image_clear: dimensions overflow: %d x %d", p->w, p->h);
268 return;
269 }
270 unsigned long pixel_count = w_ul * h_ul;
271 if (pixel_count > ULONG_MAX / sizeof(rgb_pixel_t)) {
272 SET_ERRNO(ERROR_INVALID_PARAM, "image_clear: buffer size overflow");
273 return;
274 }
275 size_t clear_size = pixel_count * sizeof(rgb_pixel_t);
276 SAFE_MEMSET(p->pixels, clear_size, 0, clear_size);
277}
278
280 if (!source) {
281 SET_ERRNO(ERROR_INVALID_PARAM, "image_new_copy: source is NULL");
282 return NULL;
283 }
284
285 // Create new image with same dimensions
286 image_t *copy = image_new((size_t)source->w, (size_t)source->h);
287 if (!copy) {
288 return NULL;
289 }
290
291 // Copy pixel data from source to copy
292 if (source->pixels && copy->pixels) {
293 size_t pixel_count = (size_t)source->w * (size_t)source->h;
294 size_t pixels_size = pixel_count * sizeof(rgb_pixel_t);
295 memcpy(copy->pixels, source->pixels, pixels_size);
296 }
297
298 return copy;
299}
300
301inline rgb_pixel_t *image_pixel(image_t *p, const int x, const int y) {
302 // Add bounds checking to prevent buffer overflow on invalid coordinates
303 if (!p || !p->pixels || x < 0 || x >= p->w || y < 0 || y >= p->h) {
304 return NULL;
305 }
306 return &p->pixels[x + y * p->w];
307}
308
309void image_resize(const image_t *s, image_t *d) {
310 if (!s || !d) {
311 SET_ERRNO(ERROR_INVALID_PARAM, "image_resize: s or d is NULL");
312 return;
313 }
314
316}
317
318// Optimized interpolation function with better integer arithmetic and memory
319// access
320void image_resize_interpolation(const image_t *source, image_t *dest) {
321 if (!source || !dest || !source->pixels || !dest->pixels) {
322 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters to image_resize_interpolation");
323 return;
324 }
325
326 const int src_w = source->w;
327 const int src_h = source->h;
328 const int dst_w = dest->w;
329 const int dst_h = dest->h;
330
331 // Handle edge cases
332 if (src_w <= 0 || src_h <= 0 || dst_w <= 0 || dst_h <= 0) {
333 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid image dimensions for resize: src=%dx%d dst=%dx%d", src_w, src_h, dst_w,
334 dst_h);
335 return;
336 }
337
338 // Defensive checks to prevent invalid shift (UBSan protection)
339 if (src_w < 1 || src_h < 1 || dst_w < 1 || dst_h < 1) {
340 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid dimensions detected after first check");
341 return;
342 }
343
344 // Use fixed-point arithmetic for better performance
345 // Use uint64_t intermediate to prevent overflow when shifting large dimensions
346 const uint32_t x_ratio = (uint32_t)((((uint64_t)src_w << 16) / (uint64_t)dst_w) + 1);
347 const uint32_t y_ratio = (uint32_t)((((uint64_t)src_h << 16) / (uint64_t)dst_h) + 1);
348
349 const rgb_pixel_t *src_pixels = source->pixels;
350 rgb_pixel_t *dst_pixels = dest->pixels;
351
352 for (int y = 0; y < dst_h; y++) {
353 const uint32_t src_y = ((uint32_t)(unsigned int)y * y_ratio) >> 16;
354 // Explicitly clamp to valid range [0, src_h-1]
355 const uint32_t safe_src_y = src_y >= (uint32_t)(unsigned int)src_h ? (uint32_t)(src_h - 1) : src_y;
356
357 // Bounds check: ensure safe_src_y is valid
358 if (safe_src_y >= (uint32_t)(unsigned int)src_h) {
359 SET_ERRNO(ERROR_INVALID_PARAM, "safe_src_y out of bounds: %u >= %d", safe_src_y, src_h);
360 return;
361 }
362
363 const rgb_pixel_t *src_row = src_pixels + (safe_src_y * (size_t)src_w);
364
365 rgb_pixel_t *dst_row = dst_pixels + ((size_t)y * (size_t)dst_w);
366
367 for (int x = 0; x < dst_w; x++) {
368 const uint32_t src_x = ((uint32_t)(unsigned int)x * x_ratio) >> 16;
369 // Explicitly clamp to valid range [0, src_w-1]
370 const uint32_t safe_src_x = src_x >= (uint32_t)(unsigned int)src_w ? (uint32_t)(src_w - 1) : src_x;
371
372 // Bounds check: ensure safe_src_x is valid
373 if (safe_src_x >= (uint32_t)(unsigned int)src_w) {
374 SET_ERRNO(ERROR_INVALID_PARAM, "safe_src_x out of bounds: %u >= %d", safe_src_x, src_w);
375 return;
376 }
377
378 dst_row[x] = src_row[safe_src_x];
379 }
380 }
381}
382
383// Note: luminance_palette is now handled by ascii_simd.c via g_ascii_cache.luminance_palette
384
385void precalc_rgb_palettes(const float red, const float green, const float blue) {
386 // Validate input parameters to prevent overflow
387 // Luminance weights should typically be in range 0.0-1.0
388 // But allow slightly larger values for brightness adjustment
389 const float max_weight = 255.0f; // Maximum value that won't overflow when multiplied by 255
390 const float min_weight = -255.0f; // Allow negative for color correction
391
392 // Note: isfinite() checks are skipped in Release builds due to -ffinite-math-only flag
393 // which disables NaN/infinity support for performance. Range checks below are sufficient.
394#if !defined(NDEBUG) && !defined(__FAST_MATH__)
395 if (!isfinite(red) || !isfinite(green) || !isfinite(blue)) {
396 log_error("Invalid weight values (non-finite): red=%f, green=%f, blue=%f", red, green, blue);
397 SET_ERRNO(ERROR_INVALID_PARAM, "precalc_rgb_palettes: non-finite weight values");
398 return;
399 }
400#endif
401
402 if (red < min_weight || red > max_weight || green < min_weight || green > max_weight || blue < min_weight ||
403 blue > max_weight) {
404 log_warn(
405 "precalc_rgb_palettes: Weight values out of expected range: red=%f, green=%f, blue=%f (clamping to safe range)",
406 red, green, blue);
407 }
408
409 // Clamp weights to safe range to prevent overflow
410 const float safe_red = (red < min_weight) ? min_weight : ((red > max_weight) ? max_weight : red);
411 const float safe_green = (green < min_weight) ? min_weight : ((green > max_weight) ? max_weight : green);
412 const float safe_blue = (blue < min_weight) ? min_weight : ((blue > max_weight) ? max_weight : blue);
413
414 const unsigned short max_ushort = 65535;
415 const unsigned short min_ushort = 0;
416
417 for (int n = 0; n < ASCII_LUMINANCE_LEVELS; ++n) {
418 // Compute with float, then clamp to unsigned short range
419 float red_val = (float)n * safe_red;
420 float green_val = (float)n * safe_green;
421 float blue_val = (float)n * safe_blue;
422
423 // Clamp to unsigned short range before assignment
424 if (red_val < (float)min_ushort) {
425 red_val = (float)min_ushort;
426 } else if (red_val > (float)max_ushort) {
427 red_val = (float)max_ushort;
428 }
429
430 if (green_val < (float)min_ushort) {
431 green_val = (float)min_ushort;
432 } else if (green_val > (float)max_ushort) {
433 green_val = (float)max_ushort;
434 }
435
436 if (blue_val < (float)min_ushort) {
437 blue_val = (float)min_ushort;
438 } else if (blue_val > (float)max_ushort) {
439 blue_val = (float)max_ushort;
440 }
441
442 RED[n] = (unsigned short)red_val;
443 GREEN[n] = (unsigned short)green_val;
444 BLUE[n] = (unsigned short)blue_val;
445 GRAY[n] = (unsigned short)n;
446 }
447}
ANSI escape sequence utilities and fast color code generation.
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
⚙️ Common definitions, error codes, macros, and types shared throughout the application
void buffer_pool_free(buffer_pool_t *pool, const void *data, size_t size)
Free a buffer back to the pool (lock-free)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Allocate a buffer from the pool (lock-free fast path)
#define SAFE_MALLOC_SIMD(size, cast)
Definition common.h:364
unsigned int uint32_t
Definition common.h:58
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MEMSET(dest, dest_size, ch, count)
Definition common.h:469
#define SAFE_MALLOC(size, cast)
Definition common.h:264
unsigned long long uint64_t
Definition common.h:59
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_INVALID_STATE
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
unsigned short int GRAY[256]
Grayscale lookup table.
unsigned short int BLUE[256]
Blue channel lookup table.
unsigned short int GREEN[256]
Green channel lookup table.
unsigned short int RED[256]
Red channel lookup table.
image_t * image_new_copy(const image_t *source)
Create a copy of an existing image.
void precalc_rgb_palettes(const float red, const float green, const float blue)
Precalculate RGB palettes with color adjustment.
void image_resize_interpolation(const image_t *source, image_t *dest)
Resize image using bilinear interpolation.
void image_destroy_to_pool(image_t *image)
Destroy an image allocated from buffer pool.
#define ASCII_LUMINANCE_LEVELS
Number of luminance levels supported (256)
Definition ascii.h:410
#define IMAGE_MAX_WIDTH
Maximum image width (4K resolution)
asciichat_error_t image_render_test_pattern(image_t *image, unsigned int *frame_counter)
Render animated color bars and grid lines into an existing image.
#define IMAGE_MAX_PIXELS_SIZE
Maximum pixel data size in bytes.
#define IMAGE_MAX_HEIGHT
Maximum image height (4K resolution)
void image_clear(image_t *p)
Clear image (set all pixels to black)
void image_resize(const image_t *s, image_t *d)
Resize image using nearest-neighbor interpolation.
image_t * image_new_from_pool(size_t width, size_t height)
Create a new image from buffer pool.
void image_destroy(image_t *p)
Destroy an image allocated with image_new()
image_t * image_new(size_t width, size_t height)
Create a new image with standard allocation.
@ IMAGE_ALLOC_POOL
Pixels allocated with buffer_pool_alloc()
@ IMAGE_ALLOC_SIMD
Pixels allocated with SAFE_MALLOC_SIMD()
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
Application limits and constraints.
🔢 Mathematical Utility Functions
✅ Safe Integer Arithmetic and Overflow Detection
ClangTool/LibTooling compatibility shim for stdbool.h.
Image structure.
int w
Image width in pixels (must be > 0)
uint8_t alloc_method
Allocation method (image_alloc_method_t) for correct deallocation.
int h
Image height in pixels (must be > 0)
rgb_pixel_t * pixels
Pixel data array (width * height RGB pixels, row-major order)
RGB pixel structure.
uint8_t b
Blue color component (0-255)
uint8_t g
Green color component (0-255)
uint8_t r
Red color component (0-255)
🖼️ Safe overflow-checked buffer size calculations for images and video frames
SIMD-optimized ASCII conversion interface.
rgb_pixel_t * image_pixel(image_t *p, const int x, const int y)
asciichat_error_t image_validate_dimensions(size_t width, size_t height)
Validate image dimensions (non-zero, within limits)
Definition video.c:12