Repository: napari/napari

Issue:
Transform mode problem when zoomed in on multiscale layer with `scale=` set
### 🐛 Bug Report

The transform box moves away from the image borders when zooming in on a multiscale image layer that is scaled (`scale=`).

This issue is very similar to https://github.com/napari/napari/issues/6382, which has been fixed by https://github.com/napari/napari/pull/6390.

Screenshot:

<img width="1086" alt="image" src="https://github.com/napari/napari/assets/12528388/fe2845f5-1212-416d-946b-4d016c0f7482">


### 💡 Steps to Reproduce

Same code as in https://github.com/napari/napari/issues/6382, only differences:
- layer is scaled
- zoom in instead of out

```python
import napari
from skimage import data
from skimage.transform import pyramids

im = data.astronaut()[:,:,0].astype(float)

viewer = napari.Viewer()
layers = viewer.add_image([im] + [pyramids.pyramid_reduce(im, downscale=s) for s in [2]], scale=[10, 10])

viewer.layers.selection.active.mode = 'transform'

viewer.camera.zoom = 0.13
```

### 💡 Expected Behavior

That the transform box remains snapped to the image.

### 🌎 Environment

The problem occurs both on 

<details>
  <summary>main</summary>

napari: 0.4.19rc8
Platform: macOS-12.6-arm64-arm-64bit
System: MacOS 12.6
Python: 3.10.13 | packaged by conda-forge | (main, Dec 23 2023, 15:35:25) [Clang 16.0.6 ]
Qt: 5.15.8
PyQt5: 5.15.9
NumPy: 1.26.3
SciPy: 1.12.0
Dask: 2024.1.1
VisPy: 0.14.1
magicgui: 0.8.1
superqt: 0.6.1
in-n-out: 0.1.9
app-model: 0.2.4
npe2: 0.7.4

OpenGL:
  - GL version:  2.1 Metal - 76.3
  - MAX_TEXTURE_SIZE: 16384

Screens:
  - screen 1: resolution 1440x900, scale 2.0
  - screen 2: resolution 1920x1080, scale 2.0
  - screen 3: resolution 1920x1080, scale 2.0

Settings path:
  - /Users/malbert/Library/Application Support/napari/napari-main_7341dd205874e3b46407ee91ab13d237573cf107/settings.yaml
Plugins:
  - napari: 0.4.19rc8 (77 contributions)
  - napari-console: 0.0.9 (0 contributions)
  - napari-svg: 0.1.10 (2 contributions)

</details>

and

<details>
  <summary>0.4.19rc8</summary>

napari: 0.5.0a2.dev531+gdd993831
Platform: macOS-12.6-arm64-arm-64bit
System: MacOS 12.6
Python: 3.10.13 | packaged by conda-forge | (main, Dec 23 2023, 15:35:25) [Clang 16.0.6 ]
Qt: 5.15.8
PyQt5: 5.15.9
NumPy: 1.26.3
SciPy: 1.12.0
Dask: 2024.1.1
VisPy: 0.14.1
magicgui: 0.8.1
superqt: 0.6.1
in-n-out: 0.1.9
app-model: 0.2.4
npe2: 0.7.4

OpenGL:
  - GL version:  2.1 Metal - 76.3
  - MAX_TEXTURE_SIZE: 16384

Screens:
  - screen 1: resolution 1440x900, scale 2.0
  - screen 2: resolution 1920x1080, scale 2.0
  - screen 3: resolution 1920x1080, scale 2.0

Settings path:
  - /Users/malbert/Library/Application Support/napari/napari-main_7341dd205874e3b46407ee91ab13d237573cf107/settings.yaml
Plugins:
  - napari: 0.5.0a2.dev531+gdd993831 (81 contributions)
  - napari-console: 0.0.9 (0 contributions)
  - napari-svg: 0.1.10 (2 contributions)

</details>

### 💡 Additional Context

It seems I'm just trying to break the transform mode in combination with multiscale and transforms 😅. The context is that I found the problem when looking into ways to have users manually edit transforms in [napari-stitcher](https://github.com/multiview-stitcher/napari-stitcher).


Discussion:
argh.
The problem do not happen for scale 1,1  but happens for 10,10 and this is why #6390 does not handle this (as I do not test it with big scale). 

Resolving patch:
diff --git a/napari/_vispy/layers/base.py b/napari/_vispy/layers/base.py
--- a/napari/_vispy/layers/base.py
+++ b/napari/_vispy/layers/base.py
@@ -14,6 +14,7 @@
 )
 from napari.layers import Layer
 from napari.utils.events import disconnect_events
+from napari.utils.transforms import Affine
 
 _L = TypeVar("_L", bound=Layer)
 
@@ -235,14 +236,34 @@ def _on_matrix_change(self):
         # To place this part of data correctly we update transform,
         # but this leads to incorrect placement of child layers.
         # To fix this we need to update child layers transform.
-        child_matrix = np.eye(4)
-        child_matrix[-1, : len(translate)] = (
-            self.layer.translate[self.layer._slice_input.displayed][::-1]
-            + self.layer.affine.translate[self.layer._slice_input.displayed][
-                ::-1
+        dims_displayed = self.layer._slice_input.displayed
+        simplified_transform = self.layer._transforms.simplified
+        if simplified_transform is None:
+            raise ValueError(
+                "simplified transform is None"
+            )  # pragma: no cover
+        translate_child = (
+            self.layer.translate[dims_displayed]
+            + self.layer.affine.translate[dims_displayed]
+        )[::-1]
+        if self.layer.affine.ndim > len(dims_displayed):
+            aff = Affine(
+                linear_matrix=simplified_transform.linear_matrix[
+                    np.ix_(dims_displayed, dims_displayed)
+                ]
+            )
+            trans_rotate = aff.rotate
+            trans_scale = aff.scale[::-1]
+        else:
+            trans_rotate = simplified_transform.rotate[
+                np.ix_(dims_displayed, dims_displayed)
             ]
-            - translate
+            trans_scale = simplified_transform.scale[dims_displayed][::-1]
+        new_translate = (
+            np.dot(trans_rotate, (translate_child - translate)) / trans_scale
         )
+        child_matrix = np.eye(4)
+        child_matrix[-1, : len(translate)] = new_translate
         for child in self.node.children:
             child.transform.matrix = child_matrix
 


Test patch: