Skip to content

Commit

Permalink
pnm: Improvements to pnm plugin (#4253)
Browse files Browse the repository at this point in the history
Fixes added:

- import/export support for 8/16 uint 32-float
- "pnm:bigendian" attribute reports/controls andianness
- "pnm:pfmflip" attribute (when set to 0) can disable the automatic vertical flip of pfm files (GIMP flips float PFM files by default, Photoshop does not)
- update in documentation

---------

Signed-off-by: Vlad (Kuzmin) Erium <libalias@gmail.com>
Signed-off-by: Vlad (Kuzmin) Erium <shaamaan@gmail.com>
  • Loading branch information
ssh4net committed May 4, 2024
1 parent 676fb58 commit ed9d8d3
Show file tree
Hide file tree
Showing 4 changed files with 293 additions and 53 deletions.
69 changes: 56 additions & 13 deletions src/doc/builtinplugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1678,22 +1678,38 @@ and :ref:`sec-imageinput-ioproxy`) as well as the `set_ioproxy()` methods.
PNM / Netpbm
===============================================

The Netpbm project, a.k.a. PNM (portable "any" map) defines PBM, PGM,
and PPM (portable bitmap, portable graymap, portable pixmap) files.
The Netpbm project, a.k.a. PNM (portable "any" map) defines PBM, PGM, PPM
and later added PFM (portable float map) as a set of simple image formats
(portable bitmap, portable graymap, portable pixmap) files.
Without loss of generality, we will refer to these all collectively as
"PNM." These files have extensions :file:`.pbm`, :file:`.pgm`, and
:file:`.ppm` and customarily correspond to bi-level bitmaps, 1-channel
grayscale, and 3-channel RGB files, respectively, or :file:`.pnm` for
those who reject the nonsense about naming the files depending on the
"PNM." These files have extensions :file:`.pbm`, :file:`.pgm`,
:file:`.ppm`, :file:`.pfm` and customarily correspond to bi-level bitmaps,
1-channel grayscale, and 3-channel RGB files, respectively, or :file:`.pnm`
for those who reject the nonsense about naming the files depending on the
number of channels and bitdepth.

PNM files are not much good for anything, but because of their
historical significance and extreme simplicity (that causes many
"amateur" programs to write images in these formats), OpenImageIO
supports them. PNM files do not support floating point images, anything
other than 1 or 3 channels, no tiles, no multi-image, no MIPmapping.
It's not a smart choice unless you are sending your images back to the
1980's via a time machine.
PNM files are widely used in the Unix world as simple ASCII or binary image
files that are easy to read and write. They are not compressed, and are
not particularly efficient for large images. They are not widely used in
the professional graphics world, but because of their historical
significance and extreme simplicity, OpenImageIO supports them.
PNM files do not support anything other than 1 or 3 channels, no tiles,
no multi-image, no MIPmapping.

The pbm, pgm, and ppm varieties are stored with scanlines ordered in the
file as top-to-bottom (the same as the usual OIIO convention), but the
float-based pfm files are conventionally ordered in the file as
bottom-to-top. Therefore, by default, reading and writing of the pfm
variety will automatically flip the image so that an application calling
the OpenImageIO API can, as usual, assume that scanline 0 is the visual
"top" (even though it is actually the last scanline stored in the file).

Both the reader and writer accept configuration hints "pnm:pfmflip"
(default: 1), which if set to 0 will disable this flipping and ensure
that scanline 0 is written as the first in the file (therefore
representing what PFM assumes is the visual "bottom" of the image).
This hint only affects PFM files and has no effect on the pbm, pgm,
or ppm varieties.

**Attributes**

Expand Down Expand Up @@ -1731,6 +1747,16 @@ attributes are supported:
- ptr
- Pointer to a ``Filesystem::IOProxy`` that will handle the I/O, for
example by reading from memory rather than the file system.
* - ``pnm:bigendian``
- int
- If nonzero, the PNM file is big-endian (the default is little-endian).
* - ``pnm:pfmflip``
- int
- If this configuration hint is present and is zero, the automatic
vertical flipping of PFM image will be disabled (i.e., scanline 0 will
really be the first one stored in the file). If nonzero (the default),
float PFM files will store scanline 0 as the last scanline in the file
(i.e. the visual "top" of the image).

**Configuration settings for PNM output**

Expand All @@ -1753,6 +1779,23 @@ control aspects of the writing itself:
- ptr
- Pointer to a ``Filesystem::IOProxy`` that will handle the I/O, for
example by writing to a memory buffer.
* - ``pnm:bigendian``
- int
- If nonzero, the PNM file is big-endian (the default is little-endian).
* - ``pnm:binary``
- int
- nonzero if the file itself used the PNM binary format, 0 if it used
ASCII. The PNM writer honors this attribute in the ImageSpec to
determine whether to write an ASCII or binary file.
Float PFM files are always written in binary format.
* - ``pnm:pfmflip``
- int
- If this configuration hint is present and is zero, for PFM files,
scanline 0 will really be stored first in the file, thus disabling the
usual automatically flipping that accounts for PFM files conventionally
being stored in bottom-to-top order. If nonzero (the default), float
PFM files will store scanline 0 as the last scanline in the file (i.e.
the visual "top" of the image).

**Custom I/O Overrides**

Expand Down
25 changes: 21 additions & 4 deletions src/pnm.imageio/pnminput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// https://github.com/AcademySoftwareFoundation/OpenImageIO

#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <string>
Expand All @@ -12,6 +13,8 @@

OIIO_PLUGIN_NAMESPACE_BEGIN

#define DBG if (0)

//
// Documentation on the PNM formats can be found at:
// http://netpbm.sourceforge.net/doc/pbm.html (B&W)
Expand Down Expand Up @@ -48,6 +51,7 @@ class PNMInput final : public ImageInput {
string_view m_remaining;
string_view m_after_header;
int m_y_next;
bool m_pfm_flip;

void init()
{
Expand Down Expand Up @@ -188,6 +192,7 @@ unpack_floats(const unsigned char* read, float* write, imagesize_t numsamples,
bool
PNMInput::read_file_scanline(void* data, int y)
{
DBG std::cerr << "PNMInput::read_file_scanline(" << y << ")\n";
if (y < m_y_next) {
// If being asked to backtrack to an earlier scanline, reset all the
// way to the beginning, right after the header.
Expand All @@ -202,9 +207,11 @@ PNMInput::read_file_scanline(void* data, int y)
for (; good && m_y_next <= y; ++m_y_next) {
// PFM files are bottom-to-top, so we need to seek to the right spot
if (m_pnm_type == PF || m_pnm_type == Pf) {
int file_scanline = m_spec.height - 1 - (y - m_spec.y);
auto offset = file_scanline * m_spec.scanline_bytes();
m_remaining = m_after_header.substr(offset);
if (m_pfm_flip) {
int file_scanline = m_spec.height - 1 - (y - m_spec.y);
auto offset = file_scanline * m_spec.scanline_bytes();
m_remaining = m_after_header.substr(offset);
}
}

if ((m_pnm_type >= P4 && m_pnm_type <= P6) || m_pnm_type == PF
Expand All @@ -219,6 +226,7 @@ PNMInput::read_file_scanline(void* data, int y)
if (size_t(numbytes) > m_remaining.size())
return false;
buf.assign(m_remaining.begin(), m_remaining.begin() + numbytes);

m_remaining.remove_prefix(numbytes);
}

Expand Down Expand Up @@ -327,6 +335,7 @@ PNMInput::read_file_header()
m_spec = ImageSpec(width, height, m_pnm_type == PF ? 3 : 1,
TypeDesc::FLOAT);
m_spec.attribute("pnm:bigendian", m_scaling_factor < 0 ? 0 : 1);
m_spec.attribute("pnm:binary", 1);
}
m_spec.attribute("oiio:ColorSpace", "Rec709");
return true;
Expand All @@ -339,7 +348,15 @@ PNMInput::open(const std::string& name, ImageSpec& newspec,
const ImageSpec& config)
{
ioproxy_retrieve_from_config(config);
return open(name, newspec);

if (!open(name, newspec)) {
errorfmt("Could not parse spec for file \"%s\"", name);
return false;
}

m_pfm_flip = config.get_int_attribute("pnm:pfmflip", 1);

return true;
}


Expand Down

0 comments on commit ed9d8d3

Please sign in to comment.