Compare commits

..

No commits in common. "0e2f8a55ce872f24b2439a16410b5956874b3594" and "f468ec5b6a8cdd7bab0782ae30407455b149dce4" have entirely different histories.

7 changed files with 36 additions and 133 deletions

View file

@ -1,4 +1,4 @@
placeany.site {
encode zstd gzip
reverse_proxy http://localhost:8080
encode zstd gzip
reverse_proxy http://localhost:5099
}

View file

@ -12,5 +12,5 @@ COPY wsgi.py wsgi.py
VOLUME images
ENTRYPOINT waitress-serve wsgi:app
ENTRYPOINT waitress-serve --host 127.0.0.1 --port 5099 wsgi:app

View file

@ -14,5 +14,5 @@ FROM app AS imgs
ARG images
copy ${images} images
ENTRYPOINT waitress-serve wsgi:app
ENTRYPOINT waitress-serve --host 127.0.0.1 --port 5099 wsgi:app

View file

@ -4,20 +4,10 @@ A quick and simple service for getting pictures of whatever-you-want
for use as placeholders in your designs or code. Just put your image
size (width & height) after the URL and you'll get a placeholder.
There is also a bookmarklet service which enhances sites with many images.
Similar URL API as [Placekitten]([http://placekitten.com](https://web.archive.org/web/20110504042732/http://placekitten.com/)).
Inspirations:
- https://web.archive.org/web/20110504042732/http://placekitten.com/
- https://web.archive.org/web/20120223050454/http://www.heyben.com/horse_ebookmarklet/.
## Example calls
Generates an image, 200px wide and 300px tall:
http://localhost:8080/200/300
Generates an image in grayscale, 200px wide and 300px tall:
http://localhost:8080/g/200/300
There is also a bookmarklet service which works the same as
[Horse_ebookmarklet]([http://www.heyben.com/horse_ebookmarklet/](https://web.archive.org/web/20120223050454/http://www.heyben.com/horse_ebookmarklet/)).
## Installation
@ -32,43 +22,29 @@ First, create an image collection.
1. Go to the code: `cd path/to/holder`. Copy `images` folder to it.
1. Create and activate a virtualenv.
1. Get dependencies in place: `pip install -r requirements.txt`
1. Start the app: `waitress-serve wsgi:app`
1. Go to [http://localhost:8080](http://localhost:8080) in your web browser.
1. Start the app: `waitress-serve --host 127.0.0.1 --port 5099 wsgi:app`
1. Go to [http://localhost:5099](http://localhost:5099) in your web browser.
1. Done!
### Run as Container
The most easy and portable way to use this is to use Docker or Podman.
In this build, waitress is used for production readyness.
In this build, waitress is used for production readyness. Port 5099 is
instead used.
podman build .
podman run -it -p 8080:8080 -v ./images:/app/images <container id>
podman run -it -p 5099:5099 -v ./images:/app/images <container id>
If you wish to embed images in container as well, use alternate
Containerfile.
podman build -f Containerfile.aio --build-arg images=./images .
podman run -it -p 8080:8080 <container id>
podman run -it -p 5099:5099 <container id>
### Run behind reverse proxy
## Example calls
A reverse proxy in front of placeany is recommended. An example
Caddyfile is available to make https "just work", but Nginx+certbot
will be equally fine.
# generates an image, 200px wide and 300px tall
http://localhost:5000/200/300
## Podman tip: generate systemd files
Make sure to enable lingering user processes.
loginctl enable-linger $USER
Then, create and change directory to systemd.
mkdir -p .config/systemd/user
cd .config/systemd/user
Now, generate the systemd user service.
podman generate systemd --new -f -n placeany
Your container can now be enabled and started.
# generates an image in grayscale, 200px wide and 300px tall
http://localhost:5000/g/200/300

View file

@ -37,9 +37,10 @@
<h1>placeany</h1>
<p>A quick and simple service for getting pictures for use as placeholders in your designs or code.
Just put your image size (width &amp; height) after our URL and you'll get a placeholder.</p>
<output>Like this: <a href="/200/300">{{ url }}/200/300</a>
<br>or: <a href="/g/200/300">{{ url }}/g/200/300</a>
</output>
<pre><output>Examples:
<a href="/200/300">{{ url }}/200/300</a>
<a href="/g/200/300">{{ url }}/g/200/300</a>
</output></pre>
</article>
<div class="unit">
<img src="/450/230" alt="">
@ -66,4 +67,4 @@
</footer>
</body>
</html>
</html>

View file

@ -1,47 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>placeany</title>
<style>
body {
width: 900px;
margin: 100px auto;
}
img {
display: block;
}
.grid {
display: flex;
gap: 1em;
flex-wrap: wrap;
}
</style>
</head>
<body>
<main>
<h1>placeany</h1>
<p>The following images are used on this placeany instance.
You may request a specific image by adding <b>?image=n</b> to your request
(where <b>n</b> is the number of the image you want), or filter the options
by adding multiple values like this: <b>?image=n1x&amp;image=n2&amp;image=n3</b>.</p>
<div class="grid">
{% for n in range(count) %}
<div class="unit">
<img src="/125/125?image={{ n }}" alt="">
</div>
{% endfor %}
</div>
</main>
<hr>
<footer role="contentinfo">
Also available as a <a href="/bookmarklet">Bookmarklet Service</a>.<br>
Source on <a href="https://github.com/madr/placeany">GitHub</a>.
</footer>
</body>
</html>

53
wsgi.py
View file

@ -2,7 +2,7 @@ import os
import random
from io import BytesIO
from flask import Flask, Response, render_template, request, send_file
from flask import Flask, render_template, request, send_file
from flask_caching import Cache
from PIL import Image, ImageOps
@ -10,7 +10,6 @@ app = Flask(__name__)
GREY = "G"
COLOR = "RGB"
IMAGE_DIR = "./images"
cache = Cache(config={"CACHE_TYPE": "SimpleCache"})
@ -18,45 +17,23 @@ app = Flask(__name__)
cache.init_app(app)
@cache.memoize(1)
def get_cropped_image(x, y, s, grey=False, retries=0):
def get_cropped_image(x, y, grey=False):
"""crops a random image from collection"""
if retries > 10:
return None
options = os.listdir(IMAGE_DIR)
try:
selection = list(
filter(
lambda i: i in range(0, len(options)),
map(int, s),
)
)
except ValueError:
return None
match len(selection):
case 0:
im_src = random.choice(options)
case 1:
im_src = options[selection[0]]
case _:
im_src = options[random.choice(selection)]
im = Image.open(f"{IMAGE_DIR}/{im_src}")
im_src = random.choice(os.listdir("./images"))
im = Image.open(f"images/{im_src}")
out = BytesIO()
max_x, max_y = im.size
if x > max_x and y > max_y:
return get_cropped_image(x, y, grey, retries + 1)
im = ImageOps.fit(im, (x, y))
if x < max_x or y < max_y:
im = ImageOps.fit(im, (x, y))
if grey:
im = ImageOps.grayscale(im)
im.save(out, "WEBP", quality=80)
im.save(out, "WEBP", quality=50)
out.seek(0)
return out
def make_response(x, y, s, color_mode=COLOR):
im = get_cropped_image(x, y, s, color_mode == GREY)
if not im:
return Response(status=401)
def make_response(x, y, color_mode=COLOR):
im = get_cropped_image(x, y, color_mode == GREY)
return send_file(im, mimetype="image/webp")
@ -72,20 +49,16 @@ def bookmarklet():
return render_template("bookmarklet.html", url=u)
@app.route("/images")
def collection():
c = len(os.listdir(IMAGE_DIR))
return render_template("list.html", count=c)
@app.route("/<int:x>/<int:y>")
@cache.cached(10)
def generate(x, y):
return make_response(x, y, request.args.getlist("image"), COLOR)
return make_response(x, y, COLOR)
@app.route("/g/<int:x>/<int:y>")
@cache.cached(10)
def generate_grey(x, y):
return make_response(x, y, request.args.getlist("image"), GREY)
return make_response(x, y, GREY)
if __name__ == "__main__":