diff --git a/.gitignore b/.gitignore index 28c84d9..ccef8c9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ *.jpg *.svg zxfy +compile_flags.txt diff --git a/zxfy.c b/zxfy.c index 205b277..83410db 100644 --- a/zxfy.c +++ b/zxfy.c @@ -36,6 +36,8 @@ #include #define ZX_VMEM_SIZE 6912 // ZX Spectrum video mem size. Bitmap + attributes. +#define ZX_WIDTH 256 // TODO:: update it in the code +#define ZX_HEIGHT 192 // TODO:: update it in the code static const uint32_t zxpalette[16] = { 0x000000, // std black @@ -56,6 +58,8 @@ static const uint32_t zxpalette[16] = { 0xFFFFFF, // bright white }; +/* ============================== SDL functions ============================= */ + /* SDL initialization function. */ static SDL_Texture *sdlInit(int width, int height, int fullscreen, SDL_Renderer **rp) { int flags = SDL_WINDOW_OPENGL; @@ -123,6 +127,8 @@ static void processSdlEvents(void) { } } +/* ============================== PNG functions ============================= */ + /* Write a PNG file. The image is passed with row_pointers as an RGB image. */ int PngWrite(FILE *fp, int width, int height, png_bytep *row_pointers) { @@ -248,6 +254,8 @@ unsigned char *PngLoad(FILE *fp, int *widthptr, int *heightptr, int *alphaptr) { return rgb; } +/* ======================== Simulated annealing helpers ===================== */ + /* Compute the difference between two RGB frame buffers. * The differece is the sum of the differences of every pixel at the same * coordinates in the two images. The returnd value is the percentage of @@ -299,12 +307,6 @@ float computeDiff(unsigned char *a, unsigned char *b, int width, int height) { return (float)d/(width*height*442)*100; } -void showHelp(char *progname) { - fprintf(stderr, - "Usage: %s \n" ,progname); - exit(1); -} - void mutate(unsigned char *zxmem, int count, int gen) { for (int j = 0; j < count; j++) { uint32_t byte = rand() % ZX_VMEM_SIZE; @@ -339,6 +341,8 @@ void mutate(unsigned char *zxmem, int count, int gen) { } } +/* ================================ ZX helpers ============================== */ + // Render the ZX Spectrum VRAM into the framebuffer. void zx2rgb(unsigned char *fb, unsigned char *zxmem) { for (int y = 0; y < 192; y++) { @@ -381,6 +385,163 @@ void zx2rgb(unsigned char *fb, unsigned char *zxmem) { } } +/* Fill the `zxmem` buffer for the block at position (bx, by) using the provided + * color information (`paper_idx`, `ink_idx`) and the pixel `matrix`. */ +void fill_zx_buff(unsigned char *zxmem, int bx, int by, + int ink_idx, int paper_idx, const char *matrix){ + /* Write attribute */ + int attr_offset = 6144 + (by * 32) + bx; + int bright = (ink_idx > 7 || paper_idx > 7) ? 1 : 0; + + uint8_t p = paper_idx & 7; + uint8_t i = ink_idx & 7; + + // Construct Byte: [Flash 0] [Bright] [Paper G R B] [Ink G R B] + uint8_t attr_byte = (bright << 6)| (p << 3) | i; + zxmem[attr_offset] = attr_byte; + + /* Write pixel */ + for (int dy = 0; dy < 8; dy++) { + uint8_t pixel_byte = 0; + for (int dx = 0; dx < 8; dx++) { + // Check matrix at current position + if (matrix[dy * 8 + dx] == 1) { + pixel_byte |= (1 << (7 - dx)); + } + } + int y = (by * 8) + dy; + uint16_t vram_addr = ((y & 0xC0) << 5) | ((y & 0x07) << 8) + | ((y & 0x38) << 2) | bx; + + zxmem[vram_addr] = pixel_byte; + } +} + +/* ============================== Init helpers ============================== */ + +/* Compute the squared Euclidean distance between the color `zxpalette[zx_idx]` + * and the RGB color provided as a 3-byte array (R, G, B). */ +int distance_rgb2zx(const char zx_idx, const unsigned char *rgb){ + // Cast uint32_t array to a unsigned char pointer for direct access + // This assumes Little Endian (Standard on x86/ARM) + const unsigned char *palette_bytes = (const unsigned char *)zxpalette; + const unsigned char *zx_color = palette_bytes + (zx_idx * 4); + + int dr = (int)rgb[0] - (int)zx_color[0]; + int dg = (int)rgb[1] - (int)zx_color[1]; + int db = (int)rgb[2] - (int)zx_color[2]; + + int distance = (dr * dr) + (dg * dg) + (db * db); + return distance; +} + +/* Find the index (0-15) in `zxpalette` that is closest to the given + * 3-byte RGB array, using squared Euclidean distance. */ +char nearest_zx_clr(const unsigned char *rgb) { + char nearest_clr = 0; + int min_err = INT_MAX; + + for (int i = 0; i < 16; i++) { + int error = distance_rgb2zx(i, rgb); + if (error < min_err) { + min_err = error; + nearest_clr = i; + } + } + return nearest_clr; +} + +/* Evaluate the best Paper and Ink colors for the 8x8 block starting at + * pixel coordinates (x, y). The colors are selected based on frequency. */ +void blk_colors(unsigned char *image, int x, int y, char* matrix, + char* paper_clr, char* ink_clr){ + + char clr_freq[64] = {0}; + /* Compute color frequency distribution */ + for(int dx = 0; dx < 8; dx++){ + for(int dy = 0; dy < 8; dy++){ + int i = ((y+dy)*ZX_WIDTH+(x+dx))*3; // get block pixel + + unsigned char rgb[3]; + rgb[0] = image[i]; + rgb[1] = image[i+1]; + rgb[2] = image[i+2]; + + int zx_clr = nearest_zx_clr(rgb); + /* NOTE: The variable `matrix` is improperly used here to store the + * index of the nearest zx color. This allow to speed up the color + * assign process in `init_zx_block`. */ + matrix[dy*8+dx] = zx_clr; + clr_freq[zx_clr]++; + } + } + + /* Evaluate Paper and Ink color as the two most frequent. */ + int max1 = -1; + int max2 = -1; + for(int i = 0; i < 16; i++){ + if (clr_freq[i] == 0) continue; + if(clr_freq[i] > max1){ + max2 = max1; + *ink_clr = *paper_clr; + max1 = clr_freq[i]; + *paper_clr = i; + } else if (clr_freq[i] > max2) { + max2 = clr_freq[i]; + *ink_clr = i; + } + } +} + +/* Given the block coordinate (x0, y0) the function compute: the Paper color, + * the Ink color and the matrix.*/ +void init_zx_block(unsigned char *image, int x0, int y0, + char *paper_clr, char *ink_clr, char *matrix){ + + *paper_clr = 0; + *ink_clr = 0; + blk_colors(image, x0, y0, matrix, paper_clr, ink_clr); + + /* Assign paper (0) or ink (1) color to each pixel in the block. */ + for(int dx = 0; dx < 8; dx++){ + for(int dy = 0; dy < 8; dy++){ + if (matrix[dy*8+dx] == *paper_clr) matrix[dy*8+dx]=0 ; + else if (matrix[dy*8+dx] == *ink_clr) matrix[dy*8+dx]=1 ; + else { + int i = ((y0+dy)*ZX_WIDTH+(x0+dx))*3; + unsigned char rgb[3]; + rgb[0] = image[i]; + rgb[1] = image[i+1]; + rgb[2] = image[i+2]; + + int dist_paper = distance_rgb2zx(*paper_clr,rgb); + int dist_ink = distance_rgb2zx(*ink_clr,rgb); + matrix[dy*8+dx] = (dist_paper < dist_ink) ? 0 : 1; + } + } + } +} + +/* Initialize the first guess for the simulated annealing. */ +void init_best(unsigned char *image, unsigned char *zx_buf){ + /* Loop on the ZX blocks. */ + for(int y = 0; y < ZX_HEIGHT/8; y++){ + for(int x = 0; x < ZX_WIDTH/8; x++){ + char paper_clr = 0; // block paper color idx + char ink_clr = 0; // block ink color idx + char matrix[64]; // block pixel value + + init_zx_block(image, x*8, y*8, &paper_clr, &ink_clr, matrix); + fill_zx_buff(zx_buf, x, y, ink_clr, paper_clr, matrix); + } + } +} +void showHelp(char *progname) { + fprintf(stderr, + "Usage: %s \n" ,progname); + exit(1); +} + int main(int argc, char **argv) { FILE *fp; @@ -423,16 +584,16 @@ int main(int argc, char **argv) texture = sdlInit(width,height,0,&renderer); fb = malloc(width*height*3); best = malloc(ZX_VMEM_SIZE); - for (int j = 0; j < ZX_VMEM_SIZE; j++) best[j] = rand(); - for (int j = 256*192/8; j < ZX_VMEM_SIZE; j++) best[j] = 7; // white fg new = malloc(ZX_VMEM_SIZE); - /* Show the current evolved image and the real image for one second each. */ + init_best(image, best); + + /* Show the real image and the current evolved image for one second each. */ + sdlShowRgb(texture,renderer,image,width,height); + sleep(1); zx2rgb(fb,best); sdlShowRgb(texture,renderer,fb,width,height); sleep(1); - sdlShowRgb(texture,renderer,image,width,height); - sleep(1); /* Evolve the current solution using simulated annealing. */ uint64_t generation = 0; @@ -460,7 +621,7 @@ int main(int argc, char **argv) if (generation % 1000 == 0) { zx2rgb(fb,best); sdlShowRgb(texture,renderer,fb,width,height); - printf("gen:%llu: diff:%f%% temp:%g\n", generation, percdiff, + printf("gen:%lu: diff:%f%% temp:%g\n", generation, percdiff, temperature); } processSdlEvents();