Merge pull request #13 from alexwlchan/webapp
- ID
1e6a8be- date
2022-05-04 21:41:20+00:00- author
Alex Chan <alex@alexwlchan.net>- parents
977c9a8,09c01c7- message
Merge pull request #13 from alexwlchan/webapp Add the web app- changed files
Changed files
webapp/requirements.in (0) → webapp/requirements.in (35)
diff --git a/webapp/requirements.in b/webapp/requirements.in
new file mode 100644
index 0000000..f4b778f
--- /dev/null
+++ b/webapp/requirements.in
@@ -0,0 +1,3 @@
+flask
+gunicorn
+wcag_contrast_ratio
webapp/requirements.txt (0) → webapp/requirements.txt (580)
diff --git a/webapp/requirements.txt b/webapp/requirements.txt
new file mode 100644
index 0000000..8b580ee
--- /dev/null
+++ b/webapp/requirements.txt
@@ -0,0 +1,29 @@
+#
+# This file is autogenerated by pip-compile with python 3.9
+# To update, run:
+#
+# pip-compile
+#
+click==8.1.3
+ # via flask
+flask==2.1.2
+ # via -r requirements.in
+gunicorn==20.1.0
+ # via -r requirements.in
+importlib-metadata==4.11.3
+ # via flask
+itsdangerous==2.1.2
+ # via flask
+jinja2==3.1.2
+ # via flask
+markupsafe==2.1.1
+ # via jinja2
+wcag-contrast-ratio==0.9
+ # via -r requirements.in
+werkzeug==2.1.2
+ # via flask
+zipp==3.8.0
+ # via importlib-metadata
+
+# The following packages are considered to be unsafe in a requirements file:
+# setuptools
webapp/server.py (0) → webapp/server.py (2403)
diff --git a/webapp/server.py b/webapp/server.py
new file mode 100755
index 0000000..98fda22
--- /dev/null
+++ b/webapp/server.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+
+import base64
+import colorsys
+import os
+import subprocess
+import tempfile
+
+from flask import Flask, render_template, request
+import wcag_contrast_ratio as contrast
+
+
+app = Flask(__name__)
+
+
+VERSION = subprocess.check_output(["dominant_colours", "--version"]).decode("utf8")
+
+
+@app.route("/")
+def index():
+ return render_template("index.html", version=VERSION)
+
+
+@app.template_filter("foreground_colour")
+def foreground_colour(hex_string):
+ red = int(hex_string[1:3], 16)
+ green = int(hex_string[3:5], 16)
+ blue = int(hex_string[5:7], 16)
+
+ ratio = contrast.rgb((red / 255, green / 255, blue / 255), (0, 0, 0))
+
+ if contrast.passes_AA(ratio):
+ return "#000000"
+ else:
+ return "#FFFFFF"
+
+
+@app.route("/palette", methods=["POST"])
+def get_palette():
+ if request.method == "POST":
+ uploaded_file = request.files["file"]
+ _, extension = os.path.splitext(uploaded_file.filename)
+
+ with tempfile.NamedTemporaryFile(suffix=extension) as tmp_file:
+ uploaded_file.save(tmp_file)
+
+ # If we don't flush here, the file may be incomplete. This can
+ # lead to errors like:
+ #
+ # failed to fill whole buffer
+ #
+ # when running dominant_colours.
+ tmp_file.flush()
+
+ result = subprocess.check_output(
+ ["dominant_colours", tmp_file.name, "--no-palette", "--max-colours=5"]
+ )
+ colours = result.decode("utf8").strip().split("\n")
+
+ with tempfile.NamedTemporaryFile(suffix="jpg") as thumbnail_file:
+ subprocess.check_call(
+ [
+ "convert",
+ tmp_file.name,
+ "-resize",
+ "600x600",
+ thumbnail_file.name,
+ ]
+ )
+ thumbnail_file.seek(0)
+ thumbnail = thumbnail_file.read()
+
+ thumbnail_data_uri = (
+ b"data:image/jpg;base64," + base64.b64encode(thumbnail)
+ ).decode("ascii")
+
+ return render_template(
+ "palette.html",
+ colours=colours,
+ thumbnail_data_uri=thumbnail_data_uri,
+ version=VERSION,
+ )
+
+
+if __name__ == "__main__":
+ app.run(debug=True, host="0.0.0.0")
webapp/start.sh (0) → webapp/start.sh (668)
diff --git a/webapp/start.sh b/webapp/start.sh
new file mode 100755
index 0000000..e2d57ba
--- /dev/null
+++ b/webapp/start.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+set -o errexit
+set -o nounset
+
+pip3 install -r requirements.txt
+
+DOWNLOAD_URL=$(curl --silent 'https://api.github.com/repos/alexwlchan/dominant_colours/releases/latest' \
+ | jq -r ' .assets | map(.browser_download_url) | map(select(test(".*linux.*")))[0]'
+)
+
+# The --location flag means we follow redirects
+curl --location "$DOWNLOAD_URL" > ~/.cargo/bin/dominant_colours.tar.gz
+tar -xzf ~/.cargo/bin/dominant_colours.tar.gz
+
+mv dominant_colours /usr/local/bin/dominant_colours
+chmod +x /usr/local/bin/dominant_colours
+dominant_colours --version
+
+if [[ "$DEBUG" == "yes" ]]
+then
+ python3 server.py
+else
+ gunicorn server:app -w 4 --log-file -
+fi
webapp/static/45degreee_fabric.png (0) → webapp/static/45degreee_fabric.png (153950)
diff --git a/webapp/static/45degreee_fabric.png b/webapp/static/45degreee_fabric.png
new file mode 100644
index 0000000..3c7fa3f
Binary files /dev/null and b/webapp/static/45degreee_fabric.png differ
webapp/static/style.css (0) → webapp/static/style.css (2097)
diff --git a/webapp/static/style.css b/webapp/static/style.css
new file mode 100644
index 0000000..5c7b01a
--- /dev/null
+++ b/webapp/static/style.css
@@ -0,0 +1,132 @@
+body {
+ margin: 0;
+ padding: 0;
+
+ /* Pattern from https://www.toptal.com/designers/subtlepatterns/awesome-pattern/ */
+ background-image: url('/static/45degreee_fabric.png');
+}
+
+main, footer {
+ max-width: 500px;
+ margin-left: auto;
+ margin-right: auto;
+ text-align: center;
+ padding: 5px;
+
+ font: 14pt Avenir, Arial, sans-serif;
+ color: #333;
+}
+
+footer {
+ font-size: 9pt;
+ color: #aaa;
+}
+
+a, a:visited {
+ color: #555;
+}
+
+footer a, footer a:visited {
+ color: #888;
+}
+
+a:hover {
+ background: #ccc;
+}
+
+#results {
+ display: grid;
+ grid-template-columns: 325px 70px auto;
+ grid-template-rows: repeat(5, 56px);
+ grid-gap: 5px;
+ height: 300px;
+
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.thumbnail {
+ grid-row: 1 / span 5;
+ grid-column: 1 / 3;
+ width: 300px;
+ height: 300px;
+}
+
+img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
+
+.sample {
+ grid-column: 1 / 2;
+}
+
+.label {
+ font-family: monospace;
+ height: 56px;
+ line-height: 56px;
+ text-align: left;
+}
+
+.sample {
+ width: 56px;
+ height: 56px;
+ grid-column: 2 / 3;
+}
+
+.label {
+ grid-column: 3 / 3;
+}
+
+#label_1, #sample_1 { grid-row: 1 / 5; }
+#label_2, #sample_2 { grid-row: 2 / 5; }
+#label_3, #sample_3 { grid-row: 3 / 5; }
+#label_4, #sample_4 { grid-row: 4 / 5; }
+#label_5, #sample_5 { grid-row: 5 / 5; }
+
+@media screen and (max-width: 600px) {
+ .thumbnail {
+ width: 300px;
+ height: 230px;
+ margin-left: auto;
+ margin-right: auto;
+ }
+
+ #results {
+ grid-template-columns: 42px auto;
+ width: 300px;
+ grid-template-rows: 240px repeat(5, 42px);
+ grid-row-gap: 10px;
+ height: auto;
+ }
+
+ .thumbnail {
+ grid-column: 1 / span 2;
+ grid-row:1 / 6;
+ }
+
+ .sample {
+ grid-column: 1 / 2;
+ }
+
+ .label {
+ grid-column: 2 / 2;
+ }
+
+ #label_1, #sample_1 { grid-row: 2 / 6; }
+ #label_2, #sample_2 { grid-row: 3 / 6; }
+ #label_3, #sample_3 { grid-row: 4 / 6; }
+ #label_4, #sample_4 { grid-row: 5 / 6; }
+ #label_5, #sample_5 { grid-row: 6 / 6; }
+
+ .label {
+ height: 42px;
+ line-height: 42px;
+ }
+
+ .sample {
+ width: 42px;
+ height: 42px;
+ }
+}
webapp/templates/base.html (0) → webapp/templates/base.html (734)
diff --git a/webapp/templates/base.html b/webapp/templates/base.html
new file mode 100644
index 0000000..9015714
--- /dev/null
+++ b/webapp/templates/base.html
@@ -0,0 +1,26 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
+
+ <link rel="stylesheet" href="/static/style.css">
+
+ <title>dominant colours</title>
+ </head>
+ <body>
+ <main>
+ <h2>find the dominant colours in an image</h2>
+ {% block content %}
+ {% endblock %}
+ </main>
+ <footer>
+ made with <span class="heart">❤</span> by <a href="https://alexwlchan.net">alexwlchan</a>
+ ·
+ {{ version }}
+ ·
+ code on <a href="https://github.com/alexwlchan/dominant_colours/tree/main/webapp">github</a>
+ </div>
+ </footer>
+ </body>
+</html>
\ No newline at end of file
webapp/templates/index.html (0) → webapp/templates/index.html (282)
diff --git a/webapp/templates/index.html b/webapp/templates/index.html
new file mode 100644
index 0000000..82c6be1
--- /dev/null
+++ b/webapp/templates/index.html
@@ -0,0 +1,11 @@
+{% extends "base.html" %}
+
+{% block content %}
+ <p>upload an image to analyse:</p>
+
+ <form action="/palette" method="POST"
+ enctype="multipart/form-data">
+ <input type="file" name="file" accept=".gif,.jpg,.jpeg,.png"/>
+ <input type="submit"/>
+ </form>
+{% endblock %}
webapp/templates/palette.html (0) → webapp/templates/palette.html (416)
diff --git a/webapp/templates/palette.html b/webapp/templates/palette.html
new file mode 100644
index 0000000..85ec9dc
--- /dev/null
+++ b/webapp/templates/palette.html
@@ -0,0 +1,15 @@
+{% extends "base.html" %}
+
+{% block content %}
+ <div id="results">
+ <div class="thumbnail">
+ <img src="{{ thumbnail_data_uri }}">
+ </div>
+ {% for c in colours %}
+ <div class="sample" id="sample_{{ loop.index }}" style="background: {{ c }};"></div>
+ <div class="label" id="label_{{ loop.index }}">{{ c }}</div>
+ {% endfor %}
+ </div>
+
+ <p><a href="/">try another image</a></p>
+{% endblock %}