こんにちは。今回は Flask と Pillow を使って、
ランダムな背景画像に日付や時間を合成し、まるで旅のワンシーンのような画像を自動生成するAPIを作ってみました。
概要
このAPI /api/image/place は、サーバー内の image フォルダにあるランダムな背景画像を選び、
その上に「日付」「月」「曜日」「時刻」などを重ね描きします。
背景を暗くして文字を浮かび上がらせるためのレイヤー処理も行い、見た目を自然に仕上げています。
出力イメージ例
使用技術
| 要素 | 使用ライブラリ |
|---|---|
| Webサーバー | Flask |
| 画像処理 | Pillow |
| フォント描画 | TrueTypeフォント (font.ttf) |
| 対応形式 | PNG, JPG, WebP |
ディレクトリ構成例
project/
├─ app.py
├─ font.ttf
└─ image/
├─ image1.jpg
├─ image2.jpg
└─ image3.webp
コード全文
from flask import Flask, jsonify, send_file
from PIL import Image, ImageDraw, ImageFont, ImageFilter
from datetime import datetime
import os, io, random
app = Flask(__name__)
@app.route("/api/image/place", methods=["GET"])
def generate_place_image():
try:
# === ランダム背景 ===
image_folder = "image"
images = [f for f in os.listdir(image_folder) if f.lower().endswith((".png", ".jpg", ".jpeg", ".webp"))]
if not images:
return jsonify({"error": "imageフォルダに画像が見つかりません。"}), 404
img_path = os.path.join(image_folder, random.choice(images))
base = Image.open(img_path).convert("RGBA")
# === 背景を少し暗くして文字を際立たせる ===
overlay = Image.new("RGBA", base.size, (0, 0, 0, 100))
base = Image.alpha_composite(base, overlay)
draw = ImageDraw.Draw(base)
font_big = ImageFont.truetype("font.ttf", int(base.height * 0.45))
font_mid = ImageFont.truetype("font.ttf", int(base.height * 0.1))
font_small = ImageFont.truetype("font.ttf", int(base.height * 0.05))
# === 現在日時 ===
now = datetime.now()
day = now.strftime("%d")
month = now.strftime("%B").upper()
weekday = now.strftime("%A").upper()
time_text = now.strftime("%H:%M")
# === 大きな日付 ===
bbox = draw.textbbox((0, 0), day, font=font_big)
w, h = bbox[2] - bbox[0], bbox[3] - bbox[1]
x = (base.width - w) * 0.2
y = (base.height - h) * 0.25
draw.text((x, y), day, font=font_big, fill=(255, 195, 0, 200))
# === 月 ===
bbox_m = draw.textbbox((0, 0), month, font=font_mid)
w_m = bbox_m[2] - bbox_m[0]
h_m = bbox_m[3] - bbox_m[1]
x_m = x + (w * 0.35) - (w_m * 0.1)
y_m = y + (h * 0.75) - (h_m * 0.5)
draw.text((x_m, y_m), month, font=font_mid, fill=(255, 255, 255, 235))
# === 時刻 ===
draw.text((x + w * 0.35, y + h * 1.00), time_text, font=font_small, fill=(255, 255, 255, 220))
# === 曜日(縦書き) ===
vertical_text = "\n".join(list(weekday))
bbox_w = draw.textbbox((0, 0), vertical_text, font=font_small)
draw.text((base.width * 0.05, (base.height - (bbox_w[3] - bbox_w[1])) / 2),
vertical_text, font=font_small, fill=(255, 255, 255, 180), spacing=5, align="center")
# === 右下署名 ===
signature = "DiscoCream"
font_signature = ImageFont.truetype("font.ttf", int(base.height * 0.035))
bbox_sig = draw.textbbox((0, 0), signature, font=font_signature)
w_sig = bbox_sig[2] - bbox_sig[0]
h_sig = bbox_sig[3] - bbox_sig[1]
margin = int(base.width * 0.03)
x_sig = base.width - w_sig - margin
y_sig = base.height - h_sig - margin
draw.text((x_sig, y_sig), signature, font=font_signature, fill=(255, 255, 255, 180))
# === 軽いグロー効果 ===
glow = base.filter(ImageFilter.GaussianBlur(2))
base = Image.alpha_composite(glow, base)
# === バイナリ出力 ===
output = io.BytesIO()
base.convert("RGB").save(output, format="PNG")
output.seek(0)
return send_file(output, mimetype="image/png")
except Exception as e:
return jsonify({"error": str(e)}), 500
解説
1. FlaskでのAPI構築
Flask は軽量なWebフレームワークであり、数行でAPIを構築できます。
@app.route() デコレーターを使い、/api/image/place にGETリクエストが来た時の処理を定義しています。
レスポンスは画像そのものを send_file() で返しているため、
<img src="http://localhost:5000/api/image/place"> のように直接HTMLから読み込むことも可能です。
2. Pillowによる画像操作
背景の読み込みと暗化処理
overlay = Image.new("RGBA", base.size, (0, 0, 0, 100))
base = Image.alpha_composite(base, overlay)
Image.alpha_composite は2つのRGBA画像を合成する関数です。
ここでは、透過黒レイヤー (0, 0, 0, 100) を重ねることで背景を暗くし、文字が見やすくなるようにしています。
テキスト描画と座標調整
ImageDraw.Draw オブジェクトを用いて文字を描画します。
テキストを中央や任意の位置に正確に配置するために、textbbox() で文字サイズを取得しています。
bbox = draw.textbbox((0, 0), day, font=font_big)
w, h = bbox[2] - bbox[0], bbox[3] - bbox[1]
x = (base.width - w) * 0.2
y = (base.height - h) * 0.25
このように、描画前にサイズを取得して相対的な座標を計算することで、
異なる画像サイズでも自然にバランスの取れたレイアウトになります。
フォント設定
font_big = ImageFont.truetype("font.ttf", int(base.height * 0.45))
画像サイズに応じてフォントサイズを動的に計算しています。
高さの45%・10%・5%をそれぞれ日付・月・時間・曜日に割り当てており、
高解像度画像でもスケーリングの問題が発生しません。
縦書き文字の描画
曜日を縦に並べて描画するため、
Pythonの文字列を "\n".join(list(weekday)) で1文字ずつ改行して描いています。
これにより、欧文フォントでも簡易的な縦書き表現が可能になります。
グロー効果
最後に GaussianBlur フィルターを用いたソフトな光の演出を追加しています。
glow = base.filter(ImageFilter.GaussianBlur(2))
base = Image.alpha_composite(glow, base)
背景や文字全体にわずかなぼかしを重ねることで、
全体に深みを出す「光が滲む」ような効果が得られます。
3. バイナリ出力とレスポンス
生成した画像は一度 BytesIO に保存し、HTTPレスポンスとして返却しています。
output = io.BytesIO()
base.convert("RGB").save(output, format="PNG")
output.seek(0)
return send_file(output, mimetype="image/png")
この方式により、ファイルを保存せずそのままレスポンスとして返すことができます。
ディスクI/Oを発生させないため、パフォーマンス面でも効率的です。
起動方法
- 依存パッケージのインストール
pip install flask pillow
- サーバー起動
python app.py
- ブラウザで確認
http://127.0.0.1:5000/api/image/place
APIレスポンス例
成功時
HTTPヘッダ:
Content-Type: image/png
ブラウザで直接画像が表示されます。
WebアプリやDiscord Botなどで動的に画像を埋め込むことも可能です。
失敗時(例: フォルダに画像がない)
{
"error": "imageフォルダに画像が見つかりません。"
}
応用例
- クエリパラメータで署名テキストを変更する (
?signature=MyApp) -
Unsplash APIなどと組み合わせてリアルタイム背景を取得 - Discord Bot の
/travelコマンドで動的に呼び出す - OpenCV と組み合わせて天気情報や気温を描画する
まとめ
このAPIは、画像生成 × Flask REST構成 の基本を押さえつつ、
動的テキストや効果の描画を実装する実践的なサンプルです。
- Pillowの
ImageDraw,ImageFont,ImageFilterを理解できる - Flaskでバイナリデータを返す構成を学べる
- スケーラブルでシンプルなAPI設計が可能
