원클릭으로
photo-editor
Edit, resize, crop, filter, and optimize images using code-based image processing
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Edit, resize, crop, filter, and optimize images using code-based image processing
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Design static ad creatives for social media and display advertising campaigns.
Source and evaluate candidates with job analysis, search strategies, specific candidate profiles, outreach templates, CV screening, and Gmail-based candidate communication.
Find relevant companies and leads for B2B sales with ICP definition and qualification frameworks.
Draft emails, manage calendar scheduling, prepare meeting agendas, track tasks, manage contacts, coordinate travel, and organize productivity
Create brand identity kits with color palettes, typography, logo concepts, brand naming, and brand guidelines.
Perform competitive market analysis with feature comparisons, positioning, and strategic recommendations.
| name | photo-editor |
| description | Edit, resize, crop, filter, and optimize images using code-based image processing |
Resize, crop, filter, and optimize images. Pillow for Python, sharp for Node. Clarify intent before starting.
When a user asks to "edit a photo" or "change an image," the request could mean two very different things. Ask before proceeding if it's ambiguous:
When to ask:
Don't ask when it's obvious:
| Tool | Use when | Install |
|---|---|---|
| Pillow | Default: resize, crop, filters, text, format conversion | pip install Pillow |
| OpenCV | Computer vision: face detection, perspective transform, contours | pip install opencv-python |
| sharp (Node) | High-volume pipelines — 4-5x faster than Pillow (libvips-backed) | npm install sharp |
| rembg | AI background removal | pip install rembg |
| ImageMagick | CLI batch ops, 200+ formats | apt install imagemagick |
from PIL import Image, ImageOps
img = Image.open("photo.jpg")
img = ImageOps.exif_transpose(img) # CRITICAL: applies EXIF rotation, then strips tag
# Without this, phone photos appear sideways after processing
from PIL import Image, ImageOps
# --- Fit inside box, keep aspect ratio (shrink only) ---
img.thumbnail((1080, 1080), Image.Resampling.LANCZOS) # modifies in place
# --- Exact size, keep aspect, center-crop overflow (best for thumbnails) ---
thumb = ImageOps.fit(img, (300, 300), Image.Resampling.LANCZOS, centering=(0.5, 0.5))
# --- Exact size, keep aspect, pad with color (letterbox) ---
padded = ImageOps.pad(img, (1920, 1080), color=(0, 0, 0))
# --- Exact size, ignore aspect (will distort) ---
stretched = img.resize((800, 600), Image.Resampling.LANCZOS)
# --- Scale by factor ---
half = img.resize((img.width // 2, img.height // 2), Image.Resampling.LANCZOS)
# --- Manual crop (left, upper, right, lower) — NOT (x, y, w, h) ---
cropped = img.crop((100, 50, 900, 650))
Resampling filters: LANCZOS for photo downscale (best quality), BICUBIC for upscale, NEAREST for pixel art/icons (no smoothing).
from PIL import ImageEnhance, ImageOps
# --- Enhancers: 1.0 = unchanged, <1 less, >1 more ---
img = ImageEnhance.Brightness(img).enhance(1.15)
img = ImageEnhance.Contrast(img).enhance(1.2)
img = ImageEnhance.Color(img).enhance(1.1) # saturation
img = ImageEnhance.Sharpness(img).enhance(1.5)
# --- Quick ops ---
gray = ImageOps.grayscale(img)
inverted = ImageOps.invert(img.convert("RGB"))
auto = ImageOps.autocontrast(img, cutoff=1) # stretch histogram, clip 1% extremes
equalized = ImageOps.equalize(img) # flatten histogram
from PIL import ImageFilter
img.filter(ImageFilter.GaussianBlur(radius=5))
img.filter(ImageFilter.UnsharpMask(radius=2, percent=150, threshold=3)) # better than SHARPEN
img.filter(ImageFilter.BoxBlur(10))
img.filter(ImageFilter.FIND_EDGES)
img.filter(ImageFilter.MedianFilter(size=3)) # denoise, removes salt-and-pepper
from PIL import Image, ImageDraw, ImageFont
draw = ImageDraw.Draw(img)
try:
font = ImageFont.truetype("DejaVuSans-Bold.ttf", 48) # Linux default
except OSError:
font = ImageFont.load_default() # fallback (tiny, ugly)
# --- Text with outline ---
draw.text((50, 50), "Caption", font=font, fill="white",
stroke_width=3, stroke_fill="black")
# --- Centered text ---
bbox = draw.textbbox((0, 0), "Centered", font=font)
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
draw.text(((img.width - tw) // 2, (img.height - th) // 2), "Centered", font=font, fill="white")
# --- Watermark (semi-transparent PNG overlay) ---
logo = Image.open("logo.png").convert("RGBA")
logo.thumbnail((img.width // 5, img.height // 5))
# Fade to 40% opacity
alpha = logo.split()[3].point(lambda p: int(p * 0.4))
logo.putalpha(alpha)
pos = (img.width - logo.width - 20, img.height - logo.height - 20)
img.paste(logo, pos, logo) # third arg = alpha mask — REQUIRED for transparency
# --- JPEG ---
img.convert("RGB").save("out.jpg", quality=85, optimize=True, progressive=True)
# convert("RGB") REQUIRED if source has alpha — JPEG can't store transparency
# --- PNG (lossless — quality param does nothing) ---
img.save("out.png", optimize=True, compress_level=9)
# --- WebP (best web format: ~30% smaller than JPEG at same quality) ---
img.save("out.webp", quality=85, method=6) # method 0-6, 6=slowest/best compression
# --- AVIF (smallest files, Pillow 11+, slower encode) ---
img.save("out.avif", quality=75) # 75 ≈ JPEG 85 visually, ~50% smaller
# --- Strip all metadata (privacy) ---
clean = Image.new(img.mode, img.size)
clean.putdata(list(img.getdata()))
clean.save("stripped.jpg", quality=85)
Quality guide: JPEG/WebP 85 = sweet spot. 90+ = diminishing returns. <70 = visible artifacts. Never re-save JPEGs repeatedly — each save degrades (generation loss).
from pathlib import Path
from PIL import Image, ImageOps
out = Path("optimized"); out.mkdir(exist_ok=True)
for p in Path("photos").glob("*.[jJ][pP]*[gG]"): # matches jpg, jpeg, JPG, JPEG
img = ImageOps.exif_transpose(Image.open(p))
img.thumbnail((1920, 1920), Image.Resampling.LANCZOS)
img.convert("RGB").save(out / f"{p.stem}.webp", quality=85, method=6)
const sharp = require('sharp');
// Resize + convert + optimize, streaming (flat memory)
await sharp('in.jpg')
.rotate() // auto-rotate from EXIF (like exif_transpose)
.resize(1080, 1080, { fit: 'cover', position: 'center' }) // = ImageOps.fit
.webp({ quality: 85 })
.toFile('out.webp');
// fit options: 'cover' (crop), 'contain' (letterbox), 'inside' (shrink to fit), 'fill' (stretch)
// Composite watermark
await sharp('photo.jpg')
.composite([{ input: 'logo.png', gravity: 'southeast' }])
.toFile('watermarked.jpg');
sharp strips all metadata by default. Use .withMetadata() to preserve EXIF/ICC.
import cv2
img = cv2.imread("in.jpg") # BGR order, not RGB!
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Face detection
cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
faces = cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5)
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.imwrite("out.jpg", img)
# Pillow ↔ OpenCV
import numpy as np
cv_img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
pil_img = Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB))
| Platform | Size | Ratio |
|---|---|---|
| Instagram post | 1080×1080 | 1:1 |
| Instagram story / TikTok | 1080×1920 | 9:16 |
| Twitter/X | 1200×675 | 16:9 |
| YouTube thumbnail | 1280×720 | 16:9 |
| Open Graph (link preview) | 1200×630 | 1.91:1 |
img.crop() box is (left, top, right, bottom) — absolute coords, NOT (x, y, width, height)thumbnail() mutates in place and returns None — don't do img = img.thumbnail(...)bg.paste(fg, pos, fg)img.convert("RGB") firstImageFont.truetype needs a real font file. Linux: /usr/share/fonts/truetype/dejavu/. Ship a .ttf with your code for portability.