nitro-image 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. nitro_image-1.0.0/.github/workflows/publishing.yml +53 -0
  2. nitro_image-1.0.0/.github/workflows/testing.yml +36 -0
  3. nitro_image-1.0.0/.gitignore +39 -0
  4. nitro_image-1.0.0/LICENSE +28 -0
  5. nitro_image-1.0.0/PKG-INFO +275 -0
  6. nitro_image-1.0.0/README.md +228 -0
  7. nitro_image-1.0.0/SKILL.md +621 -0
  8. nitro_image-1.0.0/pyproject.toml +53 -0
  9. nitro_image-1.0.0/src/nitro_img/__init__.py +33 -0
  10. nitro_image-1.0.0/src/nitro_img/batch.py +165 -0
  11. nitro_image-1.0.0/src/nitro_img/config.py +28 -0
  12. nitro_image-1.0.0/src/nitro_img/errors.py +25 -0
  13. nitro_image-1.0.0/src/nitro_img/image.py +483 -0
  14. nitro_image-1.0.0/src/nitro_img/integrations.py +87 -0
  15. nitro_image-1.0.0/src/nitro_img/loaders.py +104 -0
  16. nitro_image-1.0.0/src/nitro_img/operations/__init__.py +0 -0
  17. nitro_image-1.0.0/src/nitro_img/operations/adjust.py +46 -0
  18. nitro_image-1.0.0/src/nitro_img/operations/crop.py +43 -0
  19. nitro_image-1.0.0/src/nitro_img/operations/effects.py +66 -0
  20. nitro_image-1.0.0/src/nitro_img/operations/metadata.py +65 -0
  21. nitro_image-1.0.0/src/nitro_img/operations/overlay.py +150 -0
  22. nitro_image-1.0.0/src/nitro_img/operations/resize.py +133 -0
  23. nitro_image-1.0.0/src/nitro_img/operations/transform.py +33 -0
  24. nitro_image-1.0.0/src/nitro_img/output/__init__.py +0 -0
  25. nitro_image-1.0.0/src/nitro_img/output/encode.py +52 -0
  26. nitro_image-1.0.0/src/nitro_img/output/export.py +88 -0
  27. nitro_image-1.0.0/src/nitro_img/output/optimize.py +84 -0
  28. nitro_image-1.0.0/src/nitro_img/pipeline.py +42 -0
  29. nitro_image-1.0.0/src/nitro_img/placeholder.py +103 -0
  30. nitro_image-1.0.0/src/nitro_img/presets.py +152 -0
  31. nitro_image-1.0.0/src/nitro_img/responsive.py +71 -0
  32. nitro_image-1.0.0/src/nitro_img/types.py +40 -0
  33. nitro_image-1.0.0/src/nitro_img/utils.py +60 -0
  34. nitro_image-1.0.0/tests/conftest.py +95 -0
  35. nitro_image-1.0.0/tests/fixtures/batch_0.jpg +0 -0
  36. nitro_image-1.0.0/tests/fixtures/batch_1.jpg +0 -0
  37. nitro_image-1.0.0/tests/fixtures/batch_2.jpg +0 -0
  38. nitro_image-1.0.0/tests/fixtures/parallel_0.jpg +0 -0
  39. nitro_image-1.0.0/tests/fixtures/parallel_1.jpg +0 -0
  40. nitro_image-1.0.0/tests/fixtures/parallel_2.jpg +0 -0
  41. nitro_image-1.0.0/tests/fixtures/parallel_3.jpg +0 -0
  42. nitro_image-1.0.0/tests/fixtures/parallel_4.jpg +0 -0
  43. nitro_image-1.0.0/tests/fixtures/parallel_5.jpg +0 -0
  44. nitro_image-1.0.0/tests/fixtures/red.jpg +0 -0
  45. nitro_image-1.0.0/tests/fixtures/sample.jpg +0 -0
  46. nitro_image-1.0.0/tests/fixtures/sample.png +0 -0
  47. nitro_image-1.0.0/tests/fixtures/sample.webp +0 -0
  48. nitro_image-1.0.0/tests/fixtures/small.jpg +0 -0
  49. nitro_image-1.0.0/tests/fixtures/watermark.png +0 -0
  50. nitro_image-1.0.0/tests/fixtures/wide.jpg +0 -0
  51. nitro_image-1.0.0/tests/test_adjust.py +90 -0
  52. nitro_image-1.0.0/tests/test_batch.py +64 -0
  53. nitro_image-1.0.0/tests/test_crop.py +37 -0
  54. nitro_image-1.0.0/tests/test_effects.py +83 -0
  55. nitro_image-1.0.0/tests/test_image.py +107 -0
  56. nitro_image-1.0.0/tests/test_integrations.py +48 -0
  57. nitro_image-1.0.0/tests/test_metadata.py +28 -0
  58. nitro_image-1.0.0/tests/test_optimize.py +60 -0
  59. nitro_image-1.0.0/tests/test_output.py +81 -0
  60. nitro_image-1.0.0/tests/test_overlay.py +115 -0
  61. nitro_image-1.0.0/tests/test_parallel_batch.py +91 -0
  62. nitro_image-1.0.0/tests/test_pipeline.py +73 -0
  63. nitro_image-1.0.0/tests/test_placeholder.py +66 -0
  64. nitro_image-1.0.0/tests/test_presets.py +111 -0
  65. nitro_image-1.0.0/tests/test_resize.py +93 -0
  66. nitro_image-1.0.0/tests/test_responsive.py +72 -0
@@ -0,0 +1,53 @@
1
+ name: Upload Release to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ release-build:
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.x"
20
+
21
+ - name: Build release distributions
22
+ run: |
23
+ python -m pip install build
24
+ python -m build
25
+
26
+ - name: Upload distributions
27
+ uses: actions/upload-artifact@v4
28
+ with:
29
+ name: release-dists
30
+ path: dist/
31
+
32
+ pypi-publish:
33
+ runs-on: ubuntu-latest
34
+ environment:
35
+ name: pypi
36
+ url: https://pypi.org/p/nitro-image/
37
+ needs:
38
+ - release-build
39
+
40
+ permissions:
41
+ id-token: write
42
+
43
+ steps:
44
+ - name: Retrieve release distributions
45
+ uses: actions/download-artifact@v4
46
+ with:
47
+ name: release-dists
48
+ path: dist/
49
+
50
+ - name: Publish release distributions to PyPI
51
+ uses: pypa/gh-action-pypi-publish@release/v1
52
+ with:
53
+ password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,36 @@
1
+ name: Test Nitro CLI
2
+
3
+ on:
4
+ push:
5
+ branches: [ "main" ]
6
+ pull_request:
7
+ branches: [ "main" ]
8
+
9
+ jobs:
10
+ build:
11
+
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - name: Set up Python ${{ matrix.python-version }}
21
+ uses: actions/setup-python@v3
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+ - name: Install dependencies
25
+ run: |
26
+ python -m pip install --upgrade pip
27
+ python -m pip install flake8 pytest
28
+ if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
29
+ pip install ".[dev]"
30
+ - name: Lint with flake8
31
+ run: |
32
+ flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
33
+ flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
34
+ - name: Test with pytest
35
+ run: |
36
+ pytest
@@ -0,0 +1,39 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+
7
+ # Distribution / packaging
8
+ dist/
9
+ build/
10
+ *.egg-info/
11
+ *.egg
12
+
13
+ # Virtual environments
14
+ .venv/
15
+ venv/
16
+ env/
17
+
18
+ # IDE
19
+ .idea/
20
+ .vscode/
21
+ *.swp
22
+ *.swo
23
+ *~
24
+
25
+ # Testing / coverage
26
+ .pytest_cache/
27
+ htmlcov/
28
+ .coverage
29
+ .coverage.*
30
+ coverage.xml
31
+
32
+ # OS
33
+ .DS_Store
34
+ Thumbs.db
35
+
36
+ # Project-specific
37
+ nitro-img-plan.md
38
+ .claude
39
+ CLAUDE.md
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Nitro.sh
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,275 @@
1
+ Metadata-Version: 2.4
2
+ Name: nitro-image
3
+ Version: 1.0.0
4
+ Summary: Fast, friendly image processing for web apps and SaaS
5
+ Project-URL: Homepage, https://github.com/nitrosh/nitro-image
6
+ Project-URL: Documentation, https://github.com/nitro-sh/nitro-image#readme
7
+ Project-URL: Repository, https://github.com/nitro-sh/nitro-image
8
+ Project-URL: Issues, https://github.com/nitro-sh/nitro-image/issues
9
+ Author-email: Sean Nieuwoudt <sean@underwulf.com>
10
+ License-Expression: BSD-3-Clause
11
+ License-File: LICENSE
12
+ Keywords: image,optimization,processing,resize,thumbnail,webp
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: BSD License
16
+ Classifier: Natural Language :: English
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
26
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
+ Classifier: Topic :: Utilities
28
+ Requires-Python: >=3.10
29
+ Requires-Dist: pillow>=10.0
30
+ Provides-Extra: all
31
+ Requires-Dist: blurhash-python; extra == 'all'
32
+ Requires-Dist: httpx; extra == 'all'
33
+ Requires-Dist: pillow-avif-plugin; extra == 'all'
34
+ Provides-Extra: avif
35
+ Requires-Dist: pillow-avif-plugin; extra == 'avif'
36
+ Provides-Extra: blur
37
+ Requires-Dist: blurhash-python; extra == 'blur'
38
+ Provides-Extra: dev
39
+ Requires-Dist: black; extra == 'dev'
40
+ Requires-Dist: flake8; extra == 'dev'
41
+ Requires-Dist: httpx; extra == 'dev'
42
+ Requires-Dist: pytest; extra == 'dev'
43
+ Requires-Dist: pytest-cov; extra == 'dev'
44
+ Provides-Extra: url
45
+ Requires-Dist: httpx; extra == 'url'
46
+ Description-Content-Type: text/markdown
47
+
48
+ # nitro-image
49
+
50
+ Fast, friendly image processing for Python web apps and SaaS.
51
+
52
+ ```python
53
+ from nitro_img import Image
54
+
55
+ Image("photo.jpg").resize(800).webp(quality=80).save("photo.webp")
56
+ ```
57
+
58
+ ## Install
59
+
60
+ ```bash
61
+ pip install nitro-image
62
+ ```
63
+
64
+ Optional extras:
65
+
66
+ ```bash
67
+ pip install nitro-image[url] # Load images from URLs (httpx)
68
+ pip install nitro-image[avif] # AVIF format support
69
+ pip install nitro-image[blur] # BlurHash generation
70
+ pip install nitro-image[all] # Everything
71
+ ```
72
+
73
+ ### Claude Code Skill
74
+
75
+ Add NitroImage as a skill in [Claude Code](https://claude.ai/code) for AI-assisted image manipulation:
76
+
77
+ ```bash
78
+ npx skills add nitrosh/nitro-image
79
+ ```
80
+
81
+ ## Why nitro-image?
82
+
83
+ **With Pillow alone:**
84
+
85
+ ```python
86
+ from PIL import Image
87
+
88
+ img = Image.open("photo.jpg")
89
+ img = img.convert("RGB")
90
+ width, height = img.size
91
+ new_height = int(height * (800 / width))
92
+ img = img.resize((800, new_height), Image.LANCZOS)
93
+ img.save("photo.webp", "WEBP", quality=80)
94
+ ```
95
+
96
+ **With nitro-image:**
97
+
98
+ ```python
99
+ from nitro_img import Image
100
+
101
+ Image("photo.jpg").resize(800).webp(quality=80).save("photo.webp")
102
+ ```
103
+
104
+ nitro-image wraps Pillow with a chainable API and lazy execution pipeline. Operations queue up and only run when you call an output method like `.save()` or `.to_bytes()`.
105
+
106
+ ## Features
107
+
108
+ ### Resize and crop
109
+
110
+ ```python
111
+ Image("photo.jpg").resize(800).save("resized.jpg")
112
+ Image("photo.jpg").thumbnail(200).save("thumb.jpg")
113
+ Image("photo.jpg").cover(400, 400).save("square.jpg")
114
+ Image("photo.jpg").contain(400, 400).save("contained.jpg")
115
+ Image("photo.jpg").crop(100, 100, 500, 400).save("cropped.jpg")
116
+ ```
117
+
118
+ ### Format conversion
119
+
120
+ ```python
121
+ Image("photo.jpg").webp(quality=80).save("photo.webp")
122
+ Image("photo.jpg").png().save("photo.png")
123
+ Image("photo.jpg").jpeg(quality=90).save("photo.jpg")
124
+ Image("photo.jpg").auto_format().save("photo.webp") # picks best format
125
+ ```
126
+
127
+ ### Adjustments and effects
128
+
129
+ ```python
130
+ Image("photo.jpg").brightness(1.2).contrast(1.1).save("enhanced.jpg")
131
+ Image("photo.jpg").sharpen(1.5).save("sharp.jpg")
132
+ Image("photo.jpg").blur(2.0).save("blurred.jpg")
133
+ Image("photo.jpg").grayscale().save("gray.jpg")
134
+ Image("photo.jpg").sepia().save("sepia.jpg")
135
+ Image("photo.jpg").rounded_corners(20).png().save("rounded.png")
136
+ ```
137
+
138
+ ### Watermark and text overlay
139
+
140
+ ```python
141
+ Image("photo.jpg").watermark("logo.png", position="bottom-right", opacity=0.5).save("watermarked.jpg")
142
+ Image("photo.jpg").text_overlay("Sample", font_size=48).save("labeled.jpg")
143
+ ```
144
+
145
+ ### Responsive images
146
+
147
+ ```python
148
+ widths = Image("photo.jpg").responsive([400, 800, 1200, 1600])
149
+ # Returns {400: bytes, 800: bytes, 1200: bytes, 1600: bytes}
150
+
151
+ Image("photo.jpg").save_responsive("output/{width}w.webp", [400, 800, 1200])
152
+ # Saves output/400w.webp, output/800w.webp, output/1200w.webp
153
+ ```
154
+
155
+ ### Placeholders
156
+
157
+ ```python
158
+ Image("photo.jpg").lqip() # Low-quality base64 data URI
159
+ Image("photo.jpg").dominant_color() # "#3a6b8c"
160
+ Image("photo.jpg").color_palette(5) # ["#3a6b8c", "#d4a574", ...]
161
+ Image("photo.jpg").svg_placeholder() # SVG with dominant color
162
+ Image("photo.jpg").blurhash() # "LKO2:N%2Tw=w]~RBVZRi..."
163
+ ```
164
+
165
+ ### Optimization
166
+
167
+ ```python
168
+ Image("photo.jpg").optimize(target_kb=200).save("optimized.jpg")
169
+ ```
170
+
171
+ ### Presets
172
+
173
+ ```python
174
+ from nitro_img import Image
175
+
176
+ Image("photo.jpg").preset.thumbnail() # 300px thumbnail
177
+ Image("photo.jpg").preset.avatar() # 128px circle crop
178
+ Image("photo.jpg").preset.og_image() # 1200x630 social card
179
+ Image("photo.jpg").preset.banner() # 1920x400 banner
180
+
181
+ Image.preset.avatar_placeholder("SN") # Initials avatar image
182
+ ```
183
+
184
+ ### Batch processing
185
+
186
+ ```python
187
+ from nitro_img import BatchImage
188
+
189
+ BatchImage("photos/*.jpg").resize(800).webp().save("output/{name}.webp")
190
+ BatchImage("photos/*.jpg").resize(800).jpeg().save("output/{name}.jpg", parallel=True)
191
+ ```
192
+
193
+ ### Web framework responses
194
+
195
+ ```python
196
+ # Django
197
+ return Image("photo.jpg").resize(400).webp().to_django_response()
198
+
199
+ # Flask
200
+ return Image("photo.jpg").resize(400).webp().to_flask_response()
201
+
202
+ # FastAPI
203
+ return Image("photo.jpg").resize(400).webp().to_fastapi_response()
204
+ ```
205
+
206
+ ### Loading from anywhere
207
+
208
+ ```python
209
+ Image("photo.jpg") # File path
210
+ Image.from_bytes(raw_bytes) # Bytes
211
+ Image.from_base64(b64_string) # Base64 string
212
+ Image.from_url("https://example.com/img") # URL (requires httpx)
213
+ Image.from_file(file_object) # File-like object
214
+ ```
215
+
216
+ ### Output options
217
+
218
+ ```python
219
+ img = Image("photo.jpg").resize(400).webp()
220
+
221
+ img.save("output.webp") # Save to file
222
+ img.to_bytes() # Get raw bytes
223
+ img.to_base64() # Base64 encoded string
224
+ img.to_data_uri() # data:image/webp;base64,...
225
+ img.to_response() # {"body": bytes, "content_type": str, "content_length": int}
226
+ ```
227
+
228
+ ## Chain everything
229
+
230
+ All operations are chainable and lazily evaluated:
231
+
232
+ ```python
233
+ (
234
+ Image("photo.jpg")
235
+ .resize(800)
236
+ .brightness(1.1)
237
+ .contrast(1.05)
238
+ .sharpen(1.2)
239
+ .sepia()
240
+ .rounded_corners(10)
241
+ .png(optimize=True)
242
+ .save("final.png")
243
+ )
244
+ ```
245
+
246
+ ## Configuration
247
+
248
+ ```python
249
+ from nitro_img import config
250
+
251
+ config.update(
252
+ jpeg_quality=85,
253
+ webp_quality=80,
254
+ png_optimize=True,
255
+ max_width=4096,
256
+ max_height=4096,
257
+ )
258
+ ```
259
+
260
+ ## Requirements
261
+
262
+ - Python 3.10+
263
+ - Pillow 10.0+
264
+
265
+ ## Ecosystem
266
+
267
+ - **[nitro-ui](https://github.com/nitrosh/nitro-ui)** - Programmatic HTML generation
268
+ - **[nitro-datastore](https://github.com/nitrosh/nitro-datastore)** - Data loading with dot notation access
269
+ - **[nitro-dispatch](https://github.com/nitrosh/nitro-dispatch)** - Plugin system
270
+ - **[nitro-validate](https://github.com/nitrosh/nitro-validate)** - Data validation
271
+ - **[nitro-image](https://github.com/nitrosh/nitro-image)** - Friendly image processing
272
+
273
+ ## License
274
+
275
+ This project is licensed under the BSD 3-Clause License. See the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,228 @@
1
+ # nitro-image
2
+
3
+ Fast, friendly image processing for Python web apps and SaaS.
4
+
5
+ ```python
6
+ from nitro_img import Image
7
+
8
+ Image("photo.jpg").resize(800).webp(quality=80).save("photo.webp")
9
+ ```
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install nitro-image
15
+ ```
16
+
17
+ Optional extras:
18
+
19
+ ```bash
20
+ pip install nitro-image[url] # Load images from URLs (httpx)
21
+ pip install nitro-image[avif] # AVIF format support
22
+ pip install nitro-image[blur] # BlurHash generation
23
+ pip install nitro-image[all] # Everything
24
+ ```
25
+
26
+ ### Claude Code Skill
27
+
28
+ Add NitroImage as a skill in [Claude Code](https://claude.ai/code) for AI-assisted image manipulation:
29
+
30
+ ```bash
31
+ npx skills add nitrosh/nitro-image
32
+ ```
33
+
34
+ ## Why nitro-image?
35
+
36
+ **With Pillow alone:**
37
+
38
+ ```python
39
+ from PIL import Image
40
+
41
+ img = Image.open("photo.jpg")
42
+ img = img.convert("RGB")
43
+ width, height = img.size
44
+ new_height = int(height * (800 / width))
45
+ img = img.resize((800, new_height), Image.LANCZOS)
46
+ img.save("photo.webp", "WEBP", quality=80)
47
+ ```
48
+
49
+ **With nitro-image:**
50
+
51
+ ```python
52
+ from nitro_img import Image
53
+
54
+ Image("photo.jpg").resize(800).webp(quality=80).save("photo.webp")
55
+ ```
56
+
57
+ nitro-image wraps Pillow with a chainable API and lazy execution pipeline. Operations queue up and only run when you call an output method like `.save()` or `.to_bytes()`.
58
+
59
+ ## Features
60
+
61
+ ### Resize and crop
62
+
63
+ ```python
64
+ Image("photo.jpg").resize(800).save("resized.jpg")
65
+ Image("photo.jpg").thumbnail(200).save("thumb.jpg")
66
+ Image("photo.jpg").cover(400, 400).save("square.jpg")
67
+ Image("photo.jpg").contain(400, 400).save("contained.jpg")
68
+ Image("photo.jpg").crop(100, 100, 500, 400).save("cropped.jpg")
69
+ ```
70
+
71
+ ### Format conversion
72
+
73
+ ```python
74
+ Image("photo.jpg").webp(quality=80).save("photo.webp")
75
+ Image("photo.jpg").png().save("photo.png")
76
+ Image("photo.jpg").jpeg(quality=90).save("photo.jpg")
77
+ Image("photo.jpg").auto_format().save("photo.webp") # picks best format
78
+ ```
79
+
80
+ ### Adjustments and effects
81
+
82
+ ```python
83
+ Image("photo.jpg").brightness(1.2).contrast(1.1).save("enhanced.jpg")
84
+ Image("photo.jpg").sharpen(1.5).save("sharp.jpg")
85
+ Image("photo.jpg").blur(2.0).save("blurred.jpg")
86
+ Image("photo.jpg").grayscale().save("gray.jpg")
87
+ Image("photo.jpg").sepia().save("sepia.jpg")
88
+ Image("photo.jpg").rounded_corners(20).png().save("rounded.png")
89
+ ```
90
+
91
+ ### Watermark and text overlay
92
+
93
+ ```python
94
+ Image("photo.jpg").watermark("logo.png", position="bottom-right", opacity=0.5).save("watermarked.jpg")
95
+ Image("photo.jpg").text_overlay("Sample", font_size=48).save("labeled.jpg")
96
+ ```
97
+
98
+ ### Responsive images
99
+
100
+ ```python
101
+ widths = Image("photo.jpg").responsive([400, 800, 1200, 1600])
102
+ # Returns {400: bytes, 800: bytes, 1200: bytes, 1600: bytes}
103
+
104
+ Image("photo.jpg").save_responsive("output/{width}w.webp", [400, 800, 1200])
105
+ # Saves output/400w.webp, output/800w.webp, output/1200w.webp
106
+ ```
107
+
108
+ ### Placeholders
109
+
110
+ ```python
111
+ Image("photo.jpg").lqip() # Low-quality base64 data URI
112
+ Image("photo.jpg").dominant_color() # "#3a6b8c"
113
+ Image("photo.jpg").color_palette(5) # ["#3a6b8c", "#d4a574", ...]
114
+ Image("photo.jpg").svg_placeholder() # SVG with dominant color
115
+ Image("photo.jpg").blurhash() # "LKO2:N%2Tw=w]~RBVZRi..."
116
+ ```
117
+
118
+ ### Optimization
119
+
120
+ ```python
121
+ Image("photo.jpg").optimize(target_kb=200).save("optimized.jpg")
122
+ ```
123
+
124
+ ### Presets
125
+
126
+ ```python
127
+ from nitro_img import Image
128
+
129
+ Image("photo.jpg").preset.thumbnail() # 300px thumbnail
130
+ Image("photo.jpg").preset.avatar() # 128px circle crop
131
+ Image("photo.jpg").preset.og_image() # 1200x630 social card
132
+ Image("photo.jpg").preset.banner() # 1920x400 banner
133
+
134
+ Image.preset.avatar_placeholder("SN") # Initials avatar image
135
+ ```
136
+
137
+ ### Batch processing
138
+
139
+ ```python
140
+ from nitro_img import BatchImage
141
+
142
+ BatchImage("photos/*.jpg").resize(800).webp().save("output/{name}.webp")
143
+ BatchImage("photos/*.jpg").resize(800).jpeg().save("output/{name}.jpg", parallel=True)
144
+ ```
145
+
146
+ ### Web framework responses
147
+
148
+ ```python
149
+ # Django
150
+ return Image("photo.jpg").resize(400).webp().to_django_response()
151
+
152
+ # Flask
153
+ return Image("photo.jpg").resize(400).webp().to_flask_response()
154
+
155
+ # FastAPI
156
+ return Image("photo.jpg").resize(400).webp().to_fastapi_response()
157
+ ```
158
+
159
+ ### Loading from anywhere
160
+
161
+ ```python
162
+ Image("photo.jpg") # File path
163
+ Image.from_bytes(raw_bytes) # Bytes
164
+ Image.from_base64(b64_string) # Base64 string
165
+ Image.from_url("https://example.com/img") # URL (requires httpx)
166
+ Image.from_file(file_object) # File-like object
167
+ ```
168
+
169
+ ### Output options
170
+
171
+ ```python
172
+ img = Image("photo.jpg").resize(400).webp()
173
+
174
+ img.save("output.webp") # Save to file
175
+ img.to_bytes() # Get raw bytes
176
+ img.to_base64() # Base64 encoded string
177
+ img.to_data_uri() # data:image/webp;base64,...
178
+ img.to_response() # {"body": bytes, "content_type": str, "content_length": int}
179
+ ```
180
+
181
+ ## Chain everything
182
+
183
+ All operations are chainable and lazily evaluated:
184
+
185
+ ```python
186
+ (
187
+ Image("photo.jpg")
188
+ .resize(800)
189
+ .brightness(1.1)
190
+ .contrast(1.05)
191
+ .sharpen(1.2)
192
+ .sepia()
193
+ .rounded_corners(10)
194
+ .png(optimize=True)
195
+ .save("final.png")
196
+ )
197
+ ```
198
+
199
+ ## Configuration
200
+
201
+ ```python
202
+ from nitro_img import config
203
+
204
+ config.update(
205
+ jpeg_quality=85,
206
+ webp_quality=80,
207
+ png_optimize=True,
208
+ max_width=4096,
209
+ max_height=4096,
210
+ )
211
+ ```
212
+
213
+ ## Requirements
214
+
215
+ - Python 3.10+
216
+ - Pillow 10.0+
217
+
218
+ ## Ecosystem
219
+
220
+ - **[nitro-ui](https://github.com/nitrosh/nitro-ui)** - Programmatic HTML generation
221
+ - **[nitro-datastore](https://github.com/nitrosh/nitro-datastore)** - Data loading with dot notation access
222
+ - **[nitro-dispatch](https://github.com/nitrosh/nitro-dispatch)** - Plugin system
223
+ - **[nitro-validate](https://github.com/nitrosh/nitro-validate)** - Data validation
224
+ - **[nitro-image](https://github.com/nitrosh/nitro-image)** - Friendly image processing
225
+
226
+ ## License
227
+
228
+ This project is licensed under the BSD 3-Clause License. See the [LICENSE](LICENSE) file for details.