A digital image can be modeled as dimensions plus pixel data. For PNM processing, the pixel type matters: images may be grayscale or color, with 8-bit or 16-bit pixel values. Encoding that distinction in the image type lets functions accept and return the correct kind of image without repeatedly inspecting a catch-all pixel container. section 13.1.1 section 13.1.3
Use a parameterized PnmImage px type with constructors for grayscale and RGB images at each supported bit depth. Each constructor carries width, height, and a Vector of the matching pixel type. Accessor functions can expose the dimensions and pixels uniformly while the constructors preserve the pixel-type information. section 13.1.3
Listing 13.3 Type to store PNM images
{-# LANGUAGE GADTs #-} #1
module Graphics.PNM.Types where
import qualified Codec.Picture as P #2
import qualified Data.Vector as V #2
...
data PnmImage px where #3
PnmGray8Bit :: #4
Int ->
Int ->
V.Vector P.Pixel8 ->
PnmImage P.Pixel8
PnmGray16Bit :: #5
Int ->
Int ->
V.Vector P.Pixel16 ->
PnmImage P.Pixel16
PnmColor8Bit :: #5
Int ->
Int ->
V.Vector P.PixelRGB8 ->
PnmImage P.PixelRGB8
PnmColor16Bit :: #5
Int ->
Int ->
V.Vector P.PixelRGB16 ->
PnmImage P.PixelRGB16
Inspect the four image constructors and how each couples dimensions with a Vector of the appropriate JuicyPixels pixel type. section 13.1.3
A Vector suits image data when processing mostly reads pixels or maps over them. It supports constant-time indexing and common slicing operations, unlike a list’s slower element access. Build data with list-oriented parsing when convenient, then convert it to a Vector for compact, efficient image access. Vectors are a poor fit for frequent appends and are not generally suitable for infinite data. section 13.1.3
Exporting a PNM image as PNG
Convert a dynamically typed PNM image by matching its constructor. For each case, generate a JuicyPixels Image from a coordinate-to-pixel function, then wrap it in the corresponding DynamicImage constructor: ImageY8, ImageY16, ImageRGB8, or ImageRGB16. Pixel lookup uses the row-major index y * width + x before JuicyPixels writes the PNG. section 13.3.2
Listing 13.17 The function to transform PNM images to JuicyPixels image values
dynamicPnmToDynamicImage :: DynamicPnmImage -> P.DynamicImage
dynamicPnmToDynamicImage (DynamicPnmImage img) =
case img of
PnmGray8Bit {} -> P.ImageY8 $ pnmToImage img #1
PnmGray16Bit {} -> P.ImageY16 $ pnmToImage img #1
PnmColor8Bit {} -> P.ImageRGB8 $ pnmToImage img #1
PnmColor16Bit {} -> P.ImageRGB16 $ pnmToImage img #1
where #1
pnmToImage :: P.Pixel px => PnmImage px -> P.Image px
pnmToImage pnmImg =
P.generateImage build (pnmWidth pnmImg) (pnmHeight pnmImg) #2
where
build x y = #3
pnmPixels pnmImg V.! (y * (pnmWidth pnmImg) + x)
Follow the constructor match that turns typed PNM pixel vectors into JuicyPixels images for PNG output. section 13.3.2















