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 模式)

0
1.0
1.0
0.0

專門處理灰色/單色怪物。0.0 為原本邏輯,1.0 為強行染色。

避免影響灰色/黑色區塊

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)