diff --git a/docs/installation.rst b/docs/installation.rst
index 35b5edf19..0371e517b 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -175,7 +175,7 @@ Many of Pillow's features require external libraries:
   * setting text direction or font features is not supported without
     libraqm.
   * libraqm is dynamically loaded in Pillow 5.0.0 and above, so support
-    is available if all the libraries are installed.
+	is available if all the libraries are installed.
   * Windows support: Raqm support is currently unsupported on Windows.
 
 Once you have installed the prerequisites, run::
diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst
index de26a4d85..6b686568d 100644
--- a/docs/reference/ImageDraw.rst
+++ b/docs/reference/ImageDraw.rst
@@ -25,7 +25,6 @@ Example: Draw a gray cross over an image
     draw = ImageDraw.Draw(im)
     draw.line((0, 0) + im.size, fill=128)
     draw.line((0, im.size[1], im.size[0], 0), fill=128)
-    del draw
 
     # write to stdout
     im.save(sys.stdout, "PNG")
diff --git a/docs/reference/ImageFilter.rst b/docs/reference/ImageFilter.rst
index 3368f799f..5275329ab 100644
--- a/docs/reference/ImageFilter.rst
+++ b/docs/reference/ImageFilter.rst
@@ -37,9 +37,8 @@ image enhancement filters:
 * **SMOOTH**
 * **SMOOTH_MORE**
 
-.. autoclass:: PIL.ImageFilter.Color3DLUT
-.. autoclass:: PIL.ImageFilter.BoxBlur
 .. autoclass:: PIL.ImageFilter.GaussianBlur
+.. autoclass:: PIL.ImageFilter.BoxBlur
 .. autoclass:: PIL.ImageFilter.UnsharpMask
 .. autoclass:: PIL.ImageFilter.Kernel
 .. autoclass:: PIL.ImageFilter.RankFilter
diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py
index ff9348b21..93cd7ad47 100644
--- a/src/PIL/ImageFilter.py
+++ b/src/PIL/ImageFilter.py
@@ -15,8 +15,6 @@
 # See the README file for information on usage and redistribution.
 #
 
-from __future__ import division
-
 import functools
 
 
@@ -325,18 +323,12 @@ class Color3DLUT(MultibandFilter):
     """
     name = "Color 3D LUT"
 
-    def __init__(self, size, table, channels=3, target_mode=None, **kwargs):
-        if channels not in (3, 4):
-            raise ValueError("Only 3 or 4 output channels are supported")
+    def __init__(self, size, table, channels=3, target_mode=None):
         self.size = size = self._check_size(size)
         self.channels = channels
         self.mode = target_mode
 
-        # Hidden flag `_copy_table=False` could be used to avoid extra copying
-        # of the table if the table is specially made for the constructor.
-        if kwargs.get('_copy_table', True):
-            table = list(table)
-
+        table = list(table)
         # Convert to a flat list
         if table and isinstance(table[0], (list, tuple)):
             table, raw_table = [], table
@@ -379,82 +371,20 @@ class Color3DLUT(MultibandFilter):
                          three color channels. Will be called ``size**3``
                          times with values from 0.0 to 1.0 and should return
                          a tuple with ``channels`` elements.
-        :param channels: The number of channels which should return callback.
-        :param target_mode: Passed to the constructor of the resulting
-                            lookup table.
+        :param channels: Passed to the constructor.
+        :param target_mode: Passed to the constructor.
         """
         size1D, size2D, size3D = cls._check_size(size)
-        if channels not in (3, 4):
-            raise ValueError("Only 3 or 4 output channels are supported")
-
-        table = [0] * (size1D * size2D * size3D * channels)
-        idx_out = 0
-        for b in range(size3D):
-            for g in range(size2D):
-                for r in range(size1D):
-                    table[idx_out:idx_out + channels] = callback(
-                        r / (size1D-1), g / (size2D-1), b / (size3D-1))
-                    idx_out += channels
-
-        return cls((size1D, size2D, size3D), table, channels=channels,
-                   target_mode=target_mode, _copy_table=False)
-
-    def transform(self, callback, with_normals=False, channels=None,
-                  target_mode=None):
-        """Transforms the table values using provided callback and returns
-        a new LUT with altered values.
-
-        :param callback: A function which takes old lookup table values
-                         and returns a new set of values. The number
-                         of arguments which function should take is
-                         ``self.channels`` or ``3 + self.channels``
-                         if ``with_normals`` flag is set.
-                         Should return a tuple of ``self.channels`` or
-                         ``channels`` elements if it is set.
-        :param with_normals: If true, ``callback`` will be called with
-                             coordinates in the color cube as the first
-                             three arguments. Otherwise, ``callback``
-                             will be called only with actual color values.
-        :param channels: The number of channels in the resulting lookup table.
-        :param target_mode: Passed to the constructor of the resulting
-                            lookup table.
-        """
-        if channels not in (None, 3, 4):
-            raise ValueError("Only 3 or 4 output channels are supported")
-        ch_in = self.channels
-        ch_out = channels or ch_in
-        size1D, size2D, size3D = self.size
-
-        table = [0] * (size1D * size2D * size3D * ch_out)
-        idx_in = 0
-        idx_out = 0
+        table = []
         for b in range(size3D):
             for g in range(size2D):
                 for r in range(size1D):
-                    values = self.table[idx_in:idx_in + ch_in]
-                    if with_normals:
-                        values = callback(r / (size1D-1), g / (size2D-1),
-                                          b / (size3D-1), *values)
-                    else:
-                        values = callback(*values)
-                    table[idx_out:idx_out + ch_out] = values
-                    idx_in += ch_in
-                    idx_out += ch_out
-
-        return type(self)(self.size, table, channels=ch_out,
-                          target_mode=target_mode or self.mode,
-                          _copy_table=False)
-
-    def __repr__(self):
-        r = [
-            "{} from {}".format(self.__class__.__name__,
-                                  self.table.__class__.__name__),
-            "size={:d}x{:d}x{:d}".format(*self.size),
-            "channels={:d}".format(self.channels),
-        ]
-        if self.mode:
-            r.append("target_mode={}".format(self.mode))
-        return "<{}>".format(" ".join(r))
+                    table.append(callback(
+                        r / float(size1D-1),
+                        g / float(size2D-1),
+                        b / float(size3D-1)))
+
+        return cls((size1D, size2D, size3D), table, channels, target_mode)
 
     def filter(self, image):
         from . import Image
diff --git a/src/_imaging.c b/src/_imaging.c
index 808917ad1..544e54d87 100644
--- a/src/_imaging.c
+++ b/src/_imaging.c
@@ -379,12 +379,12 @@ getlist(PyObject* arg, Py_ssize_t* length, const char* wrong_length, int type)
     PyObject* seq;
     PyObject* op;
 
-    if ( ! PySequence_Check(arg)) {
+    if (!PySequence_Check(arg)) {
         PyErr_SetString(PyExc_TypeError, must_be_sequence);
         return NULL;
     }
 
-    n = PySequence_Size(arg);
+    n = PyObject_Length(arg);
     if (length && wrong_length && n != *length) {
         PyErr_SetString(PyExc_ValueError, wrong_length);
         return NULL;
@@ -393,12 +393,13 @@ getlist(PyObject* arg, Py_ssize_t* length, const char* wrong_length, int type)
     /* malloc check ok, type & ff is just a sizeof(something)
        calloc checks for overflow */
     list = calloc(n, type & 0xff);
-    if ( ! list)
+    if (!list)
         return PyErr_NoMemory();
 
     seq = PySequence_Fast(arg, must_be_sequence);
-    if ( ! seq) {
+    if (!seq) {
         free(list);
+        PyErr_SetString(PyExc_TypeError, must_be_sequence);
         return NULL;
     }
 
@@ -426,16 +427,12 @@ getlist(PyObject* arg, Py_ssize_t* length, const char* wrong_length, int type)
         }
     }
 
-    Py_DECREF(seq);
-
-    if (PyErr_Occurred()) {
-        free(list);
-        return NULL;
-    }
-
     if (length)
         *length = n;
 
+    PyErr_Clear();
+    Py_DECREF(seq);
+
     return list;
 }
 

ImageDraw examples - is "del" really necessary?
Hi! In our project, we have a "canvas" system, based off `PIL.Image` and `PIL.ImageDraw`. [The code](https://github.com/ZeroPhone/ZPUI/blob/devel/ui/canvas.py) is simple - create an `Image`, create an `ImageDraw` object, allow the user to do stuff with it, then allow the user to get the resulting image.

So, a question about `ImageDraw` - [in the first example](http://pillow.readthedocs.io/en/5.0.0/reference/ImageDraw.html#example-draw-a-gray-cross-over-an-image), `del` is used - what's the reasoning behind it? Is the `draw` object not collected by GC eventually (IIRC Python can deal with cyclic references quite well)?
diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py
index 8bf33be92..f9d35c83c 100644
--- a/Tests/test_color_lut.py
+++ b/Tests/test_color_lut.py
@@ -1,13 +1,11 @@
 from __future__ import division
 
-from array import array
-
 from PIL import Image, ImageFilter
 from helper import unittest, PillowTestCase
 
 
 class TestColorLut3DCoreAPI(PillowTestCase):
-    def generate_identity_table(self, channels, size):
+    def generate_unit_table(self, channels, size):
         if isinstance(size, tuple):
             size1D, size2D, size3D = size
         else:
@@ -34,31 +32,31 @@ class TestColorLut3DCoreAPI(PillowTestCase):
 
         with self.assertRaisesRegexp(ValueError, "filter"):
             im.im.color_lut_3d('RGB', Image.CUBIC,
-                *self.generate_identity_table(3, 3))
+                *self.generate_unit_table(3, 3))
 
         with self.assertRaisesRegexp(ValueError, "image mode"):
             im.im.color_lut_3d('wrong', Image.LINEAR,
-                *self.generate_identity_table(3, 3))
+                *self.generate_unit_table(3, 3))
 
         with self.assertRaisesRegexp(ValueError, "table_channels"):
             im.im.color_lut_3d('RGB', Image.LINEAR,
-                *self.generate_identity_table(5, 3))
+                *self.generate_unit_table(5, 3))
 
         with self.assertRaisesRegexp(ValueError, "table_channels"):
             im.im.color_lut_3d('RGB', Image.LINEAR,
-                *self.generate_identity_table(1, 3))
+                *self.generate_unit_table(1, 3))
 
         with self.assertRaisesRegexp(ValueError, "table_channels"):
             im.im.color_lut_3d('RGB', Image.LINEAR,
-                *self.generate_identity_table(2, 3))
+                *self.generate_unit_table(2, 3))
 
         with self.assertRaisesRegexp(ValueError, "Table size"):
             im.im.color_lut_3d('RGB', Image.LINEAR,
-                *self.generate_identity_table(3, (1, 3, 3)))
+                *self.generate_unit_table(3, (1, 3, 3)))
 
         with self.assertRaisesRegexp(ValueError, "Table size"):
             im.im.color_lut_3d('RGB', Image.LINEAR,
-                *self.generate_identity_table(3, (66, 3, 3)))
+                *self.generate_unit_table(3, (66, 3, 3)))
 
         with self.assertRaisesRegexp(ValueError, r"size1D \* size2D \* size3D"):
             im.im.color_lut_3d('RGB', Image.LINEAR,
@@ -68,79 +66,71 @@ class TestColorLut3DCoreAPI(PillowTestCase):
             im.im.color_lut_3d('RGB', Image.LINEAR,
                 3, 2, 2, 2, [0, 0, 0] * 9)
 
-        with self.assertRaises(TypeError):
-            im.im.color_lut_3d('RGB', Image.LINEAR,
-                3, 2, 2, 2, [0, 0, "0"] * 8)
-
-        with self.assertRaises(TypeError):
-            im.im.color_lut_3d('RGB', Image.LINEAR,
-                3, 2, 2, 2, 16)
-
     def test_correct_args(self):
         im = Image.new('RGB', (10, 10), 0)
 
         im.im.color_lut_3d('RGB', Image.LINEAR,
-            *self.generate_identity_table(3, 3))
+            *self.generate_unit_table(3, 3))
 
         im.im.color_lut_3d('CMYK', Image.LINEAR,
-            *self.generate_identity_table(4, 3))
+            *self.generate_unit_table(4, 3))
 
         im.im.color_lut_3d('RGB', Image.LINEAR,
-            *self.generate_identity_table(3, (2, 3, 3)))
+            *self.generate_unit_table(3, (2, 3, 3)))
 
         im.im.color_lut_3d('RGB', Image.LINEAR,
-            *self.generate_identity_table(3, (65, 3, 3)))
+            *self.generate_unit_table(3, (65, 3, 3)))
 
         im.im.color_lut_3d('RGB', Image.LINEAR,
-            *self.generate_identity_table(3, (3, 65, 3)))
+            *self.generate_unit_table(3, (3, 65, 3)))
 
         im.im.color_lut_3d('RGB', Image.LINEAR,
-            *self.generate_identity_table(3, (3, 3, 65)))
+            *self.generate_unit_table(3, (3, 3, 65)))
 
     def test_wrong_mode(self):
         with self.assertRaisesRegexp(ValueError, "wrong mode"):
             im = Image.new('L', (10, 10), 0)
             im.im.color_lut_3d('RGB', Image.LINEAR,
-                *self.generate_identity_table(3, 3))
+                *self.generate_unit_table(3, 3))
 
         with self.assertRaisesRegexp(ValueError, "wrong mode"):
             im = Image.new('RGB', (10, 10), 0)
             im.im.color_lut_3d('L', Image.LINEAR,
-                *self.generate_identity_table(3, 3))
+                *self.generate_unit_table(3, 3))
 
         with self.assertRaisesRegexp(ValueError, "wrong mode"):
             im = Image.new('L', (10, 10), 0)
             im.im.color_lut_3d('L', Image.LINEAR,
-                *self.generate_identity_table(3, 3))
+                *self.generate_unit_table(3, 3))
 
         with self.assertRaisesRegexp(ValueError, "wrong mode"):
             im = Image.new('RGB', (10, 10), 0)
             im.im.color_lut_3d('RGBA', Image.LINEAR,
-                *self.generate_identity_table(3, 3))
+                *self.generate_unit_table(3, 3))
 
         with self.assertRaisesRegexp(ValueError, "wrong mode"):
             im = Image.new('RGB', (10, 10), 0)
             im.im.color_lut_3d('RGB', Image.LINEAR,
-                *self.generate_identity_table(4, 3))
+                *self.generate_unit_table(4, 3))
 
     def test_correct_mode(self):
         im = Image.new('RGBA', (10, 10), 0)
         im.im.color_lut_3d('RGBA', Image.LINEAR,
-            *self.generate_identity_table(3, 3))
+            *self.generate_unit_table(3, 3))
 
         im = Image.new('RGBA', (10, 10), 0)
         im.im.color_lut_3d('RGBA', Image.LINEAR,
-            *self.generate_identity_table(4, 3))
+            *self.generate_unit_table(4, 3))
 
         im = Image.new('RGB', (10, 10), 0)
         im.im.color_lut_3d('HSV', Image.LINEAR,
-            *self.generate_identity_table(3, 3))
+            *self.generate_unit_table(3, 3))
 
         im = Image.new('RGB', (10, 10), 0)
         im.im.color_lut_3d('RGBA', Image.LINEAR,
-            *self.generate_identity_table(4, 3))
+            *self.generate_unit_table(4, 3))
 
-    def test_identities(self):
+    def test_units(self):
         g = Image.linear_gradient('L')
         im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90),
                                  g.transpose(Image.ROTATE_180)])
@@ -149,14 +139,14 @@ class TestColorLut3DCoreAPI(PillowTestCase):
         for size in [2, 3, 5, 7, 11, 16, 17]:
             self.assert_image_equal(im, im._new(
                 im.im.color_lut_3d('RGB', Image.LINEAR,
-                    *self.generate_identity_table(3, size))))
+                    *self.generate_unit_table(3, size))))
 
         # Not so fast
         self.assert_image_equal(im, im._new(
             im.im.color_lut_3d('RGB', Image.LINEAR,
-                *self.generate_identity_table(3, (2, 2, 65)))))
+                *self.generate_unit_table(3, (2, 2, 65)))))
 
-    def test_identities_4_channels(self):
+    def test_units_4channels(self):
         g = Image.linear_gradient('L')
         im = Image.merge('RGB', [g, g.transpose(Image.ROTATE_90),
                                  g.transpose(Image.ROTATE_180)])
@@ -165,7 +155,7 @@ class TestColorLut3DCoreAPI(PillowTestCase):
         self.assert_image_equal(
             Image.merge('RGBA', (im.split()*2)[:4]),
             im._new(im.im.color_lut_3d('RGBA', Image.LINEAR,
-                *self.generate_identity_table(4, 17))))
+                *self.generate_unit_table(4, 17))))
 
     def test_copy_alpha_channel(self):
         g = Image.linear_gradient('L')
@@ -175,7 +165,7 @@ class TestColorLut3DCoreAPI(PillowTestCase):
 
         self.assert_image_equal(im, im._new(
             im.im.color_lut_3d('RGBA', Image.LINEAR,
-                *self.generate_identity_table(3, 17))))
+                *self.generate_unit_table(3, 17))))
 
     def test_channels_order(self):
         g = Image.linear_gradient('L')
@@ -262,9 +252,6 @@ class TestColorLut3DFilter(PillowTestCase):
         with self.assertRaisesRegexp(ValueError, "should have a length of 3"):
             ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8)
 
-        with self.assertRaisesRegexp(ValueError, "Only 3 or 4 output"):
-            ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8, channels=2)
-
     def test_convert_table(self):
         lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8)
         self.assertEqual(tuple(lut.size), (2, 2, 2))
@@ -277,33 +264,9 @@ class TestColorLut3DFilter(PillowTestCase):
         self.assertEqual(lut.table, list(range(24)))
 
         lut = ImageFilter.Color3DLUT((2, 2, 2), [(0, 1, 2, 3)] * 8,
-                                     channels=4)
-
-    def test_repr(self):
-        lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8)
-        self.assertEqual(repr(lut),
-                         "<Color3DLUT from list size=2x2x2 channels=3>")
-
-        lut = ImageFilter.Color3DLUT(
-            (3, 4, 5), array('f', [0, 0, 0, 0] * (3 * 4 * 5)),
-            channels=4, target_mode='YCbCr', _copy_table=False)
-        self.assertEqual(repr(lut),
-            "<Color3DLUT from array size=3x4x5 channels=4 target_mode=YCbCr>")
-
-class TestGenerateColorLut3D(PillowTestCase):
-    def test_wrong_channels_count(self):
-        with self.assertRaisesRegexp(ValueError, "3 or 4 output channels"):
-            ImageFilter.Color3DLUT.generate(5, channels=2,
-                callback=lambda r, g, b: (r, g, b))
-
-        with self.assertRaisesRegexp(ValueError, "should have either channels"):
-            ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b, r))
-
-        with self.assertRaisesRegexp(ValueError, "should have either channels"):
-            ImageFilter.Color3DLUT.generate(5, channels=4,
-                callback=lambda r, g, b: (r, g, b))
+            channels=4)
 
-    def test_3_channels(self):
+    def test_generate(self):
         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")
@@ -311,7 +274,11 @@ class TestGenerateColorLut3D(PillowTestCase):
             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])
 
-    def test_4_channels(self):
+        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(lut))
+
         lut = ImageFilter.Color3DLUT.generate(5, channels=4,
             callback=lambda r, g, b: (b, r, g, (r+g+b) / 2))
         self.assertEqual(tuple(lut.size), (5, 5, 5))
@@ -320,110 +287,12 @@ class TestGenerateColorLut3D(PillowTestCase):
             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])
 
-    def test_apply(self):
-        lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))
-
-        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(lut))
-
+        with self.assertRaisesRegexp(ValueError, "should have a length of 3"):
+            ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b, r))
 
-class TestTransformColorLut3D(PillowTestCase):
-    def test_wrong_args(self):
-        source = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))
-
-        with self.assertRaisesRegexp(ValueError, "Only 3 or 4 output"):
-            source.transform(lambda r, g, b: (r, g, b), channels=8)
-
-        with self.assertRaisesRegexp(ValueError, "should have either channels"):
-            source.transform(lambda r, g, b: (r, g, b), channels=4)
-
-        with self.assertRaisesRegexp(ValueError, "should have either channels"):
-            source.transform(lambda r, g, b: (r, g, b, 1))
-
-        with self.assertRaises(TypeError):
-            source.transform(lambda r, g, b, a: (r, g, b))
-
-    def test_target_mode(self):
-        source = ImageFilter.Color3DLUT.generate(
-            2, lambda r, g, b: (r, g, b), target_mode='HSV')
-
-        lut = source.transform(lambda r, g, b: (r, g, b))
-        self.assertEqual(lut.mode, 'HSV')
-
-        lut = source.transform(lambda r, g, b: (r, g, b), target_mode='RGB')
-        self.assertEqual(lut.mode, 'RGB')
-
-    def test_3_to_3_channels(self):
-        source = ImageFilter.Color3DLUT.generate(
-            (3, 4, 5), lambda r, g, b: (r, g, b))
-        lut = source.transform(lambda r, g, b: (r*r, g*g, b*b))
-        self.assertEqual(tuple(lut.size), tuple(source.size))
-        self.assertEqual(len(lut.table), len(source.table))
-        self.assertNotEqual(lut.table, source.table)
-        self.assertEqual(lut.table[0:10], [
-            0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0])
-
-    def test_3_to_4_channels(self):
-        source = ImageFilter.Color3DLUT.generate(
-            (6, 5, 4), lambda r, g, b: (r, g, b))
-        lut = source.transform(lambda r, g, b: (r*r, g*g, b*b, 1), channels=4)
-        self.assertEqual(tuple(lut.size), tuple(source.size))
-        self.assertNotEqual(len(lut.table), len(source.table))
-        self.assertNotEqual(lut.table, source.table)
-        self.assertEqual(lut.table[0:16], [
-            0.0, 0.0, 0.0, 1,  0.2**2, 0.0, 0.0, 1,
-            0.4**2, 0.0, 0.0, 1,  0.6**2, 0.0, 0.0, 1])
-
-    def test_4_to_3_channels(self):
-        source = ImageFilter.Color3DLUT.generate(
-            (3, 6, 5), lambda r, g, b: (r, g, b, 1), channels=4)
-        lut = source.transform(lambda r, g, b, a: (a - r*r, a - g*g, a - b*b),
-                               channels=3)
-        self.assertEqual(tuple(lut.size), tuple(source.size))
-        self.assertNotEqual(len(lut.table), len(source.table))
-        self.assertNotEqual(lut.table, source.table)
-        self.assertEqual(lut.table[0:18], [
-            1.0, 1.0, 1.0,  0.75, 1.0, 1.0,  0.0, 1.0, 1.0,
-            1.0, 0.96, 1.0,  0.75, 0.96, 1.0,  0.0, 0.96, 1.0])
-
-    def test_4_to_4_channels(self):
-        source = ImageFilter.Color3DLUT.generate(
-            (6, 5, 4), lambda r, g, b: (r, g, b, 1), channels=4)
-        lut = source.transform(lambda r, g, b, a: (r*r, g*g, b*b, a - 0.5))
-        self.assertEqual(tuple(lut.size), tuple(source.size))
-        self.assertEqual(len(lut.table), len(source.table))
-        self.assertNotEqual(lut.table, source.table)
-        self.assertEqual(lut.table[0:16], [
-            0.0, 0.0, 0.0, 0.5,  0.2**2, 0.0, 0.0, 0.5,
-            0.4**2, 0.0, 0.0, 0.5,  0.6**2, 0.0, 0.0, 0.5])
-
-    def test_with_normals_3_channels(self):
-        source = ImageFilter.Color3DLUT.generate(
-            (6, 5, 4), lambda r, g, b: (r*r, g*g, b*b))
-        lut = source.transform(
-            lambda nr, ng, nb, r, g, b: (nr - r, ng - g, nb - b),
-            with_normals=True)
-        self.assertEqual(tuple(lut.size), tuple(source.size))
-        self.assertEqual(len(lut.table), len(source.table))
-        self.assertNotEqual(lut.table, source.table)
-        self.assertEqual(lut.table[0:18], [
-            0.0, 0.0, 0.0,  0.16, 0.0, 0.0,  0.24, 0.0, 0.0,
-            0.24, 0.0, 0.0,  0.8 - (0.8**2), 0, 0,  0, 0, 0])
-
-    def test_with_normals_4_channels(self):
-        source = ImageFilter.Color3DLUT.generate(
-            (3, 6, 5), lambda r, g, b: (r*r, g*g, b*b, 1), channels=4)
-        lut = source.transform(
-            lambda nr, ng, nb, r, g, b, a: (nr - r, ng - g, nb - b, a-0.5),
-            with_normals=True)
-        self.assertEqual(tuple(lut.size), tuple(source.size))
-        self.assertEqual(len(lut.table), len(source.table))
-        self.assertNotEqual(lut.table, source.table)
-        self.assertEqual(lut.table[0:16], [
-            0.0, 0.0, 0.0, 0.5,  0.25, 0.0, 0.0, 0.5,
-            0.0, 0.0, 0.0, 0.5,  0.0, 0.16, 0.0, 0.5])
+        with self.assertRaisesRegexp(ValueError, "should have a length of 4"):
+            ImageFilter.Color3DLUT.generate(5, channels=4,
+                callback=lambda r, g, b: (r, g, b))
 
 
 if __name__ == '__main__':

pip install -e .[dev]
parse_log_pytest
requirements.txt
apt-get update apt-get install -y gcc libjpeg-dev zlib1g-dev libtiff5-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev libharfbuzz-dev libfribidi-dev libxcb1-dev
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
babel==2.17.0
blessed==1.20.0
build==1.2.2.post1
certifi==2025.1.31
charset-normalizer==3.4.1
check-manifest==0.50
cov-core==1.15.0
coverage==7.8.0
coveralls==4.0.1
docopt==0.6.2
docutils==0.21.2
exceptiongroup==1.2.2
idna==3.10
imagesize==1.4.1
importlib_metadata==8.6.1
iniconfig==2.1.0
jarn.viewdoc==2.7
Jinja2==3.1.6
MarkupSafe==3.0.2
olefile==0.47
packaging==24.2
-e git+https://github.com/python-pillow/Pillow.git@b01ba0f50cf48a2abef9cef485e6e3a9dd246b34#egg=Pillow
pluggy==1.5.0
pycodestyle==2.13.0
pyflakes==3.3.1
Pygments==2.19.1
pyproject_hooks==1.2.0
pyroma==4.2
pytest==8.3.5
pytest-cov==6.0.0
pytz==2025.2
requests==2.32.3
six==1.17.0
snowballstemmer==2.2.0
Sphinx==7.4.7
sphinx-rtd-theme==3.0.2
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
tomli==2.2.1
trove-classifiers==2025.3.19.19
urllib3==2.3.0
wcwidth==0.2.13
zipp==3.21.0

name: Pillow
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - babel==2.17.0
      - blessed==1.20.0
      - build==1.2.2.post1
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - check-manifest==0.50
      - cov-core==1.15.0
      - coverage==7.8.0
      - coveralls==4.0.1
      - docopt==0.6.2
      - docutils==0.21.2
      - exceptiongroup==1.2.2
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - jarn-viewdoc==2.7
      - jinja2==3.1.6
      - markupsafe==3.0.2
      - olefile==0.47
      - packaging==24.2
      - pluggy==1.5.0
      - pycodestyle==2.13.0
      - pyflakes==3.3.1
      - pygments==2.19.1
      - pyproject-hooks==1.2.0
      - pyroma==4.2
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - pytz==2025.2
      - requests==2.32.3
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==7.4.7
      - sphinx-rtd-theme==3.0.2
      - sphinxcontrib-applehelp==2.0.0
      - sphinxcontrib-devhelp==2.0.0
      - sphinxcontrib-htmlhelp==2.1.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==2.0.0
      - sphinxcontrib-serializinghtml==2.0.0
      - tomli==2.2.1
      - trove-classifiers==2025.3.19.19
      - urllib3==2.3.0
      - wcwidth==0.2.13
      - zipp==3.21.0
prefix: /opt/conda/envs/Pillow

Tests/test_color_lut.py::TestColorLut3DFilter::test_generate Tests/test_color_lut.py::TestColorLut3DFilter::test_wrong_args
Tests/test_color_lut.py::TestColorLut3DCoreAPI::test_channels_order Tests/test_color_lut.py::TestColorLut3DCoreAPI::test_copy_alpha_channel Tests/test_color_lut.py::TestColorLut3DCoreAPI::test_correct_args Tests/test_color_lut.py::TestColorLut3DCoreAPI::test_correct_mode Tests/test_color_lut.py::TestColorLut3DCoreAPI::test_overflow Tests/test_color_lut.py::TestColorLut3DCoreAPI::test_units Tests/test_color_lut.py::TestColorLut3DCoreAPI::test_units_4channels Tests/test_color_lut.py::TestColorLut3DCoreAPI::test_wrong_args Tests/test_color_lut.py::TestColorLut3DCoreAPI::test_wrong_mode Tests/test_color_lut.py::TestColorLut3DFilter::test_convert_table
docs/installation.rst docs/reference/ImageFilter.rst src/_imaging.c src/PIL/ImageFilter.py docs/reference/ImageDraw.rst
docs/installation.rst docs/reference/ImageFilter.rst src/_imaging.c src/PIL/ImageFilter.py docs/reference/ImageDraw.rst