Skip to content

✨ Add Noise/Grain effect (Feature 4)#5

Merged
sjquant merged 5 commits intomainfrom
claude/plan-next-tasks-HLtZK
Mar 29, 2026
Merged

✨ Add Noise/Grain effect (Feature 4)#5
sjquant merged 5 commits intomainfrom
claude/plan-next-tasks-HLtZK

Conversation

@sjquant
Copy link
Copy Markdown
Owner

@sjquant sjquant commented Mar 29, 2026

✨ Summary

  • Add Grain effect model for per-layer noise on background and image layers
  • Add GrainLayer and canvas.grain() builder for full-canvas grain overlay
  • Full JSON serialization/deserialization with "type": "grain" discriminator
  • Pillow-only rendering; optional seed parameter for deterministic output

🔧 API

from quickthumb import Canvas, Grain

# Per-layer grain on a background
canvas = Canvas(1280, 720).background(
    color="#1A1A2E",
    effects=[Grain(intensity=0.12, monochrome=True, blend_mode="overlay")],
)

# Full-canvas grain via builder
canvas = (
    Canvas(1280, 720)
    .background(color="#0F172A")
    .grain(intensity=0.3, blend_mode="overlay", opacity=0.8)
)

🧪 Test plan

  • Grain defaults contract + JSON round-trip (test_background_layers.py)
  • Parametrized ValidationError for invalid intensity and opacity
  • canvas.grain() builder defaults + JSON round-trip (test_canvas.py)
  • Snapshot: canvas-level grain (snapshots/canvas_grain.png)
  • Snapshot: per-layer grain on background (snapshots/grain_effect_on_background.png)
  • Full suite: 383 passed, 0 new failures

https://claude.ai/code/session_01PFg4VN4EBub4RUFz2aoYtw

claude added 5 commits March 29, 2026 11:35
Adds `Grain` effect model for per-layer noise on background and image
layers, `GrainLayer` for full-canvas grain via `canvas.grain()`, JSON
serialization/deserialization, and Pillow-only rendering with optional
`seed` for deterministic output.

https://claude.ai/code/session_01PFg4VN4EBub4RUFz2aoYtw
Per product decision, grain is only exposed as a per-layer effect via
`effects=[Grain(...)]` on background and image layers. The canvas-level
`canvas.grain()` builder, `GrainLayer` model, and related tests/snapshot
are removed.

https://claude.ai/code/session_01PFg4VN4EBub4RUFz2aoYtw
- Extract _GRAIN_BLEND_MODES constant so allowed values live in one place
- Replace per-pixel randint loop with rng.randbytes() for seeded path
- Use Image.point() lookup table for scaling (avoids Python-level loop)
- Make _generate_noise_image a @staticmethod (no self dependency)
- Fix alpha handling in _blend_grain: blend only RGB channels to avoid
  fringing artifacts on semi-transparent images
- Short-circuit _blend_grain when opacity == 0.0
- Remove lazy `import os` / `import random` inside method body

https://claude.ai/code/session_01PFg4VN4EBub4RUFz2aoYtw
- Add Grain/noise section to README with API table and usage example
- Add Grain row to Feature Matrix
- Add two Grain gotchas
- Mark CLI, Template System, and Noise/Grain Effect as done in SPEC.md

https://claude.ai/code/session_01PFg4VN4EBub4RUFz2aoYtw
- api/effects.md: add Grain row to compatibility table + full Grain section
- api/background.md: update effects param type and notes link
- api/image.md: add Grain to effects list
- api/index.md: add Grain to public imports and effects page description
- json-schema.md: add Grain effect schema tab + update AI workflow prompt

https://claude.ai/code/session_01PFg4VN4EBub4RUFz2aoYtw
@sjquant sjquant merged commit c26285c into main Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants