diff --git a/apps/cload/ChangeLog b/apps/cload/ChangeLog new file mode 100644 index 0000000000..263d4078d2 --- /dev/null +++ b/apps/cload/ChangeLog @@ -0,0 +1 @@ +0.01: attempt to import diff --git a/apps/cload/Makefile b/apps/cload/Makefile new file mode 100644 index 0000000000..2b2cf77458 --- /dev/null +++ b/apps/cload/Makefile @@ -0,0 +1,45 @@ +// === Makefile === +TARGET=cload +ARCH_FLAGS= +CROSS=arm-linux-gnueabihf- +CC=$(CROSS)gcc +# -nostdinc -- ouch, olive needs stddef.h +# -mfloat-abi=soft -- breaks build with __aeabi_fsub references +CFLAGS=-ffreestanding -Os -nostdlib -fPIC $(ARCH_FLAGS) -mlittle-endian -mthumb -mcpu=cortex-m3 -mfix-cortex-m3-ldrd -mthumb-interwork +# --emit-relocs +LDFLAGS=-Ttext=0x0100 $(ARCH_FLAGS) + +all: $(TARGET).bin + +$(TARGET).o: $(TARGET).c lib.c + #-Wl,--emit-relocs + $(CC) $(CFLAGS) -c -o $@ $< + +triangle3d.o: olive/triangle3d.c olive/vc.c olive/olive.c + $(CC) $(CFLAGS) -DVC_PLATFORM=VC_ESPRUINO_PLATFORM -Iolive/ -c -o $@ $< + +triangle.o: olive/triangle.c olive/vc.c olive/olive.c + $(CC) $(CFLAGS) -DVC_PLATFORM=VC_ESPRUINO_PLATFORM -Iolive/ -c -o $@ $< + +$(TARGET).elf: $(TARGET).o $(TARGET).ld triangle.o + #$(CC) $(LDFLAGS) -o $@ $< + # --emit-relocs + arm-linux-gnueabihf-ld -T cload.ld -o cload.elf cload.o triangle.o + +$(TARGET).bin: $(TARGET).elf + # --only-section=.text + $(CROSS)objcopy -O binary $< $@ + +dump: cload.elf + $(CROSS)objdump --disassemble-all cload.elf + +qemu: + $(CC) cload.c -o cload-static -static + qemu-arm-static ./cload-static + +put: cload.bin + ../wt put cload.bin + +clean: + rm -f *.o *.elf *.bin + diff --git a/apps/cload/README.md b/apps/cload/README.md new file mode 100644 index 0000000000..817d9d416c --- /dev/null +++ b/apps/cload/README.md @@ -0,0 +1,16 @@ +# Code Load + +This allows running arm binaries as an application on +Bangle.js2. Obvious use is to run C code, and example doing that is +provided, but you should be able to run other languages, too, as long +as you can provide suitable binary (and fit within constraints), and +hack suitable basic library. + +Binary code can ask javascript to do "stuff" on its behalf, currently +it can display text and display bitmap. + +olive: + +https://tsoding.github.io/olive.c/ +https://github.com/tsoding/olive.c/tree/master + diff --git a/apps/cload/app-icon.js b/apps/cload/app-icon.js new file mode 100644 index 0000000000..4152787f02 --- /dev/null +++ b/apps/cload/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgIzwgPwAocf/4FDv//wAFC/ED4AWC8EAAoQJCoAFCCocAg4FFGgkPApUOAtw7LJopZFMopxFPoqJFSo0f/ytJAHQ=")) diff --git a/apps/cload/app.png b/apps/cload/app.png new file mode 100644 index 0000000000..44e667487e Binary files /dev/null and b/apps/cload/app.png differ diff --git a/apps/cload/cload.app.js b/apps/cload/cload.app.js new file mode 100644 index 0000000000..a899dd20f3 --- /dev/null +++ b/apps/cload/cload.app.js @@ -0,0 +1,118 @@ +/* + Code Load, or C load. + + I'm looking for loader for binaries for bangle.js2. Idea would be + I'd compile binary (likely gcc -fpic -nostdlibs -o ...) and than + have a javascript loader/linker that would load the binary into + memory and execute it. + + Ideally I'd like some way to do "syscalls" from loaded + binary. Perhaps start with implementing print() syscall. + + I'd like two word name for this, ideally steampunk-themed and + rhyming. I'm thinking about "Tin Bin". + + (Bin Spin, Bit kit, Code Load -- Tin Bin) + +Proof of concept, please. You can include design notes. And yes, code +is for Espruino. + +*/ + +// === loader.js === +// Espruino-side loader (run on Bangle.js2) +// Copy example.bin to Bangle.js2 Storage manually before use +// For example, upload using Espruino IDE and name it 'example.bin' + +var img = { + width : 176, height : 176, bpp : 3, + buffer : new Uint8Array(176*176*(3/8)).buffer +}; + +function msg(s) { + g.reset().clear().setFont("Vector", 33).drawString(s, 10, 30).flip(); +} + +var bin, io, ios, ram, rams, code, ccode; + +function step() { + io[0] = ios; + io[1] = rams; + io[2] = code; + print("Running at: ", io[2].toString(16)); + let a1 = E.getAddressOf(io.buffer, true); + let a2 = E.getAddressOf(ram.buffer, true); + let a3 = E.getAddressOf(img.buffer, true); + print(a1, a2, a3); + return ccode(0x8a61c, a1, a2, a3); +} + +var binData; + +function load() { + bin = new Uint8Array(12*1024); + binData = Uint8Array(require("Storage").readArrayBuffer("cload.bin")); + if (!binData) { + console.log("Binary not found!"); + return; + } + bin.set(binData); + code = E.getAddressOf(bin.buffer, true); + + // Create native function from binary + // '1' = use Thumb mode (bit 0 set in address) + // First 256 bytes are .got, +1 for thumb mode + ccode = E.nativeCall(code+257, "int(int,int,int,int)"); + + g.reset().clear(); + print("Fill"); + //j_test(img.buffer); + + print("Buffers"); + ios = 128; + io = new Uint32Array(ios); + rams = 2*1024; + ram = new Uint8Array(rams); + + msg("Reloc"); + io[3] = 0x51a87; + let relocated = step(); + if (relocated != 0xa11600d) { + print("Relocation failed:", relocated.toString(16)); + return; + } + + msg("Go"); + while (1) { + io[3] = 0x606060; + let going = step(); + if (going != 0x60176) { + print("Step failed", going.toString(16)); + return; + } + switch(io[1]) { + case 1: // print: start, len + print("Should print"); + { + let slice = ram.subarray(io[2], io[2]+io[3]); + let text = String.fromCharCode.apply(null, slice); + print(text); + msg(text); + } + break; + case 2: // draw + g.drawImage(img, 0, 0); + break; + default: + print("Unknown op: ", io[1]); + return; + } + g.flip(); + } +} + +msg("Cload\nready"); +function go() { + load(); +} +go(); diff --git a/apps/cload/cload.bin b/apps/cload/cload.bin new file mode 100644 index 0000000000..b4fcdf2c8d Binary files /dev/null and b/apps/cload/cload.bin differ diff --git a/apps/cload/cload.c b/apps/cload/cload.c new file mode 100644 index 0000000000..44dc6dd397 --- /dev/null +++ b/apps/cload/cload.c @@ -0,0 +1,263 @@ +// === example.c === +// Position-independent, nostdlib binary function + +// arm-none-eabi-gcc -mlittle-endian -mthumb -mcpu=cortex-m3 -mfix-cortex-m3-ldrd -mthumb-interwork -mfloat-abi=soft -nostdinc -nostdlib -c test.c -o test.o + + +typedef unsigned char uint8_t; +typedef unsigned long uint32_t; +typedef unsigned long size_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; + +int cload_main(int cmd, uint32_t *io, char *ram, char *fb); + +// Don't call it main, it goes into different section +// Make it assembly jump, so that if optimizer reorders stuff, this is still first +__attribute__((naked, used, section(".text.startup"))) int cload_entry(int cmd, uint32_t *io, char *ram, char *fb) { + __asm__ volatile ( + "b cload_main"); +} + +static char *dmesg; + +void putchar(char v) +{ + *dmesg++ = v; +} + +void puts(const char *s) { + while (*s) { + putchar(*s++); + } + putchar('\n'); +} + +void reverse(char *str, int len) { + int i = 0, j = len - 1; + while (i < j) { + char tmp = str[i]; + str[i++] = str[j]; + str[j--] = tmp; + } +} + +void itoa(int value, char *str) { + int i = 0; + int is_negative = 0; + + if (value == 0) { + str[i++] = '0'; + str[i] = '\0'; + return; + } + + if (value < 0) { + is_negative = 1; + value = -value; + } + + while (value != 0) { + int digit = value % 10; + str[i++] = digit + '0'; + value /= 10; + } + + if (is_negative) + str[i++] = '-'; + + str[i] = '\0'; + reverse(str, i); +} + +void *memset(void *dest, int val, size_t len) { + unsigned char *ptr = dest; + while (len-- > 0) { + *ptr++ = (unsigned char)val; + } + return dest; +} + +typedef struct { + uint32_t sp; // offset 0 -- aka r13 + uint32_t lr; // offset 4 -- aka r14 + uint32_t r4; // offset 8 + uint32_t r5; // ... + uint32_t r6; + uint32_t r7; + uint32_t r8; + uint32_t r9; + uint32_t r10; + uint32_t r11; // offset 40 +} context_t; + +__attribute__((naked)) void switch_stack(context_t *old_ctx, context_t *new_ctx) { + __asm__ volatile ( + // Save old context + "str sp, [r0, #0] \n" + "str lr, [r0, #4] \n" + "str r4, [r0, #8] \n" + "str r5, [r0, #12] \n" + "str r6, [r0, #16] \n" + "str r7, [r0, #20] \n" + "str r8, [r0, #24] \n" + "str r9, [r0, #28] \n" + "str r10, [r0, #32] \n" + "str r11, [r0, #36] \n" + + // Load new context + "ldr sp, [r1, #0] \n" + "ldr lr, [r1, #4] \n" + "ldr r4, [r1, #8] \n" + "ldr r5, [r1, #12] \n" + "ldr r6, [r1, #16] \n" + "ldr r7, [r1, #20] \n" + "ldr r8, [r1, #24] \n" + "ldr r9, [r1, #28] \n" + "ldr r10, [r1, #32] \n" + "ldr r11, [r1, #36] \n" + + "bx lr \n" + ); +} + +volatile char welcome[] = "Hello\nwatch\n:-)"; +uint32_t *g_io = 1; +char *g_ram = 2, *g_fb = 3; + +context_t main_ctx, alt_ctx; +uint8_t alt_stack[4096] __attribute__((aligned(8))); + +extern void olive_step(float dt, char *fb); + +void msg(char *m) { + dmesg = g_ram; + puts(m); + g_io[1] = 1; + g_io[2] = 0; + g_io[3] = dmesg - g_ram; + switch_stack(&alt_ctx, &main_ctx); + +} + +void flip(void) { + g_io[1] = 2; + switch_stack(&alt_ctx, &main_ctx); +} + +// Save 32 FPU registers (S0–S31) and FPSCR (Floating Point Status and Control Register) +void call_with_fpu_preserved(void (*func)(void)) { + __asm volatile ( + // Allocate space on the stack + "sub sp, sp, #136\n" // 32*4 + 4 = 128 + 8 = 136 bytes + + // Save FPU registers S0–S31 + "vstmia sp, {s0-s31}\n" + + // Save FPSCR to last 4 bytes + "vmrs r1, fpscr\n" + "str r1, [sp, #128]\n" + + // Call the C function + "mov r0, %0\n" + "blx r0\n" + + // Restore FPSCR + "ldr r1, [sp, #128]\n" + "vmsr fpscr, r1\n" + + // Restore FPU registers + "vldmia sp, {s0-s31}\n" + + // Restore stack + "add sp, sp, #136\n" + : + : "r" (func) + : "r0", "r1", "lr", "memory", "cc", + "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", + "s8", "s9", "s10", "s11", "s12", "s13", "s14", "s15", + "s16", "s17", "s18", "s19", "s20", "s21", "s22", "s23", + "s24", "s25", "s26", "s27", "s28", "s29", "s30", "s31" + ); +} + +void olive_ind(void) +{ + olive_step(.3, g_fb); +} + +void new_stack_fn(void) { + for (int i = 0; i<10; i++) { + dmesg = g_ram; + puts(welcome); + putchar(i+'0'); + g_io[1] = 1; + g_io[2] = 0; + g_io[3] = dmesg - g_ram; + switch_stack(&alt_ctx, &main_ctx); + } + while (1) { + msg("Compute"); + for (int i = 0; i < 20; i++) { + call_with_fpu_preserved(olive_ind); + //olive_ind(); + flip(); + } + msg("Done"); + } +} + +void prepare_alt_context(void) { + uint32_t *stack_top = (uint32_t *)(alt_stack + sizeof(alt_stack)); + + for (int i=0; i= 0 ? u : u - (int)u - 1; + int i0 = ((int)ui) & (N - 1); + float f = ui - (int)ui; + float y0 = sin_tab[i0]; + float y1 = sin_tab[i0 + 1]; + return y0 + (y1 - y0) * f; +} + +// Fast cosine via phase shift +float cosf(float x) { + return sinf(x + 1.57079632679489661923f); // + π/2 +} + +typedef struct { + int quotient; + int remainder; +} divmod_result; + +// __aeabi_idivmod: FIXME this likely needs to return values in registers +divmod_result aeabi_idivmod(int dividend, int divisor) { + divmod_result result; + unsigned int quotient = 0; + unsigned int remainder = 0; + + // If the divisor is 0, division by zero error should be handled + if (divisor == 0) { + // Handle division by zero error (return quotient as 0, remainder as dividend) + result.quotient = 0; + result.remainder = remainder; + return result; + } + + // Ensure we work with positive values for simplicity + int sign = 1; + if (dividend < 0) { + dividend = -dividend; + sign = -sign; + } + if (divisor < 0) { + divisor = -divisor; + sign = -sign; + } + + // Loop over bit positions, simulating long division + for (int i = 31; i >= 0; i--) { + // Shift the remainder left and bring in the next bit (simulate multiplication by 2) + remainder <<= 1; + remainder |= !! ((1 << i) & dividend); + int bit = (remainder >= divisor); // Compare remainder with divisor + remainder -= bit * divisor; // Subtract divisor if the bit was set + quotient |= (bit << i); // Set the bit in the quotient + } + + // Apply the sign to the quotient + result.quotient = sign * quotient; + result.remainder = sign * remainder; + + return result; +} + +int __aeabi_idiv(int dividend, int divisor) { + divmod_result result = aeabi_idivmod(dividend, divisor); + return result.quotient; +} + +typedef struct { + long long quotient; + long long remainder; +} ldivmod_result; + +// __aeabi_idivmod: FIXME this likely needs to return values in registers +ldivmod_result __aeabi_ldivmod(long long dividend, long long divisor) { + ldivmod_result result; + unsigned long long quotient = 0; + unsigned long long remainder = 0; + + // If the divisor is 0, division by zero error should be handled + if (divisor == 0) { + // Handle division by zero error (return quotient as 0, remainder as dividend) + result.quotient = 0; + result.remainder = remainder; + return result; + } + + // Ensure we work with positive values for simplicity + int sign = 1; + if (dividend < 0) { + dividend = -dividend; + sign = -sign; + } + if (divisor < 0) { + divisor = -divisor; + sign = -sign; + } + + // Loop over bit positions, simulating long division + for (int i = 63; i >= 0; i--) { + // Shift the remainder left and bring in the next bit (simulate multiplication by 2) + remainder <<= 1; + remainder |= !! ((1 << i) & dividend); + int bit = (remainder >= divisor); // Compare remainder with divisor + remainder -= bit * divisor; // Subtract divisor if the bit was set + quotient |= (bit << i); // Set the bit in the quotient + } + + // Apply the sign to the quotient + result.quotient = sign * quotient; + result.remainder = sign * remainder; + + return result; +} diff --git a/apps/cload/metadata.json b/apps/cload/metadata.json new file mode 100644 index 0000000000..045c8c8998 --- /dev/null +++ b/apps/cload/metadata.json @@ -0,0 +1,14 @@ +{ "id": "cload", + "name": "Code Load", + "version": "0.01", + "description": "Run C code on Espruino", + "icon": "app.png", + "readme": "README.md", + "supports" : ["BANGLEJS2"], + "tags": "tool", + "storage": [ + {"name":"cload.app.js","url":"cload.app.js"}, + {"name":"cload.bin","url":"cload.bin"}, + {"name":"cload.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/cload/olive/Makefile b/apps/cload/olive/Makefile new file mode 100644 index 0000000000..60c07e66e4 --- /dev/null +++ b/apps/cload/olive/Makefile @@ -0,0 +1,10 @@ +all: t3d_sdl t_sdl + +t3d_sdl: + gcc -DVC_PLATFORM=VC_SDL_PLATFORM triangle3d.c -lSDL2 -lm -I . -o t3d_sdl + +t_sdl: + gcc -DVC_PLATFORM=VC_SDL_PLATFORM triangle.c -lSDL2 -lm -I . -o t_sdl + +t3d_test: + gcc -DVC_PLATFORM=VC_ESPRUINO_PLATFORM triangle3d.c -lm -I . -o t3d_test diff --git a/apps/cload/olive/olive.c b/apps/cload/olive/olive.c new file mode 100644 index 0000000000..fe06754e10 --- /dev/null +++ b/apps/cload/olive/olive.c @@ -0,0 +1,1022 @@ +// Copyright 2022 Alexey Kutepov +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef OLIVE_C_ +#define OLIVE_C_ + +#include +#include +#include + +#ifndef OLIVECDEF +#define OLIVECDEF static inline +#endif + +#ifndef OLIVEC_AA_RES +#define OLIVEC_AA_RES 2 +#endif + +#define OLIVEC_SWAP(T, a, b) do { T t = a; a = b; b = t; } while (0) +#define OLIVEC_SIGN(T, x) ((T)((x) > 0) - (T)((x) < 0)) +#define OLIVEC_ABS(T, x) (OLIVEC_SIGN(T, x)*(x)) + +typedef struct { + size_t width, height; + const char *glyphs; +} Olivec_Font; + +#define OLIVEC_DEFAULT_FONT_HEIGHT 6 +#define OLIVEC_DEFAULT_FONT_WIDTH 6 +// TODO: allocate proper descender and acender areas for the default font +static char olivec_default_glyphs[128][OLIVEC_DEFAULT_FONT_HEIGHT][OLIVEC_DEFAULT_FONT_WIDTH] = { + ['a'] = { + {0, 0, 0, 0, 0}, + {0, 1, 1, 0, 0}, + {0, 0, 0, 1, 0}, + {0, 1, 1, 1, 0}, + {1, 0, 0, 1, 0}, + {0, 1, 1, 1, 0}, + }, + ['b'] = { + {1, 0, 0, 0, 0}, + {1, 1, 1, 0, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 1, 1, 0, 0}, + }, + ['c'] = { + {0, 0, 0, 0, 0}, + {0, 1, 1, 0, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 0, 0}, + {1, 0, 0, 1, 0}, + {0, 1, 1, 0, 0}, + }, + ['d'] = { + {0, 0, 0, 1, 0}, + {0, 1, 1, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {0, 1, 1, 1, 0}, + }, + ['e'] = { + {0, 0, 0, 0, 0}, + {0, 1, 1, 0, 0}, + {1, 0, 0, 1, 0}, + {1, 1, 1, 1, 0}, + {1, 0, 0, 0, 0}, + {0, 1, 1, 1, 0}, + }, + ['f'] = { + {0, 0, 1, 1, 0}, + {0, 1, 0, 0, 0}, + {1, 1, 1, 1, 0}, + {0, 1, 0, 0, 0}, + {0, 1, 0, 0, 0}, + {0, 1, 0, 0, 0}, + }, + ['g'] = { + {0, 1, 1, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {0, 1, 1, 1, 0}, + {0, 0, 0, 1, 0}, + {0, 1, 1, 0, 0}, + }, + ['h'] = { + {1, 0, 0, 0, 0}, + {1, 1, 1, 0, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + }, + ['i'] = { + {0, 0, 1, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + }, + ['j'] = { + {0, 0, 1, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {1, 0, 1, 0, 0}, + {0, 1, 1, 0, 0}, + }, + ['k'] = { + {1, 0, 0, 0, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 1, 0, 0}, + {1, 1, 0, 0, 0}, + {1, 0, 1, 0, 0}, + {1, 0, 0, 1, 0}, + }, + ['l'] = { + {0, 1, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 1, 1, 1, 0}, + }, + ['m'] = { + {0, 0, 0, 0, 0}, + {0, 1, 0, 1, 1}, + {1, 0, 1, 0, 1}, + {1, 0, 1, 0, 1}, + {1, 0, 1, 0, 1}, + {1, 0, 1, 0, 1}, + }, + ['n'] = { + {0, 0, 0, 0, 0}, + {0, 1, 1, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + }, + ['o'] = { + {0, 0, 0, 0, 0}, + {0, 1, 1, 0, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {0, 1, 1, 0, 0}, + }, + ['p'] = { + {1, 1, 1, 0, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 1, 1, 0, 0}, + {1, 0, 0, 0, 0}, + {1, 0, 0, 0, 0}, + }, + ['q'] = { + {0, 1, 1, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {0, 1, 1, 1, 0}, + {0, 0, 0, 1, 0}, + {0, 0, 0, 1, 0}, + }, + ['r'] = { + {0, 0, 0, 0, 0}, + {1, 0, 1, 1, 0}, + {1, 1, 0, 0, 1}, + {1, 0, 0, 0, 0}, + {1, 0, 0, 0, 0}, + {1, 0, 0, 0, 0}, + }, + ['s'] = { + {0, 0, 0, 0, 0}, + {0, 1, 1, 1, 0}, + {1, 0, 0, 0, 0}, + {1, 1, 1, 1, 0}, + {0, 0, 0, 1, 0}, + {1, 1, 1, 0, 0}, + }, + ['t'] = { + {0, 1, 0, 0, 0}, + {0, 1, 0, 0, 0}, + {1, 1, 1, 1, 0}, + {0, 1, 0, 0, 0}, + {0, 1, 0, 1, 0}, + {0, 1, 1, 0, 0}, + }, + ['u'] = { + {0, 0, 0, 0, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {0, 1, 1, 1, 0}, + }, + ['v'] = { + {0, 0, 0, 0, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {0, 1, 1, 0, 0}, + }, + ['w'] = { + {0, 0, 0, 0, 0}, + {1, 0, 0, 0, 1}, + {1, 0, 1, 0, 1}, + {1, 0, 1, 0, 1}, + {1, 0, 1, 0, 1}, + {0, 1, 1, 1, 1}, + }, + ['x'] = { + {0, 0, 0, 0, 0}, + {1, 0, 1, 0, 0}, + {1, 0, 1, 0, 0}, + {0, 1, 0, 0, 0}, + {1, 0, 1, 0, 0}, + {1, 0, 1, 0, 0}, + }, + ['y'] = { + {0, 0, 0, 0, 0}, + {1, 0, 1, 0, 0}, + {1, 0, 1, 0, 0}, + {1, 0, 1, 0, 0}, + {0, 1, 0, 0, 0}, + {0, 1, 0, 0, 0}, + }, + ['z'] = { + {0, 0, 0, 0, 0}, + {1, 1, 1, 1, 0}, + {0, 0, 0, 1, 0}, + {0, 1, 1, 0, 0}, + {1, 0, 0, 0, 0}, + {1, 1, 1, 1, 0}, + }, + + ['A'] = {0}, + ['B'] = {0}, + ['C'] = {0}, + ['D'] = {0}, + ['E'] = {0}, + ['F'] = {0}, + ['G'] = {0}, + ['H'] = {0}, + ['I'] = {0}, + ['J'] = {0}, + ['K'] = {0}, + ['L'] = {0}, + ['M'] = {0}, + ['N'] = {0}, + ['O'] = {0}, + ['P'] = {0}, + ['Q'] = {0}, + ['R'] = {0}, + ['S'] = {0}, + ['T'] = {0}, + ['U'] = {0}, + ['V'] = {0}, + ['W'] = {0}, + ['X'] = {0}, + ['Y'] = {0}, + ['Z'] = {0}, + + ['0'] = { + {0, 1, 1, 0, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {0, 1, 1, 0, 0}, + }, + ['1'] = { + {0, 0, 1, 0, 0}, + {0, 1, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 1, 0, 0}, + {0, 1, 1, 1, 0}, + }, + ['2'] = { + {0, 1, 1, 0, 0}, + {1, 0, 0, 1, 0}, + {0, 0, 0, 1, 0}, + {0, 1, 1, 0, 0}, + {1, 0, 0, 0, 0}, + {1, 1, 1, 1, 0}, + }, + ['3'] = { + {0, 1, 1, 0, 0}, + {1, 0, 0, 1, 0}, + {0, 0, 1, 0, 0}, + {0, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {0, 1, 1, 0, 0}, + }, + ['4'] = { + {0, 0, 1, 1, 0}, + {0, 1, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {1, 1, 1, 1, 1}, + {0, 0, 0, 1, 0}, + {0, 0, 0, 1, 0}, + }, + ['5'] = { + {1, 1, 1, 0, 0}, + {1, 0, 0, 0, 0}, + {1, 1, 1, 0, 0}, + {0, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {0, 1, 1, 0, 0}, + }, + ['6'] = { + {0, 1, 1, 0, 0}, + {1, 0, 0, 0, 0}, + {1, 1, 1, 0, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {0, 1, 1, 0, 0}, + }, + ['7'] = { + {1, 1, 1, 1, 0}, + {0, 0, 0, 1, 0}, + {0, 0, 1, 0, 0}, + {0, 1, 0, 0, 0}, + {0, 1, 0, 0, 0}, + {0, 1, 0, 0, 0}, + }, + ['8'] = { + {0, 1, 1, 0, 0}, + {1, 0, 0, 1, 0}, + {0, 1, 1, 0, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {0, 1, 1, 0, 0}, + + }, + ['9'] = { + {0, 1, 1, 0, 0}, + {1, 0, 0, 1, 0}, + {1, 0, 0, 1, 0}, + {0, 1, 1, 1, 0}, + {0, 0, 0, 1, 0}, + {0, 1, 1, 0, 0}, + }, + + [','] = { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 1, 0}, + {0, 0, 1, 0, 0}, + }, + + ['.'] = { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 1, 0, 0}, + }, + ['-'] = { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {1, 1, 1, 1, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + }, +}; + +static Olivec_Font olivec_default_font = { + .glyphs = &olivec_default_glyphs[0][0][0], + .width = OLIVEC_DEFAULT_FONT_WIDTH, + .height = OLIVEC_DEFAULT_FONT_HEIGHT, +}; + +// WARNING! Always initialize your Canvas with a color that has Non-Zero Alpha Channel! +// A lot of functions use `olivec_blend_color()` function to blend with the Background +// which preserves the original Alpha of the Background. So you may easily end up with +// a result that is perceptually transparent if the Alpha is Zero. +typedef struct { + uint32_t *pixels; + size_t width; + size_t height; + size_t stride; +} Olivec_Canvas; + +#define OLIVEC_CANVAS_NULL ((Olivec_Canvas) {0}) +#define OLIVEC_PIXEL(oc, x, y) (oc).pixels[(y)*(oc).stride + (x)] + +OLIVECDEF Olivec_Canvas olivec_canvas(uint32_t *pixels, size_t width, size_t height, size_t stride); +OLIVECDEF Olivec_Canvas olivec_subcanvas(Olivec_Canvas oc, int x, int y, int w, int h); +OLIVECDEF bool olivec_in_bounds(Olivec_Canvas oc, int x, int y); +OLIVECDEF void olivec_blend_color(uint32_t *c1, uint32_t c2); +OLIVECDEF void olivec_fill(Olivec_Canvas oc, uint32_t color); +OLIVECDEF void olivec_rect(Olivec_Canvas oc, int x, int y, int w, int h, uint32_t color); +OLIVECDEF void olivec_frame(Olivec_Canvas oc, int x, int y, int w, int h, size_t thiccness, uint32_t color); +OLIVECDEF void olivec_circle(Olivec_Canvas oc, int cx, int cy, int r, uint32_t color); +OLIVECDEF void olivec_ellipse(Olivec_Canvas oc, int cx, int cy, int rx, int ry, uint32_t color); +// TODO: lines with different thiccness +OLIVECDEF void olivec_line(Olivec_Canvas oc, int x1, int y1, int x2, int y2, uint32_t color); +OLIVECDEF bool olivec_normalize_triangle(size_t width, size_t height, int x1, int y1, int x2, int y2, int x3, int y3, int *lx, int *hx, int *ly, int *hy); +OLIVECDEF bool olivec_barycentric(int x1, int y1, int x2, int y2, int x3, int y3, int xp, int yp, int *u1, int *u2, int *det); +OLIVECDEF void olivec_triangle(Olivec_Canvas oc, int x1, int y1, int x2, int y2, int x3, int y3, uint32_t color); +OLIVECDEF void olivec_triangle3c(Olivec_Canvas oc, int x1, int y1, int x2, int y2, int x3, int y3, uint32_t c1, uint32_t c2, uint32_t c3); +OLIVECDEF void olivec_triangle3z(Olivec_Canvas oc, int x1, int y1, int x2, int y2, int x3, int y3, float z1, float z2, float z3); +OLIVECDEF void olivec_triangle3uv(Olivec_Canvas oc, int x1, int y1, int x2, int y2, int x3, int y3, float tx1, float ty1, float tx2, float ty2, float tx3, float ty3, float z1, float z2, float z3, Olivec_Canvas texture); +OLIVECDEF void olivec_triangle3uv_bilinear(Olivec_Canvas oc, int x1, int y1, int x2, int y2, int x3, int y3, float tx1, float ty1, float tx2, float ty2, float tx3, float ty3, float z1, float z2, float z3, Olivec_Canvas texture); +OLIVECDEF void olivec_text(Olivec_Canvas oc, const char *text, int x, int y, Olivec_Font font, size_t size, uint32_t color); +OLIVECDEF void olivec_sprite_blend(Olivec_Canvas oc, int x, int y, int w, int h, Olivec_Canvas sprite); +OLIVECDEF void olivec_sprite_copy(Olivec_Canvas oc, int x, int y, int w, int h, Olivec_Canvas sprite); +OLIVECDEF void olivec_sprite_copy_bilinear(Olivec_Canvas oc, int x, int y, int w, int h, Olivec_Canvas sprite); +OLIVECDEF uint32_t olivec_pixel_bilinear(Olivec_Canvas sprite, int nx, int ny, int w, int h); + +typedef struct { + // Safe ranges to iterate over. + int x1, x2; + int y1, y2; + + // Original uncut ranges some parts of which may be outside of the canvas boundaries. + int ox1, ox2; + int oy1, oy2; +} Olivec_Normalized_Rect; + +// The point of this function is to produce two ranges x1..x2 and y1..y2 that are guaranteed to be safe to iterate over the canvas of size pixels_width by pixels_height without any boundary checks. +// +// Olivec_Normalized_Rect nr = {0}; +// if (olivec_normalize_rect(x, y, w, h, WIDTH, HEIGHT, &nr)) { +// for (int x = nr.x1; x <= nr.x2; ++x) { +// for (int y = nr.y1; y <= nr.y2; ++y) { +// OLIVEC_PIXEL(oc, x, y) = 0x69696969; +// } +// } +// } else { +// // Rectangle is invisible cause it's completely out-of-bounds +// } +OLIVECDEF bool olivec_normalize_rect(int x, int y, int w, int h, + size_t canvas_width, size_t canvas_height, + Olivec_Normalized_Rect *nr); + +#endif // OLIVE_C_ + +#ifdef OLIVEC_IMPLEMENTATION + +OLIVECDEF Olivec_Canvas olivec_canvas(uint32_t *pixels, size_t width, size_t height, size_t stride) +{ + Olivec_Canvas oc = { + .pixels = pixels, + .width = width, + .height = height, + .stride = stride, + }; + return oc; +} + +OLIVECDEF bool olivec_normalize_rect(int x, int y, int w, int h, + size_t canvas_width, size_t canvas_height, + Olivec_Normalized_Rect *nr) +{ + // No need to render empty rectangle + if (w == 0) return false; + if (h == 0) return false; + + nr->ox1 = x; + nr->oy1 = y; + + // Convert the rectangle to 2-points representation + nr->ox2 = nr->ox1 + OLIVEC_SIGN(int, w)*(OLIVEC_ABS(int, w) - 1); + if (nr->ox1 > nr->ox2) OLIVEC_SWAP(int, nr->ox1, nr->ox2); + nr->oy2 = nr->oy1 + OLIVEC_SIGN(int, h)*(OLIVEC_ABS(int, h) - 1); + if (nr->oy1 > nr->oy2) OLIVEC_SWAP(int, nr->oy1, nr->oy2); + + // Cull out invisible rectangle + if (nr->ox1 >= (int) canvas_width) return false; + if (nr->ox2 < 0) return false; + if (nr->oy1 >= (int) canvas_height) return false; + if (nr->oy2 < 0) return false; + + nr->x1 = nr->ox1; + nr->y1 = nr->oy1; + nr->x2 = nr->ox2; + nr->y2 = nr->oy2; + + // Clamp the rectangle to the boundaries + if (nr->x1 < 0) nr->x1 = 0; + if (nr->x2 >= (int) canvas_width) nr->x2 = (int) canvas_width - 1; + if (nr->y1 < 0) nr->y1 = 0; + if (nr->y2 >= (int) canvas_height) nr->y2 = (int) canvas_height - 1; + + return true; +} + +OLIVECDEF Olivec_Canvas olivec_subcanvas(Olivec_Canvas oc, int x, int y, int w, int h) +{ + Olivec_Normalized_Rect nr = {0}; + if (!olivec_normalize_rect(x, y, w, h, oc.width, oc.height, &nr)) return OLIVEC_CANVAS_NULL; + oc.pixels = &OLIVEC_PIXEL(oc, nr.x1, nr.y1); + oc.width = nr.x2 - nr.x1 + 1; + oc.height = nr.y2 - nr.y1 + 1; + return oc; +} + +// TODO: custom pixel formats +// Maybe we can store pixel format info in Olivec_Canvas +#define OLIVEC_RED(color) (((color)&0x000000FF)>>(8*0)) +#define OLIVEC_GREEN(color) (((color)&0x0000FF00)>>(8*1)) +#define OLIVEC_BLUE(color) (((color)&0x00FF0000)>>(8*2)) +#define OLIVEC_ALPHA(color) (((color)&0xFF000000)>>(8*3)) +#define OLIVEC_RGBA(r, g, b, a) ((((r)&0xFF)<<(8*0)) | (((g)&0xFF)<<(8*1)) | (((b)&0xFF)<<(8*2)) | (((a)&0xFF)<<(8*3))) + +OLIVECDEF void olivec_blend_color(uint32_t *c1, uint32_t c2) +{ + uint32_t r1 = OLIVEC_RED(*c1); + uint32_t g1 = OLIVEC_GREEN(*c1); + uint32_t b1 = OLIVEC_BLUE(*c1); + uint32_t a1 = OLIVEC_ALPHA(*c1); + + uint32_t r2 = OLIVEC_RED(c2); + uint32_t g2 = OLIVEC_GREEN(c2); + uint32_t b2 = OLIVEC_BLUE(c2); + uint32_t a2 = OLIVEC_ALPHA(c2); + + r1 = (r1*(255 - a2) + r2*a2)/255; if (r1 > 255) r1 = 255; + g1 = (g1*(255 - a2) + g2*a2)/255; if (g1 > 255) g1 = 255; + b1 = (b1*(255 - a2) + b2*a2)/255; if (b1 > 255) b1 = 255; + + *c1 = OLIVEC_RGBA(r1, g1, b1, a1); +} + +OLIVECDEF void olivec_fill(Olivec_Canvas oc, uint32_t color) +{ + for (size_t y = 0; y < oc.height; ++y) { + for (size_t x = 0; x < oc.width; ++x) { + OLIVEC_PIXEL(oc, x, y) = color; + } + } +} + +OLIVECDEF void olivec_rect(Olivec_Canvas oc, int x, int y, int w, int h, uint32_t color) +{ + Olivec_Normalized_Rect nr = {0}; + if (!olivec_normalize_rect(x, y, w, h, oc.width, oc.height, &nr)) return; + for (int x = nr.x1; x <= nr.x2; ++x) { + for (int y = nr.y1; y <= nr.y2; ++y) { + olivec_blend_color(&OLIVEC_PIXEL(oc, x, y), color); + } + } +} + +OLIVECDEF void olivec_frame(Olivec_Canvas oc, int x, int y, int w, int h, size_t t, uint32_t color) +{ + if (t == 0) return; // Nothing to render + + // Convert the rectangle to 2-points representation + int x1 = x; + int y1 = y; + int x2 = x1 + OLIVEC_SIGN(int, w)*(OLIVEC_ABS(int, w) - 1); + if (x1 > x2) OLIVEC_SWAP(int, x1, x2); + int y2 = y1 + OLIVEC_SIGN(int, h)*(OLIVEC_ABS(int, h) - 1); + if (y1 > y2) OLIVEC_SWAP(int, y1, y2); + + olivec_rect(oc, x1 - t/2, y1 - t/2, (x2 - x1 + 1) + t/2*2, t, color); // Top + olivec_rect(oc, x1 - t/2, y1 - t/2, t, (y2 - y1 + 1) + t/2*2, color); // Left + olivec_rect(oc, x1 - t/2, y2 + t/2, (x2 - x1 + 1) + t/2*2, -t, color); // Bottom + olivec_rect(oc, x2 + t/2, y1 - t/2, -t, (y2 - y1 + 1) + t/2*2, color); // Right +} + +OLIVECDEF void olivec_ellipse(Olivec_Canvas oc, int cx, int cy, int rx, int ry, uint32_t color) +{ + Olivec_Normalized_Rect nr = {0}; + int rx1 = rx + OLIVEC_SIGN(int, rx); + int ry1 = ry + OLIVEC_SIGN(int, ry); + if (!olivec_normalize_rect(cx - rx1, cy - ry1, 2*rx1, 2*ry1, oc.width, oc.height, &nr)) return; + + for (int y = nr.y1; y <= nr.y2; ++y) { + for (int x = nr.x1; x <= nr.x2; ++x) { + float nx = (x + 0.5 - nr.x1)/(2.0f*rx1); + float ny = (y + 0.5 - nr.y1)/(2.0f*ry1); + float dx = nx - 0.5; + float dy = ny - 0.5; + if (dx*dx + dy*dy <= 0.5*0.5) { + OLIVEC_PIXEL(oc, x, y) = color; + } + } + } +} + +OLIVECDEF void olivec_circle(Olivec_Canvas oc, int cx, int cy, int r, uint32_t color) +{ + Olivec_Normalized_Rect nr = {0}; + int r1 = r + OLIVEC_SIGN(int, r); + if (!olivec_normalize_rect(cx - r1, cy - r1, 2*r1, 2*r1, oc.width, oc.height, &nr)) return; + + for (int y = nr.y1; y <= nr.y2; ++y) { + for (int x = nr.x1; x <= nr.x2; ++x) { + int count = 0; + for (int sox = 0; sox < OLIVEC_AA_RES; ++sox) { + for (int soy = 0; soy < OLIVEC_AA_RES; ++soy) { + // TODO: switch to 64 bits to make the overflow less likely + // Also research the probability of overflow + int res1 = (OLIVEC_AA_RES + 1); + int dx = (x*res1*2 + 2 + sox*2 - res1*cx*2 - res1); + int dy = (y*res1*2 + 2 + soy*2 - res1*cy*2 - res1); + if (dx*dx + dy*dy <= res1*res1*r*r*2*2) count += 1; + } + } + uint32_t alpha = ((color&0xFF000000)>>(3*8))*count/OLIVEC_AA_RES/OLIVEC_AA_RES; + uint32_t updated_color = (color&0x00FFFFFF)|(alpha<<(3*8)); + olivec_blend_color(&OLIVEC_PIXEL(oc, x, y), updated_color); + } + } +} + +OLIVECDEF bool olivec_in_bounds(Olivec_Canvas oc, int x, int y) +{ + return 0 <= x && x < (int) oc.width && 0 <= y && y < (int) oc.height; +} + +// TODO: AA for line +OLIVECDEF void olivec_line(Olivec_Canvas oc, int x1, int y1, int x2, int y2, uint32_t color) +{ + int dx = x2 - x1; + int dy = y2 - y1; + + // If both of the differences are 0 there will be a division by 0 below. + if (dx == 0 && dy == 0) { + if (olivec_in_bounds(oc, x1, y1)) { + olivec_blend_color(&OLIVEC_PIXEL(oc, x1, y1), color); + } + return; + } + + if (OLIVEC_ABS(int, dx) > OLIVEC_ABS(int, dy)) { + if (x1 > x2) { + OLIVEC_SWAP(int, x1, x2); + OLIVEC_SWAP(int, y1, y2); + } + + for (int x = x1; x <= x2; ++x) { + int y = dy*(x - x1)/dx + y1; + // TODO: move boundary checks out side of the loops in olivec_draw_line + if (olivec_in_bounds(oc, x, y)) { + olivec_blend_color(&OLIVEC_PIXEL(oc, x, y), color); + } + } + } else { + if (y1 > y2) { + OLIVEC_SWAP(int, x1, x2); + OLIVEC_SWAP(int, y1, y2); + } + + for (int y = y1; y <= y2; ++y) { + int x = dx*(y - y1)/dy + x1; + // TODO: move boundary checks out side of the loops in olivec_draw_line + if (olivec_in_bounds(oc, x, y)) { + olivec_blend_color(&OLIVEC_PIXEL(oc, x, y), color); + } + } + } +} + +OLIVECDEF uint32_t mix_colors2(uint32_t c1, uint32_t c2, int u1, int det) +{ + // TODO: estimate how much overflows are an issue in integer only environment + int64_t r1 = OLIVEC_RED(c1); + int64_t g1 = OLIVEC_GREEN(c1); + int64_t b1 = OLIVEC_BLUE(c1); + int64_t a1 = OLIVEC_ALPHA(c1); + + int64_t r2 = OLIVEC_RED(c2); + int64_t g2 = OLIVEC_GREEN(c2); + int64_t b2 = OLIVEC_BLUE(c2); + int64_t a2 = OLIVEC_ALPHA(c2); + + if (det != 0) { + int u2 = det - u1; + int64_t r4 = (r1*u2 + r2*u1)/det; + int64_t g4 = (g1*u2 + g2*u1)/det; + int64_t b4 = (b1*u2 + b2*u1)/det; + int64_t a4 = (a1*u2 + a2*u1)/det; + + return OLIVEC_RGBA(r4, g4, b4, a4); + } + + return 0; +} + +OLIVECDEF uint32_t mix_colors3(uint32_t c1, uint32_t c2, uint32_t c3, int u1, int u2, int det) +{ + // TODO: estimate how much overflows are an issue in integer only environment + int64_t r1 = OLIVEC_RED(c1); + int64_t g1 = OLIVEC_GREEN(c1); + int64_t b1 = OLIVEC_BLUE(c1); + int64_t a1 = OLIVEC_ALPHA(c1); + + int64_t r2 = OLIVEC_RED(c2); + int64_t g2 = OLIVEC_GREEN(c2); + int64_t b2 = OLIVEC_BLUE(c2); + int64_t a2 = OLIVEC_ALPHA(c2); + + int64_t r3 = OLIVEC_RED(c3); + int64_t g3 = OLIVEC_GREEN(c3); + int64_t b3 = OLIVEC_BLUE(c3); + int64_t a3 = OLIVEC_ALPHA(c3); + + if (det != 0) { + int u3 = det - u1 - u2; + int64_t r4 = (r1*u1 + r2*u2 + r3*u3)/det; + int64_t g4 = (g1*u1 + g2*u2 + g3*u3)/det; + int64_t b4 = (b1*u1 + b2*u2 + b3*u3)/det; + int64_t a4 = (a1*u1 + a2*u2 + a3*u3)/det; + + return OLIVEC_RGBA(r4, g4, b4, a4); + } + + return 0; +} + +// NOTE: we imply u3 = det - u1 - u2 +OLIVECDEF bool olivec_barycentric(int x1, int y1, int x2, int y2, int x3, int y3, int xp, int yp, int *u1, int *u2, int *det) +{ + *det = ((x1 - x3)*(y2 - y3) - (x2 - x3)*(y1 - y3)); + *u1 = ((y2 - y3)*(xp - x3) + (x3 - x2)*(yp - y3)); + *u2 = ((y3 - y1)*(xp - x3) + (x1 - x3)*(yp - y3)); + int u3 = *det - *u1 - *u2; + return ( + (OLIVEC_SIGN(int, *u1) == OLIVEC_SIGN(int, *det) || *u1 == 0) && + (OLIVEC_SIGN(int, *u2) == OLIVEC_SIGN(int, *det) || *u2 == 0) && + (OLIVEC_SIGN(int, u3) == OLIVEC_SIGN(int, *det) || u3 == 0) + ); +} + +OLIVECDEF bool olivec_normalize_triangle(size_t width, size_t height, int x1, int y1, int x2, int y2, int x3, int y3, int *lx, int *hx, int *ly, int *hy) +{ + *lx = x1; + *hx = x1; + if (*lx > x2) *lx = x2; + if (*lx > x3) *lx = x3; + if (*hx < x2) *hx = x2; + if (*hx < x3) *hx = x3; + if (*lx < 0) *lx = 0; + if ((size_t) *lx >= width) return false;; + if (*hx < 0) return false;; + if ((size_t) *hx >= width) *hx = width-1; + + *ly = y1; + *hy = y1; + if (*ly > y2) *ly = y2; + if (*ly > y3) *ly = y3; + if (*hy < y2) *hy = y2; + if (*hy < y3) *hy = y3; + if (*ly < 0) *ly = 0; + if ((size_t) *ly >= height) return false;; + if (*hy < 0) return false;; + if ((size_t) *hy >= height) *hy = height-1; + + return true; +} + +OLIVECDEF void olivec_triangle3c(Olivec_Canvas oc, int x1, int y1, int x2, int y2, int x3, int y3, + uint32_t c1, uint32_t c2, uint32_t c3) +{ + int lx, hx, ly, hy; + if (olivec_normalize_triangle(oc.width, oc.height, x1, y1, x2, y2, x3, y3, &lx, &hx, &ly, &hy)) { + for (int y = ly; y <= hy; ++y) { + for (int x = lx; x <= hx; ++x) { + int u1, u2, det; + if (olivec_barycentric(x1, y1, x2, y2, x3, y3, x, y, &u1, &u2, &det)) { + olivec_blend_color(&OLIVEC_PIXEL(oc, x, y), mix_colors3(c1, c2, c3, u1, u2, det)); + } + } + } + } +} + +OLIVECDEF void olivec_triangle3z(Olivec_Canvas oc, int x1, int y1, int x2, int y2, int x3, int y3, float z1, float z2, float z3) +{ + int lx, hx, ly, hy; + if (olivec_normalize_triangle(oc.width, oc.height, x1, y1, x2, y2, x3, y3, &lx, &hx, &ly, &hy)) { + for (int y = ly; y <= hy; ++y) { + for (int x = lx; x <= hx; ++x) { + int u1, u2, det; + if (olivec_barycentric(x1, y1, x2, y2, x3, y3, x, y, &u1, &u2, &det)) { + float z = z1*u1/det + z2*u2/det + z3*(det - u1 - u2)/det; + OLIVEC_PIXEL(oc, x, y) = *(uint32_t*)&z; + } + } + } + } +} + +OLIVECDEF void olivec_triangle3uv(Olivec_Canvas oc, int x1, int y1, int x2, int y2, int x3, int y3, float tx1, float ty1, float tx2, float ty2, float tx3, float ty3, float z1, float z2, float z3, Olivec_Canvas texture) +{ + int lx, hx, ly, hy; + if (olivec_normalize_triangle(oc.width, oc.height, x1, y1, x2, y2, x3, y3, &lx, &hx, &ly, &hy)) { + for (int y = ly; y <= hy; ++y) { + for (int x = lx; x <= hx; ++x) { + int u1, u2, det; + if (olivec_barycentric(x1, y1, x2, y2, x3, y3, x, y, &u1, &u2, &det)) { + int u3 = det - u1 - u2; + float z = z1*u1/det + z2*u2/det + z3*(det - u1 - u2)/det; + float tx = tx1*u1/det + tx2*u2/det + tx3*u3/det; + float ty = ty1*u1/det + ty2*u2/det + ty3*u3/det; + + int texture_x = tx/z*texture.width; + if (texture_x < 0) texture_x = 0; + if ((size_t) texture_x >= texture.width) texture_x = texture.width - 1; + + int texture_y = ty/z*texture.height; + if (texture_y < 0) texture_y = 0; + if ((size_t) texture_y >= texture.height) texture_y = texture.height - 1; + OLIVEC_PIXEL(oc, x, y) = OLIVEC_PIXEL(texture, (int)texture_x, (int)texture_y); + } + } + } + } +} + +OLIVECDEF void olivec_triangle3uv_bilinear(Olivec_Canvas oc, int x1, int y1, int x2, int y2, int x3, int y3, float tx1, float ty1, float tx2, float ty2, float tx3, float ty3, float z1, float z2, float z3, Olivec_Canvas texture) +{ + int lx, hx, ly, hy; + if (olivec_normalize_triangle(oc.width, oc.height, x1, y1, x2, y2, x3, y3, &lx, &hx, &ly, &hy)) { + for (int y = ly; y <= hy; ++y) { + for (int x = lx; x <= hx; ++x) { + int u1, u2, det; + if (olivec_barycentric(x1, y1, x2, y2, x3, y3, x, y, &u1, &u2, &det)) { + int u3 = det - u1 - u2; + float z = z1*u1/det + z2*u2/det + z3*(det - u1 - u2)/det; + float tx = tx1*u1/det + tx2*u2/det + tx3*u3/det; + float ty = ty1*u1/det + ty2*u2/det + ty3*u3/det; + + float texture_x = tx/z*texture.width; + if (texture_x < 0) texture_x = 0; + if (texture_x >= (float) texture.width) texture_x = texture.width - 1; + + float texture_y = ty/z*texture.height; + if (texture_y < 0) texture_y = 0; + if (texture_y >= (float) texture.height) texture_y = texture.height - 1; + + int precision = 100; + OLIVEC_PIXEL(oc, x, y) = olivec_pixel_bilinear( + texture, + texture_x*precision, texture_y*precision, + precision, precision); + } + } + } + } +} + +// TODO: AA for triangle +OLIVECDEF void olivec_triangle(Olivec_Canvas oc, int x1, int y1, int x2, int y2, int x3, int y3, uint32_t color) +{ + int lx, hx, ly, hy; + if (olivec_normalize_triangle(oc.width, oc.height, x1, y1, x2, y2, x3, y3, &lx, &hx, &ly, &hy)) { + for (int y = ly; y <= hy; ++y) { + for (int x = lx; x <= hx; ++x) { + int u1, u2, det; + if (olivec_barycentric(x1, y1, x2, y2, x3, y3, x, y, &u1, &u2, &det)) { + olivec_blend_color(&OLIVEC_PIXEL(oc, x, y), color); + } + } + } + } +} + +OLIVECDEF void olivec_text(Olivec_Canvas oc, const char *text, int tx, int ty, Olivec_Font font, size_t glyph_size, uint32_t color) +{ + for (size_t i = 0; *text; ++i, ++text) { + int gx = tx + i*font.width*glyph_size; + int gy = ty; + const char *glyph = &font.glyphs[(*text)*sizeof(char)*font.width*font.height]; + for (int dy = 0; (size_t) dy < font.height; ++dy) { + for (int dx = 0; (size_t) dx < font.width; ++dx) { + int px = gx + dx*glyph_size; + int py = gy + dy*glyph_size; + if (0 <= px && px < (int) oc.width && 0 <= py && py < (int) oc.height) { + if (glyph[dy*font.width + dx]) { + olivec_rect(oc, px, py, glyph_size, glyph_size, color); + } + } + } + } + } +} + +OLIVECDEF void olivec_sprite_blend(Olivec_Canvas oc, int x, int y, int w, int h, Olivec_Canvas sprite) +{ + if (sprite.width == 0) return; + if (sprite.height == 0) return; + + Olivec_Normalized_Rect nr = {0}; + if (!olivec_normalize_rect(x, y, w, h, oc.width, oc.height, &nr)) return; + + int xa = nr.ox1; + if (w < 0) xa = nr.ox2; + int ya = nr.oy1; + if (h < 0) ya = nr.oy2; + for (int y = nr.y1; y <= nr.y2; ++y) { + for (int x = nr.x1; x <= nr.x2; ++x) { + size_t nx = (x - xa)*((int) sprite.width)/w; + size_t ny = (y - ya)*((int) sprite.height)/h; + olivec_blend_color(&OLIVEC_PIXEL(oc, x, y), OLIVEC_PIXEL(sprite, nx, ny)); + } + } +} + +OLIVECDEF void olivec_sprite_copy(Olivec_Canvas oc, int x, int y, int w, int h, Olivec_Canvas sprite) +{ + if (sprite.width == 0) return; + if (sprite.height == 0) return; + + // TODO: consider introducing flip parameter instead of relying on negative width and height + // Similar to how SDL_RenderCopyEx does that + Olivec_Normalized_Rect nr = {0}; + if (!olivec_normalize_rect(x, y, w, h, oc.width, oc.height, &nr)) return; + + int xa = nr.ox1; + if (w < 0) xa = nr.ox2; + int ya = nr.oy1; + if (h < 0) ya = nr.oy2; + for (int y = nr.y1; y <= nr.y2; ++y) { + for (int x = nr.x1; x <= nr.x2; ++x) { + size_t nx = (x - xa)*((int) sprite.width)/w; + size_t ny = (y - ya)*((int) sprite.height)/h; + OLIVEC_PIXEL(oc, x, y) = OLIVEC_PIXEL(sprite, nx, ny); + } + } +} + +// TODO: olivec_pixel_bilinear does not check for out-of-bounds +// But maybe it shouldn't. Maybe it's a responsibility of the caller of the function. +OLIVECDEF uint32_t olivec_pixel_bilinear(Olivec_Canvas sprite, int nx, int ny, int w, int h) +{ + int px = nx%w; + int py = ny%h; + + int x1 = nx/w, x2 = nx/w; + int y1 = ny/h, y2 = ny/h; + if (px < w/2) { + // left + px += w/2; + x1 -= 1; + if (x1 < 0) x1 = 0; + } else { + // right + px -= w/2; + x2 += 1; + if ((size_t) x2 >= sprite.width) x2 = sprite.width - 1; + } + + if (py < h/2) { + // top + py += h/2; + y1 -= 1; + if (y1 < 0) y1 = 0; + } else { + // bottom + py -= h/2; + y2 += 1; + if ((size_t) y2 >= sprite.height) y2 = sprite.height - 1; + } + + return mix_colors2(mix_colors2(OLIVEC_PIXEL(sprite, x1, y1), + OLIVEC_PIXEL(sprite, x2, y1), + px, w), + mix_colors2(OLIVEC_PIXEL(sprite, x1, y2), + OLIVEC_PIXEL(sprite, x2, y2), + px, w), + py, h); +} + +OLIVECDEF void olivec_sprite_copy_bilinear(Olivec_Canvas oc, int x, int y, int w, int h, Olivec_Canvas sprite) +{ + // TODO: support negative size in olivec_sprite_copy_bilinear() + if (w <= 0) return; + if (h <= 0) return; + + Olivec_Normalized_Rect nr = {0}; + if (!olivec_normalize_rect(x, y, w, h, oc.width, oc.height, &nr)) return; + + for (int y = nr.y1; y <= nr.y2; ++y) { + for (int x = nr.x1; x <= nr.x2; ++x) { + size_t nx = (x - nr.ox1)*sprite.width; + size_t ny = (y - nr.oy1)*sprite.height; + OLIVEC_PIXEL(oc, x, y) = olivec_pixel_bilinear(sprite, nx, ny, w, h); + } + } +} + +#endif // OLIVEC_IMPLEMENTATION + +// TODO: Benchmarking +// TODO: SIMD implementations +// TODO: bezier curves +// TODO: olivec_ring +// TODO: fuzzer +// TODO: Stencil diff --git a/apps/cload/olive/vc.c b/apps/cload/olive/vc.c new file mode 100644 index 0000000000..03a61e0f3b --- /dev/null +++ b/apps/cload/olive/vc.c @@ -0,0 +1,651 @@ +// C implementation of the Virtual Console (VC) for demos. +// +// # Usage +// ```c +// // demo.c +// // vc.c expectes render() to be defined and also supplies it's own entry point +// // if needed (some platforms like WASM_PLATFORM do not have the main() +// // entry point) +// #include "vc.c" +// +// #define WIDTH 800 +// #define HEIGHT 600 +// static uint32_t pixels[WIDTH*HEIGHT]; +// +// static Olivec_Canvas vc_render(float dt) +// { +// Olivec_Canvas oc = olivec_canvas(pixels, WIDTH, HEIGHT, WIDTH); +// // ... +// // ... render into oc ... +// // ... +// return oc; +// } +// ``` +// +// # Build +// ```console +// $ clang -o demo.sdl -DVC_PLATFORM=VC_SDL_PLATFORM demo.c -lSDL2 +// $ clang -o demo.term -DVC_PLATFORM=VC_TERM_PLATFORM demo.c +// $ clang -fno-builtin --target=wasm32 --no-standard-libraries -Wl,--no-entry -Wl,--export=render -Wl,--allow-undefined -o demo.wasm -DVC_PLATFORM=VC_WASM_PLATFORM demo.c +// ``` + +#define OLIVEC_IMPLEMENTATION +#include + +Olivec_Canvas vc_render(float dt); + +#ifndef VC_PLATFORM +#error "Please define VC_PLATFORM macro" +#endif + +// Possible values of VC_PLATFORM +#define VC_WASM_PLATFORM 0 +#define VC_SDL_PLATFORM 1 +#define VC_TERM_PLATFORM 2 +#define VC_ESPRUINO_PLATFORM 3 + +#if VC_PLATFORM == VC_SDL_PLATFORM +#include +#include + +#define return_defer(value) do { result = (value); goto defer; } while (0) + +static SDL_Texture *vc_sdl_texture = NULL; +static size_t vc_sdl_actual_width = 0; +static size_t vc_sdl_actual_height = 0; + +static bool vc_sdl_resize_texture(SDL_Renderer *renderer, size_t new_width, size_t new_height) +{ + if (vc_sdl_texture != NULL) SDL_DestroyTexture(vc_sdl_texture); + vc_sdl_actual_width = new_width; + vc_sdl_actual_height = new_height; + vc_sdl_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, vc_sdl_actual_width, vc_sdl_actual_height); + if (vc_sdl_texture == NULL) return false; + return true; +} + +int main(void) +{ + int result = 0; + + SDL_Window *window = NULL; + SDL_Renderer *renderer = NULL; + + { + if (SDL_Init(SDL_INIT_VIDEO) < 0) return_defer(1); + + window = SDL_CreateWindow("Olivec", 0, 0, 0, 0, 0); + if (window == NULL) return_defer(1); + + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + if (renderer == NULL) return_defer(1); + + Uint32 prev = SDL_GetTicks(); + bool pause = false; + for (;;) { + // Compute Delta Time + Uint32 curr = SDL_GetTicks(); + float dt = (curr - prev)/1000.f; + prev = curr; + + // Flush the events + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: { + return_defer(0); + } break; + case SDL_KEYDOWN: { + if (event.key.keysym.sym == SDLK_SPACE) pause = !pause; + } break; + } + } + + SDL_Rect window_rect = {0, 0, vc_sdl_actual_width, vc_sdl_actual_height}; + + if (!pause) { + // Render the texture + Olivec_Canvas oc_src = vc_render(dt); + if (oc_src.width != vc_sdl_actual_width || oc_src.height != vc_sdl_actual_height) { + if (!vc_sdl_resize_texture(renderer, oc_src.width, oc_src.height)) return_defer(1); + SDL_SetWindowSize(window, vc_sdl_actual_width, vc_sdl_actual_height); + } + void *pixels_dst; + int pitch; + if (SDL_LockTexture(vc_sdl_texture, &window_rect, &pixels_dst, &pitch) < 0) return_defer(1); + for (size_t y = 0; y < vc_sdl_actual_height; ++y) { + // TODO: it would be cool if Olivec_Canvas supported pitch in bytes instead of pixels + // It would be more flexible and we could draw on the locked texture memory directly + memcpy((char*)pixels_dst + y*pitch, oc_src.pixels + y*vc_sdl_actual_width, vc_sdl_actual_width*sizeof(uint32_t)); + } + SDL_UnlockTexture(vc_sdl_texture); + } + + // Display the texture + if (SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0) < 0) return_defer(1); + if (SDL_RenderClear(renderer) < 0) return_defer(1); + if (SDL_RenderCopy(renderer, vc_sdl_texture, &window_rect, &window_rect) < 0) return_defer(1); + SDL_RenderPresent(renderer); + } + } + +defer: + switch (result) { + case 0: + printf("OK\n"); + break; + default: + fprintf(stderr, "SDL ERROR: %s\n", SDL_GetError()); + } + if (vc_sdl_texture) SDL_DestroyTexture(vc_sdl_texture); + if (renderer) SDL_DestroyRenderer(renderer); + if (window) SDL_DestroyWindow(window); + SDL_Quit(); + return result; +} +#elif VC_PLATFORM == VC_TERM_PLATFORM + +#include +#include +#include +#include +#include +#include +#include +#include + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +static size_t vc_term_actual_width = 0; +static size_t vc_term_actual_height = 0; +static size_t vc_term_scaled_down_width = 0; +static size_t vc_term_scaled_down_height = 0; +static int *vc_term_char_canvas = 0; + +int hsl256[][3] = { + {0, 0, 0}, + {0, 100, 25}, + {120, 100, 25}, + {60, 100, 25}, + {240, 100, 25}, + {300, 100, 25}, + {180, 100, 25}, + {0, 0, 75}, + {0, 0, 50}, + {0, 100, 50}, + {120, 100, 50}, + {60, 100, 50}, + {240, 100, 50}, + {300, 100, 50}, + {180, 100, 50}, + {0, 0, 100}, + {0, 0, 0}, + {240, 99, 18}, + {240, 100, 26}, + {240, 100, 34}, + {240, 100, 42}, + {240, 100, 50}, + {120, 99, 18}, + {180, 99, 18}, + {197, 100, 26}, + {207, 100, 34}, + {213, 100, 42}, + {217, 100, 50}, + {120, 100, 26}, + {162, 100, 26}, + {180, 100, 26}, + {193, 100, 34}, + {202, 100, 42}, + {208, 100, 50}, + {120, 100, 34}, + {152, 100, 34}, + {166, 100, 34}, + {180, 100, 34}, + {191, 100, 42}, + {198, 100, 50}, + {120, 100, 42}, + {146, 100, 42}, + {157, 100, 42}, + {168, 100, 42}, + {180, 100, 42}, + {189, 100, 50}, + {120, 100, 50}, + {142, 100, 50}, + {151, 100, 50}, + {161, 100, 50}, + {170, 100, 50}, + {180, 100, 50}, + {0, 99, 18}, + {300, 99, 18}, + {282, 100, 26}, + {272, 100, 34}, + {266, 100, 42}, + {262, 100, 50}, + {60, 99, 18}, + {0, 0, 37}, + {240, 17, 45}, + {240, 33, 52}, + {240, 60, 60}, + {240, 100, 68}, + {77, 100, 26}, + {120, 17, 45}, + {180, 17, 45}, + {210, 33, 52}, + {220, 60, 60}, + {225, 100, 68}, + {87, 100, 34}, + {120, 33, 52}, + {150, 33, 52}, + {180, 33, 52}, + {200, 60, 60}, + {210, 100, 68}, + {93, 100, 42}, + {120, 60, 60}, + {140, 60, 60}, + {160, 60, 60}, + {180, 60, 60}, + {195, 100, 68}, + {97, 100, 50}, + {120, 100, 68}, + {135, 100, 68}, + {150, 100, 68}, + {165, 100, 68}, + {180, 100, 68}, + {0, 100, 26}, + {317, 100, 26}, + {300, 100, 26}, + {286, 100, 34}, + {277, 100, 42}, + {271, 100, 50}, + {42, 100, 26}, + {0, 17, 45}, + {300, 17, 45}, + {270, 33, 52}, + {260, 60, 60}, + {255, 100, 68}, + {60, 100, 26}, + {60, 17, 45}, + {0, 0, 52}, + {240, 20, 60}, + {240, 50, 68}, + {240, 100, 76}, + {73, 100, 34}, + {90, 33, 52}, + {120, 20, 60}, + {180, 20, 60}, + {210, 50, 68}, + {220, 100, 76}, + {82, 100, 42}, + {100, 60, 60}, + {120, 50, 68}, + {150, 50, 68}, + {180, 50, 68}, + {200, 100, 76}, + {88, 100, 50}, + {105, 100, 68}, + {120, 100, 76}, + {140, 100, 76}, + {160, 100, 76}, + {180, 100, 76}, + {0, 100, 34}, + {327, 100, 34}, + {313, 100, 34}, + {300, 100, 34}, + {288, 100, 42}, + {281, 100, 50}, + {32, 100, 34}, + {0, 33, 52}, + {330, 33, 52}, + {300, 33, 52}, + {280, 60, 60}, + {270, 100, 68}, + {46, 100, 34}, + {30, 33, 52}, + {0, 20, 60}, + {300, 20, 60}, + {270, 50, 68}, + {260, 100, 76}, + {60, 100, 34}, + {60, 33, 52}, + {60, 20, 60}, + {0, 0, 68}, + {240, 33, 76}, + {240, 100, 84}, + {71, 100, 42}, + {80, 60, 60}, + {90, 50, 68}, + {120, 33, 76}, + {180, 33, 76}, + {210, 100, 84}, + {78, 100, 50}, + {90, 100, 68}, + {100, 100, 76}, + {120, 100, 84}, + {150, 100, 84}, + {180, 100, 84}, + {0, 100, 42}, + {333, 100, 42}, + {322, 100, 42}, + {311, 100, 42}, + {300, 100, 42}, + {290, 100, 50}, + {26, 100, 42}, + {0, 60, 60}, + {340, 60, 60}, + {320, 60, 60}, + {300, 60, 60}, + {285, 100, 68}, + {37, 100, 42}, + {20, 60, 60}, + {0, 50, 68}, + {330, 50, 68}, + {300, 50, 68}, + {280, 100, 76}, + {48, 100, 42}, + {40, 60, 60}, + {30, 50, 68}, + {0, 33, 76}, + {300, 33, 76}, + {270, 100, 84}, + {60, 100, 42}, + {60, 60, 60}, + {60, 50, 68}, + {60, 33, 76}, + {0, 0, 84}, + {240, 100, 92}, + {69, 100, 50}, + {75, 100, 68}, + {80, 100, 76}, + {90, 100, 84}, + {120, 100, 92}, + {180, 100, 92}, + {0, 100, 50}, + {337, 100, 50}, + {328, 100, 50}, + {318, 100, 50}, + {309, 100, 50}, + {300, 100, 50}, + {22, 100, 50}, + {0, 100, 68}, + {345, 100, 68}, + {330, 100, 68}, + {315, 100, 68}, + {300, 100, 68}, + {31, 100, 50}, + {15, 100, 68}, + {0, 100, 76}, + {340, 100, 76}, + {320, 100, 76}, + {300, 100, 76}, + {41, 100, 50}, + {30, 100, 68}, + {20, 100, 76}, + {0, 100, 84}, + {330, 100, 84}, + {300, 100, 84}, + {50, 100, 50}, + {45, 100, 68}, + {40, 100, 76}, + {30, 100, 84}, + {0, 100, 92}, + {300, 100, 92}, + {60, 100, 50}, + {60, 100, 68}, + {60, 100, 76}, + {60, 100, 84}, + {60, 100, 92}, + {0, 0, 100}, + {0, 0, 3}, + {0, 0, 7}, + {0, 0, 10}, + {0, 0, 14}, + {0, 0, 18}, + {0, 0, 22}, + {0, 0, 26}, + {0, 0, 30}, + {0, 0, 34}, + {0, 0, 38}, + {0, 0, 42}, + {0, 0, 46}, + {0, 0, 50}, + {0, 0, 54}, + {0, 0, 58}, + {0, 0, 61}, + {0, 0, 65}, + {0, 0, 69}, + {0, 0, 73}, + {0, 0, 77}, + {0, 0, 81}, + {0, 0, 85}, + {0, 0, 89}, + {0, 0, 93}, +}; + +int distance_hsl256(int i, int h, int s, int l) +{ + int dh = h - hsl256[i][0]; + int ds = s - hsl256[i][1]; + int dl = l - hsl256[i][2]; + return dh*dh + ds*ds + dl*dl; +} + +// TODO: bring find_ansi_index_by_rgb from image2term +int find_ansi_index_by_hsl(int h, int s, int l) +{ + int index = 0; + for (int i = 0; i < 256; ++i) { + if (distance_hsl256(i, h, s, l) < distance_hsl256(index, h, s, l)) { + index = i; + } + } + return index; +} + +static uint32_t vc_term_compress_pixels_chunk(Olivec_Canvas oc) +{ + size_t r = 0; + size_t g = 0; + size_t b = 0; + size_t a = 0; + + for (size_t y = 0; y < oc.height; ++y) { + for (size_t x = 0; x < oc.width; ++x) { + r += OLIVEC_RED(OLIVEC_PIXEL(oc, x, y)); + g += OLIVEC_GREEN(OLIVEC_PIXEL(oc, x, y)); + b += OLIVEC_BLUE(OLIVEC_PIXEL(oc, x, y)); + a += OLIVEC_ALPHA(OLIVEC_PIXEL(oc, x, y)); + } + } + + r /= oc.width*oc.height; + g /= oc.width*oc.height; + b /= oc.width*oc.height; + a /= oc.width*oc.height; + + return OLIVEC_RGBA(r, g, b, a); +} + +#ifndef VC_TERM_SCALE_DOWN_FACTOR +#define VC_TERM_SCALE_DOWN_FACTOR 20 +#endif // VC_TERM_SCALE_DOWN_FACTOR + +static void vc_term_resize_char_canvas(size_t new_width, size_t new_height) +{ + // TODO: warn the user if vc_term_actual_width does not fit into the screen + // TODO: can we just do something so the divisibility is not important? + // Like round the stuff or something? + // Or we can resize the frame on the fly similarly to how we resize sprites in olivec_sprite_*() functions. + assert(new_width%VC_TERM_SCALE_DOWN_FACTOR == 0 && "Width must be divisible by VC_TERM_SCALE_DOWN_FACTOR"); + assert(new_height%VC_TERM_SCALE_DOWN_FACTOR == 0 && "Height must be divisible by VC_TERM_SCALE_DOWN_FACTOR"); + vc_term_actual_width = new_width; + vc_term_actual_height = new_height; + vc_term_scaled_down_width = vc_term_actual_width/VC_TERM_SCALE_DOWN_FACTOR; + vc_term_scaled_down_height = vc_term_actual_height/VC_TERM_SCALE_DOWN_FACTOR; + free(vc_term_char_canvas); + vc_term_char_canvas = malloc(sizeof(*vc_term_char_canvas)*vc_term_scaled_down_width*vc_term_scaled_down_height); + assert(vc_term_char_canvas != NULL && "Just buy more RAM"); +} + +void rgb_to_hsl(int r, int g, int b, int *h, int *s, int *l) +{ + float r01 = r/255.0f; + float g01 = g/255.0f; + float b01 = b/255.0f; + float cmax = r01; + if (g01 > cmax) cmax = g01; + if (b01 > cmax) cmax = b01; + float cmin = r01; + if (g01 < cmin) cmin = g01; + if (b01 < cmin) cmin = b01; + float delta = cmax - cmin; + float epsilon = 1e-6; + float hf = 0; + if (delta < epsilon) hf = 0; + else if (cmax == r01) hf = 60.0f*fmod((g01 - b01)/delta, 6.0f); + else if (cmax == g01) hf = 60.0f*((b01 - r01)/delta + 2); + else if (cmax == b01) hf = 60.0f*((r01 - g01)/delta + 4); + else assert(0 && "unreachable"); + + float lf = (cmax + cmin)/2; + + float sf = 0; + if (delta < epsilon) sf = 0; + else sf = delta/(1 - fabsf(2*lf - 1)); + + *h = fmodf(fmodf(hf, 360.0f) + 360.0f, 360.0f); + *s = sf*100.0f; + *l = lf*100.0f; +} + +static void vc_term_compress_pixels(Olivec_Canvas oc) +{ + if (vc_term_actual_width != oc.width || vc_term_actual_height != oc.height) { + vc_term_resize_char_canvas(oc.width, oc.height); + } + + for (size_t y = 0; y < vc_term_scaled_down_height; ++y) { + for (size_t x = 0; x < vc_term_scaled_down_width; ++x) { + Olivec_Canvas soc = olivec_subcanvas(oc, x*VC_TERM_SCALE_DOWN_FACTOR, y*VC_TERM_SCALE_DOWN_FACTOR, VC_TERM_SCALE_DOWN_FACTOR, VC_TERM_SCALE_DOWN_FACTOR); + uint32_t cp = vc_term_compress_pixels_chunk(soc); + int r = OLIVEC_RED(cp); + int g = OLIVEC_GREEN(cp); + int b = OLIVEC_BLUE(cp); + int a = OLIVEC_ALPHA(cp); + r = a*r/255; + g = a*g/255; + b = a*b/255; + int h, s, l; + rgb_to_hsl(r, g, b, &h, &s, &l); + vc_term_char_canvas[y*vc_term_scaled_down_width + x] = find_ansi_index_by_hsl(h, s, l); + } + } +} + +int main(void) +{ + for (;;) { + vc_term_compress_pixels(vc_render(1.f/60.f)); + for (size_t y = 0; y < vc_term_scaled_down_height; ++y) { + for (size_t x = 0; x < vc_term_scaled_down_width; ++x) { + // TODO: explore the idea of figuring out aspect ratio of the character using escape ANSI codes of the terminal and rendering the image accordingly + printf("\033[48;5;%dm ", vc_term_char_canvas[y*vc_term_scaled_down_width + x]); + } + printf("\033[0m\n"); + } + + usleep(1000*1000/60); + printf("\033[%zuA", vc_term_scaled_down_height); + printf("\033[%zuD", vc_term_scaled_down_width); + } + return 0; +} +#elif VC_PLATFORM == VC_WASM_PLATFORM +// Do nothing because all the work is done in ../js/vc.js +#elif VC_PLATFORM == VC_ESPRUINO_PLATFORM + +extern void msg(char *m); + +unsigned int rand_simple(void) { static unsigned int seed = 12345; seed = (1664525 * seed + 1013904223) % 0xFFFFFFFF; return seed; } + +#define WIDTH 176 +#define HEIGHT 176 + +// Set a pixel at (x, y) to a specific color (0-7) +void setPixel(unsigned char *framebuffer, int x, int y, uint8_t color) { + if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT || color > 7) { + // Invalid input: Out of bounds or color out of range + msg("Pixel\nBad"); + return; + } + + // Calculate pixel position in the framebuffer + int pixel_index = y * WIDTH + x; // Linear index of the pixel + int bit_index = pixel_index * 3; // Start bit position in the buffer + int byte_index = bit_index / 8; // Byte containing the pixel + int bit_offset = bit_index % 8; // Bit offset within that byte + + int mask = 0xe000 >> bit_offset; + int val = (color << 13) >> bit_offset; + + // Clear the existing pixel value + framebuffer[byte_index] &= ~(mask >> 8); // Clear 3 bits for the pixel + framebuffer[byte_index] |= (val >> 8); // Set new value + + // Handle bits spilling into the next byte + if (mask & 0x00ff) { + framebuffer[byte_index + 1] &= ~mask; // Clear next byte's bits + framebuffer[byte_index + 1] |= val; // Set next byte's bits + } +} + + +// Function to convert RGB888 to a 3-bit color (0-7) +uint8_t to_3bit(int v) { + uint8_t r, g, b; + b = v >> 16; + g = v >> 8; + r = v; + v = 0; + if (rand_simple() >> 24 < r) v |= 4; + if (rand_simple() >> 24 < g) v |= 2; + if (rand_simple() >> 24 < b) v |= 1; + return v; +} + + +void put_pixel(char *fb, int x, int y, int c) +{ + setPixel(fb, x, y, c); +} + +void big_pixel(char *fb, int x1, int y1, int c, int zoom) +{ + for (int y=0; y WIDTH) { + msg("Bad"); + return; + } + //memset(fb, 0x8, WIDTH*HEIGHT/3); + for (int y=0; y