summaryrefslogtreecommitdiff
path: root/kon/konsofren.h
diff options
context:
space:
mode:
authorKonAki <konaki@anokonaki.org>2026-10-08 06:15:37 +0800
committerKonAki <konaki@anokonaki.org>2026-10-08 06:15:37 +0800
commitb784a525bfa07bd2da20ae51a1987b2e7c3cb74f (patch)
tree629dec25db236162735e51575a76307b0807dfed /kon/konsofren.h
parentbd1ead4dcd78b33cb5cc4333fd197f198d038284 (diff)
konsofren: sprite regions, rotated sprites, linear filtering
Diffstat (limited to 'kon/konsofren.h')
-rw-r--r--kon/konsofren.h247
1 files changed, 239 insertions, 8 deletions
diff --git a/kon/konsofren.h b/kon/konsofren.h
index 122efe6..c45091b 100644
--- a/kon/konsofren.h
+++ b/kon/konsofren.h
@@ -49,6 +49,16 @@ typedef enum kon_imageFormat {
konFormatBGRA8
} kon_imageFormat_t;
+typedef enum kon_filter {
+ KON_FILTER_NEAREST = 0,
+ KON_FILTER_LINEAR
+} kon_filter_t;
+
+typedef struct kon_rect { int x, y, w, h; } kon_rect_t;
+
+#define KON_RECT(x, y, w, h) ((kon_rect_t){(int)(x), (int)(y), (int)(w), (int)(h)})
+
+
/* game is the fixed-size framebuffer you draw into, screen always matches the window */
typedef struct kon_display {
@@ -59,6 +69,8 @@ typedef struct kon_display {
/* where the game image sits inside screen, kept up to date by the functions below */
int viewX, viewY, viewWidth, viewHeight;
int *columnMap;
+ int *linearMap; /* x0, x1 and weight for every view column, only built while the filter is linear */
+ kon_filter_t filter;
} kon_display_t;
typedef enum kon_projection {
@@ -104,6 +116,7 @@ typedef struct kon_vertex {
typedef struct kon_material {
const kon_image *texture;
uint32_t color;
+ kon_filter_t filter;
} kon_material_t;
/* one light from far away, like the sun, plus a base light that reaches everything */
@@ -139,6 +152,16 @@ void kon_freeImage(kon_image *image);
void kon_drawImage(kon_framebuffer_t *fb, int x, int y, int width, int height, kon_image *image);
+/* sprite sheets: draws the src rectangle of image at x, y with no scaling */
+void kon_drawImageRegion(kon_framebuffer_t *fb, int x, int y, const kon_image *image, kon_rect_t src);
+
+/* the rectangle of frame index in a sheet of equally sized frames, counted left to right then top to bottom */
+kon_rect_t kon_frameRect(const kon_image *image, int frameWidth, int frameHeight, int index);
+
+/* x, y is where the origin point (in src pixels, so w / 2, h / 2 is the centre) lands on fb.
+ angle is in radians, positive turns clockwise on screen. a negative scale flips */
+void kon_drawImageEx(kon_framebuffer_t *fb, const kon_image *image, kon_rect_t src, float x, float y, float originX, float originY, float scaleX, float scaleY, float angle, kon_filter_t filter);
+
/*** draw function declarations ***/
void kon_drawRectangle(kon_framebuffer_t *fb, int x, int y, int width, int height, uint32_t color);
@@ -157,6 +180,7 @@ void kon_freeDisplay(kon_display_t *display);
void kon_setGameResolution(kon_display_t *display, int gameWidth, int gameHeight);
void kon_setIntegerScaling(kon_display_t *display, int integerOnly);
void kon_resizeDisplay(kon_display_t *display, int windowWidth, int windowHeight);
+void kon_setDisplayFilter(kon_display_t *display, kon_filter_t filter);
/* scales game into screen, then blit display->screen to the window */
void kon_presentDisplay(kon_display_t *display);
@@ -224,6 +248,46 @@ static inline uint32_t kon_blendColor(uint32_t dst, uint32_t src) {
return ((uint32_t)dst_a << 24) | ((uint32_t)out_r << 16) | ((uint32_t)out_g << 8) | (uint32_t)out_b;
}
+static inline int kon_wrapInt_(int v, int n) {
+ v %= n;
+ return v < 0 ? v + n : v;
+}
+
+/* w is 0 to 255, red/blue and green/alpha are blended together in one multiply each */
+static inline uint32_t kon_lerpColor_(uint32_t a, uint32_t b, uint32_t w) {
+ uint32_t rb = ((a & 0x00FF00FFu) * (256u - w) + (b & 0x00FF00FFu) * w) >> 8;
+ uint32_t ga = ((a >> 8) & 0x00FF00FFu) * (256u - w) + ((b >> 8) & 0x00FF00FFu) * w;
+ return (rb & 0x00FF00FFu) | (ga & 0xFF00FF00u);
+}
+
+/* fx, fy are in pixels inside r, texel i covers i to i + 1. wrap repeats r, otherwise the edge pixels are held */
+static uint32_t kon_sampleLinear_(const kon_framebuffer_t *image, kon_rect_t r, float fx, float fy, int wrap) {
+ float px = fx - 0.5f, py = fy - 0.5f;
+ int x0 = (int)floorf(px), y0 = (int)floorf(py);
+ int x1 = x0 + 1, y1 = y0 + 1;
+ uint32_t wx = (uint32_t)((px - (float)x0) * 256.0f) & 0xFF;
+ uint32_t wy = (uint32_t)((py - (float)y0) * 256.0f) & 0xFF;
+
+ if (wrap) {
+ x0 = kon_wrapInt_(x0, r.w); x1 = kon_wrapInt_(x1, r.w);
+ y0 = kon_wrapInt_(y0, r.h); y1 = kon_wrapInt_(y1, r.h);
+ } else {
+ if (x0 < 0) x0 = 0;
+ if (y0 < 0) y0 = 0;
+ if (x1 < 0) x1 = 0;
+ if (y1 < 0) y1 = 0;
+ if (x0 > r.w - 1) x0 = r.w - 1;
+ if (x1 > r.w - 1) x1 = r.w - 1;
+ if (y0 > r.h - 1) y0 = r.h - 1;
+ if (y1 > r.h - 1) y1 = r.h - 1;
+ }
+
+ const uint32_t *row0 = &image->data[(size_t)(r.y + y0) * (size_t)image->width + (size_t)r.x];
+ const uint32_t *row1 = &image->data[(size_t)(r.y + y1) * (size_t)image->width + (size_t)r.x];
+
+ return kon_lerpColor_(kon_lerpColor_(row0[x0], row0[x1], wx), kon_lerpColor_(row1[x0], row1[x1], wx), wy);
+}
+
/* Cohen-Sutherland: clips the line to the framebuffer, returns 0 if nothing is visible */
static int kon_outCode_(double x, double y, int width, int height) {
int code = 0;
@@ -585,8 +649,139 @@ void kon_drawImage(kon_framebuffer_t *fb, int x, int y, int width, int height, k
}
}
+static int kon_clipRegion_(const kon_image *image, kon_rect_t *src) {
+ if (src->x < 0) { src->w += src->x; src->x = 0; }
+ if (src->y < 0) { src->h += src->y; src->y = 0; }
+ if (src->x + src->w > image->width) src->w = image->width - src->x;
+ if (src->y + src->h > image->height) src->h = image->height - src->y;
+ return src->w > 0 && src->h > 0;
+}
+
+void kon_drawImageRegion(kon_framebuffer_t *fb, int x, int y, const kon_image *image, kon_rect_t src) {
+ if (!fb || !image) return;
+ if (!kon_clipRegion_(image, &src)) return;
+
+ int startX = 0, startY = 0, endX = src.w, endY = src.h;
+ if (x < 0) startX = -x;
+ if (y < 0) startY = -y;
+ if (x + endX > fb->width) endX = fb->width - x;
+ if (y + endY > fb->height) endY = fb->height - y;
+
+ for (int iy = startY; iy < endY; iy++) {
+ const uint32_t *in = &image->data[(size_t)(src.y + iy) * (size_t)image->width + (size_t)src.x];
+ uint32_t *out = &fb->data[(size_t)(y + iy) * (size_t)fb->width + (size_t)x];
+
+ for (int ix = startX; ix < endX; ix++) {
+ uint32_t color = in[ix];
+ uint32_t alpha = color >> 24;
+
+ if (alpha == 0xFF) out[ix] = color;
+ else if (alpha != 0) out[ix] = kon_blendColor(out[ix], color);
+ }
+ }
+}
+
+kon_rect_t kon_frameRect(const kon_image *image, int frameWidth, int frameHeight, int index) {
+ kon_rect_t r = {0, 0, 0, 0};
+ if (!image || frameWidth <= 0 || frameHeight <= 0 || index < 0) return r;
+
+ int columns = image->width / frameWidth;
+ if (columns <= 0) return r;
+
+ r.x = (index % columns) * frameWidth;
+ r.y = (index / columns) * frameHeight;
+ r.w = frameWidth;
+ r.h = frameHeight;
+ return r;
+}
+
+void kon_drawImageEx(kon_framebuffer_t *fb, const kon_image *image, kon_rect_t src, float x, float y, float originX, float originY, float scaleX, float scaleY, float angle, kon_filter_t filter) {
+ if (!fb || !image) return;
+ if (!kon_clipRegion_(image, &src)) return;
+ if (scaleX == 0.0f || scaleY == 0.0f) return;
+
+ if (angle == 0.0f && scaleX == 1.0f && scaleY == 1.0f && filter == KON_FILTER_NEAREST) {
+ kon_drawImageRegion(fb, (int)floorf(x - originX + 0.5f), (int)floorf(y - originY + 0.5f), image, src);
+ return;
+ }
+
+ float c = cosf(angle), s = sinf(angle);
+
+ float cornerX[4], cornerY[4];
+ const float lx[4] = {0.0f, (float)src.w, (float)src.w, 0.0f};
+ const float ly[4] = {0.0f, 0.0f, (float)src.h, (float)src.h};
+ float minX = 0.0f, maxX = 0.0f, minY = 0.0f, maxY = 0.0f;
+
+ for (int i = 0; i < 4; i++) {
+ float ox = (lx[i] - originX) * scaleX;
+ float oy = (ly[i] - originY) * scaleY;
+ cornerX[i] = x + c * ox - s * oy;
+ cornerY[i] = y + s * ox + c * oy;
+ if (i == 0 || cornerX[i] < minX) minX = cornerX[i];
+ if (i == 0 || cornerX[i] > maxX) maxX = cornerX[i];
+ if (i == 0 || cornerY[i] < minY) minY = cornerY[i];
+ if (i == 0 || cornerY[i] > maxY) maxY = cornerY[i];
+ }
+
+ int x0 = (int)floorf(minX), x1 = (int)ceilf(maxX);
+ int y0 = (int)floorf(minY), y1 = (int)ceilf(maxY);
+ if (x0 < 0) x0 = 0;
+ if (y0 < 0) y0 = 0;
+ if (x1 > fb->width) x1 = fb->width;
+ if (y1 > fb->height) y1 = fb->height;
+
+ float stepXx = c / scaleX, stepXy = -s / scaleY;
+
+ for (int py = y0; py < y1; py++) {
+ float dx = (float)x0 + 0.5f - x, dy = (float)py + 0.5f - y;
+ float sx = (c * dx + s * dy) / scaleX + originX;
+ float sy = (-s * dx + c * dy) / scaleY + originY;
+ uint32_t *out = &fb->data[(size_t)py * (size_t)fb->width];
+
+ for (int px = x0; px < x1; px++, sx += stepXx, sy += stepXy) {
+ if (sx < 0.0f || sy < 0.0f || sx >= (float)src.w || sy >= (float)src.h) continue;
+
+ uint32_t color;
+ if (filter == KON_FILTER_LINEAR) {
+ color = kon_sampleLinear_(image, src, sx, sy, 0);
+ } else {
+ color = image->data[(size_t)(src.y + (int)sy) * (size_t)image->width + (size_t)(src.x + (int)sx)];
+ }
+
+ uint32_t alpha = color >> 24;
+ if (alpha == 0xFF) out[px] = color;
+ else if (alpha != 0) out[px] = kon_blendColor(out[px], color);
+ }
+ }
+}
+
/*** display implementation ***/
+static void kon_buildLinearMap_(kon_display_t *display) {
+ if (display->filter != KON_FILTER_LINEAR || display->viewWidth <= 0) return;
+
+ int *map = realloc(display->linearMap, (size_t)display->viewWidth * 3 * sizeof(int));
+ if (!map) return;
+ display->linearMap = map;
+
+ int gameWidth = display->game->width;
+ for (int x = 0; x < display->viewWidth; x++) {
+ float fx = ((float)x + 0.5f) * (float)gameWidth / (float)display->viewWidth - 0.5f;
+ int x0 = (int)floorf(fx);
+ int weight = (int)((fx - (float)x0) * 256.0f) & 0xFF;
+ int x1 = x0 + 1;
+
+ if (x0 < 0) x0 = 0;
+ if (x1 < 0) x1 = 0;
+ if (x0 > gameWidth - 1) x0 = gameWidth - 1;
+ if (x1 > gameWidth - 1) x1 = gameWidth - 1;
+
+ map[x * 3 + 0] = x0;
+ map[x * 3 + 1] = x1;
+ map[x * 3 + 2] = weight;
+ }
+}
+
static void kon_updateView_(kon_display_t *display) {
int gameWidth = display->game->width, gameHeight = display->game->height;
int windowWidth = display->screen->width, windowHeight = display->screen->height;
@@ -622,6 +817,8 @@ static void kon_updateView_(kon_display_t *display) {
for (int x = 0; x < display->viewWidth; x++) {
map[x] = (int)((int64_t)x * gameWidth / display->viewWidth);
}
+
+ kon_buildLinearMap_(display);
}
kon_display_t *kon_createDisplay(int gameWidth, int gameHeight, int windowWidth, int windowHeight) {
@@ -648,6 +845,7 @@ void kon_freeDisplay(kon_display_t *display) {
kon_freeFramebuffer(display->game);
kon_freeFramebuffer(display->screen);
free(display->columnMap);
+ free(display->linearMap);
free(display);
}
@@ -672,6 +870,12 @@ void kon_resizeDisplay(kon_display_t *display, int windowWidth, int windowHeight
kon_updateView_(display);
}
+void kon_setDisplayFilter(kon_display_t *display, kon_filter_t filter) {
+ if (!display) return;
+ display->filter = filter;
+ kon_buildLinearMap_(display);
+}
+
void kon_presentDisplay(kon_display_t *display) {
if (!display) return;
@@ -688,6 +892,28 @@ void kon_presentDisplay(kon_display_t *display) {
continue;
}
+ if (display->filter == KON_FILTER_LINEAR && display->linearMap) {
+ float fy = ((float)(y - display->viewY) + 0.5f) * (float)game->height / (float)display->viewHeight - 0.5f;
+ int y0 = (int)floorf(fy), y1 = y0 + 1;
+ uint32_t wy = (uint32_t)((fy - (float)y0) * 256.0f) & 0xFF;
+ if (y0 < 0) y0 = 0;
+ if (y1 < 0) y1 = 0;
+ if (y0 > game->height - 1) y0 = game->height - 1;
+ if (y1 > game->height - 1) y1 = game->height - 1;
+
+ const uint32_t *row0 = &game->data[(size_t)y0 * (size_t)game->width];
+ const uint32_t *row1 = &game->data[(size_t)y1 * (size_t)game->width];
+ const int *lin = display->linearMap;
+
+ for (int x = 0; x < display->viewX; x++) row[x] = barColor;
+ for (int x = 0; x < display->viewWidth; x++, lin += 3) {
+ uint32_t wx = (uint32_t)lin[2];
+ row[display->viewX + x] = kon_lerpColor_(kon_lerpColor_(row0[lin[0]], row0[lin[1]], wx), kon_lerpColor_(row1[lin[0]], row1[lin[1]], wx), wy);
+ }
+ for (int x = rightX; x < screen->width; x++) row[x] = barColor;
+ continue;
+ }
+
int srcY = (int)((int64_t)(y - display->viewY) * game->height / display->viewHeight);
const uint32_t *srcRow = &game->data[(size_t)srcY * (size_t)game->width];
@@ -818,12 +1044,12 @@ void kon_drawWireMesh(kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_mat4
}
kon_material_t kon_materialColor(uint32_t color) {
- kon_material_t material = { NULL, color };
+ kon_material_t material = { NULL, color, KON_FILTER_NEAREST };
return material;
}
kon_material_t kon_materialTexture(const kon_image *texture) {
- kon_material_t material = { texture, 0xFFFFFFFF };
+ kon_material_t material = { texture, 0xFFFFFFFF, KON_FILTER_NEAREST };
return material;
}
@@ -992,12 +1218,17 @@ static void kon_rasterizeTriangle_(kon_framebuffer_t *fb, const kon_rasterVertex
u -= floor(u);
v -= floor(v);
- int tx = (int)(u * texture->width);
- int ty = (int)(v * texture->height);
- if (tx > texture->width - 1) tx = texture->width - 1;
- if (ty > texture->height - 1) ty = texture->height - 1;
-
- texel = texture->data[(size_t)ty * (size_t)texture->width + (size_t)tx];
+ if (material->filter == KON_FILTER_LINEAR) {
+ texel = kon_sampleLinear_(texture, KON_RECT(0, 0, texture->width, texture->height),
+ (float)(u * texture->width), (float)(v * texture->height), 1);
+ } else {
+ int tx = (int)(u * texture->width);
+ int ty = (int)(v * texture->height);
+ if (tx > texture->width - 1) tx = texture->width - 1;
+ if (ty > texture->height - 1) ty = texture->height - 1;
+
+ texel = texture->data[(size_t)ty * (size_t)texture->width + (size_t)tx];
+ }
}
if (light) {