Repository: Qiskit/qiskit

Issue:
Feature Request: multi-format file output in QuantumCircuit.draw(..., filename='...')
Qiskit uses PIL for processing image in the Qiskit circuit drawer, which supports several [image file formats](https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html). However, it always saves in `image/png`:
```python
from qiskit import *

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.draw('latex', filename='./file.jpg')

!file file.jpg
```
```
file.jpg: PNG image data, 276 x 90, 8-bit/color RGB, non-interlaced
```

It would be great if the drawer notices the `filename` file extension and saves the image in the correct format.



Discussion:
I'm not sure we want to go down this path. The circuit drawers are already complex enough and have a lot of different paths and capabilities depending on the backend. It's already easy enough to do something like: `circuit.draw('mpl').savefig('test.svg')` or with the latex drawer `circuit.draw('latex').save('circuit.jpg')`. What I'm worried about here the pillow file formats are different from the mpl supported file formats which are different from the text drawer's image formats (ie none). At a certain point a user will just have to take responsibility for doing things themselves and use the libraries themselves to do what they want it's why return the `Image` and `Figure` objects for people. (or they can always just imagemagick convert or another purpose built image format conversion tool).
For PIL, the support is almost for free (`image.save(filename)` takes filename extension, right?). I think, for mpl, it's already supported. 

```
from qiskit import *

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.draw('mpl', filename='./file.jpg')

!file file.jpg
```
```
file.jpg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 150x150, segment length 16, baseline, precision 8, 355x257, components 3
```

For `text`, it makes sense to check filename and warning if extension is not `.txt`. 

Just to be clear. I dont think the formats should be converted if the inner library does not support the format, but a warning/error should be raised.

Resolving patch:
diff --git a/qiskit/visualization/__init__.py b/qiskit/visualization/__init__.py
--- a/qiskit/visualization/__init__.py
+++ b/qiskit/visualization/__init__.py
@@ -126,11 +126,11 @@
 from qiskit.visualization.transition_visualization import visualize_transition
 from qiskit.visualization.array import array_to_latex
 
-from .circuit_visualization import circuit_drawer
+from .circuit_visualization import circuit_drawer, HAS_PIL, HAS_PDFLATEX
 from .dag_visualization import dag_drawer
 from .exceptions import VisualizationError
 from .gate_map import plot_gate_map, plot_circuit_layout, plot_error_map
-from .matplotlib import HAS_MATPLOTLIB
+from .matplotlib import HAS_MATPLOTLIB, HAS_PYLATEX
 from .pass_manager_visualization import pass_manager_drawer
 from .pulse.interpolation import step_wise, linear, cubic_spline
 from .pulse.qcstyle import PulseStyle, SchedStyle
diff --git a/qiskit/visualization/circuit_visualization.py b/qiskit/visualization/circuit_visualization.py
--- a/qiskit/visualization/circuit_visualization.py
+++ b/qiskit/visualization/circuit_visualization.py
@@ -46,6 +46,11 @@
 from qiskit.visualization import utils
 from qiskit.visualization import matplotlib as _matplotlib
 
+try:
+    subprocess.run("pdflatex -v", check=True)
+except OSError as ex:
+    HAS_PDFLATEX = False
+
 logger = logging.getLogger(__name__)
 
 
@@ -307,6 +312,9 @@ def _text_circuit_drawer(
 
     Returns:
         TextDrawing: An instance that, when printed, draws the circuit in ascii art.
+
+    Raises:
+        VisualizationError: When the filename extenstion is not .txt.
     """
     qubits, clbits, nodes = utils._get_layered_instructions(
         circuit, reverse_bits=reverse_bits, justify=justify, idle_wires=idle_wires
@@ -335,6 +343,8 @@ def _text_circuit_drawer(
     text_drawing.vertical_compression = vertical_compression
 
     if filename:
+        if not filename.endswith(".txt"):
+            raise VisualizationError("ERROR: filename parameter does not use .txt extension.")
         text_drawing.dump(filename, encoding=encoding)
     return text_drawing
 
@@ -388,6 +398,7 @@ def _latex_circuit_drawer(
                  missing.
         CalledProcessError: usually points to errors during diagram creation.
         MissingOptionalLibraryError: if pillow is not installed
+        VisualizationError: If unsupported image format is given as filename extension.
     """
     tmpfilename = "circuit"
     with tempfile.TemporaryDirectory() as tmpdirname:
@@ -454,7 +465,12 @@ def _latex_circuit_drawer(
                     if filename.endswith(".pdf"):
                         os.rename(base + ".pdf", filename)
                     else:
-                        image.save(filename, "PNG")
+                        try:
+                            image.save(filename)
+                        except VisualizationError as ve:
+                            raise VisualizationError(
+                                "ERROR: filename parameter does not use a supported extension."
+                            ) from ve
             except (OSError, subprocess.CalledProcessError) as ex:
                 logger.warning(
                     "WARNING: Unable to convert pdf to image. "


Test patch:
diff --git a/test/python/visualization/test_circuit_drawer.py b/test/python/visualization/test_circuit_drawer.py
--- a/test/python/visualization/test_circuit_drawer.py
+++ b/test/python/visualization/test_circuit_drawer.py
@@ -13,12 +13,15 @@
 # pylint: disable=missing-docstring
 
 import unittest
+import os
 from unittest.mock import patch
+from PIL import Image
 
 from qiskit import QuantumCircuit
 from qiskit.test import QiskitTestCase
 from qiskit import visualization
 from qiskit.visualization import text
+from qiskit.visualization.exceptions import VisualizationError
 
 if visualization.HAS_MATPLOTLIB:
     from matplotlib import figure
@@ -73,3 +76,33 @@ def test_default_backend_auto_output_without_mpl(self):
                 circuit = QuantumCircuit()
                 out = visualization.circuit_drawer(circuit)
                 self.assertIsInstance(out, text.TextDrawing)
+
+    @unittest.skipUnless(
+        visualization.HAS_PYLATEX and visualization.HAS_PIL and visualization.HAS_PDFLATEX,
+        "Skipped because PIL and pylatex is not available",
+    )
+    def test_unsupported_image_format_error_message(self):
+        with patch("qiskit.user_config.get_config", return_value={"circuit_drawer": "latex"}):
+            circuit = QuantumCircuit()
+            with self.assertRaises(VisualizationError) as ve:
+                visualization.circuit_drawer(circuit, filename="file.spooky")
+                self.assertEqual(
+                    str(ve.exception),
+                    "ERROR: filename parameter does not use a supported extension.",
+                )
+
+    @unittest.skipUnless(
+        visualization.HAS_PYLATEX and visualization.HAS_PIL and visualization.HAS_PDFLATEX,
+        "Skipped because PIL and pylatex is not available",
+    )
+    def test_output_file_correct_format(self):
+        with patch("qiskit.user_config.get_config", return_value={"circuit_drawer": "latex"}):
+            circuit = QuantumCircuit()
+            filename = "file.gif"
+            visualization.circuit_drawer(circuit, filename=filename)
+            with Image.open(filename) as im:
+                if filename.endswith("jpg"):
+                    self.assertIn(im.format.lower(), "jpeg")
+                else:
+                    self.assertIn(im.format.lower(), filename.split(".")[-1])
+            os.remove(filename)
diff --git a/test/python/visualization/test_circuit_text_drawer.py b/test/python/visualization/test_circuit_text_drawer.py
--- a/test/python/visualization/test_circuit_text_drawer.py
+++ b/test/python/visualization/test_circuit_text_drawer.py
@@ -45,6 +45,7 @@
     CPhaseGate,
 )
 from qiskit.transpiler.passes import ApplyLayout
+from qiskit.visualization.exceptions import VisualizationError
 from .visualization import path_to_diagram_reference, QiskitVisualizationTestCase
 
 
@@ -4761,6 +4762,16 @@ def test_text_drawer_cp437(self):
         self.assertFilesAreEqual(filename, self.text_reference_cp437, "cp437")
         os.remove(filename)
 
+    def test_filename_extension_error_message(self):
+        """Test that the error message shown for wrong file extension is correct."""
+        circuit = self.sample_circuit()
+        with self.assertRaises(VisualizationError) as ve:
+            _text_circuit_drawer(circuit, filename="file.spooky")
+            self.assertEqual(
+                str(ve.exception),
+                "ERROR: filename parameter does not use .txt extension.",
+            )
+
 
 if __name__ == "__main__":
     unittest.main()