원클릭으로
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, and outreach templates.
Draft emails, manage calendar scheduling, prepare meeting agendas, and organize productivity
Create brand identity kits with color palettes, typography, logo concepts, and brand guidelines.
Perform competitive market analysis with feature comparisons, positioning, and strategic recommendations.
Create social media posts, newsletters, and marketing content calibrated to your voice and platform.
| 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.