diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 5523d068b..2173d245f 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -1,3 +1,4 @@ +import os import re from io import BytesIO @@ -13,6 +14,8 @@ from .helper import ( skip_unless_feature, ) +EXTRA_DIR = "Tests/images/jpeg2000" + pytestmark = skip_unless_feature("jpg_2000") test_card = Image.open("Tests/images/test-card.png") @@ -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", [ @@ -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 diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c index 6af0f2eae..601bd4b62 100644 --- a/src/libImaging/Jpeg2KDecode.c +++ b/src/libImaging/Jpeg2KDecode.c @@ -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; }; @@ -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; @@ -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; @@ -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) { @@ -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; } @@ -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; @@ -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; @@ -433,7 +439,7 @@ 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) { @@ -441,7 +447,7 @@ j2ku_sycc_rgb( 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) { @@ -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; } @@ -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; @@ -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; @@ -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) { @@ -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; } @@ -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; @@ -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; @@ -565,7 +573,7 @@ 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) { @@ -573,7 +581,7 @@ j2ku_sycca_rgba( 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) { @@ -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; } @@ -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}, }; /* -------------------------------------------------------------------- */ @@ -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); @@ -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; } } @@ -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) */ @@ -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;