Skip to content

Commit 9478de4

Browse files
committed
Fix #106 -- Convert JPEG colorspace to RGB
Non-RGB images currently can't be converted to JPEG and Pillow will raise an exception. However, we don't case about changes to the colorspace in smaller variations especially in JPEG, knowing that JPEG doesn't have an alpha channel amongst other features.
1 parent c295bcb commit 9478de4

File tree

7 files changed

+117
-38
lines changed

7 files changed

+117
-38
lines changed

pictures/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
from pictures import conf, utils
2121

22+
RGB_FORMATS = ["JPEG"]
23+
2224

2325
@dataclasses.dataclass
2426
class SimplePicture:
@@ -79,6 +81,8 @@ def process(self, image) -> Image:
7981
def save(self, image):
8082
with io.BytesIO() as file_buffer:
8183
img = self.process(image)
84+
if (self.file_type in RGB_FORMATS) and (img.mode != "RGB"):
85+
img = img.convert("RGB")
8286
img.save(file_buffer, format=self.file_type)
8387
self.storage.delete(self.name) # avoid any filename collisions
8488
self.storage.save(self.name, ContentFile(file_buffer.getvalue()))

tests/conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010

1111
@pytest.fixture
1212
def image_upload_file():
13-
img = Image.new("RGB", (800, 800), (255, 55, 255))
13+
img = Image.new("RGBA", (800, 800), (255, 55, 255, 1))
1414

1515
with io.BytesIO() as output:
16-
img.save(output, format="JPEG")
17-
return SimpleUploadedFile("image.jpg", output.getvalue())
16+
img.save(output, format="PNG")
17+
return SimpleUploadedFile("image.png", output.getvalue())
1818

1919

2020
@pytest.fixture(autouse=True, scope="function")

tests/contrib/test_rest_framework.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def test_to_representation(self, image_upload_file, settings):
4949
serializer = ProfileSerializer(profile)
5050

5151
assert serializer.data["picture"] == {
52-
"url": "/media/testapp/profile/image.jpg",
52+
"url": "/media/testapp/profile/image.png",
5353
"width": 800,
5454
"height": 800,
5555
"ratios": {
@@ -127,7 +127,7 @@ def test_to_representation__with_aspect_ratios(
127127
serializer = ProfileSerializer(profile, context={"request": request})
128128

129129
assert serializer.data["picture"] == {
130-
"url": "/media/testapp/profile/image.jpg",
130+
"url": "/media/testapp/profile/image.png",
131131
"width": 800,
132132
"height": 800,
133133
"ratios": {
@@ -179,7 +179,7 @@ def test_to_representation__with_container(self, rf, image_upload_file, settings
179179
request.GET["picture_container"] = "1200"
180180
serializer = ProfileSerializer(profile, context={"request": request})
181181
assert serializer.data["picture"] == {
182-
"url": "/media/testapp/profile/image.jpg",
182+
"url": "/media/testapp/profile/image.png",
183183
"width": 800,
184184
"height": 800,
185185
"ratios": {
@@ -213,7 +213,7 @@ def test_to_representation__without_container(
213213
request.GET["picture_ratio"] = "16/9"
214214
serializer = ProfileSerializer(profile, context={"request": request})
215215
assert serializer.data["picture"] == {
216-
"url": "/media/testapp/profile/image.jpg",
216+
"url": "/media/testapp/profile/image.png",
217217
"width": 800,
218218
"height": 800,
219219
"ratios": {

tests/test_models.py

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from PIL import Image, ImageDraw
1111

1212
from pictures.models import PictureField, SimplePicture
13-
from tests.testapp.models import Profile, SimpleModel
13+
from tests.testapp.models import JPEGModel, Profile, SimpleModel
1414

1515

1616
@contextlib.contextmanager
@@ -23,15 +23,15 @@ def override_field_aspect_ratios(field, aspect_ratios):
2323

2424
class TestSimplePicture:
2525
picture_with_ratio = SimplePicture(
26-
parent_name="testapp/simplemodel/image.jpg",
26+
parent_name="testapp/simplemodel/image.png",
2727
file_type="WEBP",
2828
aspect_ratio=Fraction("4/3"),
2929
storage=default_storage,
3030
width=800,
3131
)
3232

3333
picture_without_ratio = SimplePicture(
34-
parent_name="testapp/simplemodel/image.jpg",
34+
parent_name="testapp/simplemodel/image.png",
3535
file_type="WEBP",
3636
aspect_ratio=None,
3737
storage=default_storage,
@@ -79,7 +79,7 @@ def test_process__copy(self):
7979
"""Do not mutate input image."""
8080
image = Image.new("RGB", (800, 800), (255, 55, 255))
8181
assert SimplePicture(
82-
parent_name="testapp/simplemodel/image.jpg",
82+
parent_name="testapp/simplemodel/image.png",
8383
file_type="WEBP",
8484
aspect_ratio=None,
8585
storage=default_storage,
@@ -89,7 +89,7 @@ def test_process__copy(self):
8989
assert image.size == (800, 800), "Image was mutated."
9090

9191
assert SimplePicture(
92-
parent_name="testapp/simplemodel/image.jpg",
92+
parent_name="testapp/simplemodel/image.png",
9393
file_type="WEBP",
9494
aspect_ratio="4/3",
9595
storage=default_storage,
@@ -109,6 +109,15 @@ def test_save(self, stub_worker, image_upload_file):
109109
assert default_storage.exists(obj.picture.name)
110110
assert obj.picture.aspect_ratios["16/9"]["WEBP"][100].path.exists()
111111

112+
@pytest.mark.django_db
113+
def test_save_JPEG_RGA(self, stub_worker, image_upload_file):
114+
obj = JPEGModel(picture=image_upload_file)
115+
obj.save()
116+
stub_worker.join()
117+
118+
assert default_storage.exists(obj.picture.name)
119+
assert obj.picture.aspect_ratios["16/9"]["JPEG"][100].path.exists()
120+
112121
@pytest.mark.django_db
113122
def test_exif_transpose(self, stub_worker):
114123
img = Image.new("RGB", (600, 800), (255, 0, 0))
@@ -186,56 +195,56 @@ def test_integration(self, image_upload_file):
186195
None: {
187196
"WEBP": {
188197
800: SimplePicture(
189-
parent_name="testapp/simplemodel/image.jpg",
198+
parent_name="testapp/simplemodel/image.png",
190199
file_type="WEBP",
191200
aspect_ratio=None,
192201
storage=default_storage,
193202
width=800,
194203
),
195204
100: SimplePicture(
196-
parent_name="testapp/simplemodel/image.jpg",
205+
parent_name="testapp/simplemodel/image.png",
197206
file_type="WEBP",
198207
aspect_ratio=None,
199208
storage=default_storage,
200209
width=100,
201210
),
202211
200: SimplePicture(
203-
parent_name="testapp/simplemodel/image.jpg",
212+
parent_name="testapp/simplemodel/image.png",
204213
file_type="WEBP",
205214
aspect_ratio=None,
206215
storage=default_storage,
207216
width=200,
208217
),
209218
300: SimplePicture(
210-
parent_name="testapp/simplemodel/image.jpg",
219+
parent_name="testapp/simplemodel/image.png",
211220
file_type="WEBP",
212221
aspect_ratio=None,
213222
storage=default_storage,
214223
width=300,
215224
),
216225
400: SimplePicture(
217-
parent_name="testapp/simplemodel/image.jpg",
226+
parent_name="testapp/simplemodel/image.png",
218227
file_type="WEBP",
219228
aspect_ratio=None,
220229
storage=default_storage,
221230
width=400,
222231
),
223232
500: SimplePicture(
224-
parent_name="testapp/simplemodel/image.jpg",
233+
parent_name="testapp/simplemodel/image.png",
225234
file_type="WEBP",
226235
aspect_ratio=None,
227236
storage=default_storage,
228237
width=500,
229238
),
230239
600: SimplePicture(
231-
parent_name="testapp/simplemodel/image.jpg",
240+
parent_name="testapp/simplemodel/image.png",
232241
file_type="WEBP",
233242
aspect_ratio=None,
234243
storage=default_storage,
235244
width=600,
236245
),
237246
700: SimplePicture(
238-
parent_name="testapp/simplemodel/image.jpg",
247+
parent_name="testapp/simplemodel/image.png",
239248
file_type="WEBP",
240249
aspect_ratio=None,
241250
storage=default_storage,
@@ -246,56 +255,56 @@ def test_integration(self, image_upload_file):
246255
"3/2": {
247256
"WEBP": {
248257
800: SimplePicture(
249-
parent_name="testapp/simplemodel/image.jpg",
258+
parent_name="testapp/simplemodel/image.png",
250259
file_type="WEBP",
251260
aspect_ratio=Fraction(3, 2),
252261
storage=default_storage,
253262
width=800,
254263
),
255264
100: SimplePicture(
256-
parent_name="testapp/simplemodel/image.jpg",
265+
parent_name="testapp/simplemodel/image.png",
257266
file_type="WEBP",
258267
aspect_ratio=Fraction(3, 2),
259268
storage=default_storage,
260269
width=100,
261270
),
262271
200: SimplePicture(
263-
parent_name="testapp/simplemodel/image.jpg",
272+
parent_name="testapp/simplemodel/image.png",
264273
file_type="WEBP",
265274
aspect_ratio=Fraction(3, 2),
266275
storage=default_storage,
267276
width=200,
268277
),
269278
300: SimplePicture(
270-
parent_name="testapp/simplemodel/image.jpg",
279+
parent_name="testapp/simplemodel/image.png",
271280
file_type="WEBP",
272281
aspect_ratio=Fraction(3, 2),
273282
storage=default_storage,
274283
width=300,
275284
),
276285
400: SimplePicture(
277-
parent_name="testapp/simplemodel/image.jpg",
286+
parent_name="testapp/simplemodel/image.png",
278287
file_type="WEBP",
279288
aspect_ratio=Fraction(3, 2),
280289
storage=default_storage,
281290
width=400,
282291
),
283292
500: SimplePicture(
284-
parent_name="testapp/simplemodel/image.jpg",
293+
parent_name="testapp/simplemodel/image.png",
285294
file_type="WEBP",
286295
aspect_ratio=Fraction(3, 2),
287296
storage=default_storage,
288297
width=500,
289298
),
290299
600: SimplePicture(
291-
parent_name="testapp/simplemodel/image.jpg",
300+
parent_name="testapp/simplemodel/image.png",
292301
file_type="WEBP",
293302
aspect_ratio=Fraction(3, 2),
294303
storage=default_storage,
295304
width=600,
296305
),
297306
700: SimplePicture(
298-
parent_name="testapp/simplemodel/image.jpg",
307+
parent_name="testapp/simplemodel/image.png",
299308
file_type="WEBP",
300309
aspect_ratio=Fraction(3, 2),
301310
storage=default_storage,
@@ -306,56 +315,56 @@ def test_integration(self, image_upload_file):
306315
"16/9": {
307316
"WEBP": {
308317
800: SimplePicture(
309-
parent_name="testapp/simplemodel/image.jpg",
318+
parent_name="testapp/simplemodel/image.png",
310319
file_type="WEBP",
311320
aspect_ratio=Fraction(16, 9),
312321
storage=default_storage,
313322
width=800,
314323
),
315324
100: SimplePicture(
316-
parent_name="testapp/simplemodel/image.jpg",
325+
parent_name="testapp/simplemodel/image.png",
317326
file_type="WEBP",
318327
aspect_ratio=Fraction(16, 9),
319328
storage=default_storage,
320329
width=100,
321330
),
322331
200: SimplePicture(
323-
parent_name="testapp/simplemodel/image.jpg",
332+
parent_name="testapp/simplemodel/image.png",
324333
file_type="WEBP",
325334
aspect_ratio=Fraction(16, 9),
326335
storage=default_storage,
327336
width=200,
328337
),
329338
300: SimplePicture(
330-
parent_name="testapp/simplemodel/image.jpg",
339+
parent_name="testapp/simplemodel/image.png",
331340
file_type="WEBP",
332341
aspect_ratio=Fraction(16, 9),
333342
storage=default_storage,
334343
width=300,
335344
),
336345
400: SimplePicture(
337-
parent_name="testapp/simplemodel/image.jpg",
346+
parent_name="testapp/simplemodel/image.png",
338347
file_type="WEBP",
339348
aspect_ratio=Fraction(16, 9),
340349
storage=default_storage,
341350
width=400,
342351
),
343352
500: SimplePicture(
344-
parent_name="testapp/simplemodel/image.jpg",
353+
parent_name="testapp/simplemodel/image.png",
345354
file_type="WEBP",
346355
aspect_ratio=Fraction(16, 9),
347356
storage=default_storage,
348357
width=500,
349358
),
350359
600: SimplePicture(
351-
parent_name="testapp/simplemodel/image.jpg",
360+
parent_name="testapp/simplemodel/image.png",
352361
file_type="WEBP",
353362
aspect_ratio=Fraction(16, 9),
354363
storage=default_storage,
355364
width=600,
356365
),
357366
700: SimplePicture(
358-
parent_name="testapp/simplemodel/image.jpg",
367+
parent_name="testapp/simplemodel/image.png",
359368
file_type="WEBP",
360369
aspect_ratio=Fraction(16, 9),
361370
storage=default_storage,

tests/test_templatetags.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<source type="image/webp"
99
srcset="/media/testapp/profile/image/800w.webp 800w, /media/testapp/profile/image/100w.webp 100w, /media/testapp/profile/image/200w.webp 200w, /media/testapp/profile/image/300w.webp 300w, /media/testapp/profile/image/400w.webp 400w, /media/testapp/profile/image/500w.webp 500w, /media/testapp/profile/image/600w.webp 600w, /media/testapp/profile/image/700w.webp 700w"
1010
sizes="(min-width: 0px) and (max-width: 991px) 100vw, (min-width: 992px) and (max-width: 1199px) 33vw, 600px">
11-
<img src="/media/testapp/profile/image.jpg" alt="Spiderman" width="800" height="800">
11+
<img src="/media/testapp/profile/image.png" alt="Spiderman" width="800" height="800">
1212
</picture>
1313
"""
1414

@@ -17,7 +17,7 @@
1717
<source type="image/webp"
1818
srcset="/_pictures/Spiderman/3x2/800w.WEBP 800w, /_pictures/Spiderman/3x2/100w.WEBP 100w, /_pictures/Spiderman/3x2/200w.WEBP 200w, /_pictures/Spiderman/3x2/300w.WEBP 300w, /_pictures/Spiderman/3x2/400w.WEBP 400w, /_pictures/Spiderman/3x2/500w.WEBP 500w, /_pictures/Spiderman/3x2/600w.WEBP 600w, /_pictures/Spiderman/3x2/700w.WEBP 700w"
1919
sizes="(min-width: 0px) and (max-width: 991px) 100vw, (min-width: 992px) and (max-width: 1199px) 33vw, 600px">
20-
<img src="/media/testapp/profile/image.jpg" alt="Spiderman" width="800" height="800">
20+
<img src="/media/testapp/profile/image.png" alt="Spiderman" width="800" height="800">
2121
</picture>
2222
"""
2323

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Generated by Django 4.1 on 2023-04-17 15:42
2+
3+
from django.db import migrations, models
4+
5+
import pictures.models
6+
7+
8+
class Migration(migrations.Migration):
9+
dependencies = [
10+
("testapp", "0003_validatormodel"),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name="JPEGModel",
16+
fields=[
17+
(
18+
"id",
19+
models.BigAutoField(
20+
auto_created=True,
21+
primary_key=True,
22+
serialize=False,
23+
verbose_name="ID",
24+
),
25+
),
26+
("picture_width", models.PositiveIntegerField(null=True)),
27+
("picture_height", models.PositiveIntegerField(null=True)),
28+
(
29+
"picture",
30+
pictures.models.PictureField(
31+
aspect_ratios=[None, "3/2", "16/9"],
32+
blank=True,
33+
breakpoints={
34+
"l": 1200,
35+
"m": 992,
36+
"s": 768,
37+
"xl": 1400,
38+
"xs": 576,
39+
},
40+
container_width=1200,
41+
file_types=["WEBP", "JPEG"],
42+
grid_columns=12,
43+
height_field="picture_height",
44+
null=True,
45+
pixel_densities=[1, 2],
46+
upload_to="testapp/simplemodel/",
47+
width_field="picture_width",
48+
),
49+
),
50+
],
51+
),
52+
]

0 commit comments

Comments
 (0)