#include #include "pxl8_ase.h" #include "pxl8_blit.h" #include "pxl8_gfx.h" #include "pxl8_macros.h" #include "pxl8_types.h" static u32 pxl8_get_palette_size(pxl8_color_mode mode) { switch (mode) { case PXL8_COLOR_MODE_HICOLOR: return 0; case PXL8_COLOR_MODE_FAMI: return 64; case PXL8_COLOR_MODE_MEGA: return 512; case PXL8_COLOR_MODE_GBA: return 32768; case PXL8_COLOR_MODE_SUPERFAMI: return 32768; default: return 256; } } void pxl8_gfx_get_resolution_dimensions(pxl8_resolution resolution, i32* width, i32* height) { switch (resolution) { case PXL8_RESOLUTION_240x160: *width = 240; *height = 160; break; case PXL8_RESOLUTION_320x180: *width = 320; *height = 180; break; case PXL8_RESOLUTION_320x240: *width = 320; *height = 240; break; case PXL8_RESOLUTION_640x360: *width = 640; *height = 360; break; case PXL8_RESOLUTION_640x480: *width = 640; *height = 480; break; case PXL8_RESOLUTION_800x600: *width = 800; *height = 600; break; case PXL8_RESOLUTION_960x540: *width = 960; *height = 540; break; default: *width = 640; *height = 360; break; } } pxl8_result pxl8_gfx_init( pxl8_gfx_ctx* ctx, pxl8_color_mode mode, pxl8_resolution resolution, const char* title, i32 window_width, i32 window_height ) { memset(ctx, 0, sizeof(pxl8_gfx_ctx)); ctx->color_mode = mode; pxl8_gfx_get_resolution_dimensions(resolution, &ctx->framebuffer_width, &ctx->framebuffer_height); ctx->window = SDL_CreateWindow( title, window_width, window_height, SDL_WINDOW_RESIZABLE ); if (!ctx->window) { pxl8_error("Failed to create window: %s", SDL_GetError()); return PXL8_ERROR_SYSTEM_FAILURE; } ctx->renderer = SDL_CreateRenderer(ctx->window, NULL); if (!ctx->renderer) { pxl8_error("Failed to create renderer: %s", SDL_GetError()); SDL_DestroyWindow(ctx->window); return PXL8_ERROR_SYSTEM_FAILURE; } SDL_SetRenderLogicalPresentation(ctx->renderer, ctx->framebuffer_width, ctx->framebuffer_height, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE); i32 bytes_per_pixel = (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; i32 fb_size = ctx->framebuffer_width * ctx->framebuffer_height * bytes_per_pixel; ctx->framebuffer = (u8*)SDL_calloc(1, fb_size); if (!ctx->framebuffer) { pxl8_error("Failed to allocate framebuffer"); pxl8_gfx_shutdown(ctx); return PXL8_ERROR_OUT_OF_MEMORY; } ctx->framebuffer_texture = SDL_CreateTexture( ctx->renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, ctx->framebuffer_width, ctx->framebuffer_height ); SDL_SetTextureScaleMode(ctx->framebuffer_texture, SDL_SCALEMODE_NEAREST); if (!ctx->framebuffer_texture) { pxl8_error("Failed to create framebuffer texture: %s", SDL_GetError()); pxl8_gfx_shutdown(ctx); return PXL8_ERROR_SYSTEM_FAILURE; } ctx->palette_size = pxl8_get_palette_size(mode); if (ctx->palette_size > 0) { ctx->palette = (u32*)SDL_calloc(ctx->palette_size, sizeof(u32)); if (!ctx->palette) { pxl8_error("Failed to allocate palette"); pxl8_gfx_shutdown(ctx); return PXL8_ERROR_OUT_OF_MEMORY; } for (u32 i = 0; i < 256 && i < ctx->palette_size; i++) { u8 gray = (u8)(i * 255 / 255); ctx->palette[i] = 0xFF000000 | (gray << 16) | (gray << 8) | gray; } } ctx->viewport_x = 0; ctx->viewport_y = 0; ctx->viewport_width = ctx->framebuffer_width; ctx->viewport_height = ctx->framebuffer_height; ctx->initialized = true; return PXL8_OK; } void pxl8_gfx_shutdown(pxl8_gfx_ctx* ctx) { if (!ctx) return; if (ctx->framebuffer_texture) { SDL_DestroyTexture(ctx->framebuffer_texture); ctx->framebuffer_texture = NULL; } if (ctx->sprite_atlas_texture) { SDL_DestroyTexture(ctx->sprite_atlas_texture); ctx->sprite_atlas_texture = NULL; } if (ctx->renderer) { SDL_DestroyRenderer(ctx->renderer); ctx->renderer = NULL; } if (ctx->window) { SDL_DestroyWindow(ctx->window); ctx->window = NULL; } SDL_free(ctx->framebuffer); SDL_free(ctx->palette); SDL_free(ctx->atlas); SDL_free(ctx->atlas_entries); ctx->initialized = false; } // resource loading pxl8_result pxl8_gfx_init_atlas(pxl8_gfx_ctx* ctx, u32 width, u32 height) { if (!ctx || !ctx->initialized) return PXL8_ERROR_INVALID_ARGUMENT; ctx->sprite_atlas_width = width; ctx->sprite_atlas_height = height; i32 bytes_per_pixel = (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; ctx->atlas = (u8*)SDL_calloc(width * height, bytes_per_pixel); if (!ctx->atlas) { return PXL8_ERROR_OUT_OF_MEMORY; } ctx->sprite_atlas_texture = SDL_CreateTexture( ctx->renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, width, height ); if (!ctx->sprite_atlas_texture) { SDL_free(ctx->atlas); ctx->atlas = NULL; return PXL8_ERROR_SYSTEM_FAILURE; } ctx->atlas_entries_cap = 256; ctx->atlas_entries = (pxl8_atlas_entry*)SDL_calloc(ctx->atlas_entries_cap, sizeof(pxl8_atlas_entry)); if (!ctx->atlas_entries) { SDL_DestroyTexture(ctx->sprite_atlas_texture); SDL_free(ctx->atlas); return PXL8_ERROR_OUT_OF_MEMORY; } ctx->sprite_frames_per_row = width / 128; if (ctx->sprite_frames_per_row == 0) ctx->sprite_frames_per_row = 1; return PXL8_OK; } pxl8_result pxl8_gfx_load_sprite(pxl8_gfx_ctx* ctx, const char* path) { if (!ctx || !ctx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT; if (!ctx->atlas) { pxl8_error("Atlas not initialized"); return PXL8_ERROR_NOT_INITIALIZED; } for (u32 i = 0; i < ctx->atlas_entries_len; i++) { if (strcmp(ctx->atlas_entries[i].path, path) == 0) { return PXL8_OK; } } pxl8_ase_file ase_file; pxl8_result result = pxl8_ase_load(path, &ase_file); if (result != PXL8_OK) { pxl8_error("Failed to load ASE file: %s", path); return result; } if (ase_file.frame_count == 0) { pxl8_error("No frames in ASE file"); pxl8_ase_free(&ase_file); return PXL8_ERROR_INVALID_FORMAT; } u32 sprite_w = ase_file.header.width; u32 sprite_h = ase_file.header.height; if (ctx->sprite_frame_width == 0 || ctx->sprite_frame_height == 0) { ctx->sprite_frame_width = sprite_w; ctx->sprite_frame_height = sprite_h; } u32 id = ctx->atlas_entries_len; u32 frames_per_row = ctx->sprite_frames_per_row; u32 atlas_x = (id % frames_per_row) * ctx->sprite_frame_width; u32 atlas_y = (id / frames_per_row) * ctx->sprite_frame_height; if (atlas_x + sprite_w > ctx->sprite_atlas_width || atlas_y + sprite_h > ctx->sprite_atlas_height) { pxl8_error("Sprite doesn't fit in atlas"); pxl8_ase_free(&ase_file); return PXL8_ERROR_INVALID_SIZE; } i32 bytes_per_pixel = (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; for (u32 y = 0; y < sprite_h; y++) { for (u32 x = 0; x < sprite_w; x++) { u32 frame_idx = y * sprite_w + x; u32 atlas_idx = (atlas_y + y) * ctx->sprite_atlas_width + (atlas_x + x); if (bytes_per_pixel == 4) { ((u32*)ctx->atlas)[atlas_idx] = ((u32*)ase_file.frames[0].pixels)[frame_idx]; } else { ctx->atlas[atlas_idx] = ase_file.frames[0].pixels[frame_idx]; } } } if (ctx->atlas_entries_len >= ctx->atlas_entries_cap) { ctx->atlas_entries_cap *= 2; pxl8_atlas_entry* new_entries = (pxl8_atlas_entry*)SDL_realloc( ctx->atlas_entries, ctx->atlas_entries_cap * sizeof(pxl8_atlas_entry) ); if (!new_entries) { pxl8_ase_free(&ase_file); return PXL8_ERROR_OUT_OF_MEMORY; } ctx->atlas_entries = new_entries; } ctx->atlas_entries[id].x = atlas_x; ctx->atlas_entries[id].y = atlas_y; ctx->atlas_entries[id].w = sprite_w; ctx->atlas_entries[id].h = sprite_h; ctx->atlas_entries[id].sprite_id = id; strncpy(ctx->atlas_entries[id].path, path, sizeof(ctx->atlas_entries[id].path) - 1); ctx->atlas_entries[id].path[sizeof(ctx->atlas_entries[id].path) - 1] = '\0'; ctx->atlas_entries_len++; ctx->atlas_dirty = true; pxl8_ase_free(&ase_file); pxl8_debug("Loaded sprite %u: %ux%u at (%u,%u)", id, sprite_w, sprite_h, atlas_x, atlas_y); return id; } pxl8_result pxl8_gfx_load_palette(pxl8_gfx_ctx* ctx, const char* path) { if (!ctx || !ctx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT; if (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) return PXL8_OK; pxl8_debug("Loading palette from: %s", path); pxl8_ase_file ase_file; pxl8_result result = pxl8_ase_load(path, &ase_file); if (result != PXL8_OK) { pxl8_error("Failed to load ASE file for palette: %s", path); return result; } if (ase_file.palette.entry_count == 0) { pxl8_error("No palette data in ASE file"); pxl8_ase_free(&ase_file); return PXL8_ERROR_INVALID_FORMAT; } u32 copy_size = (ase_file.palette.entry_count < ctx->palette_size) ? ase_file.palette.entry_count : ctx->palette_size; memcpy(ctx->palette, ase_file.palette.colors, copy_size * sizeof(u32)); if (ctx->color_mode != PXL8_COLOR_MODE_HICOLOR && ctx->framebuffer_texture) { SDL_Color colors[256]; for (u32 i = 0; i < 256 && i < ctx->palette_size; i++) { colors[i].r = (ctx->palette[i] >> 16) & 0xFF; colors[i].g = (ctx->palette[i] >> 8) & 0xFF; colors[i].b = ctx->palette[i] & 0xFF; colors[i].a = (ctx->palette[i] >> 24) & 0xFF; } SDL_SetPaletteColors(SDL_CreateSurfacePalette(NULL), colors, 0, 256); } pxl8_ase_free(&ase_file); pxl8_debug("Loaded palette with %u colors", copy_size); return PXL8_OK; } pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx_ctx* ctx) { (void)ctx; return PXL8_OK; } // rendering pipeline void pxl8_gfx_upload_framebuffer(pxl8_gfx_ctx* ctx) { if (!ctx || !ctx->initialized || !ctx->framebuffer_texture) return; if (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) { SDL_UpdateTexture(ctx->framebuffer_texture, NULL, ctx->framebuffer, ctx->framebuffer_width * 4); } else { static u32* rgba_buffer = NULL; static size_t buffer_size = 0; size_t needed_size = ctx->framebuffer_width * ctx->framebuffer_height; if (buffer_size < needed_size) { rgba_buffer = (u32*)SDL_realloc(rgba_buffer, needed_size * 4); buffer_size = needed_size; } if (!rgba_buffer) return; for (i32 i = 0; i < ctx->framebuffer_width * ctx->framebuffer_height; i++) { u8 index = ctx->framebuffer[i]; rgba_buffer[i] = (index < ctx->palette_size) ? ctx->palette[index] : 0xFF000000; } SDL_UpdateTexture(ctx->framebuffer_texture, NULL, rgba_buffer, ctx->framebuffer_width * 4); } } void pxl8_gfx_upload_atlas(pxl8_gfx_ctx* ctx) { if (!ctx || !ctx->initialized || !ctx->sprite_atlas_texture || !ctx->atlas_dirty) return; if (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) { SDL_UpdateTexture(ctx->sprite_atlas_texture, NULL, ctx->atlas, ctx->sprite_atlas_width * 4); } else { u32* rgba_buffer = (u32*)SDL_malloc(ctx->sprite_atlas_width * ctx->sprite_atlas_height * 4); if (!rgba_buffer) return; for (u32 i = 0; i < ctx->sprite_atlas_width * ctx->sprite_atlas_height; i++) { u8 index = ctx->atlas[i]; rgba_buffer[i] = (index < ctx->palette_size) ? ctx->palette[index] : 0x00000000; } SDL_UpdateTexture(ctx->sprite_atlas_texture, NULL, rgba_buffer, ctx->sprite_atlas_width * 4); SDL_free(rgba_buffer); } ctx->atlas_dirty = false; pxl8_debug("Atlas uploaded to GPU"); } void pxl8_gfx_present(pxl8_gfx_ctx* ctx) { if (!ctx || !ctx->initialized) return; SDL_SetRenderDrawColor(ctx->renderer, 0, 0, 0, 255); SDL_RenderClear(ctx->renderer); if (ctx->framebuffer_texture) { SDL_RenderTexture(ctx->renderer, ctx->framebuffer_texture, NULL, NULL); } SDL_RenderPresent(ctx->renderer); } void pxl8_gfx_viewport(pxl8_gfx_ctx* ctx, i32 x, i32 y, i32 width, i32 height) { if (!ctx) return; ctx->viewport_x = x; ctx->viewport_y = y; ctx->viewport_width = width; ctx->viewport_height = height; } void pxl8_gfx_project(pxl8_gfx_ctx* ctx, f32 left, f32 right, f32 top, f32 bottom) { (void)ctx; (void)left; (void)right; (void)top; (void)bottom; } // drawing primitives void pxl8_clr(pxl8_gfx_ctx* ctx, u32 color) { if (!ctx || !ctx->framebuffer) return; i32 bytes_per_pixel = (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; i32 size = ctx->framebuffer_width * ctx->framebuffer_height; if (bytes_per_pixel == 4) { u32* fb32 = (u32*)ctx->framebuffer; for (i32 i = 0; i < size; i++) { fb32[i] = color; } } else { memset(ctx->framebuffer, color & 0xFF, size); } } void pxl8_pixel(pxl8_gfx_ctx* ctx, i32 x, i32 y, u32 color) { if (!ctx || !ctx->framebuffer) return; if (x < 0 || x >= ctx->framebuffer_width || y < 0 || y >= ctx->framebuffer_height) return; i32 idx = y * ctx->framebuffer_width + x; if (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) { ((u32*)ctx->framebuffer)[idx] = color; } else { ctx->framebuffer[idx] = color & 0xFF; } } u32 pxl8_get_pixel(pxl8_gfx_ctx* ctx, i32 x, i32 y) { if (!ctx || !ctx->framebuffer) return 0; if (x < 0 || x >= ctx->framebuffer_width || y < 0 || y >= ctx->framebuffer_height) return 0; i32 idx = y * ctx->framebuffer_width + x; if (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) { return ((u32*)ctx->framebuffer)[idx]; } else { return ctx->framebuffer[idx]; } } void pxl8_line(pxl8_gfx_ctx* ctx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color) { if (!ctx) return; i32 dx = abs(x1 - x0); i32 dy = abs(y1 - y0); i32 sx = x0 < x1 ? 1 : -1; i32 sy = y0 < y1 ? 1 : -1; i32 err = dx - dy; while (1) { pxl8_pixel(ctx, x0, y0, color); if (x0 == x1 && y0 == y1) break; i32 e2 = 2 * err; if (e2 > -dy) { err -= dy; x0 += sx; } if (e2 < dx) { err += dx; y0 += sy; } } } void pxl8_rect(pxl8_gfx_ctx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color) { if (!ctx) return; pxl8_line(ctx, x, y, x + w - 1, y, color); pxl8_line(ctx, x + w - 1, y, x + w - 1, y + h - 1, color); pxl8_line(ctx, x + w - 1, y + h - 1, x, y + h - 1, color); pxl8_line(ctx, x, y + h - 1, x, y, color); } void pxl8_rect_fill(pxl8_gfx_ctx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color) { if (!ctx) return; for (i32 py = y; py < y + h; py++) { for (i32 px = x; px < x + w; px++) { pxl8_pixel(ctx, px, py, color); } } } void pxl8_circle(pxl8_gfx_ctx* ctx, i32 cx, i32 cy, i32 radius, u32 color) { if (!ctx) return; i32 x = radius; i32 y = 0; i32 err = 0; while (x >= y) { pxl8_pixel(ctx, cx + x, cy + y, color); pxl8_pixel(ctx, cx + y, cy + x, color); pxl8_pixel(ctx, cx - y, cy + x, color); pxl8_pixel(ctx, cx - x, cy + y, color); pxl8_pixel(ctx, cx - x, cy - y, color); pxl8_pixel(ctx, cx - y, cy - x, color); pxl8_pixel(ctx, cx + y, cy - x, color); pxl8_pixel(ctx, cx + x, cy - y, color); if (err <= 0) { y += 1; err += 2 * y + 1; } else { x -= 1; err -= 2 * x + 1; } } } void pxl8_circle_fill(pxl8_gfx_ctx* ctx, i32 cx, i32 cy, i32 radius, u32 color) { if (!ctx) return; for (i32 y = -radius; y <= radius; y++) { for (i32 x = -radius; x <= radius; x++) { if (x * x + y * y <= radius * radius) { pxl8_pixel(ctx, cx + x, cy + y, color); } } } } void pxl8_text(pxl8_gfx_ctx* ctx, const char* text, i32 x, i32 y, u32 color) { if (!ctx || !text) return; (void)x; (void)y; (void)color; } void pxl8_sprite(pxl8_gfx_ctx* ctx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) { if (!ctx || !ctx->atlas || !ctx->framebuffer || sprite_id >= ctx->atlas_entries_len) return; pxl8_atlas_entry* entry = &ctx->atlas_entries[sprite_id]; i32 clip_left = (x < 0) ? -x : 0; i32 clip_top = (y < 0) ? -y : 0; i32 clip_right = (x + w > ctx->framebuffer_width) ? x + w - ctx->framebuffer_width : 0; i32 clip_bottom = (y + h > ctx->framebuffer_height) ? y + h - ctx->framebuffer_height : 0; i32 draw_width = w - clip_left - clip_right; i32 draw_height = h - clip_top - clip_bottom; if (draw_width <= 0 || draw_height <= 0) return; i32 dest_x = x + clip_left; i32 dest_y = y + clip_top; bool is_1to1_scale = (w == entry->w && h == entry->h); bool is_unclipped = (clip_left == 0 && clip_top == 0 && clip_right == 0 && clip_bottom == 0); if (is_1to1_scale && is_unclipped && pxl8_is_simd_aligned(w)) { const u8* sprite_data = ctx->atlas + entry->y * ctx->sprite_atlas_width + entry->x; if (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) { pxl8_blit_simd_hicolor((u32*)ctx->framebuffer, ctx->framebuffer_width, (const u32*)sprite_data, ctx->sprite_atlas_width, x, y, w, h); } else { pxl8_blit_simd_indexed(ctx->framebuffer, ctx->framebuffer_width, sprite_data, ctx->sprite_atlas_width, x, y, w, h); } } else { for (i32 py = 0; py < draw_height; py++) { for (i32 px = 0; px < draw_width; px++) { i32 src_x = entry->x + ((px + clip_left) * entry->w) / w; i32 src_y = entry->y + ((py + clip_top) * entry->h) / h; i32 src_idx = src_y * ctx->sprite_atlas_width + src_x; i32 dest_idx = (dest_y + py) * ctx->framebuffer_width + (dest_x + px); if (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) { u32 pixel = ((u32*)ctx->atlas)[src_idx]; if (pixel & 0xFF000000) { ((u32*)ctx->framebuffer)[dest_idx] = pixel; } } else { u8 pixel = ctx->atlas[src_idx]; if (pixel != 0) { ctx->framebuffer[dest_idx] = pixel; } } } } } } // palette effects void pxl8_gfx_cycle_palette(pxl8_gfx_ctx* ctx, u8 start, u8 count, i32 step) { if (!ctx || !ctx->palette || count == 0) return; u32 temp[256]; for (u8 i = 0; i < count; i++) { temp[i] = ctx->palette[start + i]; } for (u8 i = 0; i < count; i++) { u8 src_idx = i; u8 dst_idx = (i + step) % count; ctx->palette[start + dst_idx] = temp[src_idx]; } } void pxl8_gfx_swap_palette(pxl8_gfx_ctx* ctx, u8 start, u8 count, u32* new_colors) { if (!ctx || !ctx->palette || !new_colors) return; for (u8 i = 0; i < count && (start + i) < ctx->palette_size; i++) { ctx->palette[start + i] = new_colors[i]; } } void pxl8_gfx_fade_palette(pxl8_gfx_ctx* ctx, u8 start, u8 count, f32 amount, u32 target_color) { if (!ctx || !ctx->palette || count == 0) return; if (amount < 0.0f) amount = 0.0f; if (amount > 1.0f) amount = 1.0f; u8 target_r = target_color & 0xFF; u8 target_g = (target_color >> 8) & 0xFF; u8 target_b = (target_color >> 16) & 0xFF; u8 target_a = (target_color >> 24) & 0xFF; for (u8 i = 0; i < count && (start + i) < ctx->palette_size; i++) { u32 current = ctx->palette[start + i]; u8 cur_r = current & 0xFF; u8 cur_g = (current >> 8) & 0xFF; u8 cur_b = (current >> 16) & 0xFF; u8 cur_a = (current >> 24) & 0xFF; u8 new_r = cur_r + (i32)((target_r - cur_r) * amount); u8 new_g = cur_g + (i32)((target_g - cur_g) * amount); u8 new_b = cur_b + (i32)((target_b - cur_b) * amount); u8 new_a = cur_a + (i32)((target_a - cur_a) * amount); ctx->palette[start + i] = new_r | (new_g << 8) | (new_b << 16) | (new_a << 24); } } void pxl8_gfx_interpolate_palettes(pxl8_gfx_ctx* ctx, u32* palette1, u32* palette2, u8 start, u8 count, f32 t) { if (!ctx || !ctx->palette || !palette1 || !palette2 || count == 0) return; if (t < 0.0f) t = 0.0f; if (t > 1.0f) t = 1.0f; for (u8 i = 0; i < count && (start + i) < ctx->palette_size; i++) { u32 col1 = palette1[i]; u32 col2 = palette2[i]; u8 r1 = col1 & 0xFF; u8 g1 = (col1 >> 8) & 0xFF; u8 b1 = (col1 >> 16) & 0xFF; u8 a1 = (col1 >> 24) & 0xFF; u8 r2 = col2 & 0xFF; u8 g2 = (col2 >> 8) & 0xFF; u8 b2 = (col2 >> 16) & 0xFF; u8 a2 = (col2 >> 24) & 0xFF; u8 r = r1 + (i32)((r2 - r1) * t); u8 g = g1 + (i32)((g2 - g1) * t); u8 b = b1 + (i32)((b2 - b1) * t); u8 a = a1 + (i32)((a2 - a1) * t); ctx->palette[start + i] = r | (g << 8) | (b << 16) | (a << 24); } } void pxl8_gfx_color_ramp(pxl8_gfx_ctx* ctx, u8 start, u8 count, u32 from_color, u32 to_color) { if (!ctx || !ctx->palette || count <= 1) return; u8 from_r = from_color & 0xFF; u8 from_g = (from_color >> 8) & 0xFF; u8 from_b = (from_color >> 16) & 0xFF; u8 from_a = (from_color >> 24) & 0xFF; u8 to_r = to_color & 0xFF; u8 to_g = (to_color >> 8) & 0xFF; u8 to_b = (to_color >> 16) & 0xFF; u8 to_a = (to_color >> 24) & 0xFF; for (u8 i = 0; i < count && (start + i) < ctx->palette_size; i++) { f32 t = (f32)i / (f32)(count - 1); u8 r = from_r + (i32)((to_r - from_r) * t); u8 g = from_g + (i32)((to_g - from_g) * t); u8 b = from_b + (i32)((to_b - from_b) * t); u8 a = from_a + (i32)((to_a - from_a) * t); ctx->palette[start + i] = r | (g << 8) | (b << 16) | (a << 24); } } void pxl8_gfx_process_effects(pxl8_gfx_ctx* ctx, pxl8_effects* effects, f32 dt) { if (!ctx || !effects) return; effects->time += dt; for (i32 i = 0; i < 8; i++) { pxl8_palette_cycle* cycle = &effects->palette_cycles[i]; if (!cycle->active) continue; cycle->timer += dt * cycle->speed; if (cycle->timer >= 1.0f) { cycle->timer -= 1.0f; i32 count = cycle->end_index - cycle->start_index + 1; pxl8_gfx_cycle_palette(ctx, cycle->start_index, count, 1); } } }