Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion Tests/test_file_jpeg2k.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import re
from io import BytesIO

Expand All @@ -13,6 +14,8 @@
skip_unless_feature,
)

EXTRA_DIR = "Tests/images/jpeg2000"

pytestmark = skip_unless_feature("jpg_2000")

test_card = Image.open("Tests/images/test-card.png")
Expand Down Expand Up @@ -233,6 +236,26 @@ def test_parser_feed():
assert p.image.size == (640, 480)


@pytest.mark.skipif(
not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
)
@pytest.mark.parametrize("name", ("subsampling_1", "subsampling_2", "zoo1", "zoo2"))
def test_subsampling_decode(name):
test = f"{EXTRA_DIR}/{name}.jp2"
reference = f"{EXTRA_DIR}/{name}.ppm"

with Image.open(test) as im:
epsilon = 3 # for YCbCr images
with Image.open(reference) as im2:
width, height = im2.size
if name[-1] == "2":
# RGB reference images are downscaled
epsilon = 3e-3
width, height = width * 2, height * 2
expected = im2.resize((width, height), Image.NEAREST)
assert_image_similar(im, expected, epsilon)


@pytest.mark.parametrize(
"test_file",
[
Expand All @@ -246,4 +269,7 @@ def test_crashes(test_file):
with open(test_file, "rb") as f:
with Image.open(f) as im:
# Valgrind should not complain here
im.load()
try:
im.load()
except OSError:
pass
137 changes: 81 additions & 56 deletions src/libImaging/Jpeg2KDecode.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ struct j2k_decode_unpacker {
const char *mode;
OPJ_COLOR_SPACE color_space;
unsigned components;
/* bool indicating if unpacker supports subsampling */
int subsampling;
j2k_unpacker_t unpacker;
};

Expand Down Expand Up @@ -350,6 +352,7 @@ j2ku_srgb_rgb(
unsigned h = tileinfo->y1 - tileinfo->y0;

int shifts[3], offsets[3], csiz[3];
unsigned dx[3], dy[3];
const UINT8 *cdata[3];
const UINT8 *cptr = tiledata;
unsigned n, x, y;
Expand All @@ -359,6 +362,8 @@ j2ku_srgb_rgb(
shifts[n] = 8 - in->comps[n].prec;
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
csiz[n] = (in->comps[n].prec + 7) >> 3;
dx[n] = (in->comps[n].dx);
dy[n] = (in->comps[n].dy);

if (csiz[n] == 3) {
csiz[n] = 4;
Expand All @@ -368,14 +373,14 @@ j2ku_srgb_rgb(
offsets[n] += 1 << (-shifts[n] - 1);
}

cptr += csiz[n] * w * h;
cptr += csiz[n] * (w / dx[n]) * (h / dy[n]);
}

for (y = 0; y < h; ++y) {
const UINT8 *data[3];
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
for (n = 0; n < 3; ++n) {
data[n] = &cdata[n][csiz[n] * y * w];
data[n] = &cdata[n][csiz[n] * (y / dy[n]) * (w / dx[n])];
}

for (x = 0; x < w; ++x) {
Expand All @@ -384,15 +389,13 @@ j2ku_srgb_rgb(

switch (csiz[n]) {
case 1:
word = *data[n]++;
word = data[n][x / dx[n]];
break;
case 2:
word = *(const UINT16 *)data[n];
data[n] += 2;
word = ((const UINT16 *)data[n])[x / dx[n]];
break;
case 4:
word = *(const UINT32 *)data[n];
data[n] += 4;
word = ((const UINT32 *)data[n])[x / dx[n]];
break;
}

Expand All @@ -415,6 +418,7 @@ j2ku_sycc_rgb(
unsigned h = tileinfo->y1 - tileinfo->y0;

int shifts[3], offsets[3], csiz[3];
unsigned dx[3], dy[3];
const UINT8 *cdata[3];
const UINT8 *cptr = tiledata;
unsigned n, x, y;
Expand All @@ -424,6 +428,8 @@ j2ku_sycc_rgb(
shifts[n] = 8 - in->comps[n].prec;
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
csiz[n] = (in->comps[n].prec + 7) >> 3;
dx[n] = (in->comps[n].dx);
dy[n] = (in->comps[n].dy);

if (csiz[n] == 3) {
csiz[n] = 4;
Expand All @@ -433,15 +439,15 @@ j2ku_sycc_rgb(
offsets[n] += 1 << (-shifts[n] - 1);
}

cptr += csiz[n] * w * h;
cptr += csiz[n] * (w / dx[n]) * (h / dy[n]);
}

for (y = 0; y < h; ++y) {
const UINT8 *data[3];
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
UINT8 *row_start = row;
for (n = 0; n < 3; ++n) {
data[n] = &cdata[n][csiz[n] * y * w];
data[n] = &cdata[n][csiz[n] * (y / dy[n]) * (w / dx[n])];
}

for (x = 0; x < w; ++x) {
Expand All @@ -450,15 +456,13 @@ j2ku_sycc_rgb(

switch (csiz[n]) {
case 1:
word = *data[n]++;
word = data[n][x / dx[n]];
break;
case 2:
word = *(const UINT16 *)data[n];
data[n] += 2;
word = ((const UINT16 *)data[n])[x / dx[n]];
break;
case 4:
word = *(const UINT32 *)data[n];
data[n] += 4;
word = ((const UINT32 *)data[n])[x / dx[n]];
break;
}

Expand All @@ -483,6 +487,7 @@ j2ku_srgba_rgba(
unsigned h = tileinfo->y1 - tileinfo->y0;

int shifts[4], offsets[4], csiz[4];
unsigned dx[4], dy[4];
const UINT8 *cdata[4];
const UINT8 *cptr = tiledata;
unsigned n, x, y;
Expand All @@ -492,6 +497,8 @@ j2ku_srgba_rgba(
shifts[n] = 8 - in->comps[n].prec;
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
csiz[n] = (in->comps[n].prec + 7) >> 3;
dx[n] = (in->comps[n].dx);
dy[n] = (in->comps[n].dy);

if (csiz[n] == 3) {
csiz[n] = 4;
Expand All @@ -501,14 +508,14 @@ j2ku_srgba_rgba(
offsets[n] += 1 << (-shifts[n] - 1);
}

cptr += csiz[n] * w * h;
cptr += csiz[n] * (w / dx[n]) * (h / dy[n]);
}

for (y = 0; y < h; ++y) {
const UINT8 *data[4];
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
for (n = 0; n < 4; ++n) {
data[n] = &cdata[n][csiz[n] * y * w];
data[n] = &cdata[n][csiz[n] * (y / dy[n]) * (w / dx[n])];
}

for (x = 0; x < w; ++x) {
Expand All @@ -517,15 +524,13 @@ j2ku_srgba_rgba(

switch (csiz[n]) {
case 1:
word = *data[n]++;
word = data[n][x / dx[n]];
break;
case 2:
word = *(const UINT16 *)data[n];
data[n] += 2;
word = ((const UINT16 *)data[n])[x / dx[n]];
break;
case 4:
word = *(const UINT32 *)data[n];
data[n] += 4;
word = ((const UINT32 *)data[n])[x / dx[n]];
break;
}

Expand All @@ -547,6 +552,7 @@ j2ku_sycca_rgba(
unsigned h = tileinfo->y1 - tileinfo->y0;

int shifts[4], offsets[4], csiz[4];
unsigned dx[4], dy[4];
const UINT8 *cdata[4];
const UINT8 *cptr = tiledata;
unsigned n, x, y;
Expand All @@ -556,6 +562,8 @@ j2ku_sycca_rgba(
shifts[n] = 8 - in->comps[n].prec;
offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0;
csiz[n] = (in->comps[n].prec + 7) >> 3;
dx[n] = (in->comps[n].dx);
dy[n] = (in->comps[n].dy);

if (csiz[n] == 3) {
csiz[n] = 4;
Expand All @@ -565,15 +573,15 @@ j2ku_sycca_rgba(
offsets[n] += 1 << (-shifts[n] - 1);
}

cptr += csiz[n] * w * h;
cptr += csiz[n] * (w / dx[n]) * (h / dy[n]);
}

for (y = 0; y < h; ++y) {
const UINT8 *data[4];
UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4;
UINT8 *row_start = row;
for (n = 0; n < 4; ++n) {
data[n] = &cdata[n][csiz[n] * y * w];
data[n] = &cdata[n][csiz[n] * (y / dy[n]) * (w / dx[n])];
}

for (x = 0; x < w; ++x) {
Expand All @@ -582,15 +590,13 @@ j2ku_sycca_rgba(

switch (csiz[n]) {
case 1:
word = *data[n]++;
word = data[n][x / dx[n]];
break;
case 2:
word = *(const UINT16 *)data[n];
data[n] += 2;
word = ((const UINT16 *)data[n])[x / dx[n]];
break;
case 4:
word = *(const UINT32 *)data[n];
data[n] += 4;
word = ((const UINT32 *)data[n])[x / dx[n]];
break;
}

Expand All @@ -604,22 +610,22 @@ j2ku_sycca_rgba(
}

static const struct j2k_decode_unpacker j2k_unpackers[] = {
{"L", OPJ_CLRSPC_GRAY, 1, j2ku_gray_l},
{"I;16", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i},
{"I;16B", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i},
{"LA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la},
{"RGB", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb},
{"RGB", OPJ_CLRSPC_GRAY, 2, j2ku_gray_rgb},
{"RGB", OPJ_CLRSPC_SRGB, 3, j2ku_srgb_rgb},
{"RGB", OPJ_CLRSPC_SYCC, 3, j2ku_sycc_rgb},
{"RGB", OPJ_CLRSPC_SRGB, 4, j2ku_srgb_rgb},
{"RGB", OPJ_CLRSPC_SYCC, 4, j2ku_sycc_rgb},
{"RGBA", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb},
{"RGBA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la},
{"RGBA", OPJ_CLRSPC_SRGB, 3, j2ku_srgb_rgb},
{"RGBA", OPJ_CLRSPC_SYCC, 3, j2ku_sycc_rgb},
{"RGBA", OPJ_CLRSPC_SRGB, 4, j2ku_srgba_rgba},
{"RGBA", OPJ_CLRSPC_SYCC, 4, j2ku_sycca_rgba},
{"L", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_l},
{"I;16", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
{"I;16B", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
{"LA", OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la},
{"RGB", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb},
{"RGB", OPJ_CLRSPC_GRAY, 2, 0, j2ku_gray_rgb},
{"RGB", OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb},
{"RGB", OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb},
{"RGB", OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgb_rgb},
{"RGB", OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycc_rgb},
{"RGBA", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb},
{"RGBA", OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la},
{"RGBA", OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb},
{"RGBA", OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb},
{"RGBA", OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgba_rgba},
{"RGBA", OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycca_rgba},
};

/* -------------------------------------------------------------------- */
Expand All @@ -644,6 +650,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
j2k_unpacker_t unpack = NULL;
size_t buffer_size = 0, tile_bytes = 0;
unsigned n, tile_height, tile_width;
int subsampling;
int total_component_width = 0;

stream = opj_stream_create(BUFFER_SIZE, OPJ_TRUE);
Expand Down Expand Up @@ -706,11 +713,16 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
goto quick_exit;
}

for (n = 1; n < image->numcomps; ++n) {
/*
* Find first component with subsampling.
*
* This is a heuristic to determine the colorspace if unspecified.
*/
subsampling = -1;
for (n = 0; n < image->numcomps; ++n) {
if (image->comps[n].dx != 1 || image->comps[n].dy != 1) {
state->errcode = IMAGING_CODEC_BROKEN;
state->state = J2K_STATE_FAILED;
goto quick_exit;
subsampling = n;
break;
}
}

Expand All @@ -726,12 +738,14 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {

If colorspace is unspecified, we assume:

Number of components Colorspace
-----------------------------------------
1 gray
2 gray (+ alpha)
3 sRGB
4 sRGB (+ alpha)
Number of components Subsampling Colorspace
-------------------------------------------------------
1 Any gray
2 Any gray (+ alpha)
3 -1, 0 sRGB
3 1, 2 YCbCr
4 -1, 0, 3 sRGB (+ alpha)
4 1, 2 YCbCr (+ alpha)

*/

Expand All @@ -746,14 +760,25 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
break;
case 3:
case 4:
color_space = OPJ_CLRSPC_SRGB;
break;
switch (subsampling) {
case -1:
case 0:
case 3:
color_space = OPJ_CLRSPC_SRGB;
break;
case 1:
case 2:
color_space = OPJ_CLRSPC_SYCC;
break;
}
break;
}
}

for (n = 0; n < sizeof(j2k_unpackers) / sizeof(j2k_unpackers[0]); ++n) {
if (color_space == j2k_unpackers[n].color_space &&
image->numcomps == j2k_unpackers[n].components &&
(j2k_unpackers[n].subsampling || (subsampling == -1)) &&
strcmp(im->mode, j2k_unpackers[n].mode) == 0) {
unpack = j2k_unpackers[n].unpacker;
break;
Expand Down