commit 08f41605032d3fabda5e4c68535f41818258b28a Author: JailDoctor Date: Tue Feb 28 17:03:40 2023 +0100 - Initial commit diff --git a/source/gif.c b/source/gif.c new file mode 100644 index 0000000..8c19b14 --- /dev/null +++ b/source/gif.c @@ -0,0 +1,478 @@ +#include +#include +#include +#include + +#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> 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 \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 ); +}*/ diff --git a/source/jaudio.cpp b/source/jaudio.cpp new file mode 100644 index 0000000..1024f11 --- /dev/null +++ b/source/jaudio.cpp @@ -0,0 +1,142 @@ +#include "jaudio.h" +#include +#include +#include + +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); + } +} \ No newline at end of file diff --git a/source/jaudio.h b/source/jaudio.h new file mode 100644 index 0000000..388e220 --- /dev/null +++ b/source/jaudio.h @@ -0,0 +1,97 @@ +#pragma once +#include + +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); +} \ No newline at end of file diff --git a/source/jdraw.cpp b/source/jdraw.cpp new file mode 100644 index 0000000..b78c1be --- /dev/null +++ b/source/jdraw.cpp @@ -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); + } +} diff --git a/source/jdraw.h b/source/jdraw.h new file mode 100644 index 0000000..272bcf2 --- /dev/null +++ b/source/jdraw.h @@ -0,0 +1,78 @@ +#pragma once +#include + +#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(); +} diff --git a/source/jfile.cpp b/source/jfile.cpp new file mode 100644 index 0000000..87cc813 --- /dev/null +++ b/source/jfile.cpp @@ -0,0 +1,422 @@ +#include +#include +#include +#include +#include "jfile.h" +#include +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#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 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 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(); + } +} \ No newline at end of file diff --git a/source/jfile.h b/source/jfile.h new file mode 100644 index 0000000..f356ba3 --- /dev/null +++ b/source/jfile.h @@ -0,0 +1,60 @@ +#pragma once +#include +#include + +#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); +} diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..576eeb1 --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,7 @@ +#include "jfile.h" +#include "jdraw.h" + +int main(int argc, char *argv[]) +{ + +} \ No newline at end of file