Repository: napari/napari

Issue:
Incorrect brush drawing rendering in the direct mode
### 🐛 Bug Report

I haven't been following napari for a while, but today I decided to try the latest main branch and it turned out that the brush drawing in the direct mode is broken as it can be seen in the video below:

https://github.com/napari/napari/assets/5459767/f486534a-04ee-492d-9bc4-a753e926b7aa

I haven't dug into it, but I suspect it is related to the big changes in the color mapping code that were introduced in #6439, #3308

### 💡 Steps to Reproduce

1. Add the following colors to `examples/add_labels.py`:
```python
color = {1: 'white', 2: 'blue', 3: 'green', 4: 'red',
         5: 'yellow', 6: 'pink', 7: 'orange', 8: 'blue', None: 'magenta'}
# add the labels
label_layer = viewer.add_labels(label_image, name='segmentation', color=color)
```
2. Launch `examples/add_labels.py`

### 💡 Expected Behavior

_No response_

### 🌎 Environment

napari: 0.1.dev3200+gbb785f3.d20240108
Platform: Linux-5.15.0-91-generic-x86_64-with-glibc2.29
System: Ubuntu 20.04.6 LTS
Python: 3.8.10 (default, Nov 22 2023, 10:22:35)  [GCC 9.4.0]
Qt: 5.15.2
PyQt5: 5.15.10
NumPy: 1.23.0
SciPy: 1.10.0
Dask: 2023.3.2
VisPy: 0.14.1
magicgui: 0.7.2
superqt: 0.6.1
in-n-out: 0.1.7
app-model: 0.2.4
npe2: 0.7.3

OpenGL:
  - GL version:  4.6.0 NVIDIA 525.147.05
  - MAX_TEXTURE_SIZE: 32768

Screens:
  - screen 1: resolution 2560x1440, scale 1.0
  - screen 2: resolution 1920x1080, scale 1.0

Settings path:
  - /home/konstantin/.config/napari/usr_5c0e77c46be77719bc1c0a5ec9c64b94d2a2e50c/settings.yaml
Plugins:
  - napari: 0.1.dev3200+gbb785f3.d20240108 (81 contributions)
  - napari-console: 0.0.9 (0 contributions)
  - napari-svg: 0.1.10 (2 contributions)


### 💡 Additional Context

_No response_


Discussion:

Resolving patch:
diff --git a/examples/add_labels.py b/examples/add_labels.py
--- a/examples/add_labels.py
+++ b/examples/add_labels.py
@@ -7,7 +7,6 @@
 
 .. tags:: layers, visualization-basic
 """
-
 from skimage import data
 from skimage.filters import threshold_otsu
 from skimage.measure import label
@@ -26,7 +25,7 @@
 cleared = remove_small_objects(clear_border(bw), 20)
 
 # label image regions
-label_image = label(cleared)
+label_image = label(cleared).astype("uint8")
 
 # initialise viewer with coins image
 viewer = napari.view_image(image, name='coins', rgb=False)
diff --git a/napari/layers/labels/labels.py b/napari/layers/labels/labels.py
--- a/napari/layers/labels/labels.py
+++ b/napari/layers/labels/labels.py
@@ -992,9 +992,22 @@ def _setup_cache(self, labels):
         if self._cached_labels is not None:
             return
 
+        if isinstance(self._colormap, LabelColormap):
+            mapped_background = _cast_labels_data_to_texture_dtype_auto(
+                labels.dtype.type(self.colormap.background_value),
+                self._random_colormap,
+            )
+        else:  # direct
+            mapped_background = _cast_labels_data_to_texture_dtype_direct(
+                labels.dtype.type(self.colormap.background_value),
+                self._direct_colormap,
+            )
+
         self._cached_labels = np.zeros_like(labels)
-        self._cached_mapped_labels = np.zeros_like(
-            labels, dtype=self._get_cache_dtype(labels.dtype)
+        self._cached_mapped_labels = np.full(
+            shape=labels.shape,
+            fill_value=mapped_background,
+            dtype=self._get_cache_dtype(labels.dtype),
         )
 
     def _raw_to_displayed(


Test patch:
diff --git a/napari/layers/labels/_tests/test_labels.py b/napari/layers/labels/_tests/test_labels.py
--- a/napari/layers/labels/_tests/test_labels.py
+++ b/napari/layers/labels/_tests/test_labels.py
@@ -23,6 +23,7 @@
 from napari.layers.labels._labels_utils import get_contours
 from napari.utils import Colormap
 from napari.utils.colormaps import label_colormap
+from napari.utils.colormaps.colormap import DirectLabelColormap
 
 
 def test_random_labels():
@@ -1685,6 +1686,35 @@ def test_copy():
     assert l1.data is l3.data
 
 
+@pytest.mark.parametrize(
+    "colormap,expected",
+    [
+        (label_colormap(49, 0.5), [0, 1]),
+        (
+            DirectLabelColormap(
+                color_dict={
+                    0: np.array([0, 0, 0, 0]),
+                    1: np.array([1, 0, 0, 1]),
+                    None: np.array([1, 1, 0, 1]),
+                }
+            ),
+            [1, 2],
+        ),
+    ],
+    ids=["auto", "direct"],
+)
+def test_draw(colormap, expected):
+    labels = Labels(np.zeros((30, 30), dtype=np.uint32))
+    labels.mode = "paint"
+    labels.colormap = colormap
+    labels.selected_label = 1
+    npt.assert_array_equal(np.unique(labels._slice.image.raw), [0])
+    npt.assert_array_equal(np.unique(labels._slice.image.view), expected[:1])
+    labels._draw(1, (15, 15), (15, 15))
+    npt.assert_array_equal(np.unique(labels._slice.image.raw), [0, 1])
+    npt.assert_array_equal(np.unique(labels._slice.image.view), expected)
+
+
 class TestLabels:
     @staticmethod
     def get_objects():