SNAPKITTYWEST's picture
push from SNAPKITTYWEST/sovereign-engine-v2
9abace2 verified
Raw
History Blame Contribute Delete
7.27 kB
"""
Image Editing
Part of SOVEREIGN PYTHON LLM ENGINE
Edit images using PIL/Pillow.
"""
from typing import Any
from pathlib import Path
from dataclasses import dataclass
from io import BytesIO
@dataclass
class EditedImage:
"""Edited image result"""
image_bytes: bytes
format: str
size: tuple[int, int]
operations: list[str]
class ImageEditor:
"""
Image editing operations using Pillow.
Supports:
- Resize, crop, rotate
- Format conversion
- Filters and adjustments
- Watermarking
"""
def __init__(self):
try:
from PIL import Image
except ImportError:
raise ImportError("Pillow is required. Install: pip install Pillow")
async def resize(
self,
image_path: Path,
width: int,
height: int,
maintain_aspect: bool = True
) -> EditedImage:
"""
Resize image.
Args:
image_path: Input image
width: Target width
height: Target height
maintain_aspect: Maintain aspect ratio
Returns:
EditedImage
"""
from PIL import Image
import asyncio
def _sync_resize():
img = Image.open(image_path)
if maintain_aspect:
img.thumbnail((width, height), Image.Resampling.LANCZOS)
else:
img = img.resize((width, height), Image.Resampling.LANCZOS)
# Save to bytes
buffer = BytesIO()
img.save(buffer, format=img.format or "PNG")
buffer.seek(0)
return EditedImage(
image_bytes=buffer.read(),
format=img.format or "PNG",
size=img.size,
operations=["resize"]
)
return await asyncio.to_thread(_sync_resize)
async def crop(
self,
image_path: Path,
left: int,
top: int,
right: int,
bottom: int
) -> EditedImage:
"""
Crop image.
Args:
image_path: Input image
left, top, right, bottom: Crop box
Returns:
EditedImage
"""
from PIL import Image
import asyncio
def _sync_crop():
img = Image.open(image_path)
img = img.crop((left, top, right, bottom))
buffer = BytesIO()
img.save(buffer, format=img.format or "PNG")
buffer.seek(0)
return EditedImage(
image_bytes=buffer.read(),
format=img.format or "PNG",
size=img.size,
operations=["crop"]
)
return await asyncio.to_thread(_sync_crop)
async def rotate(
self,
image_path: Path,
angle: float,
expand: bool = True
) -> EditedImage:
"""
Rotate image.
Args:
image_path: Input image
angle: Rotation angle in degrees
expand: Expand image to fit rotated content
Returns:
EditedImage
"""
from PIL import Image
import asyncio
def _sync_rotate():
img = Image.open(image_path)
img = img.rotate(angle, expand=expand)
buffer = BytesIO()
img.save(buffer, format=img.format or "PNG")
buffer.seek(0)
return EditedImage(
image_bytes=buffer.read(),
format=img.format or "PNG",
size=img.size,
operations=["rotate"]
)
return await asyncio.to_thread(_sync_rotate)
async def convert_format(
self,
image_path: Path,
target_format: str
) -> EditedImage:
"""
Convert image format.
Args:
image_path: Input image
target_format: Target format ("PNG", "JPEG", "WEBP", etc.)
Returns:
EditedImage
"""
from PIL import Image
import asyncio
def _sync_convert():
img = Image.open(image_path)
# Convert RGBA to RGB if saving as JPEG
if target_format.upper() == "JPEG" and img.mode == "RGBA":
img = img.convert("RGB")
buffer = BytesIO()
img.save(buffer, format=target_format)
buffer.seek(0)
return EditedImage(
image_bytes=buffer.read(),
format=target_format,
size=img.size,
operations=["convert"]
)
return await asyncio.to_thread(_sync_convert)
async def apply_filter(
self,
image_path: Path,
filter_name: str
) -> EditedImage:
"""
Apply filter to image.
Args:
image_path: Input image
filter_name: Filter name ("blur", "sharpen", "contour", etc.)
Returns:
EditedImage
"""
from PIL import Image, ImageFilter
import asyncio
def _sync_filter():
img = Image.open(image_path)
# Map filter names
filters = {
"blur": ImageFilter.BLUR,
"sharpen": ImageFilter.SHARPEN,
"contour": ImageFilter.CONTOUR,
"detail": ImageFilter.DETAIL,
"edge_enhance": ImageFilter.EDGE_ENHANCE,
"smooth": ImageFilter.SMOOTH,
}
filter_obj = filters.get(filter_name.lower())
if not filter_obj:
raise ValueError(f"Unknown filter: {filter_name}")
img = img.filter(filter_obj)
buffer = BytesIO()
img.save(buffer, format=img.format or "PNG")
buffer.seek(0)
return EditedImage(
image_bytes=buffer.read(),
format=img.format or "PNG",
size=img.size,
operations=[f"filter:{filter_name}"]
)
return await asyncio.to_thread(_sync_filter)
# Tool registration helpers
async def resize_image_tool(
image_path: str,
width: int,
height: int,
output_path: str | None = None
) -> dict:
"""Resize image tool"""
editor = ImageEditor()
result = await editor.resize(Path(image_path), width, height)
if output_path:
Path(output_path).write_bytes(result.image_bytes)
return {
"size": result.size,
"format": result.format,
"operations": result.operations
}
async def convert_image_tool(
image_path: str,
target_format: str,
output_path: str | None = None
) -> dict:
"""Convert image format tool"""
editor = ImageEditor()
result = await editor.convert_format(Path(image_path), target_format)
if output_path:
Path(output_path).write_bytes(result.image_bytes)
return {
"format": result.format,
"size": result.size
}