packages feed

camh-0.0.2: camh.hs

{-# LANGUAGE OverloadedStrings #-}

import Control.Applicative ((<|>))
import Control.Monad
import Control.Exception (handle)
import Data.Bits
import qualified Data.ByteString.Char8 as B
import Data.List (group)
import Data.Word (Word32)
import Foreign.Ptr
import Foreign.Storable
import Graphics.Imlib
import System.Console.GetOpt
import System.Console.Terminfo
import System.Environment (getArgs)
import System.Exit (exitSuccess)
import System.IO (stderr, hPutStrLn)

from24bitColor :: Word32 -> Int
from24bitColor w =
    let p = fromIntegral w
        r = p `shift` (-16) .&. 255
        g = p `shift`  (-8) .&. 255
        b = p               .&. 255
    in 16 + 36 * (r `quot` 43) + 6 * (g `quot` 43) + (b `quot` 43)

changeBgColor256 :: Int -> B.ByteString
changeBgColor256 col =
    "\x1b[48;5;" `B.append` B.pack (show col) `B.append` "m"

restoreBgColor :: B.ByteString
restoreBgColor = "\x1b[m"

colorLine :: [Word32] -> B.ByteString
colorLine row =
    let g        = group row
        e        = changeBgColor256 . from24bitColor
        s n      = B.replicate n ' '
        f (w:ws) = e w `B.append` s (fromIntegral $ 1 + length ws)
    in B.concat (map f g) `B.append` restoreBgColor

writeImage :: Int -> Int -> Ptr Word32 -> IO ()
writeImage w h p =
    forM_ [0 .. h-1] $ \j -> do
      row <- forM [0 .. w-1] $ \i -> peekElemOff p (w * j + i)
      B.putStrLn $ colorLine row

dealFile :: Config -> FilePath -> IO ()
dealFile conf filename =
  loadImageWithErrorReturn filename >>= \(image, err) ->
      if err == ImlibLoadErrorNone then
          do
            let tw = getWidth  conf
                th = getHeight conf

            contextSetImage image
            iw <- imageGetWidth
            ih <- imageGetHeight

            let ratio x y = fromIntegral x / fromIntegral y
                z  = ratio tw iw `min` ratio (2 * th) ih
                rw = floor $ fromIntegral iw * z
                rh = floor $ fromIntegral ih * z / 2

            createCroppedScaledImage 0 0 iw ih rw rh >>= contextSetImage

            createColorModifier >>= contextSetColorModifier

            let whenJust (Just a) f = f a
                whenJust Nothing  f = return ()
            whenJust (getGamma      conf) modifyColorModifierGamma
            whenJust (getBrightness conf) modifyColorModifierBrightness
            whenJust (getContrast   conf) modifyColorModifierContrast

            applyColorModifier
            freeColorModifier

            imageWithData $ writeImage rw rh
            freeImage

            contextSetImage image
            freeImage
      else
          hPutStrLn stderr $ "error " ++ show err ++ ": " ++ filename

getTermSize :: IO (Maybe (Int, Int))
getTermSize = handle (const $ return Nothing
                          :: SetupTermError -> IO (Maybe a)) $ do
  term <- setupTermFromEnv
  let size = do
        lines <- tiGetNum "lines"
        cols  <- tiGetNum "cols"
        return (lines, cols)

  return $ getCapability term size

data Opts = Help
          | Version
          | WidthChr   Int
          | HeightChr  Int
          | Gamma      Double
          | Brightness Double
          | Contrast   Double

options :: Config -> [OptDescr Opts]
options conf =
    let tw = getWidth  conf
        th = getHeight conf
    in [ Option "?" ["help"] (NoArg Help) "Show this message"
       , Option "v" ["version"] (NoArg Version) "Show version info"
       , Option "W" ["width-char"]
                    (ReqArg (WidthChr . read) "WIDTH")
                    "Set output width (in chars)"
       , Option "w" ["width-percent"]
                    (ReqArg (WidthChr . percent tw . read) "WIDTH")
                    "Set output width (in percent)"
       , Option "H" ["height-char"]
                    (ReqArg (HeightChr . read) "HEIGHT")
                    "Set output height (in chars)"
       , Option "h" ["height-percent"]
                    (ReqArg (HeightChr . percent th . read) "HEIGHT")
                    "Set output height (in percent)"
       , Option "g" ["gamma"]
                    (ReqArg (Gamma . read) "GAMMA")
                    "Apply gamma correction"
       , Option "b" ["blightness"]
                    (ReqArg (Brightness . read) "BLIGHTNESS")
                    "Apply blightness correction"
       , Option "c" ["contrast"]
                    (ReqArg (Contrast . read) "CONTRAST")
                    "Apply contrast correction" ]

percent :: Int -> Double -> Int
percent v p = floor $ fromIntegral v * p / 100

usage = usageInfo (version ++ "\n\nOptions:") $ options defaultConfig

version = "camh version 0.0.2"

data Config = Config { getWidth      :: Int
                     , getHeight     :: Int
                     , getGamma      :: Maybe Double
                     , getBrightness :: Maybe Double
                     , getContrast   :: Maybe Double }

defaultConfig = Config { getWidth      = 80
                       , getHeight     = 25
                       , getGamma      = Nothing
                       , getBrightness = Nothing
                       , getContrast   = Nothing }

getOpts :: Config -> [String] -> IO ([Opts], [String])
getOpts conf args =
    let th = getHeight conf
        tw = getWidth  conf
    in case getOpt Permute (options conf) args of
         (o, n, []  ) -> return (o, n)
         (_, _, errs) -> error $ concat errs ++ usage

reduceOpt :: Config -> Opts -> IO Config
reduceOpt conf opts =
    case opts of
      Help         -> putStr   usage   >> exitSuccess
      Version      -> putStrLn version >> exitSuccess
      WidthChr   w -> return conf { getWidth      = w }
      HeightChr  h -> return conf { getHeight     = h }
      Gamma      g -> return conf { getGamma      = Just g }
      Brightness b -> return conf { getBrightness = Just b }
      Contrast   c -> return conf { getContrast   = Just c }

main = do
  s <- getTermSize
  let Just (th, tw) = s <|> Just ( getHeight defaultConfig
                                 , getWidth  defaultConfig )
      conf = defaultConfig { getWidth = tw, getHeight = th-1 }

  (opts, args) <- getArgs >>= getOpts conf

  conf' <- foldM reduceOpt conf opts

  mapM_ (dealFile conf') args