Validation turns parsed PNM data into an image value that is safe to process. Raw input may have the wrong number of pixels, a pixel shape that disagrees with its header, or color values outside the declared maximum. A validator checks these conditions while converting raw pixels into suitable image pixels. section 13.1.1 section 13.2
A PNM validation procedure checks that:
- The pixel count equals the header width multiplied by height. section 13.1.1 section 13.2
- Pixel constructors match the format identified by the magic number: bitmap and graymap use single values, while pixmap uses RGB values. section 13.1.1 section 13.2
- Graymap and pixmap component values do not exceed the header’s maximum value. section 13.1.1 section 13.2
- Bitmap images do not carry a maximum value, and their raw values must be 0 or 1. section 13.2
Use Either String to make failure explicit: a successful check returns Right with a converted pixel or image, while a failed check returns Left with an error. This lets validation stop at the first invalid element instead of constructing a partially trustworthy image. section 13.2 section 13.2
Validating a graymap pixel
For a graymap pixel represented as Single g, first reject any value greater than maxVal. Otherwise, compute the ratio g / maxVal, scale it to the desired output representation, and round it. A non-Single value is rejected because it cannot represent a graymap pixel. section 13.2
Validation can be organized as small conversion functions for bitmap, graymap, and pixmap pixels, plus helpers that classify magic numbers and verify image size. A top-level validatePnm function chooses the appropriate conversion from the header and applies it across all pixels with mapM. section 13.2 section 13.2
Listing 13.11 The validation function for PNM parse results
import qualified Data.Vector as V
...
validatePnm :: RawPnm -> Either String DynamicPnmImage
validatePnm img@(RawPnm {header, imageData})
| not $ imageSizeCorrect img =
Left $ #1
"The number of pixels given does not match "
<> "the expected size inferred from the header"
| indicatesBitmap magicNum =
case maxVal header of
Nothing -> PnmGray8Bit `withTransform`
bitmapPixel #2
Just _ ->
Left $ #1
"Image seems to be a bitmap image "
<> "but has a maximum value set"
| indicatesGraymap magicNum =
case maxVal header of
Nothing ->
Left $ #1
"Image seems to be a graymap image "
<> "but has no maximum value set"
Just maxVal ->
if maxVal <= 255
then
PnmGray8Bit #3
`withTransform` graymapPixel maxVal
else
PnmGray16Bit #3
`withTransform` graymapPixel maxVal
| indicatesPixmap magicNum =
case maxVal header of
Nothing ->
Left $ #1
"Image seems to be a pixmap image "
<> "but has no maximum value set"
Just maxVal ->
if maxVal <= 255
then
PnmColor8Bit #4
`withTransform` pixmapPixel maxVal P.PixelRGB8
else
PnmColor16Bit #4
`withTransform` pixmapPixel maxVal P.PixelRGB16
| otherwise = Left "Image seems to be of unknown type" #5
where
magicNum = magicNumber header
width' = fromIntegral $ width header
height' = fromIntegral $ height header
withTransform ::
(Int -> Int -> V.Vector px -> PnmImage px) ->
(RawPixel -> Either String px) ->
Either String DynamicPnmImage
withTransform c f =
let mConvertedData = mapM f $ pixels imageData #6
in fmap mkRes mConvertedData #7
where
mkRes = DynamicPnmImage . c width' height'
. V.fromList #8
The complete validator shows the order of global checks, header-based format selection, and error cases before pixel data is accepted. section 13.2





























