Files
ai-game-2/tools/ora_editor/routes/images.py
Bryce fb812e57bc Restructure ORA editor into modular blueprints with browse dialog
- Split app.py into route blueprints (files, layers, images, polygon, mask, krita)
- Create services layer (polygon_storage, comfyui, file_browser)
- Extract config constants to config.py
- Split templates into Jinja partials (base, components, modals)
- Add browse dialog for visual file navigation
- Add /api/browse endpoint for directory listing
2026-03-27 21:29:27 -07:00

166 lines
5.4 KiB
Python

"""Image serving routes for ORA Editor."""
import io
import zipfile
from flask import Blueprint, request, jsonify, send_file, Response
from pathlib import Path
from PIL import Image, ImageDraw
from ora_editor.ora_ops import parse_stack_xml
from ora_editor.config import PROJECT_ROOT
images_bp = Blueprint('images', __name__)
@images_bp.route('/api/image/<layer_name>')
def api_image(layer_name):
"""Serve a specific layer image."""
ora_path = request.args.get('ora_path')
if not ora_path:
return jsonify({'error': 'Missing ora_path'}), 400
try:
with zipfile.ZipFile(ora_path, 'r') as zf:
img_data = zf.read('mergedimage.png')
return send_file(io.BytesIO(img_data), mimetype='image/png')
except Exception as e:
return Response(f"Error loading image: {e}", status=500)
@images_bp.route('/api/image/base')
def api_image_base():
"""Serve the base/merged image."""
ora_path = request.args.get('ora_path')
if not ora_path:
return jsonify({'error': 'Missing ora_path'}), 400
try:
with zipfile.ZipFile(ora_path, 'r') as zf:
img_data = zf.read('mergedimage.png')
return send_file(io.BytesIO(img_data), mimetype='image/png')
except Exception as e:
return Response(f"Error loading image: {e}", status=500)
@images_bp.route('/api/image/layer/<layer_name>')
def api_image_layer(layer_name):
"""Serve a specific layer as image."""
ora_path = request.args.get('ora_path')
if not ora_path:
return jsonify({'error': 'Missing ora_path'}), 400
try:
from xml.etree import ElementTree as ET
with zipfile.ZipFile(ora_path, 'r') as zf:
stack = ET.fromstring(zf.read('stack.xml'))
for child in stack.find('stack'):
if child.tag == 'layer' and child.get('name') == layer_name:
src = child.get('src')
img_data = zf.read(src)
return send_file(io.BytesIO(img_data), mimetype='image/png')
elif child.tag == 'stack':
for layer in child:
if layer.tag == 'layer' and layer.get('name') == layer_name:
src = layer.get('src')
img_data = zf.read(src)
return send_file(io.BytesIO(img_data), mimetype='image/png')
return jsonify({'error': 'Layer not found'}), 404
except Exception as e:
import traceback
traceback.print_exc()
return Response(f"Error loading layer: {e}", status=500)
@images_bp.route('/api/image/polygon')
def api_image_polygon():
"""Serve polygon overlay if stored."""
from ora_editor.services import polygon_storage
ora_path = request.args.get('ora_path')
if not ora_path:
return Response("Missing ora_path", status=400)
poly_data = polygon_storage.get(ora_path)
if not poly_data:
return Response("No polygon data", status=404)
try:
with zipfile.ZipFile(ora_path, 'r') as zf:
base_img = Image.open(zf.open('mergedimage.png')).convert('RGBA')
points = poly_data.get('points', [])
color = poly_data.get('color', '#FF0000')
width = poly_data.get('width', 2)
if len(points) < 3:
return Response("Not enough points", status=404)
w, h = base_img.size
pixel_points = [(int(p['x'] * w), int(p['y'] * h)) for p in points]
draw = ImageDraw.Draw(base_img)
hex_color = color if len(color) == 7 else color + 'FF'
draw.polygon(pixel_points, outline=hex_color, width=width)
img_io = io.BytesIO()
base_img.save(img_io, format='PNG')
img_io.seek(0)
return send_file(img_io, mimetype='image/png')
except Exception as e:
return Response(f"Error drawing polygon: {e}", status=500)
@images_bp.route('/api/image/masked')
def api_image_masked():
"""Serve the base image with mask applied as alpha channel."""
ora_path = request.args.get('ora_path')
mask_path = request.args.get('mask_path')
if not ora_path or not mask_path:
return jsonify({'error': 'Missing ora_path or mask_path'}), 400
try:
with zipfile.ZipFile(ora_path, 'r') as zf:
base_img = Image.open(zf.open('mergedimage.png')).convert('RGBA')
mask_img = Image.open(mask_path).convert('L')
if mask_img.size != base_img.size:
mask_img = mask_img.resize(base_img.size, Image.Resampling.BILINEAR)
r, g, b, _ = base_img.split()
result = Image.merge('RGBA', (r, g, b, mask_img))
img_io = io.BytesIO()
result.save(img_io, format='PNG')
img_io.seek(0)
return send_file(img_io, mimetype='image/png')
except Exception as e:
return Response(f"Error creating masked image: {e}", status=500)
@images_bp.route('/api/file/mask')
def api_file_mask():
"""Serve a mask file from temp directory."""
path = request.args.get('path')
if not path:
return jsonify({'error': 'Missing path'}), 400
full_path = Path(path)
if not full_path.exists():
return jsonify({'error': 'File not found'}), 404
return send_file(full_path, mimetype='image/png')