从OpenGL/GLUT应用程序读取像素

8
我有一个应用程序,使用OpenGL和GLUT在屏幕上显示一些东西。 我想以“逐像素”的方式访问显示的颜色,但我不理解提供的方法。
似乎访问此类数据的方法是使用函数: “readPixels :: Position -> Size -> PixelData a -> IO()” 这显然不是Haskelly,因为它使用C模式接收目标指针并将其写入该指针。
第三个参数是重要部分,它构造如“PixelData PixelFormat DataType(Ptr a)”。 "PixelFormat" 和 "DataType" 都是枚举,前者取值如“RGBAInteger,RGBA,CMYK等”,后者取值如“UnsignedByte,Short,Float等”。 选择对我来说很困惑,但那不是真正的问题。 我真正遇到麻烦的地方在于使用指针。我不知道要使用哪种“可存储”数据来“malloc”创建指针,如果我选择使用“mallocBytes”,要分配多少字节。
我正在尝试玩弄的一个函数当前看起来像这样:
pixels :: IO ()
pixels = do
    pointer <- mallocBytes 50000
    let pdata = PixelData RGBA Int pointer
    let pos = Position 0 0
    let siz = Size 10 10
    readPixels pos siz pdata
    print pdata
    mypixels <- peek pointer
    -- TODO, what kind of data is mypixels?
    free pointer 

它可以正常运行,但我不知道如何使用从Ptr获取的数据。也许我没有完全理解文档,但我该如何确定指针处的数据类型以及如何在程序中使用它?请注意,我选择RGBAInt参数是任意的,它们听起来无害。我真正想要的是一些格式的RGBA像素值的列表或多维列表(例如Color 4)。任何帮助都将不胜感激,我似乎陷入了困境。
2个回答

7
您正在使用低级绑定,因此不要感到意外它们是低级的。 readPixels 直接对应于 glReadPixels(它们使用 MathML,因此请使用 支持它的浏览器 查看)。
您得到的是一个包含 RGBA 颜色值的 32 位整数数组。您只需要 width * height * 4 字节,您正在过度分配很多空间。
通过 FFI 机制从中读取变得非常容易-它提供了 peekArray,可以用来将像素数据提取到 Haskell 列表中。您还需要使用正确类型的指针进行操作。 mallocBytes 返回特殊类型的指针,对应于 void*。我正在使用 ForeignPtr,以便运行时负责完成它(使用 mallocBytes,您需要使用 castPtr 并且还要使用 bracket 来处理异常)。
import Data.Word
import Foreign
import Graphics.Rendering.OpenGL

readPixelArray :: Int -> Int -> Int -> Int -> IO [Word32]
readPixelArray x y w h = do
    let arraySize = w * h
    array <- mallocForeignPtrArray arraySize :: IO (ForeignPtr Word32)
    withForeignPtr array $ \ptr -> do
        -- ptr :: Ptr Word32
        -- fromIntegral is needed because Position and Size store GLints not Ints
        let position = Position (fromIntegral x) (fromIntegral y)
        let size = Size (fromIntegral w) (fromIntegral h)
        readPixels position size $ PixelData RGBA UnsignedInt ptr
        peekArray arraySize ptr

现在你有一个由颜色值组成的一维数组。我们可以将其拆分成这样的类型:
import Data.Bits

data Pixel = Pixel {
    red :: Word8, green :: Word8, blue :: Word8, alpha :: Word8
} deriving (Eq, Show)

readPixels' :: Int -> Int -> Int -> Int -> IO [Pixel]
readPixels' x y w h = do
    rawPixels <- readPixelArray x y w h
    return $ map pixelFromWord32 rawPixels

-- pixelFromWord32 0xAABBCCDD = Pixel 0xAA 0xBB 0xCC 0xDD
pixelFromWord32 :: Word32 -> Pixel
pixelFromWord32 colour = Pixel (extract 3) (extract 2) (extract 1) (extract 0)
    where extract idx = fromIntegral $ (colour `shiftR` (idx * 8)) .&. 0xFF

withForeignPtr 之后我得到了一个段错误,有什么想法吗? - Scott
peekArray期间发生了“段错误”。 - Scott

1
这里有一个版本,它将图像读入到 JuicyPixels 图像中,可以轻松地保存为 PNG 文件等:
import Codec.Picture (Image, PixelRGB8(..), withImage)
import Control.Lens.Operators
import qualified Foreign as F
import qualified Graphics.Rendering.OpenGL as GL

takeScreenshot :: IO (Image PixelRGB8)
takeScreenshot =
    do
        (pos, size@(GL.Size wGl hGl)) <- GL.get GL.viewport
        let width = fromIntegral wGl
        let height = fromIntegral hGl
        let pixelSize = 3
        -- glY converts top-origin coordinates to OpenGL's bottom-origin system
        let glY y = height - 1 - y
        let pixelOffset x y = ((glY y) * width + x) * pixelSize
        F.allocaBytes (pixelSize * width * height) $
            \ptr ->
            do
                GL.readPixels pos size (GL.PixelData GL.RGB GL.UnsignedByte ptr)
                let readPixel x y =
                        F.plusPtr ptr (pixelOffset x y)
                        & F.peekArray pixelSize
                        <&> (\[r, g, b] -> PixelRGB8 r g b)
                withImage width height readPixel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接