- Canvi de idea

This commit is contained in:
2025-10-29 16:22:32 +01:00
parent 692aae96b3
commit 107d370bfe
20 changed files with 8524 additions and 0 deletions

715
source/japi/draw.cpp Normal file
View File

@@ -0,0 +1,715 @@
#include "draw.h"
#include <SDL3/SDL.h>
#include "gif.h"
#include "file.h"
#include "shader.h"
namespace draw
{
// La idea de esta unitat es usar "superficies", que no son mes que arrays de bytes, per a anar pintant.
// El resultat final s'ha de pintar en algun moment a la superficie "screen" (o siga, especificar nullptr en setDestination)
// Aleshores, en "render" el contingut de screen se volca a la textura SDL que crearem,
// i eixa textura se pintarà a pantalla com se sol fer amb SDL. Ho anirem veient en el codi.
SDL_Window *sdl_window {nullptr}; // La finestra de SDL
SDL_Renderer *sdl_renderer {nullptr}; // El renderer de SDL
SDL_Texture *sdl_texture {nullptr}; // La textura de SDL a la que pintarem la nostra superficie "screen" i que despres volcarem a pantalla
SDL_Texture *sdl_shadertex {nullptr}; // La textura de SDL per al shader
static int screen_zoom = 1;
static bool screen_fullscreen = false;
static bool screen_cursor = true;
static char* screen_shader = nullptr;
static bool shader_enabled = false;
static float window_ratio = 1;
static int canvas_width;
static int canvas_height;
static int desktop_width;
static int desktop_height;
static int window_width;
static int window_height;
static int offset_x = 0;
static int offset_y = 0;
char window_title[256];
surface *screen {nullptr}; // La superficie screen, que representa la pantalla. Se crea i destrueix internament
surface *destination {nullptr}; // Punter a la actual superficie de destí
surface *source {nullptr}; // Punter a la actual superficie d'oritge
surface *pushed_source {nullptr}; // Punter a la superficie d'oritge que s'ha pushat
uint32_t palette[256]; // La paleta de colors
uint32_t aux_palette[256]; // La paleta de colors, para els fadein
uint8_t color_indices[256]; // Indices dels colors per defecte
uint8_t sel_color {0}; // Color seleccionat per defecte
uint8_t transparent {0}; // El color transparent
//surface *textsurf = nullptr; // Surface on guardar el gif amb la font
SDL_Rect viewport;
bool fading_out = false;
bool fading_in = false;
void createDisplay()
{
// Ajustem el zoom i el ratio, per si tenen valors locs
if (screen_zoom <= 0) screen_zoom = 1;
if (window_ratio <= 0) window_ratio = 1;
// Ajustem el tamany de la finestra, segons el zoom i el ratio
window_width = canvas_width*screen_zoom;
window_height = window_ratio != 1 ? int(float(canvas_width)*window_ratio*float(screen_zoom)) : canvas_height*screen_zoom;
// Mentres no càpiga en la pantalla, reduïm el zoom
while (window_width > desktop_width || window_height > desktop_height) {
screen_zoom--;
window_width = canvas_width*screen_zoom;
window_height = window_ratio != 1 ? int(float(canvas_width)*window_ratio*float(screen_zoom)) : canvas_height*screen_zoom;
}
if (screen_fullscreen) {
if (desktop_width * window_ratio > desktop_height) {
offset_y = 0;
window_height = desktop_height;
window_width = desktop_height/window_ratio;
offset_x = (desktop_width - window_width)/2;
} else {
offset_x = 0;
window_width = desktop_width;
window_height = desktop_width*window_ratio;
offset_y = (desktop_height - window_height)/2;
}
} else {
offset_x = offset_y = 0;
}
sdl_window = SDL_CreateWindow(window_title, window_width, window_height, SDL_WINDOW_OPENGL|(screen_fullscreen?SDL_WINDOW_FULLSCREEN:0));
if (!sdl_window) {
SDL_LogCritical(SDL_LOG_CATEGORY_VIDEO, "ERROR (draw::init): Failed to initialize window!\n");
exit(1);
}
sdl_renderer = SDL_CreateRenderer(sdl_window, NULL);
if (!sdl_renderer) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "ERROR (draw::init): Failed to initialize renderer!\n");
exit(1);
}
if (screen_cursor) SDL_ShowCursor(); else SDL_HideCursor();
sdl_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, canvas_width, canvas_height);
SDL_SetTextureScaleMode(sdl_texture, SDL_SCALEMODE_NEAREST);
if (!sdl_texture) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "ERROR (draw::init): Failed to initialize texture!\n");
exit(1);
}
sdl_shadertex = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, window_width, window_height);
SDL_SetTextureScaleMode(sdl_shadertex, SDL_SCALEMODE_NEAREST);
loadShader();
}
void destroyDisplay()
{
SDL_DestroyTexture(sdl_texture);
SDL_DestroyRenderer(sdl_renderer);
SDL_DestroyWindow(sdl_window);
}
// Inicialització de tot el que fa falta per a carregar gràfics i pintar en pantalla
void init(const char *titol, const uint16_t width, const uint16_t height, const int zoom, const bool fullscreen, const float ratio)
{
screen_zoom = file::getConfigValueInteger("zoom", zoom);
screen_fullscreen = file::getConfigValueBool("fullscreen", fullscreen);
window_ratio = ratio;
canvas_width = width;
canvas_height = height;
strcpy(window_title, titol);
const SDL_DisplayMode *dm = SDL_GetDesktopDisplayMode(SDL_GetPrimaryDisplay());
if (!dm)
{
SDL_Log("SDL_GetDesktopDisplayMode failed: %s", SDL_GetError());
exit(1);
}
desktop_width = dm->w;
desktop_height = dm->h;
createDisplay();
// Inicialització de les estructures de SDL
/*
sdl_window = SDL_CreateWindow(titol, width * zoom, height * zoom, 0);
if (!sdl_window) {
SDL_LogCritical(SDL_LOG_CATEGORY_VIDEO, "ERROR (draw::init): Failed to initialize window!\n");
exit(1);
}
sdl_renderer = SDL_CreateRenderer(sdl_window, NULL);
if (!sdl_renderer) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "ERROR (draw::init): Failed to initialize renderer!\n");
exit(1);
}
sdl_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height);
SDL_SetTextureScaleMode(sdl_texture, SDL_SCALEMODE_NEAREST);
if (!sdl_texture) {
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "ERROR (draw::init): Failed to initialize texture!\n");
exit(1);
}
*/
// Creem la superficie "screen" i la establim com a superficie destinació
screen = createSurface(width, height);
destination = screen;
viewport.x = viewport.y = 0;
viewport.w = width;
viewport.h = height;
sel_color = transparent = 0;
for (int i=0;i<256;++i) color_indices[i] = i;
//SDL_HideCursor();
//textsurf = loadSurface("font.gif");
}
// Finalització del sistema
void quit()
{
//if (textsurf) freeSurface(textsurf);
// Si la superficie "screen" existia, alliberem la seua memòria
if (screen != nullptr)
{
freeSurface(screen);
}
// Destruim tot el relacionat amb SDL
destroyDisplay();
// Fiquem tots els punters a nullptr, per si de cas no estem eixint del programa
// i anem a tornar a inicialitzar el sistema
sdl_window = nullptr;
sdl_renderer = nullptr;
sdl_texture = nullptr;
screen = destination = source = nullptr;
}
void setZoom(const int value) {
screen_zoom = value;
destroyDisplay();
createDisplay();
file::setConfigValueInteger("zoom", screen_zoom);
}
const int getZoom()
{
return screen_zoom;
}
const float getScaleX()
{
return float(window_width) / float(canvas_width);
}
const float getScaleY()
{
return float(window_height) / float(canvas_height);
}
const int getOffsetX()
{
return offset_x;
}
const int getOffsetY()
{
return offset_y;
}
bool getFullscreen() {
return screen_fullscreen;
}
void setFullscreen(const bool value) {
screen_fullscreen=value;
destroyDisplay();
createDisplay();
file::setConfigValueBool("fullscreen", screen_fullscreen);
}
void loadShader()
{
char *buffer = nullptr;
if (screen_shader) {
int size;
buffer = file::getFileBuffer(screen_shader, size, true);
}
shader::setAspectRatio(3.0f/4.0f);
shader::init(sdl_window, sdl_shadertex, buffer);
if (buffer) free(buffer);
}
void setShader(const char* shader_file)
{
if (screen_shader) free(screen_shader);
screen_shader = (char*)malloc(strlen(shader_file)+1);
strcpy(screen_shader, shader_file);
loadShader();
if (file::getConfigValueBool("shader_enabled", false)) enableShader();
}
void enableShader()
{
shader_enabled = true;
shader::enable();
//destroyDisplay();
//createDisplay();
file::setConfigValueBool("shader_enabled", shader_enabled);
}
void disableShader()
{
shader_enabled = false;
shader::disable();
//destroyDisplay();
//createDisplay();
file::setConfigValueBool("shader_enabled", shader_enabled);
}
void toggleShader()
{
shader_enabled ? disableShader() : enableShader();
}
void hideCursor()
{
screen_cursor = false;
SDL_HideCursor();
}
void showCursor()
{
screen_cursor = true;
SDL_ShowCursor();
}
// Crea una superficie i torna un punter a ella
surface *createSurface(const uint16_t w, const uint16_t h)
{
// Primer reservem memòria per a la estructura "surface"
surface *surf = (surface *)malloc(sizeof(surface));
// Després reservem memòria per als pixels
surf->pixels = (uint8_t *)malloc(w * h);
// I apuntem el ample i alt de la superficie
surf->w = w;
surf->h = h;
// ...i tornem la superficie creada, clar
return surf;
}
// Carrega un gràfic d'un arxiu (en format GIF) a una nova superficie, i torna un punter a ella
surface *loadSurface(const char *filename, const bool loadPalette)
{
// Agafem un buffer de bytes de l'arxiu especificat
// getFileBuffer() simplement ens torna el arxiu sencer dins de un array de char
int size;
uint8_t *buffer = (uint8_t *)file::getFileBuffer(filename, size);
// Si ens ha tornat nullptr, es que no l'ha trobat, tornem nosaltres també nullptr ja que no s'ha pogut crear la superficie
if (buffer == nullptr)
{
return nullptr;
}
// Primer reservem memòria per a la estructura "surface"
surface *surf = (surface *)malloc(sizeof(surface));
// Després li passem el buffer de bytes a la funció de carregar un GIF.
// El resultat es un array de bytes, els pixels en sí. Ja havem reservat
// la memòria necessaria en "LoadGif", així que no tenim que fer-ho ara,
// però, ojo, sí que tindrem que alliberar-la.
surf->pixels = LoadGif(buffer, &surf->w, &surf->h);
// Si li havem dit que carregue també la paleta...
if (loadPalette)
{
// Li passem el array del arxiu a LoadPalette. Ell ens torna un array de uint32_t amb la paleta
// Van a ser com a molt 256 entrades de 32 bits (pero no sempre), cada entrada es un color, amb el format 0xAARRGGBB
int paletteSize;
uint32_t *pal = LoadPalette(buffer, &paletteSize);
// Copiem eixe array al nostre array de la paleta de sistema. Ara ja tenim la paleta carregada.
for (int i=0;i<256;++i) {
palette[i] = i<paletteSize ? pal[i] : 0;
}
//memset(palette, 0, 1024); // Fiquem tot a 0, que la paleta potser no es de 256 i quedaria basura
//memcpy(palette, pal, paletteSize*4); // 32 bits per entrada == 4 bytes x 'paletteSize' entrades
// Alliberem el array que ens habia tornat LoadPalette()
free(pal);
}
// Com ja no ens fa falta, alliberem la memòria del buffer del arxiu
free(buffer);
// I finalment tornem la superficie
return surf;
}
// Allibera la memòria d'una superficie, els seus pixels inclosos
void freeSurface(surface *surf)
{
// Si la superficie existeix...
if (surf != nullptr)
{
// Si el array de pixels existeix, l'alliberem
if (surf->pixels != nullptr)
{
free(surf->pixels);
}
// ... alliberem la superficie
free(surf);
}
}
// Estableix una superficie com a superficie que rebrà les funcions de pintat (especificar nullptr per a pintar a pantalla)
void setDestination(surface *surf)
{
// Si han especificat nullptr, fiquem "screen" com a destinació
destination = surf == nullptr ? screen : surf;
resetViewport();
}
// Estableix una superficie com a superficie de la que s'agafaràn els gràfics per a pintar
void setSource(surface *surf)
{
// Si han especificat nullptr, fiquem "screen" com a font
source = surf == nullptr ? screen : surf;
}
void pushSource()
{
pushed_source = source;
}
void popSource()
{
source = pushed_source;
}
void setViewport(const int x, const int y, const int w, const int h)
{
viewport.x = x>0?x:0;
viewport.y = y>0?y:0;
viewport.w = w+x<destination->w?w:destination->w;
viewport.h = h+y<destination->h?h:destination->h;
}
void resetViewport()
{
viewport.x = viewport.y = 0;
viewport.w = destination->w;
viewport.h = destination->h;
}
const int getLocalX(const int x)
{
return x - viewport.x;
}
const int getLocalY(const int y)
{
return y - viewport.y;
}
// Carrega la paleta d'un GIF i la torna en un array de uint32_t
uint32_t *loadPalette(const char *filename, int *paletteSize)
{
// Agafem un buffer de bytes de l'arxiu especificat
// getFileBuffer() simplement ens torna el arxiu sencer dins de un array de char
int size;
uint8_t *buffer = (uint8_t *)file::getFileBuffer(filename, size);
// Li passem el array del arxiu a LoadPalette. Ell ens torna un array de uint32_t amb la paleta
// Van a ser 256 entrades de 32 bits, cada entrada es un color, amb el format 0xAARRGGBB
uint32_t *pal = LoadPalette(buffer);
// I també el buffer del arxiu
free(buffer);
if (paletteSize) *paletteSize = size;
return pal;
}
// Estableix la paleta del sistema, o part de ella, des d'un array especificat
void setPalette(const uint32_t *pal, const int len, const int pos)
{
for (int i=0; i<len; ++i)
{
palette[i+pos] = pal[i];
}
}
//Recupera la paleta del sistema, o part de ella, a un array
uint32_t *getPalette()
{
uint32_t *p = (uint32_t*)malloc(256*sizeof(uint32_t));
for (int i=0; i<256; ++i) p[i] = palette[i];
return p;
}
// Estableix una entrada de la paleta del sistema
void setPaletteEntry(const uint8_t index, const uint8_t r, const uint8_t g, const uint8_t b)
{
palette[index] = (r << 16) + (g << 8) + b;
}
// Esborra la superficie "destination" amb el color especificat
void cls(const uint8_t color)
{
// El tamany es width x height bytes
const int size = destination->w * destination->h;
// Omplim la memòria dels pixels de la superficie de destinació amb "color"
memset(destination->pixels, color, size);
}
//Estableix el color especificat com a transparent
void setTrans(const uint8_t color)
{
transparent = color;
}
// Funció interna per a pintar un pixel d'una superficie sense eixir-se'n de la memòria i petar el mame
void pset(surface *surface, const int x, const int y, const uint8_t color)
{
// Si el color es transparent, eixim, ni ens molestem en mirar res més
if (color == transparent) return;
// Si pintem a "destination", mirem que estiga dins del "viewport" i sinó fora
if (surface == destination) {
if (x+viewport.x >= 0 && y+viewport.y >= 0 && x < viewport.w && y < viewport.h)
surface->pixels[(viewport.x+x) + (y+viewport.y) * surface->w] = color_indices[color];
} else {
// Si no es destinations, mirem que estiga dins de la surface, i sinó fora!
if (x >= 0 && y >= 0 && x < surface->w && y < surface->h)
surface->pixels[x + y * surface->w] = color_indices[color];
}
}
// Funció interna per a llegir un pixel d'una superficie eixir-se'n de la memòria i petar el mame
const uint8_t pget(surface *surface, const int x, const int y)
{
// Si estem llegint de "destination", mirar que estigam llegint dins del viewport
if (surface == destination) {
if (x+viewport.x >= 0 && y+viewport.y >= 0 && x < viewport.w && y < viewport.h)
return surface->pixels[(viewport.x + x) + (viewport.y + y) * surface->w];
} else {
// Si no es "destination", si la coordenada està dins del rang que abarca la superficie,
if (x >= 0 && y >= 0 && x < surface->w && y < surface->h)
return surface->pixels[x + y * surface->w];
}
return 0;
}
void putPixel(const int x, const int y, const uint8_t color)
{
pset(screen, x, y, color);
}
// Pinta un troç de la superficie "source" en la superficie "destination".
void draw(const int dx, const int dy, const int w, const int h, const int sx, const int sy, const draw::flip flip)
{
// Si no hi ha superficie d'oritge especificada, no fem res, o petarà el mame
if (source == nullptr)
{
return;
}
// En principi, el quadrat de l'oritge començarà en (sx,sy) i avançarem 1 pixel en positiu tant en x com en y
int sdx = 1, sdy = 1, ssx = sx, ssy = sy;
// Però si s'ha especificat que fem flip en horitzontal...
if (flip & draw::flip::horizontal)
{
sdx = -1; // Avançarem 1 pixel en negatiu
ssx = sx + w - 1; // I començarem al final, o siga, sumarem a sx el ample
}
// De igual forma per al flip en vertical, per a la y
if (flip & draw::flip::vertical)
{
sdy = -1;
ssy = sy + h - 1;
}
// guardem la coordenada d'oritge en x per a restablir-la a cada linea
int csx = ssx;
// Anem linea per linea. Les variables dels dos bucles for controlen les coordenades en la destinació, que sempre van avant.
for (int y = dy; y < dy + h; ++y)
{
ssx = csx; // fiquem la coordenada de l'oritge al principi
// en cada linea, anem pixel a pixel
for (int x = dx; x < dx + w; ++x)
{
pset(destination, x, y, pget(source, ssx, ssy)); // Agafem pixel de l'oritge i el fiquem en la destinació
ssx += sdx; // avancem (o retrocedim) la coordenada x de l'oritge
}
ssy += sdy; // avancem (o retrocedim) la coordenada y de l'oritge
}
}
// Pinta tota la superficie "source" en la superficie "destination", posició (x,y).
void draw(const int x, const int y)
{
draw(x, y, source->w, source->h, 0, 0);
}
// Pinta tota la superficie "source" en la superficie "destination", posició (0,0)
void draw()
{
draw(0,0,source->w, source->h, 0, 0);
}
// Carrega la superficie especificada en "source" i la pinta tota en la superficie "destination", posició (0,0).
void draw(draw::surface* surf)
{
setSource(surf);
draw();
}
void swapcol(const Uint8 c1, const Uint8 c2)
{
color_indices[c1] = c2;
}
void restorecol(const Uint8 c)
{
color_indices[c] = c;
}
void color(const Uint8 col)
{
sel_color = col;
}
void hline(const int x, const int y, const int w)
{
for (int i=x;i<x+w;++i) pset(destination, i, y, sel_color);
}
void vline(const int x, const int y, const int h)
{
for (int i=y;i<y+h;++i) pset(destination, x, i, sel_color);
}
void fillrect(const int x, const int y, const int w, const int h)
{
for (int j=y;j<y+h;++j) for (int i=x;i<x+w;++i) pset(destination, i, j, sel_color);
}
void rect(const int x, const int y, const int w, const int h)
{
hline(x,y,w);
hline(x,y+h-1,w);
vline(x,y,h);
vline(x+w-1,y,h);
}
bool decPalEntry(const uint8_t index, const uint8_t val)
{
const uint32_t entry = palette[index];
uint8_t r = (entry >> 16) & 0xff;
uint8_t g = (entry >> 8) & 0xff;
uint8_t b = entry & 0xff;
r = r>=val ? r-val : 0;
g = g>=val ? g-val : 0;
b = b>=val ? b-val : 0;
palette[index] = (r << 16) + (g << 8) + b;
return palette[index] != 0;
}
bool incPalEntry(const uint8_t index, const uint8_t val)
{
const uint32_t entry = palette[index];
uint8_t r = (entry >> 16) & 0xff;
uint8_t g = (entry >> 8) & 0xff;
uint8_t b = entry & 0xff;
const uint32_t dest_entry = aux_palette[index];
const uint8_t dr = (dest_entry >> 16) & 0xff;
const uint8_t dg = (dest_entry >> 8) & 0xff;
const uint8_t db = dest_entry & 0xff;
r = (r+val > dr) ? dr : r+val;
g = (g+val > dg) ? dg : g+val;
b = (b+val > db) ? db : b+val;
palette[index] = (r << 16) + (g << 8) + b;
return palette[index] != aux_palette[index];
}
void fadein()
{
if (!fading_in) {
for (int i=0;i<256;++i) {
aux_palette[i] = palette[i];
palette[i] = 0;
}
}
fading_in = false;
for (int i=0; i<256; ++i) if (incPalEntry(i, 8)) fading_in = true;
}
void fadeout()
{
fading_out = false;
for (int i=0; i<256; ++i) if (decPalEntry(i, 8)) fading_out = true;
}
bool isfading()
{
return fading_in || fading_out;
}
// Refresca la pantalla
void render()
{
Uint32 *sdl_pixels; // Punter al array de pixels que enstornarà SDL_LockTexture
int sdl_pitch; // Ací estarà guardat el pitch de la textura, com es de 32 bits, no m'afecta
const uint32_t size = screen->w * screen->h; // tamany de la superficie
if (fading_in) fadein();
if (fading_out) fadeout();
// Bloquejem la textura SDL i agafem els seus pixels (son enters de 32 bits amb format 0xAARRGGBB)
SDL_LockTexture(sdl_texture, NULL, (void **)&sdl_pixels, &sdl_pitch);
// Cada pixel de la superficie "screen" es un enter de 8 bits que representa un index en la paleta de colors
// Per tant, per a pintar en la textura SDL, pillem el color de la paleta que correspon al index en "screen"
// i el enviem a la textura SDL
for (uint32_t i = 0; i < size; ++i)
{
sdl_pixels[i] = palette[screen->pixels[i]] | 0xff000000;
}
// Desbloquejem la textura
SDL_UnlockTexture(sdl_texture);
SDL_SetRenderTarget(sdl_renderer, sdl_shadertex);
// Pintem la textura a pantalla
SDL_RenderTexture(sdl_renderer, sdl_texture, NULL, NULL);
// I ho presentem
shader::render();
//SDL_RenderPresent(sdl_renderer);
}
}