summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--README.md8
-rw-r--r--lib/hitomezashi.c6
-rw-r--r--lib/hitomezashi.h10
-rw-r--r--lib/hitomezashi_utils.c2
-rw-r--r--lib/hitomezashi_utils.h2
-rw-r--r--web/hitomezashi_web.c169
-rw-r--r--web/hitomezashi_web.h33
-rw-r--r--web/hitomezashi_web_shell.html164
9 files changed, 178 insertions, 218 deletions
diff --git a/Makefile b/Makefile
index 3458c89..80f8e04 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/README.md b/README.md
index 3d75887..a780310 100644
--- a/README.md
+++ b/README.md
@@ -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