explore-0.0.5.1: Main.hs
-- (c) 2009, Cetin Sert
-- | ExPloRe: Experimental Plot Reconstructor
module Main where
import System.Environment
import System.IO.Unsafe
import System.Exit
import Data.Word
import Foreign.Ptr
import Foreign.Storable
import Data.Array.Storable
import Control.Monad
import Codec.Image.PNG
type Name = String
-- | 32-bit color representation
data RGBA = RGBA Word8 Word8 Word8 Word8 -- ^ RED BLUE GREEN ALPHA
deriving (Show, Read, Eq)
-- storable instance for retrieveal
instance Storable RGBA where
sizeOf _ = sizeOf (0 :: Word8) * 4
alignment _ = 1
peek color = do
let byte :: Ptr Word8 = castPtr color
[r,g,b,a] <- mapM (byte @!) [0..3]
return $ RGBA r g b a
-- | colour matching behaviour
data Matching = S -- ^ strict
| TA Int -- ^ shared threshold for all color components
| TC Int Int Int Int -- ^ individual thresholds for color components
deriving (Show, Read, Eq)
-- | scan area
-- w = width of the PNG image
-- h = height of the PNG image
data ScanArea = F -- ^ search all the area (FULL) | 0 0 w h
| O Int Int -- ^ offset from LEFT TOP | L T w h
| M Int Int Int Int -- ^ offset from LEFT TOP RIGHT BOTTOM | L T (w-R) (h-B)
| A Int Int Int Int -- ^ area LEFT TOP RIGHT BOTTOM | L T R B
deriving (Show, Read, Eq)
-- | normalizes scan area representations
normalize :: Int -> Int -> ScanArea -> ScanArea
normalize w h F = A 0 0 w h
normalize w h (O l t) = A l t w h
normalize w h (M l t r b) = A l t (w-r) (h-b)
normalize _ _ area = area
-- | ExPloRe requires PNGs with transparency channel and no interlacing
checkAlpha :: PNGImage -> IO ()
checkAlpha img = case hasAlphaChannel img of
True -> return ()
_ -> putStrLn "no alpha channel going on!!!" >> exitWith (ExitFailure 1)
main :: IO ()
main = do
putStrLn $ "ExPloRe 0.0.5.1 : Experimental Plot Reconstructor"
args@(imgPath:legendPath:matching_:area_:step_:_) <- getArgs
-- initialize image
Right img <- loadPNGFile imgPath
let bitmap = imageData img
let (wu,hu) = dimensions img
let (w,h) = (fromIntegral wu, fromIntegral hu)
putStrLn $ "-------------------------------------------------------------------"
putStrLn $ ""
putStrLn $ "call : " ++ tail (filter (/= '"') $ concatMap ((' ':) . show) args)
putStrLn $ ""
putStrLn $ "image : " ++ imgPath
putStrLn $ "width : " ++ show w
putStrLn $ "height: " ++ show h
putStrLn $ ""
checkAlpha img
-- initialize scan
let area@(A l t r b) = normalize w h . read $ area_ :: ScanArea
let start = fromIntegral l :: Double
let step = read step_ :: Double
let (@#) = make2DIndexer w
let rows = [t..b]
let cols = takeWhile (< r) $ map (floor . (start +) . (step *)) [0..]
let icols = zip [1..] cols
let matching = read matching_ :: Matching
let (~=) = colorEq matching
putStrLn $ "area : " ++ show area
putStrLn $ "match : " ++ show matching
-- initialize lines
lines_ <- readFile legendPath
let lines = read lines_ :: [(Name,RGBA)]
putStrLn $ "legend: " ++ legendPath
putStrLn $ "lines : " ++ (show $ length lines)
putStrLn $ "step : " ++ show step
putStrLn $ ""
mapM_ (putStrLn . show) lines
-- scan bitmap
mapM_ (scan bitmap area icols rows (@#) (~=)) lines
-- | a very simple scanning algorithm that works only if no lines
-- occlude each other.
scan bitmap (A l t r b) icols rows (@#) (~=) (name,color) = do
putStrLn $ ""
putStrLn $ "-------------------------------------------------------------------"
putStrLn $ show color
putStrLn $ ""
putStrLn $ name
putStrLn $ ""
putStrLn $ "point\tx\tmid y\tmatches y"
putStrLn $ ""
withStorableArray bitmap $ \byte -> do
let pixel :: Ptr RGBA = castPtr byte
forM_ icols $ \(n,j) -> do
let matches = flip filter rows $ \i -> (pixel @# i) j ~= color
let m = mid matches - t
putStrLn $ case not . null $ matches of
True -> show n ++ "\t" ++ show j ++ "\t" ++ show m ++ "\t" ++ show matches
False -> show n ++ "\t" ++ show j ++ "\t \t[]"
-- | fuzzy color matching
colorEq :: Matching -> RGBA -> RGBA -> Bool
colorEq S = compEq 0 0 0 0
colorEq (TA v) = compEq v v v v
colorEq (TC r g b a) = compEq r g b a
-- | fuzzy color matching
compEq :: Int -> Int -> Int -> Int -> RGBA -> RGBA -> Bool
compEq tr tg tb ta (RGBA a b c d) (RGBA x y z w) = let fe = fuzzyEq in
fe tr a x && fe tg b y && fe tb c z && fe ta d w
-- | simple fuzzy equality
fuzzyEq :: forall b a. (Num b, Ord b, Integral a) => b -> a -> a -> Bool
fuzzyEq 0 = (==)
fuzzyEq t = \x y -> t > (abs $ fromIntegral x - fromIntegral y)
-- | gets the middle element of a list
mid :: forall a. [a] -> a
mid xs = xs !! (fromIntegral . floor . (/ 2) . fromIntegral . length) xs
(@!) :: Storable a => Ptr a -> Int -> IO a
(@!) = peekElemOff
make2DIndexer :: Storable a => Int -> Ptr a -> Int -> Int -> a
make2DIndexer w = \p j i -> unsafePerformIO $ p @! (i + j * w)