packages feed

tart-0.4: programs/Draw/Line.hs

{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE TemplateHaskell #-}
module Draw.Line
  ( plotLine
  )
where

import Data.Bits (shiftR)
import Control.Monad (when, forM_)
import Control.Monad.State.Lazy
import Lens.Micro.Platform

data LinePlot =
    LinePlot { _lpX :: Int
             , _lpY :: Int
             , _lpDx1 :: Int
             , _lpDy1 :: Int
             , _lpDx2 :: Int
             , _lpDy2 :: Int
             , _lpLongest :: Int
             , _lpShortest :: Int
             , _lpNumerator :: Int
             , _lpPixels :: [(Int, Int)]
             }

makeLenses ''LinePlot

-- From:
-- http://tech-algorithm.com/articles/drawing-line-using-bresenham-algorithm/
plotLine :: (Int, Int) -> (Int, Int) -> [(Int, Int)]
plotLine p0 p1 = finalSt^.lpPixels
    where
        finalSt = execState plot (LinePlot 0 0 0 0 0 0 0 0 0 [])
        plot = do
            let ((x0, y0), (x1, y1)) = (p0, p1)
                w = x1 - x0
                h = y1 - y0

            lpX .= x0
            lpY .= y0
            lpDx1 .= 0
            lpDy1 .= 0
            lpDx2 .= 0
            lpDy2 .= 0

            if | w<0 -> lpDx1 .= -1
               | w>0 -> lpDx1 .= 1
               | otherwise -> return ()

            if | h<0 -> lpDy1 .= -1
               | h>0 -> lpDy1 .= 1
               | otherwise -> return ()

            if | w<0 -> lpDx2 .= -1
               | w>0 -> lpDx2 .= 1
               | otherwise -> return ()

            lpLongest  .= abs w
            lpShortest .= abs h

            longest <- use lpLongest
            shortest <- use lpShortest

            when (not $ longest > shortest) $ do
                lpLongest .= abs h
                lpShortest .= abs w

                if | (h<0) -> lpDy2 .= -1
                   | (h>0) -> lpDy2 .= 1
                   | otherwise -> return ()

                lpDx2 .= 0

            longest' <- use lpLongest
            lpNumerator .= (longest' `shiftR` 1)

            forM_ [0..longest'] $ \_ -> do
                x <- use lpX
                y <- use lpY
                lpPixels %= ((x, y):)

                shortest' <- use lpShortest
                lpNumerator += shortest'
                numerator <- use lpNumerator
                longest'' <- use lpLongest
                if not $ numerator < longest''
                   then do
                       lpNumerator -= longest''
                       (lpX +=) =<< use lpDx1
                       (lpY +=) =<< use lpDy1
                   else do
                       (lpX +=) =<< use lpDx2
                       (lpY +=) =<< use lpDy2