pillow — Cursor rule: what it does, how to install
Guides Cursor when writing Python code with Pillow. Enforces context managers and safe image format export.
.cursor/rules/pillow.mdcWhat pillow does
Applied to **/*.py files, this rule stops the assistant from leaking system file descriptors. It requires wrapping calls in with Image.open(...) as im: blocks. For output, it forces explicit parameter choices: converting transparent RGBA images to RGB before JPEG export, setting optimize=True, and applying maximum compression for PNG files.
The instructions direct the model away from manual pixel loops toward built-in methods like im.resize() with Image.Resampling.LANCZOS. For validation, it tests generated images against reference fixtures using ImageChops.difference. Custom format plugins must be imported explicitly since Pillow does not auto-load them. Without explicit RGB conversion, saving transparent PNGs as JPEG crashes at runtime.
What it helps with
- prevent file descriptor leaks using
withblocks - convert RGBA modes to RGB before JPEG export
- tune compression levels when saving PNG and JPEG files
- verify image manipulation functions with
ImageChops.difference
Example prompts for the agent
- Create a helper function to resize avatar uploads for our website
- Write a batch script that optimizes JPEG images in a directory
- Add a watermark to this image and write a pytest test case for it
How to install pillow
Manually: put the files in place
.cursor/rules/pillow.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/pillow.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/pillow.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/pillow.mdManually: put the files in place
.clinerules/pillow.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about pillow
How to install pillow in Cursor?
Place the rule file at .cursor/rules/pillow.mdc in your project repository.
What does the pillow rule do?
It directs Cursor to apply safe Pillow practices in Python, including context managers, Image.Image type hints, and explicit format conversion.
Why does the rule enforce RGB conversion before saving JPEGs?
JPEG does not support alpha channels. Saving an RGBA image directly to JPEG causes an unhandled runtime error.
Source text
pillow.mdc · CC0-1.0
---
description: Definitive guidelines for using Pillow (Python Imaging Library) effectively, focusing on modern best practices, performance, and maintainability.
globs: **/*.py
---
# Pillow Best Practices
Pillow is the definitive image processing library for Python. Adhere to these guidelines to ensure your image manipulation code is robust, performant, and maintainable.
## Critical Guidelines:
### 1. Resource Management: Always Use Context Managers
Images opened from files consume system resources. Ensure they are properly closed using `with` statements.
❌ BAD:
```python
from PIL import Image
im = Image.open("input.jpg")
# ... process im ...
# Resource might leak if not explicitly closed or on error
# im.close() # Often forgotten, especially on exceptions
```
✅ GOOD:
```python
from PIL import Image
def process_image_with_thumbnail(path: str) -> None:
with Image.open(path) as im:
# Image is automatically closed when exiting the 'with' block
im.thumbnail((128, 128))
im.save("thumbnail.jpg")
```
### 2. Prefer High-Level API for Common Operations
Pillow provides a rich, high-level API for most common image transformations. Use these methods instead of manual pixel manipulation or low-level `ImageFile` operations.
❌ BAD:
```python
from PIL import Image
# Avoid manual pixel access for transformations; it's inefficient and error-prone
def resize_manual(im: Image.Image, size: tuple[int, int]) -> Image.Image:
new_im = Image.new(im.mode, size)
# ... complex, slow loop to copy pixels ...
return new_im
```
✅ GOOD:
```python
from PIL import Image, ImageFilter
def apply_common_transforms(im: Image.Image) -> Image.Image:
# Use built-in methods for efficiency and correctness
im_resized = im.resize((256, 256), Image.Resampling.LANCZOS)
im_rotated = im_resized.rotate(45, expand=True)
im_blurred = im_rotated.filter(ImageFilter.BLUR)
return im_blurred
```
### 3. Explicitly Import Custom Plugins
Pillow no longer auto-loads plugins (since 2.1.0). If you're using a custom image format or a third-party plugin, you *must* import it explicitly before use.
❌ BAD:
```python
from PIL import Image
# Assuming 'MyCustomImagePlugin.py' is on PYTHONPATH
# This will fail if MyCustomImagePlugin is not explicitly imported
try:
with Image.open("custom.myformat") as im:
im.save("output.png")
except Exception as e:
print(f"Error: {e}") # Likely 'cannot identify image file'
```
✅ GOOD:
```python
from PIL import Image
import MyCustomImagePlugin # Explicitly import your plugin
def load_custom_image(path: str) -> Image.Image:
with Image.open(path) as im:
return im.copy() # Return a copy if further processing is needed outside the 'with' block
```
### 4. Optimize Image Saving for Web/Performance
When saving images, especially for web use, optimize for file size and quality. Always convert to `RGB` for JPEG to avoid issues with alpha channels.
❌ BAD:
```python
from PIL import Image
# Default JPEG quality (often 75) might not be optimal, no explicit optimization
with Image.open("input.png") as im:
im.save("output.jpg")
```
✅ GOOD:
```python
from PIL import Image
def save_optimized_jpeg(im: Image.Image, path: str, quality: int = 85) -> None:
# Convert to RGB if not already, as JPEG doesn't support RGBA
if im.mode == 'RGBA':
im = im.convert('RGB')
im.save(path, quality=quality, optimize=True)
def save_optimized_png(im: Image.Image, path: str, compress_level: int = 9) -> None:
# PNG compression level 0-9 (9 is highest compression, slowest)
im.save(path, compress_level=compress_level)
```
### 5. Use Type Hints for Clarity and Maintainability
Always type-hint Pillow objects, especially `Image.Image`, to improve code readability and enable static analysis.
❌ BAD:
```python
from PIL import Image
def process_img(img_obj, scale): # What is img_obj? What does it return?
return img_obj.resize((img_obj.width // scale, img_obj.height // scale))
```
✅ GOOD:
```python
from PIL import Image
def process_img(img_obj: Image.Image, scale: int) -> Image.Image:
"""Resizes an image by a given scale factor."""
new_size = (img_obj.width // scale, img_obj.height // scale)
return img_obj.resize(new_size, Image.Resampling.BICUBIC)
```
### 6. Manage Dependencies with Virtual Environments
Always install Pillow within an isolated virtual environment to prevent dependency conflicts and ensure reproducible builds.
❌ BAD:
```bash
pip install Pillow # Global installation, pollutes system Python
```
✅ GOOD:
```bash
python3 -m venv .venv
source .venv/bin/activate # or .venv\Scripts\activate on Windows
pip install --upgrade pip
pip install --upgrade Pillow defusedxml olefile # Include optional dependencies for broader format support
```
### 7. Test Image Operations Rigorously
Write unit tests for functions that process images. Use small, controlled input images and assert expected output properties or pixel differences.
❌ BAD:
```python
# No tests for image processing logic, leading to potential regressions
def watermark_image(im: Image.Image, watermark_text: str) -> Image.Image:
# ... complex drawing logic ...
return im
```
✅ GOOD:
```python
from PIL import Image, ImageDraw, ImageChops
import io
def watermark_image(im: Image.Image, watermark_text: str) -> Image.Image:
draw = ImageDraw.Draw(im)
draw.text((10, 10), watermark_text, fill=(255, 255, 255))
return im
# Example test function (integrate into your test suite, e.g., pytest)
def test_watermark_image():
original_image = Image.new("RGB", (100, 100), "blue")
watermarked_image = watermark_image(original_image.copy(), "TEST")
# Create a reference image with the expected watermark
expected_image = Image.new("RGB", (100, 100), "blue")
draw_expected = ImageDraw.Draw(expected_image)
draw_expected.text((10, 10), "TEST", fill=(255, 255, 255))
# Assert that the images are identical using ImageChops.difference
diff = ImageChops.difference(watermarked_image, expected_image)
assert not diff.getbbox(), "Watermarked image does not match expected output"
```
### 8. Stay Current with Pillow Releases
Regularly update Pillow to benefit from performance improvements, bug fixes, and new features, and to avoid deprecated APIs. Always check release notes for breaking changes (e.g., the 12.x series deprecates older `ImageFile` shortcuts).
❌ BAD:
```python
# Sticking to an old version (e.g., < 7.0)
# Relying on auto-loading plugins or deprecated ImageFile shortcuts
```
✅ GOOD:
```bash
# Periodically update your environment to the latest stable version
pip install --upgrade Pillow
```
```python
# Consult release notes (e.g., Pillow 12.1.0.dev0) and adapt code
# to new APIs and deprecation warnings as they arise.
```
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc