Merge pull request #730 from videan42/jpeg2k-16bit-clean

16-bit monochrome support for JPEG2000
This commit is contained in:
wiredfool 2014-06-25 22:26:57 -07:00
commit 77561e0fa7
6 changed files with 141 additions and 10 deletions

View File

@ -40,7 +40,10 @@ def _parse_codestream(fp):
size = (xsiz - xosiz, ysiz - yosiz)
if csiz == 1:
mode = 'L'
if (yrsiz[0] & 0x7f) > 8:
mode = 'I;16'
else:
mode = 'L'
elif csiz == 2:
mode = 'LA'
elif csiz == 3:
@ -78,6 +81,7 @@ def _parse_jp2_header(fp):
size = None
mode = None
bpc = None
hio = io.BytesIO(header)
while True:
@ -95,7 +99,9 @@ def _parse_jp2_header(fp):
= struct.unpack('>IIHBBBB', content)
size = (width, height)
if unkc:
if nc == 1:
if nc == 1 and (bpc & 0x7f) > 8:
mode = 'I;16'
elif nc == 1:
mode = 'L'
elif nc == 2:
mode = 'LA'
@ -109,13 +115,19 @@ def _parse_jp2_header(fp):
if meth == 1:
cs = struct.unpack('>I', content[3:7])[0]
if cs == 16: # sRGB
if nc == 3:
if nc == 1 and (bpc & 0x7f) > 8:
mode = 'I;16'
elif nc == 1:
mode = 'L'
elif nc == 3:
mode = 'RGB'
elif nc == 4:
mode = 'RGBA'
break
elif cs == 17: # grayscale
if nc == 1:
if nc == 1 and (bpc & 0x7f) > 8:
mode = 'I;16'
elif nc == 1:
mode = 'L'
elif nc == 2:
mode = 'LA'
@ -129,10 +141,10 @@ def _parse_jp2_header(fp):
return (size, mode)
##
# Image plugin for JPEG2000 images.
class Jpeg2KImageFile(ImageFile.ImageFile):
format = "JPEG2000"
format_description = "JPEG 2000 (ISO 15444)"
@ -174,7 +186,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
f.seek(pos, 0)
except:
length = -1
self.tile = [('jpeg2k', (0, 0) + self.size, 0,
(self.codec, self.reduce, self.layers, fd, length))]

Binary file not shown.

Binary file not shown.

View File

@ -120,6 +120,42 @@ class TestFileJpeg2k(PillowTestCase):
self.assertEqual(j2k.mode, 'RGBA')
self.assertEqual(jp2.mode, 'RGBA')
def test_16bit_monochrome_has_correct_mode(self):
j2k = Image.open('Tests/images/16bit.cropped.j2k')
jp2 = Image.open('Tests/images/16bit.cropped.jp2')
j2k.load()
jp2.load()
self.assertEqual(j2k.mode, 'I;16')
self.assertEqual(jp2.mode, 'I;16')
def test_16bit_monchrome_jp2_like_tiff(self):
tiff_16bit = Image.open('Tests/images/16bit.cropped.tif')
jp2 = Image.open('Tests/images/16bit.cropped.jp2')
self.assert_image_similar(jp2, tiff_16bit, 1e-3)
def test_16bit_monchrome_j2k_like_tiff(self):
tiff_16bit = Image.open('Tests/images/16bit.cropped.tif')
j2k = Image.open('Tests/images/16bit.cropped.j2k')
self.assert_image_similar(j2k, tiff_16bit, 1e-3)
def test_16bit_j2k_roundtrips(self):
j2k = Image.open('Tests/images/16bit.cropped.j2k')
im = self.roundtrip(j2k)
self.assert_image_equal(im, j2k)
def test_16bit_jp2_roundtrips(self):
jp2 = Image.open('Tests/images/16bit.cropped.jp2')
im = self.roundtrip(jp2)
self.assert_image_equal(im, jp2)
if __name__ == '__main__':
unittest.main()

View File

@ -135,6 +135,56 @@ j2ku_gray_l(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
}
}
static void
j2ku_gray_i(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
const UINT8 *tiledata, Imaging im)
{
unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0;
unsigned w = tileinfo->x1 - tileinfo->x0;
unsigned h = tileinfo->y1 - tileinfo->y0;
int shift = 16 - in->comps[0].prec;
int offset = in->comps[0].sgnd ? 1 << (in->comps[0].prec - 1) : 0;
int csiz = (in->comps[0].prec + 7) >> 3;
unsigned x, y;
if (csiz == 3)
csiz = 4;
if (shift < 0)
offset += 1 << (-shift - 1);
switch (csiz) {
case 1:
for (y = 0; y < h; ++y) {
const UINT8 *data = &tiledata[y * w];
UINT16 *row = (UINT16 *)im->image[y0 + y] + x0;
for (x = 0; x < w; ++x)
*row++ = j2ku_shift(offset + *data++, shift);
}
break;
case 2:
for (y = 0; y < h; ++y) {
const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w];
UINT16 *row = (UINT16 *)im->image[y0 + y] + x0;
for (x = 0; x < w; ++x)
*row++ = j2ku_shift(offset + *data++, shift);
}
break;
case 4:
for (y = 0; y < h; ++y) {
const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w];
UINT16 *row = (UINT16 *)im->image[y0 + y] + x0;
for (x = 0; x < w; ++x)
*row++ = j2ku_shift(offset + *data++, shift);
}
break;
}
}
static void
j2ku_gray_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
const UINT8 *tiledata, Imaging im)
@ -466,6 +516,8 @@ j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo,
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 },

View File

@ -88,6 +88,22 @@ j2k_pack_l(Imaging im, UINT8 *buf,
}
}
static void
j2k_pack_i16(Imaging im, UINT8 *buf,
unsigned x0, unsigned y0, unsigned w, unsigned h)
{
UINT8 *ptr = buf;
unsigned x,y;
for (y = 0; y < h; ++y) {
UINT8 *data = (UINT8 *)(im->image[y + y0] + x0);
for (x = 0; x < w; ++x) {
*ptr++ = *data++;
*ptr++ = *data++;
}
}
}
static void
j2k_pack_la(Imaging im, UINT8 *buf,
unsigned x0, unsigned y0, unsigned w, unsigned h)
@ -247,6 +263,9 @@ j2k_encode_entry(Imaging im, ImagingCodecState state,
j2k_pack_tile_t pack;
int ret = -1;
unsigned prec = 8;
unsigned bpp = 8;
stream = opj_stream_default_create(OPJ_FALSE);
if (!stream) {
@ -271,6 +290,18 @@ j2k_encode_entry(Imaging im, ImagingCodecState state,
components = 1;
color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_l;
} else if (strcmp (im->mode, "I;16") == 0){
components = 1;
color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_i16;
prec = 16;
bpp = 12;
} else if (strcmp (im->mode, "I;16B") == 0){
components = 1;
color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_i16;
prec = 16;
bpp = 12;
} else if (strcmp (im->mode, "LA") == 0) {
components = 2;
color_space = OPJ_CLRSPC_GRAY;
@ -298,8 +329,8 @@ j2k_encode_entry(Imaging im, ImagingCodecState state,
image_params[n].w = im->xsize;
image_params[n].h = im->ysize;
image_params[n].x0 = image_params[n].y0 = 0;
image_params[n].prec = 8;
image_params[n].bpp = 8;
image_params[n].prec = prec;
image_params[n].bpp = bpp;
image_params[n].sgnd = 0;
}
@ -442,7 +473,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state,
num_tiles = tiles_x * tiles_y;
state->buffer = malloc (tile_width * tile_height * components);
state->buffer = malloc (tile_width * tile_height * components * prec / 8);
tile_ndx = 0;
for (y = 0; y < tiles_y; ++y) {
@ -474,7 +505,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state,
pack(im, state->buffer, pixx, pixy, pixw, pixh);
data_size = pixw * pixh * components;
data_size = pixw * pixh * components * prec / 8;
if (!opj_write_tile(codec, tile_ndx++, state->buffer,
data_size, stream)) {