Builds a browser-playable .html/.js/.wasm/.data via `./build-web.sh` (no
new CMake top-level target — Emscripten is detected at configure time and
takes the existing kobodl through a web-flavored pipeline).
Key pieces:
- CMakeLists.txt: route SDL3 through `--use-port=sdl3`; substitute
`--use-port=libpng` for the missing sdl3_image port (Emscripten 5.0.7
ships only sdl3 + sdl3_ttf). pthread (atomics+bulk-memory) lifted to
kobo_deps so every TU agrees with wasm-ld's --shared-memory. Link with
-sASYNCIFY (avoids rewriting kobo's blocking main loop around
emscripten_set_main_loop), -sALLOW_MEMORY_GROWTH/-sMAXIMUM_MEMORY=1gb,
-sPTHREAD_POOL_SIZE=4, -lidbfs.js, and --preload-file data@/data.
- graphics/img_compat.{h,c}: libpng-backed IMG_Load shim, used in
sprite.c / gfxengine.cpp / filters.c via #include "img_compat.h".
Native builds passthrough to <SDL3_image/SDL_image.h> unchanged.
- web/shell.html: HTML wrapper that mounts /home as IDBFS for persistent
scores/config. Startup gated on addRunDependency until syncfs(true)
finishes, then /home/scores is mkdir'd post-load (the game's addPlayer
doesn't auto-create the score dir; see TODO in score.cpp).
beforeunload + visibilitychange flushes /home back to IndexedDB.
- build-web.sh: emcmake wrapper + a static server that sets the
COOP/COEP headers SharedArrayBuffer/pthreads require.
Source fixes needed to build under Emscripten's libc/libcxx:
- Renamed enemy_kind `pipe2` -> `pipe2k` (collided with libc pipe2(2)
always declared in Emscripten's <unistd.h>).
- graphics/toolkit.cpp: added missing <stdio.h> for snprintf (Emscripten's
libcxx headers don't pull it in transitively).
- states.cpp: gated the "Quit Kobo Deluxe" main-menu button on
!__EMSCRIPTEN__; exit() on the web leaves a dead canvas.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
1141 lines
25 KiB
C
1141 lines
25 KiB
C
/*(LGPL)
|
|
----------------------------------------------------------------------
|
|
filters.c - Filters for the sprite manager
|
|
----------------------------------------------------------------------
|
|
* Copyright (C) 2001, 2003, 2006, 2007 David Olofson
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include "logger.h"
|
|
#include "sdl_compat.h"
|
|
#include "img_compat.h"
|
|
#include "sprite.h"
|
|
#include "filters.h"
|
|
|
|
|
|
/*--------------------------------------------------
|
|
RNG
|
|
--------------------------------------------------*/
|
|
|
|
static unsigned int rnd = 16576;
|
|
|
|
//Resets the noise generator
|
|
static void noise_reset(int seed)
|
|
{
|
|
rnd = 16576 + seed;
|
|
}
|
|
|
|
//Returns a pseudo random number in the range [0, 65535]
|
|
static inline int noise(void)
|
|
{
|
|
rnd *= 1566083941UL;
|
|
rnd++;
|
|
rnd &= 0xffffffffUL; /* NOP on 32 bit machines */
|
|
return (int)(rnd * (rnd >> 16) >> 16);
|
|
}
|
|
|
|
|
|
/*--------------------------------------------------
|
|
Pixel access ops for 32 bit RGBA
|
|
--------------------------------------------------*/
|
|
|
|
static pix_t getpix32_empty = {0x7f, 0x7f, 0x7f, 0x7f};
|
|
|
|
static inline pix_t getpix32(SDL_Surface *s, int x, int y)
|
|
{
|
|
pix_t *p;
|
|
if((x < 0) || (x >= s->w) || (y < 0) || (y >= s->h))
|
|
return getpix32_empty;
|
|
|
|
p = (pix_t *)((char *)s->pixels + y * s->pitch);
|
|
return p[x];
|
|
}
|
|
|
|
|
|
/* Clamping version */
|
|
static inline pix_t getpix32c(SDL_Surface *s, int x, int y)
|
|
{
|
|
pix_t *p;
|
|
if(x < 0)
|
|
x = 0;
|
|
else if(x >= s->w-1)
|
|
x = s->w-1;
|
|
if(y < 0)
|
|
y = 0;
|
|
else if(y >= s->h-1)
|
|
y = s->h-1;
|
|
p = (pix_t *)((char *)s->pixels + y * s->pitch);
|
|
return p[x];
|
|
}
|
|
|
|
#define GETPIXI(gp) \
|
|
{ \
|
|
int c0x, c0y, c1x, c1y; \
|
|
int c[4]; \
|
|
pix_t e = {0, 0, 0, 0}; \
|
|
pix_t p[4], r; \
|
|
getpix32_empty = e; \
|
|
\
|
|
/* Calculate filter core */ \
|
|
x -= 8; \
|
|
y -= 8; \
|
|
c1x = x & 0xf; \
|
|
c1y = y & 0xf; \
|
|
c0x = 16 - c1x; \
|
|
c0y = 16 - c1y; \
|
|
c[0] = c0x * c0y; \
|
|
c[1] = c1x * c0y; \
|
|
c[2] = c0x * c1y; \
|
|
c[3] = c1x * c1y; \
|
|
\
|
|
/* Grab input pixels */ \
|
|
x >>= 4; \
|
|
y >>= 4; \
|
|
p[0] = gp(s, x, y); \
|
|
p[1] = gp(s, x+1, y); \
|
|
p[2] = gp(s, x, y+1); \
|
|
p[3] = gp(s, x+1, y+1); \
|
|
\
|
|
/* Interpolate... */ \
|
|
r.r = (p[0].r*c[0] + p[1].r*c[1] + p[2].r*c[2] + p[3].r*c[3])>>8; \
|
|
r.g = (p[0].g*c[0] + p[1].g*c[1] + p[2].g*c[2] + p[3].g*c[3])>>8; \
|
|
r.b = (p[0].b*c[0] + p[1].b*c[1] + p[2].b*c[2] + p[3].b*c[3])>>8; \
|
|
r.a = (p[0].a*c[0] + p[1].a*c[1] + p[2].a*c[2] + p[3].a*c[3])>>8; \
|
|
return r; \
|
|
}
|
|
|
|
/* Interpolated; 28:4 fixed point coords. */
|
|
static inline pix_t getpix32i(SDL_Surface *s, int x, int y)
|
|
{
|
|
GETPIXI(getpix32)
|
|
}
|
|
|
|
|
|
/* Clamping version */
|
|
static inline pix_t getpix32ic(SDL_Surface *s, int x, int y)
|
|
{
|
|
GETPIXI(getpix32c)
|
|
}
|
|
|
|
|
|
static inline pix_t *pix32(SDL_Surface *s, int x, int y)
|
|
{
|
|
static pix_t dummy = {0x7f, 0x7f, 0x7f, 0x7f};
|
|
pix_t *p;
|
|
if((x < 0) || (x >= s->w) || (y < 0) || (y >= s->h))
|
|
return &dummy;
|
|
p = (pix_t *)((char *)s->pixels + y * s->pitch);
|
|
return p + x;
|
|
}
|
|
|
|
|
|
static inline void setpix32(SDL_Surface *s, int x, int y, pix_t pix)
|
|
{
|
|
pix_t *p;
|
|
if((x < 0) || (x >= s->w) || (y < 0) || (y >= s->h))
|
|
return;
|
|
p = (pix_t *)((char *)s->pixels + y * s->pitch);
|
|
p[x] = pix;
|
|
}
|
|
|
|
|
|
/*--------------------------------------------------
|
|
Non-clipping versions of everything
|
|
(No clamping here, obviously...!)
|
|
--------------------------------------------------*/
|
|
|
|
static inline pix_t getpix32_nc(SDL_Surface *s, int x, int y)
|
|
{
|
|
pix_t *p = (pix_t *)((char *)s->pixels + y * s->pitch);
|
|
#ifdef DEBUG
|
|
if((x < 0) || (x >= s->w) || (y < 0) || (y >= s->h))
|
|
{
|
|
log_printf(ELOG, "Clip in getpix32_nc()!"
|
|
" x = %d, y = %d\n", x, y);
|
|
*((char *)NULL) = 0;
|
|
return getpix32_empty;
|
|
}
|
|
#endif
|
|
return p[x];
|
|
}
|
|
|
|
|
|
/* Interpolated; 28:4 fixed point coords. */
|
|
static inline pix_t getpix32i_nc(SDL_Surface *s, int x, int y)
|
|
{
|
|
#ifdef DEBUG
|
|
if((x < 0) || (x >= s->w << 4) || (y < 0) || (y >= s->h << 4))
|
|
{
|
|
log_printf(ELOG, "Clip in getpix32i_nc()!"
|
|
" x = %d, y = %d\n", x>>4, y>>4);
|
|
*((char *)NULL) = 0;
|
|
// return getpix32_empty;
|
|
}
|
|
GETPIXI(getpix32)
|
|
#else
|
|
GETPIXI(getpix32_nc)
|
|
#endif
|
|
}
|
|
#undef GETPIXI
|
|
|
|
static inline void setpix32_nc(SDL_Surface *s, int x, int y, pix_t pix)
|
|
{
|
|
pix_t *p = (pix_t *)((char *)s->pixels + y * s->pitch);
|
|
#ifdef DEBUG
|
|
if((x < 0) || (x >= s->w) || (y < 0) || (y >= s->h))
|
|
{
|
|
log_printf(ELOG, "Clip in setpix32_nc()!"
|
|
" x = %d, y = %d\n", x, y);
|
|
*((char *)NULL) = 0;
|
|
// return;
|
|
}
|
|
#endif
|
|
p[x] = pix;
|
|
}
|
|
|
|
|
|
static inline pix_t *pix32_nc(SDL_Surface *s, int x, int y)
|
|
{
|
|
pix_t *p = (pix_t *)((char *)s->pixels + y * s->pitch);
|
|
#ifdef DEBUG
|
|
// static pix_t trash;
|
|
if((x < 0) || (x >= s->w) || (y < 0) || (y >= s->h))
|
|
{
|
|
log_printf(ELOG, "Clip in pix32_nc()!"
|
|
" x = %d, y = %d\n", x, y);
|
|
*((char *)NULL) = 0;
|
|
// return &trash;
|
|
}
|
|
#endif
|
|
return p + x;
|
|
}
|
|
|
|
|
|
|
|
/*--------------------------------------------------
|
|
The filters
|
|
--------------------------------------------------*/
|
|
|
|
int s_filter_rgba8(s_bank_t *b, unsigned first, unsigned frames,
|
|
s_filter_args_t *args)
|
|
{
|
|
unsigned i;
|
|
for(i = 0; i < frames; ++i)
|
|
{
|
|
SDL_Surface *tmp;
|
|
s_sprite_t *s = s_get_sprite_b(b, first+i);
|
|
if(!s)
|
|
continue;
|
|
tmp = SDL_ConvertSurface(s->surface, SDL_PIXELFORMAT_RGBA32);
|
|
if(!tmp)
|
|
return -1;
|
|
|
|
SDL_DestroySurface(s->surface);
|
|
s->surface = tmp;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int s_filter_dither(s_bank_t *b, unsigned first, unsigned frames,
|
|
s_filter_args_t *args)
|
|
{
|
|
int x, y;
|
|
unsigned i;
|
|
int ar, ag, ab;
|
|
if(!args->x && !args->r && !args->g && !args->b)
|
|
return 0;
|
|
noise_reset(first);
|
|
for(i = 0; i < frames; ++i)
|
|
{
|
|
s_sprite_t *s = s_get_sprite_b(b, first+i);
|
|
if(!s)
|
|
continue;
|
|
ar = args->r;
|
|
ag = args->g;
|
|
ab = args->b;
|
|
if(args->x)
|
|
switch (s_blitmode)
|
|
{
|
|
case S_BLITMODE_AUTO:
|
|
if(SDL_ISPIXELFORMAT_ALPHA(s->surface->format))
|
|
ar = ag = ab = 8;
|
|
break;
|
|
case S_BLITMODE_OPAQUE:
|
|
break;
|
|
case S_BLITMODE_COLORKEY:
|
|
case S_BLITMODE_ALPHA:
|
|
ar = ag = ab = 8;
|
|
break;
|
|
}
|
|
switch(args->y)
|
|
{
|
|
default:
|
|
case 0:
|
|
case 1:
|
|
ar >>= 1;
|
|
ag >>= 1;
|
|
ab >>= 1;
|
|
case 2:
|
|
break;
|
|
}
|
|
for(y = (args->flags & SF_CLAMP_SFONT) ? 1 : 0; y < b->h; ++y)
|
|
{
|
|
pix_t *p = (pix_t *)((char *)s->surface->pixels +
|
|
y * s->surface->pitch);
|
|
switch(args->y)
|
|
{
|
|
default:
|
|
case 0:
|
|
/* 2x2 filter */
|
|
for(x = 0; x < b->w; ++x)
|
|
{
|
|
int r, g, b;
|
|
pix_t *pix = p + x;
|
|
if((x^y)&1)
|
|
{
|
|
r = pix->r + ar;
|
|
g = pix->g + ag;
|
|
b = pix->b + ab;
|
|
if(r>255)
|
|
r = 255;
|
|
if(g>255)
|
|
g = 255;
|
|
if(b>255)
|
|
b = 255;
|
|
}
|
|
else
|
|
{
|
|
r = pix->r - ar;
|
|
g = pix->g - ag;
|
|
b = pix->b - ab;
|
|
if(r<0)
|
|
r = 0;
|
|
if(g<0)
|
|
g = 0;
|
|
if(b<0)
|
|
b = 0;
|
|
}
|
|
pix->r = r;
|
|
pix->g = g;
|
|
pix->b = b;
|
|
}
|
|
break;
|
|
case 1:
|
|
/* 4x4 filter */
|
|
for(x = 0; x < b->w; ++x)
|
|
{
|
|
int dr, dg, db;
|
|
int r, g, b;
|
|
pix_t *pix = p + x;
|
|
r = pix->r;
|
|
g = pix->g;
|
|
b = pix->b;
|
|
if((x^y) & 1)
|
|
{
|
|
dr = ar;
|
|
dg = ag;
|
|
db = ab;
|
|
}
|
|
else
|
|
{
|
|
dr = -ar;
|
|
dg = -ag;
|
|
db = -ab;
|
|
}
|
|
if(y & 1)
|
|
{
|
|
dr += ar >> 1;
|
|
dg += ag >> 1;
|
|
db += ab >> 1;
|
|
}
|
|
else
|
|
{
|
|
dr -= ar >> 1;
|
|
dg -= ag >> 1;
|
|
db -= ab >> 1;
|
|
}
|
|
r += dr;
|
|
g += dg;
|
|
b += db;
|
|
|
|
if(r>255)
|
|
r = 255;
|
|
else if(r<0)
|
|
r = 0;
|
|
|
|
if(g>255)
|
|
g = 255;
|
|
else if(g<0)
|
|
g = 0;
|
|
|
|
if(b>255)
|
|
b = 255;
|
|
else if(b<0)
|
|
b = 0;
|
|
|
|
pix->r = r;
|
|
pix->g = g;
|
|
pix->b = b;
|
|
}
|
|
break;
|
|
case 2:
|
|
/* Random */
|
|
for(x = 0; x < b->w; ++x)
|
|
{
|
|
int r, g, b, z;
|
|
pix_t *pix = p + x;
|
|
z = noise();
|
|
r = pix->r - ar + (((ar << 1) - 1) & z);
|
|
g = pix->g - ag + (((ag << 1) - 1) & z);
|
|
b = pix->b - ab + (((ab << 1) - 1) & z);
|
|
|
|
if(r>255)
|
|
r = 255;
|
|
else if(r<0)
|
|
r = 0;
|
|
|
|
if(g>255)
|
|
g = 255;
|
|
else if(g<0)
|
|
g = 0;
|
|
|
|
if(b>255)
|
|
b = 255;
|
|
else if(b<0)
|
|
b = 0;
|
|
|
|
pix->r = r;
|
|
pix->g = g;
|
|
pix->b = b;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Ugly hack to get around the problems caused by
|
|
* SDL_SetColorKey() ignoring the alpha channel.
|
|
* Basically just sets the blue LSB of any pixels
|
|
* that match the colorkey, but have non-transparent
|
|
* alpha.
|
|
*/
|
|
static void tweak_ck(SDL_Surface *s)
|
|
{
|
|
int x, y;
|
|
const SDL_PixelFormatDetails *fmt = SDL_GetPixelFormatDetails(s->format);
|
|
if(!fmt || fmt->bits_per_pixel != 32)
|
|
return;
|
|
for(y = 0; y < s->h; ++y)
|
|
{
|
|
pix_t *p = (pix_t *)((char *)s->pixels + y * s->pitch);
|
|
for(x = 0; x < s->w; ++x)
|
|
{
|
|
pix_t *pix = p + x;
|
|
if(!pix->r && !pix->g && !pix->b)
|
|
if(pix->a)
|
|
pix->b |= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int s_filter_displayformat(s_bank_t * b, unsigned first, unsigned frames,
|
|
s_filter_args_t * args)
|
|
{
|
|
unsigned i;
|
|
for(i = 0; i < frames; ++i)
|
|
{
|
|
SDL_Surface *tmp;
|
|
s_sprite_t *s = s_get_sprite_b(b, first + i);
|
|
if(!s)
|
|
continue;
|
|
|
|
switch(s_blitmode)
|
|
{
|
|
case S_BLITMODE_AUTO:
|
|
if(SDL_ISPIXELFORMAT_ALPHA(s->surface->format))
|
|
{
|
|
SDL_SetSurfaceAlphaMod(s->surface,
|
|
SDL_ALPHA_OPAQUE);
|
|
SDL_SetSurfaceBlendMode(s->surface,
|
|
SDL_BLENDMODE_BLEND);
|
|
}
|
|
else
|
|
SDL_SetSurfaceColorKey(s->surface, false, 0);
|
|
break;
|
|
case S_BLITMODE_OPAQUE:
|
|
SDL_SetSurfaceColorKey(s->surface, false, 0);
|
|
break;
|
|
case S_BLITMODE_COLORKEY:
|
|
SDL_SetSurfaceColorKey(s->surface, true,
|
|
SDL_MapSurfaceRGB(s->surface,
|
|
s_colorkey.r,
|
|
s_colorkey.g,
|
|
s_colorkey.b));
|
|
break;
|
|
case S_BLITMODE_ALPHA:
|
|
SDL_SetSurfaceAlphaMod(s->surface, s_alpha);
|
|
SDL_SetSurfaceBlendMode(s->surface,
|
|
SDL_BLENDMODE_BLEND);
|
|
break;
|
|
}
|
|
|
|
// SDL 3 dropped SDL_DisplayFormat. We always convert sprites
|
|
// to the back-buffer format (ARGB8888); see gfxengine.cpp.
|
|
if(args->x)
|
|
{
|
|
if(SDL_ISPIXELFORMAT_ALPHA(s->surface->format))
|
|
tweak_ck(s->surface);
|
|
tmp = SDL_ConvertSurface(s->surface,
|
|
SDL_PIXELFORMAT_ARGB8888);
|
|
if(tmp && SDL_ISPIXELFORMAT_ALPHA(s->surface->format))
|
|
SDL_SetSurfaceColorKey(tmp, true,
|
|
SDL_MapSurfaceRGB(tmp, 0,0,0));
|
|
}
|
|
else
|
|
{
|
|
tmp = SDL_ConvertSurface(s->surface,
|
|
SDL_PIXELFORMAT_ARGB8888);
|
|
}
|
|
if(!tmp)
|
|
return -1;
|
|
|
|
SDL_DestroySurface(s->surface);
|
|
s->surface = tmp;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
typedef struct
|
|
{
|
|
SDL_Surface *src; /* Source surface */
|
|
SDL_Surface *dst; /* Destination surface */
|
|
int scx, scy; /* Scale (fixp 16:16) */
|
|
int start_sy, start_sx; /* Src start pos (fixp 16:16) */
|
|
int start_x, start_y; /* Dst start pos (pixels) */
|
|
int max_x, max_y; /* Dst end pos (pixels) */
|
|
} scale_params_t;
|
|
|
|
|
|
static inline void select_rect(scale_params_t *p, scale_params_t *orig,
|
|
int x, int y, int w, int h)
|
|
{
|
|
p->start_sx = orig->start_sx + x * orig->scx;
|
|
p->start_sy = orig->start_sy + y * orig->scy;
|
|
p->start_x = orig->start_x + x;
|
|
p->start_y = orig->start_y + y;
|
|
p->max_x = p->start_x + w;
|
|
p->max_y = p->start_y + h;
|
|
}
|
|
|
|
/* Optimization tool; use the slow clipping funcs only around the edges! */
|
|
static inline void do_scale(scale_params_t *p,
|
|
void (*border)(scale_params_t *p),
|
|
void (*body)(scale_params_t *p))
|
|
{
|
|
/* Copy the parameters */
|
|
scale_params_t lp = *p;
|
|
int w = p->max_x - p->start_x;
|
|
int h = p->max_y - p->start_y;
|
|
int bx = (65535 + p->scx) / p->scx;
|
|
int by = (65535 + p->scy) / p->scy;
|
|
if(bx * 2 >= w || by * 2 >= h)
|
|
{
|
|
/* Area too small - body size would be zero or negative! */
|
|
select_rect(&lp, p, 0, 0, w, h);
|
|
border(&lp);
|
|
return;
|
|
}
|
|
|
|
/* Top border */
|
|
select_rect(&lp, p, 0, 0, w, by);
|
|
border(&lp);
|
|
|
|
/* Bottom border */
|
|
select_rect(&lp, p, 0, h - by, w, by);
|
|
border(&lp);
|
|
|
|
/* Left border */
|
|
select_rect(&lp, p, 0, by, bx, h - 2 * by);
|
|
border(&lp);
|
|
|
|
/* Right border */
|
|
select_rect(&lp, p, w - bx, by, bx, h - 2 * by);
|
|
border(&lp);
|
|
|
|
/* Body */
|
|
select_rect(&lp, p, bx, by, w - 2 * bx, h - 2 * by);
|
|
body(&lp);
|
|
}
|
|
|
|
|
|
/*
|
|
* Ugly but fast "nearest pixel" scaling
|
|
*/
|
|
|
|
static void scale_nearest(scale_params_t *p)
|
|
{
|
|
int x, y, sx, sy;
|
|
for(y = p->start_y, sy = p->start_sy; y < p->max_y; ++y, sy += p->scy)
|
|
{
|
|
pix_t *pix = (pix_t *)((char *)p->dst->pixels +
|
|
y * p->dst->pitch);
|
|
for(x = p->start_x, sx = p->start_sx; x < p->max_x;
|
|
++x, sx += p->scx)
|
|
pix[x] = getpix32_nc(p->src, sx >> 16, sy >> 16);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Bilinear scaling
|
|
*/
|
|
|
|
static void do_scale_bilinear_noclip(scale_params_t *p)
|
|
{
|
|
int x, y, sx, sy;
|
|
for(y = p->start_y, sy = p->start_sy; y < p->max_y; ++y, sy += p->scy)
|
|
{
|
|
pix_t *pix = (pix_t *)((char *)p->dst->pixels +
|
|
y * p->dst->pitch);
|
|
for(x = p->start_x, sx = p->start_sx; x < p->max_x;
|
|
++x, sx += p->scx)
|
|
pix[x] = getpix32i_nc(p->src, sx >> 12, sy >> 12);
|
|
}
|
|
}
|
|
|
|
static void do_scale_bilinear_clip(scale_params_t *p)
|
|
{
|
|
int x, y, sx, sy;
|
|
for(y = p->start_y, sy = p->start_sy; y < p->max_y; ++y, sy += p->scy)
|
|
{
|
|
pix_t *pix = (pix_t *)((char *)p->dst->pixels +
|
|
y * p->dst->pitch);
|
|
for(x = p->start_x, sx = p->start_sx; x < p->max_x;
|
|
++x, sx += p->scx)
|
|
pix[x] = getpix32i(p->src, sx >> 12, sy >> 12);
|
|
}
|
|
}
|
|
|
|
static void do_scale_bilinear_clamp(scale_params_t *p)
|
|
{
|
|
int x, y, sx, sy;
|
|
for(y = p->start_y, sy = p->start_sy; y < p->max_y; ++y, sy += p->scy)
|
|
{
|
|
pix_t *pix = (pix_t *)((char *)p->dst->pixels +
|
|
y * p->dst->pitch);
|
|
for(x = p->start_x, sx = p->start_sx; x < p->max_x;
|
|
++x, sx += p->scx)
|
|
pix[x] = getpix32ic(p->src, sx >> 12, sy >> 12);
|
|
}
|
|
}
|
|
|
|
static void scale_bilinear(scale_params_t *p)
|
|
{
|
|
do_scale(p, do_scale_bilinear_clip, do_scale_bilinear_noclip);
|
|
}
|
|
|
|
static void scale_bilinear_clamp(scale_params_t *p)
|
|
{
|
|
do_scale(p, do_scale_bilinear_clamp, do_scale_bilinear_noclip);
|
|
}
|
|
|
|
|
|
/*
|
|
* Scale2x scaling
|
|
*
|
|
* This one's cool for 16 color graphics and the like, but
|
|
* even with the fuzziness factor, it doesn't work well
|
|
* with 24 bit data, or even 256 color graphics.
|
|
*/
|
|
#define CDIFF(x,y) (((x).r^(y).r) | ((x).g^(y).g) | ((x).b^(y).b) | ((x).a^(y).a))
|
|
#define SCALE2X(gp, sp) \
|
|
{ \
|
|
int DB, BF, DH, FH; \
|
|
pix_t B ; \
|
|
pix_t D, E, F; \
|
|
pix_t H ; \
|
|
pix_t E0, E1; \
|
|
pix_t E2, E3; \
|
|
B = gp(p->src, sx, sy-1); \
|
|
D = gp(p->src, sx-1, sy); \
|
|
E = gp(p->src, sx, sy); \
|
|
F = gp(p->src, sx+1, sy); \
|
|
H = gp(p->src, sx, sy+1); \
|
|
DB = CDIFF(D, B) & 0xc0; \
|
|
BF = CDIFF(B, F) & 0xc0; \
|
|
DH = CDIFF(D, H) & 0xc0; \
|
|
FH = CDIFF(F, H) & 0xc0; \
|
|
E0 = (!DB && BF && DH) ? D : E; \
|
|
E1 = (!BF && DB && FH) ? F : E; \
|
|
E2 = (!DH && DB && FH) ? D : E; \
|
|
E3 = (!FH && DH && BF) ? F : E; \
|
|
sp(p->dst, x, y, E0); \
|
|
sp(p->dst, x+1, y, E1); \
|
|
sp(p->dst, x, y+1, E2); \
|
|
sp(p->dst, x+1, y+1, E3); \
|
|
}
|
|
static void do_scale_2x_noclip(scale_params_t *p)
|
|
{
|
|
int x, y, sx, sy;
|
|
for(y = p->start_y, sy = p->start_sy >> 16; y < p->max_y; y += 2, ++sy)
|
|
for(x = p->start_x, sx = p->start_sx >> 16; x < p->max_x;
|
|
x += 2, ++sx)
|
|
SCALE2X(getpix32_nc, setpix32_nc);
|
|
}
|
|
|
|
static void do_scale_2x_clip(scale_params_t *p)
|
|
{
|
|
int x, y, sx, sy;
|
|
for(y = p->start_y, sy = p->start_sy >> 16; y < p->max_y; y += 2, ++sy)
|
|
for(x = p->start_x, sx = p->start_sx >> 16; x < p->max_x;
|
|
x += 2, ++sx)
|
|
SCALE2X(getpix32, setpix32);
|
|
}
|
|
|
|
static void do_scale_2x_clamp(scale_params_t *p)
|
|
{
|
|
int x, y, sx, sy;
|
|
for(y = p->start_y, sy = p->start_sy >> 16; y < p->max_y; y += 2, ++sy)
|
|
for(x = p->start_x, sx = p->start_sx >> 16; x < p->max_x;
|
|
x += 2, ++sx)
|
|
SCALE2X(getpix32c, setpix32);
|
|
}
|
|
#undef CDIFF
|
|
#undef SCALE2X
|
|
|
|
static void scale_2x(scale_params_t *p)
|
|
{
|
|
do_scale(p, do_scale_2x_clip, do_scale_2x_noclip);
|
|
}
|
|
|
|
static void scale_2x_clamp(scale_params_t *p)
|
|
{
|
|
do_scale(p, do_scale_2x_clamp, do_scale_2x_noclip);
|
|
}
|
|
|
|
|
|
/*
|
|
* Diamond2x scaling
|
|
*
|
|
* This works *great* though; kicks the butt of
|
|
* Linear/Oversampled real hard - and at an order
|
|
* of magnitude higher speed! :-)
|
|
*/
|
|
#define MIX(t,x,y,z) (t).r = ((x).r+(x).r+(y).r+(z).r)>>2; \
|
|
(t).g = ((x).g+(x).g+(y).g+(z).g)>>2; \
|
|
(t).b = ((x).b+(x).b+(y).b+(z).b)>>2; \
|
|
(t).a = ((x).a+(x).a+(y).a+(z).a)>>2;
|
|
#define DIAMOND(gp, sp) \
|
|
{ \
|
|
pix_t B; \
|
|
pix_t D, E, F; \
|
|
pix_t H; \
|
|
pix_t E0, E1; \
|
|
pix_t E2, E3; \
|
|
B = gp(p->src, sx, sy-1); \
|
|
D = gp(p->src, sx-1, sy); \
|
|
E = gp(p->src, sx, sy); \
|
|
F = gp(p->src, sx+1, sy); \
|
|
H = gp(p->src, sx, sy+1); \
|
|
MIX(E0, E, B, D); \
|
|
MIX(E1, E, B, F); \
|
|
MIX(E2, E, H, D); \
|
|
MIX(E3, E, H, F); \
|
|
sp(p->dst, x, y, E0); \
|
|
sp(p->dst, x+1, y, E1); \
|
|
sp(p->dst, x, y+1, E2); \
|
|
sp(p->dst, x+1, y+1, E3); \
|
|
}
|
|
static void do_scale_diamond2x_noclip(scale_params_t *p)
|
|
{
|
|
int x, y, sx, sy;
|
|
for(y = p->start_y, sy = p->start_sy >> 16; y < p->max_y; y += 2, ++sy)
|
|
for(x = p->start_x, sx = p->start_sx >> 16; x < p->max_x;
|
|
x += 2, ++sx)
|
|
DIAMOND(getpix32_nc, setpix32_nc);
|
|
}
|
|
|
|
static void do_scale_diamond2x_clip(scale_params_t *p)
|
|
{
|
|
int x, y, sx, sy;
|
|
for(y = p->start_y, sy = p->start_sy >> 16; y < p->max_y; y += 2, ++sy)
|
|
for(x = p->start_x, sx = p->start_sx >> 16; x < p->max_x;
|
|
x += 2, ++sx)
|
|
DIAMOND(getpix32, setpix32);
|
|
}
|
|
|
|
static void do_scale_diamond2x_clamp(scale_params_t *p)
|
|
{
|
|
int x, y, sx, sy;
|
|
for(y = p->start_y, sy = p->start_sy >> 16; y < p->max_y; y += 2, ++sy)
|
|
for(x = p->start_x, sx = p->start_sx >> 16; x < p->max_x;
|
|
x += 2, ++sx)
|
|
DIAMOND(getpix32c, setpix32);
|
|
}
|
|
#undef MIX
|
|
#undef DIAMOND
|
|
|
|
static void scale_diamond2x(scale_params_t *p)
|
|
{
|
|
do_scale(p, do_scale_diamond2x_clip, do_scale_diamond2x_noclip);
|
|
}
|
|
|
|
static void scale_diamond2x_clamp(scale_params_t *p)
|
|
{
|
|
do_scale(p, do_scale_diamond2x_clamp, do_scale_diamond2x_noclip);
|
|
}
|
|
|
|
|
|
int s_filter_scale(s_bank_t *b, unsigned first, unsigned frames,
|
|
s_filter_args_t *args)
|
|
{
|
|
scale_params_t params;
|
|
int fmode, smode = args->x;
|
|
unsigned i;
|
|
if((args->fx <= 0.0f) && (args->fy <= 0.0f))
|
|
return 0;
|
|
if((args->fx == 1.0f) && (args->fy <= 1.0f))
|
|
return 0;
|
|
|
|
switch(smode)
|
|
{
|
|
case SF_SCALE_NEAREST:
|
|
case SF_SCALE_BILINEAR:
|
|
break;
|
|
case SF_SCALE_SCALE2X:
|
|
case SF_SCALE_DIAMOND:
|
|
if((args->fx != 2.0) || (args->fy != 2.0))
|
|
{
|
|
log_printf(WLOG, "Scale2x and Diamond2x scaling "
|
|
"works only with a scaling factor "
|
|
"of exactly 2.0!\n");
|
|
/* smode = SF_SCALE_NEAREST;*/
|
|
smode = SF_SCALE_BILINEAR;
|
|
}
|
|
break;
|
|
}
|
|
|
|
getpix32_empty = s_clampcolor;
|
|
|
|
params.max_x = (int)ceil(b->w * args->fx);
|
|
params.scx = (int)(65536.0 / args->fx);
|
|
params.scy = (int)(65536.0 / args->fy);
|
|
params.start_x = 0;
|
|
params.start_sx = 0;
|
|
|
|
/* Check for special SFont mode */
|
|
if(args->flags & SF_CLAMP_SFONT)
|
|
{
|
|
params.start_y = 1;
|
|
params.start_sy = 65536;
|
|
params.max_y = (int)((b->h - 1) * args->fy + 1 + 0.5f);
|
|
}
|
|
else
|
|
{
|
|
params.start_y = 0;
|
|
params.start_sy = 0;
|
|
params.max_y = (int)(b->h * args->fy + 0.5f);
|
|
}
|
|
|
|
/* Decode filter type + SF_CLAMP_EXTEND flag */
|
|
fmode = (args->flags & SF_CLAMP_EXTEND) ? 1 : 0;
|
|
switch(smode)
|
|
{
|
|
case SF_SCALE_NEAREST:
|
|
fmode += 0;
|
|
break;
|
|
case SF_SCALE_BILINEAR:
|
|
fmode += 2;
|
|
/* Offset by half a pixel */
|
|
params.start_sx += params.scx / 2;
|
|
params.start_sy += params.scy / 2;
|
|
break;
|
|
case SF_SCALE_SCALE2X:
|
|
fmode += 4;
|
|
break;
|
|
case SF_SCALE_DIAMOND:
|
|
fmode += 6;
|
|
break;
|
|
}
|
|
|
|
for(i = 0; i < frames; ++i)
|
|
{
|
|
s_sprite_t *s = s_get_sprite_b(b, first+i);
|
|
if(!s)
|
|
continue;
|
|
params.src = s->surface;
|
|
params.dst = SDL_CreateSurface(params.max_x, params.max_y,
|
|
SDL_PIXELFORMAT_RGBA32);
|
|
if(!params.dst)
|
|
return -1;
|
|
#ifdef DEBUG
|
|
SDL_FillSurfaceRect(params.dst, NULL,
|
|
SDL_MapSurfaceRGB(params.dst, 255, 128, 0));
|
|
#endif
|
|
|
|
/* Deal with SFont marker row */
|
|
if(args->flags & SF_CLAMP_SFONT)
|
|
{
|
|
int x, sx;
|
|
pix_t clear = {0, 0, 0, 0};
|
|
/* Scale marker row with "nearest" filter */
|
|
for(x = 0, sx = params.start_sx; x < params.max_x;
|
|
++x, sx += params.scx)
|
|
setpix32_nc(params.dst, x, 0,
|
|
getpix32(params.src,
|
|
sx >> 16, 0));
|
|
/* Remove marker row to avoid garbage */
|
|
for(x = 0; x < b->w; ++x)
|
|
setpix32_nc(params.src, x, 0, clear);
|
|
}
|
|
|
|
/* Do the scaling! */
|
|
switch(fmode)
|
|
{
|
|
case 0: /*Nearest*/
|
|
case 1: /*Nearest + extend clamping*/
|
|
scale_nearest(¶ms);
|
|
break;
|
|
case 2: /*Bilinear*/
|
|
scale_bilinear(¶ms);
|
|
break;
|
|
case 3: /*Bilinear + extend clamping*/
|
|
scale_bilinear_clamp(¶ms);
|
|
break;
|
|
case 4: /*Scale2x*/
|
|
scale_2x(¶ms);
|
|
break;
|
|
case 5: /*Scale2x + extend clamping*/
|
|
scale_2x_clamp(¶ms);
|
|
break;
|
|
case 6: /*Diamond2x*/
|
|
scale_diamond2x(¶ms);
|
|
break;
|
|
case 7: /*Diamond2x + extend clamping*/
|
|
scale_diamond2x_clamp(¶ms);
|
|
break;
|
|
}
|
|
|
|
SDL_DestroySurface(s->surface);
|
|
s->surface = params.dst;
|
|
}
|
|
b->w = params.max_x;
|
|
b->h = params.max_y;
|
|
return 0;
|
|
}
|
|
|
|
|
|
int s_filter_cleanalpha(s_bank_t *b, unsigned first, unsigned frames,
|
|
s_filter_args_t *args)
|
|
{
|
|
int min = args->min;
|
|
int max = args->max;
|
|
int x, y;
|
|
unsigned i;
|
|
int contrast = (int)(args->fx * 256);
|
|
int offset = args->x + 128; /* 128 for the bias! */
|
|
if(!contrast)
|
|
contrast = 256; /*Default = 1.0*/
|
|
for(i = 0; i < frames; ++i)
|
|
{
|
|
s_sprite_t *s = s_get_sprite_b(b, first+i);
|
|
if(!s)
|
|
continue;
|
|
for(y = (args->flags & SF_CLAMP_SFONT) ? 1 : 0; y < b->h; ++y)
|
|
{
|
|
pix_t *p = (pix_t *)((char *)s->surface->pixels +
|
|
y * s->surface->pitch);
|
|
for(x = 0; x < b->w; ++x)
|
|
{
|
|
pix_t *pix = p + x;
|
|
int alpha = pix->a;
|
|
alpha = (alpha-128) * contrast >> 8;
|
|
alpha += offset;
|
|
if(alpha <= min)
|
|
pix->a = pix->r = pix->g = pix->b = 0;
|
|
else if(alpha > max)
|
|
pix->a = 255;
|
|
else
|
|
pix->a = alpha;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int s_filter_brightness(s_bank_t *b, unsigned first, unsigned frames,
|
|
s_filter_args_t *args)
|
|
{
|
|
int min = args->min;
|
|
int max = args->max;
|
|
int x, y;
|
|
unsigned i;
|
|
int contrast = (int)(args->fy * 256);
|
|
int offset = (int)(args->fx * 256 - 256) + 128; /* bias! */
|
|
if((256 == contrast) && (128 == offset) && (min <= 0) && (max >= 255))
|
|
return 0; /* No effect! --> */
|
|
|
|
for(i = 0; i < frames; ++i)
|
|
{
|
|
s_sprite_t *s = s_get_sprite_b(b, first + i);
|
|
if(!s)
|
|
continue;
|
|
for(y = (args->flags & SF_CLAMP_SFONT) ? 1 : 0; y < b->h; ++y)
|
|
{
|
|
pix_t *p = (pix_t *)((char *)s->surface->pixels +
|
|
y * s->surface->pitch);
|
|
for(x = 0; x < b->w; ++x)
|
|
{
|
|
int v;
|
|
pix_t *pix = p + x;
|
|
v = ((pix->r - 128) * contrast >> 8) + offset;
|
|
if(v < min)
|
|
v = min;
|
|
else if(v > max)
|
|
v = max;
|
|
pix->r = v;
|
|
v = ((pix->g - 128) * contrast >> 8) + offset;
|
|
if(v < min)
|
|
v = min;
|
|
else if(v > max)
|
|
v = max;
|
|
pix->g = v;
|
|
v = ((pix->b - 128) * contrast >> 8) + offset;
|
|
if(v < min)
|
|
v = min;
|
|
else if(v > max)
|
|
v = max;
|
|
pix->b = v;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void construct_limits(int *min, int *max, int maxdev)
|
|
{
|
|
*min -= maxdev;
|
|
if(*min < 0)
|
|
*min = 0;
|
|
*max += maxdev;
|
|
if(*max > 255)
|
|
*max = 255;
|
|
}
|
|
|
|
|
|
static inline int check_limits(int min, int max, int value)
|
|
{
|
|
if(value < min)
|
|
return 0;
|
|
if(value > max)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
|
|
int s_filter_key2alpha(s_bank_t *b, unsigned first, unsigned frames,
|
|
s_filter_args_t *args)
|
|
{
|
|
int x, y, krmin, krmax, kgmin, kgmax, kbmin, kbmax;
|
|
unsigned i;
|
|
krmin = krmax = s_colorkey.r;
|
|
kgmin = kgmax = s_colorkey.g;
|
|
kbmin = kbmax = s_colorkey.b;
|
|
construct_limits(&krmin, &krmax, args->max);
|
|
construct_limits(&kgmin, &kgmax, args->max);
|
|
construct_limits(&kbmin, &kbmax, args->max);
|
|
for(i = 0; i < frames; ++i)
|
|
{
|
|
s_sprite_t *s = s_get_sprite_b(b, first+i);
|
|
if(!s)
|
|
continue;
|
|
for(y = (args->flags & SF_CLAMP_SFONT) ? 1 : 0; y < b->h; ++y)
|
|
{
|
|
pix_t *p = (pix_t *)((char *)s->surface->pixels +
|
|
y * s->surface->pitch);
|
|
for(x = 0; x < b->h; ++x)
|
|
{
|
|
pix_t *pix = p + x;
|
|
if(check_limits(krmin, krmax, pix->r)
|
|
&& check_limits(kgmin, kgmax, pix->g)
|
|
&& check_limits(kbmin, kbmax, pix->b))
|
|
pix->r = pix->g = pix->b = pix->a = 0;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int s_filter_noise(s_bank_t *b, unsigned first, unsigned frames,
|
|
s_filter_args_t *args)
|
|
{
|
|
int x, y;
|
|
unsigned i;
|
|
int br, bg, bb; /* bias */
|
|
int ar, ag, ab; /* amplitude */
|
|
i = args->min;
|
|
br = args->r * i >> 8;
|
|
bg = args->g * i >> 8;
|
|
bb = args->b * i >> 8;
|
|
i = args->max - args->min;
|
|
ar = args->r * i >> 8;
|
|
ag = args->g * i >> 8;
|
|
ab = args->b * i >> 8;
|
|
noise_reset(args->x);
|
|
for(i = 0; i < frames; ++i)
|
|
{
|
|
s_sprite_t *s = s_get_sprite_b(b, first+i);
|
|
if(!s)
|
|
continue;
|
|
for(y = (args->flags & SF_CLAMP_SFONT) ? 1 : 0; y < b->h; ++y)
|
|
{
|
|
pix_t *p = (pix_t *)((char *)s->surface->pixels +
|
|
y * s->surface->pitch);
|
|
for(x = 0; x < b->w; ++x)
|
|
{
|
|
int r, g, b;
|
|
pix_t *pix = p + x;
|
|
int rnd = noise();
|
|
r = rnd * ar + br + pix->r;
|
|
g = rnd * ag + bg + pix->g;
|
|
b = rnd * ab + bb + pix->b;
|
|
if(r>255)
|
|
r = 255;
|
|
if(g>255)
|
|
g = 255;
|
|
if(b>255)
|
|
b = 255;
|
|
pix->r = r;
|
|
pix->g = g;
|
|
pix->b = b;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|