diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Author name here (c) 2016
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Author name here nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,77 @@
+module Main where
+  import Data.Picture
+  import System.Environment
+
+  data Options = Options { file :: FilePath
+                         , output :: FilePath
+                         , argInvert :: Bool
+                         , argGrayscale :: Bool
+                         , argRotate :: Double
+                         , argFade :: Double
+                         , argContrast :: Double
+                         , argGamma :: Int
+                         , argBrightness :: Double
+                         }
+
+  opts = Options { file = ""
+                 , output = "output.png"
+                 , argInvert = False
+                 , argGrayscale = False
+                 , argRotate = 0
+                 , argFade = 100
+                 , argContrast = 0
+                 , argGamma = 1
+                 , argBrightness = 0
+                 }
+
+  main :: IO ()
+  main = do
+    args <- getArgs
+
+    if null args
+      then do
+        putStrLn "Usage: picedit <input> [OPTIONS]"
+        putStrLn "Options:"
+        putStrLn "  --contrast <n> - a number between -255 and 255"
+        putStrLn "  --brightness <n> - a number between -255 and 255"
+        putStrLn "  --gamma <n>"
+        putStrLn "  --fade <n> - a number between 0 and 100"
+        putStrLn "  --rotate <n> - rotate image by n degrees"
+        putStrLn "  --grayscale - turn the image grayscale"
+        putStrLn "  --invert - invert (negative) the image"
+        putStrLn "  --output <filename> - output name, defaults to output.png"
+      else do 
+        let options = parseArgs args opts
+
+        pic <- readPicture (file options)
+
+        case pic of
+          Left err -> print err
+          Right p -> do
+            let edited = rotate (argRotate options) Nothing 
+                      . fade (argFade options / 100)
+                      . contrast (argContrast options)
+                      . gamma (argGamma options)
+                      . brightness (argBrightness options)
+                      . conditionalFn grayscale (argGrayscale options) 
+                      . conditionalFn invert (argInvert options) $ p
+            writePicturePng "output.png" edited
+            
+        return ()
+
+    where
+      conditionalFn f True = f
+      conditionalFn f False = id
+
+  parseArgs :: [String] -> Options -> Options
+  parseArgs [] opts = opts
+  parseArgs ("--invert":rest) opts = parseArgs rest (opts { argInvert = True })
+  parseArgs ("--grayscale":rest) opts = parseArgs rest (opts { argGrayscale = True })
+  parseArgs ("--rotate":n:rest) opts = parseArgs rest (opts { argRotate = read n })
+  parseArgs ("--fade":n:rest) opts = parseArgs rest (opts { argFade = read n })
+  parseArgs ("--contrast":n:rest) opts = parseArgs rest (opts { argContrast = read n })
+  parseArgs ("--brightness":n:rest) opts = parseArgs rest (opts { argBrightness = read n })
+  parseArgs ("--gamma":n:rest) opts = parseArgs rest (opts { argGamma = read n })
+  parseArgs ("--output":n:rest) opts = parseArgs rest (opts { output = n })
+  parseArgs (name:rest) opts = parseArgs rest (opts { file = name })
+
diff --git a/picedit.cabal b/picedit.cabal
new file mode 100644
--- /dev/null
+++ b/picedit.cabal
@@ -0,0 +1,36 @@
+name:                picedit
+version:             0.1.0.0
+synopsis:            simple image manipulation functions
+description:         Simple set of functions for image manipulation: contrast, brightnesss, rotation, etc.
+homepage:            https://github.com/mdibaiee/picedit#readme
+license:             GPL-3
+license-file:        LICENSE
+author:              Mahdi Dibaiee
+maintainer:          mdibaiee@aol.com
+copyright:           2016 Mahdi Dibaiee
+category:            Image, Picture, Matrix
+build-type:          Simple
+-- extra-source-files:
+cabal-version:       >=1.10
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Data.Picture
+  build-depends:       base >= 4.7 && < 5,
+                       JuicyPixels >= 3.2.8 && < 3.3,
+                       hmatrix >= 0.17.0.2 && < 0.18,
+                       vector >= 0.11.0.0 && < 0.12
+  default-language:    Haskell2010
+
+executable picedit
+  hs-source-dirs:      app
+  main-is:             Main.hs
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
+  build-depends:       base
+                     , picedit
+                     , cli >= 0.1.2 && < 0.2
+  default-language:    Haskell2010
+
+source-repository head
+  type:     git
+  location: https://github.com/mdibaiee/picedit
diff --git a/src/Data/Picture.hs b/src/Data/Picture.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Picture.hs
@@ -0,0 +1,135 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE TypeFamilies #-}
+
+{-|
+Module      : Picture
+Description : manipulation functions
+Copyright   : (c) Mahdi Dibaiee, 2016
+License     : GPL-3
+Maintainer  : mdibaiee@aol.com
+Stability   : experimental
+Portability : POSIX
+-}
+module Data.Picture ( grayscale
+                    , readPicture
+                    , fromImage
+                    , toImage
+                    , writePicturePng
+                    , fade
+                    , rotate
+                    , contrast
+                    , brightness
+                    , gamma
+                    , invert
+                    )
+  where
+
+    import Codec.Picture
+    import Numeric.LinearAlgebra
+    import qualified Data.Vector.Storable as V
+    import System.IO
+    import Data.Maybe
+
+    -- | 'Picture' type is just a triple of color channel matrices: (R, G, B)
+    type Picture = (Matrix Double, Matrix Double, Matrix Double)
+
+    -- |Converts a JuicyPixel 'Image PixelRGB8' to 'Picture'
+    fromImage :: Image PixelRGB8 -> Picture
+    fromImage Image { imageWidth = w, imageHeight = h, imageData = vec } =
+     let [r, g, b] = map (reshape w . V.fromList . reverse) (snd $ V.foldl' gp (0, [[],[],[]]) (V.map fromIntegral vec))
+     in (r, g, b)
+     where
+       gp acc x =
+         case acc of
+           (0, [r, g, b]) -> (1, [x:r, g, b])
+           (1, [r, g, b]) -> (2, [r, x:g, b])
+           (2, [r, g, b]) -> (0, [r, g, x:b])
+
+    -- |Converts a 'Picture' to JuicyPixel 'Image PixelRGB8'
+    toImage :: Picture -> Image PixelRGB8
+    toImage (r, g, b) = 
+      let (fr, fg, fb) = (toList $ flatten r, toList $ flatten g, toList $ flatten b)
+          img = V.map (fromIntegral . floor) . V.concat $ zipWith3 (\a b c -> vector [a, b, c]) fr fg fb
+      in Image { imageWidth = cols r, imageHeight = rows r, imageData = img }
+
+
+    -- | Reads a 'Picture' from specified path
+    readPicture :: FilePath -> IO (Either String Picture)
+    readPicture path = do
+      img <- readImage path
+      return $ case img of 
+        Left err -> Left err
+        Right im -> Right $ fromImage (convertRGB8 im)
+
+    -- | Write the specified 'Picture' to a PNG file
+    writePicturePng :: FilePath -> Picture -> IO ()
+    writePicturePng path pic = writePng path (toImage pic)
+
+    -- | Turn the 'Picture' grayscale
+    grayscale :: Picture -> Picture
+    grayscale (r, g, b) =
+      let (fr, fg, fb) = (flatten r, flatten g, flatten b)
+          mean = reshape (cols r) $ V.map (/ 3) (fr + fg + fb)
+      in (mean, mean, mean)
+
+    -- | Fade the 'Picture' by a number between 0 and 1
+    fade :: Double -> Picture -> Picture
+    fade opacity (r, g, b) = (f r, f g, f b)
+      where
+        f = cmap (*opacity)
+
+    -- | Set contrast level of 'Picture', a number between -255 and 255
+    contrast :: Double -> Picture -> Picture
+    contrast level (r, g, b) = (f r, f g, f b)
+      where
+        cfactor = (259 * (level + 255)) / (255 * (259 - level))
+        f = cmap (\x -> pixelBound $ cfactor * (x - 128) + 128)
+
+    -- | Set brightness level of 'Picture', a number between -255 and 255
+    brightness :: Double -> Picture -> Picture
+    brightness level (r, g, b) = (f r, f g, f b)
+      where
+        f = cmap (pixelBound . (+level))
+
+    -- | Set gamma level of 'Picture'
+    gamma :: Int -> Picture -> Picture
+    gamma level (r, g, b) = (f r, f g, f b)
+      where
+        f = cmap (\x -> pixelBound $ 255 * (x / 255) ^ level)
+
+    -- | Inverts the 'Picture'
+    invert :: Picture -> Picture
+    invert (r, g, b) = (f r, f g, f b)
+      where
+        f = cmap (`subtract` 255)
+
+    {- | Rotate 'Picture' for the specified degrees, around the specified origin.
+     - If the origin is `Nothing`, rotates around the center
+     -}
+    rotate :: Double -> Maybe (Int, Int) -> Picture -> Picture
+    rotate deg orig (r, g, b) = (f r, f g, f b)
+      where
+        -- rotation in radians
+        rad = deg * pi / 180
+
+        -- rotation matrix (clockwise)
+        rm = fromLists [[cos rad, sin rad],
+                        [negate $ sin rad, cos rad]]
+
+        -- origin of rotation
+        (originX, originY) = if isJust orig then fromJust orig else (cols r `div` 2, rows r `div` 2)
+
+        -- all index pairs
+        indices = [vector [fromIntegral x - fromIntegral originX, fromIntegral y - fromIntegral originY] | y <- [0..rows r - 1], x <- [0..cols r - 1]]
+
+        -- rotate them using rotation matrix
+        rotatedIndices :: [[Int]] = map (\r -> toList . V.map (fromIntegral . round) $ rm #> r) indices
+
+        -- move them back to the origin
+        movedIndices = map (\[x, y] -> [x + originX, y + originY]) rotatedIndices
+
+        f m = reshape (cols m) $ fromList $ map (\[x, y] -> if y < 0 || y >= rows r || x < 0 || x >= cols r then 255 else m `atIndex` (y, x)) movedIndices
+
+    bound (l, u) x = max l $ min u x
+    pixelBound = bound (0, 255)
