diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5112308
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+server-script/*.csv
+server-script/*.log
+server-script/*.png
+server-script/*.svg
+server-script/index.conf.php
+server-script/solacon.conf.sh
diff --git a/README.md b/README.md
index bb4f9e0..a38fd13 100644
--- a/README.md
+++ b/README.md
@@ -1,31 +1,45 @@
-# solacon
+# About Solacon
-A *solacon* is a variation of an [identicon](https://en.wikipedia.org/wiki/Identicon), in the form of a solar/spiral/floral shape.
-This is also known as a "visual hash".
+A *solacon* is a variation of an [identicon](https://en.wikipedia.org/wiki/Identicon), in the form of a solar/spiral/floral shape. This is also known as a *visual hash*.
The solacon is **seeded with a value** (string) which determines the shape, symmetry, and shades of the image.
-The SVG file contains all the javascript needed to generate the content, so one only needs to set attributes (e.g. `data-value`) on the object
-that is embedding the svg.
+## Examples
-![Solacon.svg](Solacon.svg.png "Solacon.svg")
+![Solacon examples](screenshots/solacon-examples.png)
-## Example
+## Form
-Try out some [dynamical examples](https://naknomum.github.io/solacon-example/) of **solacons** in action.
+The HTML file contains a form allowing to customize the image generation:
+- **Random color:** If checked, a new color will be used for each image generation.
+- **Background:** We can choose between no background (image with transparency), a white background or a colored background (shade of the main colour).
+- **String:** If filled, the string will be used to generate the solacon. If empty, a random string will be used.
-## Usage
+At the end of the form, there are buttons to generate a new image or to download the solacon in PNG or SVG format.
-```javascript
-
+![Solacon form](screenshots/solacon-form.png)
+
+## GET variables
+
+The image generation can also be customised using GET variables in the URL. Here is the list of all variables that can be used:
+
+- `string=TEXT` where `TEXT` is the string used to generate the solacon. Example: `string=lorem`.
+- `color=COLOR` where `COLOR` is the color of the solacon. Hex colors and RGB colors can be used. Examples: `color=#326496`, `color=326496`, `color=50,100,150`, `color=rgb(50,100,150)`.
+- `background=TYPE` where `TYPE` is `white` or `colored`.
+- `size=SIZE` where `SIZE` is the width and height in px. Example: `size=512`.
+- `download=TYPE` where `TYPE` is `png` or `svg`. The image download will start automatically without having to click on the download button.
+
+Examples:
+
+- `index.html?string=hujMdpAtoaDztvZ&color=b079b4`
+- `index.html?string=Lorem%20Ipsum&color=326496&background=white&download=png`
+
+## JavaScript
+The parent document can alter the solacon:
-// the parent document can alter the solacon
+```javascript
var svgObj = document.getElementById('svg-obj');
svgObj.setRGB('100, 150, 200');
svgObj.generate('some new value');
@@ -35,9 +49,24 @@ svgObj.setRGBFromHash(); // color is based off hashValue (default behavior)
svgObj.refresh(); // applies color change to current shape
```
+## Server script
+
+The file `server-script/solacon.sh` illustrates how to get solacon images on a server, without user interaction.
+
+Run `solacon.sh -h` for more details.
+
+## Live demo
+
+Try out some [dynamical examples](https://misaki-web.github.io/solacon/) of **solacons** in action.
+
## Similar projects
-Another identicon project of mine, [Hexicon.js](https://github.com/naknomum/hexicon).
+- [Hexicon.js](https://github.com/naknomum/hexicon): hexagonal identicons
+- [Awesome Identicons](https://github.com/drhus/awesome-identicons): a curated list of *visual hashes* maintained by **Husam ABBOUD**
+
+## License
-For related work, check out [Awesome Identicons](https://github.com/drhus/awesome-identicons), a curated list of _visual hashes_ maintained by **Husam ABBOUD**.
+Copyright (c) 2022 Jon Van Oast
+Copyright (C) 2023 Misaki F.
+Released under MIT License. See the file [LICENSE](LICENSE) for more details.
\ No newline at end of file
diff --git a/Solacon.svg.png b/Solacon.svg.png
deleted file mode 100644
index 45cc2c9..0000000
Binary files a/Solacon.svg.png and /dev/null differ
diff --git a/example.html b/example.html
deleted file mode 100644
index 8d093d1..0000000
--- a/example.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-Solacon
-
-
-
-
-
-
-
-
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..fd8d5a1
--- /dev/null
+++ b/index.html
@@ -0,0 +1,696 @@
+
+
+
+
+
+
+
+ Solacon | Identicon visual hash
+
+
+
+
+
+
+
+
+
+
+
+
Solacon (identicon visual hash)
+
+
+
+
+
+
String:
+
Color:
+
+
+
+
+
+
+
+
+
+
+
+
+
History
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/screenshots/solacon-examples.png b/screenshots/solacon-examples.png
new file mode 100644
index 0000000..a573ca9
Binary files /dev/null and b/screenshots/solacon-examples.png differ
diff --git a/screenshots/solacon-form.png b/screenshots/solacon-form.png
new file mode 100644
index 0000000..f277ae8
Binary files /dev/null and b/screenshots/solacon-form.png differ
diff --git a/server-script/access-from-outside/index.php b/server-script/access-from-outside/index.php
new file mode 100644
index 0000000..222666c
--- /dev/null
+++ b/server-script/access-from-outside/index.php
@@ -0,0 +1,86 @@
+ realpath(__DIR__ . '/../solacon.sh'),
+];
+
+# For custom configuration, rename the file `index.conf.php.tpl` to `index.conf.php` and change values.
+$custom_conf_file = realpath(__DIR__ . '/../index.conf.php');
+
+if (file_exists($custom_conf_file)) {
+ $custom_conf = parse_ini_file($custom_conf_file);
+
+ if (is_array($custom_conf)) {
+ $conf = array_merge($conf, $custom_conf);
+ }
+}
+
+################################################################################
+# SCRIPT
+################################################################################
+
+if (file_exists($conf['solacon_path'])) {
+ $args = [$conf['solacon_path'], '-r', 'name+b64'];
+
+ if (isset($_GET['background']) && ($_GET['background'] == 'colored' || $_GET['background'] == 'white')) {
+ $args[] = '-b';
+ $args[] = escapeshellarg($_GET['background']);
+ }
+
+ if (
+ isset($_GET['color']) &&
+ (preg_match(RX_3D_HEX, $_GET['color']) || preg_match(RX_6D_8D_HEX, $_GET['color']) || preg_match(RX_RGB, $_GET['color']))
+ ) {
+ $args[] = '-c';
+ $args[] = escapeshellarg($_GET['color']);
+ }
+
+ if (isset($_GET['download']) && ($_GET['download'] == 'png' || $_GET['download'] == 'svg')) {
+ $args[] = '-f';
+ $args[] = escapeshellarg($_GET['download']);
+ }
+
+ if (isset($_GET['size']) && preg_match('/^[1-9][0-9]*$/', $_GET['size'])) {
+ $args[] = '-s';
+ $args[] = escapeshellarg($_GET['size']);
+ }
+
+ if (isset($_GET['string'])) {
+ $args[] = '-t';
+ $args[] = escapeshellarg($_GET['string']);
+ }
+
+ $exec_return = shell_exec(implode(' ', $args));
+
+ if (is_string($exec_return)) {
+ $img_data = explode("\n", $exec_return);
+
+ if (!empty($img_data[0]) && !empty($img_data[1])) {
+ $img_name = $img_data[0];
+ $img_content = base64_decode($img_data[1], true);
+
+ if ($img_content !== false) {
+ header('Content-Type: application/octet-stream');
+ header('Content-Transfer-Encoding: Binary');
+ header('Content-Disposition: attachment; filename="' . str_replace('"', '\"', $img_name) . '"');
+ header('Content-Length: ' . strlen($img_content));
+
+ echo $img_content;
+
+ exit();
+ }
+ }
+ }
+}
diff --git a/server-script/index.conf.php.tpl b/server-script/index.conf.php.tpl
new file mode 100644
index 0000000..1e700ce
--- /dev/null
+++ b/server-script/index.conf.php.tpl
@@ -0,0 +1,2 @@
+;
+solacon_path = "../solacon.sh"
diff --git a/server-script/solacon.sh b/server-script/solacon.sh
new file mode 100755
index 0000000..537c6ea
--- /dev/null
+++ b/server-script/solacon.sh
@@ -0,0 +1,654 @@
+#!/bin/bash
+
+# Generate and save [Solacon images](https://github.com/misaki-web/solacon)
+# without user interaction.
+#
+# Run `solacon.sh -h` for more details.
+
+################################################################################
+# CONFIGURATION
+################################################################################
+
+# Note that configuration variables can be set in a file "./solacon.conf.sh".
+
+# Path to Chrome/Chromium
+conf_chrome_path="chromium-browser"
+
+# Solacon distant base URL
+conf_solacon_distant_base_url="https://misaki-web.github.io/solacon/"
+
+# If access from outside is enabled, base URL
+conf_solacon_outside_base_url="0.0.0.0:850"
+
+# If access from outside is enabled, URL scheme
+conf_solacon_outside_url_scheme="http"
+
+# If access from outside is enabled, path to the file "index.php"
+conf_solacon_path_to_index_php="access-from-outside/index.php"
+
+# If access from outside is enabled, run the script with the specified user
+conf_solacon_outside_user=""
+
+# Solacon local base URL (if PHP is installed, a built-in web server
+# will be started locally; keep empty to disable the local server)
+conf_solacon_local_base_url="0.0.0.0:851"
+
+# If a built-in server is started locally, number of server workers
+conf_nb_server_workers=1
+
+# If a built-in server is started locally, path to the file "index.html"
+conf_solacon_path_to_index_html="../index.html"
+
+# If a built-in server is started locally, keep it running at the end of the script
+conf_keep_web_server_running=true
+
+# Add the string to the image file name
+conf_add_string_to_image_name=true
+
+# Script return ("save", "content", "b64" or "name+b64")
+conf_return="save"
+
+# History file name (must end with ".csv"; keep empty to disable history)
+conf_history_file_name="solacon-history.csv"
+
+# Log file name (must end with ".log"; keep empty to disable log)
+conf_log_file_name="solacon.log"
+
+################################################################################
+# FUNCTIONS
+################################################################################
+
+# Thanks to .
+# catch STDO_VAR STDE_VAR COMMAND [ARGS]
+catch() {
+ {
+ IFS=$'\n' read -r -d '' "${1}";
+ IFS=$'\n' read -r -d '' "${2}";
+ (IFS=$'\n' read -r -d '' _ERRNO_; return "${_ERRNO_}");
+ } < <( ( printf '\0%s\0%d\0' "$( ( ( ( { shift 2; "${@}"; echo "${?}" 1>&3-; } | tr -d '\0' 1>&4- ) 4>&2- 2>&1- | tr -d '\0' 1>&4- ) 3>&1- | exit "$(cat)" ) 4>&1- )" "${?}" 1>&2 ) 2>&1 )
+}
+
+cd_exit() {
+ local path=$1
+
+ # ----------
+
+ debug "Can't enter the directory \"$path\"."
+
+ exit 1
+}
+
+convert_to_base64url() {
+ tr '+/' '-_' | tr -d '='
+}
+
+debug() {
+ local content=$1
+ local display=$2
+
+ if [[ $display != true && $display != false ]]; then
+ display=true
+ fi
+
+ # ----------
+
+ if [[ -n $content ]]; then
+ if [[ $display == true ]]; then
+ echo "$content" 1>&2
+ fi
+
+ if [[ -f $LOG_FILE ]]; then
+ echo "$EPOCHSECONDS:$content" >> "$LOG_FILE"
+ fi
+ fi
+}
+
+# First argument passed by reference.
+get_server_pid() {
+ local -n pid_array=$1
+ local base_url=$2
+
+ # ----------
+
+ # shellcheck disable=SC2034
+ mapfile -t pid_array < <(pgrep -f "php -S $base_url")
+}
+
+image_is_valid() {
+ local image_path=$1
+
+ # ----------
+
+ [[ -f $image_path ]] && \
+ (( $(stat --printf="%s" "$image_path") > 0 )) && \
+ { \
+ { [[ $image_path =~ ".png"$ ]] && identify +ping "$image_path" &> /dev/null; } || \
+ { [[ $image_path =~ ".svg"$ ]] && [[ $(head -n 1 "$image_path") =~ ^'