module Graphics.Pgm
( pgmToArray,
pgmsToArrays,
pgmToArrayWithComments, pgmsToArraysWithComments,
arrayToPgmWithComment,
pgmsFromFile, pgmsFromHandle,
arrayToPgm, arrayToFile, arrayToHandle, arraysToHandle,
arraysToFile
)
where
import Control.Monad (liftM)
import Data.Array.Unboxed (IArray, Ix, UArray, amap, bounds, elems, listArray)
import Data.ByteString.Internal (c2w)
import Data.List (intercalate)
import Data.Word (Word8, Word16)
import System.IO (Handle, IOMode(ReadMode, WriteMode), hClose, openFile)
import Text.Parsec (ParseError, (<?>), choice, getInput, many1, manyTill, parse, setInput, try)
import Text.Parsec (anyChar, char, digit, newline, space, spaces)
import Text.Parsec.ByteString (Parser)
import Text.Printf (printf)
import qualified Data.ByteString as B
magicNumber :: Parser ()
magicNumber = do { _ <- char 'P'; _ <- char '5'; return () }
integer :: Parser Int
integer = do { s <- many1 digit; return $ read s }
width :: Parser Int
width = integer
height :: Parser Int
height = integer
maxVal :: Parser Int
maxVal = do { i <- integer; return $ min 65536 i }
comment :: Parser String
comment = do { _ <- char '#'; c <- manyTill anyChar (try newline); return $ c ++ "\n" }
commentAwareWhiteSpace :: Parser String
commentAwareWhiteSpace = liftM concat $ many1 $
choice
[ comment
, do { _ <- many1 space; return "" }]
pgmHeader :: Parser (Int,Int,Int,String)
pgmHeader = do magicNumber <?> "magic number"
hVal0 <- commentAwareWhiteSpace
cols <- width <?> "width"
hVal1 <- commentAwareWhiteSpace
rows <- height <?> "height"
hVal2 <- commentAwareWhiteSpace
m <- maxVal <?> "maximum grey value"
_ <- space
let q = hVal0 ++ hVal1 ++ hVal2
return (rows,cols,m, Prelude.init q)
pgmWithComments :: (IArray UArray a, Integral a) => Parser (UArray (Int,Int) a, String)
pgmWithComments = do (rows,cols,m,comments) <- pgmHeader
let d = if m < 256 then 1 else 2
ip <- getInput
let body = B.take (rows*cols*d) ip
setInput $ B.drop (rows*cols*d) ip
let arr = readArray (if d == 1 then Depth8 else Depth16) rows cols body
return (arr,comments)
pgmsWithComments :: (IArray UArray a, Integral a) => Parser [(UArray (Int,Int) a, String)]
pgmsWithComments = many1 (do { h <- pgmWithComments ; spaces ; return h })
pgm :: (IArray UArray a, Integral a) => Parser (UArray (Int,Int) a)
pgm = do (rows,cols,m,_) <- pgmHeader
let d = if m < 256 then 1 else 2
ip <- getInput
let body = B.take (rows*cols*d) ip
setInput $ B.drop (rows*cols*d) ip
let arr = readArray (if d == 1 then Depth8 else Depth16) rows cols body
return arr
pgms :: (IArray UArray a, Integral a) => Parser [UArray (Int,Int) a]
pgms = many1 (do { h <- pgm ; spaces ; return h })
pgmToArray :: (Integral a, IArray UArray a) => B.ByteString -> Either ParseError (UArray (Int,Int) a)
pgmToArray = parse pgm "Failed to parse PGM."
pgmToArrayWithComments :: (Integral a, IArray UArray a) => B.ByteString -> Either ParseError (UArray (Int,Int) a, String)
pgmToArrayWithComments = parse pgmWithComments "Failed to parse PGM."
pgmsToArrays :: (Integral a, IArray UArray a) => B.ByteString -> Either ParseError [UArray (Int,Int) a]
pgmsToArrays = parse pgms "Failed to parse PGMs."
pgmsToArraysWithComments :: (Integral a, IArray UArray a) => B.ByteString -> Either ParseError [(UArray (Int,Int) a, String)]
pgmsToArraysWithComments = parse pgmsWithComments "Failed to parse PGMs."
pgmsFromFile :: String -> IO (Either ParseError [UArray (Int,Int) Int])
pgmsFromFile fname = do h <- openFile fname ReadMode
s <- pgmsFromHandle h
hClose h
return s
pgmsFromHandle :: Handle -> IO (Either ParseError [UArray (Int,Int) Int])
pgmsFromHandle = liftM pgmsToArrays . B.hGetContents
readArray8 :: Int -> Int -> B.ByteString -> UArray (Int,Int) Word8
readArray8 rows cols src = listArray ((0,0), (rows1,cols1)) (B.unpack src)
readArray16 :: Int -> Int -> B.ByteString -> UArray (Int,Int) Word16
readArray16 rows cols src = listArray ((0,0), (rows1,cols1)) src'
where raw = B.unpack src
src' = pairWith f raw
f a b = 256 * fromIntegral a + fromIntegral b
data Depth = Depth8 | Depth16
readArray :: (IArray UArray a, Integral a) => Depth -> Int -> Int -> B.ByteString -> UArray (Int,Int) a
readArray Depth8 rows cols src = amap fromIntegral $ readArray8 rows cols src
readArray Depth16 rows cols src = amap fromIntegral $ readArray16 rows cols src
pair :: [a] -> [(a,a)]
pair [] = []
pair (_:[]) = []
pair (a:b:ls) = (a,b) : pair ls
pairWith :: (a -> a -> b) -> [a] -> [b]
pairWith f ls = Prelude.map (uncurry f) $ pair ls
pgmHeaderString :: Int -> Int -> Word16 -> String -> B.ByteString
pgmHeaderString rows cols mVal comm = B.pack $ Prelude.map c2w $
printf "P5\n#%s\n%d %d %d\n" (format comm)
(cols+1) (rows+1) mVal
where format = Data.List.intercalate "\n#" . lines
arrayToPgm :: IArray m Word16 => m (Int,Int) Word16 -> B.ByteString
arrayToPgm arr = pgmHeaderString rows cols mVal "" `B.append`
listToByteString mVal (elems arr)
where (rows,cols) = (xmaxxmin,ymaxymin)
((xmin,ymin),(xmax,ymax)) = bounds arr
mVal = arrayLift max arr
arrayToPgmWithComment :: IArray m Word16 => m (Int,Int) Word16 -> String -> B.ByteString
arrayToPgmWithComment arr cm = pgmHeaderString rows cols mVal cm `B.append`
listToByteString mVal (elems arr)
where (rows,cols) = (xmaxxmin,ymaxymin)
((xmin,ymin),(xmax,ymax)) = bounds arr
mVal = arrayLift max arr
arrayLift :: (Ix i, IArray m a) => (a -> a -> a) -> m i a -> a
arrayLift f arr = Prelude.foldl f (head q) q
where q = elems arr
listToByteString :: Word16 -> [Word16] -> B.ByteString
listToByteString d vs
| d < 256 = B.pack ((Prelude.map fromIntegral vs)::[Word8])
| otherwise = B.pack $ concatMap (\x -> [fromIntegral (x `div` 256),
fromIntegral (x `rem` 256)]) vs
arrayToHandle :: IArray m Word16 => Handle -> m (Int,Int) Word16 -> IO ()
arrayToHandle h arr = B.hPutStr h (arrayToPgm arr)
arrayToFile :: IArray m Word16 => String -> m (Int,Int) Word16 -> IO ()
arrayToFile fname arr = do h <- openFile fname WriteMode
arrayToHandle h arr
hClose h
arraysToHandle :: IArray m Word16 => Handle -> [m (Int,Int) Word16] -> IO ()
arraysToHandle = mapM_ . arrayToHandle
arraysToFile :: IArray m Word16 => String -> [m (Int,Int) Word16] -> IO ()
arraysToFile fname arrs = do h <- openFile fname WriteMode
arraysToHandle h arrs
hClose h