packages feed

gegl-0.0.0.1: examples/example02.hs

{-# LANGUAGE RecordWildCards #-}

import GEGL
import qualified BABL as B
import qualified SDL
import qualified SDL.Raw as Raw

import Foreign.C.Types
import Foreign.Ptr
import Foreign.Storable

import Control.Concurrent
import Control.Monad
import Control.Monad.Loops

import Data.Maybe (fromMaybe)

import Debug.Trace

main :: IO ()
main = do
  SDL.initialize [SDL.InitVideo, SDL.InitTimer]
  traceIO "SDL init"
  window <- SDL.createWindow "GEGL example02: SDL2" SDL.defaultWindow
  surface <- SDL.getWindowSurface window
  gegl_init
  traceIO "GEGL init"
  let sdl_raw_format = B.PixelFormat B.RGBA B.CFu8
  sdl_format <- B.babl_format sdl_raw_format
  root <- gegl_node_new
  buffer <- gegl_buffer_new (Just $ GeglRectangle 0 0 0 0) =<<
    B.babl_format (B.PixelFormat B.RGBA B.CFfloat)
  background <- gegl_node_new_child root $ checkerboardOperation
    [ Property "color1" $ PropertyColor $ RGBA 0.4 0.4 0.4 1
    , Property "color2" $ PropertyColor $ RGBA 0.6 0.6 0.6 1
    ]
  over <- gegl_node_new_child root defaultOverOperation
  buffer_src <- gegl_node_new_child root $ bufferSourceOperation
    [ Property "buffer" $ PropertyBuffer buffer
    ]
  gegl_node_link background over
  gegl_node_connect_to buffer_src "output" over "aux"
  traceIO "Nodes constructed"
  SDL.V2 (CInt rw) (CInt rh) <- SDL.surfaceDimensions surface
  let (w, h) = (fromIntegral rw, fromIntegral rh)
  _ <- gegl_buffer_set_extent buffer $ GeglRectangle 0 0 w h
  pixels <- SDL.surfacePixels surface
  let SDL.Surface rawSurfacePtr _ = surface
  rawSurface <- peek rawSurfacePtr
  pixelFormat <- peek $ Raw.surfaceFormat rawSurface
  let pitch = fromIntegral (Raw.pixelFormatBytesPerPixel pixelFormat) * w :: Int
  traceIO $ "pitch is: " ++ show pitch
  ld <- return $ LoopData
    surface
    window
    (w, h)
    pitch
    pixels
    buffer
    over
    root
    sdl_format
    sdl_raw_format
  traceIO "first update"
  updateSurface (GeglRectangle 0 0 w h, ld)
  go <- newMVar True
  butDown <- newMVar False
  traceIO "begin iteration"
  whileM_ (fromMaybe False <$> tryReadMVar go) (loop ld butDown go)
  mapM_ gegl_node_drop [root, background, over]
  gegl_exit
  SDL.quit
  traceIO "bye bye"

data LoopData = LoopData
  { ldSurface    :: SDL.Surface
  , ldWindow     :: SDL.Window
  , ldSurfDim    :: (Int, Int)
  , ldPitch      :: Int
  , ldPixels     :: Ptr ()
  , ldBuffer     :: GeglBuffer
  , ldNode       :: GeglNode
  , ldGraph      :: GeglNode
  , ldFormat     :: B.BablFormatPtr
  , ldBablFormat :: B.PixelFormat
  }

type UpdateData = (GeglRectangle, LoopData)

updateSurface :: UpdateData -> IO ()
updateSurface (roi, LoopData{..}) = do
  -- traceIO "locking surface"
  SDL.lockSurface ldSurface
  let surfaceRect = uncurry (GeglRectangle 0 0) ldSurfDim
  sroi@(GeglRectangle ix iy iw ih) <- fromMaybe surfaceRect <$>
    gegl_rectangle_intersect surfaceRect roi
  let origin = ldPixels `plusPtr` (ix * B.babl_components_per_pixel ldBablFormat + iy * ldPitch)
  -- traceIO "blit"
  gegl_node_blit
    ldNode
    1
    sroi
    ldFormat
    origin
    ldPitch
    [GeglBlitDefault]
  -- traceIO "unlocking surface"
  SDL.unlockSurface ldSurface
  -- traceIO "update window"
  SDL.updateWindowSurface ldWindow

-- | Window update loop
loop :: LoopData -> MVar Bool -> MVar Bool -> IO ()
loop ld down go = do
  e <- SDL.waitEvent
  case SDL.eventPayload e of
    SDL.MouseButtonEvent dat ->
      when (SDL.mouseButtonEventButton dat == SDL.ButtonLeft) $
        if (SDL.mouseButtonEventMotion dat == SDL.Pressed)
          then do
            _ <- swapMVar down True
            let (SDL.P (SDL.V2 x y)) = SDL.mouseButtonEventPos dat
            drawCircle ld (fromIntegral x) (fromIntegral y)
          else do
            _ <- swapMVar down False
            return ()
    SDL.MouseMotionEvent dat -> do
      isDown <- readMVar down
      when isDown $ do
        let (SDL.P (SDL.V2 x y)) = SDL.mouseMotionEventPos dat
        drawCircle ld (fromIntegral x) (fromIntegral y)
    SDL.WindowClosedEvent _ -> do
      traceIO "seeya!"
      _ <- swapMVar go False
      return ()
    _ ->
      return ()

drawCircle :: LoopData -> Int -> Int -> IO ()
drawCircle ld@LoopData{..} x y = do
  let r = 20
      roi = GeglRectangle (x - r) (y - r) (2 * r) (2 * r)
  pixelMap ldBuffer roi (B.PixelFormat B.RGBA B.CFfloat) GeglAccessReadWrite GeglAbyssNone
    (\(Pixel px py pc) ->
      let dsqr = ((x - px) ^ 2) + ((y - py) ^ 2)
          (CVfloat (CFloat pr), CVfloat (CFloat pg), CVfloat (CFloat pb), CVfloat (CFloat pa)) = pc
          dist = (sqrt (fromIntegral dsqr :: Float))
      in if dsqr < r ^ 2
        then
          if dist < fromIntegral (r - 1)
            then
              Pixel px py
                ( CVfloat $ CFloat 0
                , CVfloat $ CFloat 0
                , CVfloat $ CFloat 0
                , CVfloat $ CFloat $ if pa < 1 then 1 else pa
                )
            else
              let alpha = fromIntegral r - dist
                  dst_a = pa
                  a = alpha + dst_a * (1 - alpha)
                  a_term = dst_a * (1 - alpha)
                  red = 0 * alpha + pr * a_term
                  gre = 0 * alpha + pg * a_term
                  blu = 0 * alpha + pb * a_term
              in
                Pixel px py
                  ( CVfloat $ CFloat $ red / a
                  , CVfloat $ CFloat $ gre / a
                  , CVfloat $ CFloat $ blu / a
                  , CVfloat $ CFloat $ if pa < alpha then alpha else pa
                  )
        else
          Pixel px py pc
      )
  updateSurface (roi, ld)