Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
*.jpg
*.svg
zxfy
compile_flags.txt
185 changes: 173 additions & 12 deletions zxfy.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
#include <SDL.h>

#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
Expand All @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 <filename.png>\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;
Expand Down Expand Up @@ -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++) {
Expand Down Expand Up @@ -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 <filename.png>\n" ,progname);
exit(1);
}

int main(int argc, char **argv)
{
FILE *fp;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down