home Funktionale Programmierung: Bilder als Funktionen Prof. Dr. Uwe Schmidt FH Wedel

Bilder als Funktionen

weiter

weiter

Aufgabe

Bildbearbeitung
als Trainingslager für Funktionen als Daten.
 
Bilder können wesentlich flexibler durch 2-stellige Funktionen als durch geschachtelte Listen oder Matrizen repräsentiert werden. Es wird in dieser Aufgabe der Einfachheit halber mit Graustufenbildern mit Farbwerten im Bereich 0.0..1.0 gearbeitet. Die Algorithmen können aber einfach auf Mehrkanalbilder erweitert werden.
Datentypen
für Bilder können folgende Gestalt besitzen:
 
type Lightness  = Double
 
type Channel    = Int -> Int -> Lightness
 
data Geo
    = Geo { width  :: ! Int
          , height :: ! Int
          }
 
data Image
    = Image { geo :: ! Geo
            , img :: Channel
            }
weiter
Helligkeitswerte
werden verändert, indem aus einem gegebenen Bild ein neues mit einer Pixel-Zugriffsfunktion aufgebaut wird, bei der nach der Anwendung der alten Funktion der Farbwert mit einer Helligkeitstransformation kombiniert wird.
weiter
Koordinatentransformationen
werden auf die gleiche Weise realisiert, nur dass die Koordinaten vor Anwendung der gegebenen Funktion transformiert werden.
weiter
Pixmap-Bilder
können effizent gespeichert werden, indem man ein (unboxed) Array nutzt und die Pixel-Funktion in dieses Feld verweist.
weiter
Auswertung
Die Kombination der Bilder kann mit sehr geringem Aufwand geschehen. Die Bildpunkte eines so erzeugten Bildes werden erst bei der Ausgabe in ein Dateiformat berechnet.
als .pgm Datei (256x256 Punkte).
Das .jpg Original
 
und die erste Manipulation
 
weiter
(280x392 Pixel) das für Rotationen und andere Koordinatentransformationen ein besserer Test ist, da es nicht quadratisch ist.
weiter
Vorgegebene Programmteile
In der Datei ImageTypesUndef.hs (bitte in ImageTypes.hs umbenennen) sind die Datentypen und primitive Funktionen, sowie das Lesen und Schreiben von PPM Dateien vorgegeben. Die eigentlichen Bildoperationen sind noch nicht implementiert, die Definitionen sind bis auf eine als undefined gekennzeichnet.

weiter

ImageTypes.hs.undefined
Die Quelle mit einigen Lücken

module ImageTypes
where
 
-- ----------------------------------------
 
type Lightness  = Double
 
type Channel    = Int -> Int -> Lightness
 
data Geo
    = Geo { width  :: ! Int
          , height :: ! Int
          }
      deriving (Eq)
 
data Image
    = Image { geo :: ! Geo
            , img :: Channel
            }
 
-- ----------------------------------------
 
-- mothers little helper
 
on2             :: (a -> b) ->
                   (c -> d -> a) -> (c -> d -> b)
f `on2` c       = \ x y -> f (c x y)
 
merge           :: (a -> b -> c) ->
                   (x -> y -> a) ->
                   (x -> y -> b) ->
                   (x -> y -> c)
 
merge op f1 f2  = \ x y -> f1 x y `op` f2 x y
 
-- ----------------------------------------
 
-- predefined Lightness values
 
dark            :: Lightness
dark            = 0.0
 
light           :: Lightness
light           = 1.0
 
grey            :: Lightness
grey            = 0.5
 
lightOrDark     :: Lightness -> Lightness
lightOrDark c
    | c <= 0.5  = 0.0
    | otherwise = 1.0
 
gammaLight      :: Lightness -> Lightness -> Lightness
gammaLight g x  = x ** (1/g)
 
invertLight     :: Lightness -> Lightness
invertLight     = (1.0 -)
 
reduceLight     :: Int -> Lightness -> Lightness
reduceLight n c = fromIntegral c' / fromIntegral (n - 1)
                where
                c' :: Int
                c' = floor (fromIntegral n * c)
 
-- ----------------------------------------
 
-- predefined channel values and combinators
 
uniChan         :: Lightness -> Channel
uniChan         = const . const
 
darkChan        :: Channel
darkChan        = uniChan dark
 
lightChan       :: Channel
lightChan       = uniChan light
 
mergeChan       :: Lightness -> Channel -> Channel -> Channel
mergeChan a c1 c2
                = \ x y -> a * c1 x y + (- a) * c2 x y
 
addChan         :: Channel -> Channel -> Channel
addChan         = mergeChan grey
 
addChans        :: [Channel] -> Channel
addChans cs     = \ x y ->
                  sum (map (\ c -> c x y) cs)
                  / fromIntegral (length cs)
 
rectangleChan   :: Geo -> Channel
rectangleChan (Geo w h)
                = \ x y ->
                  if 0 <= x && x < w && 0 <= y && y < h
                  then light
                  else dark
 
-- ----------------------------------------
--
-- predefined geometry values anf functions
 
instance Show Geo where
    show (Geo w h) = show w ++ "x" ++ show h
 
mkGeo   :: Int -> Int -> Geo
mkGeo w h
    | w > 0 && h > 0    = Geo w h
    | otherwise         = error $ "illegal geometry " ++ show (Geo w h)
 
maxGeo                                  :: Geo -> Geo -> Geo
(Geo w1 h1) `maxGeo` (Geo w2 h2)        = Geo (w1 `max` w2) (h1 `max` h2)
 
minGeo                                  :: Geo -> Geo -> Geo
(Geo w1 h1) `minGeo` (Geo w2 h2)        = Geo (w1 `min` w2) (h1 `min` h2)
 
flipGeo                 :: Geo -> Geo
flipGeo (Geo w h)       = Geo h w
 
scaleGeo                :: Int -> Int -> Geo -> Geo
scaleGeo n m (Geo w h)  = mkGeo (n * w) (m * h)
 
shiftGeo                :: Int -> Int -> Geo -> Geo
shiftGeo n m (Geo w h)  = mkGeo (w + n) (h + m)
 
-- ----------------------------------------
 
mkBitmap        :: Geo -> (Int -> Int -> Bool) -> Image
mkBitmap g f    = Image g ((fromIntegral . fromEnum) `on2` f)
 
 
-- ----------------------------------------
 
invert          :: Image -> Image
invert  (Image g c)
                = Image g $ \ x y -> 1 - c x y
 
gamma           :: Lightness -> Image -> Image
gamma           = undefined
 
bitmap          :: Image -> Image
bitmap          = undefined
 
reduceColors    :: Int -> Image -> Image
reduceColors n  = transLight (reduceLight n)
 
transLight :: (Lightness -> Lightness) ->
              Image -> Image
transLight tf (Image g c)
                = Image g $ tf `on2` c
 
-- ----------------------------------------
 
flipV           :: Image -> Image
flipV (Image g@(Geo w _h) c)
                = Image g $ \ x y -> c (w - x - 1) y
 
flipH           :: Image -> Image
flipH (Image g i)
                = undefined
 
rot90           :: Image -> Image
rot90 (Image g i)
                = undefined
 
rot180          :: Image -> Image
rot180          = undefined
 
rot270          :: Image -> Image
rot270          = undefined
 
-- das Bild um n Spalten nach rechts rotieren und
-- um m Zeilen nach unten
 
shiftRot        :: Int -> Int -> Image -> Image
shiftRot n m (Image g i)
                = undefined
 
-- das Bild n mal in der Wagerechten und
-- m mal in der Senkrechten wiederhohlen
-- (kacheln)
 
tile            :: Int -> Int -> Image -> Image
tile n m (Image g i)
                = undefined
 
-- das gleiche wie tile, nur mit abwechselnd gespiegelten
-- Bildern, so dass keine sichtbaren Kanten an den Bildgrenzen
-- entstehen
 
tileMirr        :: Int -> Int -> Image -> Image
tileMirr n m (Image g i)
                = undefined
 
-- Groesse halbieren
 
halfSize        :: Image -> Image
halfSize (Image (Geo w h) i)
                = undefined
 
-- vergroessern um die Faktoren n un m
 
scale           :: Int -> Int -> Image -> Image
scale n m (Image g i)
                = undefined
 
-- Zwei Bilder kombinieren, z.B. addieren
 
merge2          :: (Lightness -> Lightness -> Lightness) ->
                   Image -> Image -> Image
merge2 op (Image g1 i1) (Image g2 i2)
                = undefined
 
-- ----------------------------------------

weiter

Main.hs:
Ein Hauptprogramm zum Testen

module Main where
 
import           Data.Maybe
import           ImageTypes
import           ReadImage
import           ShowImage
import           System.Environment
import           System.IO
 
-- --------------------------------------
 
processImage :: FilePath -> FilePath
             -> (Image -> Image)
             -> IO ()
processImage src dst f
    = do ih <- openBinaryFile src ReadMode
         c  <- hGetContents ih
         let res = showImage True . f . readImage $ c
         oh <- openBinaryFile dst WriteMode
         hPutStr oh res
         hClose ih
         hClose oh
 
-- --------------------------------------
 
main    :: IO()
main
    = getArgs >>= main1
 
main1   :: [String] -> IO()
main1 [opr, inp, outp]
    = do
      processImage  inp outp (lookupOp opr)
    where
    lookupOp op
        = (fromMaybe id . lookup op) fctTab
 
    fctTab      :: [(String, Image -> Image)]
    fctTab
        = [ ("id"       , id            )
          , ("bitmap"   , bitmap        )
          , ("fliph"    , flipH         )
          , ("flipv"    , flipV         )
          , ("gamma.5"  , gamma 0.5     )
          , ("gamma2"   , gamma 2.0     )
          , ("halfsize" , halfSize      )
          , ("invert"   , invert        )
          , ("reduce3"  , reduceColors 3        )
          , ("rot90"    , rot90         )
          , ("rot180"   , rot180        )
          , ("rot270"   , rot270        )
          , ("double"   , scale 2 3     )
          , ("tile4"    , tile 2 2      )
          , ("tilemir4" , tileMirr 2 2  )
          , ("shiftrot" , shiftRot 20 20)
          , ("mult"      , \ i -> merge2 (*) i (flipV i))
          ]
main1 args
    = hPutStrLn stderr $ "usage: Uebung7 <fct> <input> <output> (got: " ++ show args ++ ")"
 
-- --------------------------------------

weiter

ReadImage.hs:
Einleseroutine für .pgm Bilder

module ReadImage
    (readImage)
where
 
import           Data.Array.Unboxed
import           Data.Word
import           ImageTypes
 
 
readImage       :: String -> Image
readImage       = readPNM . item
 
readPNM         :: (String, String) -> Image
readPNM ("P1", str)
    = mkBitmap (mkGeo w h) at
      where
      (ns, rest)= readRow 2 str
      [w, h]    = map read ns
      pixels    = fst . readRow len $ rest
      len       = w * h
 
      bits      :: [Int]
      bits      = map read pixels
 
      bitMx     :: UArray Int Bool
      bitMx     = listArray (0, len - 1) . map (== 0) $ bits
 
      at x y
          | 0 <= x && x < w &&
            0 <= y && y < h
                =  bitMx ! (x + w * y)
          | otherwise
              = False
 
readPNM ("P2", str)
    = Image (mkGeo w h) at
      where
      ([w', h', m'], rest)      = readRow 3 str
      w         = (read w')::Int
      h         = (read h')::Int
      m         = (read m')::Lightness
      l         = w * h
      pixels    = fst . readRow l $ rest
 
      bytes     :: [Word8]
      bytes     = map read pixels
 
      byteMx    :: UArray Int Word8
      byteMx    = listArray (0, l - 1) bytes
 
      at x y
          | 0 <= x && x < w &&
            0 <= y && y < h
                =  fromIntegral (byteMx ! (x + w * y)) / m
          | otherwise
              = dark
 
readPNM ("P3", str)
    = Image (mkGeo w h) at
      where
      ([w', h', m'], rest)      = readRow 3 str
      w         = (read w')::Int
      h         = (read h')::Int
      m         = (read m')::Lightness
      len       = w * h
      len3      = len * 3
      pixels    = fst . readRow len3 $ rest
 
      bytes     :: [Word8]
      bytes     = merge3 (map read pixels)
                  where
                  merge3 (r:g:b:xs) = (r + g + b) `div` 3 : merge3 xs
                  merge3 _          = []
 
      byteMx    :: UArray Int Word8
      byteMx    = listArray (0, len - 1) bytes
 
      at x y
          | 0 <= x && x < w &&
            0 <= y && y < h
                =  fromIntegral (byteMx ! (x + w * y)) / m
          | otherwise
              = dark
 
readPNM ("P5", str)
    = Image (mkGeo w h) at
      where
      ([w', h', m'], rest)      = readRow 3 str
      w         = (read w')::Int
      h         = (read h')::Int
      m         = (read m')::Lightness
      l         = w * h
 
      bytes     :: [Word8]
      bytes     = map (toEnum . fromEnum) .tail $ rest
 
      byteMx    :: UArray Int Word8
      byteMx    = listArray (0, l - 1) bytes
 
      at x y
          | 0 <= x && x < w &&
            0 <= y && y < h
                =  fromIntegral (byteMx ! (x + w * y)) / m
          | otherwise
              = dark
 
readPNM ("P6", str)
    = Image (mkGeo w h) at
      where
      ([w', h', m'], rest)      = readRow 3 str
      w         = (read w')::Int
      h         = (read h')::Int
      m         = (read m')::Lightness
      len       = w * h
 
      bytes     :: [Word8]
      bytes     = merge3 . map (toEnum . fromEnum) . tail $ rest
                  where
                  merge3 (r:g:b:xs) = (r + g + b) `div` 3 : merge3 xs
                  merge3 _          = []
 
      byteMx    :: UArray Int Word8
      byteMx    = listArray (0, len - 1) bytes
 
      at x y
          | 0 <= x && x < w &&
            0 <= y && y < h
                =  fromIntegral (byteMx ! (x + w * y)) / m
          | otherwise
              = dark
 
 
readPNM (fmt, _)
    = error $ "unsupported PNM format " ++ show fmt
 
-- ----------------------------------------
 
item    :: String -> (String, String)
item str
    = i1
      where
      i@(w,r) = head (lex str)
      i1 | head w == '#'
             = item r1
         | otherwise
             = i
         where
         r1 = ( drop 1 . snd . break (== '\n') . drop 1) r
 
readFct :: (String -> (a, String)) ->
           Int ->
           String -> ([a], String)
 
readFct _f 0 str
    = ([],str)
 
readFct f n str
    = (i:r, str2)
      where
      (i, str1) = f str
      (r, str2) = readFct f (n-1) str1
 
readRow         :: Int -> String -> ([String], String)
readRow         = readFct item
 
 
-- ----------------------------------------

weiter

ShowImage.hs:
Ausgaberoutine für .pgm Bilder

module ShowImage
    (showImage)
where
 
import           ImageTypes
 
-- ----------------------------------------
 
showImage       :: Bool -> Image -> String
showImage bin (Image (Geo w h) at)
    = imgType ++ imgGeo ++ "255\n" ++ imgData
    where
    imgGeo      = show w ++ " " ++ show h ++ "\n# Haskell PPM Tools\n"
 
    imgType
        | bin           = "P5\n"
        | otherwise     = "P2\n"
 
    imgData
        = pixToChar . pixToList $ at
 
    pixToChar
        | bin           = map toEnum
        | otherwise     = concatMap ((++ "\n") . show)
 
    pixToList f
        = [ toPix (f x y) | y <- [0..h-1], x <- [0..w-1] ]
        where
        toPix   :: Lightness -> Int
        toPix c = floor (c * 256.0) `min` 255 `max` 0
 
-- ----------------------------------------
 

weiter

Eine Build-Datei für cabal:

-- Initial Uebung7.cabal generated by cabal init. For further -- documentation, see http://haskell.org/cabal/users-guide/ name: Uebung7 version: 0.1.0.0 synopsis: Simple Image Manipulation build-type: Simple cabal-version: >=1.10 executable Uebung7 main-is: Main.hs other-modules: ImageTypes ReadImage ShowImage other-extensions: OverloadedStrings build-depends: base >=4 , array >=0.4 , bytestring >=0.10 default-language: Haskell2010 ghc-options: -Wall -funbox-strict-fields -fwarn-tabs

weiter

Vorgefertigte Module (mit ghc getestet)


Letzte Änderung: 27.03.2015
© Prof. Dr. Uwe Schmidt
Prof. Dr. Uwe Schmidt FH Wedel