diff options
| author | KonAki <konaki@anokonaki.org> | 2026-10-08 06:15:37 +0800 |
|---|---|---|
| committer | KonAki <konaki@anokonaki.org> | 2026-10-08 06:15:37 +0800 |
| commit | b784a525bfa07bd2da20ae51a1987b2e7c3cb74f (patch) | |
| tree | 629dec25db236162735e51575a76307b0807dfed /kon/konsofren.h | |
| parent | bd1ead4dcd78b33cb5cc4333fd197f198d038284 (diff) | |
konsofren: sprite regions, rotated sprites, linear filtering
Diffstat (limited to 'kon/konsofren.h')
| -rw-r--r-- | kon/konsofren.h | 247 |
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) { |
