diff --git a/include/mazemaker.h b/include/mazemaker.h index fc295f4..6e43b5f 100644 --- a/include/mazemaker.h +++ b/include/mazemaker.h @@ -38,13 +38,18 @@ void mazemaker_generate_maze_opt(int width, int height, mazegrid_t* result, maze void mazemaker_free_maze(mazegrid_t* maze); int mazemaker_maze_to_png(mazegrid_t const* maze, char const* filename); int mazemaker_maze_to_png_opt(mazegrid_t const* maze, char const* filename, mazeoptions_t const*); +int mazemaker_path_to_png(maze_path_t const* path, char const* filename); +int mazemaker_path_to_png_opt(maze_path_t const* path, char const* filename, mazeoptions_t const*); int mazemaker_maze_to_png_mem(mazegrid_t const* maze, size_t* len, uint8_t** buf); int mazemaker_maze_to_png_mem_opt(mazegrid_t const* maze, size_t* len, uint8_t** buf, mazeoptions_t const*); +int mazemaker_path_to_png_mem(maze_path_t const* maze, size_t* len, uint8_t** buf); +int mazemaker_path_to_png_mem_opt(maze_path_t const* maze, size_t* len, uint8_t** buf, mazeoptions_t const*); mazeoptions_t* mazemaker_options_new(); void mazemaker_options_free(mazeoptions_t*); int mazemaker_options_set_wall_color(mazeoptions_t*, char const* color_desc); int mazemaker_options_set_background_color(mazeoptions_t*, char const* color_desc); +int mazemaker_options_set_path_color(mazeoptions_t*, char const* color_desc); void mazemaker_options_set_seed(mazeoptions_t*, unsigned int seed); int mazemaker_solve(mazegrid_t const* maze, maze_path_t* path); diff --git a/lib/image.c b/lib/image.c index c7d8295..f302250 100644 --- a/lib/image.c +++ b/lib/image.c @@ -37,6 +37,94 @@ static void setBox(img_data_t* img, int x, int y, int w, int h, char v) { } } +static void setBoxCoords(img_data_t* img, int x1, int y1, int x2, int y2, char v) { + if (x1 > x2) setBoxCoords(img, x2, x1, y1, y2, v); + else if (y1 > y2) setBoxCoords(img, x1, x2, y2, y1, v); + else setBox(img, x1, y1, (x2 - x1 + 1), (y2 - y1 + 1), v); +} + +#if 0 +static void printImageData(img_data_t const* img) { + FILE *ptr = fopen("output.txt", "wt"); + for (int i = 0; i < img->h; i++) { + int flip_i = img->h - 1 - i; + for (int j = 0; j < img->w; j++) { + char v = img->data[img->w * flip_i + j]; + if (v) + fprintf(ptr, "#"); + else + fprintf(ptr, "."); + } + fprintf(ptr, "\n"); + } + fclose(ptr); +} +#endif // 0 + +static int _box_x(int x) { + return MARGIN + LINE_THICKNESS + BLOCK_SIZE * x; +} + +static int _box_y(int y, int height) { + return height - MARGIN - LINE_THICKNESS - BLOCK_SIZE * y; +} + +void drawPathSegment(size_t x, size_t y, size_t new_x, size_t new_y, img_data_t* result) { + size_t img_height = result->h; + if ((x > new_x) || (y > new_y)) { + drawPathSegment(new_x, new_y, x, y, result); + return; + } + + // note: it is ALWAYS the case that either x == new_x OR y == new_y + // so the above should not result in infinite recursion + + if (y == new_y) { + // horizontal line (right) + setBox(result, _box_x(x) + BLOCK_SIZE / 2 - LINE_THICKNESS / 2, + _box_y(y, img_height) - BLOCK_SIZE / 2 - LINE_THICKNESS / 2, + BLOCK_SIZE + LINE_THICKNESS, + LINE_THICKNESS, + 1); + } else { + // vertical line (up) + setBox(result, _box_x(x) + BLOCK_SIZE / 2 - LINE_THICKNESS / 2, + _box_y(new_y, img_height) - BLOCK_SIZE / 2 - LINE_THICKNESS / 2, + LINE_THICKNESS, + BLOCK_SIZE + LINE_THICKNESS, + 1); + } +} + +static img_data_t* pathToImageData(maze_path_t const* path) { + size_t width = path->nodes[path->length - 1].x + 1, + height = path->nodes[path->length - 1].y + 1, + x, y; + img_data_t* result = malloc(sizeof(img_data_t)); + result->w = LINE_THICKNESS + width * BLOCK_SIZE + 2 * MARGIN; + result->h = LINE_THICKNESS + height * BLOCK_SIZE + 2 * MARGIN; + result->data = malloc(result->w * result->h); + //fprintf(stderr, "#########\n"); + + // set background + memset(result->data, 0, result->w * result->h); + + // set start and end points + x = y = 0; + setBox(result, MARGIN, _box_y(0, result->h) - BLOCK_SIZE / 2 - LINE_THICKNESS / 2, LINE_THICKNESS + BLOCK_SIZE / 2, LINE_THICKNESS, 1); + setBox(result, _box_x(width - 1) + BLOCK_SIZE / 2 - LINE_THICKNESS / 2, _box_y(height - 1, result->h) - BLOCK_SIZE / 2 - LINE_THICKNESS / 2, BLOCK_SIZE / 2 + LINE_THICKNESS, LINE_THICKNESS, 1); + + for (int i = 1; i < path->length; i++) { + size_t new_x = path->nodes[i].x, new_y = path->nodes[i].y; + drawPathSegment(x, y, new_x, new_y, result); + x = new_x; + y = new_y; + } + + //printImageData(result); + return result; +} + static img_data_t* gridToImageData(mazegrid_t const* g) { img_data_t* result = malloc(sizeof(img_data_t)); result->w = LINE_THICKNESS + g->width * BLOCK_SIZE + 2 * MARGIN; @@ -92,11 +180,103 @@ static void writeImageData(img_data_t const* img, png_structp png_ptr, mazeoptio } } +static void writePathImageData(img_data_t const* img, png_structp png_ptr, mazeoptions_t const* options) { + for (int i = 0; i < img->h; i++) { + png_byte row[4 * img->w]; + for (int j = 0; j < img->w; j++) { + if (img->data[i * img->w + j]) { + // wall color + row[4 * j] = options->path_color[0]; + row[4 * j + 1] = options->path_color[1]; + row[4 * j + 2] = options->path_color[2]; + row[4 * j + 3] = 0xff; + } else memset(row + 4 * j, 0, 4); + } + png_write_row(png_ptr, row); + } +} + static void freeImageData(img_data_t* img) { free(img->data); free(img); } +static int +prepare_png(png_structp* png_ptr, png_infop* info_ptr) { + *png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (*png_ptr == NULL) { + fprintf(stderr, "Could not allocate PNG write struct.\n"); + return 0; + } + + *info_ptr = png_create_info_struct(*png_ptr); + if (info_ptr == NULL) { + fprintf(stderr, "Could not allocate PNG info struct.\n"); + return 0; + } + + if (setjmp(png_jmpbuf(*png_ptr))) { + fprintf(stderr, "Error during PNG creation.\n"); + return 0; + } + + return 1; +} + +static void +cleanup_png(png_structp png_ptr, png_infop info_ptr) { + if (info_ptr != NULL) { + png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); + png_destroy_info_struct(png_ptr, &info_ptr); + } + if (png_ptr != NULL) png_destroy_write_struct(&png_ptr, (png_infopp)NULL); +} + +int mazemaker_path_to_png_opt(maze_path_t const* path, char const* filename, mazeoptions_t const* options) { + int code = 1; + img_data_t* img = pathToImageData(path); + FILE *f = NULL; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + + if (strcmp(filename, "-") == 0) { + f = stdout; + } else { + f = fopen(filename, "wb"); + if (NULL == f) { + fprintf(stderr, "Could not open %s for writing.\n", filename); + code = 0; + goto exit; + } + } + + if (prepare_png(&png_ptr, &info_ptr) == 0) { + code = 0; + goto exit; + } + + png_init_io(png_ptr, f); + + // Write header (8 bit color depth) + png_set_IHDR(png_ptr, info_ptr, img->w, img->h, + 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_write_info(png_ptr, info_ptr); + + writePathImageData(img, png_ptr, options); + + png_write_end(png_ptr, NULL); + +exit: + if (strcmp(filename, "-") != 0) { + if (f != NULL) fclose(f); + } + cleanup_png(png_ptr, info_ptr); + if (img != NULL) freeImageData(img); + return code; +} + int mazemaker_maze_to_png_opt(mazegrid_t const* g, char const* filename, mazeoptions_t const* options) { int code = 1; img_data_t* img = gridToImageData(g); @@ -115,25 +295,11 @@ int mazemaker_maze_to_png_opt(mazegrid_t const* g, char const* filename, mazeopt } } - png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (png_ptr == NULL) { - fprintf(stderr, "Could not allocate PNG write struct.\n"); + if (prepare_png(&png_ptr, &info_ptr) == 0) { code = 0; goto exit; } - info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == NULL) { - fprintf(stderr, "Could not allocate PNG info struct.\n"); - code = 0; - goto exit; - } - - if (setjmp(png_jmpbuf(png_ptr))) { - fprintf(stderr, "Error during PNG creation.\n"); - code = 0; - goto exit; - } png_init_io(png_ptr, f); @@ -152,11 +318,7 @@ exit: if (strcmp(filename, "-") != 0) { if (f != NULL) fclose(f); } - if (info_ptr != NULL) { - png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); - png_destroy_info_struct(png_ptr, &info_ptr); - } - if (png_ptr != NULL) png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + cleanup_png(png_ptr, info_ptr); if (img != NULL) freeImageData(img); return code; } @@ -168,6 +330,13 @@ int mazemaker_maze_to_png(mazegrid_t const* g, char const* filename) { return result; } +int mazemaker_path_to_png(maze_path_t const* path, char const* filename) { + mazeoptions_t* options = mazemaker_options_new(); // use default options + int result = mazemaker_path_to_png_opt(path, filename, options); + mazemaker_options_free(options); + return result; +} + static void append_png_data(png_structp png_ptr, png_bytep data, png_size_t length) { struct mem_encode* p = (struct mem_encode*)png_get_io_ptr(png_ptr); size_t nsize = p->size + length; @@ -192,22 +361,7 @@ int mazemaker_maze_to_png_mem_opt(mazegrid_t const* g, size_t* len, uint8_t** bu png_infop info_ptr = NULL; struct mem_encode menc = { .buffer = NULL, .size = 0 }; - png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (png_ptr == NULL) { - fprintf(stderr, "Could not allocate PNG write struct.\n"); - code = 0; - goto exit; - } - - info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == NULL) { - fprintf(stderr, "Could not allocate PNG info struct.\n"); - code = 0; - goto exit; - } - - if (setjmp(png_jmpbuf(png_ptr))) { - fprintf(stderr, "Error during PNG creation.\n"); + if (prepare_png(&png_ptr, &info_ptr) == 0) { code = 0; goto exit; } @@ -232,11 +386,7 @@ exit: if (!code && menc.buffer) { free(menc.buffer); } - if (info_ptr != NULL) { - png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); - png_destroy_info_struct(png_ptr, &info_ptr); - } - if (png_ptr != NULL) png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + cleanup_png(png_ptr, info_ptr); if (img != NULL) freeImageData(img); return code; } @@ -247,3 +397,47 @@ int mazemaker_maze_to_png_mem(mazegrid_t const* g, size_t* len, uint8_t** buf) { mazemaker_options_free(options); return result; } + +int mazemaker_path_to_png_mem_opt(maze_path_t const* path, size_t* len, uint8_t** buf, mazeoptions_t const* options) { + int code = 1; + img_data_t* img = pathToImageData(path); + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + struct mem_encode menc = { .buffer = NULL, .size = 0 }; + + if (prepare_png(&png_ptr, &info_ptr) == 0) { + code = 0; + goto exit; + } + + png_set_write_fn(png_ptr, &menc, append_png_data, png_no_flush); + + // Write header (8 bit color depth) + png_set_IHDR(png_ptr, info_ptr, img->w, img->h, + 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_write_info(png_ptr, info_ptr); + + writePathImageData(img, png_ptr, options); + + png_write_end(png_ptr, NULL); + + *len = menc.size; + *buf = menc.buffer; + +exit: + if (!code && menc.buffer) { + free(menc.buffer); + } + cleanup_png(png_ptr, info_ptr); + if (img != NULL) freeImageData(img); + return code; +} + +int mazemaker_path_to_png_mem(maze_path_t const* path, size_t* len, uint8_t** buf) { + mazeoptions_t* options = mazemaker_options_new(); // use default options + int result = mazemaker_path_to_png_mem_opt(path, len, buf, options); + mazemaker_options_free(options); + return result; +} diff --git a/lib/options.c b/lib/options.c index 1ac7598..57e3985 100644 --- a/lib/options.c +++ b/lib/options.c @@ -10,6 +10,8 @@ mazeoptions_t* mazemaker_options_new() { // set defaults memset(result->wall_color, 0, 3); memset(result->background_color, 0xff, 3); + result->path_color[0] = 0xff; + result->path_color[1] = result->path_color[2] = 0; result->seed = 0; result->seed_set = false; @@ -80,6 +82,10 @@ int mazemaker_options_set_background_color(mazeoptions_t* options, char const* c return stringToColor(color_desc, options->background_color); } +int mazemaker_options_set_path_color(mazeoptions_t* options, char const* color_desc) { + return stringToColor(color_desc, options->path_color); +} + void mazemaker_options_set_seed(mazeoptions_t* options, rand_seed_t seed) { options->seed = seed; options->seed_set = true; diff --git a/lib/options.h b/lib/options.h index bef31c4..5d2cfad 100644 --- a/lib/options.h +++ b/lib/options.h @@ -10,6 +10,7 @@ typedef unsigned int rand_seed_t; typedef struct mazeoptions { rgb_color_t wall_color; rgb_color_t background_color; + rgb_color_t path_color; rand_seed_t seed; bool seed_set; } mazeoptions_t; diff --git a/lib/path.c b/lib/path.c index e990d9f..e8ec9c8 100644 --- a/lib/path.c +++ b/lib/path.c @@ -107,3 +107,7 @@ int mazemaker_solve(mazegrid_t const* maze, maze_path_t* path) { return code; } +void mazemaker_path_free(maze_path_t* path) { + path->length = 0; + free(path->nodes); +} diff --git a/utils/mazemaker.c b/utils/mazemaker.c index a1e9303..1ec9bd1 100644 --- a/utils/mazemaker.c +++ b/utils/mazemaker.c @@ -7,7 +7,7 @@ int main(int argc, char* argv[]) { char c; int width = 0, height = 0; unsigned int seed = 0; - char const *filename = NULL, *fg_color = NULL, *bg_color = NULL, *seed_str = NULL; + char const *filename = NULL, *fg_color = NULL, *bg_color = NULL, *path_color = NULL, *seed_str = NULL, *solution_fn = NULL; struct poptOption options_table[] = { { "width", 'w', POPT_ARG_INT, &width, 0, "Width of the maze", "BLOCKS" }, @@ -17,8 +17,12 @@ int main(int argc, char* argv[]) { "Foreground (wall) color", "#rrggbb" }, { "background", 'b', POPT_ARG_STRING, &bg_color, 0, "Background color", "#rrggbb" }, + { "path", 'p', POPT_ARG_STRING, &path_color, 0, + "Path (solution) color", "#rrggbb" }, { "seed", 's', POPT_ARG_STRING, &seed_str, 0, "Random seed", "SEED" }, + { "solution", 'u', POPT_ARG_STRING, &solution_fn, 0, + "Output solution", "FILENAME" }, POPT_AUTOHELP { NULL, 0, 0, NULL, 0 } }; @@ -56,12 +60,27 @@ int main(int argc, char* argv[]) { exit(1); } } + if (path_color != NULL) { + if (0 > mazemaker_options_set_path_color(options, path_color)) { + fprintf(stderr, "Unknown color: \"%s\"\n", path_color); + exit(1); + } + } if (seed_str != NULL) { seed = (unsigned int)atol(seed_str); mazemaker_options_set_seed(options, seed); } mazemaker_generate_maze_opt(width, height, &maze, options); mazemaker_maze_to_png_opt(&maze, filename, options); + if (solution_fn != NULL) { + maze_path_t solution; + if (mazemaker_solve(&maze, &solution) == 0) { + fprintf(stderr, "Error: no maze solution!\n"); + } else { + mazemaker_path_to_png_opt(&solution, solution_fn, options); + mazemaker_path_free(&solution); + } + } mazemaker_free_maze(&maze); mazemaker_options_free(options); poptFreeContext(ctx);