module Codec.Binary.QRCode.Placement where
import Codec.Binary.QRCode.Matrix
import Codec.Binary.QRCode.Spec
import Codec.Binary.QRCode.Utils
import Data.Tuple
import Data.List
import qualified Data.Set as S
import Control.Applicative
import Control.Monad
import Control.Monad.Reader
import Data.Array.IArray
type Coords = [(Int,Int)]
type ReaderCoords = ReaderQR Coords
type MatrixPart = [((Int,Int),Module)]
intertwine :: [a] -> [a] -> [a]
intertwine (x:xs) (y:ys) = x:y:intertwine xs ys
intertwine (x:xs) [] = x:intertwine xs []
intertwine [] (y:ys) = y:intertwine [] ys
intertwine [] [] = []
mkMatrix :: Int -> [((Int, Int), Module)] -> Matrix
mkMatrix width = QRM . array ((0,0),(width1,width1))
mkSymbolWithFunctionPatterns :: Version -> Modules -> Matrix
mkSymbolWithFunctionPatterns ver mods = mkMatrix width combinedMods
where
width = qrNumModulesPerSide $ qrGetInfo ver
combinedMods = flip runReader ver $ do
let genAllLight = liftM $ flip zip $ repeat Light
formats <- formatInfoRegions
versions <- versionInfoRegions
finderTL <- genAllLight finderPatternTopLeft
finderBL <- genAllLight finderPatternBottomLeft
finderTR <- genAllLight finderPatternTopRight
timingH <- genAllLight timingPatternHorizontal
timingV <- genAllLight timingPatternVertical
hardcoded <- hardcodedDarkModule
path <- mkPath
let dat = zip path mods
finders = finderTL ++ finderBL ++ finderTR
timings = timingH ++ timingV
hc = zip hardcoded (repeat Light)
formats' = zip formats (repeat Light)
versions' = zip versions (repeat Light)
return $ dat ++ finders ++ timings ++ hc ++ formats' ++ versions'
mkSymbol :: Coords -> Version -> Modules -> Matrix
mkSymbol path ver mods = mkMatrix width combinedMods
where
width = qrNumModulesPerSide $ qrGetInfo ver
combinedMods = flip runReader ver $ do
formats <- formatInfoRegions
versions <- versionInfoRegions
finderTL <- genFinderPatternTopLeft finderPatternTopLeft
finderBL <- genFinderPatternBottomLeft finderPatternBottomLeft
finderTR <- genFinderPatternTopRight finderPatternTopRight
timingH <- genTimingPattern timingPatternHorizontal
timingV <- genTimingPattern timingPatternVertical
alignments <- liftM genAlignmentPatterns alignmentCoords
hardcoded <- hardcodedDarkModule
let dat = zip path mods
finders = finderTL ++ finderBL ++ finderTR
timings = timingH ++ timingV
hc = zip hardcoded [Dark]
formats' = zip formats $ repeat Light
versions' = zip versions $ repeat Light
return $ dat ++ finders ++ timings ++ hc ++ formats' ++ alignments ++ versions'
qrmApplyInfo :: ReaderQR Coords -> ReaderQR Coords -> Version -> Matrix-> BitStream -> Matrix
qrmApplyInfo region1 region2 ver mat bitstream = qrmOverlay mat overlays
where
mods = toModules bitstream
overlays = flip runReader ver $ do
path1 <- region1
path2 <- region2
return $ zip path1 mods ++ zip path2 mods
qrmApplyFormatInfo :: Version -> Matrix -> BitStream -> Matrix
qrmApplyFormatInfo = qrmApplyInfo formatInfoRegionHorizontal formatInfoRegionVertical
qrmApplyVersionInfo :: Version -> Matrix -> BitStream -> Matrix
qrmApplyVersionInfo = qrmApplyInfo versionInfoRegionBottomLeft versionInfoRegionTopRight
newtype MyChar = MC Char deriving (Eq, Ord, Enum)
instance Show MyChar where
show (MC '\n') = "\n"
show (MC c) = [c]
mkDebugPath :: Version -> Array (Int,Int) MyChar
mkDebugPath ver = base // trail
where
width = qrNumModulesPerSide $ qrGetInfo ver
ix = ((0,0),(width1,width1))
blanks = repeat (MC ' ')
trail = zip (runReader mkPath ver) $ cycle $ reverse [(MC '0')..(MC '7')]
base = listArray ix blanks
mkPath :: ReaderQR Coords
mkPath = mkRawPath `subtractPatterns` allFunctionPatterns
mkRawPath :: ReaderQR Coords
mkRawPath = do
ver <- ask
time <- natural 7
let width = qrGetWidth ver
upRowPair = concatMap (replicate 2) [0..(width1)]
downRowPair = reverse upRowPair
mkCols = concat . concatMap (replicate width) . chunksOf 2
mkRows = concat . cycle
cols1 = mkCols [0..(time1)]
rows1 = mkRows [upRowPair, downRowPair]
cols2 = mkCols [(time+1)..(width1)]
rows2 = mkRows [downRowPair, upRowPair]
return $ filter ((/=time) . fst) $ zip rows1 cols1 ++ zip rows2 cols2
genTimingPattern :: Monad m => m [a] -> m [(a, Module)]
genTimingPattern path = do
p <- path
return $ zip p (cycle [Dark, Light])
joinPatterns :: Applicative f => f [a] -> f [a] -> f [a]
joinPatterns = (<*>) . ((++) <$>)
fastDiff :: (Ord a) => [a] -> [a] -> [a]
xs `fastDiff` ys = filter (flip S.notMember ys') xs
where ys' = S.fromList ys
subtractPatterns :: (Applicative f, Ord a) => f [a] -> f [a] -> f [a]
subtractPatterns = (<*>) . (fastDiff <$>)
(/+/) :: Applicative f => f [a] -> f [a] -> f [a]
(/+/) = joinPatterns
allFunctionPatterns :: ReaderQR Coords
allFunctionPatterns = timingPatterns /+/ finderPatterns
/+/ formatInfoRegions
/+/ hardcodedDarkModule
/+/ alignmentCoords
/+/ versionInfoRegions
natural :: Int -> ReaderQR Int
natural n = do
ver <- ask
return $ qrGetWidth ver n
timingPatterns :: ReaderQR Coords
timingPatterns = timingPatternHorizontal /+/ timingPatternVertical
timingPatternHorizontal :: ReaderQR Coords
timingPatternHorizontal = do
ver <- ask
row <- natural 7
let width = qrGetWidth ver
v = [(row,y) | y <- [0..(width1)]]
finder <- finderPatterns
return $ v \\ finder
timingPatternVertical :: ReaderQR Coords
timingPatternVertical = map swap `fmap` timingPatternHorizontal
versionInfoRegion' :: (Num a, Enum a) => (a -> Int -> b) -> ReaderQR [b]
versionInfoRegion' f = do
ver@(Version v) <- ask
a <- natural 6
let width = qrGetWidth ver
rows = cycle [8..10]
cols = concatMap (replicate 3) [a..width1]
return $ do
guard $ v >= 7
zipWith f rows cols
versionInfoRegionBottomLeft :: (Num a, Enum a) => ReaderQR [(a, Int)]
versionInfoRegionBottomLeft = versionInfoRegion' (,)
versionInfoRegionTopRight :: (Num a, Enum a) => ReaderQR [(Int, a)]
versionInfoRegionTopRight = versionInfoRegion' $ flip (,)
versionInfoRegions :: ReaderQR [(Int, Int)]
versionInfoRegions = versionInfoRegionBottomLeft /+/ versionInfoRegionTopRight
hardcodedDarkModule :: Num t => ReaderQR [(t, Int)]
hardcodedDarkModule = do
col <- natural 9
return [(7,col)]
formatInfoRegions :: ReaderQR [(Int, Int)]
formatInfoRegions = formatInfoRegionHorizontal /+/ formatInfoRegionVertical
formatInfoRegionHorizontal :: ReaderQR [(Int, Int)]
formatInfoRegionHorizontal = do
ver <- ask
row <- natural 9
c' <- natural 8
let width = qrGetWidth ver
return $ reverse [(row,col) | col <- [0..7] ++ [c'] ++ [c'+2..width1]]
formatInfoRegionVertical :: ReaderQR [(Int, Int)]
formatInfoRegionVertical = do
col <- natural 9
let a = 0
b = 6
c <- natural 9
d <- natural 8
e <- natural 6
f <- natural 1
return [(row,col) | row <- [a..b] ++ [c..d] ++ [e..f]]
finderPatterns :: ReaderQR [(Int, Int)]
finderPatterns = finderPatternTopLeft /+/ finderPatternBottomLeft /+/ finderPatternTopRight
finderPatternTopLeft :: ReaderQR Coords
finderPatternTopLeft = do
ver <- ask
r' <- natural 8
let width = qrGetWidth ver
return [(row,col) | let vals = [r'..(width1)], row <- vals, col <- vals]
finderPatternTopRight :: ReaderQR Coords
finderPatternTopRight = do
ver <- ask
r' <- natural 8
let width = qrGetWidth ver
return [(row,col) | row <- [r'..width1], col <- [0..7]]
finderPatternBottomLeft :: ReaderQR Coords
finderPatternBottomLeft = do
ver <- ask
let width = qrGetWidth ver
c' <- natural 8
return [(row,col) | row <- [0..7], col <- [c'..width1]]
genFinderPattern :: Monad m => Modules -> Modules -> Modules -> Modules -> m [a] -> m [(a, Module)]
genFinderPattern prepend append lpadCol rpadCol path = do
p <- path
let pat = prepend ++ rawFinderPattern ++ append
return $ zip p pat
where
rawFinderPattern = r1 ++ r2 ++ r3 ++ r3 ++ r3 ++ r2 ++ r1
r1 = rpadCol ++ replicate 7 Dark ++ lpadCol
r2 = rpadCol ++ [Dark, Light, Light, Light, Light, Light, Dark] ++ lpadCol
r3 = rpadCol ++ [Dark, Light, Dark, Dark, Dark, Light, Dark] ++ lpadCol
emptyFinderPatternRow :: Modules
emptyFinderPatternRow = replicate 8 Light
genFinderPatternTopLeft :: Monad m => m [a] -> m [(a, Module)]
genFinderPatternTopLeft = genFinderPattern emptyFinderPatternRow [] [] [Light]
genFinderPatternTopRight :: Monad m => m [a] -> m [(a, Module)]
genFinderPatternTopRight = genFinderPattern emptyFinderPatternRow [] [Light] []
genFinderPatternBottomLeft :: Monad m => m [a] -> m [(a, Module)]
genFinderPatternBottomLeft = genFinderPattern [] emptyFinderPatternRow [] [Light]
genAlignmentPatterns :: [a] -> [(a, Module)]
genAlignmentPatterns = flip zip (cycle patternMods)
where
patternMods = [ Dark, Dark, Dark, Dark, Dark
, Dark, Light, Light, Light, Dark
, Dark, Light, Dark, Light, Dark
, Dark, Light, Light, Light, Dark
, Dark, Dark, Dark, Dark, Dark
]
overlapsFinder :: (Int,Int) -> ReaderQR Bool
overlapsFinder (r,c) = do
a <- natural 8
return $ (r >= a && c >= a) || (r <= 8 && c >= a) || (r >= a && c <= 8)
alignmentCoords :: MonadReader Version m => m [(Int, Int)]
alignmentCoords = do
ver <- ask
let centers = qrAlignmentCenters ver
validCenters = [(x,y) | x <- centers, y <- centers, let inFinder = runReader (overlapsFinder (x,y)) ver, not inFinder]
mkPat (r,c) = [(r',c') | c' <- [c2..c+2], r' <- [r2..r+2]]
pats = concatMap mkPat validCenters
return pats