| name | image-processing |
| description | Image processing and manipulation |
| license | MIT |
| compatibility | opencode |
| metadata | {"audience":"developers","category":"utilities"} |
What I do
- Resize and transform images
- Handle image formats
- Optimize image quality
- Generate thumbnails
- Extract image metadata
- Apply filters and effects
- Handle image uploads securely
- Serve optimized images
When to use me
When implementing image processing or handling image uploads.
Image Processing Basics
from PIL import Image, ImageFilter, ImageEnhance
from dataclasses import dataclass
from pathlib import Path
from typing import Optional, List, Tuple
import io
@dataclass
class ImageDimensions:
"""Image dimensions."""
width: int
height: int
@property
def aspect_ratio(self) -> float:
return self.width / self.height
def resize(self, max_width: int, max_height: int) -> 'ImageDimensions':
"""Resize while maintaining aspect ratio."""
ratio = min(max_width / self.width, max_height / self.height)
return ImageDimensions(
width=int(self.width * ratio),
height=int(self.height * ratio)
)
@dataclass
class ImageConfig:
"""Image processing configuration."""
format: str = "JPEG"
quality: int = 85
optimize: bool = True
preserve_metadata: bool = False
class ImageProcessor:
"""Image processing operations."""
def __init__(self, config: ImageConfig = None):
self.config = config or ImageConfig()
def open(self, image_path: Path) -> Image:
"""Open an image file."""
return Image.open(image_path)
def resize(
self,
image: Image,
width: int,
height: int,
maintain_aspect: bool = True
) -> Image:
"""Resize image."""
if maintain_aspect:
current_dims = ImageDimensions(image.width, image.height)
new_dims = current_dims.resize(width, height)
width, height = new_dims.width, new_dims.height
return image.resize((width, height), Image.LANCZOS)
def create_thumbnail(
self,
image: Image,
max_size: Tuple[int, int] = (300, 300)
) -> Image:
"""Create thumbnail preserving aspect ratio."""
image.thumbnail(max_size, Image.LANCZOS)
return image
def crop(
self,
image: Image,
left: int,
top: int,
right: int,
bottom: int
) -> Image:
"""Crop image."""
return image.crop((left, top, right, bottom))
def rotate(self, image: Image, degrees: float) -> Image:
"""Rotate image."""
return image.rotate(-degrees, expand=True)
def flip_horizontal(self, image: Image) -> Image:
"""Flip image horizontally."""
return image.transpose(Image.FLIP_LEFT_RIGHT)
def flip_vertical(self, image: Image) -> Image:
"""Flip image vertically."""
return image.transpose(Image.FLIP_TOP_BOTTOM)
class ImageFilterApplier:
"""Apply filters to images."""
@staticmethod
def blur(image: Image, radius: float = 2) -> Image:
"""Apply Gaussian blur."""
return image.filter(ImageFilter.GaussianBlur(radius))
@staticmethod
def sharpen(image: Image) -> Image:
"""Sharpen image."""
return image.filter(ImageFilter.SHARPEN)
@staticmethod
def contour(image: Image) -> Image:
"""Apply contour filter."""
return image.filter(ImageFilter.CONTOUR)
@staticmethod
def edge_enhance(image: Image) -> Image:
"""Edge enhancement."""
return image.filter(ImageFilter.EDGE_ENHANCE)
@staticmethod
def emboss(image: Image) -> Image:
"""Apply emboss effect."""
return image.filter(ImageFilter.EMBOSS)
@staticmethod
def adjust_brightness(image: Image, factor: float) -> Image:
"""Adjust brightness."""
enhancer = ImageEnhance.Brightness(image)
return enhancer.enhance(factor)
@staticmethod
def adjust_contrast(image: Image, factor: float) -> Image:
"""Adjust contrast."""
enhancer = ImageEnhance.Contrast(image)
return enhancer.enhance(factor)
@staticmethod
def adjust_saturation(image: Image, factor: float) -> Image:
"""Adjust saturation."""
enhancer = ImageEnhance.Color(image)
return enhancer.enhance(factor)
class ImageOptimizer:
"""Optimize images for web."""
def __init__(self, config: ImageConfig = None):
self.config = config or ImageConfig()
def optimize(
self,
image: Image,
output_format: str = None
) -> Image:
"""Optimize image."""
format = output_format or self.config.format
if format.upper() == "JPEG":
image = image.convert("RGB")
return image
def save_with_quality(
self,
image: Image,
output_path: Path,
quality: int = None,
format: str = None
) -> None:
"""Save image with specific quality."""
quality = quality or self.config.quality
format = format or self.config.format
save_kwargs = {
"quality": quality,
"optimize": self.config.optimize,
}
if format.upper() == "JPEG":
save_kwargs["subsampling"] = 0
image.save(output_path, format=format, **save_kwargs)
def save_to_bytes(
self,
image: Image,
format: str = None,
quality: int = None
) -> bytes:
"""Save image to bytes."""
format = format or self.config.format
quality = quality or self.config.quality
buffer = io.BytesIO()
save_kwargs = {
"quality": quality,
"optimize": self.config.optimize,
}
image.save(buffer, format=format, **save_kwargs)
return buffer.getvalue()
def create_responsive_set(
self,
image: Image,
sizes: List[Tuple[int, str]],
output_dir: Path,
prefix: str = "image"
) -> List[dict]:
"""Create responsive image set."""
results = []
for width, suffix in sizes:
new_height = int(image.height * (width / image.width))
resized = self.resize(
image,
width,
new_height,
maintain_aspect=True
)
filename = f"{prefix}_{suffix}.jpg"
output_path = output_dir / filename
self.save_with_quality(resized, output_path)
results.append({
"filename": filename,
"width": resized.width,
"height": resized.height,
"url": f"/images/{filename}"
})
return results
Image Upload Handling
from pathlib import Path
from typing import Optional
import magic
import hashlib
class SecureImageUploader:
"""Secure image upload handling."""
ALLOWED_TYPES = {
"image/jpeg": ".jpg",
"image/png": ".png",
"image/gif": ".gif",
"image/webp": ".webp",
}
MAX_SIZE = 10 * 1024 * 1024
def __init__(self, upload_dir: Path, processor: ImageProcessor):
self.upload_dir = upload_dir
self.processor = processor
self.upload_dir.mkdir(parents=True, exist_ok=True)
async def save_upload(
self,
file_content: bytes,
original_filename: str
) -> dict:
"""Save uploaded image securely."""
mime_type = magic.from_buffer(file_content, mime=True)
if mime_type not in self.ALLOWED_TYPES:
raise InvalidImageError(
f"Invalid file type: "
)
(file_content) > .MAX_SIZE:
InvalidImageError(
)
ext = .ALLOWED_TYPES[mime_type]
safe_filename = ._generate_safe_filename(original_filename, ext)
file_path = .upload_dir / safe_filename
file_path.write_bytes(file_content)
image = .processor.(file_path)
thumbnail = .processor.create_thumbnail(image)
thumbnail_path = .upload_dir /
.processor.save_with_quality(thumbnail, thumbnail_path)
{
: (file_path),
: (thumbnail_path),
: safe_filename,
: image.width,
: image.height,
: (file_content),
: mime_type,
}
() -> :
stem = Path(original_filename).stem
safe_stem = .join(
c c stem
c.isalnum() c
)
safe_stem = safe_stem[:]
hash_suffix = hashlib.md5(
.encode()
).hexdigest()[:]
Image Analysis
from PIL import ImageDraw, ImageFont
from collections import Counter
class ImageAnalyzer:
"""Analyze image properties."""
@staticmethod
def get_dimensions(image: Image) -> Tuple[int, int]:
"""Get image dimensions."""
return image.width, image.height
@staticmethod
def get_format(image: Image) -> str:
"""Get image format."""
return image.format or "Unknown"
@staticmethod
def get_mode(image: Image) -> str:
"""Get image mode (RGB, L, etc.)."""
return image.mode
@staticmethod
def get_info(image: Image) -> dict:
"""Get comprehensive image info."""
return {
"format": image.format,
"mode": image.mode,
"width": image.width,
"height": image.height,
"size_bytes": image.size[0] * image.size[1],
: image.mode (, , ),
: image.width / image.height image.height ,
}
() -> :
image.mode != :
image = image.convert()
colors = (image.getdata())
color_counts = Counter(colors)
{
: (color_counts),
: color_counts.most_common(),
}
() -> :
image.height > image.width
() -> :
image.width > image.height
() -> :
image.width == image.height
() -> :
width = image.width
height = image.height
width < height < :
width < height < :
width < height < :
:
:
():
.text = text
.font_size = font_size
() -> Image:
image.mode != :
image = image.convert()
watermark = Image.new(, image.size, (, , , ))
draw = ImageDraw.Draw(watermark)
width, height = image.size
position == :
x = width -
y = height -
position == :
x =
y = height -
position == :
x = width -
y =
position == :
x =
y =
:
x = width // -
y = height //
draw.text(
(x, y),
.text,
fill=(, , , ( * opacity))
)
watermarked = Image.alpha_composite(image, watermark)
watermarked.convert()
Best Practices
Image Processing Best Practices:
1. Validate uploads
Check file type
Check file size
2. Generate unique names
Prevent overwrites
Use hashes
3. Create thumbnails
For previews
Reduce bandwidth
4. Use appropriate formats
JPEG for photos
PNG for graphics
WebP for modern browsers
5. Optimize for web
Compress images
Right-size dimensions
6. Preserve aspect ratio
Don't distort images
Calculate dimensions
7. Handle metadata
Strip sensitive EXIF
Preserve copyright if needed
8. Serve efficiently
CDN for delivery
Responsive images
9. Process asynchronously
Don't block requests
Use queues
10. Clean up old files
Define retention
Delete unused images