diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 3bfb05f02..6d1cb2373 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -253,37 +253,37 @@ class TestColorLut3DFilter(PillowTestCase): ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8) def test_convert_table(self): - flt = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8) - self.assertEqual(tuple(flt.size), (2, 2, 2)) - self.assertEqual(flt.name, "Color 3D LUT") + lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8) + self.assertEqual(tuple(lut.size), (2, 2, 2)) + self.assertEqual(lut.name, "Color 3D LUT") - flt = ImageFilter.Color3DLUT((2, 2, 2), [ + lut = ImageFilter.Color3DLUT((2, 2, 2), [ (0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14), (15, 16, 17), (18, 19, 20), (21, 22, 23)]) - self.assertEqual(tuple(flt.size), (2, 2, 2)) - self.assertEqual(flt.table, list(range(24))) + self.assertEqual(tuple(lut.size), (2, 2, 2)) + self.assertEqual(lut.table, list(range(24))) - flt = ImageFilter.Color3DLUT((2, 2, 2), [(0, 1, 2, 3)] * 8, + lut = ImageFilter.Color3DLUT((2, 2, 2), [(0, 1, 2, 3)] * 8, channels=4) def test_generate(self): - flt = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) - self.assertEqual(tuple(flt.size), (5, 5, 5)) - self.assertEqual(flt.name, "Color 3D LUT") - self.assertEqual(flt.table[:24], [ + lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) + self.assertEqual(tuple(lut.size), (5, 5, 5)) + self.assertEqual(lut.name, "Color 3D LUT") + self.assertEqual(lut.table[:24], [ 0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 0.5, 0.0, 0.0, 0.75, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.25, 0.25, 0.0, 0.5, 0.25, 0.0]) g = Image.linear_gradient('L') im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)]) - self.assertEqual(im, im.filter(flt)) + self.assertEqual(im, im.filter(lut)) - flt = ImageFilter.Color3DLUT.generate(5, channels=4, + lut = ImageFilter.Color3DLUT.generate(5, channels=4, callback=lambda r, g, b: (b, r, g, (r+g+b) / 2)) - self.assertEqual(tuple(flt.size), (5, 5, 5)) - self.assertEqual(flt.name, "Color 3D LUT") - self.assertEqual(flt.table[:24], [ + self.assertEqual(tuple(lut.size), (5, 5, 5)) + self.assertEqual(lut.name, "Color 3D LUT") + self.assertEqual(lut.table[:24], [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.125, 0.0, 0.5, 0.0, 0.25, 0.0, 0.75, 0.0, 0.375, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 0.25, 0.125]) @@ -294,6 +294,47 @@ class TestColorLut3DFilter(PillowTestCase): ImageFilter.Color3DLUT.generate(5, channels=4, callback=lambda r, g, b: (r, g, b)) + def test_from_cube_file_minimal(self): + lut = ImageFilter.Color3DLUT.from_cube_file([ + "LUT_3D_SIZE 2", + "", + "0 0 0.031", + "0.96 0 0.031", + "0 1 0.031", + "0.96 1 0.031", + "0 0 0.931", + "0.96 0 0.931", + "0 1 0.931", + "0.96 1 0.931", + ]) + self.assertEqual(tuple(lut.size), (2, 2, 2)) + self.assertEqual(lut.name, "Color 3D LUT") + self.assertEqual(lut.table[:12], [ + 0, 0, 0.031, 0.96, 0, 0.031, 0, 1, 0.031, 0.96, 1, 0.031]) + + def test_from_cube_file_parser(self): + lut = ImageFilter.Color3DLUT.from_cube_file([ + " # Comment", + 'TITLE "LUT name from file"', + "LUT_3D_SIZE 2 3 4", + " # Comment", + "CHANNELS 4", + "", + ] + [ + " # Comment", + "0 0 0.031 1", + "0.96 0 0.031 1", + "", + "0 1 0.031 1", + "0.96 1 0.031 1", + ] * 6, target_mode='HSV') + self.assertEqual(tuple(lut.size), (2, 3, 4)) + self.assertEqual(lut.channels, 4) + self.assertEqual(lut.name, "LUT name from file") + self.assertEqual(lut.mode, 'HSV') + self.assertEqual(lut.table[:12], [ + 0, 0, 0.031, 1, 0.96, 0, 0.031, 1, 0, 1, 0.031, 1]) + if __name__ == '__main__': unittest.main() diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index e16b47bdd..916c24cae 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -17,6 +17,8 @@ import functools +from ._util import isPath + class Filter(object): pass @@ -343,7 +345,8 @@ class Color3DLUT(MultibandFilter): raise ValueError( "The table should have channels * size**3 float items " "either size**3 items of channels-sized tuples with floats. " - "Table length: {}".format(len(table))) + "Table size: {}x{}x{}. Table length: {}".format( + size[0], size[1], size[2], len(table))) self.table = table @staticmethod @@ -385,6 +388,58 @@ class Color3DLUT(MultibandFilter): return cls((size1D, size2D, size3D), table, channels, target_mode) + @classmethod + def from_cube_file(cls, lines, target_mode=None): + name, size = None, None + channels = 3 + file = None + + if isPath(lines): + file = lines = open(lines, 'rt') + + try: + iterator = iter(lines) + + for i, line in enumerate(iterator, 1): + line = line.strip() + if not line: + break + if line.startswith('TITLE "'): + name = line.split('"')[1] + continue + if line.startswith('LUT_3D_SIZE '): + size = [int(x) for x in line.split()[1:]] + if len(size) == 1: + size = size[0] + continue + if line.startswith('CHANNELS '): + channels = int(line.split()[1]) + + if size is None: + raise ValueError('No size found in the file') + + table = [] + for i, line in enumerate(iterator, i + 1): + line = line.strip() + if not line or line.startswith('#'): + continue + try: + pixel = [float(x) for x in line.split()] + except ValueError: + raise ValueError("Not a number on line {}".format(i)) + if len(pixel) != channels: + raise ValueError( + "Wrong number of colors on line {}".format(i)) + table.append(tuple(pixel)) + finally: + if file is not None: + file.close() + + instance = cls(size, table, channels, target_mode) + if name is not None: + instance.name = name + return instance + def filter(self, image): from . import Image