/* SPDX-License-Identifier: MIT */ /* * konfx.c * * Copyright (c) 2026 KonAki * * 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. */ #include "konfx.h" #include #include #define PI 3.14159265358979f static float numberOr0(float v) { return v == v && v - v == 0.0f ? v : 0.0f; } static int clampInt(float v, int lo, int hi) { if (!(v > (float)lo)) return lo; if (v > (float)hi) return hi; return (int)floorf(v + 0.5f); } /*** state ***/ void kon_screenfxInit(kon_screenfx_t *fx) { kon_screenfx_t zero = {0}; *fx = zero; kon_tweenSet(&fx->fade, 0.0f); kon_tweenSet(&fx->flash, 0.0f); kon_tweenSet(&fx->tone[0], 0.0f); kon_tweenSet(&fx->tone[1], 0.0f); kon_tweenSet(&fx->tone[2], 0.0f); kon_tweenSet(&fx->tone[3], 0.0f); } void kon_screenfxFade(kon_screenfx_t *fx, uint32_t color, float alpha, float seconds, kon_ease_t ease) { fx->fadeColor = color & 0x00FFFFFFu; kon_tweenTo(&fx->fade, numberOr0(alpha), seconds, ease); } void kon_screenfxFlash(kon_screenfx_t *fx, uint32_t color, float seconds) { fx->flashColor = color & 0x00FFFFFFu; kon_tweenStart(&fx->flash, (float)(color >> 24) / 255.0f, 0.0f, seconds, KON_EASE_LINEAR); } void kon_screenfxShake(kon_screenfx_t *fx, float powerX, float powerY, float hz, float seconds) { fx->shakeX = numberOr0(powerX); fx->shakeY = numberOr0(powerY); fx->shakeHz = numberOr0(hz); fx->shakeTime = 0.0f; fx->shakeDuration = seconds > 0.0f ? numberOr0(seconds) : 0.0f; } void kon_screenfxTone(kon_screenfx_t *fx, int r, int g, int b, int gray, float seconds, kon_ease_t ease) { kon_tweenTo(&fx->tone[0], (float)r, seconds, ease); kon_tweenTo(&fx->tone[1], (float)g, seconds, ease); kon_tweenTo(&fx->tone[2], (float)b, seconds, ease); kon_tweenTo(&fx->tone[3], (float)gray, seconds, ease); } void kon_screenfxUpdate(kon_screenfx_t *fx, float dt) { int i; kon_tweenStep(&fx->fade, dt); kon_tweenStep(&fx->flash, dt); for (i = 0; i < 4; i++) kon_tweenStep(&fx->tone[i], dt); if (fx->shakeTime < fx->shakeDuration && dt > 0.0f) { fx->shakeTime += dt; if (fx->shakeTime > fx->shakeDuration) fx->shakeTime = fx->shakeDuration; } } int kon_screenfxBusy(const kon_screenfx_t *fx) { int i; if (!kon_tweenDone(&fx->fade) || !kon_tweenDone(&fx->flash) || fx->shakeTime < fx->shakeDuration) return 1; for (i = 0; i < 4; i++) { if (!kon_tweenDone(&fx->tone[i])) return 1; } return 0; } void kon_screenfxOffset(const kon_screenfx_t *fx, int *x, int *y) { float left, wave; *x = *y = 0; if (!(fx->shakeTime < fx->shakeDuration)) return; left = 1.0f - fx->shakeTime / fx->shakeDuration; wave = sinf(2.0f * PI * fx->shakeHz * fx->shakeTime) * left; *x = clampInt(wave * fx->shakeX, -100000, 100000); *y = clampInt(wave * fx->shakeY, -100000, 100000); } /*** passes ***/ static void tone(const kon_screenfx_t *fx, kon_framebuffer_t *fb) { int add[3], gray = clampInt(kon_tweenValue(&fx->tone[3]), 0, 255), i; size_t n = (size_t)fb->width * (size_t)fb->height, p; for (i = 0; i < 3; i++) add[i] = clampInt(kon_tweenValue(&fx->tone[i]), -255, 255); if (!add[0] && !add[1] && !add[2] && !gray) return; for (p = 0; p < n; p++) { uint32_t c = fb->data[p]; int ch[3], k; ch[0] = (int)((c >> 16) & 0xFF); ch[1] = (int)((c >> 8) & 0xFF); ch[2] = (int)(c & 0xFF); if (gray) { int lum = (ch[0] * 299 + ch[1] * 587 + ch[2] * 114) / 1000; for (k = 0; k < 3; k++) ch[k] += (lum - ch[k]) * gray / 255; } for (k = 0; k < 3; k++) { ch[k] += add[k]; if (ch[k] < 0) ch[k] = 0; if (ch[k] > 255) ch[k] = 255; } fb->data[p] = (c & 0xFF000000u) | ((uint32_t)ch[0] << 16) | ((uint32_t)ch[1] << 8) | (uint32_t)ch[2]; } } /* moves the whole picture, the edge pixels are repeated into what is uncovered */ static void shift(kon_framebuffer_t *fb, int dx, int dy) { int w = fb->width, h = fb->height, y; if (w < 1 || h < 1) return; if (dx >= w) dx = w - 1; if (dx <= -w) dx = -w + 1; if (dy >= h) dy = h - 1; if (dy <= -h) dy = -h + 1; if (!dx && !dy) return; if (dy > 0) { for (y = h - 1; y >= 0; y--) { int from = y - dy < 0 ? 0 : y - dy; if (from != y) memcpy(&fb->data[(size_t)y * (size_t)w], &fb->data[(size_t)from * (size_t)w], (size_t)w * sizeof fb->data[0]); } } else if (dy < 0) { for (y = 0; y < h; y++) { int from = y - dy > h - 1 ? h - 1 : y - dy; if (from != y) memcpy(&fb->data[(size_t)y * (size_t)w], &fb->data[(size_t)from * (size_t)w], (size_t)w * sizeof fb->data[0]); } } if (!dx) return; for (y = 0; y < h; y++) { uint32_t *row = &fb->data[(size_t)y * (size_t)w]; int i; if (dx > 0) { memmove(row + dx, row, (size_t)(w - dx) * sizeof row[0]); for (i = 0; i < dx; i++) row[i] = row[dx]; } else { memmove(row, row - dx, (size_t)(w + dx) * sizeof row[0]); for (i = w + dx; i < w; i++) row[i] = row[w + dx - 1]; } } } static void cover(kon_framebuffer_t *fb, uint32_t color, float alpha) { uint32_t a; if (!(alpha > 0.0f)) return; if (alpha > 1.0f) alpha = 1.0f; a = (uint32_t)(alpha * 255.0f + 0.5f); if (a) kon_fillRectangle(fb, 0, 0, fb->width, fb->height, (a << 24) | (color & 0x00FFFFFFu)); } void kon_screenfxApplyScene(const kon_screenfx_t *fx, kon_framebuffer_t *fb) { int x, y; if (!fb) return; tone(fx, fb); kon_screenfxOffset(fx, &x, &y); shift(fb, x, y); } void kon_screenfxApplyOverlay(const kon_screenfx_t *fx, kon_framebuffer_t *fb) { if (!fb) return; cover(fb, fx->flashColor, kon_tweenValue(&fx->flash)); cover(fb, fx->fadeColor, kon_tweenValue(&fx->fade)); } void kon_screenfxApply(const kon_screenfx_t *fx, kon_framebuffer_t *fb) { kon_screenfxApplyScene(fx, fb); kon_screenfxApplyOverlay(fx, fb); }