Repository: pytorch/vision

Issue:
`F.crop` behavior diverges between tensor and PIL images
```py
import torch
from torchvision.prototype.transforms import functional as F

image_tensor = torch.rand(3, 7, 33)
image_pil = F.to_image_pil(image_tensor)

print(
    F.get_spatial_size(F.crop(image_tensor, top=9, left=9, height=20, width=12)),
    F.get_spatial_size(F.crop(image_pil, top=9, left=9, height=20, width=12)),
)
```

```
[22, 12] [20, 12]
```

cc @vfdev-5 @datumbox @bjuncek


Discussion:
Actually, this is not a prototype issue:

```py
import torch
from torchvision.transforms import functional as F

image_tensor = torch.rand(3, 7, 33)
image_pil = F.to_pil_image(image_tensor)

print(
    F.get_image_size(F.crop(image_tensor, top=9, left=9, height=20, width=12)),
    F.get_image_size(F.crop(image_pil, top=9, left=9, height=20, width=12)),
)
```

```
[12, 22] [12, 20]
```
Seems like a bug in `padding_ltrb`, it may have to be something like : 
```diff
        padding_ltrb = [
-            max(-left, 0), 
-            max(-top, 0), 
+            max(-left + min(right, 0), 0),
+            max(-top + min(bottom, 0), 0), 
-            max(right - w, 0), 
+            max(right - max(w, left), 0), 
-            max(bottom - h, 0), 
+            max(bottom - max(h, top), 0),
        ]        
```

This will also fix the issue with too negative top-left corner and small crop size:
```python
import torch
from torchvision.transforms import functional as F

image_tensor = torch.arange(5 * 5, dtype=torch.float).reshape(1, 5, 5)
image_pil = F.to_pil_image(image_tensor)

o1 = F.crop(image_tensor, top=-10, left=-10, height=3, width=3)
o2 = F.crop(image_pil, top=-10, left=-10, height=3, width=3)
o1.shape, o2.size
> (torch.Size([1, 10, 10]), (3, 3))
```

Resolving patch:
diff --git a/torchvision/transforms/functional_tensor.py b/torchvision/transforms/functional_tensor.py
--- a/torchvision/transforms/functional_tensor.py
+++ b/torchvision/transforms/functional_tensor.py
@@ -137,7 +137,12 @@ def crop(img: Tensor, top: int, left: int, height: int, width: int) -> Tensor:
     bottom = top + height
 
     if left < 0 or top < 0 or right > w or bottom > h:
-        padding_ltrb = [max(-left, 0), max(-top, 0), max(right - w, 0), max(bottom - h, 0)]
+        padding_ltrb = [
+            max(-left + min(0, right), 0),
+            max(-top + min(0, bottom), 0),
+            max(right - max(w, left), 0),
+            max(bottom - max(h, top), 0),
+        ]
         return pad(img[..., max(top, 0) : bottom, max(left, 0) : right], padding_ltrb, fill=0)
     return img[..., top:bottom, left:right]
 


Test patch:
diff --git a/test/test_functional_tensor.py b/test/test_functional_tensor.py
--- a/test/test_functional_tensor.py
+++ b/test/test_functional_tensor.py
@@ -1104,6 +1104,8 @@ def test_hflip(device):
         (2, 12, 3, 4),  # crop inside top-right corner
         (8, 3, 5, 6),  # crop inside bottom-left corner
         (8, 11, 4, 3),  # crop inside bottom-right corner
+        (50, 50, 10, 10),  # crop outside the image
+        (-50, -50, 10, 10),  # crop outside the image
     ],
 )
 def test_crop(device, top, left, height, width):