我正在开发一个应用程序,在该应用程序中,到目前为止(到目前为止)我发现最好的操作图像的方法是使用Graphics.Netpbm库。我将把许多操作(例如原始文件开发)委派给外部应用程序,而我只是想要一种非常干净且受支持的格式。

我实际上让它正常工作。我可以告诉ufraw-batch“开发”原始文件,并将结果以pnm格式输出到stdout。 Graphics.Netpbm.parsePPM可以很好地解析图像。然后,我需要将其转换为pixbuf,以显示在GtkImage小部件中,尽管我有解决方案,但我认为它相当粗糙。我希望有一种更好的方法。

阅读这段代码时,您可以跳过整个let声明,并了解img_list :: Netpbm.PPM -> [CUChar]

display_image :: Graphics.UI.Gtk.Image -> Netpbm.PPM -> IO ()
display_image canvas image_bitmap =
    let get_rgb_vector = Netpbm.pixelVectorToList . (\(Netpbm.PpmPixelDataRGB8 v) -> v) . Netpbm.ppmData
        vector_to_cuchar_list = map CUChar . concat . map (\(Netpbm.PpmPixelRGB8 r g b) -> r:g:b:[])
        img_list = vector_to_cuchar_list . get_rgb_vector
        len = length . img_list
        width = Netpbm.ppmWidth . Netpbm.ppmHeader
        height = Netpbm.ppmHeight . Netpbm.ppmHeader
    in do
    img_ptr <- mallocBytes (len image_bitmap)
    mapM_ (\(b, off) -> pokeByteOff img_ptr off (b :: CUChar)) (zip (img_list image_bitmap) [0,1..])
    pb <- pixbufNewFromData img_ptr ColorspaceRgb False 8 (width image_bitmap) (height image_bitmap) ((width image_bitmap) * 3)
    imageSetFromPixbuf canvas pb

顶部的功能始于许多实用工具,可实际获取原始像素数据。它们也很杂乱,我还真的不知道更好的组织它们的方法。但是最重​​要的部分在do块中。我分配了一个C字节数组来存储所有数据。然后,我遍历表示图像的[CUChar],将每个单独的字节插入C数组中的正确位置。然后根据这些数据创建一个GtkPixbuf。

g什么是更快,更清洁,更安全的方法?

pixbufNewFromData接受Ptr CUChar作为第一个参数,其中Netpbm具有所有展开的功能,最终可以为我提供[Word8]。因此,在我看来,我需要一个执行[Word8] -> Ptr CUChar的干净函数。它不一定是纯净的。

我也不介意进行重大更改,但总体而言,我的数据流是
operation that generates a ByteString of pnm data ---> GtkImage

最佳答案

让我从语法重写等效地开始:

{-# LANGUAGE NamedFieldPuns #-}

display_image :: Graphics.UI.Gtk.Image -> Netpbm.PPM -> IO ()
display_image canvas PPM{ ppmData
                        , ppmHeader = PPMHeader{ ppmWidth  = width
                                               , ppmHeight = height } } = do

    let word8s :: [Word8]
        word8s = case ppmData of

            PpmPixelDataRGB8 vec -> concat [ [r,g,b] | PpmPixelRGB8 r g b <- Netpbm.pixelVectorToList vec ]
            _ -> error "expected RGB8 data"

    img_ptr <- mallocBytes (length word8s)

    forM_ (zip word8s [0..]) $ \(word8, off) ->
        pokeByteOff img_ptr off (CUChar word8)

    pb <- pixbufNewFromData img_ptr ColorspaceRgb False 8 width height (width * 3)
    imageSetFromPixbuf canvas pb

现在更安全的方法是,只需使用newArray将列表转换为Ptr:
import Foreign.Marshal.Array (newArray)

    img_ptr <- newArray (map CUChar word8s)
    pb <- pixbufNewFromData img_ptr ColorspaceRgb False 8 width height (width * 3)
    imageSetFromPixbuf canvas pb

这很短,很容易阅读。
  • 请注意,如果pixbufNewFromData自己制作数据副本,请使用allocaBytes代替mallocBytes,并使用withArray代替newArray。否则,您将泄漏malloc


  • 如果不是安全的方法,而是想要更快的方法,请不要转换为列表并通过未装箱的U.Vector直接操作:
    import qualified Data.Vector.Unboxed as U
    
        case ppmData of
    
            PpmPixelDataRGB8 vec -> do
    
                let len = U.length vec
    
                img_ptr <- mallocBytes len
    
                forM_ [0..len-1] $ \i -> do
                    let PpmPixelRGB8 r g b = vec U.! i
                    pokeByteOff img_ptr (3 * off    ) r
                    pokeByteOff img_ptr (3 * off + 1) g
                    pokeByteOff img_ptr (3 * off + 2) b
    
                pb <- pixbufNewFromData img_ptr ColorspaceRgb False 8 width height (width * 3)
                imageSetFromPixbuf canvas pb
    
            _ -> error "expected RGB8 data"
    
  • 甚至更快的方法是更改​​netpbm(我写了,欢迎发表),并使其使用Vector.Storable而不是Vector.Unboxed-然后您可以直接获取数据的Ptr而无需执行任何操作。
  • 您可能不需要此更快的版本,因为最有可能解析基于文本的PPM文件将成为瓶颈,而不是访问已解析的数据。


  • 更新:我刚刚发布了netpbm-1.0.0,它具有Storable而不是Unboxed向量。

    因此,您可以使用
    S.unsafeWith vec $ \ppmPixelRgb8Ptr -> do
        let word8Ptr = castPtr (ppmPixelRgb8Ptr :: Ptr PpmPixelDataRGB8)
        -- do something with the Ptr
    

    07-26 03:29