{-# LANGUAGE ScopedTypeVariables, BangPatterns #-}
-- | Writing out matricies as PPM image files.
module PPM
( writeMatrixAsNormalisedPPM
, writeMatrixAsPPM
, readPPMAsMatrix
, readPPMAsMatrix2)
where
import qualified Data.Array.Parallel.Unlifted as U
import Data.List as L
import Data.Array.Repa as A
import Prelude as P
import System.IO
import Control.Monad
import Data.Char
-- Write ------------------------------------------------------------------------------------------
-- | Convert a matrix to a PPM image,
-- while normalising it to the maximum value present in the matrix.
writeMatrixAsNormalisedPPM
:: FilePath -- ^ Filename of output file.
-> (Double -> (Double, Double, Double)) -- ^ Function for producing colors from data values,
-> Array DIM2 Double -- ^ Matrix of values (need not be normalised).
-> IO ()
writeMatrixAsNormalisedPPM fileName colorFn arr
= let -- Use the maximum elem in the array as the white value.
vals = U.toList $ toUArray arr
maxVal = maximum vals
-- Normalise the array to the range [0..1] for display.
arrNorm = A.map (/ maxVal) arr
in writeMatrixAsPPM fileName colorFn arrNorm
-- | Convert a matrix to a PPM image.
-- Matrix elements should be normalised to [0..1]
writeMatrixAsPPM
:: FilePath
-> (Double -> (Double, Double, Double)) -- ^ Function for producing colors from data values.
-> Array DIM2 Double -- ^ Matrix of values, normalised to [0..1]
-> IO ()
writeMatrixAsPPM fileName colorFn arr
= let
-- Break flat array data into individual rows
Z :. width :. height
= extent arr
in do
file <- openFile fileName WriteMode
hPutStrLn file $ "P3"
hPutStrLn file $ show width ++ " " ++ show height
hPutStrLn file $ "255"
hWritePixels file colorFn $ toList arr
hClose file
-- | Write out pixel values to a file.
hWritePixels
:: Handle
-> (Double -> (Double, Double, Double)) -- ^ Function for producing colors from data values.
-> [Double] -- ^ Data values.
-> IO ()
hWritePixels h colorFn !xx
= go xx
where
go [] = return ()
go (x:xs)
= do let (r, g, b) = colorFn x
hPutStr h $ showInt $ truncate (r * 255)
hPutStr h $ " "
hPutStr h $ showInt $ truncate (g * 255)
hPutStr h $ " "
hPutStr h $ showInt $ truncate (b * 255)
hPutStr h $ "\n"
go xs
showInt :: Int -> String
showInt i = show i
-- Read -------------------------------------------------------------------------------------------
readPPMAsMatrix
:: (Int -> Int -> Int -> Double) -- ^ Function for producing array values from RGB pixel values
-> FilePath -- ^ File name of ppm file.
-> IO (Array DIM2 Double) -- ^ Loaded matrix.
readPPMAsMatrix pointFn fileName
= do file <- openFile fileName ReadMode
"P3" <- hGetLine file
[width, height] <- liftM (P.map read . words) $ hGetLine file
_maxVal <- hGetLine file
vals <- loadPixels pointFn file
let dims = Z :. width :. height
let mat = fromList dims vals
return mat
-- | Read the values of two separate matricies that are encoded in a single PPM file.
readPPMAsMatrix2
:: (Int -> Int -> Int -> (Double, Double))
-- ^ Function for producing array values from RGB pixel values
-> FilePath -- ^ File name of ppm file.
-> IO ( Array DIM2 Double -- ^ Loaded matrix.
, Array DIM2 Double)
readPPMAsMatrix2 pointFn fileName
= do file <- openFile fileName ReadMode
"P3" <- hGetLine file
[width, height] <- liftM (P.map read . words) $ hGetLine file
_maxVal <- hGetLine file
vals <- loadPixels pointFn file
let dims = Z :. width :. height
let mat1 = fromList dims $ P.map fst vals
let mat2 = fromList dims $ P.map snd vals
return (mat1, mat2)
-- | Load list of array data from the data part of a PPM file.
loadPixels
:: (Int -> Int -> Int -> a) -- ^ Function for producing array values from RGB pixel values.
-> Handle -- ^ Handle of file to load from.
-> IO [a]
loadPixels pointFn handle
= do str <- hGetContents handle
let ints = readInts str
let vals = convertLine pointFn ints
return vals
-- | Convert groups of RGB components to values.
convertLine
:: (Int -> Int -> Int -> a) -- ^ Function for producing array values from RGB pixel values.
-> [Int] -- ^ Ints read from the PPM file.
-> [a] -- ^ Output values.
convertLine fn vs
= case vs of
[] -> []
r : g : b : rest -> fn r g b : convertLine fn rest
_ -> error "PPM.convertLine: bad pixel"
-- | Read a string containing ints separated by whitespace.
readInts :: String -> [Int]
readInts cs = readInts' [] cs
where readInts' _ [] = []
readInts' acc (c : rest)
| isSpace c
= if null acc
then readInts' [] rest
else read (reverse acc) : readInts' [] rest
| isDigit c
= readInts' (c : acc) rest
| otherwise
= error $ "unexpected char in PPM file " ++ show (ord c)