imagefilters-0.1: Graphics/Filters/GD.hs
module Graphics.Filters.GD
(
-- * Filters
brightness
, colorize
, contrast
, gaussianBlur
, grayscale
, edgeDetect
, emboss
, meanRemoval
, negative
, smoothing
-- * Pixel transformation functions
, pixelTransform
, convolute
) where
import Graphics.GD
import Graphics.Filters.Util
import Control.Monad (mapM_,foldM)
{- |
Performs the supplied transform function on every pixel of the image.
The transform function should take as it's argument a quadruple of Ints (RGBA) and returns a new RGBA quadruple
which is will be the new RGBA values of the the pixel.
For example, the 'colorize' filter's inner workings are implemented with this as the transform function:
@
(\\(r,g,b,a) -> let
nr = clamp 0 255 (r+ar)
ng = clamp 0 255 (g+ag)
nb = clamp 0 255 (b+ab)
na = clamp 0 127 (a+aa)
in (nr,ng,nb,na))
@
-}
pixelTransform :: Image
-> ( RGBA -> RGBA ) -- ^ Transform function to be performed on each pixel
-> IO ()
pixelTransform img fx = do
(width,height) <- imageSize img
mapM_ (\y ->
mapM_ (\x -> do
curr <- getPixel (x,y) img
let
(r,g,b,a) = toRGBA curr
(nr,ng,nb,na) = fx (r,g,b,a)
setPixel (x,y) (rgba nr ng nb na) img
) [0..(width-1)]
) [0..(height-1)]
return ()
{- |
Performs the convolution matrix on each pixel of the original image.
After the matrix has been applied, the resulting RGBA value has each of it's elements divided by the Divisor argument
and then the Offset argument is added to each element
For example, the 'emboss' filter is implemented with the following convolution:
> emboss img = convolute img [[1.5,0.0,0.0],[0.0,0.0,0.0],[0.0,0.0,-1.5]] 1 127
-}
convolute :: Image
-> [[Float]] -- ^ Convolution matrix
-> Float -- ^ Divisor
-> Float -- ^ Offset
-> IO ()
convolute img matrix fdiv offset = do
(width,height) <- imageSize img
imgCpy <- copyImage img
mapM_ (\y ->
mapM_ (\x -> convoluteImage img imgCpy matrix fdiv offset x y) [0..(width-1)]
) [0..(height-1)]
return ()
convoluteImage :: Image -> Image -> [[Float]] -> Float -> Float -> Int -> Int -> IO ()
convoluteImage img imgCpy matrix fdiv offset x y = do
(nr,ng,nb,na) <- foldM (\(or,og,ob,oa) j -> do
let yy = min (max (y-(1+j)) 0) (max (y-1) 0)
(pr,pg,pb,pa) <- foldM (\(ar,ag,ab,aa) k -> do
let xx = min (max (x-(1+k)) 0) (max (x-1) 0)
curr <- getPixel (xx,yy) imgCpy
let (r,g,b,a) = toRGBA curr
return (ar + fromIntegral r * ((matrix!!j)!!k)
,ag + fromIntegral g * ((matrix!!j)!!k)
,ab + fromIntegral b * ((matrix!!j)!!k)
,fromIntegral a)
) (or,og,ob,oa) [0.. (length (matrix!!j) - 1)]
return (pr,pg,pb,pa)
) ((0.0,0.0,0.0,0.0) :: (Float,Float,Float,Float)) [0.. (length matrix - 1)]
let
new_r = clamp 0 255 . truncate $ (nr/fdiv)+offset
new_g = clamp 0 255 . truncate $ (ng/fdiv)+offset
new_b = clamp 0 255 . truncate $ (nb/fdiv)+offset
setPixel (x,y) (rgba new_r new_g new_b (truncate na)) img
{- |
Applies the supplied color transformation to the image.
The range of the passed RGB values are -255 to +255, and the range of the A value is -127 to +127.
-}
colorize :: Image -> RGBA -> IO ()
colorize img (ar,ag,ab,aa) =
if or [ar > 255, ar < (-255), ag > 255, ag < (-255), ab > 255, ab < (-255), aa > 127, aa < (-127)] then
error "Argument out of bounds. Colorize expects the color argument's RGB components to be between -255 and +255\
\ and it's A component between -127 and +127"
else
pixelTransform img (\(r,g,b,a) -> let
nr = clamp 0 255 (r+ar)
ng = clamp 0 255 (g+ag)
nb = clamp 0 255 (b+ab)
na = clamp 0 127 (a+aa)
in (nr,ng,nb,na))
{- |
Inverts the image's color.
-}
negative :: Image -> IO ()
negative img =
pixelTransform img (\(r,g,b,a) -> let
nr = abs 255-r
ng = abs 255-g
nb = abs 255-b
in (nr,ng,nb,a))
{- |
Converts an image to grayscale
-}
grayscale :: Image -> IO ()
grayscale img =
pixelTransform img (\(r,g,b,a) -> let
newcol = truncate $ 0.299 * fromIntegral r + 0.587 * fromIntegral g + 0.114 * fromIntegral b
in (newcol, newcol, newcol, a))
{- |
Applies the supplied contrast adjustment to the image.
The range of the contrast argument is -100 to +100, with
-100 being maximum contrast, and +100 being minimum contrast
-}
contrast :: Image -> Int -> IO ()
contrast img contVal =
if or [contVal > 100, contVal < (-100)] then
error "Argument out of bounds. Contrast expects the contrast argument to be between -100 and +100"
else
pixelTransform img (\(r,g,b,a) -> let
contr = (**2) $ (100.0 - fromIntegral contVal )/100.0
(ur,ug,ub,_) = normalizeRGBA (r,g,b,a)
nr = clamp 0 255 $ (*) 255 . (+) 0.5 . (*) contr $ ur-0.5
ng = clamp 0 255 $ (*) 255 . (+) 0.5 . (*) contr $ ug-0.5
nb = clamp 0 255 $ (*) 255 . (+) 0.5 . (*) contr $ ub-0.5
in (truncate nr, truncate ng, truncate nb, a))
{- |
Applies the supplied brightness adjustment to the image.
The range of the brightness argument is -255 to +255
-}
brightness :: Image -> Int -> IO ()
brightness img brightVal =
if or [brightVal > 255, brightVal < (-255)] then
error "Argument out of bounds. Brightness expects the brightness argument to be between -255 and +255"
else
colorize img (brightVal,brightVal,brightVal,0)
{- |
Applies Gaussian blur to the image
-}
gaussianBlur :: Image -> IO ()
gaussianBlur img = convolute img [[1.0,2.0,1.0],[2.0,4.0,2.0],[1.0,2.0,1.0]] 16 0
{- |
Applies an Emboss effect to the image
-}
emboss :: Image -> IO ()
emboss img = convolute img [[1.5,0.0,0.0],[0.0,0.0,0.0],[0.0,0.0,-1.5]] 1 127
{- |
Applies Edge Detection to the image
-}
edgeDetect :: Image -> IO ()
edgeDetect img = convolute img [[-1.0,0.0,-1.0],[0.0,4.0,0.0],[-1.0,0.0,-1.0]] 1 127
{- |
Applies a Mean Removal effect to the image
-}
meanRemoval :: Image -> IO ()
meanRemoval img = convolute img [[-1.0,-1.0,-1.0],[-1.0,9.0,-1.0],[-1.0,-1.0,-1.0]] 1 0
{- |
Applies weighted Smoothing to the image. The smoothing amount is technically unbounded,
but larger values produce a less noticeable result
-}
smoothing :: Image -> Float -> IO ()
smoothing img weighting= convolute img [[1.0,1.0,1.0],[1.0,weighting,1.0],[1.0,1.0,1.0]] (weighting+8.0) 0