ttobsen: I'v just added the PNM/PPM support and tested it localy with the following script:

```python
from imageio import imwrite, imread
import numpy as np
from binaryornot.check import is_binary

def is_ascii(file):
    is_bin = is_binary(file)
    return not is_bin

if __name__ == '__main__':

    tests = [
        {"data": np.full((200, 300, 3), 200, np.uint8),  "format": "PPM-FI",    "use_ascii": True},
        {"data": np.full((200, 300),    200, np.uint8),  "format": "PPM-FI",    "use_ascii": True},
        {"data": np.full((200, 300, 3), 200, np.uint8),  "format": "PPM-FI",    "use_ascii": False},
        {"data": np.full((200, 300),    200, np.uint8),  "format": "PPM-FI",    "use_ascii": False},
        {"data": np.full((200, 300, 3), 200, np.uint8),  "format": "PPMRAW-FI", "use_ascii": True},
        {"data": np.full((200, 300),    200, np.uint8),  "format": "PPMRAW-FI", "use_ascii": True},
        {"data": np.full((200, 300, 3), 200, np.uint8),  "format": "PPMRAW-FI", "use_ascii": False},
        {"data": np.full((200, 300),    200, np.uint8),  "format": "PPMRAW-FI", "use_ascii": False},
        {"data": np.full((200, 300, 3), 200, np.uint16), "format": "PPM-FI",    "use_ascii": True},
        {"data": np.full((200, 300),    200, np.uint16), "format": "PPM-FI",    "use_ascii": True},
        {"data": np.full((200, 300, 3), 200, np.uint16), "format": "PPM-FI",    "use_ascii": False},
        {"data": np.full((200, 300),    200, np.uint16), "format": "PPM-FI",    "use_ascii": False},
        {"data": np.full((200, 300, 3), 200, np.uint16), "format": "PPMRAW-FI", "use_ascii": True},
        {"data": np.full((200, 300),    200, np.uint16), "format": "PPMRAW-FI", "use_ascii": True},
        {"data": np.full((200, 300, 3), 200, np.uint16), "format": "PPMRAW-FI", "use_ascii": False},
        {"data": np.full((200, 300),    200, np.uint16), "format": "PPMRAW-FI", "use_ascii": False},
    ]

    for key, test in enumerate(tests):
        image_data = test["data"]
        filename = str(key) + ".ppm"
        fmt = test["format"]
        use_ascii = test["use_ascii"]

        imwrite(filename, image_data, fmt, use_ascii=use_ascii)
        assert is_ascii(filename) == use_ascii, "ASCII Flag missmatch!"
        read_data = imread(filename, fmt)

        np.testing.assert_array_equal(read_data, image_data, "Missmatch!")

        print("Test " + str(key) + " done.")
```

I'm not sure how to implement these tests properly. It seems that the special classes testing is done in `test_freeimage.py`. Are there any other places where tests have to be implemented?

As a starting point, I would use the Bitmap testing in `test_bmp()` as reference.
coveralls: 
[![Coverage Status](https://coveralls.io/builds/23441459/badge)](https://coveralls.io/builds/23441459)

Coverage decreased (-0.06%) to 90.96% when pulling **71a66d289e491dd21d809b51d1aca89100c4a790 on ttobsen:master** into **88b2a43cea81139e1a76460c68fa9aadebadfc1e on imageio:master**.

almarklein: >  Are there any other places where tests have to be implemented?

No, adding one test function to `test_freeimage.py` should be fine
ttobsen: > > Are there any other places where tests have to be implemented?
> 
> No, adding one test function to `test_freeimage.py` should be fine

Ok, I think I'm done. Can you have a look on it?
almarklein: This is great. No comments. Thanks for the contribution!
diff --git a/.gitignore b/.gitignore
index ec7c106..35d4389 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,3 +33,6 @@ __pychache__
 
 # IDE
 .idea
+.project
+.pydevproject
+.settings
diff --git a/imageio/plugins/freeimage.py b/imageio/plugins/freeimage.py
index 7f93cb6..ca7db87 100644
--- a/imageio/plugins/freeimage.py
+++ b/imageio/plugins/freeimage.py
@@ -26,7 +26,7 @@ class FreeimageFormat(Format):
     The freeimage plugin requires a `freeimage` binary. If this binary
     not available on the system, it can be downloaded manually from
     <https://github.com/imageio/imageio-binaries> by either
-    
+
     - the command line script ``imageio_download_bin freeimage``
     - the Python method ``imageio.plugins.freeimage.download()``
 
@@ -35,7 +35,7 @@ class FreeimageFormat(Format):
     flags : int
         A freeimage-specific option. In most cases we provide explicit
         parameters for influencing image reading.
-    
+
     Parameters for saving
     ----------------------
     flags : int
@@ -145,13 +145,13 @@ class FreeimageFormat(Format):
 
 class FreeimageBmpFormat(FreeimageFormat):
     """ A BMP format based on the Freeimage library.
-    
+
     This format supports grayscale, RGB and RGBA images.
 
     The freeimage plugin requires a `freeimage` binary. If this binary
     not available on the system, it can be downloaded manually from
     <https://github.com/imageio/imageio-binaries> by either
-    
+
     - the command line script ``imageio_download_bin freeimage``
     - the Python method ``imageio.plugins.freeimage.download()``
 
@@ -161,7 +161,7 @@ class FreeimageBmpFormat(FreeimageFormat):
         Whether to compress the bitmap using RLE when saving. Default False.
         It seems this does not always work, but who cares, you should use
         PNG anyway.
-    
+
     """
 
     class Writer(FreeimageFormat.Writer):
@@ -182,13 +182,13 @@ class FreeimageBmpFormat(FreeimageFormat):
 
 class FreeimagePngFormat(FreeimageFormat):
     """ A PNG format based on the Freeimage library.
-    
+
     This format supports grayscale, RGB and RGBA images.
 
     The freeimage plugin requires a `freeimage` binary. If this binary
     not available on the system, it can be downloaded manually from
     <https://github.com/imageio/imageio-binaries> by either
-    
+
     - the command line script ``imageio_download_bin freeimage``
     - the Python method ``imageio.plugins.freeimage.download()``
 
@@ -196,7 +196,7 @@ class FreeimagePngFormat(FreeimageFormat):
     ----------------------
     ignoregamma : bool
         Avoid gamma correction. Default True.
-    
+
     Parameters for saving
     ---------------------
     compression : {0, 1, 6, 9}
@@ -263,13 +263,13 @@ class FreeimagePngFormat(FreeimageFormat):
 
 class FreeimageJpegFormat(FreeimageFormat):
     """ A JPEG format based on the Freeimage library.
-    
+
     This format supports grayscale and RGB images.
 
     The freeimage plugin requires a `freeimage` binary. If this binary
     not available on the system, it can be downloaded manually from
     <https://github.com/imageio/imageio-binaries> by either
-    
+
     - the command line script ``imageio_download_bin freeimage``
     - the Python method ``imageio.plugins.freeimage.download()``
 
@@ -280,9 +280,9 @@ class FreeimageJpegFormat(FreeimageFormat):
         Default True. If 2 is given, do the rotation in Python instead
         of freeimage.
     quickread : bool
-        Read the image more quickly, at the expense of quality. 
+        Read the image more quickly, at the expense of quality.
         Default False.
-    
+
     Parameters for saving
     ---------------------
     quality : scalar
@@ -296,7 +296,7 @@ class FreeimageJpegFormat(FreeimageFormat):
         few percent of file size). Default False.
     baseline : bool
         Save basic JPEG, without metadata or any markers. Default False.
-    
+
     """
 
     class Reader(FreeimageFormat.Reader):
@@ -316,7 +316,7 @@ class FreeimageJpegFormat(FreeimageFormat):
             return im, meta
 
         def _rotate(self, im, meta):
-            """ Use Orientation information from EXIF meta data to 
+            """ Use Orientation information from EXIF meta data to
             orient the image correctly. Freeimage is also supposed to
             support that, and I am pretty sure it once did, but now it
             does not, so let's just do it in Python.
@@ -370,12 +370,43 @@ class FreeimageJpegFormat(FreeimageFormat):
             return FreeimageFormat.Writer._append_data(self, im, meta)
 
 
+class FreeimagePnmFormat(FreeimageFormat):
+    """ A PNM format based on the Freeimage library.
+
+    This format supports single bit (PBM), grayscale (PGM) and RGB (PPM)
+    images, even with ASCII or binary coding.
+
+    The freeimage plugin requires a `freeimage` binary. If this binary
+    not available on the system, it can be downloaded manually from
+    <https://github.com/imageio/imageio-binaries> by either
+
+    - the command line script ``imageio_download_bin freeimage``
+    - the Python method ``imageio.plugins.freeimage.download()``
+
+    Parameters for saving
+    ---------------------
+    use_ascii : bool
+        Save with ASCII coding. Default True.
+    """
+
+    class Writer(FreeimageFormat.Writer):
+        def _open(self, flags=0, use_ascii=True):
+            # Build flags from kwargs
+            flags = int(flags)
+            if use_ascii:
+                flags |= IO_FLAGS.PNM_SAVE_ASCII
+            # Act as usual, but with modified flags
+            return FreeimageFormat.Writer._open(self, flags)
+
+
 ## Create the formats
 
 SPECIAL_CLASSES = {
     "jpeg": FreeimageJpegFormat,
     "png": FreeimagePngFormat,
     "bmp": FreeimageBmpFormat,
+    "ppm": FreeimagePnmFormat,
+    "ppmraw": FreeimagePnmFormat,
     "gif": None,  # defined in freeimagemulti
     "ico": None,  # defined in freeimagemulti
     "mng": None,  # defined in freeimagemulti

PPM-FI not creating ASCII output
When ssing the PPM-FI format string, no ASCII output is created.

```python
imageio.imwrite(path, image_data, "PPM-FI")
```

Produces for example

```
P6
200 100
65535
<binary data>
```

This is the expected behavior when using PPMRAW-FI. The expected ASCII output would be something like 

```
P3
200 100
65535
1 2 3 4 5 6 ...
```
diff --git a/tests/test_freeimage.py b/tests/test_freeimage.py
index 537fe76..b8be9d4 100644
--- a/tests/test_freeimage.py
+++ b/tests/test_freeimage.py
@@ -23,7 +23,7 @@ def setup_module():
     # This tests requires our version of the FI lib
     try:
         imageio.plugins.freeimage.download()
-    except imageio.core.InternetNotAllowedError:
+    except core.InternetNotAllowedError:
         # We cannot download; skip all freeimage tests
         need_internet()
 
@@ -486,6 +486,23 @@ def test_mng():
     # ims = imageio.imread(get_remote_file('images/mngexample.mng'))
 
 
+def test_pnm():
+
+    for useAscii in (True, False):
+        for crop in (0, 1, 2):
+            for colors in (0, 1, 3):
+                fname = fnamebase
+                fname += "%i.%i.%i.ppm" % (useAscii, crop, colors)
+                rim = get_ref_im(colors, crop, isfloat=False)
+                imageio.imsave(fname, rim, use_ascii=useAscii)
+                im = imageio.imread(fname)
+                assert_close(rim, im, 0.1)  # lossless
+
+                # Parameter fail
+                raises(TypeError, imageio.imread, fname, notavalidkwarg=True)
+                raises(TypeError, imageio.imsave, fname, im, notavalidk=True)
+
+
 def test_other():
 
     # Cannot save float

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest
apt-get update apt-get install -y gcc libfreeimage3
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
astropy==6.0.1
astropy-iers-data==0.2025.3.31.0.36.18
babel==2.17.0
black==25.1.0
certifi==2025.1.31
charset-normalizer==3.4.1
click==8.1.8
coverage==7.8.0
coveralls==4.0.1
docopt==0.6.2
docutils==0.21.2
exceptiongroup==1.2.2
execnet==2.1.1
flake8==7.2.0
idna==3.10
-e git+https://github.com/imageio/imageio.git@88b2a43cea81139e1a76460c68fa9aadebadfc1e#egg=imageio
imageio-ffmpeg==0.6.0
imagesize==1.4.1
importlib_metadata==8.6.1
iniconfig==2.1.0
invoke==2.2.0
Jinja2==3.1.6
MarkupSafe==3.0.2
mccabe==0.7.0
mypy-extensions==1.0.0
numpy==1.26.4
numpydoc==1.8.0
packaging==24.2
pathspec==0.12.1
pillow==11.1.0
platformdirs==4.3.7
pluggy==1.5.0
psutil==7.0.0
pycodestyle==2.13.0
pyerfa==2.0.1.5
pyflakes==3.3.1
Pygments==2.19.1
pytest==8.3.5
pytest-cov==6.0.0
pytest-xdist==3.6.1
PyYAML==6.0.2
requests==2.32.3
SimpleITK==2.4.1
snowballstemmer==2.2.0
Sphinx==7.4.7
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
tabulate==0.9.0
tomli==2.2.1
typing_extensions==4.13.0
urllib3==2.3.0
zipp==3.21.0

name: imageio
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - astropy==6.0.1
      - astropy-iers-data==0.2025.3.31.0.36.18
      - babel==2.17.0
      - black==25.1.0
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - click==8.1.8
      - coverage==7.8.0
      - coveralls==4.0.1
      - docopt==0.6.2
      - docutils==0.21.2
      - exceptiongroup==1.2.2
      - execnet==2.1.1
      - flake8==7.2.0
      - idna==3.10
      - imageio-ffmpeg==0.6.0
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - invoke==2.2.0
      - jinja2==3.1.6
      - markupsafe==3.0.2
      - mccabe==0.7.0
      - mypy-extensions==1.0.0
      - numpy==1.26.4
      - numpydoc==1.8.0
      - packaging==24.2
      - pathspec==0.12.1
      - pillow==11.1.0
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - psutil==7.0.0
      - pycodestyle==2.13.0
      - pyerfa==2.0.1.5
      - pyflakes==3.3.1
      - pygments==2.19.1
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - pytest-xdist==3.6.1
      - pyyaml==6.0.2
      - requests==2.32.3
      - simpleitk==2.4.1
      - snowballstemmer==2.2.0
      - sphinx==7.4.7
      - sphinxcontrib-applehelp==2.0.0
      - sphinxcontrib-devhelp==2.0.0
      - sphinxcontrib-htmlhelp==2.1.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==2.0.0
      - sphinxcontrib-serializinghtml==2.0.0
      - tabulate==0.9.0
      - tomli==2.2.1
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - zipp==3.21.0
prefix: /opt/conda/envs/imageio

tests/test_freeimage.py::test_pnm
tests/test_freeimage.py::test_get_ref_im tests/test_freeimage.py::test_get_fi_lib tests/test_freeimage.py::test_freeimage_format tests/test_freeimage.py::test_freeimage_lib tests/test_freeimage.py::test_png tests/test_freeimage.py::test_png_dtypes tests/test_freeimage.py::test_jpg tests/test_freeimage.py::test_jpg_more tests/test_freeimage.py::test_bmp tests/test_freeimage.py::test_gif tests/test_freeimage.py::test_animated_gif tests/test_freeimage.py::test_ico tests/test_freeimage.py::test_mng tests/test_freeimage.py::test_other tests/test_freeimage.py::test_gamma_correction
.gitignore imageio/plugins/freeimage.py
.gitignore imageio/plugins/freeimage.py