Merge pull request #4996 from nulano/jp2-decode-subsample

This commit is contained in:
Hugo van Kemenade 2021-04-17 21:42:17 +03:00 committed by GitHub
commit 197673b9b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 108 additions and 57 deletions

View File

@ -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

View File

@ -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;