diff options
author | untir_l <87096069+untir-l@users.noreply.github.com> | 2022-06-11 15:43:23 +0000 |
---|---|---|
committer | untir_l <87096069+untir-l@users.noreply.github.com> | 2022-06-11 15:49:12 +0000 |
commit | 2aa35e2c108f954949ec001f1e33846379e7cc98 (patch) | |
tree | 1653252d1419d01b7347a70f47416ae45d4fb2c4 | |
parent | e0babc53b16ef49ff40db21bbc4326e6e23185fa (diff) | |
download | hitomezashi-2aa35e2c108f954949ec001f1e33846379e7cc98.tar hitomezashi-2aa35e2c108f954949ec001f1e33846379e7cc98.tar.gz hitomezashi-2aa35e2c108f954949ec001f1e33846379e7cc98.zip |
Web: canvas now updates on the fly based on input
Also some other minor changes to thw web README.md and the Makefile, and
making some stuff const where they weren't throughout the lib/ tree as well.
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | README.md | 8 | ||||
-rw-r--r-- | lib/hitomezashi.c | 6 | ||||
-rw-r--r-- | lib/hitomezashi.h | 10 | ||||
-rw-r--r-- | lib/hitomezashi_utils.c | 2 | ||||
-rw-r--r-- | lib/hitomezashi_utils.h | 2 | ||||
-rw-r--r-- | web/hitomezashi_web.c | 169 | ||||
-rw-r--r-- | web/hitomezashi_web.h | 33 | ||||
-rw-r--r-- | web/hitomezashi_web_shell.html | 164 |
9 files changed, 178 insertions, 218 deletions
@@ -30,4 +30,4 @@ format-code: clang-format -i --verbose **/*.c **/*.h clean: - rm -f *.a *.o *.js *.wasm *.html hitomezashi_cli + rm -vf *.a *.o *.js *.wasm *.html hitomezashi_cli @@ -32,22 +32,16 @@ To build, run `CC=gcc make libhitomezashi.a`. This is in the `cli/` directory. Run `./hitomezashi_cli -h` for usage info. -Colours are supported! - -The output of the CLI app is a BMP image as this is supported by default in SDL2; this will change eventually to output PNGs since BMP is an antiquated format. For now, you can convert using FFmpeg or similar. - To build, run `CC=gcc make hitomezashi_cli`. ### Hitomezashi Web App -Try it: *link pending upload*. +Try it: *link pending*. To build, first run `make clean` if you previously built to native code, then (with Emscripten SDK in your PATH) run `emmake make hitomezashi_web.html`. You will need to serve, from a web server: `hitomezashi_web.html`, `hitomezashi_web.js`, `hitomezashi_web.wasm` in order for it to work; the `file://` protocol may not be sufficient. -The UI is self-explanatory. - ## Technical and copyright information Written in C11 with SDL2. Code style: `make format-code` will run `clang-format` with the correct parameters. diff --git a/lib/hitomezashi.c b/lib/hitomezashi.c index 9275517..5a3002d 100644 --- a/lib/hitomezashi.c +++ b/lib/hitomezashi.c @@ -7,9 +7,9 @@ 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, Uint32 fg_colour, - Uint32 bg_colour) { + int y_pattern_len, const char *x_pattern, + const char *y_pattern, int gap, int line_thickness, + Uint32 fg_colour, Uint32 bg_colour) { assert(x_pattern_len >= 0); assert(y_pattern_len >= 0); assert(gap >= 0); diff --git a/lib/hitomezashi.h b/lib/hitomezashi.h index 51da75b..f1cf2fa 100644 --- a/lib/hitomezashi.h +++ b/lib/hitomezashi.h @@ -14,11 +14,11 @@ struct Hitomezashi_State { /** Pattern used to draw the vertical lines conceptually originating from the * x axis. This is an array of 0s and 1s. */ - char *x_pattern; + const char *x_pattern; /** Pattern used to draw the horizontal lines conceptually originating from * the y axis. This is an array of 0s and 1s. */ - char *y_pattern; + const char *y_pattern; /** Gap between parallel lines. */ int gap; @@ -57,9 +57,9 @@ enum Hitomezashi_Draw_Result { * hitomezashi_draw(). */ 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, Uint32 fg_colour, - Uint32 bg_colour); + int y_pattern_len, const char *x_pattern, + const char *y_pattern, int gap, int line_thickness, + Uint32 fg_colour, Uint32 bg_colour); /** Draw the hitomezashi pattern to state->surface. */ enum Hitomezashi_Draw_Result hitomezashi_draw(struct Hitomezashi_State *state); diff --git a/lib/hitomezashi_utils.c b/lib/hitomezashi_utils.c index 32832de..93ec850 100644 --- a/lib/hitomezashi_utils.c +++ b/lib/hitomezashi_utils.c @@ -3,7 +3,7 @@ #include <stddef.h> #include <stdlib.h> -char *hitomezashi_ascii_binary_str_to_ints(char *ascii_str, size_t len) { +char *hitomezashi_ascii_binary_str_to_ints(const char *ascii_str, size_t len) { char *res = malloc(len); for (int i = 0; i < len; ++i) { switch (ascii_str[i]) { diff --git a/lib/hitomezashi_utils.h b/lib/hitomezashi_utils.h index 040ea00..95a5a3f 100644 --- a/lib/hitomezashi_utils.h +++ b/lib/hitomezashi_utils.h @@ -12,6 +12,6 @@ * be freed**. * @param len Length of ascii_str, not including any terminating null bytes. */ -char *hitomezashi_ascii_binary_str_to_ints(char *ascii_str, size_t len); +char *hitomezashi_ascii_binary_str_to_ints(const char *ascii_str, size_t len); #endif // HITOMEZASHI_UTILS_H diff --git a/web/hitomezashi_web.c b/web/hitomezashi_web.c index 0a36a2c..413f727 100644 --- a/web/hitomezashi_web.c +++ b/web/hitomezashi_web.c @@ -1,120 +1,111 @@ #include "hitomezashi_web.h" - -#include <stdbool.h> -#include <stdio.h> -#include <string.h> - #include "SDL2/SDL.h" #include "emscripten.h" #include "hitomezashi.h" #include "hitomezashi_utils.h" +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> -int EMSCRIPTEN_KEEPALIVE main(void) { - char *param_str = hitomezashi_web_get_args(); +SDL_Window *window; - if (param_str[0] == 'i') { - return Hitomezashi_Web_Result_Err_Get_Params; - } +int EMSCRIPTEN_KEEPALIVE main(void) { + if (SDL_Init(SDL_INIT_VIDEO) != 0) + return EXIT_FAILURE; - char *x_pattern_str = strtok(param_str, "\n"); - int x_pattern_len = strlen(x_pattern_str); - // We needn't bother checking whether - // hitomezashi_ascii_binary_str_to_ints() was successful as the strings - // passed to it are validated by hitomezashi_web_get_args() in JS. - char *x_pattern = - hitomezashi_ascii_binary_str_to_ints(x_pattern_str, x_pattern_len); + // Stop SDL from capturing all input and stopping input elements from working + SDL_SetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT, "#canvas"); - char *y_pattern_str = strtok(NULL, "\n"); - int y_pattern_len = strlen(y_pattern_str); - char *y_pattern = - hitomezashi_ascii_binary_str_to_ints(y_pattern_str, y_pattern_len); + if (!(window = SDL_CreateWindow("", 0, 0, 100, 100, SDL_WINDOW_RESIZABLE))) + return EXIT_FAILURE; - int gap = strtol(strtok(NULL, "\n"), NULL, 0); - int line_thickness = strtol(strtok(NULL, "\n"), NULL, 0); + emscripten_set_main_loop(main_loop, -1, true); - Uint32 fg_colour = strtol(strtok(NULL, "\n"), NULL, 0); - Uint32 bg_colour = strtol(strtok(NULL, "\n"), NULL, 0); + return EXIT_SUCCESS; +} - if (SDL_Init(SDL_INIT_VIDEO) != 0) { - return Hitomezashi_Web_Result_Err_Sdl_Init; - } +void EMSCRIPTEN_KEEPALIVE main_loop(void) { + const char *x_pattern_raw = hitomezashi_web_get_x_pattern(); + const char *y_pattern_raw = hitomezashi_web_get_y_pattern(); + const int x_pattern_len = strlen(x_pattern_raw); + const int y_pattern_len = strlen(y_pattern_raw); + const char *x_pattern = + hitomezashi_ascii_binary_str_to_ints(x_pattern_raw, x_pattern_len); + const char *y_pattern = + hitomezashi_ascii_binary_str_to_ints(y_pattern_raw, y_pattern_len); - SDL_Window *window; - SDL_Renderer *renderer; - SDL_Texture *texture; + const int gap = hitomezashi_web_get_gap(); + const int line_thickness = hitomezashi_web_get_line_thickness(); - struct Hitomezashi_State state; - if (hitomezashi_state_init(&state, x_pattern_len, y_pattern_len, x_pattern, - y_pattern, gap, line_thickness, fg_colour, - bg_colour) != 0) { - return Hitomezashi_Web_Result_Err_State_Init; - } + const Uint32 fg_colour = hitomezashi_web_get_fg_colour(); + const Uint32 bg_colour = hitomezashi_web_get_bg_colour(); - // Stop SDL from capturing all input and stopping input elements from working - SDL_SetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT, "#canvas"); - SDL_CreateWindowAndRenderer(state.output_width, state.output_height, 0, - &window, &renderer); + // If any of these are 0, input was invalid + if (!(x_pattern_len && y_pattern_len && gap && line_thickness)) + return; + struct Hitomezashi_State state; + hitomezashi_state_init(&state, x_pattern_len, y_pattern_len, x_pattern, + y_pattern, gap, line_thickness, fg_colour, bg_colour); + SDL_SetWindowSize(window, state.output_width, state.output_height); hitomezashi_draw(&state); - texture = SDL_CreateTextureFromSurface(renderer, state.surface); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); - - // Since this is a short-lived application which is only run on page load, - // we do not need to delay the exit of main by freeing memory - it will be - // reclaimed by the browser automatically. + SDL_BlitSurface(state.surface, NULL, SDL_GetWindowSurface(window), NULL); + SDL_UpdateWindowSurface(window); - return Hitomezashi_Web_Result_Success; + free((char *)x_pattern_raw); + free((char *)y_pattern_raw); + free((char *)x_pattern); + free((char *)y_pattern); + SDL_FreeSurface(state.surface); } -EM_JS(char *, hitomezashi_web_get_args, (void), { - function validate_pattern(pattern) { - let valid = true; - for (let c of pattern) { - if (c != "0" && c != "1") { - valid = false; - break; - } +EM_JS(char *, hitomezashi_web_get_x_pattern, (void), { + let result_js = document.querySelector("input[name=\"x_pattern\"]").value; + for (let c of result_js) { + if (c != "0" && c != "1") { + result_js = ""; + break; } - return valid; } + const result_length_bytes = lengthBytesUTF8(result_js) + 1; + let result_wasm_heap = _malloc(result_length_bytes); + stringToUTF8(result_js, result_wasm_heap, result_length_bytes); + return result_wasm_heap; +}) - let result_js = ""; - - let search_params = new URLSearchParams(window.location.search); - - const x_pattern = search_params.get("x_pattern"); - const y_pattern = search_params.get("y_pattern"); - const gap = search_params.get("gap"); - const line_thickness = search_params.get("line_thickness"); - let fg_colour = search_params.get("fg_colour"); - let bg_colour = search_params.get("bg_colour"); - if (!fg_colour) { - fg_colour = "#000000"; - } - if (!bg_colour) { - bg_colour = "#FFFFFF"; - } - fg_colour = "0x" + fg_colour.slice(1); - bg_colour = "0x" + bg_colour.slice(1); - - if (!(x_pattern && y_pattern && gap && line_thickness)) { - result_js = "i"; - } else { - const patterns_are_valid = - validate_pattern(x_pattern) && validate_pattern(y_pattern); - if (!patterns_are_valid) { - result_js = "i"; - } else { - result_js = x_pattern + "\n" + y_pattern + "\n" + gap + "\n" + - line_thickness + "\n" + fg_colour + "\n" + bg_colour + "\n"; +EM_JS(char *, hitomezashi_web_get_y_pattern, (void), { + let result_js = document.querySelector("input[name=\"y_pattern\"]").value; + for (let c of result_js) { + if (c != "0" && c != "1") { + result_js = ""; + break; } } - const result_length_bytes = lengthBytesUTF8(result_js) + 1; let result_wasm_heap = _malloc(result_length_bytes); stringToUTF8(result_js, result_wasm_heap, result_length_bytes); return result_wasm_heap; }) + +EM_JS(unsigned int, hitomezashi_web_get_gap, (void), { + const result = +document.querySelector("input[name=\"gap\"").value; + return (result > 0 ? result : 0); +}) + +EM_JS(unsigned int, hitomezashi_web_get_line_thickness, (void), { + const result = +document.querySelector("input[name=\"line_thickness\"").value; + return (result > 0 ? result : 0); +}) + +EM_JS(Uint32, hitomezashi_web_get_fg_colour, (void), { + const result = parseInt( + document.querySelector("input[name=\"fg_colour\"]").value.slice(1), 16); + return (result ? result : 0); +}) + +EM_JS(Uint32, hitomezashi_web_get_bg_colour, (void), { + const result = parseInt( + document.querySelector("input[name=\"bg_colour\"]").value.slice(1), 16); + return (result ? result : 0); +}) diff --git a/web/hitomezashi_web.h b/web/hitomezashi_web.h index 361a8c2..c7db1c9 100644 --- a/web/hitomezashi_web.h +++ b/web/hitomezashi_web.h @@ -5,32 +5,13 @@ #include "SDL2/SDL.h" #include "emscripten.h" -enum Hitomezashi_Web_Result { - Hitomezashi_Web_Result_Success = 0, - Hitomezashi_Web_Result_Err_Get_Params = 1, - Hitomezashi_Web_Result_Err_Sdl_Init = 2, - Hitomezashi_Web_Result_Err_State_Init = 3, -}; +void main_loop(void); -int hitomezashi_web(int x_pattern_len, int y_pattern_len, char *x_pattern, - char *y_pattern, int gap, int line_thickness, - Uint32 fg_colour, Uint32 bg_colour); - -/** Parse the URL query string and return the parameters thereof. - * - * This is implemented in JS with EM_JS, in order to access the query string. - * The friction of interoperation is the reason for turning the query string - * into a different string that the C code must parse to the actual values, - * rather than sending the data back individually with the correct types. - * That is probably possible, but it seems unpleasant. - * - * The query string's x_pattern, y_pattern, gap, and line_thickness are - * parsed. If not all of them are present, "i" is returned. If x_pattern or - * y_pattern in the query string have characters other than ASCII digits 0 - * and 1, "i" is returned. Otherwise, the values are returned as a - * newline-delimited string in the order x_pattern, y_pattern, gap, - * line_thickness. - */ -char *hitomezashi_web_get_args(void); +char *hitomezashi_web_get_x_pattern(void); +char *hitomezashi_web_get_y_pattern(void); +unsigned int hitomezashi_web_get_gap(void); +unsigned int hitomezashi_web_get_line_thickness(void); +Uint32 hitomezashi_web_get_fg_colour(void); +Uint32 hitomezashi_web_get_bg_colour(void); #endif // HITOMEZASHI_WEB_H diff --git a/web/hitomezashi_web_shell.html b/web/hitomezashi_web_shell.html index a774f1e..e0e23d4 100644 --- a/web/hitomezashi_web_shell.html +++ b/web/hitomezashi_web_shell.html @@ -1,90 +1,84 @@ <!doctype html> <html lang="en-us"> - <head> - <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> - <!--This is code modified from https://github.com/emscripten-core/emscripten/blob/main/src/shell_minimal.html.--> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Hitomezashi</title> - <style> - canvas { - padding-right: 0; - margin-left: auto; - margin-right: auto; - display: block; - border: 0px none; - background-color: black; - } - form { - display: table; - margin: auto; - } - form > p { - display: table-row; - } - form > p > label { - display: table-cell; - } - form > p > label > input { - display: table-cell; - } - button#downloadCanvas { - display: block; - margin: 0 auto; - } - p#info { - text-align: center; - } - p#info > a { - margin-left: 1ex; - margin-right: 1ex; - } - </style> - </head> - <body> - <p id="info">See <a href="https://github.com/untir-l/hitomezashi/blob/main/README.md">the README</a> for information.</p> - <form name="hitomezashi parameters" autocomplete="off" method="get"> - <p><label>x pattern: <input name="x_pattern"></label></p> - <p><label>y pattern: <input name="y_pattern"></label></p> - <p><label>gap: <input name="gap" type="number"></label></p> - <p><label>line thickness: <input name="line_thickness" type="number"></label></p> - <p><label>foreground colour: <input name="fg_colour" type="color"></label></p> - <p><label>background colour: <input name="bg_colour" type="color"></label></p> - <p><input type="submit" value="submit"></p> - </form> - <br> - <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas> - <br> - <button id="downloadCanvas">download as image</button> - <script type='text/javascript'> - // Make sure the WebGL context preserves the drawing buffer so the download button works - // Based on https://stackoverflow.com/a/43979935 - const canvas = document.querySelector("#canvas"); - canvas.getContext("webgl", {preserveDrawingBuffer: true}); - var Module = { - canvas: (function() { - canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false); - return canvas; - })(), - }; +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <!--This is code modified from https://github.com/emscripten-core/emscripten/blob/main/src/shell_minimal.html.--> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Hitomezashi</title> + <style> + canvas { + padding-right: 0; + margin-left: auto; + margin-right: auto; + display: block; + border: 0px none; + background-color: black; + } - // Set the input fields based on the query string so they appear sticky - const params = new URLSearchParams(window.location.search); - document.querySelector("input[name=\"x_pattern\"]").value = params.get("x_pattern"); - document.querySelector("input[name=\"y_pattern\"]").value = params.get("y_pattern"); - document.querySelector("input[name=\"gap\"]").value = params.get("gap"); - document.querySelector("input[name=\"line_thickness\"]").value = params.get("line_thickness"); - document.querySelector("input[name=\"fg_colour\"]").value = params.get("fg_colour"); - document.querySelector("input[name=\"bg_colour\"]").value = params.get("bg_colour"); + form { + display: table; + margin: auto; + } - function downloadCanvas() { - let downloadLink = document.createElement("a"); - downloadLink.setAttribute("download", "hitomezashi"); - downloadLink.setAttribute("href", canvas.toDataURL()); - downloadLink.click(); - } - document.querySelector("#downloadCanvas").addEventListener("click", downloadCanvas); - </script> - {{{ SCRIPT }}} - </body> -</html> + form>p { + display: table-row; + } + + form>p>label { + display: table-cell; + } + + form>p>label>input { + display: table-cell; + } + + button#downloadCanvas { + display: block; + margin: 0 auto; + } + + p#info { + text-align: center; + } + + p#info>a { + margin-left: 1ex; + margin-right: 1ex; + } + </style> +</head> + +<body> + <form name="hitomezashi parameters" autocomplete="off"> + <p><label>x pattern: <input name="x_pattern"></label></p> + <p><label>y pattern: <input name="y_pattern"></label></p> + <p><label>gap: <input name="gap" type="number"></label></p> + <p><label>line thickness: <input name="line_thickness" type="number"></label></p> + <p><label>foreground colour: <input name="fg_colour" type="color"></label></p> + <p><label>background colour: <input name="bg_colour" type="color"></label></p> + </form> + + <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas> + <script type='text/javascript'> + var Module = { + canvas: (function () { + var canvas = document.getElementById('canvas'); + + canvas.addEventListener("webglcontextlost", function (e) { alert('Error: WebGL context lost. Please reload the page.'); e.preventDefault(); }, false); + + return canvas; + })(), + }; + function downloadCanvas() { + let downloadLink = document.createElement("a"); + downloadLink.setAttribute("download", "hitomezashi"); + downloadLink.setAttribute("href", canvas.toDataURL()); + downloadLink.click(); + } + document.querySelector("#downloadCanvas").addEventListener("click", downloadCanvas); + </script> + {{{ SCRIPT }}} +</body> + +</html>
\ No newline at end of file |