0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Flask + Pillowで「旅してる気分になる画像API」を作ってみた

Last updated at Posted at 2025-10-25

こんにちは。今回は FlaskPillow を使って、
ランダムな背景画像に日付や時間を合成し、まるで旅のワンシーンのような画像を自動生成するAPIを作ってみました。


概要

このAPI /api/image/place は、サーバー内の image フォルダにあるランダムな背景画像を選び、
その上に「日付」「月」「曜日」「時刻」などを重ね描きします。
背景を暗くして文字を浮かび上がらせるためのレイヤー処理も行い、見た目を自然に仕上げています。

出力イメージ例

example.png


使用技術

要素 使用ライブラリ
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を発生させないため、パフォーマンス面でも効率的です。


起動方法

  1. 依存パッケージのインストール
pip install flask pillow
  1. サーバー起動
python app.py
  1. ブラウザで確認
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設計が可能
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?