4
\$\begingroup\$

I have a function which takes GTK Image (It is RGB) and returns a representation of this image as list of grey levels.

This is my code:

grayscaleFromRGB :: Image -> IO [Word8]
grayscaleFromRGB img = do
 imgType <- img `get` imageStorageType
 if imgType == ImageEmpty
 then
 return (replicate 256 0)
 else do
 pixbuf <- imageGetPixbuf img
 pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
 rgbList <- getElems pixels
 nChannels <- pixbufGetNChannels pixbuf
 return (map convert $ chunksOf nChannels rgbList)
 where
 convert channels = round $ foldl (+) 0 $ zipWith (*) [0.3, 0.59, 0.11] $ map fromIntegral channels

But I have a problem: this function uses too much memory and CPU time. For example for an image with a size of 500x500 it takes nearly 100MB of memory. Opening an image with a size of 2000x1000 it use more than 1GB of memory and it takes a lot of time.

How can it be written in an efficient way? What is the reason for this behavior?

Update:

Whole code which use this function (this program takes too much RAM):

import Graphics.Rendering.Cairo
import Graphics.UI.Gtk
import Data.List.Split
import Data.Array.MArray
import Data.Array.IO
import Data.Word
import Data.List
import Foreign.Storable
grayscaleFromRGB :: Image -> IO [Word8]
grayscaleFromRGB img = do
 imgType <- img `get` imageStorageType
 if imgType == ImageEmpty
 then
 return (replicate 256 0)
 else do
 pixbuf <- imageGetPixbuf img
 pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
 rgbList <- getElems pixels
 nChannels <- pixbufGetNChannels pixbuf
 return (map convert $ chunksOf nChannels rgbList)
 where
 convert channels = round $ foldl' (+) 0 $ zipWith (*) [0.3, 0.59, 0.11] $ map fromIntegral channels 
main :: IO ()
main= do
 initGUI
 window <- windowNew
 set window [windowTitle := "Hello Cairo",
 windowDefaultWidth := 300, windowDefaultHeight := 200,
 containerBorderWidth := 30 ]
 image <- imageNew
 let menuBarData = [
 ("File", [("Open", openPressed image),
 ("Exit", mainQuit)
 ])
 ]
 tab <- tableNew 5 10 True
 menuBar <- createMenuBar menuBarData
 scrolled <- scrolledWindowNew Nothing Nothing
 scrolledWindowAddWithViewport scrolled image
 tableAttachDefaults tab menuBar 0 10 0 1 
 tableAttachDefaults tab scrolled 0 5 1 10 
 containerAdd window tab
 widgetShowAll window 
 onDestroy window mainQuit
 mainGUI
createMenuBar menuBarData = do
 menuBar <- menuBarNew
 mapM_ (setMenuEntries menuBar) menuBarData
 return menuBar
 where
 setMenuEntries menuBar (entryName, items) = do
 entry <- menuItemNewWithLabel entryName
 menu <- menuNew
 mapM_ (addItemToEntry menu) items
 menuItemSetSubmenu entry menu
 menuShellAppend menuBar entry
 addItemToEntry menu (name, action) = do
 item <- menuItemNewWithLabel name
 item `on` menuItemActivated $ action 
 menuShellAppend menu item
openPressed :: Image -> IO ()
openPressed image = do
 chooser <- fileChooserDialogNew 
 (Just "Open file") 
 Nothing 
 FileChooserActionOpen
 [("OK", ResponseOk),
 ("Cancel", ResponseCancel)]
 widgetShow chooser
 res <- dialogRun chooser
 filename <- performResponse res chooser
 widgetHide chooser
 where
 performResponse ResponseCancel _ = return ()
 performResponse ResponseOk chooser = do
 Just fname <- fileChooserGetFilename chooser
 imageSetFromFile image fname
 grey <- grayscaleFromRGB image
 putStrLn $ show $ grey
asked Mar 19, 2018 at 21:20
\$\endgroup\$
4
  • \$\begingroup\$ Are you compiling with optimizations? Try using foldl' instead of foldl. \$\endgroup\$ Commented Mar 20, 2018 at 4:43
  • \$\begingroup\$ @Li-yaoXia, I even replaced convert function without using lists, it increased performance but not much. Profiling says that most allocations are made from grayscaleFromRGB by itself. \$\endgroup\$ Commented Mar 20, 2018 at 9:32
  • \$\begingroup\$ I can't find anything wrong with that function. Do you have a minimal compilable example? \$\endgroup\$ Commented Mar 20, 2018 at 11:41
  • \$\begingroup\$ @Li-yaoXia, updated with example \$\endgroup\$ Commented Mar 20, 2018 at 18:41

1 Answer 1

1
\$\begingroup\$

By commenting out various parts of grayscaleFromRGB, we can deduce that what is taking the most time is getElems (and possibly the computations after return). Indeed, for a 2000x1000 picture, there are 2M pixels (x3 or 4 channels), and getElems is putting them in a list word by word. Lists are quite inefficient for storing large amounts of data like pictures (at least 4 or 5 extra words per element, and the GC comes with an extra x2 factor).

To reduce memory usage, we can write a custom loop to process the elements in the array as we read them, like below. To reduce that further, don't use lists to store large amounts of data. It still takes a while to read millions of elements from memory byte by byte; there may be a better way using a specialized data processing library but I don't know it.

grayscaleFromRGB :: Image -> IO [Word8]
grayscaleFromRGB img = do
 imgType <- img `get` imageStorageType
 if imgType == ImageEmpty
 then
 return (replicate 256 0)
 else do
 pixbuf <- imageGetPixbuf img
 pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
 nChannels <- pixbufGetNChannels pixbuf
 (_, bound) <- getBounds pixels
 loop pixels nChannels bound 0 []
 where
 -- the returned list will be reversed compared to the original code
 loop pixels n bound i acc | i + 2 > bound = return acc
 loop pixels n bound i acc = do
 let get i = fromIntegral <$> (readArray (pixels :: PixbufData Int Word8) i) :: IO Double
 a <- (0.3 *) <$> get i
 b <- (0.59 *) <$> get (i + 1)
 c <- (0.11 *) <$> get (i + 2)
 loop pixels n bound (i + n) $! (((:) $! round (a + b + c)) $! acc)
answered Mar 21, 2018 at 2:47
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.