diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | hitomezashi.c | 141 | ||||
-rw-r--r-- | hitomezashi.h | 40 | ||||
-rw-r--r-- | hitomezashi_cli.c | 279 | ||||
-rw-r--r-- | hitomezashi_cli.h | 18 |
6 files changed, 251 insertions, 236 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b45858 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.a +*.o +hitomezashi_cli @@ -6,9 +6,9 @@ Library and CLI app to generate hitomezashi patterns like the following: Inspired by [Numberphile's video](https://www.youtube.com/watch?v=JbfhzlMk2eY) on hitomezashi stitch patterns. -Written in C11 with SDL2. Licensed under GPLv2 (see `LICENSE` file for full text). +Written in C11 with SDL2. Code style: whatever clang-format outputs. -This project's source code is copyright © 2022-present Arjun Satarkar and potentially other future contributors. +Licensed under GPLv2 (see `LICENSE` file for full text). This project's source code is copyright © 2022-present Arjun Satarkar and potentially other future contributors. ## Usage @@ -16,7 +16,7 @@ This project's source code is copyright © 2022-present Arjun Satarkar and poten `hitomezashi_cli.c` and corresponding header file are the CLI app. `make` will build it; run `./hitomezashi_cli -h` for help. -Output of the CLI app is a BMP image; you can convert it to something nicer like a PNG with ffmpeg or similar. +Output of the CLI app is a BMP image; you can convert it to something nicer like a PNG with FFmpeg or similar. Additionally, the output is black and white - you can replace these colours using your tool of choice if desired. diff --git a/hitomezashi.c b/hitomezashi.c index c1c581e..09feb11 100644 --- a/hitomezashi.c +++ b/hitomezashi.c @@ -2,84 +2,85 @@ #include "SDL.h" #include <assert.h> -SDL_Color HITOMEZASHI_FG_COLOUR = { .r = 0, .g = 0, .b = 0 }; -SDL_Color HITOMEZASHI_BG_COLOUR = { .r = 255, .g = 255, .b = 255 }; +SDL_Color HITOMEZASHI_FG_COLOUR = {.r = 0, .g = 0, .b = 0}; +SDL_Color HITOMEZASHI_BG_COLOUR = {.r = 255, .g = 255, .b = 255}; enum Hitomezashi_State_Init_Result -hitomezashi_state_init( - struct Hitomezashi_State *state, - int x_pattern_len, int y_pattern_len, - char *x_pattern, char *y_pattern, - int gap, int line_thickness) { - assert(x_pattern_len >= 0); - assert(y_pattern_len >= 0); - assert(gap >= 0); - assert(line_thickness > 0); +hitomezashi_state_init(struct Hitomezashi_State *state, int x_pattern_len, + int y_pattern_len, char *x_pattern, char *y_pattern, + int gap, int line_thickness) { + assert(x_pattern_len >= 0); + assert(y_pattern_len >= 0); + assert(gap >= 0); + assert(line_thickness > 0); - state->y_pattern_len = y_pattern_len; - state->x_pattern_len = x_pattern_len; - state->x_pattern = x_pattern; - state->y_pattern = y_pattern; - state->gap = gap; - state->line_thickness = line_thickness; - state->output_width = x_pattern_len * gap; - state->output_height = y_pattern_len * gap; + state->y_pattern_len = y_pattern_len; + state->x_pattern_len = x_pattern_len; + state->x_pattern = x_pattern; + state->y_pattern = y_pattern; + state->gap = gap; + state->line_thickness = line_thickness; + state->output_width = x_pattern_len * gap; + state->output_height = y_pattern_len * gap; - state->surface = SDL_CreateRGBSurface(0, state->output_width, state->output_height, 32, 0, 0, 0, 0); - if (!state->surface) { - return Hitomezashi_State_Init_Result_Err_Create_Surface; - } - return Hitomezashi_State_Init_Result_Success; + state->surface = SDL_CreateRGBSurface(0, state->output_width, + state->output_height, 32, 0, 0, 0, 0); + if (!state->surface) { + return Hitomezashi_State_Init_Result_Err_Create_Surface; + } + return Hitomezashi_State_Init_Result_Success; } -enum Hitomezashi_Draw_Result -hitomezashi_draw(struct Hitomezashi_State *state) { - if (SDL_LockSurface(state->surface) != 0) { - return Hitomezashi_Draw_Result_Err_Lock_Surface; - } +enum Hitomezashi_Draw_Result hitomezashi_draw(struct Hitomezashi_State *state) { + if (SDL_LockSurface(state->surface) != 0) { + return Hitomezashi_Draw_Result_Err_Lock_Surface; + } - Uint32 bg_colour = SDL_MapRGB(state->surface->format, HITOMEZASHI_BG_COLOUR.r, HITOMEZASHI_BG_COLOUR.g, HITOMEZASHI_BG_COLOUR.b); - Uint32 fg_colour = SDL_MapRGB(state->surface->format, HITOMEZASHI_FG_COLOUR.r, HITOMEZASHI_FG_COLOUR.g, HITOMEZASHI_FG_COLOUR.b); + Uint32 bg_colour = + SDL_MapRGB(state->surface->format, HITOMEZASHI_BG_COLOUR.r, + HITOMEZASHI_BG_COLOUR.g, HITOMEZASHI_BG_COLOUR.b); + Uint32 fg_colour = + SDL_MapRGB(state->surface->format, HITOMEZASHI_FG_COLOUR.r, + HITOMEZASHI_FG_COLOUR.g, HITOMEZASHI_FG_COLOUR.b); - SDL_FillRect(state->surface, NULL, bg_colour); + SDL_FillRect(state->surface, NULL, bg_colour); - SDL_Rect rect; - // Draw y pattern (horizontal) lines - for (int i = 0; i < state->y_pattern_len; ++i) { - for (int j = 0; j < state->x_pattern_len; ++j) { - if (j % 2 == state->y_pattern[i]) { - rect.x = j * state->gap; - rect.y = i * state->gap; - rect.w = state->gap; - rect.h = state->line_thickness; - SDL_FillRect(state->surface, &rect, fg_colour); - } - } - } - // Draw x pattern (vertical) lines - for (int i = 0; i < state->x_pattern_len; ++i) { - for (int j = 0; j < state->y_pattern_len; ++j) { - if (j % 2 == state->x_pattern[i]) { - rect.x = i * state->gap; - rect.y = j * state->gap; - rect.w = state->line_thickness; - rect.h = state->gap; - SDL_FillRect(state->surface, &rect, fg_colour); - } - } - } - // Join up the lines to avoid leaving holes at the intersections - for (int x = state->gap; x < state->output_width; x += state->gap) { - for (int y = state->gap; y < state->output_height; y += state->gap) { - rect.x = x; - rect.y = y; - rect.w = state->line_thickness; - rect.h = state->line_thickness; - SDL_FillRect(state->surface, &rect, fg_colour); + SDL_Rect rect; + // Draw y pattern (horizontal) lines + for (int i = 0; i < state->y_pattern_len; ++i) { + for (int j = 0; j < state->x_pattern_len; ++j) { + if (j % 2 == state->y_pattern[i]) { + rect.x = j * state->gap; + rect.y = i * state->gap; + rect.w = state->gap; + rect.h = state->line_thickness; + SDL_FillRect(state->surface, &rect, fg_colour); + } + } + } + // Draw x pattern (vertical) lines + for (int i = 0; i < state->x_pattern_len; ++i) { + for (int j = 0; j < state->y_pattern_len; ++j) { + if (j % 2 == state->x_pattern[i]) { + rect.x = i * state->gap; + rect.y = j * state->gap; + rect.w = state->line_thickness; + rect.h = state->gap; + SDL_FillRect(state->surface, &rect, fg_colour); + } + } + } + // Join up the lines to avoid leaving holes at the intersections + for (int x = state->gap; x < state->output_width; x += state->gap) { + for (int y = state->gap; y < state->output_height; y += state->gap) { + rect.x = x; + rect.y = y; + rect.w = state->line_thickness; + rect.h = state->line_thickness; + SDL_FillRect(state->surface, &rect, fg_colour); + } + } - } - } - - SDL_UnlockSurface(state->surface); - return Hitomezashi_Draw_Result_Success; + SDL_UnlockSurface(state->surface); + return Hitomezashi_Draw_Result_Success; } diff --git a/hitomezashi.h b/hitomezashi.h index acd8ab9..e0782eb 100644 --- a/hitomezashi.h +++ b/hitomezashi.h @@ -4,43 +4,37 @@ #include "SDL.h" struct Hitomezashi_State { - int x_pattern_len; - int y_pattern_len; + int x_pattern_len; + int y_pattern_len; - char *x_pattern; - char *y_pattern; + char *x_pattern; + char *y_pattern; - int gap; + int gap; - int line_thickness; + int line_thickness; - int output_width; - int output_height; + int output_width; + int output_height; - SDL_Surface *surface; + SDL_Surface *surface; }; enum Hitomezashi_State_Init_Result { - Hitomezashi_State_Init_Result_Success, - Hitomezashi_State_Init_Result_Err_Create_Surface, + Hitomezashi_State_Init_Result_Success, + Hitomezashi_State_Init_Result_Err_Create_Surface, }; - enum Hitomezashi_Draw_Result { - Hitomezashi_Draw_Result_Success, - Hitomezashi_Draw_Result_Err_Lock_Surface, + Hitomezashi_Draw_Result_Success, + Hitomezashi_Draw_Result_Err_Lock_Surface, }; - enum Hitomezashi_State_Init_Result -hitomezashi_state_init( - struct Hitomezashi_State *state, - int x_pattern_len, int y_pattern_len, - char *x_pattern, char *y_pattern, - int gap, int line_thickness); - +hitomezashi_state_init(struct Hitomezashi_State *state, int x_pattern_len, + int y_pattern_len, char *x_pattern, char *y_pattern, + int gap, int line_thickness); -enum Hitomezashi_Draw_Result -hitomezashi_draw(struct Hitomezashi_State *state); +enum Hitomezashi_Draw_Result hitomezashi_draw(struct Hitomezashi_State *state); #endif // HITOMEZASHI_HITOMEZASHI_H diff --git a/hitomezashi_cli.c b/hitomezashi_cli.c index 24f488f..4a9f02d 100644 --- a/hitomezashi_cli.c +++ b/hitomezashi_cli.c @@ -1,7 +1,7 @@ #include "hitomezashi_cli.h" -#include "hitomezashi.h" -#include "getopt/xgetopt.h" #include "SDL.h" +#include "getopt/xgetopt.h" +#include "hitomezashi.h" #include <limits.h> #include <stdbool.h> #include <stddef.h> @@ -9,150 +9,165 @@ #include <stdlib.h> int main(int argc, char **argv) { - if (SDL_Init(SDL_INIT_VIDEO) != 0) { - SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Failed to initialise SDL: %s", SDL_GetError()); - return Hitomezashi_Cli_Exit_Code_Err_Sdl_Init; - } + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Failed to initialise SDL: %s", + SDL_GetError()); + return Hitomezashi_Cli_Exit_Code_Err_Sdl_Init; + } - char *out_file_path; - int x_pattern_len; - int y_pattern_len; - char *x_pattern; - char *y_pattern; - int gap; - int thickness; - hitomezashi_cli_handle_args(&out_file_path, &x_pattern_len, &y_pattern_len, &x_pattern, &y_pattern, &gap, &thickness, argc, argv); + char *out_file_path; + int x_pattern_len; + int y_pattern_len; + char *x_pattern; + char *y_pattern; + int gap; + int thickness; + hitomezashi_cli_handle_args(&out_file_path, &x_pattern_len, &y_pattern_len, + &x_pattern, &y_pattern, &gap, &thickness, argc, + argv); - struct Hitomezashi_State state; - if (hitomezashi_state_init(&state, x_pattern_len, y_pattern_len, x_pattern, y_pattern, gap, thickness) != 0) { - return Hitomezashi_Cli_Exit_Code_Err_State_Init; - } + struct Hitomezashi_State state; + if (hitomezashi_state_init(&state, x_pattern_len, y_pattern_len, x_pattern, + y_pattern, gap, thickness) != 0) { + return Hitomezashi_Cli_Exit_Code_Err_State_Init; + } - hitomezashi_draw(&state); + hitomezashi_draw(&state); - if (SDL_SaveBMP(state.surface, out_file_path) != 0) { - SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Failed to save image"); - return Hitomezashi_Cli_Exit_Code_Err_Save_Image; - } + if (SDL_SaveBMP(state.surface, out_file_path) != 0) { + SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Failed to save image"); + return Hitomezashi_Cli_Exit_Code_Err_Save_Image; + } - SDL_FreeSurface(state.surface); - free(state.x_pattern); - free(state.y_pattern); + SDL_FreeSurface(state.surface); + free(state.x_pattern); + free(state.y_pattern); - SDL_Quit(); + SDL_Quit(); } // Attempts to prints help. Exits 0 on success, 2 on failure. void hitomezashi_cli_help(void) { - if (puts( - "hitomezashi_cli - generate hitomezashi patterns\n" - "Options:\n" - "-o - specify the output image (BMP) path\n" - "-x - specify the x pattern as a string of zeroes and ones (eg. 0101)\n" - "-y - specify the y pattern, as with -x\n" - "-g - specify the gap between lines as an integer\n" - "-t - specify the line thickness as an integer\n" - "-h - print this help and exit" - ) < 0) { - exit(Hitomezashi_Cli_Exit_Code_Err_Print_Help); - } - exit(Hitomezashi_Cli_Exit_Code_Success); + if (puts("hitomezashi_cli - generate hitomezashi patterns\n" + "Options:\n" + "-o - specify the output image (BMP) path\n" + "-x - specify the x pattern as a string of zeroes and ones (eg. " + "0101)\n" + "-y - specify the y pattern, as with -x\n" + "-g - specify the gap between lines as an integer\n" + "-t - specify the line thickness as an integer\n" + "-h - print this help and exit") < 0) { + exit(Hitomezashi_Cli_Exit_Code_Err_Print_Help); + } + exit(Hitomezashi_Cli_Exit_Code_Success); } char *hitomezashi_cli_ascii_binary_str_to_ints(char *ascii_str, size_t n) { - char *res = malloc(n); - for (int i = 0; i < n; ++i) { - switch(ascii_str[i]) { - case '0':; - res[i] = 0; - break; - case '1':; - res[i] = 1; - break; - default:; - free(res); - printf("%d\n", ascii_str[i]); - return NULL; - } - } - return res; + char *res = malloc(n); + for (int i = 0; i < n; ++i) { + switch (ascii_str[i]) { + case '0':; + res[i] = 0; + break; + case '1':; + res[i] = 1; + break; + default:; + free(res); + printf("%d\n", ascii_str[i]); + return NULL; + } + } + return res; } -// Parses arguments with xgetopt, ensures they are valid, initialises various variables based on them. -// Prints and exits if it encounters an error. -void hitomezashi_cli_handle_args(char **out_file_path, int *x_pattern_len, int *y_pattern_len, char **x_pattern, char **y_pattern, int *gap, int *thickness, int argc, char **argv) { - bool out_file_path_specified = false; - bool x_pattern_specified = false; - bool y_pattern_specified = false; - bool gap_specified = false; - bool thickness_specified = false; +// Parses arguments with xgetopt, ensures they are valid, initialises various +// variables based on them. Prints and exits if it encounters an error. +void hitomezashi_cli_handle_args(char **out_file_path, int *x_pattern_len, + int *y_pattern_len, char **x_pattern, + char **y_pattern, int *gap, int *thickness, + int argc, char **argv) { + bool out_file_path_specified = false; + bool x_pattern_specified = false; + bool y_pattern_specified = false; + bool gap_specified = false; + bool thickness_specified = false; - struct xgetopt xgetopt_state = XGETOPT_INIT; - int option; - while ((option = xgetopt(&xgetopt_state, argc, argv, "o:x:y:g:t:h") != -1)) { - switch (xgetopt_state.optopt) { - case 'o':; - *out_file_path = xgetopt_state.optarg; - out_file_path_specified = true; - break; - case 'x':; - size_t x_pattern_len_l = strlen(xgetopt_state.optarg); - if (x_pattern_len_l >= INT_MAX) { - SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "X pattern length must be shorter than %d", INT_MAX); - exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); - } - *x_pattern_len = x_pattern_len_l; - *x_pattern = hitomezashi_cli_ascii_binary_str_to_ints(xgetopt_state.optarg, *x_pattern_len); - if (!*x_pattern) { - SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Invalid x pattern; see -h"); - exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); - } - x_pattern_specified = true; - break; - case 'y':; - size_t y_pattern_len_l = strlen(xgetopt_state.optarg); - if (y_pattern_len_l >= INT_MAX) { - SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Y pattern length must be shorter than %d", INT_MAX); - exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); - } - *y_pattern_len = y_pattern_len_l; - *y_pattern = hitomezashi_cli_ascii_binary_str_to_ints(xgetopt_state.optarg, *y_pattern_len); - if (!*y_pattern) { - SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Invalid y pattern; see -h"); - exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); - } - y_pattern_specified = true; - break; - case 'g':; - long gap_l = strtol(xgetopt_state.optarg, NULL, 0); - if (gap_l >= INT_MAX) { - SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Value for gap must be less than %d", INT_MAX); - exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); - } - *gap = gap_l; - gap_specified = true; - break; - case 't':; - long thickness_l = strtol(xgetopt_state.optarg, NULL, 0); - if (thickness_l >= INT_MAX) { - SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Value for line thickness must be less than %d", INT_MAX); - exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); - } - *thickness = thickness_l; - thickness_specified = true; - break; - case 'h':; - hitomezashi_cli_help(); - break; - default:; - SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Invalid argument: run with -h for help"); - exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); - } - } - if (!(out_file_path_specified && x_pattern_specified && y_pattern_specified && gap_specified && thickness_specified)) { - SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "-o, -x, -y, -g, -t *must* be specified; run with -h for help"); - exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); - } + struct xgetopt xgetopt_state = XGETOPT_INIT; + int option; + while ((option = xgetopt(&xgetopt_state, argc, argv, "o:x:y:g:t:h") != -1)) { + switch (xgetopt_state.optopt) { + case 'o':; + *out_file_path = xgetopt_state.optarg; + out_file_path_specified = true; + break; + case 'x':; + size_t x_pattern_len_l = strlen(xgetopt_state.optarg); + if (x_pattern_len_l >= INT_MAX) { + SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, + "X pattern length must be shorter than %d", INT_MAX); + exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); + } + *x_pattern_len = x_pattern_len_l; + *x_pattern = hitomezashi_cli_ascii_binary_str_to_ints( + xgetopt_state.optarg, *x_pattern_len); + if (!*x_pattern) { + SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Invalid x pattern; see -h"); + exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); + } + x_pattern_specified = true; + break; + case 'y':; + size_t y_pattern_len_l = strlen(xgetopt_state.optarg); + if (y_pattern_len_l >= INT_MAX) { + SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, + "Y pattern length must be shorter than %d", INT_MAX); + exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); + } + *y_pattern_len = y_pattern_len_l; + *y_pattern = hitomezashi_cli_ascii_binary_str_to_ints( + xgetopt_state.optarg, *y_pattern_len); + if (!*y_pattern) { + SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, "Invalid y pattern; see -h"); + exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); + } + y_pattern_specified = true; + break; + case 'g':; + long gap_l = strtol(xgetopt_state.optarg, NULL, 0); + if (gap_l >= INT_MAX) { + SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, + "Value for gap must be less than %d", INT_MAX); + exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); + } + *gap = gap_l; + gap_specified = true; + break; + case 't':; + long thickness_l = strtol(xgetopt_state.optarg, NULL, 0); + if (thickness_l >= INT_MAX) { + SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, + "Value for line thickness must be less than %d", + INT_MAX); + exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); + } + *thickness = thickness_l; + thickness_specified = true; + break; + case 'h':; + hitomezashi_cli_help(); + break; + default:; + SDL_LogCritical(SDL_LOG_CATEGORY_ERROR, + "Invalid argument: run with -h for help"); + exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); + } + } + if (!(out_file_path_specified && x_pattern_specified && y_pattern_specified && + gap_specified && thickness_specified)) { + SDL_LogCritical( + SDL_LOG_CATEGORY_ERROR, + "-o, -x, -y, -g, -t *must* be specified; run with -h for help"); + exit(Hitomezashi_Cli_Exit_Code_Err_Handle_Args); + } } - - diff --git a/hitomezashi_cli.h b/hitomezashi_cli.h index 0147e41..0aad37b 100644 --- a/hitomezashi_cli.h +++ b/hitomezashi_cli.h @@ -4,17 +4,19 @@ #include <stddef.h> enum Hitomezashi_Cli_Exit_Code { - Hitomezashi_Cli_Exit_Code_Success = 0, - Hitomezashi_Cli_Exit_Code_Err_Handle_Args = 1, - Hitomezashi_Cli_Exit_Code_Err_Print_Help = 2, - Hitomezashi_Cli_Exit_Code_Err_Sdl_Init = 3, - Hitomezashi_Cli_Exit_Code_Err_State_Init = 4, - Hitomezashi_Cli_Exit_Code_Err_Save_Image = 5, + Hitomezashi_Cli_Exit_Code_Success = 0, + Hitomezashi_Cli_Exit_Code_Err_Handle_Args = 1, + Hitomezashi_Cli_Exit_Code_Err_Print_Help = 2, + Hitomezashi_Cli_Exit_Code_Err_Sdl_Init = 3, + Hitomezashi_Cli_Exit_Code_Err_State_Init = 4, + Hitomezashi_Cli_Exit_Code_Err_Save_Image = 5, }; -void hitomezashi_cli_handle_args(char **out_file_path, int *x_pattern_len, int *y_pattern_len, char **x_pattern, char **y_pattern, int *gap, int *thickness, int argc, char **argv); +void hitomezashi_cli_handle_args(char **out_file_path, int *x_pattern_len, + int *y_pattern_len, char **x_pattern, + char **y_pattern, int *gap, int *thickness, + int argc, char **argv); void hitomezashi_cli_help(void); char *hitomezashi_cli_ascii_binary_str_to_ints(char *ascii_str, size_t n); - #endif // HITOMEZASHI_CLI_H |