Created
April 1, 2026 03:44
-
-
Save scturtle/4e3325c1dc2149d9e4f5e37c56466c3b to your computer and use it in GitHub Desktop.
kitty graphics protocol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* gcc -O2 -march=native -o kgp kgp.c -lm */ | |
| #define _POSIX_C_SOURCE 200809L | |
| #include <math.h> | |
| #include <signal.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <time.h> | |
| #ifndef M_PI | |
| #define M_PI 3.14159265358979323846 | |
| #endif | |
| #define WIDTH 320 | |
| #define HEIGHT 200 | |
| /* ════════════════════════════════════════════════════════════════════════ | |
| * Math & Plasma Animation | |
| * ════════════════════════════════════════════════════════════════════════ */ | |
| static float sin_lut[4096]; | |
| void init_sin_lut() { | |
| for (int i = 0; i < 4096; i++) | |
| sin_lut[i] = sinf((float)i * (2.f * (float)M_PI / 4096.f)); | |
| } | |
| static inline float fsin(float x) { | |
| return sin_lut[(int)(x * (4096.f / (2.f * (float)M_PI))) & 4095]; | |
| } | |
| static void plasma_frame(uint8_t *rgb, float t) { | |
| for (int y = 0; y < HEIGHT; y++) { | |
| float fy = (float)y / HEIGHT, cy = fy - 0.5f; | |
| for (int x = 0; x < WIDTH; x++) { | |
| float fx = (float)x / WIDTH, cx = fx - 0.5f; | |
| float v = fsin(fx * 10.f + t) + fsin(fy * 8.f + t * 0.7f) + | |
| fsin((fx + fy) * 7.f + t * 1.1f) + | |
| fsin(sqrtf(cx * cx + cy * cy) * 18.f - t * 1.8f); | |
| float a = v * (float)M_PI * 0.25f; | |
| *rgb++ = (uint8_t)(128.f + 127.f * fsin(a)); | |
| *rgb++ = (uint8_t)(128.f + 127.f * fsin(a + 2.094f)); | |
| *rgb++ = (uint8_t)(128.f + 127.f * fsin(a + 4.189f)); | |
| } | |
| } | |
| } | |
| /* ════════════════════════════════════════════════════════════════════════ | |
| * Base64 & Kitty Graphics Protocol | |
| * ════════════════════════════════════════════════════════════════════════ */ | |
| static size_t base64_encode(const uint8_t *in, size_t len, char *out) { | |
| static const char b64[] = | |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
| char *p = out; | |
| for (size_t i = 0; i < len; i += 3) { | |
| uint32_t w = (in[i] << 16) | ((i + 1 < len ? in[i + 1] : 0) << 8) | | |
| ((i + 2 < len ? in[i + 2] : 0)); | |
| *p++ = b64[(w >> 18) & 0x3f]; | |
| *p++ = b64[(w >> 12) & 0x3f]; | |
| *p++ = (i + 1 < len) ? b64[(w >> 6) & 0x3f] : '='; | |
| *p++ = (i + 2 < len) ? b64[w & 0x3f] : '='; | |
| } | |
| return p - out; | |
| } | |
| static struct { | |
| long id; | |
| int frame; | |
| char b64[256005]; /* 320*200*3/3*4 ≈ 256000 */ | |
| char proto[300000]; | |
| } ctx; | |
| static void render_frame(const uint8_t *rgb) { | |
| size_t b64_len = base64_encode(rgb, WIDTH * HEIGHT * 3, ctx.b64); | |
| char *buf = ctx.proto; | |
| size_t off = 0; | |
| for (size_t i = 0; i < b64_len; i += 4096) { | |
| int more = (i + 4096 < b64_len) ? 1 : 0; | |
| size_t chunk = more ? 4096 : (b64_len - i); | |
| if (i == 0) { | |
| if (ctx.frame == 0) | |
| off += | |
| sprintf(buf + off, "\033_Ga=T,i=%ld,f=24,s=%d,v=%d,q=2,c=60,m=%d;", | |
| ctx.id, WIDTH, HEIGHT, more); | |
| else | |
| off += | |
| sprintf(buf + off, "\033_Ga=f,r=1,i=%ld,f=24,q=2,s=%d,v=%d,m=%d;", | |
| ctx.id, WIDTH, HEIGHT, more); | |
| } else { | |
| if (ctx.frame == 0) | |
| off += sprintf(buf + off, "\033_Gm=%d;", more); | |
| else | |
| off += sprintf(buf + off, "\033_Ga=f,r=1,q=2,m=%d;", more); | |
| } | |
| memcpy(buf + off, ctx.b64 + i, chunk); | |
| off += chunk; | |
| memcpy(buf + off, "\033\\", 2); | |
| off += 2; | |
| } | |
| if (ctx.frame > 0) | |
| off += sprintf(buf + off, "\033_Ga=a,q=2,c=1,i=%ld;\033\\", ctx.id); | |
| else { | |
| memcpy(buf + off, "\r\n", 2); | |
| off += 2; | |
| } | |
| fwrite(ctx.proto, 1, off, stdout); | |
| fflush(stdout); | |
| ctx.frame++; | |
| } | |
| static void render_init() { printf("\033[?25l\033[2J\033[H"); } | |
| static void render_done(long id) { | |
| printf("\033_Ga=d,q=2,i=%ld;\033\\\033[H\033[2J\033[?25h", id); | |
| fflush(stdout); | |
| } | |
| /* ════════════════════════════════════════════════════════════════════════ | |
| * Main Loop | |
| * ════════════════════════════════════════════════════════════════════════ */ | |
| static volatile sig_atomic_t g_quit = 0; | |
| static void on_signal(int s) { | |
| (void)s; | |
| g_quit = 1; | |
| } | |
| int main(void) { | |
| struct sigaction sa; | |
| memset(&sa, 0, sizeof(sa)); | |
| sa.sa_handler = on_signal; | |
| sigaction(SIGINT, &sa, NULL); | |
| sigaction(SIGTERM, &sa, NULL); | |
| srand((unsigned)time(NULL)); | |
| ctx.id = (long)rand() | 1; | |
| struct timespec last_time; | |
| clock_gettime(CLOCK_MONOTONIC, &last_time); | |
| float t = 0.f; | |
| int frames = 0; | |
| static uint8_t rgb24[WIDTH * HEIGHT * 3]; | |
| init_sin_lut(); | |
| render_init(); | |
| while (!g_quit) { | |
| plasma_frame(rgb24, t); | |
| render_frame(rgb24); | |
| t += 0.01f; | |
| frames++; | |
| struct timespec now; | |
| clock_gettime(CLOCK_MONOTONIC, &now); | |
| long ms = (now.tv_sec - last_time.tv_sec) * 1000L + | |
| (now.tv_nsec - last_time.tv_nsec) / 1000000L; | |
| if (ms >= 1000) { | |
| fprintf(stderr, "FPS: %d\r", frames); | |
| frames = 0; | |
| last_time = now; | |
| } | |
| } | |
| render_done(ctx.id); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment