tttool-1.6: src/OidCode.hs
{-# LANGUAGE BangPatterns, TupleSections, FlexibleContexts #-}
module OidCode (genRawPixels, genRawPNG, DPI(..), PixelSize(..)) where
import Data.Word
import Data.Bits
import Data.Functor
import qualified Data.ByteString.Lazy as B
import Data.Monoid
import Control.Monad
import Codec.Picture
import Codec.Picture.Types
import Codec.Picture.Metadata
import Control.Monad.ST
import Control.Applicative
import Data.Vector.Storable.ByteString
import Utils
-- Image generation
checksum :: Word16 -> Word16
checksum dec = c3
where
c1 = (((dec >> 2) ^ (dec >> 8) ^ (dec >> 12) ^ (dec >> 14)) & 0x01) << 1
c2 = c1 .|. (((dec) ^ (dec >> 4) ^ (dec >> 6) ^ (dec >> 10)) & 0x01)
c3 = c2 ^ 0x02
(>>) = shiftR
(<<) = shiftL
(^) = xor
(&) = (.&.)
type DPI = Int
type PixelSize = Int
imageFromBlackPixels :: ColorConvertible PixelYA8 p => Int -> Int -> [(Int, Int)] -> Image p
imageFromBlackPixels width height pixels = runST $ do
i <- createMutableImage width height background
forM_ pixels $ \(x,y) -> do
writePixel i x y black
freezeImage i
where
black = promotePixel $ PixelYA8 minBound maxBound
background = promotePixel $ PixelYA8 maxBound minBound
-- | Renders a single OID Image, returns its dimensions and the black pixels therein
singeOidImage :: DPI -> PixelSize -> Word16 -> ((Int, Int), [(Int, Int)])
singeOidImage dpi ps code = ((width, height), pixels)
where
spacePerPoint = dpi `div2` 100
width = 4*spacePerPoint
height = 4*spacePerPoint
pixels = mconcat $ map position $
zip (flip (,) <$> [3,2,1] <*> [3,2,1])
[ value (quart n) | n <- [0..8] ] ++
[ (p, centeredDot) | p <- [(0,0), (1,0), (2,0), (3,0), (0,1), (0,3) ] ] ++
[ ((0,2), special) ]
quart 8 = checksum code
quart n = (code `div` 4^n) `mod` 4
dot = [(x,y) | x <- [1..ps], y <- [1..ps]]
centeredDot | xshift < 0 || yshift < 0 = error "Dots too large. Try a smaller pixel size"
| otherwise = at (xshift, yshift) dot
where xshift = (spacePerPoint - ps) `div` 2 - 1
yshift = (spacePerPoint - ps) `div` 2 - 1
-- | how many pixels to shift dots horizontally
s = dpi `div2` 600
-- | how many pixels to shift the special dot horizontally
ss = dpi `div2` 400
value 0 = at ( s, s) centeredDot
value 1 = at (-s, s) centeredDot
value 2 = at (-s,-s) centeredDot
value 3 = at ( s,-s) centeredDot
special = at (ss,0) centeredDot
position ((n,m), p) = at (n*spacePerPoint, m*spacePerPoint) p
-- Drawing combinators
at (x, y) = map (\(x', y') -> (x + x', y + y'))
-- integer division rounded up
x `div2` y = ((x-1) `div` y) + 1
oidImage :: ColorConvertible PixelYA8 p => Int -> Int -> DPI -> PixelSize -> Word16 -> Image p
oidImage w h dpi ps code =
imageFromBlackPixels w h tiledPixels
where
((cw,ch), pixels) = singeOidImage dpi ps code
tiledPixels =
[ (x',y')
| (x,y) <- pixels
, x' <- [x,x + cw..w-1]
, y' <- [y,y + ch..h-1]
]
-- Width and height in pixels
genRawPixels :: Int -> Int -> DPI -> PixelSize -> Word16 -> B.ByteString
genRawPixels w h dpi ps code =
-- All very shaky here, but it seems to work
B.fromStrict $
vectorToByteString $
imageData $
(oidImage w h dpi ps code :: Image PixelRGB8)
genRawPNG :: Int -> Int -> DPI -> PixelSize -> Word16 -> FilePath -> IO ()
genRawPNG w h dpi ps code filename =
B.writeFile filename $
encodePngWithMetadata metadata $
(oidImage w h dpi ps code :: Image PixelYA8)
where
metadata = mconcat
[ singleton DpiX (fromIntegral dpi)
, singleton DpiY (fromIntegral dpi)
, singleton Title $ "Tiptoi OID Code " ++ show code
, singleton Software $ "tttool " ++ tttoolVersion
]