Skip to content

Commit ffa84e5

Browse files
committed
Corrected ICC profile test
1 parent 96f0db3 commit ffa84e5

File tree

3 files changed

+49
-79
lines changed

3 files changed

+49
-79
lines changed

Tests/test_file_jxl.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ def test_unsupported(self, monkeypatch: pytest.MonkeyPatch) -> None:
2424

2525
with pytest.raises(OSError):
2626
with pytest.warns(UserWarning, match="JXL support not installed"):
27-
with Image.open("Tests/images/hopper.jxl"):
28-
pass
27+
Image.open("Tests/images/hopper.jxl")
2928

3029

3130
@skip_unless_feature("jpegxl")
@@ -45,7 +44,6 @@ def test_read_rgb(self) -> None:
4544
assert im.mode == "RGB"
4645
assert im.size == (128, 128)
4746
assert im.format == "JPEG XL"
48-
im.load()
4947
im.getdata()
5048

5149
# generated with:
@@ -58,7 +56,6 @@ def test_read_rgba(self) -> None:
5856
assert im.mode == "RGBA"
5957
assert im.size == (200, 150)
6058
assert im.format == "JPEG XL"
61-
im.load()
6259
im.getdata()
6360

6461
im.tobytes()
@@ -74,7 +71,6 @@ def test_read_i16(self) -> None:
7471
assert im.mode == "I;16"
7572
assert im.size == (128, 64)
7673
assert im.format == "JPEG XL"
77-
im.load()
7874
im.getdata()
7975

8076
assert_image_similar_tofile(

Tests/test_file_jxl_metadata.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def test_read_exif_metadata() -> None:
3939
with Image.open("Tests/images/flower.jpg") as im_jpeg:
4040
expected_exif = im_jpeg.info["exif"]
4141

42-
# JPEG XL always returns exif without 'Exif\0\0' prefix
42+
# JPEG XL always returns exif without "Exif\x00\x00" prefix
4343
assert exif_data == expected_exif[6:]
4444

4545

@@ -53,14 +53,8 @@ def test_read_exif_metadata_without_prefix() -> None:
5353

5454

5555
def test_read_icc_profile() -> None:
56-
with Image.open("Tests/images/flower2.jxl") as im:
57-
assert im.format == "JPEG XL"
58-
icc = im.info["icc_profile"]
59-
60-
with Image.open("Tests/images/flower2.jxl") as im_jpeg:
61-
expected_icc = im_jpeg.info["icc_profile"]
62-
63-
assert icc == expected_icc
56+
with Image.open("Tests/images/flower.jxl") as im:
57+
assert "icc_profile" in im.info
6458

6559

6660
def test_getxmp() -> None:

src/_jpegxl.c

Lines changed: 45 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
#include <Python.h>
33
#include "libImaging/Imaging.h"
44

5-
#include <jxl/codestream_header.h>
65
#include <jxl/decode.h>
7-
#include <jxl/types.h>
86
#include <jxl/thread_parallel_runner.h>
97

108
#define _JXL_CHECK(call_name) \
@@ -25,13 +23,9 @@ _jxl_get_pixel_format(JxlPixelFormat *pf, const JxlBasicInfo *bi) {
2523
pf->data_type = JXL_TYPE_UINT8;
2624
}
2725

28-
// this *might* cause some issues on Big-Endian systems
29-
// would be great to test it
30-
pf->endianness = JXL_NATIVE_ENDIAN;
3126
pf->align = 0;
3227
}
3328

34-
// TODO: floating point mode
3529
char *
3630
_jxl_get_mode(const JxlBasicInfo *bi) {
3731
if (bi->num_color_channels == 1 && !bi->alpha_bits) {
@@ -41,19 +35,13 @@ _jxl_get_mode(const JxlBasicInfo *bi) {
4135
}
4236

4337
if (bi->bits_per_sample == 8) {
44-
// image has transparency
4538
if (bi->alpha_bits) {
39+
// image has transparency
4640
if (bi->num_color_channels == 3) {
47-
if (bi->alpha_premultiplied) {
48-
return "RGBa";
49-
}
50-
return "RGBA";
41+
return bi->alpha_premultiplied ? "RGBa" : "RGBA";
5142
}
5243
if (bi->num_color_channels == 1) {
53-
if (bi->alpha_premultiplied) {
54-
return "La";
55-
}
56-
return "LA";
44+
return bi->alpha_premultiplied ? "La" : "LA";
5745
}
5846
} else {
5947
// image has no transparency
@@ -78,8 +66,8 @@ typedef struct {
7866
uint8_t *jxl_data; // input jxl bitstream
7967
Py_ssize_t jxl_data_len; // length of input jxl bitstream
8068

81-
uint8_t *outbuf;
82-
size_t outbuf_len;
69+
uint8_t *output_buffer;
70+
size_t output_buffer_len;
8371

8472
uint8_t *jxl_icc;
8573
size_t jxl_icc_len;
@@ -106,10 +94,10 @@ _jxl_decoder_dealloc(PyObject *self) {
10694
decp->jxl_data = NULL;
10795
decp->jxl_data_len = 0;
10896
}
109-
if (decp->outbuf) {
110-
free(decp->outbuf);
111-
decp->outbuf = NULL;
112-
decp->outbuf_len = 0;
97+
if (decp->output_buffer) {
98+
free(decp->output_buffer);
99+
decp->output_buffer = NULL;
100+
decp->output_buffer_len = 0;
113101
}
114102
if (decp->jxl_icc) {
115103
free(decp->jxl_icc);
@@ -139,7 +127,6 @@ _jxl_decoder_dealloc(PyObject *self) {
139127
}
140128

141129
// sets input jxl bitstream loaded into jxl_data
142-
// has to be called after every rewind
143130
void
144131
_jxl_decoder_set_input(PyObject *self) {
145132
JpegXlDecoderObject *decp = (JpegXlDecoderObject *)self;
@@ -184,28 +171,28 @@ PyObject *
184171
_jxl_decoder_new(PyObject *self, PyObject *args) {
185172
PyBytesObject *jxl_string;
186173

174+
// parse one argument which is a string with jxl data
175+
if (!PyArg_ParseTuple(args, "O", &jxl_string)) {
176+
return NULL;
177+
}
178+
187179
JpegXlDecoderObject *decp = NULL;
188180
decp = PyObject_New(JpegXlDecoderObject, &JpegXlDecoder_Type);
189-
decp->mode = NULL;
190181
decp->jxl_data = NULL;
191182
decp->jxl_data_len = 0;
192-
decp->outbuf = NULL;
193-
decp->outbuf_len = 0;
183+
decp->output_buffer = NULL;
184+
decp->output_buffer_len = 0;
194185
decp->jxl_icc = NULL;
195186
decp->jxl_icc_len = 0;
196187
decp->jxl_exif = NULL;
197188
decp->jxl_exif_len = 0;
198189
decp->jxl_xmp = NULL;
199190
decp->jxl_xmp_len = 0;
191+
decp->mode = NULL;
200192

201193
// used for printing more detailed error messages
202194
char *jxl_call_name;
203195

204-
// parse one argument which is a string with jxl data
205-
if (!PyArg_ParseTuple(args, "S", &jxl_string)) {
206-
return NULL;
207-
}
208-
209196
// this data needs to be copied to JpegXlDecoderObject
210197
// so that input bitstream is preserved across calls
211198
const uint8_t *_tmp_jxl_data;
@@ -216,7 +203,6 @@ _jxl_decoder_new(PyObject *self, PyObject *args) {
216203
(PyObject *)jxl_string, (char **)&_tmp_jxl_data, &_tmp_jxl_data_len
217204
);
218205

219-
// here occurs this copying (inefficiency)
220206
decp->jxl_data = malloc(_tmp_jxl_data_len);
221207
memcpy(decp->jxl_data, _tmp_jxl_data, _tmp_jxl_data_len);
222208
decp->jxl_data_len = _tmp_jxl_data_len;
@@ -250,7 +236,6 @@ _jxl_decoder_new(PyObject *self, PyObject *args) {
250236

251237
decoder_loop_skip_process:
252238

253-
// there was an error at JxlDecoderProcessInput stage
254239
if (decp->status == JXL_DEC_ERROR) {
255240
jxl_call_name = "JxlDecoderProcessInput";
256241
goto end;
@@ -262,11 +247,7 @@ _jxl_decoder_new(PyObject *self, PyObject *args) {
262247

263248
_jxl_get_pixel_format(&decp->pixel_format, &decp->basic_info);
264249
decp->mode = _jxl_get_mode(&decp->basic_info);
265-
266-
continue;
267-
}
268-
269-
if (decp->status == JXL_DEC_COLOR_ENCODING) {
250+
} else if (decp->status == JXL_DEC_COLOR_ENCODING) {
270251
decp->status = JxlDecoderGetICCProfileSize(
271252
decp->decoder, JXL_COLOR_PROFILE_TARGET_DATA, &decp->jxl_icc_len
272253
);
@@ -285,49 +266,45 @@ _jxl_decoder_new(PyObject *self, PyObject *args) {
285266
decp->jxl_icc_len
286267
);
287268
_JXL_CHECK("JxlDecoderGetColorAsICCProfile");
288-
289-
continue;
290-
}
291-
292-
if (decp->status == JXL_DEC_BOX) {
293-
char btype[4];
294-
decp->status = JxlDecoderGetBoxType(decp->decoder, btype, JXL_TRUE);
269+
} else if (decp->status == JXL_DEC_BOX) {
270+
char box_type[4];
271+
decp->status = JxlDecoderGetBoxType(decp->decoder, box_type, JXL_TRUE);
295272
_JXL_CHECK("JxlDecoderGetBoxType");
296273

297-
int is_box_exif = !memcmp(btype, "Exif", 4);
298-
int is_box_xmp = !memcmp(btype, "xml ", 4);
274+
int is_box_exif = !memcmp(box_type, "Exif", 4);
275+
int is_box_xmp = is_box_exif ? 0 : !memcmp(box_type, "xml ", 4);
299276
if (!is_box_exif && !is_box_xmp) {
300277
// not exif/xmp box so continue
301278
continue;
302279
}
303280

304-
uint64_t cur_compr_box_size;
305-
decp->status = JxlDecoderGetBoxSizeRaw(decp->decoder, &cur_compr_box_size);
281+
uint64_t compressed_box_size;
282+
decp->status = JxlDecoderGetBoxSizeRaw(decp->decoder, &compressed_box_size);
306283
_JXL_CHECK("JxlDecoderGetBoxSizeRaw");
307284

308285
uint8_t *final_jxl_buf = NULL;
309286
Py_ssize_t final_jxl_buf_len = 0;
310287

311-
// cur_box_size is actually compressed box size
312-
// it will also serve as our chunk size
313288
do {
314289
uint8_t *_new_jxl_buf =
315-
realloc(final_jxl_buf, final_jxl_buf_len + cur_compr_box_size);
290+
realloc(final_jxl_buf, final_jxl_buf_len + compressed_box_size);
316291
if (!_new_jxl_buf) {
317292
PyErr_SetString(PyExc_OSError, "failed to allocate final_jxl_buf");
318293
goto end;
319294
}
320295
final_jxl_buf = _new_jxl_buf;
321296

322297
decp->status = JxlDecoderSetBoxBuffer(
323-
decp->decoder, final_jxl_buf + final_jxl_buf_len, cur_compr_box_size
298+
decp->decoder,
299+
final_jxl_buf + final_jxl_buf_len,
300+
compressed_box_size
324301
);
325302
_JXL_CHECK("JxlDecoderSetBoxBuffer");
326303

327304
decp->status = JxlDecoderProcessInput(decp->decoder);
328305

329306
size_t remaining = JxlDecoderReleaseBoxBuffer(decp->decoder);
330-
final_jxl_buf_len += (cur_compr_box_size - remaining);
307+
final_jxl_buf_len += compressed_box_size - remaining;
331308
} while (decp->status == JXL_DEC_BOX_NEED_MORE_OUTPUT);
332309

333310
if (is_box_exif) {
@@ -409,8 +386,8 @@ _jxl_decoder_get_next(PyObject *self) {
409386
while (decp->status != JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
410387
decp->status = JxlDecoderProcessInput(decp->decoder);
411388

412-
// this should only occur after rewind
413389
if (decp->status == JXL_DEC_NEED_MORE_INPUT) {
390+
// this should only occur after rewind
414391
_jxl_decoder_set_input((PyObject *)decp);
415392
_JXL_CHECK("JxlDecoderSetInput")
416393
} else if (decp->status == JXL_DEC_FRAME) {
@@ -420,37 +397,40 @@ _jxl_decoder_get_next(PyObject *self) {
420397
}
421398
}
422399

423-
size_t new_outbuf_len;
400+
size_t new_output_buffer_len;
424401
decp->status = JxlDecoderImageOutBufferSize(
425-
decp->decoder, &decp->pixel_format, &new_outbuf_len
402+
decp->decoder, &decp->pixel_format, &new_output_buffer_len
426403
);
427404
_JXL_CHECK("JxlDecoderImageOutBufferSize");
428405

429406
// only allocate memory when current buffer is too small
430-
if (decp->outbuf_len < new_outbuf_len) {
431-
decp->outbuf_len = new_outbuf_len;
432-
uint8_t *_new_outbuf = realloc(decp->outbuf, decp->outbuf_len);
433-
if (!_new_outbuf) {
434-
PyErr_SetString(PyExc_OSError, "failed to allocate outbuf");
407+
if (decp->output_buffer_len < new_output_buffer_len) {
408+
decp->output_buffer_len = new_output_buffer_len;
409+
uint8_t *new_output_buffer =
410+
realloc(decp->output_buffer, decp->output_buffer_len);
411+
if (!new_output_buffer) {
412+
PyErr_SetString(PyExc_OSError, "failed to allocate buffer");
435413
return NULL;
436414
}
437-
decp->outbuf = _new_outbuf;
415+
decp->output_buffer = new_output_buffer;
438416
}
439417

440418
decp->status = JxlDecoderSetImageOutBuffer(
441-
decp->decoder, &decp->pixel_format, decp->outbuf, decp->outbuf_len
419+
decp->decoder, &decp->pixel_format, decp->output_buffer, decp->output_buffer_len
442420
);
443421
_JXL_CHECK("JxlDecoderSetImageOutBuffer");
444422

445-
// decode image into output_buffer
423+
// decode image into output buffer
446424
decp->status = JxlDecoderProcessInput(decp->decoder);
447425

448426
if (decp->status != JXL_DEC_FULL_IMAGE) {
449427
PyErr_SetString(PyExc_OSError, "failed to read next frame");
450428
return NULL;
451429
}
452430

453-
bytes = PyBytes_FromStringAndSize((char *)(decp->outbuf), decp->outbuf_len);
431+
bytes = PyBytes_FromStringAndSize(
432+
(char *)(decp->output_buffer), decp->output_buffer_len
433+
);
454434

455435
ret = Py_BuildValue("SIi", bytes, fhdr.duration, fhdr.is_last);
456436

0 commit comments

Comments
 (0)