- Initial commit

This commit is contained in:
2023-02-28 17:03:40 +01:00
commit 08f4160503
8 changed files with 1561 additions and 0 deletions

478
source/gif.c Normal file
View File

@@ -0,0 +1,478 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#define EXTENSION_INTRODUCER 0x21
#define IMAGE_DESCRIPTOR 0x2C
#define TRAILER 0x3B
#define GRAPHIC_CONTROL 0xF9
#define APPLICATION_EXTENSION 0xFF
#define COMMENT_EXTENSION 0xFE
#define PLAINTEXT_EXTENSION 0x01
#define READ(dst, size) memcpy(dst, buffer, size); buffer += size
typedef struct
{
unsigned short width;
unsigned short height;
unsigned char fields;
unsigned char background_color_index;
unsigned char pixel_aspect_ratio;
}
screen_descriptor_t;
typedef struct
{
unsigned char r;
unsigned char g;
unsigned char b;
}
rgb;
typedef struct
{
unsigned short image_left_position;
unsigned short image_top_position;
unsigned short image_width;
unsigned short image_height;
unsigned char fields;
}
image_descriptor_t;
typedef struct
{
unsigned char byte;
int prev;
int len;
}
dictionary_entry_t;
typedef struct
{
unsigned char extension_code;
unsigned char block_size;
}
extension_t;
typedef struct
{
unsigned char fields;
unsigned short delay_time;
unsigned char transparent_color_index;
}
graphic_control_extension_t;
typedef struct
{
unsigned char application_id[ 8 ];
unsigned char version[ 3 ];
}
application_extension_t;
typedef struct
{
unsigned short left;
unsigned short top;
unsigned short width;
unsigned short height;
unsigned char cell_width;
unsigned char cell_height;
unsigned char foreground_color;
unsigned char background_color;
}
plaintext_extension_t;
//static unsigned short width = 0;
//static unsigned short height = 0;
//static unsigned char* uncompressed_data = NULL;
void uncompress( int code_length,
const unsigned char *input,
int input_length,
unsigned char *out )
{
//int maxbits;
int i, bit;
int code, prev = -1;
dictionary_entry_t *dictionary;
int dictionary_ind;
unsigned int mask = 0x01;
int reset_code_length;
int clear_code; // This varies depending on code_length
int stop_code; // one more than clear code
int match_len;
clear_code = 1 << ( code_length );
stop_code = clear_code + 1;
// To handle clear codes
reset_code_length = code_length;
// Create a dictionary large enough to hold "code_length" entries.
// Once the dictionary overflows, code_length increases
dictionary = ( dictionary_entry_t * )
malloc( sizeof( dictionary_entry_t ) * ( 1 << ( code_length + 1 ) ) );
// Initialize the first 2^code_len entries of the dictionary with their
// indices. The rest of the entries will be built up dynamically.
// Technically, it shouldn't be necessary to initialize the
// dictionary. The spec says that the encoder "should output a
// clear code as the first code in the image data stream". It doesn't
// say must, though...
for ( dictionary_ind = 0;
dictionary_ind < ( 1 << code_length );
dictionary_ind++ )
{
dictionary[ dictionary_ind ].byte = dictionary_ind;
// XXX this only works because prev is a 32-bit int (> 12 bits)
dictionary[ dictionary_ind ].prev = -1;
dictionary[ dictionary_ind ].len = 1;
}
// 2^code_len + 1 is the special "end" code; don't give it an entry here
dictionary_ind++;
dictionary_ind++;
// TODO verify that the very last byte is clear_code + 1
while ( input_length )
{
code = 0x0;
// Always read one more bit than the code length
for ( i = 0; i < ( code_length + 1 ); i++ )
{
// This is different than in the file read example; that
// was a call to "next_bit"
bit = ( *input & mask ) ? 1 : 0;
mask <<= 1;
if ( mask == 0x100 )
{
mask = 0x01;
input++;
input_length--;
}
code = code | ( bit << i );
}
if ( code == clear_code )
{
code_length = reset_code_length;
dictionary = ( dictionary_entry_t * ) realloc( dictionary,
sizeof( dictionary_entry_t ) * ( 1 << ( code_length + 1 ) ) );
for ( dictionary_ind = 0;
dictionary_ind < ( 1 << code_length );
dictionary_ind++ )
{
dictionary[ dictionary_ind ].byte = dictionary_ind;
// XXX this only works because prev is a 32-bit int (> 12 bits)
dictionary[ dictionary_ind ].prev = -1;
dictionary[ dictionary_ind ].len = 1;
}
dictionary_ind++;
dictionary_ind++;
prev = -1;
continue;
}
else if ( code == stop_code )
{
/*if ( input_length > 1 )
{
fprintf( stderr, "Malformed GIF (early stop code)\n" );
exit( 0 );
}*/
break;
}
// Update the dictionary with this character plus the _entry_
// (character or string) that came before it
if ( ( prev > -1 ) && ( code_length < 12 ) )
{
if ( code > dictionary_ind )
{
fprintf( stderr, "code = %.02x, but dictionary_ind = %.02x\n",
code, dictionary_ind );
exit( 0 );
}
// Special handling for KwKwK
if ( code == dictionary_ind )
{
int ptr = prev;
while ( dictionary[ ptr ].prev != -1 )
{
ptr = dictionary[ ptr ].prev;
}
dictionary[ dictionary_ind ].byte = dictionary[ ptr ].byte;
}
else
{
int ptr = code;
while ( dictionary[ ptr ].prev != -1 )
{
ptr = dictionary[ ptr ].prev;
}
dictionary[ dictionary_ind ].byte = dictionary[ ptr ].byte;
}
dictionary[ dictionary_ind ].prev = prev;
dictionary[ dictionary_ind ].len = dictionary[ prev ].len + 1;
dictionary_ind++;
// GIF89a mandates that this stops at 12 bits
if ( ( dictionary_ind == ( 1 << ( code_length + 1 ) ) ) &&
( code_length < 11 ) )
{
code_length++;
dictionary = ( dictionary_entry_t * ) realloc( dictionary,
sizeof( dictionary_entry_t ) * ( 1 << ( code_length + 1 ) ) );
}
}
prev = code;
// Now copy the dictionary entry backwards into "out"
match_len = dictionary[ code ].len;
while ( code != -1 )
{
out[ dictionary[ code ].len - 1 ] = dictionary[ code ].byte;
if ( dictionary[ code ].prev == code )
{
fprintf( stderr, "Internal error; self-reference." );
exit( 0 );
}
code = dictionary[ code ].prev;
}
out += match_len;
}
}
static int read_sub_blocks( unsigned char* buffer, unsigned char **data )
{
int data_length;
int index;
unsigned char block_size;
// Everything following are data sub-blocks, until a 0-sized block is
// encountered.
data_length = 0;
*data = NULL;
index = 0;
while ( 1 )
{
READ(&block_size, 1);
if ( block_size == 0 ) // end of sub-blocks
{
break;
}
data_length += block_size;
*data = (unsigned char*)realloc( *data, data_length );
// TODO this could be split across block size boundaries
READ(*data + index, block_size);
index += block_size;
}
return data_length;
}
unsigned char* process_image_descriptor( unsigned char* buffer,
rgb *gct,
int gct_size,
int resolution_bits )
{
image_descriptor_t image_descriptor;
int compressed_data_length;
unsigned char *compressed_data = NULL;
unsigned char lzw_code_size;
int uncompressed_data_length = 0;
unsigned char *uncompressed_data = NULL;
// TODO there could actually be lots of these
READ(&image_descriptor, 9);
// TODO if LCT = true, read the LCT
READ(&lzw_code_size, 1);
compressed_data_length = read_sub_blocks( buffer, &compressed_data );
// width = image_descriptor.image_width;
// height = image_descriptor.image_height;
uncompressed_data_length = image_descriptor.image_width *
image_descriptor.image_height;
uncompressed_data = (unsigned char*)malloc( uncompressed_data_length );
uncompress( lzw_code_size, compressed_data, compressed_data_length,
uncompressed_data );
if ( compressed_data ) free( compressed_data );
//if ( uncompressed_data )
// free( uncompressed_data );
return uncompressed_data;
}
/**
* @param gif_file the file descriptor of a file containing a
* GIF-encoded file. This should point to the first byte in
* the file when invoked.
*/
#define rb (*(buffer++))
uint32_t* LoadPalette(unsigned char *buffer) {
unsigned char header[7];
screen_descriptor_t screen_descriptor;
//int color_resolution_bits;
int global_color_table_size = 0; // number of entries in global_color_table
uint32_t *global_color_table = NULL;
READ(header, 6);
READ(&screen_descriptor, 7);
//color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
global_color_table = (uint32_t *)calloc(1, 1024);
if (screen_descriptor.fields & 0x80) {
global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
//global_color_table = (rgb *)malloc(3 * global_color_table_size);
//READ(global_color_table, 3 * global_color_table_size);
for (int i=0; i<global_color_table_size;++i) {
global_color_table[i] = (buffer[0]<<16) + (buffer[1]<<8) + buffer[2];
buffer+=3;
}
}
return global_color_table;
}
static unsigned char* process_gif_stream(unsigned char *buffer, unsigned short* w, unsigned short* h)
{
unsigned char header[ 7 ];
screen_descriptor_t screen_descriptor;
int color_resolution_bits;
int global_color_table_size =0; // number of entries in global_color_table
rgb *global_color_table = NULL;
unsigned char block_type = 0x0;
// A GIF file starts with a Header (section 17)
READ(header, 6);
header[ 6 ] = 0x0;
// XXX there's another format, GIF87a, that you may still find
// floating around.
/*if ( strcmp( "GIF89a", (char*)header ) )
{
fprintf( stderr,
"Invalid GIF file (header is '%s', should be 'GIF89a')\n",
header );
return NULL;
}*/
// Followed by a logical screen descriptor
// Note that this works because GIFs specify little-endian order; on a
// big-endian machine, the height & width would need to be reversed.
// Can't use sizeof here since GCC does byte alignment;
// sizeof( screen_descriptor_t ) = 8!
READ(&screen_descriptor, 7);
*w = screen_descriptor.width;
*h = screen_descriptor.height;
color_resolution_bits = ( ( screen_descriptor.fields & 0x70 ) >> 4 ) + 1;
if ( screen_descriptor.fields & 0x80 )
{
//int i;
// If bit 7 is set, the next block is a global color table; read it
global_color_table_size = 1 <<
( ( ( screen_descriptor.fields & 0x07 ) + 1 ) );
global_color_table = ( rgb * ) malloc( 3 * global_color_table_size );
// XXX this could conceivably return a short count...
READ(global_color_table, 3 * global_color_table_size);
}
while ( block_type != TRAILER )
{
READ(&block_type, 1);
unsigned char size;
switch ( block_type )
{
case IMAGE_DESCRIPTOR:
return process_image_descriptor(buffer,
global_color_table,
global_color_table_size,
color_resolution_bits);
break;
case EXTENSION_INTRODUCER:
buffer++;
size = *(buffer++);
buffer += size;
do {
size = *(buffer++);
buffer += size;
} while (size != 0);
/*if ( !process_extension( buffer ) )
{
return NULL;
}*/
break;
case TRAILER:
break;
default:
fprintf( stderr, "Bailing on unrecognized block type %.02x\n",
block_type );
return NULL;
}
}
return NULL;
}
unsigned char* LoadGif(unsigned char *buffer, unsigned short* w, unsigned short* h) {
return process_gif_stream(buffer, w, h);
}
/*int main( int argc, char *argv[] )
{
FILE* gif_file;
if ( argc < 2 )
{
fprintf( stderr, "Usage: %s <path-to-gif-file>\n", argv[ 0 ] );
exit( 0 );
}
gif_file = fopen( argv[ 1 ], "rb" );
if ( gif_file == NULL )
{
fprintf( stderr, "Unable to open file '%s'", argv[ 1 ] );
perror( ": " );
}
process_gif_stream( gif_file );
fclose( gif_file );
}*/

142
source/jaudio.cpp Normal file
View File

@@ -0,0 +1,142 @@
#include "jaudio.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_mixer.h>
#include <stdio.h>
namespace audio
{
// Açò son estructures de mentires, per a usar estructures de SDL_Mixer de forma opaca.
// Al final es un punter, així que és irrelevant el tipus del punter,
// però si amague que son estructures de SDL_Mixer, no fa falta ficar el include a SDL_mixer fora de este arxiu
struct sound
{
}; // Dummy structs
struct music
{
};
// Inicialitza el sistema de só
void init()
{
// Al final he ficat la configuració automàtica i au. Si en el futur necesitem canviar-ho pos se canvia
Mix_OpenAudio(48000, AUDIO_S16, 2, 1024);
}
// Tanca el sistema de só (no shit, sherlock)
void quit()
{
Mix_CloseAudio();
}
// Carrega un arxiu de música en format OGG
const music *loadMusic(const std::string filename)
{
return (music *)Mix_LoadMUS(filename.c_str());
}
// Comença a reproduïr la música en questió
void playMusic(music *mus, const int loop)
{
Mix_PlayMusic((Mix_Music *)mus, loop);
}
// Pausa la música que està sonant ara
void pauseMusic()
{
Mix_PauseMusic();
}
// Continua la música pausada
void resumeMusic()
{
Mix_ResumeMusic();
}
// Para la música que estava sonant
void stopMusic()
{
Mix_HaltMusic();
}
// Obté el estat actual de la música
const music_state getMusicState()
{
if (Mix_PausedMusic())
{
return MUSIC_PAUSED;
}
else if (Mix_PlayingMusic())
{
return MUSIC_PLAYING;
}
else
{
return MUSIC_STOPPED;
}
}
// Allibera una música
void deleteMusic(music *mus)
{
Mix_FreeMusic((Mix_Music *)mus);
}
// Carrega un só des d'un arxiu WAV
const sound *loadSound(const std::string filename)
{
return (sound *)Mix_LoadWAV(filename.c_str());
}
// Comença a reproduïr el só especificat
const int playSound(sound *snd, const int loop)
{
return Mix_PlayChannel(-1, (Mix_Chunk *)snd, loop);
}
// Allibera un só
void deleteSound(sound *snd)
{
Mix_FreeChunk((Mix_Chunk *)snd);
}
// Pausa un canal en el que s'estava reproduïnt un só
void pauseChannel(const int channel)
{
Mix_Pause(channel);
}
// Continua un canal pausat
void resumeChannel(const int channel)
{
Mix_Resume(channel);
}
// Para un canal que estava reproduïnt un só
void stopChannel(const int channel)
{
Mix_HaltChannel(channel);
}
// Obté l'estat d'un canal
const channel_state getChannelState(const int channel)
{
if (Mix_Paused(channel))
{
return CHANNEL_PAUSED;
}
else if (Mix_Playing(channel))
{
return CHANNEL_PLAYING;
}
else
{
return CHANNEL_FREE;
}
}
// Estableix el volum general
const int setVolume(int volume)
{
return Mix_Volume(-1, volume);
}
}

97
source/jaudio.h Normal file
View File

@@ -0,0 +1,97 @@
#pragma once
#include <string>
namespace audio
{
// Enumeració per a representar el estat de un canal de sò
enum channel_state
{
CHANNEL_INVALID,
CHANNEL_FREE,
CHANNEL_PLAYING,
CHANNEL_PAUSED
};
// Enumeració per a representar el estat de la música
enum music_state
{
MUSIC_INVALID,
MUSIC_PLAYING,
MUSIC_PAUSED,
MUSIC_STOPPED
};
// Estructures per a gestionar música i só
struct sound;
struct music;
/// @brief Inicialitza el sistema de só
void init();
/// @brief Tanca el sistema de só
void quit();
/// @brief Carrega un arxiu de música en format OGG
/// @param filename nom de l'arxiu
/// @return punter a la música
const music *loadMusic(const std::string filename);
/// @brief Comença a reproduïr la música en questió
/// @param mus punter a la música
/// @param loop quants bucles farà (-1=infinit, 0=no repeteix, 1=repeteix 1 vegada...)
void playMusic(music *mus, const int loop = -1);
/// @brief Pausa la música que està sonant ara
void pauseMusic();
/// @brief Continua la música pausada
void resumeMusic();
/// @brief Para la música que estava sonant
void stopMusic();
/// @brief Obté el estat actual de la música
/// @return estat actual de la música (MUSIC_INVALID, MUSIC_PLAYING, MUSIC_PAUSED o MUSIC_STOPPED)
const music_state getMusicState();
/// @brief Allibera una música
/// @param mus punter a la música a alliberar
void deleteMusic(music *mus);
/// @brief Carrega un só des d'un arxiu WAV
/// @param filename nom de l'arxiu
/// @return un punter al só
const sound *loadSound(const std::string filename);
/// @brief Comença a reproduïr el só especificat
/// @param snd punter al só a reproduïr
/// @param loop si es fa bucle (-1=infinit, 0=no repeteix, 1=repeteix 1 vegada...)
/// @return número del canal en que està sonant el só
const int playSound(sound *snd, const int loop = 0);
/// @brief Pausa un canal en el que s'estava reproduïnt un só
/// @param channel número del canal a pausar
void pauseChannel(const int channel);
/// @brief Continua un canal pausat
/// @param channel número del canal pausat
void resumeChannel(const int channel);
/// @brief Para un canal que estava reproduïnt un só
/// @param channel número del canal a parar
void stopChannel(const int channel);
/// @brief Obté l'estat d'un canal
/// @param channel canal del que es vol obtindre l'estat
/// @return estat del canal (CHANNEL_INVALID, CHANNEL_FREE, CHANNEL_PLAYING o CHANNEL_PAUSED)
const channel_state getChannelState(const int channel);
/// @brief Allibera un só
/// @param snd punter al só
void deleteSound(sound *snd);
/// @brief Estableix el volum general
/// @param volume valor a establir com a volum (128 màxim)
/// @return el volum anterior
const int setVolume(int volume);
}

277
source/jdraw.cpp Normal file
View File

@@ -0,0 +1,277 @@
#include "jdraw.h"
#include "SDL2/SDL.h"
#include "gif.c"
#include "jfile.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
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 de oritge
uint32_t palette[256]; // La paleta de colors
uint8_t transparent = 0; // El color transparent
// Inicialització de tot el que fa falta per a carregar gràfics i pintar en pantalla
void init(const std::string &titol, const uint16_t width, const uint16_t height, const int zoom)
{
// [TODO] Incloure gestió de pantalla completa
// Inicialització de les estructures de SDL
sdl_window = SDL_CreateWindow(titol.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width * zoom, height * zoom, SDL_WINDOW_SHOWN);
sdl_renderer = SDL_CreateRenderer(sdl_window, -1, 0);
sdl_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height);
// Establim el tamany "logic", indepndent del tamany de finestra
SDL_RenderSetLogicalSize(sdl_renderer, width, height);
// Creem la superficie "screen" i la establim com a superficie destinació
screen = createSurface(width, height);
destination = screen;
transparent = 0;
}
// Finalització del sistema
void quit()
{
// Si la superficie "screen" existia, alliberem la seua memòria
if (screen != nullptr)
{
freeSurface(screen);
}
// Destruim tot el relacionat amb SDL
SDL_DestroyTexture(sdl_texture);
SDL_DestroyRenderer(sdl_renderer);
SDL_DestroyWindow(sdl_window);
// 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;
}
// 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 std::string &filename)
{
// 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);
// 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;
}
// 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;
}
// Estableix la paleta del sistema carregant-la d'un GIF
void loadPalette(const std::string &filename)
{
// 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);
// Copiem eixe array al nostre array de la paleta de sistema. Ara ja tenim la paleta carregada.
memcpy(palette, pal, 1024); // 32 bits per entrada == 4 bytes x 256 entrades == 1024
// Alliberem el array que ens habia tornat LoadPalette()
free(pal);
// I també el buffer del arxiu
free(buffer);
}
// 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 la coordenada està dins del rang que abarca la superficie,
// escriure el byte "color" on toca en el array de pixels de la superficie
if (color != transparent && x >= 0 && y >= 0 && x < surface->w && y < surface->h)
{
surface->pixels[x + y * surface->w] = 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 la coordenada està dins del rang que abarca la superficie,
// tornar el byte que toca del array de pixels de la superficie
if (x >= 0 && y >= 0 && x < surface->w && y < surface->h)
{
return surface->pixels[x + y * surface->w];
}
// Si no, algo tenim que tornar... pos "0"
return 0;
}
// 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 int flip = DRAW_FLIP_NONE)
{
// 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
}
}
// 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 int size = screen->w * screen->h; // tamany de la superficie
// 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]];
}
// Desbloquejem la textura
SDL_UnlockTexture(sdl_texture);
// Pintem la textura a pantalla
SDL_RenderCopy(sdl_renderer, sdl_texture, NULL, NULL);
// I ho presentem
SDL_RenderPresent(sdl_renderer);
}
}

78
source/jdraw.h Normal file
View File

@@ -0,0 +1,78 @@
#pragma once
#include <string>
#define DRAW_FLIP_NONE 0
#define DRAW_FLIP_HORIZONTAL 1
#define DRAW_FLIP_VERTICAL 2
#define DRAW_FLIP_BOTH 3
// Unitat per a la gestió dels recursos gràfics i dibuixat en pantalla
namespace draw
{
// Estructura per a mantindre una superficie de pintat, la "pantalla virtual" de tota la vida
struct surface
{
uint16_t w; // Ample de la superficie
uint16_t h; // Alt de la superficie
uint8_t *pixels; // pixels de la superficie
};
/// @brief Inicialització de tot el que fa falta per a carregar gràfics i pintar en pantalla.
/// @brief La finestra serà width*zoom x height*zoom de gran.
/// @param titol es el text que apareixerà en la finestra
/// @param width es el ample de la finestra "virtual"
/// @param height es el alt de la finestra "virtual"
/// @param zoom es com de grans son els pixels.
void init(const std::string &titol, const uint16_t width, const uint16_t height, const int zoom);
/// @brief Finalització del sistema (tancar coses de SDL, superficies fixes, etc...)
void quit();
/// @brief Crea una superficie i torna un punter a ella
/// @param w ample de la superficie
/// @param h alt de la superficie
/// @return un punter a una nova superficie
surface *createSurface(const uint16_t w, const uint16_t h);
/// @brief Carrega un gràfic d'un arxiu (en format GIF) a una nova superficie, i torna un punter a ella
/// @param filename nom de l'arxiu GIF d'on carregar la superficie
/// @return un punter a una nova superficie
surface *loadSurface(const std::string &filename);
/// @brief Allibera la memòria d'una superficie, els seus pixels inclosos
/// @param surf punter a la superficie a alliberar
void freeSurface(surface *surf);
/// @brief Estableix una superficie com a superficie que rebrà les funcions de pintat (especificar nullptr per a pintar a pantalla)
/// @param surf punter a la superficie a establir com a destinació
void setDestination(surface *surf);
/// @brief Estableix una superficie com a superficie de la que s'agafaràn els gràfics per a pintar
/// @param surf punter a la superficie a establir com a oritge
void setSource(surface *surf);
/// @brief Estableix la paleta del sistema carregant-la d'un GIF
/// @param filename nom de l'arxiu GIF d'on carregar la paleta
void loadPalette(const std::string &filename);
/// @brief Esborra la superficie "destination" amb el color especificat
/// @param color color a usar per a borrar la superficie de destinació
void cls(const uint8_t color);
/// @brief Estableix el color especificat com a transparent
/// @param color color a usar com a transparent
void setTrans(const uint8_t color);
/// @brief Pinta un troç de la superficie "source" en la superficie "destination".
/// @param dx coordenada x de la destinació
/// @param dy coordenada y de la destinació
/// @param w ample del quadrat a pintar
/// @param h alt del quadrat a pintar
/// @param sx coordenada x de l'oritge
/// @param sy coordenada y de l'oritge
/// @param flip si s'ha de fer flip en hortizontal o vertical (o ambdos)
void draw(const int dx, const int dy, const int w, const int h, const int sx, const int sy, const int flip = DRAW_FLIP_NONE);
/// @brief Refresca la pantalla
void render();
}

422
source/jfile.cpp Normal file
View File

@@ -0,0 +1,422 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include "jfile.h"
#include <sys/stat.h>
#include <unistd.h>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <string>
#include <vector>
#ifndef _WIN32
#include <pwd.h>
#endif
#define DEFAULT_FILENAME "data.jf2"
#define DEFAULT_FOLDER "data/"
#define CONFIG_FILENAME "config.txt"
/* FORMAT DEL ARXIU .JF2
4 bytes header capçalera "PK2" (caracter 0 de final de cadena al final) (en realitat passe de ella, pero be...)
4 bytes num_files nombre d'arxius inclosos
4 bytes toc_offset en quina posició de l'arxiu està la taula de continguts
... Ara venen tots els arxius inclosos, uno darrere de l'altre.
... Quan acaben ve la taula de continguts. La taula te tantes entrades com arxius inclosos. Cada entrada te el següent format:
(per cert, toc_offset apunta ací)
4 bytes offset en quina posició del arxiu de recursos comença este arxiu
4 bytes size tamany d'este arxiu
1 byte path_size tamany de la cadena amb la ruta de l'arxiu
path_size bytes path ruta de l'arxiu original. La usem per a trobar el arxiu que ens demanen.
EXEMPLE SIMPLE:
- Imaginem que volem incloure un arxiu "data/hola.txt" amb el contingut "HOLA", i un arxiu "data/adios.txt" amb el contingut "ADIOS":
OFFSET CONTINGUT TAMANY DESCRIPCIÓ
0 "PK2"+0 4 La capçalera
4 2 4 nombre d'arxius
8 21 4 offset a la taula de continguts
--- COMENCEN ELS ARXIUS INCLOSOS ---
12 HOLA 4 el contingut del primer arxiu
16 ADIOS 5 el contingut del segon arxiu
--- COMENÇA LA TAULA DE CONTINGUTS ---
21 12 4 offset al primer arxiu
25 4 4 tamany del primer axiu
29 13 1 tamany de la ruta al primer arxiu
30 "data/hola.txt" 13 la ruta al primer arxiu
43 16 4 offset al primer arxiu
47 4 4 tamany del primer axiu
51 13 1 tamany de la ruta al primer arxiu
52 "data/adios.txt" 14 la ruta al primer arxiu
- Es un exemple raro, perque ocupa mes la ruta al arxiu que l'arxiu en si, pero espere que la idea quede clara!
Al principi se carrega la tabla de continguts en memòria, així el acces als arxius es ràpid.
Y com funciona tot açò? pos per defecte va a intentar llegir tots els arxius de "data.jf2". Si no troba e l'arxiu, automaticament passa a
buscar-ho tot en la carpeta "data" en arxius independents. En principi, si no se tenen requeriments diferents, no fa falta configurar res.
Respecte al tema de l'arxiu de configuració, està montat de forma que se pot escriure i llegir valors asociats a una clau sense calfar-se el cap.
En l'arxiu de configuració els valor se guarden de la forma:
CLAU=VALOR
Cada un en una linea. Si llegim i no existeix, torna cadena buida. Si escrivim i ja exisita, se reemplaça.
Tot son valors de cadena. Si en realitat son números, tindràs que encarregar-te tu de la caonversió de cadena a numero o al reves.
*/
namespace file
{
// Estructures
// ===============================================================================================================================
// Estructura que representa un arxiu en la tabla de continguts del arxiu de recursos
struct file_t
{
std::string path; // Ruta relativa de l'arxiu
uint32_t size; // Tamany de l'arxiu
uint32_t offset; // Offset a l'arxiu dins de l'arxiu de recursos
};
std::vector<file_t> toc; // vector que conté la taula de continguts de l'arxiu de recursos
/* El std::map me fa coses rares, vaig a usar un good old std::vector amb una estructura key,value propia i au, que sempre funciona */
struct keyvalue_t
{
std::string key, value;
};
// Variables
// ===============================================================================================================================
static std::string resource_filename = ""; // Nom de l'arxiu de recursos
static std::string resource_folder = ""; // Nom de la carpeta de recursos
static int file_source = SOURCE_FILE; // D'on anem a pillar els recursos, arxiu o carpeta
static std::string config_folder; // Nom de la carpeta on guardar la configuració
std::vector<keyvalue_t> config; // Vector amb els valors guardats a l'arxiu de configuració
// Funcions
// ===============================================================================================================================
// Estableix el nom de l'arxiu on estàn guardats els recursos (es "data.jf2" per defecte)
void setResourceFilename(const std::string str)
{
resource_filename = str;
}
// Estableix el nom de la carpeta on estàn guardats els recursos (es "data" per defecte)
void setResourceFolder(const std::string str)
{
resource_folder = str;
}
// Estableix d'on s'han de obtenir els recursos (arxius individuals dins d'una carpeta o arxiu de recursos)
void setSource(const int src)
{
file_source = src % 2; // mod 2 de forma que sempre es un valor vàlid, 0 (arxiu) o 1 (carpeta)
// Si volem que busque en carpeta i encara no haviem especificat una carpeta, usem la per defecte
if (src == SOURCE_FOLDER && resource_folder == "")
{
setResourceFolder(DEFAULT_FOLDER);
}
}
// Carreguem la taula de continguts de l'arxiu de recursos
bool getTableOfContents()
{
// Si encara no haviem especificat un arxiu de recursos, usem el arxiu de recursos per defecte
if (resource_filename == "")
{
setResourceFilename(DEFAULT_FILENAME);
}
// Si l'arxiu de recursos existeix...
std::ifstream fi(resource_filename, std::ios::binary);
if (fi.is_open())
{
// Llegim la capçalera (controlar que siga correcta?)
char header[4];
fi.read(header, 4);
// LLegim el nombre d'arxius i la posició de la taula de continguts
uint32_t num_files, toc_offset;
fi.read((char *)&num_files, 4);
fi.read((char *)&toc_offset, 4);
// Anem a la taula de continguts
fi.seekg(toc_offset);
// Per a cada arxiu inclos en l'arxiu de recursos...
for (int i = 0; i < num_files; ++i)
{
// Llegim en quina posició està i quant copua
uint32_t file_offset, file_size;
fi.read((char *)&file_offset, 4);
fi.read((char *)&file_size, 4);
// Llegim la seua ruta
uint8_t path_size;
fi.read((char *)&path_size, 1);
char file_name[path_size + 1];
fi.read(file_name, path_size);
file_name[path_size] = 0;
std::string filename = file_name;
// Y afegim les dades a la taula de continguts en memòria
toc.push_back({filename, file_size, file_offset});
}
// Tanquem la paradeta i tornem true
fi.close();
return true;
}
else // Si no s'ha pogut llegir el arxiu de recursos, tornem false
{
return false;
}
}
// Obté un "FILE*" al arxiu que se li demana, independentment de la font (arxius individual en carpeta, o arxiu de recursos)
FILE *getFilePointer(const std::string resourcename, int &filesize, const bool binary)
{
// Si tenim configurat agafar els recursos de arxiu, pero encara no tenim la taula de continguts carregada...
if (file_source == SOURCE_FILE and toc.size() == 0)
{
// Si fallem al intentar carregar la taula de continguts de l'arxiu de recursos, canviem a pillar els recursos de carpeta
if (not getTableOfContents())
{
setSource(SOURCE_FOLDER);
}
}
FILE *f;
// Si estem pillant els recursos de un arxiu de recursos...
if (file_source == SOURCE_FILE)
{
// Busquem el recurs en la taula de continguts usant la ruta
bool found = false;
uint32_t count = 0;
while (!found && count < toc.size())
{
found = (resourcename == toc[count].path);
if (!found)
{
count++;
}
}
// Si no trobem el recurs, petem el mame
if (!found)
{
// [TODO] Donar mes informació de quin recurs no havem trobat
perror("El recurs no s'ha trobat en l'arxiu de recursos");
exit(1);
}
// Agafem el tamany del recurs de la taula de continguts
filesize = toc[count].size;
// obrim l'arxiu de recursos
f = fopen(resource_filename.c_str(), binary ? "rb" : "r");
if (!f) // En el raruno cas de que a este altures pete al obrir el arxiu de recursos, petem el mame
{
// [TODO] Donar mes informació de quin recurs no havem trobat
perror("No s'ha pogut obrir l'arxiu de recursos");
exit(1);
}
// Anem a la posició on està el recurs que volem. Amb açò "f" ja està preparat per a ser tornar.
// Ojo, realment estic tornant un FILE* al arxiu de recursos, pero ja apuntant al moment en que comença el recurs que volem.
// Ho dic perque si fem fseek(f, 0, SEEK_SET) tornarà al principi de l'arxiu de recursos, no del recurs. Tindre-ho en compte.
fseek(f, toc[count].offset, SEEK_SET);
}
else
{
// Si estem pillant els recursos de carpeta, simplement obrim el arxiu en questió i tornem el FILE* associat.
f = fopen((resource_folder + resourcename).c_str(), binary ? "rb" : "r");
fseek(f, 0, SEEK_END);
filesize = ftell(f);
fseek(f, 0, SEEK_SET);
}
// Tornar el punter FILE* al arxiu. OJO! Tenim que tancar-lo quan acabem amb ell
return f;
}
// Obté un buffer de memòria en format "char*" del arxiu que se li demana, independentment de la font (arxius individual en carpeta, o arxiu de recursos)
char *getFileBuffer(const std::string resourcename, int &filesize)
{
// Usem la funció anterior per a obtinde un FILE*, independentment de on pillem els recursos
FILE *f = getFilePointer(resourcename, filesize, true);
// Reservem memòria per al buffer
char *buffer = (char *)malloc(filesize);
// llegim el contingut del arxiu i el fiquem en el buffer
fread(buffer, filesize, 1, f);
// Tanquem l'arxiu
fclose(f);
// Tornem el buffer. OJO! recordar alliberar-lo amb free(buffer) quan acabem amb ell.
return buffer;
}
// Estableix el nom de la carpeta on es guardarà la configuració
// Adaptat del codi que va escriure JailDesigner en el JailDoctor's Dilemma
// Vull revisar-la tranquilament algun dia
void setConfigFolder(const std::string foldername)
{
#ifdef _WIN32
config_folder = std::string(getenv("APPDATA")) + "/" + foldername;
#elif __APPLE__
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
config_folder = std::string(homedir) + "/Library/Application Support/" + foldername;
#elif __linux__
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
config_folder = std::string(homedir) + "/." + foldername;
#endif
struct stat st = {0};
if (stat(config_folder.c_str(), &st) == -1)
{
#ifdef _WIN32
int ret = mkdir(config_folder.c_str());
#else
int ret = mkdir(config_folder.c_str(), S_IRWXU);
#endif
if (ret == -1)
{
printf("ERROR CREATING CONFIG FOLDER.");
exit(EXIT_FAILURE);
}
}
}
// Obté el nom de la carpeta on es guardarà la configuració
const std::string getConfigFolder()
{
return config_folder + "/";
}
// Carrega tots els valors guardats a l'arxiu de recursos
void loadConfigValues()
{
// Buidem la taula de claus-valors en memòria
config.clear();
// Obrim l'arxiu de configuració
std::string config_file = config_folder + "/config.txt";
FILE *f = fopen(config_file.c_str(), "r");
if (f)
{
// Agafem linea a linea
char line[1024];
while (fgets(line, sizeof(line), f))
{
// Separem la clau del valor
char *value = strchr(line, '=');
if (value)
{
*value = '\0';
value++;
value[strlen(value) - 1] = '\0';
// i els afegim a la taula de claus-valors en memòria
config.push_back({line, value});
}
}
// tanquem la paradeta
fclose(f);
}
}
// Guardem tots els valors en la taula de claus-valors a l'arxiu de configuració
void saveConfigValues()
{
// Obrim l'arxiu de configuració
std::string config_file = config_folder + "/config.txt";
FILE *f = fopen(config_file.c_str(), "w");
if (f)
{
// Guardem cada parella clau/valor de la taula en memòria en una linea en format "clau=valor\n" en l'arxiu de configuració
for (auto pair : config)
{
fprintf(f, "%s=%s\n", pair.key.c_str(), pair.value.c_str());
}
// tanquem la paradeta
fclose(f);
}
}
// Obté un valor de l'arxiu de configuració per a la clau donada (o cadena buida si no existeix)
const std::string getConfigValue(const std::string key)
{
// Si la taula de claus-valors esta buida, la carreguem de l'arxiu de configuració
if (config.empty())
{
loadConfigValues();
}
// busquem la clau en la taula
for (auto pair : config)
{
if (pair.key == std::string(key))
{
// Si la trobem, tornem el seu valor
return pair.value;
}
}
// Si no la trobem, tornem cadena buida
return "";
}
// Estableix un valor en l'arxiu de configuració per a la clau donada
void setConfigValue(const std::string key, const std::string value)
{
// Si la taula de claus-valors esta buida, la carreguem de l'arxiu de configuració
if (config.empty())
{
loadConfigValues();
}
// busquem la clau en la taula
for (auto &pair : config)
{
if (pair.key == std::string(key))
{
// Si la trobem, actualitzem el seu valor i guardem els canvis a l'arxiu de configuració
pair.value = value;
saveConfigValues();
return;
}
}
// Si NO la trobem, afegim la nova clau i el seu valor, i guardem els canvis a l'arxiu de configuració
config.push_back({key, value});
saveConfigValues();
}
}

60
source/jfile.h Normal file
View File

@@ -0,0 +1,60 @@
#pragma once
#include <stdio.h>
#include <string>
#define SOURCE_FILE 0
#define SOURCE_FOLDER 1
// Unitat per a la gestió de l'acces a arxius
namespace file
{
// Funcions d'acces als recursos (gràfics, musiques...)
// -----------------------------------------------------------------------------------------------------------
/// @brief Estableix el nom de l'arxiu on estàn guardats els recursos (es "data.jf2" per defecte)
/// @param str nom de l'arxiu de recursos
void setResourceFilename(const std::string str);
/// @brief Estableix el nom de la carpeta on estàn guardats els recursos (es "data" per defecte)
/// @param str nom de la carpeta de recursos
void setResourceFolder(const std::string str);
/// @brief Estableix d'on s'han de obtenir els recursos (arxius individuals dins d'una carpeta o arxiu de recursos)
/// @param src SOURCE_FILE o SOURCE_FOLDER, si es vol que se pillen recursos de arxiu o de carpeta
void setSource(const int src);
/// @brief Obté un "FILE*" al arxiu que se li demana, independentment de la font (arxius individual en carpeta, o arxiu de recursos). Recordar tancar-lo al acabar amb ell.
/// @param resourcename el nom de l'arxiu que volem obrir
/// @param filesize paràmetre de retorn. Ací es torna el tamany de l'arxiu
/// @param binary si volem obrir el arxiu en format binary
/// @return un punter FILE* al arxiu
FILE *getFilePointer(const std::string resourcename, int &filesize, const bool binary = false);
/// @brief Obté un buffer de memòria en format "char*" del arxiu que se li demana, independentment de la font (arxius individual en carpeta, o arxiu de recursos).
/// @brief Recordar alliberar la memoria del buffer amb free(buffer) al acabar amb ell.
/// @param resourcename el nom de l'arxiu del que volem obrindre el buffer
/// @param filesize paràmetre de retorn. Ací es torna el tamany de l'arxiu
/// @return un array de "filesize" bytes amb el contingut del arxiu
char *getFileBuffer(const std::string resourcename, int &filesize);
// Funcions d'access a la configuració (clau = valor)
// -----------------------------------------------------------------------------------------------------------
/// @brief Estableix el nom de la carpeta on es guardarà la configuració
/// @param foldername nom de la carpeta
void setConfigFolder(const std::string foldername);
/// @brief Obté el nom de la carpeta on es guardarà la configuració
/// @return nom de la carpeta
const std::string getConfigFolder();
/// @brief Obté un valor de l'arxiu de configuració per a la clau donada (o cadena buida si no existeix)
/// @param key clau de la que obtindre el valor
/// @return el valor de la clau especificada
const std::string getConfigValue(const std::string key);
/// @brief Estableix un valor en l'arxiu de configuració per a la clau donada
/// @param key clau a la que establir un valor
/// @param value valor a establir per a la clau
void setConfigValue(const std::string key, const std::string value);
}

7
source/main.cpp Normal file
View File

@@ -0,0 +1,7 @@
#include "jfile.h"
#include "jdraw.h"
int main(int argc, char *argv[])
{
}