qr-0.2.0.0: src/Data/QR/Layout.hs
module Data.QR.Layout where
import Data.Array
import Data.Bits
import Data.Function
import Data.List
import Data.Word
import Data.QR.Types
type Coord = (Int, Int)
data Module = Empty | Reserved | Light | Dark
deriving (Eq, Ord, Read, Show, Enum)
type Matrix = Array Coord Module
size :: Version -> Int
size v = v * 4 + 17
bitToModule :: Bit -> Module
bitToModule Z = Light
bitToModule O = Dark
maskModule :: Int -> Coord -> Bool
maskModule 0 (x, y) = (x + y) `mod` 2 == 0
maskModule 1 (_, y) = y `mod` 2 == 0
maskModule 2 (x, _) = x `mod` 3 == 0
maskModule 3 (x, y) = (x + y) `mod` 3 == 0
maskModule 4 (x, y) = (x `div` 3 + y `div` 2) `mod` 2 == 0
maskModule 5 (x, y) = let z = x * y in z `mod` 2 + z `mod` 3 == 0
maskModule 6 (x, y) = let z = x * y in (z `mod` 2 + z `mod` 3) `mod` 2 == 0
maskModule 7 (x, y) = ((x + y) `mod` 2 + (x * y) `mod` 3) `mod` 2 == 0
maskModule n _ = error $ "maskModule: invalid mask " ++ show n
neighbours :: Int -> Coord -> [Coord]
neighbours k (x, y) = concat
[ (,) <$> [x-k,x+k] <*> [y-k..y+k]
, (,) <$> [x-k+1..x+k-1] <*> [y-k,y+k] ]
alignmentPatterns :: Version -> [(Coord, Module)]
alignmentPatterns v = centers >>= pattern
where
cs = alignment v
sz = size v
centers = filter valid ((,) <$> cs <*> cs)
valid (x, y) = not $ or
[ x < 10 && y < 10
, x < 10 && y > sz - 11
, x > sz - 11 && y < 10 ]
pattern c = [(x, Dark) | x <- c : neighbours 2 c]
++ [(x, Light) | x <- neighbours 1 c]
finderPatterns :: Version -> [(Coord, Module)]
finderPatterns v = centers >>= pattern
where
sz = size v
centers = [(3, 3), (sz - 4, 3), (3, sz - 4)]
pattern c = [(x, Dark) | x <- c : neighbours 1 c ++ neighbours 3 c ]
++ [(x, Light) | x <- neighbours 2 c ++ neighbours 4 c
, valid x ]
valid (x, y) = x >= 0 && x < sz && y >= 0 && y < sz
timingPatterns :: Version -> [(Coord, Module)]
timingPatterns v = pattern ++ map swap pattern
where
sz = size v
pattern = [((x, 6), col x) | x <- [8 .. sz - 9]]
col x = if x `mod` 2 == 0 then Dark else Light
swap ((x, y), t) = ((y, x), t)
darkModule :: Version -> [(Coord, Module)]
darkModule v = [((8, 4 * v + 9), Dark)]
reservedAreas :: Version -> [Coord]
reservedAreas v = (8,8)
: [(8, y) | y <- [0 .. 5] ++ [7] ++ [sz - 7 .. sz - 1] ]
++ [(x, 8) | x <- [0 .. 5] ++ [7] ++ [sz - 8 .. sz - 1] ]
++ [c | v >= 7
, x <- [sz - 11 .. sz - 9]
, y <- [0 .. 5]
, c <- sym x y ]
where
sz = size v
sym x y = [(x, y), (y, x)]
reservedPatterns :: Version -> [(Coord, Module)]
reservedPatterns v = [(c, Reserved) | c <- reservedAreas v]
showModule :: Module -> Char
showModule Light = '-'
showModule Dark = '*'
showModule Empty = ' '
showModule Reserved = 'x'
showMatrix :: Matrix -> String
showMatrix m = unlines $ map row [0 .. h]
where
(_, (w, h)) = bounds m
row y = map (\x -> tile x y) [0 .. w]
tile x y = showModule $ m ! (x, y)
placement :: Matrix -> [Coord]
placement m = filter available cs
where
available c = m ! c == Empty
(_, (_, n)) = bounds m
line (x, r) =
[ c | y <- if r then [n, n-1 .. 0] else [0 .. n]
, c <- [(x, y), (x-1, y)] ]
cols = zip ([n, n-2 .. 8] ++ [5, 3, 1])
(cycle [True, False])
cs = cols >>= line
placeBits :: Matrix -> [Word8] -> [(Coord, Module)]
placeBits m = placeWords (placement m)
where
bits :: Word8 -> [Module]
bits w = map (\mask -> if w .&. mask == 0 then Light else Dark)
(take 8 (iterate (`shiftR` 1) 0x80))
placeWord :: [Coord] -> Word8 -> ([Coord], [(Coord, Module)])
placeWord cs w = case splitAt 8 cs of
(cs1, cs') -> (cs', zip cs1 (bits w))
placeWords :: [Coord] -> [Word8] -> [(Coord, Module)]
placeWords [] _ = []
placeWords cs [] = zip cs (repeat Light)
placeWords cs (w : ws') = case placeWord cs w of
(cs', ms) -> ms ++ placeWords cs' ws'
mkMatrix :: Version -> [(Coord, Module)] -> Matrix
mkMatrix v = accumArray max Empty ((0, 0), (sz-1, sz-1))
where
sz = size v
baseMatrix :: Version -> Matrix
baseMatrix v = mkMatrix v $ concat
[ finderPatterns v
, alignmentPatterns v
, timingPatterns v
, darkModule v
, reservedPatterns v ]
maskedMatrices :: Version -> [Word8] -> [Matrix]
maskedMatrices v ws = map (\i -> mat // allBits i) [0..7]
where
mat = baseMatrix v
ms = placeBits mat ws
maskedBits i = map (\(c, m) -> (c, if maskModule i c then invert m else m)) ms
reservedBits = [(c, Light) | c <- reservedAreas v]
allBits i = maskedBits i ++ reservedBits
invert Light = Dark
invert Dark = Light
invert m = m
formatBits :: Level -> Int -> Matrix -> [(Coord, Module)]
formatBits l k m = zip cs1 info ++ zip cs2 info
where
(_, (n,_)) = bounds m
cs1 = [(x, 8) | x <- [0,1,2,3,4,5,7,8]]
++ [(8, y) | y <- [7,5,4,3,2,1,0]]
cs2 = [(8, y) | y <- [n, n-1 .. n-7]]
++ [(x, 8) | x <- [n-6, n-5 .. n]]
info = map bitToModule $ formatInfo l k
versionBits :: Version -> Matrix -> [(Coord, Module)]
versionBits v m | v > 6 = zip cs1 info ++ zip cs2 info
| otherwise = []
where
(_, (n,_)) = bounds m
cs1 = [(x, y) | x <- [5,4,3,2,1,0]
, y <- [n-8,n-9,n-10]]
cs2 = [(x, y) | y <- [5,4,3,2,1,0]
, x <- [n-8,n-9,n-10]]
info = map bitToModule $ versionInfo v
layout :: Version -> Level -> [Word8] -> Matrix
layout v l ws = mat // (formatBits l k mat ++ versionBits v mat)
where
mats = zip [0..] (maskedMatrices v ws)
(k, mat) = minimumBy (compare `on` matScore) mats
matScore (_, m) = score m
rle :: Eq a => [a] -> [(a, Int)]
rle [] = []
rle (x : xs) = case span (== x) xs of
(xs1, xs') -> (x, length xs1 + 1) : rle xs'
score1 :: Matrix -> Int
score1 m = sum (map s rows) + sum (map s cols)
where
(_, (n, _)) = bounds m
s = sum . map s0 . rle
s0 (_, i)
| i < 5 = 0
| otherwise = i - 2
rows = [[m ! (x , y) | x <- [0..n]] | y <- [0..n]]
cols = [[m ! (x , y) | y <- [0..n]] | x <- [0..n]]
score2 :: Matrix -> Int
score2 m = 3 * length (filter solid (indices m))
where
solid (x, y) = and
[ x > 0, y > 0
, m ! (x - 1, y) == c
, m ! (x, y - 1) == c
, m ! (x - 1, y - 1) == c ]
where c = m ! (x, y)
score3 :: Matrix -> Int
score3 m = 40 * length found
where
(_, (n, _)) = bounds m
cs = range ((11,11),(n,n))
pt = [ Dark, Light, Dark, Dark, Dark, Light
, Dark, Light, Light, Light, Light ]
pts = [pt, reverse pt]
found = filter (matches (\x y -> m ! (x, y))) cs
++ filter (matches (\x y -> m ! (y, x))) cs
matches mat (x, y) = map (mat y) [x - 10 .. x] `elem` pts
score4 :: Matrix -> Int
score4 m = 10 * deviation
where
(_, (n, _)) = bounds m
darks = length (filter (== Dark) (elems m))
total = (n + 1) * (n + 1)
deviation = truncate (abs (fromIntegral darks * (20 :: Double)
/ fromIntegral total - 10))
score :: Matrix -> Int
score m = score1 m + score2 m + score3 m + score4 m
formatInfo :: Level -> Int -> [Bit]
formatInfo l k = toBinary 15 $ pats !! (levelIndex l * 8 + k)
where
pats :: [Int]
pats = [ 30660, 29427, 32170, 30877, 26159, 25368, 27713
, 26998, 21522, 20773, 24188, 23371, 17913, 16590
, 20375, 19104, 13663, 12392, 16177, 14854, 9396
, 8579, 11994, 11245, 5769, 5054, 7399, 6608, 1890
, 597, 3340, 2107]
versionInfo :: Version -> [Bit]
versionInfo v = toBinary 18 $ pats !! (v - 7)
where
pats :: [Int]
pats = [ 31892, 34236, 39577, 42195, 48118, 51042, 55367
, 58893, 63784, 68472, 70749, 76311, 79154, 84390
, 87683, 92361, 96236, 102084, 102881, 110507, 110734
, 117786, 119615, 126325, 127568, 133589, 136944
, 141498, 145311, 150283, 152622, 158308, 161089, 167017]
alignment :: Version -> [Int]
alignment 1 = []
alignment 2 = [6, 18]
alignment 3 = [6, 22]
alignment 4 = [6, 26]
alignment 5 = [6, 30]
alignment 6 = [6, 34]
alignment 7 = [6, 22, 38]
alignment 8 = [6, 24, 42]
alignment 9 = [6, 26, 46]
alignment 10 = [6, 28, 50]
alignment 11 = [6, 30, 54]
alignment 12 = [6, 32, 58]
alignment 13 = [6, 34, 62]
alignment 14 = [6, 26, 46, 66]
alignment 15 = [6, 26, 48, 70]
alignment 16 = [6, 26, 50, 74]
alignment 17 = [6, 30, 54, 78]
alignment 18 = [6, 30, 56, 82]
alignment 19 = [6, 30, 58, 86]
alignment 20 = [6, 34, 62, 90]
alignment 21 = [6, 28, 50, 72, 94]
alignment 22 = [6, 26, 50, 74, 98]
alignment 23 = [6, 30, 54, 78, 102]
alignment 24 = [6, 28, 54, 80, 106]
alignment 25 = [6, 32, 58, 84, 110]
alignment 26 = [6, 30, 58, 86, 114]
alignment 27 = [6, 34, 62, 90, 118]
alignment 28 = [6, 26, 50, 74, 98, 122]
alignment 29 = [6, 30, 54, 78, 102, 126]
alignment 30 = [6, 26, 52, 78, 104, 130]
alignment 31 = [6, 30, 56, 82, 108, 134]
alignment 32 = [6, 34, 60, 86, 112, 138]
alignment 33 = [6, 30, 58, 86, 114, 142]
alignment 34 = [6, 34, 62, 90, 118, 146]
alignment 35 = [6, 30, 54, 78, 102, 126, 150]
alignment 36 = [6, 24, 50, 76, 102, 128, 154]
alignment 37 = [6, 28, 54, 80, 106, 132, 158]
alignment 38 = [6, 32, 58, 84, 110, 136, 162]
alignment 39 = [6, 26, 54, 82, 110, 138, 166]
alignment 40 = [6, 30, 58, 86, 114, 142, 170]
alignment n = error $ "alignment: invalid version " ++ show n
{-# ANN module "HLint: ignore Use ++" #-}
{-# ANN module "HLint: ignore Use ||" #-}
{-# ANN showMatrix "HLint: ignore Avoid lambda" #-}
{-# ANN score3 "HLint: ignore Use curry" #-}