From 69a179af25896ede3699352ab9d9bebb37db8f3e Mon Sep 17 00:00:00 2001 From: Tom Goddard Date: Tue, 13 Jun 2017 18:24:07 -0700 Subject: [PATCH 1/6] Add basic support for TIFF 16-bit signed integer pixels (mode I;16S). Only small additions needed to access the pixel data as numpy arrays were made. Code to convert to other pixel types such as 32-bit integers are not included. --- libImaging/Access.c | 1 + libImaging/Pack.c | 1 + libImaging/Storage.c | 3 ++- libImaging/Unpack.c | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/libImaging/Access.c b/libImaging/Access.c index 292968f1c..05eb10c7c 100644 --- a/libImaging/Access.c +++ b/libImaging/Access.c @@ -227,6 +227,7 @@ ImagingAccessInit() ADD("I;16", line_16, get_pixel_16L, put_pixel_16L); ADD("I;16L", line_16, get_pixel_16L, put_pixel_16L); ADD("I;16B", line_16, get_pixel_16B, put_pixel_16B); + ADD("I;16S", line_16, get_pixel_16L, put_pixel_16L); ADD("I;32L", line_32, get_pixel_32L, put_pixel_32L); ADD("I;32B", line_32, get_pixel_32B, put_pixel_32B); ADD("F", line_32, get_pixel_32, put_pixel_32); diff --git a/libImaging/Pack.c b/libImaging/Pack.c index e6253fca4..f74e18e1b 100644 --- a/libImaging/Pack.c +++ b/libImaging/Pack.c @@ -614,6 +614,7 @@ static struct { {"I;16", "I;16", 16, copy2}, {"I;16B", "I;16B", 16, copy2}, {"I;16L", "I;16L", 16, copy2}, + {"I;16S", "I;16S", 16, copy2}, {"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian. {"I;16L", "I;16N", 16, packI16N_I16}, {"I;16B", "I;16N", 16, packI16N_I16B}, diff --git a/libImaging/Storage.c b/libImaging/Storage.c index 615c5fa20..d20af29a1 100644 --- a/libImaging/Storage.c +++ b/libImaging/Storage.c @@ -116,7 +116,8 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) im->type = IMAGING_TYPE_INT32; } else if (strcmp(mode, "I;16") == 0 || strcmp(mode, "I;16L") == 0 \ - || strcmp(mode, "I;16B") == 0 || strcmp(mode, "I;16N") == 0) { + || strcmp(mode, "I;16B") == 0 || strcmp(mode, "I;16N") == 0 \ + || strcmp(mode, "I;16S") == 0) { /* EXPERIMENTAL */ /* 16-bit raw integer images */ im->bands = 1; diff --git a/libImaging/Unpack.c b/libImaging/Unpack.c index c7b9a606c..183193983 100644 --- a/libImaging/Unpack.c +++ b/libImaging/Unpack.c @@ -1406,6 +1406,7 @@ static struct { {"I;16", "I;16", 16, copy2}, {"I;16B", "I;16B", 16, copy2}, {"I;16L", "I;16L", 16, copy2}, + {"I;16S", "I;16S", 16, copy2}, {"I;16", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian. {"I;16L", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian. From 76cf2cd4c8a3364af9a2ae99b62720d204f5b5de Mon Sep 17 00:00:00 2001 From: Tom Goddard Date: Thu, 15 Jun 2017 13:21:54 -0700 Subject: [PATCH 2/6] Added conversions between I;16S and modes I, L, and unit test for I;16S. --- PIL/ImImagePlugin.py | 1 + PIL/Image.py | 2 +- PIL/ImageMode.py | 1 + Tests/test_mode_i16.py | 6 +++++ libImaging/Convert.c | 58 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 1 deletion(-) diff --git a/PIL/ImImagePlugin.py b/PIL/ImImagePlugin.py index 3371b303f..388015b7f 100644 --- a/PIL/ImImagePlugin.py +++ b/PIL/ImImagePlugin.py @@ -310,6 +310,7 @@ SAVE = { "I;16": ("L 16", "I;16"), "I;16L": ("L 16L", "I;16L"), "I;16B": ("L 16B", "I;16B"), + "I;16S": ("L 16S", "I;16S"), "F": ("L 32F", "F;32F"), "RGB": ("RGB", "RGB;L"), "RGBA": ("RGBA", "RGBA;L"), diff --git a/PIL/Image.py b/PIL/Image.py index e49b1555d..e530d42ac 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -227,7 +227,7 @@ _MODEINFO = { "LAB": ("RGB", "L", ("L", "A", "B")), "HSV": ("RGB", "L", ("H", "S", "V")), - # Experimental modes include I;16, I;16L, I;16B, RGBa, BGR;15, and + # Experimental modes include I;16, I;16L, I;16B, I;16S, RGBa, BGR;15, and # BGR;24. Use these modes only if you know exactly what you're # doing... diff --git a/PIL/ImageMode.py b/PIL/ImageMode.py index b227f2127..3f638d5f5 100644 --- a/PIL/ImageMode.py +++ b/PIL/ImageMode.py @@ -50,6 +50,7 @@ def getmode(mode): modes["I;16"] = ModeDescriptor("I;16", "I", "L", "L") modes["I;16L"] = ModeDescriptor("I;16L", "I", "L", "L") modes["I;16B"] = ModeDescriptor("I;16B", "I", "L", "L") + modes["I;16S"] = ModeDescriptor("I;16S", "I", "L", "L") # set global mode cache atomically _modes = modes return _modes[mode] diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index d51847199..b150126f0 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -79,6 +79,7 @@ class TestModeI16(PillowTestCase): basic("I;16") basic("I;16B") basic("I;16L") + basic("I;16S") basic("I") @@ -92,6 +93,7 @@ class TestModeI16(PillowTestCase): self.assertEqual(tobytes("L"), b"\x01") self.assertEqual(tobytes("I;16"), b"\x01\x00") self.assertEqual(tobytes("I;16B"), b"\x00\x01") + self.assertEqual(tobytes("I;16S"), b"\x01\x00") self.assertEqual(tobytes("I"), b"\x01\x00\x00\x00"[::order]) def test_convert(self): @@ -106,6 +108,10 @@ class TestModeI16(PillowTestCase): self.verify(im.convert("I;16B").convert("L")) self.verify(im.convert("I;16B").convert("I")) + self.verify(im.convert("I;16S")) + self.verify(im.convert("I;16S").convert("L")) + self.verify(im.convert("I;16S").convert("I")) + if __name__ == '__main__': unittest.main() diff --git a/libImaging/Convert.c b/libImaging/Convert.c index b3e48e52b..4ed6abc29 100644 --- a/libImaging/Convert.c +++ b/libImaging/Convert.c @@ -694,6 +694,17 @@ I_I16B(UINT8* out, const UINT8* in_, int xsize) } } +static void +I_I16S(UINT8* out, const UINT8* in_, int xsize) +{ + int x, v; + INT32* in = (INT32*) in_; + for (x = 0; x < xsize; x++, in++) { + v = CLIP16(*in); + *out++ = (UINT8) v; + *out++ = (UINT8) (v >> 8); + } +} static void I16L_I(UINT8* out_, const UINT8* in, int xsize) @@ -714,6 +725,15 @@ I16B_I(UINT8* out_, const UINT8* in, int xsize) *out++ = in[1] + ((int) in[0] << 8); } +static void +I16S_I(UINT8* out_, const UINT8* in, int xsize) +{ + int x; + INT32* out = (INT32*) out_; + for (x = 0; x < xsize; x++, in += 2) + *out++ = (INT16)(in[0] + ((int) in[1] << 8)); +} + static void I16L_F(UINT8* out_, const UINT8* in, int xsize) { @@ -733,6 +753,15 @@ I16B_F(UINT8* out_, const UINT8* in, int xsize) *out++ = (FLOAT32) (in[1] + ((int) in[0] << 8)); } +static void +I16S_F(UINT8* out_, const UINT8* in, int xsize) +{ + int x; + FLOAT32* out = (FLOAT32*) out_; + for (x = 0; x < xsize; x++, in += 2) + *out++ = (FLOAT32) (INT16) (in[0] + ((int) in[1] << 8)); +} + static void L_I16L(UINT8* out, const UINT8* in, int xsize) { @@ -753,6 +782,16 @@ L_I16B(UINT8* out, const UINT8* in, int xsize) } } +static void +L_I16S(UINT8* out, const UINT8* in, int xsize) +{ + int x; + for (x = 0; x < xsize; x++, in++) { + *out++ = *in; + *out++ = 0; + } +} + static void I16L_L(UINT8* out, const UINT8* in, int xsize) { @@ -775,6 +814,19 @@ I16B_L(UINT8* out, const UINT8* in, int xsize) *out++ = in[1]; } +static void +I16S_L(UINT8* out, const UINT8* in, int xsize) +{ + int x; + for (x = 0; x < xsize; x++, in += 2) + if (in[1] & 0x80) + *out++ = 0; /* Negative -> 0 */ + else if (in[1] != 0) + *out++ = 255; /* Greater than 255 -> 255 */ + else + *out++ = in[0]; +} + static struct { const char* from; const char* to; @@ -873,9 +925,15 @@ static struct { { "L", "I;16B", L_I16B }, { "I;16B", "L", I16B_L }, + { "I", "I;16S", I_I16S }, + { "I;16S", "I", I16S_I }, + { "L", "I;16S", L_I16S }, + { "I;16S", "L", I16S_L }, + { "I;16", "F", I16L_F }, { "I;16L", "F", I16L_F }, { "I;16B", "F", I16B_F }, + { "I;16S", "F", I16S_F }, { NULL } }; From d1116cc93913773784c4d3a89324c7241410edb6 Mon Sep 17 00:00:00 2001 From: Tom Goddard Date: Mon, 11 Sep 2017 11:02:41 -0700 Subject: [PATCH 3/6] Fixed indentation to use 4 spaces. --- libImaging/Convert.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libImaging/Convert.c b/libImaging/Convert.c index 4ed6abc29..f7c01a65d 100644 --- a/libImaging/Convert.c +++ b/libImaging/Convert.c @@ -49,7 +49,7 @@ #ifndef round double round(double x) { - return floor(x+0.5); + return floor(x+0.5); } #endif @@ -731,7 +731,7 @@ I16S_I(UINT8* out_, const UINT8* in, int xsize) int x; INT32* out = (INT32*) out_; for (x = 0; x < xsize; x++, in += 2) - *out++ = (INT16)(in[0] + ((int) in[1] << 8)); + *out++ = (INT16)(in[0] + ((int) in[1] << 8)); } static void @@ -820,9 +820,9 @@ I16S_L(UINT8* out, const UINT8* in, int xsize) int x; for (x = 0; x < xsize; x++, in += 2) if (in[1] & 0x80) - *out++ = 0; /* Negative -> 0 */ + *out++ = 0; /* Negative -> 0 */ else if (in[1] != 0) - *out++ = 255; /* Greater than 255 -> 255 */ + *out++ = 255; /* Greater than 255 -> 255 */ else *out++ = in[0]; } From 585b7e42efe5e95cd14ae29b96c2caecaee508d7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 19 Sep 2015 10:12:00 +1000 Subject: [PATCH 4/6] Added test image --- Tests/images/16bit.s.tif | Bin 0 -> 334 bytes Tests/test_file_tiff.py | 5 +++++ 2 files changed, 5 insertions(+) create mode 100644 Tests/images/16bit.s.tif diff --git a/Tests/images/16bit.s.tif b/Tests/images/16bit.s.tif new file mode 100644 index 0000000000000000000000000000000000000000..f36e68d610b3787efaa0778b4f7d07538a7094f6 GIT binary patch literal 334 zcmebD)MB{6z`*do9!Ow-p(o14z`)4NzzFmSkj21=#AbrB1%TqrP&P;%8xmWPkp--m a11K(vBrXmWKLHe%K@ty!vYCM5AOHZ4HWFz7 literal 0 HcmV?d00001 diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 2a9132d48..4c6ae4af3 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -198,6 +198,11 @@ class TestFileTiff(PillowTestCase): self.assertEqual(b[0], b'\x01') self.assertEqual(b[1], b'\xe0') + def test_16bit_s(self): + im = Image.open('Tests/images/16bit.s.tif') + im.load() + self.assertEqual(im.mode, 'I;16S') + def test_12bit_rawmode(self): """ Are we generating the same interpretation of the image as Imagemagick is? """ From efe8479af9cf88743a6fe325ed0c81d9aa9d8d1c Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 19 Sep 2017 10:32:11 +0100 Subject: [PATCH 5/6] Additional tests -- access/pack/unpack --- Tests/test_mode_i16.py | 40 ++++++++++++++++++++++++++++++++++++++++ _imaging.c | 14 +++++++++----- libImaging/Pack.c | 11 +++++++++++ libImaging/Unpack.c | 13 ++++++++++++- 4 files changed, 72 insertions(+), 6 deletions(-) diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index b150126f0..42d5ed83e 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -1,7 +1,12 @@ from helper import unittest, PillowTestCase, hopper from PIL import Image +import struct +HAS_PYACCESS = False +try: + from PIL import PyAccess +except: pass class TestModeI16(PillowTestCase): @@ -113,5 +118,40 @@ class TestModeI16(PillowTestCase): self.verify(im.convert("I;16S").convert("I")) + def _test_i16s(self, pixels, data, rawmode): + ct = len(pixels) + im = Image.frombytes('I;16S', + (ct,1), + data, + 'raw', + rawmode) + + self.assertEqual(im.tobytes('raw', rawmode), data) + + + def _test_access(im, pixels): + access = im.load() + if HAS_PYACCESS: + py_access = PyAccess.new(im, im.readonly) + for ix, val in enumerate(pixels): + self.assertEqual(access[(ix, 0)], val) + if HAS_PYACCESS: + self.assertEqual(py_access[(ix, 0)], val) + + _test_access(im, pixels) + _test_access(im.convert('I'), pixels) # lossless + _test_access(im.convert('F'), pixels) # lossless + _test_access(im.convert('L'), (0,0,0,0,1,128,255,255)) #lossy + + def test_i16s(self): + # LE, base Rawmode: + pixels = (-2**15, -2**7, -1, 0, 1, 128, 255, 2**15-1) + ct = len(pixels) + data = struct.pack("<%dh"%ct, *pixels) + self._test_i16s(pixels, data, 'I;16S') + data = struct.pack(">%dh"%ct, *pixels) + self._test_i16s(pixels, data, 'I;16BS') + + if __name__ == '__main__': unittest.main() diff --git a/_imaging.c b/_imaging.c index d0777c73a..5bedbdeaa 100644 --- a/_imaging.c +++ b/_imaging.c @@ -440,10 +440,11 @@ static inline PyObject* getpixel(Imaging im, ImagingAccess access, int x, int y) { union { - UINT8 b[4]; - UINT16 h; - INT32 i; - FLOAT32 f; + UINT8 b[4]; + UINT16 H; + INT16 h; + INT32 i; + FLOAT32 f; } pixel; if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) { @@ -471,8 +472,11 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) case IMAGING_TYPE_FLOAT32: return PyFloat_FromDouble(pixel.f); case IMAGING_TYPE_SPECIAL: - if (strncmp(im->mode, "I;16", 4) == 0) + if (strncmp(im->mode, "I;16S", 5) == 0) { return PyInt_FromLong(pixel.h); + } else if (strncmp(im->mode, "I;16", 4) == 0) { + return PyInt_FromLong(pixel.H); + } break; } diff --git a/libImaging/Pack.c b/libImaging/Pack.c index f74e18e1b..5b3e883ea 100644 --- a/libImaging/Pack.c +++ b/libImaging/Pack.c @@ -402,6 +402,16 @@ packI16N_I16(UINT8* out, const UINT8* in, int pixels){ } } +static void +packI16S_I16BS(UINT8* out, const UINT8* in, int pixels){ + int i; + UINT8* tmp = (UINT8*) in; + for (i = 0; i < pixels; i++) { + C16S; + out += 2; tmp += 2; + } +} + static void packI32S(UINT8* out, const UINT8* in, int pixels) @@ -615,6 +625,7 @@ static struct { {"I;16B", "I;16B", 16, copy2}, {"I;16L", "I;16L", 16, copy2}, {"I;16S", "I;16S", 16, copy2}, + {"I;16S", "I;16BS", 16, packI16S_I16BS}, {"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian. {"I;16L", "I;16N", 16, packI16N_I16}, {"I;16B", "I;16N", 16, packI16N_I16B}, diff --git a/libImaging/Unpack.c b/libImaging/Unpack.c index 183193983..e1a3e7845 100644 --- a/libImaging/Unpack.c +++ b/libImaging/Unpack.c @@ -966,6 +966,16 @@ unpackI16N_I16(UINT8* out, const UINT8* in, int pixels){ } } +static void +unpackI16BS_I16S(UINT8* out, const UINT8* in, int pixels){ + int i; + UINT8* tmp = (UINT8*) out; + for (i = 0; i < pixels; i++) { + C16S; + in += 2; tmp += 2; + } +} + static void unpackI12_I16(UINT8* out, const UINT8* in, int pixels){ /* Fillorder 1/MSB -> LittleEndian, for 12bit integer greyscale tiffs. @@ -1406,7 +1416,8 @@ static struct { {"I;16", "I;16", 16, copy2}, {"I;16B", "I;16B", 16, copy2}, {"I;16L", "I;16L", 16, copy2}, - {"I;16S", "I;16S", 16, copy2}, + {"I;16S", "I;16S", 16, copy2}, + {"I;16S", "I;16BS", 16, unpackI16BS_I16S}, {"I;16", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian. {"I;16L", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian. From d4f28b5d97b3e404a41a73717715883aecb99175 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Tue, 19 Sep 2017 11:17:25 +0000 Subject: [PATCH 6/6] Added PyAccess methods for I;16S, tests for setvalue --- PIL/PyAccess.py | 45 ++++++++++++++++++++++++++++++++++++++++++ Tests/test_mode_i16.py | 7 +++++++ 2 files changed, 52 insertions(+) diff --git a/PIL/PyAccess.py b/PIL/PyAccess.py index 09ce86af4..f23f86152 100644 --- a/PIL/PyAccess.py +++ b/PIL/PyAccess.py @@ -188,6 +188,21 @@ class _PyAccessI16_N(PyAccess): self.pixels[y][x] = min(color[0], 65535) +class _PyAccessI16_NS(PyAccess): + """ I;16S access, native bitendian without conversion """ + def _post_init(self, *args, **kwargs): + self.pixels = ffi.cast('short **', self.image) + + def get_pixel(self, x, y): + return self.pixels[y][x] + + def set_pixel(self, x, y, color): + try: + self.pixels[y][x] = max(min(color, 2**15-1), -2**15) + except TypeError: + self.pixels[y][x] = max(min(color[0], 2**15-1), -2**15) + + class _PyAccessI16_L(PyAccess): """ I;16L access, with conversion """ def _post_init(self, *args, **kwargs): @@ -208,6 +223,34 @@ class _PyAccessI16_L(PyAccess): pixel.r = color >> 8 +class _PyAccessI16_LS(PyAccess): + """ Littleendian I;16S access, with conversion """ + def _post_init(self, *args, **kwargs): + self.pixels = ffi.cast('struct Pixel_I16 **', self.image) + + def reverse(self, i): + orig = ffi.new('short *', i) + chars = ffi.cast('unsigned char *', orig) + chars[0], chars[1], = chars[1], chars[0] + return ffi.cast('short *', chars)[0] + + def get_pixel(self, x, y): + return self.reverse(self.pixels[y][x]) + + def set_pixel(self, x, y, color): + pixel = self.pixels[y][x] + + try: + color = max(min(color, 2**15-1), -2**15) + except TypeError: + color = max(min(color[0], 2**15-1), -2**15) + + mask = color < 0 and 0x80 or 0x00 + + pixel.l = color & 0xFF + pixel.r = (color >> 8) | mask + + class _PyAccessI16_B(PyAccess): """ I;16B access, with conversion """ def _post_init(self, *args, **kwargs): @@ -298,6 +341,7 @@ if sys.byteorder == 'little': mode_map['I;16'] = _PyAccessI16_N mode_map['I;16L'] = _PyAccessI16_N mode_map['I;16B'] = _PyAccessI16_B + mode_map['I;16S'] = _PyAccessI16_NS mode_map['I;32L'] = _PyAccessI32_N mode_map['I;32B'] = _PyAccessI32_Swap @@ -305,6 +349,7 @@ else: mode_map['I;16'] = _PyAccessI16_L mode_map['I;16L'] = _PyAccessI16_L mode_map['I;16B'] = _PyAccessI16_N + mode_map['I;16S'] = _PyAccessI16_LS mode_map['I;32L'] = _PyAccessI32_Swap mode_map['I;32B'] = _PyAccessI32_N diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index 42d5ed83e..9679be7d3 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -6,6 +6,7 @@ import struct HAS_PYACCESS = False try: from PIL import PyAccess + HAS_PYACCESS = True except: pass class TestModeI16(PillowTestCase): @@ -134,9 +135,15 @@ class TestModeI16(PillowTestCase): if HAS_PYACCESS: py_access = PyAccess.new(im, im.readonly) for ix, val in enumerate(pixels): + self.assertEqual(access[(ix, 0)], val) + access[(ix,0)] = 0 + access[(ix,0)] = val self.assertEqual(access[(ix, 0)], val) if HAS_PYACCESS: self.assertEqual(py_access[(ix, 0)], val) + py_access[(ix,0)] = 0 + py_access[(ix,0)] = val + self.assertEqual(py_access[(ix, 0)], val) _test_access(im, pixels) _test_access(im.convert('I'), pixels) # lossless