diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py
--- a/src/PIL/IcnsImagePlugin.py
+++ b/src/PIL/IcnsImagePlugin.py
@@ -337,6 +337,10 @@ def _save(im, fp, filename):
 
         # iconutil -c icns -o {} {}
 
+        fp_only = not filename
+        if fp_only:
+            f, filename = tempfile.mkstemp(".icns")
+            os.close(f)
         convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset]
         convert_proc = subprocess.Popen(
             convert_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
@@ -349,6 +353,10 @@ def _save(im, fp, filename):
         if retcode:
             raise subprocess.CalledProcessError(retcode, convert_cmd)
 
+        if fp_only:
+            with open(filename, "rb") as f:
+                fp.write(f.read())
+
 
 Image.register_open(IcnsImageFile.format, IcnsImageFile, lambda x: x[:4] == b"icns")
 Image.register_extension(IcnsImageFile.format, ".icns")

diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py
--- a/Tests/test_file_icns.py
+++ b/Tests/test_file_icns.py
@@ -55,6 +55,19 @@ def test_save_append_images(tmp_path):
             assert_image_equal(reread, provided_im)
 
 
+@pytest.mark.skipif(sys.platform != "darwin", reason="Requires macOS")
+def test_save_fp():
+    fp = io.BytesIO()
+
+    with Image.open(TEST_FILE) as im:
+        im.save(fp, format="ICNS")
+
+    with Image.open(fp) as reread:
+        assert reread.mode == "RGBA"
+        assert reread.size == (1024, 1024)
+        assert reread.format == "ICNS"
+
+
 def test_sizes():
     # Check that we can load all of the sizes, and that the final pixel
     # dimensions are as expected

CalledProcessError saving icns file to io.BytesIO()
Currently, writing an icns file to an io.ByteIO object fails. This operation is supported by other filetypes (jpg, png, ico, etc) so I'm not clear if this report falls under feature request or bug

### What did you do?
Write icns file to io.BytesIO() object
```python
from PIL import Image
import io
import base64

im = Image.new(mode="RGBA", size=(256,256), color=(0,100,246,255))
f = io.BytesIO()
im.save(fp=f, format="icns")

b64 = base64.b64encode(f.getvalue()).decode()
print(f'Image: {b64}')
```

### What did you expect to happen?
Fill the io.BytesIO object 

### What actually happened?
macOS's iconutil failed because it was provided an empty filename for output (-o)
```bash
subprocess.CalledProcessError: Command '['iconutil', '-c', 'icns', '-o', '', '/var/folders/91/q75bkgks1196cr1rsr0399540000gn/T/tmppmsm3z2y.iconset']' returned non-zero exit status 1.
```

I believe one solution would be if `filename` variable is empty then use a dummy name and after popen returns, read the file from disk.

### What are your OS, Python and Pillow versions?

* OS: macOS Mojave 10.14.6
* Python: 3.7.7
* Pillow: 7.1.2

This would be fixed by merging #4526.