{-# LANGUAGE CPP #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TupleSections #-}
module Codec.Picture.HDR( decodeHDR
, decodeHDRWithMetadata
, encodeHDR
, encodeRawHDR
, encodeRLENewStyleHDR
, writeHDR
, writeRLENewStyleHDR
) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative( pure, (<*>), (<$>) )
#endif
import Data.Bits( Bits, (.&.), (.|.), unsafeShiftL, unsafeShiftR )
import Data.Char( ord, chr, isDigit )
import Data.Word( Word8 )
#if !MIN_VERSION_base(4,11,0)
import Data.Monoid( (<>) )
#endif
import Control.Monad( when, foldM, foldM_, forM, forM_, unless )
import Control.Monad.Trans.Class( lift )
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Char8 as BC
import Data.List( partition )
import Data.Binary( Binary( .. ), encode )
import Data.Binary.Get( Get, getByteString, getWord8 )
import Data.Binary.Put( putByteString, putLazyByteString )
import Control.Monad.ST( ST, runST )
import Foreign.Storable ( Storable )
import Control.Monad.Primitive ( PrimState, PrimMonad )
import qualified Data.Vector.Storable as V
import qualified Data.Vector.Storable.Mutable as M
import Codec.Picture.Metadata( Metadatas
, SourceFormat( SourceHDR )
, basicMetadata )
import Codec.Picture.InternalHelper
import Codec.Picture.Types
import Codec.Picture.VectorByteConversion
#if MIN_VERSION_transformers(0, 4, 0)
import Control.Monad.Trans.Except( ExceptT, throwE, runExceptT )
#else
import Control.Monad.Trans.Error( Error, ErrorT, throwError, runErrorT )
type ExceptT = ErrorT
throwE :: (Monad m, Error e) => e -> ErrorT e m a
throwE = throwError
runExceptT :: ErrorT e m a -> m (Either e a)
runExceptT = runErrorT
#endif
{-# INLINE (.<<.) #-}
(.<<.), (.>>.) :: (Bits a) => a -> Int -> a
(.<<.) = unsafeShiftL
(.>>.) = unsafeShiftR
{-# INLINE (.<-.) #-}
(.<-.) :: (PrimMonad m, Storable a)
=> M.STVector (PrimState m) a -> Int -> a -> m ()
(.<-.) = M.write
type HDRReader s a = ExceptT String (ST s) a
data RGBE = RGBE !Word8 !Word8 !Word8 !Word8
instance Binary RGBE where
put (RGBE r g b e) = put r >> put g >> put b >> put e
get = RGBE <$> get <*> get <*> get <*> get
checkLineLength :: RGBE -> Int
checkLineLength (RGBE _ _ a b) =
(fromIntegral a .<<. 8) .|. fromIntegral b
isNewRunLengthMarker :: RGBE -> Bool
isNewRunLengthMarker (RGBE 2 2 _ _) = True
isNewRunLengthMarker _ = False
data RadianceFormat =
FormatRGBE
| FormatXYZE
radiance32bitRleRGBEFormat, radiance32bitRleXYZEFromat :: B.ByteString
radiance32bitRleRGBEFormat = BC.pack "32-bit_rle_rgbe"
radiance32bitRleXYZEFromat = BC.pack "32-bit_rle_xyze"
instance Binary RadianceFormat where
put FormatRGBE = putByteString radiance32bitRleRGBEFormat
put FormatXYZE = putByteString radiance32bitRleXYZEFromat
get = getByteString (B.length radiance32bitRleRGBEFormat) >>= format
where format sig
| sig == radiance32bitRleRGBEFormat = pure FormatRGBE
| sig == radiance32bitRleXYZEFromat = pure FormatXYZE
| otherwise = fail "Unrecognized Radiance format"
toRGBE :: PixelRGBF -> RGBE
toRGBE (PixelRGBF r g b)
| d <= 1e-32 = RGBE 0 0 0 0
| otherwise = RGBE (fix r) (fix g) (fix b) (fromIntegral $ e + 128)
where d = maximum [r, g, b]
e = exponent d
coeff = significand d * 255.9999 / d
fix v = truncate $ v * coeff
dropUntil :: Word8 -> Get ()
dropUntil c = getWord8 >>= inner
where inner val | val == c = pure ()
inner _ = getWord8 >>= inner
getUntil :: (Word8 -> Bool) -> B.ByteString -> Get B.ByteString
getUntil f initialAcc = getWord8 >>= inner initialAcc
where inner acc c | f c = pure acc
inner acc c = getWord8 >>= inner (B.snoc acc c)
data RadianceHeader = RadianceHeader
{ radianceInfos :: [(B.ByteString, B.ByteString)]
, radianceFormat :: RadianceFormat
, radianceHeight :: !Int
, radianceWidth :: !Int
, radianceData :: L.ByteString
}
radianceFileSignature :: B.ByteString
radianceFileSignature = BC.pack "#?RADIANCE\n"
unpackColor :: L.ByteString -> Int -> RGBE
unpackColor str idx = RGBE (at 0) (at 1) (at 2) (at 3)
where at n = L.index str . fromIntegral $ idx + n
storeColor :: M.STVector s Word8 -> Int -> RGBE -> ST s ()
storeColor vec idx (RGBE r g b e) = do
(vec .<-. (idx + 0)) r
(vec .<-. (idx + 1)) g
(vec .<-. (idx + 2)) b
(vec .<-. (idx + 3)) e
parsePair :: Char -> Get (B.ByteString, B.ByteString)
parsePair firstChar = do
let eol c = c == fromIntegral (ord '\n')
line <- getUntil eol B.empty
case BC.split '=' line of
[] -> pure (BC.singleton firstChar, B.empty)
[val] -> pure (BC.singleton firstChar, val)
[key, val] -> pure (BC.singleton firstChar <> key, val)
(key : vals) -> pure (BC.singleton firstChar <> key, B.concat vals)
decodeInfos :: Get [(B.ByteString, B.ByteString)]
decodeInfos = do
char <- getChar8
case char of
'#' -> dropUntil (fromIntegral $ ord '\n') >> decodeInfos
'\n' -> pure []
c -> (:) <$> parsePair c <*> decodeInfos
decodeHDR :: B.ByteString -> Either String DynamicImage
decodeHDR = fmap fst . decodeHDRWithMetadata
decodeHDRWithMetadata :: B.ByteString -> Either String (DynamicImage, Metadatas)
decodeHDRWithMetadata str = runST $ runExceptT $
case runGet decodeHeader $ L.fromChunks [str] of
Left err -> throwE err
Right rez ->
let meta = basicMetadata SourceHDR (abs $ radianceWidth rez) (abs $ radianceHeight rez) in
(, meta) . ImageRGBF <$> (decodeRadiancePicture rez >>= lift . unsafeFreezeImage)
getChar8 :: Get Char
getChar8 = chr . fromIntegral <$> getWord8
isSign :: Char -> Bool
isSign c = c == '+' || c == '-'
isAxisLetter :: Char -> Bool
isAxisLetter c = c == 'X' || c == 'Y'
decodeNum :: Get Int
decodeNum = do
sign <- getChar8
letter <- getChar8
space <- getChar8
unless (isSign sign && isAxisLetter letter && space == ' ')
(fail "Invalid radiance size declaration")
let numDec acc c | isDigit c =
getChar8 >>= numDec (acc * 10 + ord c - ord '0')
numDec acc _
| sign == '-' = pure $ negate acc
| otherwise = pure acc
getChar8 >>= numDec 0
copyPrevColor :: M.STVector s Word8 -> Int -> ST s ()
copyPrevColor scanLine idx = do
r <- scanLine `M.unsafeRead` (idx - 4)
g <- scanLine `M.unsafeRead` (idx - 3)
b <- scanLine `M.unsafeRead` (idx - 2)
e <- scanLine `M.unsafeRead` (idx - 1)
(scanLine `M.unsafeWrite` (idx + 0)) r
(scanLine `M.unsafeWrite` (idx + 1)) g
(scanLine `M.unsafeWrite` (idx + 2)) b
(scanLine `M.unsafeWrite` (idx + 3)) e
oldStyleRLE :: L.ByteString -> Int -> M.STVector s Word8
-> HDRReader s Int
oldStyleRLE inputData initialIdx scanLine = inner initialIdx 0 0
where maxOutput = M.length scanLine
maxInput = fromIntegral $ L.length inputData
inner readIdx writeIdx _
| readIdx >= maxInput || writeIdx >= maxOutput = pure readIdx
inner readIdx writeIdx shift = do
let color@(RGBE r g b e) = unpackColor inputData readIdx
isRun = r == 1 && g == 1 && b == 1
if not isRun
then do
lift $ storeColor scanLine writeIdx color
inner (readIdx + 4) (writeIdx + 4) 0
else do
let count = fromIntegral e .<<. shift
lift $ forM_ [0 .. count] $ \i -> copyPrevColor scanLine (writeIdx + 4 * i)
inner (readIdx + 4) (writeIdx + 4 * count) (shift + 8)
newStyleRLE :: L.ByteString -> Int -> M.STVector s Word8
-> HDRReader s Int
newStyleRLE inputData initialIdx scanline = foldM inner initialIdx [0 .. 3]
where dataAt idx
| fromIntegral idx >= maxInput = throwE $ "Read index out of bound (" ++ show idx ++ ")"
| otherwise = pure $ L.index inputData (fromIntegral idx)
maxOutput = M.length scanline
maxInput = fromIntegral $ L.length inputData
stride = 4
strideSet count destIndex _ | endIndex > maxOutput + stride =
throwE $ "Out of bound HDR scanline " ++ show endIndex ++ " (max " ++ show maxOutput ++ ")"
where endIndex = destIndex + count * stride
strideSet count destIndex val = aux destIndex count
where aux i 0 = pure i
aux i c = do
lift $ (scanline .<-. i) val
aux (i + stride) (c - 1)
strideCopy _ count destIndex
| writeEndBound > maxOutput + stride = throwE "Out of bound HDR scanline"
where writeEndBound = destIndex + count * stride
strideCopy sourceIndex count destIndex = aux sourceIndex destIndex count
where aux _ j 0 = pure j
aux i j c = do
val <- dataAt i
lift $ (scanline .<-. j) val
aux (i + 1) (j + stride) (c - 1)
inner readIdx writeIdx
| readIdx >= maxInput || writeIdx >= maxOutput = pure readIdx
inner readIdx writeIdx = do
code <- dataAt readIdx
if code > 128
then do
let repeatCount = fromIntegral code .&. 0x7F
newVal <- dataAt $ readIdx + 1
endIndex <- strideSet repeatCount writeIdx newVal
inner (readIdx + 2) endIndex
else do
let iCode = fromIntegral code
endIndex <- strideCopy (readIdx + 1) iCode writeIdx
inner (readIdx + iCode + 1) endIndex
instance Binary RadianceHeader where
get = decodeHeader
put hdr = do
putByteString radianceFileSignature
putByteString $ BC.pack "FORMAT="
put $ radianceFormat hdr
let sizeString =
BC.pack $ "\n\n-Y " ++ show (radianceHeight hdr)
++ " +X " ++ show (radianceWidth hdr) ++ "\n"
putByteString sizeString
putLazyByteString $ radianceData hdr
decodeHeader :: Get RadianceHeader
decodeHeader = do
sig <- getByteString $ B.length radianceFileSignature
when (sig /= radianceFileSignature)
(fail "Invalid radiance file signature")
infos <- decodeInfos
let formatKey = BC.pack "FORMAT"
case partition (\(k,_) -> k /= formatKey) infos of
(_, []) -> fail "No radiance format specified"
(info, [(_, formatString)]) ->
case runGet get $ L.fromChunks [formatString] of
Left err -> fail err
Right format -> do
(n1, n2, b) <- (,,) <$> decodeNum
<*> decodeNum
<*> getRemainingBytes
return . RadianceHeader info format n1 n2 $ L.fromChunks [b]
_ -> fail "Multiple radiance format specified"
toFloat :: RGBE -> PixelRGBF
toFloat (RGBE r g b e) = PixelRGBF rf gf bf
where f = encodeFloat 1 $ fromIntegral e - (128 + 8)
rf = (fromIntegral r + 0.0) * f
gf = (fromIntegral g + 0.0) * f
bf = (fromIntegral b + 0.0) * f
encodeScanlineColor :: M.STVector s Word8
-> M.STVector s Word8
-> Int
-> ST s Int
encodeScanlineColor vec outVec outIdx = do
val <- vec `M.unsafeRead` 0
runLength 1 0 val 1 outIdx
where maxIndex = M.length vec
pushRun len val at = do
(outVec `M.unsafeWrite` at) $ fromIntegral $ len .|. 0x80
(outVec `M.unsafeWrite` (at + 1)) val
return $ at + 2
pushData start len at = do
(outVec `M.unsafeWrite` at) $ fromIntegral len
let first = start - len
end = start - 1
offset = at - first + 1
forM_ [first .. end] $ \i -> do
v <- vec `M.unsafeRead` i
(outVec `M.unsafeWrite` (offset + i)) v
return $ at + len + 1
runLength run cpy prev idx at | idx >= maxIndex =
case (run, cpy) of
(0, 0) -> pure at
(0, n) -> pushData idx n at
(n, 0) -> pushRun n prev at
(_, _) -> error "HDR - Run length algorithm is wrong"
runLength r@127 _ prev idx at = do
val <- vec `M.unsafeRead` idx
pushRun r prev at >>=
runLength 1 0 val (idx + 1)
runLength _ c@127 _ idx at = do
val <- vec `M.unsafeRead` idx
pushData idx c at >>=
runLength 1 0 val (idx + 1)
runLength n 0 prev idx at = do
val <- vec `M.unsafeRead` idx
case val == prev of
True -> runLength (n + 1) 0 prev (idx + 1) at
False | n < 4 -> runLength 0 (n + 1) val (idx + 1) at
False ->
pushRun n prev at >>=
runLength 1 0 val (idx + 1)
runLength 0 n prev idx at = do
val <- vec `M.unsafeRead` idx
if val /= prev
then runLength 0 (n + 1) val (idx + 1) at
else
pushData (idx - 1) (n - 1) at >>=
runLength (2 :: Int) 0 val (idx + 1)
runLength _ _ _ _ _ =
error "HDR RLE inconsistent state"
writeHDR :: FilePath -> Image PixelRGBF -> IO ()
writeHDR filename img = L.writeFile filename $ encodeHDR img
writeRLENewStyleHDR :: FilePath -> Image PixelRGBF -> IO ()
writeRLENewStyleHDR filename img =
L.writeFile filename $ encodeRLENewStyleHDR img
encodeHDR :: Image PixelRGBF -> L.ByteString
encodeHDR = encodeRawHDR
encodeRawHDR :: Image PixelRGBF -> L.ByteString
encodeRawHDR pic = encode descriptor
where
newImage = pixelMap rgbeInRgba pic
rgbeInRgba pixel = PixelRGBA8 r g b e
where RGBE r g b e = toRGBE pixel
descriptor = RadianceHeader
{ radianceInfos = []
, radianceFormat = FormatRGBE
, radianceHeight = imageHeight pic
, radianceWidth = imageWidth pic
, radianceData = L.fromChunks [toByteString $ imageData newImage]
}
encodeRLENewStyleHDR :: Image PixelRGBF -> L.ByteString
encodeRLENewStyleHDR pic = encode $ runST $ do
let w = imageWidth pic
h = imageHeight pic
scanLineR <- M.new w :: ST s (M.STVector s Word8)
scanLineG <- M.new w
scanLineB <- M.new w
scanLineE <- M.new w
encoded <-
forM [0 .. h - 1] $ \line -> do
buff <- M.new $ w * 4 + w `div` 127 + 2
let columner col | col >= w = return ()
columner col = do
let RGBE r g b e = toRGBE $ pixelAt pic col line
(scanLineR `M.unsafeWrite` col) r
(scanLineG `M.unsafeWrite` col) g
(scanLineB `M.unsafeWrite` col) b
(scanLineE `M.unsafeWrite` col) e
columner (col + 1)
columner 0
(buff `M.unsafeWrite` 0) 2
(buff `M.unsafeWrite` 1) 2
(buff `M.unsafeWrite` 2) $ fromIntegral ((w .>>. 8) .&. 0xFF)
(buff `M.unsafeWrite` 3) $ fromIntegral (w .&. 0xFF)
i1 <- encodeScanlineColor scanLineR buff 4
i2 <- encodeScanlineColor scanLineG buff i1
i3 <- encodeScanlineColor scanLineB buff i2
endIndex <- encodeScanlineColor scanLineE buff i3
(\v -> blitVector v 0 endIndex) <$> V.unsafeFreeze buff
pure RadianceHeader
{ radianceInfos = []
, radianceFormat = FormatRGBE
, radianceHeight = h
, radianceWidth = w
, radianceData = L.fromChunks encoded
}
decodeRadiancePicture :: RadianceHeader -> HDRReader s (MutableImage s PixelRGBF)
decodeRadiancePicture hdr = do
let width = abs $ radianceWidth hdr
height = abs $ radianceHeight hdr
packedData = radianceData hdr
scanLine <- lift $ M.new $ width * 4
resultBuffer <- lift $ M.new $ width * height * 3
let scanLineImage = MutableImage
{ mutableImageWidth = width
, mutableImageHeight = 1
, mutableImageData = scanLine
}
finalImage = MutableImage
{ mutableImageWidth = width
, mutableImageHeight = height
, mutableImageData = resultBuffer
}
let scanLineExtractor readIdx line = do
let color = unpackColor packedData readIdx
inner | isNewRunLengthMarker color = do
let calcSize = checkLineLength color
when (calcSize /= width)
(throwE "Invalid sanline size")
pure $ \idx -> newStyleRLE packedData (idx + 4)
| otherwise = pure $ oldStyleRLE packedData
f <- inner
newRead <- f readIdx scanLine
forM_ [0 .. width - 1] $ \i -> do
PixelRGBA8 r g b e <- lift $ readPixel scanLineImage i 0
lift $ writePixel finalImage i line . toFloat $ RGBE r g b e
return newRead
foldM_ scanLineExtractor 0 [0 .. height - 1]
return finalImage