ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
render.c File Reference

Go to the source code of this file.

Data Structures

struct  glyph_candidate_t

Macros

#define VIDEO_ASCII_RENDER_SEQUENCE_BUFFER_SIZE   16
#define ASCII_TEMPORAL_MOTION_LIMIT   8
#define ASCII_TEMPORAL_MAX_INTERVAL_NS   250000000ULL
#define ASCII_TEMPORAL_COLOR_TIME_NS   50000000ULL
#define ASCII_RENDER_WORKSPACE_POOL_SIZE   4
#define GLYPH_ASCII_COUNT   95
#define GLYPH_BLOCK_COUNT   20
#define GLYPH_COUNT   (GLYPH_ASCII_COUNT + GLYPH_BLOCK_COUNT)

Functions

ascii_stabilizer_t * ascii_stabilizer_set_target (ascii_stabilizer_t *state)
void ascii_stabilizer_reset (ascii_stabilizer_t *state)
asciichat_error_t ascii_stabilizer_prepare (ascii_stabilizer_t *state, size_t width, size_t height, uint64_t configuration)
uint32_t ascii_stabilizer_choose (ascii_temporal_cell_t *cell, const uint8_t samples[16], uint32_t candidate, uint64_t candidate_error, uint64_t previous_error, uint32_t color, uint64_t elapsed_ns, uint32_t *stabilized_color)
outbuf_t * ascii_render_set_output_target (outbuf_t *target)
char * ascii_render (const image_t *image, const char *palette, ascii_color_t mode, bool background, bool dither, const simd_cells_t *ops)
char * ascii_render_halfblocks (const uint8_t *rgb, int width, int height, int stride, ascii_color_t mode, const simd_cells_t *ops)
asciichat_error_t ascii_frame_init (ascii_frame_t *frame, const image_t *image, const char *palette)
void ascii_frame_emit_row (ascii_frame_t *frame, ascii_color_t mode, bool background, bool more_rows, const simd_cells_t *ops)
char * ascii_frame_finish (ascii_frame_t *frame)
asciichat_error_t ascii_half_frame_init (ascii_frame_t *frame, const uint8_t *rgb, int width, int height, int *stride, ascii_color_t mode)
void ascii_frame_emit_halfrow (ascii_frame_t *frame, ascii_color_t mode, bool more_rows, const simd_cells_t *ops)
bool ascii_glyph_palette_supported (const char *palette)
char * ascii_render_glyph_match (const image_t *image, ascii_color_t mode, const simd_cells_t *ops, const char *palette)
char * ascii_render_glyph_match_mode (const image_t *image, ascii_color_t mode, const simd_cells_t *ops, const char *palette, bool background)

Detailed Description

Shared cell construction and terminal emission for scalar and SIMD kernels.

Definition in file lib/video/ascii/render.c.

Macro Definition Documentation

◆ ASCII_RENDER_WORKSPACE_POOL_SIZE

#define ASCII_RENDER_WORKSPACE_POOL_SIZE   4

Definition at line 25 of file lib/video/ascii/render.c.

◆ ASCII_TEMPORAL_COLOR_TIME_NS

#define ASCII_TEMPORAL_COLOR_TIME_NS   50000000ULL

◆ ASCII_TEMPORAL_MAX_INTERVAL_NS

#define ASCII_TEMPORAL_MAX_INTERVAL_NS   250000000ULL

◆ ASCII_TEMPORAL_MOTION_LIMIT

#define ASCII_TEMPORAL_MOTION_LIMIT   8

◆ GLYPH_ASCII_COUNT

#define GLYPH_ASCII_COUNT   95

Definition at line 490 of file lib/video/ascii/render.c.

Referenced by ascii_render_glyph_match_mode().

◆ GLYPH_BLOCK_COUNT

#define GLYPH_BLOCK_COUNT   20

Definition at line 491 of file lib/video/ascii/render.c.

◆ GLYPH_COUNT

#define GLYPH_COUNT   (GLYPH_ASCII_COUNT + GLYPH_BLOCK_COUNT)

Definition at line 492 of file lib/video/ascii/render.c.

Referenced by ascii_render_glyph_match_mode().

◆ VIDEO_ASCII_RENDER_SEQUENCE_BUFFER_SIZE

#define VIDEO_ASCII_RENDER_SEQUENCE_BUFFER_SIZE   16

Definition at line 6 of file lib/video/ascii/render.c.

Function Documentation

◆ ascii_frame_emit_halfrow()

void ascii_frame_emit_halfrow ( ascii_frame_t * frame,
ascii_color_t mode,
bool more_rows,
const simd_cells_t * ops )

Definition at line 453 of file lib/video/ascii/render.c.

453 {
454 if (frame->stabilizer) {
455 size_t top_row = frame->row * 2;
456 bool has_bottom = top_row + 1 < frame->source_height;
457 for (size_t x = 0; x < frame->width; ++x) {
458 uint32_t *pixels[] = {&frame->packed[x], &frame->index[x]};
459 for (size_t half = 0; half < (has_bottom ? 2u : 1u); ++half) {
460 uint32_t color = *pixels[half];
461 uint8_t luma = (uint8_t)((76u * (color >> 16) + 150u * ((color >> 8) & 255) + 29u * (color & 255)) >> 8);
462 uint8_t samples[16];
463 memset(samples, luma, sizeof(samples));
464 ascii_temporal_cell_t *cell = &frame->stabilizer->cells[(top_row + half) * frame->width + x];
465 ascii_stabilizer_choose(cell, samples, 0, 0, UINT64_MAX, color, frame->stabilizer->elapsed_ns, pixels[half]);
466 }
467 if (!has_bottom)
468 frame->index[x] = frame->packed[x];
469 }
470 if (mode == ASCII_COLOR_TRUE) {
471 memcpy(frame->fg, frame->packed, frame->width * sizeof(uint32_t));
472 memcpy(frame->bg, frame->index, frame->width * sizeof(uint32_t));
473 } else if (mode != ASCII_COLOR_NONE) {
474 ops->quantize(frame->packed, frame->fg, frame->width, mode == ASCII_COLOR_16 ? 16 : 256);
475 ops->quantize(frame->index, frame->bg, frame->width, mode == ASCII_COLOR_16 ? 16 : 256);
476 }
477 ops->halfblock(frame->packed, frame->index, frame->glyph, frame->fg, frame->bg, frame->width, (unsigned)mode,
478 frame->table);
479 }
480 emit_prepared_row(&frame->output, mode, ops, frame->glyph, frame->fg, frame->bg, frame->width);
481 emit_row_end(&frame->output, mode != ASCII_COLOR_NONE, more_rows);
482 ++frame->row;
483}
unsigned int uint32_t
Definition common.h:62
unsigned char uint8_t
Definition common.h:60
uint32_t ascii_stabilizer_choose(ascii_temporal_cell_t *cell, const uint8_t samples[16], uint32_t candidate, uint64_t candidate_error, uint64_t previous_error, uint32_t color, uint64_t elapsed_ns, uint32_t *stabilized_color)
void emit_row_end(outbuf_t *out, bool colored, bool newline)
For ascii stabilizer, this is the live record that ties cells, width, height, configuration together....
void(* halfblock)(const uint32_t *top, const uint32_t *bottom, uint32_t *glyph, uint32_t *fg, uint32_t *bg, size_t count, unsigned mode, const uint32_t keys[5])
Definition cells.h:15
void(* quantize)(const uint32_t *rgb, uint32_t *out, size_t count, unsigned colors)
Definition cells.h:14

References ASCII_COLOR_16, ASCII_COLOR_NONE, ASCII_COLOR_TRUE, ascii_stabilizer_choose(), ascii_frame_t::bg, ascii_stabilizer_t::cells, ascii_stabilizer_t::elapsed_ns, emit_row_end(), ascii_frame_t::fg, ascii_frame_t::glyph, simd_cells_t::halfblock, ascii_frame_t::index, ascii_frame_t::output, ascii_frame_t::packed, simd_cells_t::quantize, ascii_frame_t::row, ascii_frame_t::source_height, ascii_frame_t::stabilizer, ascii_frame_t::table, and ascii_frame_t::width.

Referenced by ascii_render_halfblocks().

◆ ascii_frame_emit_row()

void ascii_frame_emit_row ( ascii_frame_t * frame,
ascii_color_t mode,
bool background,
bool more_rows,
const simd_cells_t * ops )

Definition at line 340 of file lib/video/ascii/render.c.

341 {
342 if (frame->stabilizer && !frame->temporal_preselected) {
343 const uint64_t elapsed = frame->stabilizer->elapsed_ns;
344 const bool recent = elapsed <= ASCII_TEMPORAL_MAX_INTERVAL_NS;
345 const unsigned weight = recent ? (unsigned)(elapsed * 256 / (elapsed + ASCII_TEMPORAL_COLOR_TIME_NS)) : 256;
346 for (size_t x = 0; x < frame->width; ++x) {
347 ascii_temporal_cell_t *cell = &frame->stabilizer->cells[frame->row * frame->width + x];
348 uint8_t luma = frame->luma[x];
349 uint32_t glyph = frame->glyph[x];
350 uint32_t color = frame->packed[x];
351 if (cell->valid && recent && abs((int)luma - (int)cell->samples[0]) <= ASCII_TEMPORAL_MOTION_LIMIT) {
352 if (cell->glyph != glyph) {
353 // Only nearby brightness bins can win the hysteresis test. Searching
354 // outward avoids scanning the entire 64-entry palette for every cell.
355 unsigned bin = luma >> 2;
356 int best_delta = (int)luma - ((int)bin * 4 + 2);
357 uint64_t best_error = (uint64_t)(best_delta * best_delta);
358 uint64_t margin = best_error / 8 + 128;
359 for (unsigned distance = 1; distance < 64; ++distance) {
360 bool found = false;
361 for (int direction = -1; direction <= 1; direction += 2) {
362 int index = (int)bin + direction * (int)distance;
363 if (index < 0 || index >= 64 || frame->table[index] != cell->glyph)
364 continue;
365 int delta = (int)luma - (index * 4 + 2);
366 uint64_t error = (uint64_t)(delta * delta);
367 if (error <= best_error || error - best_error <= margin)
368 glyph = cell->glyph;
369 found = true;
370 }
371 // Bins farther than this cannot fall within the error margin.
372 int nearest_delta = (int)distance * 4 - 3;
373 if (found || (uint64_t)(nearest_delta * nearest_delta) > best_error + margin)
374 break;
375 }
376 }
377 color = temporal_blend_color(cell->color, color, weight);
378 }
379 cell->samples[0] = luma;
380 cell->glyph = frame->glyph[x] = glyph;
381 cell->color = frame->packed[x] = color;
382 cell->valid = true;
383 if (background)
384 frame->luma[x] =
385 (uint8_t)((77u * (color >> 16) + 150u * ((color >> 8) & 255) + 29u * (color & 255) + 128u) >> 8);
386 }
387 if (!frame->dithered && (mode == ASCII_COLOR_16 || mode == ASCII_COLOR_256))
388 ops->quantize(frame->packed, frame->index, frame->width, mode == ASCII_COLOR_16 ? 16 : 256);
389 }
390 for (size_t x = 0; x < frame->width; ++x) {
391 frame->fg[x] = frame->bg[x] = UINT32_MAX;
392 if (mode == ASCII_COLOR_NONE)
393 continue;
394 uint32_t value = mode == ASCII_COLOR_TRUE ? frame->packed[x] : frame->index[x];
395 uint32_t contrast = frame->luma[x] < 128 ? (mode == ASCII_COLOR_TRUE ? 0xffffffu : 15u) : 0;
396 if (mode == ASCII_COLOR_16) {
397 uint8_t r, g, b;
398 get_16color_rgb((uint8_t)value, &r, &g, &b);
399 contrast = (77 * r + 150 * g + 29 * b) / 256 < 127 ? 15u : 0;
400 }
401 frame->fg[x] = background ? contrast : value;
402 frame->bg[x] = background ? value : UINT32_MAX;
403 }
404 emit_prepared_row(&frame->output, mode, ops, frame->glyph, frame->fg, frame->bg, frame->width);
405 emit_row_end(&frame->output, mode != ASCII_COLOR_NONE, more_rows);
406 ++frame->row;
407}
void get_16color_rgb(uint8_t color_index, uint8_t *r, uint8_t *g, uint8_t *b)
Definition ansi.c:397
unsigned long long uint64_t
Definition common.h:63
#define ASCII_TEMPORAL_COLOR_TIME_NS
#define ASCII_TEMPORAL_MAX_INTERVAL_NS
#define ASCII_TEMPORAL_MOTION_LIMIT
static bool size_t index
Definition queries.h:8

References ASCII_COLOR_16, ASCII_COLOR_256, ASCII_COLOR_NONE, ASCII_COLOR_TRUE, ASCII_TEMPORAL_COLOR_TIME_NS, ASCII_TEMPORAL_MAX_INTERVAL_NS, ASCII_TEMPORAL_MOTION_LIMIT, ascii_frame_t::bg, ascii_stabilizer_t::cells, ascii_temporal_cell_t::color, ascii_frame_t::dithered, ascii_stabilizer_t::elapsed_ns, emit_row_end(), ascii_frame_t::fg, get_16color_rgb(), ascii_frame_t::glyph, ascii_temporal_cell_t::glyph, ascii_frame_t::index, index, ascii_frame_t::luma, ascii_frame_t::output, ascii_frame_t::packed, simd_cells_t::quantize, ascii_frame_t::row, ascii_temporal_cell_t::samples, ascii_frame_t::stabilizer, ascii_frame_t::table, ascii_frame_t::temporal_preselected, ascii_temporal_cell_t::valid, and ascii_frame_t::width.

Referenced by ascii_frame_t::_Alignas(), ascii_render(), and ascii_render_glyph_match_mode().

◆ ascii_frame_finish()

char * ascii_frame_finish ( ascii_frame_t * frame)

Definition at line 408 of file lib/video/ascii/render.c.

408 {
409 if (frame->scratch != frame->inline_scratch)
410 release_scratch(frame->scratch);
411 ob_term(&frame->output);
412 if (frame->output_target)
413 *frame->output_target = frame->output;
414 return frame->output.buf;
415}
void ob_term(outbuf_t *ob)
Append null terminator to buffer.
char * buf
Buffer pointer (allocated, owned by caller, must be freed).

References outbuf_t::buf, ob_term(), ascii_frame_t::output, ascii_frame_t::output_target, and ascii_frame_t::scratch.

Referenced by ascii_frame_t::_Alignas(), ascii_render(), ascii_render_glyph_match_mode(), and ascii_render_halfblocks().

◆ ascii_frame_init()

asciichat_error_t ascii_frame_init ( ascii_frame_t * frame,
const image_t * image,
const char * palette )

Definition at line 305 of file lib/video/ascii/render.c.

305 {
306 ASCII_OTEL_SCOPE(ASCII_FRAME_INIT);
307 outbuf_t *target = g_output_target;
308 memset(frame, 0, sizeof(*frame));
309 if (!image || !image->pixels || image->w <= 0 || image->h <= 0 || !palette || !palette[0])
310 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid image or palette");
311 size_t bytes, pixels, capacity, rows;
312 if (checked_size_mul((size_t)image->w, (size_t)image->h, &pixels) != ASCIICHAT_OK ||
313 checked_size_mul(pixels, 48u, &capacity) != ASCIICHAT_OK ||
314 checked_size_mul((size_t)image->h, 5u, &rows) != ASCIICHAT_OK ||
315 checked_size_add(capacity, rows, &capacity) != ASCIICHAT_OK ||
316 checked_size_add(capacity, 1u, &capacity) != ASCIICHAT_OK ||
317 checked_size_mul((size_t)image->w, 6 * sizeof(uint32_t), &bytes) != ASCIICHAT_OK)
318 return ERROR_INVALID_PARAM;
320 if (!cache)
321 return ERROR_INVALID_PARAM;
322 prepare_output(frame, capacity, target);
323 frame->width = (size_t)image->w;
324 if (g_stabilizer_target && g_stabilizer_target->cells && g_stabilizer_target->width == (size_t)image->w &&
325 g_stabilizer_target->height == (size_t)image->h)
326 frame->stabilizer = g_stabilizer_target;
327 for (size_t i = 0; i < 64; ++i)
328 memcpy(frame->table + i, cache->cache64[i].utf8_bytes, cache->cache64[i].byte_len);
329 frame->scratch = acquire_scratch(frame->inline_scratch, bytes);
330 if (!frame->scratch)
331 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate ASCII scratch workspace");
332 frame->packed = frame->scratch;
333 frame->index = frame->packed + frame->width;
334 frame->glyph = frame->index + frame->width;
335 frame->fg = frame->glyph + frame->width;
336 frame->bg = frame->fg + frame->width;
337 frame->luma = (uint8_t *)(frame->bg + frame->width);
338 return ASCIICHAT_OK;
339}
#define SET_ERRNO(code,...)
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
uint8_t utf8_bytes[4]
utf8_palette_cache_t * get_utf8_palette_cache(const char *ascii_chars)
Get UTF-8 palette cache for a character set.
struct utf8_palette_cache utf8_palette_cache_t
utf8_char_t cache64[64]
#define ASCII_OTEL_SCOPE(id)
Definition otel.h:72
int w
Image width in pixels (must be > 0).
int h
Image height in pixels (must be > 0).
rgb_pixel_t * pixels
Pixel data array (width * height RGB pixels, row-major order).
Dynamic output buffer (auto-expanding).

References ASCII_OTEL_SCOPE, ASCIICHAT_OK, ascii_frame_t::bg, utf8_char_t::byte_len, utf8_palette_cache::cache64, ERROR_INVALID_PARAM, ERROR_MEMORY, ascii_frame_t::fg, get_utf8_palette_cache(), ascii_frame_t::glyph, image_t::h, ascii_frame_t::index, ascii_frame_t::luma, ascii_frame_t::packed, image_t::pixels, ascii_frame_t::scratch, SET_ERRNO, ascii_frame_t::stabilizer, ascii_frame_t::table, utf8_char_t::utf8_bytes, image_t::w, and ascii_frame_t::width.

Referenced by ascii_frame_t::_Alignas(), ascii_render(), and ascii_render_glyph_match_mode().

◆ ascii_glyph_palette_supported()

bool ascii_glyph_palette_supported ( const char * palette)

Definition at line 540 of file lib/video/ascii/render.c.

540 {
541 if (!palette || !*palette)
542 return false;
543 for (const uint8_t *p = (const uint8_t *)palette; *p;) {
544 size_t length;
545 if (glyph_palette_index(p, &length) < 0)
546 return false;
547 p += length;
548 }
549 return true;
550}

Referenced by acip_render_settings_prepare(), ascii_convert_with_buffers_stabilized(), ascii_render_glyph_match_mode(), mirror_convert_frame(), and validate_glyph_palette().

◆ ascii_half_frame_init()

asciichat_error_t ascii_half_frame_init ( ascii_frame_t * frame,
const uint8_t * rgb,
int width,
int height,
int * stride,
ascii_color_t mode )

Definition at line 417 of file lib/video/ascii/render.c.

418 {
419 ASCII_OTEL_SCOPE(ASCII_HALF_FRAME_INIT);
420 outbuf_t *target = g_output_target;
421 memset(frame, 0, sizeof(*frame));
422 size_t row_bytes, extent, bytes, capacity;
423 if (!rgb || width <= 0 || height <= 0 || mode < ASCII_COLOR_NONE || mode > ASCII_COLOR_TRUE ||
424 checked_size_mul((size_t)width, 3, &row_bytes) != ASCIICHAT_OK || row_bytes > INT_MAX)
425 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid half-block input");
426 if (*stride <= 0)
427 *stride = (int)row_bytes;
428 if ((size_t)*stride < row_bytes || checked_size_mul((size_t)*stride, (size_t)height, &extent) != ASCIICHAT_OK ||
429 checked_size_mul((size_t)width, 6 * sizeof(uint32_t), &bytes) != ASCIICHAT_OK ||
430 checked_size_mul((size_t)width, ((size_t)height + 1) / 2, &capacity) != ASCIICHAT_OK ||
431 checked_size_mul(capacity, 48u, &capacity) != ASCIICHAT_OK ||
432 checked_size_add(capacity, 1u, &capacity) != ASCIICHAT_OK)
433 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid half-block stride or size");
434 prepare_output(frame, capacity, target);
435 frame->width = (size_t)width;
436 frame->source_height = (size_t)height;
437 if (g_stabilizer_target && g_stabilizer_target->cells && g_stabilizer_target->width == (size_t)width &&
438 g_stabilizer_target->height == (size_t)height)
439 frame->stabilizer = g_stabilizer_target;
440 frame->scratch = acquire_scratch(frame->inline_scratch, bytes);
441 if (!frame->scratch)
442 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate ASCII scratch workspace");
443 frame->packed = frame->scratch;
444 frame->index = frame->packed + width;
445 frame->glyph = frame->index + width;
446 frame->fg = frame->glyph + width;
447 frame->bg = frame->fg + width;
448 const char blocks[5][4] = {"\xe2\x96\x91", "\xe2\x96\x92", "\xe2\x96\x93", "\xe2\x96\x88", "\xe2\x96\x80"};
449 for (size_t i = 0; i < 5; ++i)
450 memcpy(frame->table + i, blocks[i], 3);
451 return ASCIICHAT_OK;
452}
unsigned int height
Definition fft.c:23
unsigned int width
Definition fft.c:23

References ASCII_COLOR_TRUE, ASCII_OTEL_SCOPE, ASCIICHAT_OK, ascii_frame_t::bg, ERROR_INVALID_PARAM, ERROR_MEMORY, ascii_frame_t::fg, ascii_frame_t::glyph, height, ascii_frame_t::index, ascii_frame_t::packed, ascii_frame_t::scratch, SET_ERRNO, ascii_frame_t::source_height, ascii_frame_t::stabilizer, ascii_frame_t::table, ascii_frame_t::width, and width.

Referenced by ascii_render_halfblocks().

◆ ascii_render()

char * ascii_render ( const image_t * image,
const char * palette,
ascii_color_t mode,
bool background,
bool dither,
const simd_cells_t * ops )

Definition at line 197 of file lib/video/ascii/render.c.

198 {
199 if (!ops || mode < ASCII_COLOR_NONE || mode > ASCII_COLOR_TRUE) {
200 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid rendering backend or color mode");
201 return NULL;
202 }
203 if (!dither) {
204#if SIMD_SUPPORT_SSE2
205 if (ops == &simd_cells_sse2)
206 return render_ascii_cells_sse2(image, palette, mode, background);
207#endif
208#if SIMD_SUPPORT_SSSE3
209 if (ops == &simd_cells_ssse3)
210 return render_ascii_cells_ssse3(image, palette, mode, background);
211#endif
212#if SIMD_SUPPORT_AVX2
213 if (ops == &simd_cells_avx2)
214 return render_ascii_cells_avx2(image, palette, mode, background);
215#endif
216#if SIMD_SUPPORT_NEON
217 if (ops == &simd_cells_neon)
218 return render_ascii_cells_neon(image, palette, mode, background);
219#endif
220#if SIMD_SUPPORT_SVE
221 if (ops == &simd_cells_sve)
222 return render_ascii_cells_sve(image, palette, mode, background);
223#endif
224 }
225 ascii_frame_t frame;
226 if (ascii_frame_init(&frame, image, palette) != ASCIICHAT_OK)
227 return NULL;
228 rgb_error_t *errors = NULL;
229 if (mode == ASCII_COLOR_16 && dither)
230 errors = SAFE_CALLOC((size_t)image->w * (size_t)image->h, sizeof(rgb_error_t), rgb_error_t *);
231 frame.dithered = errors != NULL;
232 for (int y = 0; y < image->h; ++y) {
233 const uint8_t *source = (const uint8_t *)(image->pixels + (size_t)y * frame.width);
234 ops->luminance(source, frame.luma, frame.width);
235 ops->pack(source, frame.packed, frame.width);
236 ops->lookup(frame.luma, frame.table, frame.glyph, frame.width);
237 if (mode == ASCII_COLOR_16 || mode == ASCII_COLOR_256) {
238 if (errors) {
239 // Floyd-Steinberg error propagation is ordered; the other stages remain SIMD.
240 for (int x = 0; x < image->w; ++x) {
241 uint32_t pixel = frame.packed[x];
242 frame.index[x] = rgb_to_16color_dithered((int)(pixel >> 16), (int)((pixel >> 8) & 255), (int)(pixel & 255), x,
243 y, image->w, image->h, errors);
244 }
245 } else
246 ops->quantize(frame.packed, frame.index, frame.width, mode == ASCII_COLOR_16 ? 16 : 256);
247 }
248 ascii_frame_emit_row(&frame, mode, background, y + 1 < image->h, ops);
249 }
250 SAFE_FREE(errors);
251 return ascii_frame_finish(&frame);
252}
const simd_cells_t simd_cells_sve
const simd_cells_t simd_cells_sse2
const simd_cells_t simd_cells_avx2
const simd_cells_t simd_cells_ssse3
const simd_cells_t simd_cells_neon
audio_visualization_source_t source
Definition fft.c:24
uint8_t rgb_to_16color_dithered(int r, int g, int b, int x, int y, int width, int height, rgb_error_t *error_buffer)
Definition ansi.c:428
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
char * render_ascii_cells_sve(const image_t *, const char *, ascii_color_t, bool)
char * render_ascii_cells_sse2(const image_t *, const char *, ascii_color_t, bool)
char * render_ascii_cells_ssse3(const image_t *, const char *, ascii_color_t, bool)
char * render_ascii_cells_neon(const image_t *, const char *, ascii_color_t, bool)
char * render_ascii_cells_avx2(const image_t *, const char *, ascii_color_t, bool)
char * ascii_frame_finish(ascii_frame_t *frame)
void ascii_frame_emit_row(ascii_frame_t *frame, ascii_color_t mode, bool background, bool more_rows, const simd_cells_t *ops)
asciichat_error_t ascii_frame_init(ascii_frame_t *frame, const image_t *image, const char *palette)
When code needs to work with ascii frame, it keeps output, output_target, width, source_height here i...
void(* pack)(const uint8_t *rgb, uint32_t *out, size_t count)
Definition cells.h:12
simd_luminance_fn luminance
Definition cells.h:11
void(* lookup)(const uint8_t *luma, const uint32_t table[64], uint32_t *out, size_t count)
Definition cells.h:13

References ASCII_COLOR_16, ASCII_COLOR_256, ASCII_COLOR_TRUE, ascii_frame_emit_row(), ascii_frame_finish(), ascii_frame_init(), ASCIICHAT_OK, ascii_frame_t::dithered, ERROR_INVALID_PARAM, ascii_frame_t::glyph, image_t::h, ascii_frame_t::index, simd_cells_t::lookup, ascii_frame_t::luma, simd_cells_t::luminance, simd_cells_t::pack, ascii_frame_t::packed, image_t::pixels, simd_cells_t::quantize, render_ascii_cells_avx2(), render_ascii_cells_neon(), render_ascii_cells_sse2(), render_ascii_cells_ssse3(), render_ascii_cells_sve(), rgb_to_16color_dithered(), SAFE_CALLOC, SAFE_FREE, SET_ERRNO, simd_cells_avx2, simd_cells_neon, simd_cells_sse2, simd_cells_ssse3, simd_cells_sve, source, ascii_frame_t::table, image_t::w, and ascii_frame_t::width.

Referenced by image_print(), image_print_16color(), image_print_16color_dithered(), image_print_16color_dithered_with_background(), image_print_256color(), image_print_color(), image_print_color_background(), image_print_color_simd(), image_print_color_utf8(), and image_print_with_capabilities().

◆ ascii_render_glyph_match()

char * ascii_render_glyph_match ( const image_t * image,
ascii_color_t mode,
const simd_cells_t * ops,
const char * palette )

Definition at line 584 of file lib/video/ascii/render.c.

584 {
585 return ascii_render_glyph_match_mode(image, mode, ops, palette, false);
586}
char * ascii_render_glyph_match_mode(const image_t *image, ascii_color_t mode, const simd_cells_t *ops, const char *palette, bool background)

References ascii_render_glyph_match_mode().

◆ ascii_render_glyph_match_mode()

char * ascii_render_glyph_match_mode ( const image_t * image,
ascii_color_t mode,
const simd_cells_t * ops,
const char * palette,
bool background )

Definition at line 588 of file lib/video/ascii/render.c.

589 {
590 if (!image || !image->pixels || !ops || image->w <= 0 || image->h <= 0 || image->w % 8 || image->h % 8 ||
591 mode < ASCII_COLOR_NONE || mode > ASCII_COLOR_TRUE) {
592 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid glyph matching input");
593 return NULL;
594 }
595 bool allowed[GLYPH_COUNT] = {false};
596 int candidates[GLYPH_COUNT];
597 // Restrict the atlas to unique palette entries before scoring any cells.
598 size_t candidate_count = 0;
599 int (*shape_correlation)(const int16_t[16], const int16_t[16]) =
601 if (palette) {
602 if (!ascii_glyph_palette_supported(palette)) {
603 SET_ERRNO(ERROR_INVALID_PARAM, "Glyph palette contains an unsupported character");
604 return NULL;
605 }
606 for (const uint8_t *p = (const uint8_t *)palette; *p;) {
607 size_t length;
608 int index = glyph_palette_index(p, &length);
609 if (!allowed[index]) {
610 allowed[index] = true;
611 candidates[candidate_count++] = index;
612 }
613 p += length;
614 }
615 } else {
616 for (int i = 0; i < GLYPH_ASCII_COUNT; ++i) {
617 allowed[i] = true;
618 candidates[candidate_count++] = i;
619 }
620 }
621 // Prepare coverage and its sufficient statistics once per frame.
623 // Precompute each mask's brightness and centered shape for correlation.
624 for (size_t c = 0; c < candidate_count; ++c) {
625 int g = candidates[c];
626 glyph_candidate_t *glyph = &glyphs[c];
627 glyph->key = glyph_key(g);
628 uint8_t block_mask[16];
629 const uint8_t *mask = g < GLYPH_ASCII_COUNT ? glyph_masks[g] : block_mask;
630 if (g >= GLYPH_ASCII_COUNT)
631 glyph_block_mask(g - GLYPH_ASCII_COUNT, block_mask);
632 glyph->sum = glyph->energy = 0;
633 for (int i = 0; i < 16; ++i) {
634 glyph->mask[i] = mask[i];
635 glyph->sum += mask[i];
636 }
637 int mean = glyph->sum / 16;
638 glyph->tone = glyph->sum * 255 / (g < GLYPH_ASCII_COUNT ? GLYPH_MASK_MAX_SUM : 16 * 255);
639 for (int i = 0; i < 16; ++i) {
640 glyph->centered[i] = (int16_t)((int)mask[i] - mean);
641 glyph->energy += glyph->centered[i] * glyph->centered[i];
642 }
643 }
644 image_t cells = *image;
645 cells.w /= 8;
646 cells.h /= 8;
647 ascii_frame_t frame;
648 if (ascii_frame_init(&frame, &cells, " .") != ASCIICHAT_OK)
649 return NULL;
650 frame.temporal_preselected = true;
651 for (int y = 0; y < cells.h; ++y) {
652 for (int x = 0; x < cells.w; ++x) {
653 int16_t samples[3][16] = {{0}};
654 int sums[3] = {0};
655 uint8_t patch[16];
656 int sample_mean = 0;
657 for (int sy = 0; sy < 4; ++sy) {
658 const rgb_pixel_t *top = image->pixels + (size_t)(y * 8 + sy * 2) * (size_t)image->w + (size_t)(x * 8);
659 const rgb_pixel_t *bottom = top + image->w;
660 for (int sx = 0; sx < 4; ++sx) {
661 const rgb_pixel_t a = top[sx * 2], b = top[sx * 2 + 1];
662 const rgb_pixel_t c = bottom[sx * 2], d = bottom[sx * 2 + 1];
663 int i = sy * 4 + sx;
664 int red = (a.r + b.r + c.r + d.r) / 4;
665 int green = (a.g + b.g + c.g + d.g) / 4;
666 int blue = (a.b + b.b + c.b + d.b) / 4;
667 patch[i] = (uint8_t)((77 * red + 150 * green + 29 * blue) >> 8);
668 samples[0][i] = (int16_t)red;
669 samples[1][i] = (int16_t)green;
670 samples[2][i] = (int16_t)blue;
671 sample_mean += patch[i];
672 for (int channel = 0; channel < 3; ++channel)
673 sums[channel] += samples[channel][i];
674 }
675 }
676 // Compare local shape independently of the patch's average brightness.
677 sample_mean /= 16;
678 int16_t centered_samples[16];
679 int sample_energy = 0;
680 for (int i = 0; i < 16; ++i) {
681 int centered = (int)patch[i] - sample_mean;
682 centered_samples[i] = (int16_t)centered;
683 sample_energy += centered * centered;
684 }
685 size_t best = 0, previous_index = 0;
686 uint64_t best_error = UINT64_MAX;
687 uint64_t previous_error = UINT64_MAX;
688 ascii_temporal_cell_t *previous = frame.stabilizer ? &frame.stabilizer->cells[(size_t)y * frame.width + x] : NULL;
689 for (size_t c = 0; c < candidate_count; ++c) {
690 bool is_previous = previous && previous->valid && previous->glyph == glyphs[c].key;
691 int tone_delta = sample_mean - glyphs[c].tone;
692 int tone_error = 16 * tone_delta * tone_delta;
693 // Shape error cannot lower the score below the brightness error.
694 if (!is_previous && (uint64_t)tone_error > best_error)
695 continue;
696 int dot = shape_correlation(centered_samples, glyphs[c].centered);
697 uint64_t error = (uint64_t)(tone_error + sample_energy + glyphs[c].energy - 2 * dot);
698 if (is_previous) {
699 previous_error = error;
700 previous_index = c;
701 }
702 if (error < best_error) {
703 best_error = error;
704 best = c;
705 }
706 }
707 uint32_t rgb = glyph_foreground(&glyphs[best], samples, sums);
708 uint32_t bg_rgb =
709 ((uint32_t)(sums[0] / 128) << 16) | ((uint32_t)(sums[1] / 128) << 8) | (uint32_t)(sums[2] / 128);
710 uint32_t selected = glyphs[best].key;
711 if (previous) {
712 ascii_temporal_cell_t old = *previous;
713 unsigned motion = 0;
714 unsigned color_motion = 0;
715 for (int i = 0; i < 16; ++i)
716 motion += (unsigned)abs((int)patch[i] - (int)old.samples[i]);
717 if (old.valid && mode != ASCII_COLOR_NONE) {
718 for (int channel = 0; channel < 3; ++channel)
719 for (int i = 0; i < 16; ++i)
720 color_motion += (unsigned)abs(samples[channel][i] - (int)old.rgb_samples[channel][i]);
721 color_motion /= 48;
722 }
723 uint64_t elapsed = frame.stabilizer->elapsed_ns;
724 uint64_t effective_elapsed = color_motion > ASCII_TEMPORAL_MOTION_LIMIT ? UINT64_MAX : elapsed;
725 selected = ascii_stabilizer_choose(previous, patch, selected, best_error, previous_error, rgb,
726 effective_elapsed, &rgb);
727 if (selected != glyphs[best].key) {
728 *previous = old;
729 selected = ascii_stabilizer_choose(previous, patch, glyphs[best].key, best_error, previous_error,
730 glyph_foreground(&glyphs[previous_index], samples, sums),
731 effective_elapsed, &rgb);
732 }
733 if (old.valid && motion / 16 <= ASCII_TEMPORAL_MOTION_LIMIT && color_motion <= ASCII_TEMPORAL_MOTION_LIMIT &&
735 unsigned weight = (unsigned)(elapsed * 256 / (elapsed + ASCII_TEMPORAL_COLOR_TIME_NS));
736 bg_rgb = temporal_blend_color(old.background, bg_rgb, weight);
737 }
738 for (int channel = 0; channel < 3; ++channel)
739 for (int i = 0; i < 16; ++i)
740 previous->rgb_samples[channel][i] = (uint8_t)samples[channel][i];
741 previous->background = bg_rgb;
742 }
743 frame.glyph[x] = selected;
744 frame.packed[x] = rgb;
745 frame.index[x] = bg_rgb;
746 frame.luma[x] = (uint8_t)((77 * ((rgb >> 16) & 255) + 150 * ((rgb >> 8) & 255) + 29 * (rgb & 255)) >> 8);
747 }
748 if (background && mode != ASCII_COLOR_NONE) {
749 if (mode == ASCII_COLOR_TRUE) {
750 memcpy(frame.fg, frame.packed, frame.width * sizeof(uint32_t));
751 memcpy(frame.bg, frame.index, frame.width * sizeof(uint32_t));
752 } else {
753 ops->quantize(frame.packed, frame.fg, frame.width, mode == ASCII_COLOR_16 ? 16 : 256);
754 ops->quantize(frame.index, frame.bg, frame.width, mode == ASCII_COLOR_16 ? 16 : 256);
755 }
756 emit_prepared_row(&frame.output, mode, ops, frame.glyph, frame.fg, frame.bg, frame.width);
757 emit_row_end(&frame.output, true, y + 1 < cells.h);
758 ++frame.row;
759 } else {
760 if (mode == ASCII_COLOR_16 || mode == ASCII_COLOR_256)
761 ops->quantize(frame.packed, frame.index, frame.width, mode == ASCII_COLOR_16 ? 16 : 256);
762 ascii_frame_emit_row(&frame, mode, false, y + 1 < cells.h, ops);
763 }
764 }
765 return ascii_frame_finish(&frame);
766}
int glyph_shape_correlation_scalar(const int16_t samples[16], const int16_t mask[16])
Definition cells.c:4
char cells[(512 *256 *2)]
Definition fft.c:21
#define GLYPH_MASK_MAX_SUM
#define GLYPH_COUNT
#define GLYPH_ASCII_COUNT
bool ascii_glyph_palette_supported(const char *palette)
SIMD-friendly storage for one RGB pixel. r, g, and b hold the color channels and padding keeps the la...
uint8_t b
Blue color component (0-255).
uint8_t g
Green color component (0-255).
uint8_t r
Red color component (0-255).
int(* glyph_shape_correlation)(const int16_t samples[16], const int16_t mask[16])
Definition cells.h:20

References ASCII_COLOR_16, ASCII_COLOR_256, ASCII_COLOR_NONE, ASCII_COLOR_TRUE, ascii_frame_emit_row(), ascii_frame_finish(), ascii_frame_init(), ascii_glyph_palette_supported(), ascii_stabilizer_choose(), ASCII_TEMPORAL_COLOR_TIME_NS, ASCII_TEMPORAL_MAX_INTERVAL_NS, ASCII_TEMPORAL_MOTION_LIMIT, ASCIICHAT_OK, rgb_pixel_t::b, ascii_temporal_cell_t::background, ascii_frame_t::bg, ascii_stabilizer_t::cells, cells, glyph_candidate_t::centered, ascii_stabilizer_t::elapsed_ns, emit_row_end(), glyph_candidate_t::energy, ERROR_INVALID_PARAM, ascii_frame_t::fg, rgb_pixel_t::g, ascii_frame_t::glyph, GLYPH_ASCII_COUNT, GLYPH_COUNT, GLYPH_MASK_MAX_SUM, simd_cells_t::glyph_shape_correlation, glyph_shape_correlation_scalar(), image_t::h, ascii_frame_t::index, index, glyph_candidate_t::key, ascii_frame_t::luma, glyph_candidate_t::mask, ascii_frame_t::output, ascii_frame_t::packed, image_t::pixels, simd_cells_t::quantize, rgb_pixel_t::r, ascii_temporal_cell_t::rgb_samples, ascii_frame_t::row, ascii_temporal_cell_t::samples, SET_ERRNO, ascii_frame_t::stabilizer, glyph_candidate_t::sum, ascii_frame_t::temporal_preselected, glyph_candidate_t::tone, ascii_temporal_cell_t::valid, image_t::w, and ascii_frame_t::width.

Referenced by ascii_convert_with_buffers_stabilized(), and ascii_render_glyph_match().

◆ ascii_render_halfblocks()

char * ascii_render_halfblocks ( const uint8_t * rgb,
int width,
int height,
int stride_bytes,
ascii_color_t color,
const simd_cells_t * ops )

Two source rows per terminal row; the supplied backend processes both rows.

Definition at line 254 of file lib/video/ascii/render.c.

255 {
256#if SIMD_SUPPORT_SSE2
257 if (ops == &simd_cells_sse2)
258 return render_halfblocks_sse2(rgb, width, height, stride, mode);
259#endif
260#if SIMD_SUPPORT_SSSE3
261 if (ops == &simd_cells_ssse3)
262 return render_halfblocks_ssse3(rgb, width, height, stride, mode);
263#endif
264#if SIMD_SUPPORT_AVX2
265 if (ops == &simd_cells_avx2)
266 return render_halfblocks_avx2(rgb, width, height, stride, mode);
267#endif
268#if SIMD_SUPPORT_NEON
269 if (ops == &simd_cells_neon)
270 return render_halfblocks_neon(rgb, width, height, stride, mode);
271#endif
272#if SIMD_SUPPORT_SVE
273 if (ops == &simd_cells_sve)
274 return render_halfblocks_sve(rgb, width, height, stride, mode);
275#endif
276
277 if (width <= 0 || height <= 0)
278 return platform_strdup("");
279 if (!ops) {
280 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid half-block backend");
281 return NULL;
282 }
283 ascii_frame_t frame;
284 if (ascii_half_frame_init(&frame, rgb, width, height, &stride, mode) != ASCIICHAT_OK)
285 return NULL;
286 for (size_t y = 0; y < (size_t)height; y += 2) {
287 const uint8_t *top = rgb + y * (size_t)stride;
288 const uint8_t *bottom = y + 1 < (size_t)height ? top + stride : top;
289 ops->pack(top, frame.packed, frame.width);
290 ops->pack(bottom, frame.index, frame.width);
291 if (mode == ASCII_COLOR_TRUE) {
292 memcpy(frame.fg, frame.packed, frame.width * sizeof(uint32_t));
293 memcpy(frame.bg, frame.index, frame.width * sizeof(uint32_t));
294 } else if (mode != ASCII_COLOR_NONE) {
295 ops->quantize(frame.packed, frame.fg, frame.width, mode == ASCII_COLOR_16 ? 16 : 256);
296 ops->quantize(frame.index, frame.bg, frame.width, mode == ASCII_COLOR_16 ? 16 : 256);
297 }
298 ops->halfblock(frame.packed, frame.index, frame.glyph, frame.fg, frame.bg, frame.width, (unsigned)mode,
299 frame.table);
300 ascii_frame_emit_halfrow(&frame, mode, y + 2 < (size_t)height, ops);
301 }
302 return ascii_frame_finish(&frame);
303}
char * render_halfblocks_sse2(const uint8_t *, int, int, int, ascii_color_t)
char * render_halfblocks_ssse3(const uint8_t *, int, int, int, ascii_color_t)
char * render_halfblocks_avx2(const uint8_t *, int, int, int, ascii_color_t)
char * render_halfblocks_sve(const uint8_t *, int, int, int, ascii_color_t)
char * render_halfblocks_neon(const uint8_t *, int, int, int, ascii_color_t)
asciichat_error_t ascii_half_frame_init(ascii_frame_t *frame, const uint8_t *rgb, int width, int height, int *stride, ascii_color_t mode)
void ascii_frame_emit_halfrow(ascii_frame_t *frame, ascii_color_t mode, bool more_rows, const simd_cells_t *ops)

References ASCII_COLOR_16, ASCII_COLOR_NONE, ASCII_COLOR_TRUE, ascii_frame_emit_halfrow(), ascii_frame_finish(), ascii_half_frame_init(), ASCIICHAT_OK, ascii_frame_t::bg, ERROR_INVALID_PARAM, ascii_frame_t::fg, ascii_frame_t::glyph, simd_cells_t::halfblock, height, ascii_frame_t::index, simd_cells_t::pack, ascii_frame_t::packed, simd_cells_t::quantize, render_halfblocks_avx2(), render_halfblocks_neon(), render_halfblocks_sse2(), render_halfblocks_ssse3(), render_halfblocks_sve(), SET_ERRNO, simd_cells_avx2, simd_cells_neon, simd_cells_sse2, simd_cells_ssse3, simd_cells_sve, ascii_frame_t::table, ascii_frame_t::width, and width.

Referenced by ascii_convert(), image_print_with_capabilities(), rgb_to_16color_halfblocks_scalar(), rgb_to_256color_halfblocks_scalar(), rgb_to_halfblocks_scalar(), and rgb_to_truecolor_halfblocks_scalar().

◆ ascii_render_set_output_target()

outbuf_t * ascii_render_set_output_target ( outbuf_t * target)

Internal one-shot output target, consumed by the next frame initializer. Returns the prior target so capability conversion can restore its scope.

Definition at line 126 of file lib/video/ascii/render.c.

126 {
127 outbuf_t *previous = g_output_target;
128 g_output_target = target;
129 return previous;
130}

Referenced by ascii_convert_with_buffers_stabilized().

◆ ascii_stabilizer_choose()

uint32_t ascii_stabilizer_choose ( ascii_temporal_cell_t * cell,
const uint8_t samples[16],
uint32_t candidate,
uint64_t candidate_error,
uint64_t previous_error,
uint32_t color,
uint64_t elapsed_ns,
uint32_t * stabilized_color )

Choose a glyph using current and previous candidate errors and patch motion.

Definition at line 97 of file lib/video/ascii/render.c.

99 {
100 unsigned motion = 0;
101 if (cell->valid) {
102 for (size_t i = 0; i < 16; ++i)
103 motion += (unsigned)abs((int)samples[i] - (int)cell->samples[i]);
104 motion /= 16;
105 }
106 uint32_t result = candidate;
107 uint32_t output_color = color;
108 if (cell->valid && motion <= ASCII_TEMPORAL_MOTION_LIMIT && elapsed_ns <= ASCII_TEMPORAL_MAX_INTERVAL_NS) {
109 uint64_t margin = candidate_error / 8 + 128;
110 if (previous_error != UINT64_MAX &&
111 (previous_error <= candidate_error || previous_error - candidate_error <= margin))
112 result = cell->glyph;
113 // Weight depends on elapsed time, avoiding a different response at each FPS.
114 unsigned weight = (unsigned)(elapsed_ns * 256 / (elapsed_ns + ASCII_TEMPORAL_COLOR_TIME_NS));
115 output_color = temporal_blend_color(cell->color, color, weight);
116 }
117 memcpy(cell->samples, samples, sizeof(cell->samples));
118 cell->glyph = result;
119 cell->color = output_color;
120 cell->valid = true;
121 if (stabilized_color)
122 *stabilized_color = output_color;
123 return result;
124}

References ASCII_TEMPORAL_COLOR_TIME_NS, ASCII_TEMPORAL_MAX_INTERVAL_NS, ASCII_TEMPORAL_MOTION_LIMIT, ascii_temporal_cell_t::color, ascii_temporal_cell_t::glyph, ascii_temporal_cell_t::samples, and ascii_temporal_cell_t::valid.

Referenced by ascii_frame_emit_halfrow(), and ascii_render_glyph_match_mode().

◆ ascii_stabilizer_prepare()

asciichat_error_t ascii_stabilizer_prepare ( ascii_stabilizer_t * state,
size_t width,
size_t height,
uint64_t configuration )

Prepare history for a viewport and rendering contract, resetting it on change.

Definition at line 67 of file lib/video/ascii/render.c.

68 {
69 if (!state || !width || !height || width > SIZE_MAX / height ||
70 width * height > SIZE_MAX / sizeof(ascii_temporal_cell_t))
71 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid stabilization viewport");
72 if (state->cells && state->width == width && state->height == height && state->configuration == configuration)
73 return ASCIICHAT_OK;
75 if (!cells)
76 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate stabilization history");
77 SAFE_FREE(state->cells);
78 state->cells = cells;
79 state->width = width;
80 state->height = height;
81 state->configuration = configuration;
82 state->last_timestamp_ns = 0;
83 state->elapsed_ns = UINT64_MAX;
84 return ASCIICHAT_OK;
85}

References ASCIICHAT_OK, cells, ERROR_INVALID_PARAM, ERROR_MEMORY, height, SAFE_CALLOC, SAFE_FREE, SET_ERRNO, and width.

Referenced by ascii_convert_with_buffers_stabilized().

◆ ascii_stabilizer_reset()

void ascii_stabilizer_reset ( ascii_stabilizer_t * state)

Definition at line 60 of file lib/video/ascii/render.c.

60 {
61 if (!state)
62 return;
63 SAFE_FREE(state->cells);
64 memset(state, 0, sizeof(*state));
65}

References SAFE_FREE.

Referenced by mirror_convert_frame(), session_display_destroy(), session_display_reset_stabilizer(), session_host_destroy(), and session_render_reset_stabilizer().

◆ ascii_stabilizer_set_target()

ascii_stabilizer_t * ascii_stabilizer_set_target ( ascii_stabilizer_t * state)

Select history for the current thread until the previous target is restored.

Definition at line 54 of file lib/video/ascii/render.c.

54 {
55 ascii_stabilizer_t *previous = g_stabilizer_target;
56 g_stabilizer_target = state;
57 return previous;
58}

Referenced by ascii_convert_with_buffers_stabilized().