import traceback
from fastapi import FastAPI, UploadFile, File, Form, Request
from fastapi.responses import StreamingResponse, HTMLResponse, FileResponse, JSONResponse
import cv2
import numpy as np
from PIL import Image
import io, os, base64
app = FastAPI()
BASE = os.getcwd()
OUTPUT_DIR = os.path.join(BASE, 'shiny_output')
os.makedirs(OUTPUT_DIR, exist_ok=True)
HTML = '''
Shiny Monster Maker Pro
全維度色違調整工具 (HSV 模式)
AWAITING IMAGE...
'''
@app.get('/', response_class=HTMLResponse)
def home():
return HTML
@app.middleware("http")
async def catch_exceptions_middleware(request: Request, call_next):
try:
return await call_next(request)
except Exception as e:
# 在終端機印出完整的錯誤追蹤
traceback.print_exc()
return JSONResponse(
status_code=500,
content={"message": str(e), "traceback": traceback.format_exc()}
)
@app.post('/process_shiny')
async def process_shiny(
file: UploadFile = File(...),
hue_shift: int = Form(...),
sat_min: int = Form(...),
sat_scale: float = Form(...),
val_scale: float = Form(...),
# 新增參數:染色強度 (0.0 為原本邏輯, 1.0 為完全強制染色)
tint_strength: float = Form(0.0)
):
try:
data = await file.read()
nparr = np.frombuffer(data, np.uint8)
img = cv2.imdecode(nparr, cv2.IMREAD_UNCHANGED)
# 頻道分離 (處理透明度)
if img.shape[2] == 4:
bgr = img[:, :, :3]
alpha = img[:, :, 3]
else:
bgr = img
alpha = None
# --- 針對低飽和度怪物的優化處理 ---
if tint_strength > 0:
# 1. 將圖片轉為灰階獲取亮度資訊
gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
# 2. 建立目標顏色 (根據 Hue 決定目標色)
# 建立一個單一像素的 HSV,轉回 BGR 獲得目標色
target_hsv = np.array([[[hue_shift, 200, 255]]], dtype=np.uint8)
target_color = cv2.cvtColor(target_hsv, cv2.COLOR_HSV2BGR)[0][0]
# 3. 線性混合:將亮度圖對應到目標顏色
# 亮度越高的地方顏色越亮,亮度越低顏色越深
tinted = np.zeros_like(bgr, dtype=np.float32)
for i in range(3): # B, G, R
tinted[:, :, i] = (gray / 255.0) * target_color[i]
# 4. 根據強度與原圖混合
bgr = cv2.addWeighted(bgr.astype(np.float32), 1 - tint_strength, tinted, tint_strength, 0)
bgr = np.clip(bgr, 0, 255).astype(np.uint8)
# --- 原始 HSV 調整 (對染色後的結果再做 S/V 微調) ---
hsv = cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV).astype(np.float32)
h, s, v = cv2.split(hsv)
# 僅在 tint_strength 為 0 時旋轉色相,避免衝突
if tint_strength == 0:
mask = s > (float(sat_min) * 2.55)
h[mask] = (h[mask] + float(hue_shift)) % 180
s = np.clip(s * sat_scale, 0, 255)
v = np.clip(v * val_scale, 0, 255)
hsv_new = cv2.merge([h, s, v]).astype(np.uint8)
res_bgr = cv2.cvtColor(hsv_new, cv2.COLOR_HSV2BGR)
# 重組 Alpha 並回傳
if alpha is not None:
res = cv2.merge([res_bgr, alpha])
else:
res = res_bgr
_, buffer = cv2.imencode('.png', res)
return StreamingResponse(io.BytesIO(buffer.tobytes()), media_type='image/png')
except Exception as e:
traceback.print_exc()
return JSONResponse(status_code=500, content={"message": str(e)})
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8001)