{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE BangPatterns #-}

module Graphics.Image.Processing.Noise where

import Control.Monad (forM_)
import Control.Monad.ST
import System.Random

import Prelude as P hiding (subtract)
import Graphics.Image.Interface as I
import Graphics.Image
import Graphics.Image.Types as IP

-- | Helper function for generating a list of random co-ordinates.
randomCoords :: StdGen -> Int -> Int -> [(Int,Int)]
randomCoords a width height = (rnx1, rny1) : randomCoords g2 width height
  where
    (rnx1, g1) = randomR (0, width) a
    (rny1, g2) = randomR (0, height) g1

-- | Salt and pepper noise or impulse noise is a form of noise seen on images.
-- It is mainly caused by sharp and sudden disturbances in the image signal. 
-- 
-- 'saltAndPepper' generates this particular type of noise by introducing a sparse 
-- distribution of white and black pixels in the input image. The level or intensity
-- of the noise to be introduced is a parameter of this method and is scaled 
-- between 0 and 1, that is the input Noise Intensity has a domain : (0, 1).
-- 
-- <<images/yield.jpg>>   <<images/yield_snp.png>>
--
-- Usage :
-- 
-- >>> img <- readImageY VU "images/yield.jpg"
-- >>> input1 <- getLine
-- >>> g <- newStdGen
-- >>> let noiseLevel = (P.read input1 :: Float)
-- >>> let snpImage :: Image VU Y Double
-- >>>     snpImage = saltAndPepper img noiseLevel g
-- >>> writeImage "images/yield_snp.png" snpImage
--
saltAndPepper
  :: forall arr e cs . (MArray arr Y Double, IP.Array arr Y Double)
  => Image arr Y Double
  -> Float  -- ^ Noise Intensity -> Domain : (0, 1)
  -> StdGen -- ^ Instance of RandomGen
  -> Image arr Y Double
saltAndPepper image noiseLevel = accBin
 where
   widthMax, heightMax, noiseIntensity :: Int
   widthMax = ((rows image) - 1)
   heightMax = ((cols image) - 1)
   noiseIntensity = round (noiseLevel * (fromIntegral widthMax) * (fromIntegral heightMax))

   accBin :: StdGen -> Image arr Y Double
   accBin g = runST $
      do arr <- I.thaw image
         let coords = take (noiseIntensity + 1) (randomCoords g widthMax heightMax)
         forM_ coords $ \i -> do
           let a :: Int
               a = uncurry (+) i
           if (a `mod` 2 == 0)
              then do let px =  0
                      I.write arr i px
              else do let px = 1.0
                      I.write arr i px
         freeze arr