picedit (empty) → 0.1.0.0
raw patch · 5 files changed
+280/−0 lines, 5 filesdep +JuicyPixelsdep +basedep +clisetup-changed
Dependencies added: JuicyPixels, base, cli, hmatrix, picedit, vector
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- app/Main.hs +77/−0
- picedit.cabal +36/−0
- src/Data/Picture.hs +135/−0
+ LICENSE view
@@ -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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -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 })+
+ picedit.cabal view
@@ -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
+ src/Data/Picture.hs view
@@ -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)