Haskell中PNG转BMP(用于Gloss)

4
我有PNG文件,Gloss库有一个PictureBitmap构造函数。因为文件类型原因,我不能使用loadBMP :: FilePath -> IO Picture,所以我正在寻找如何加载PNG文件,将其转换为BMP,并将其提供给bitmapOfBMP :: BMP -> PicturebitmapOfForeignPtr :: Int -> Int -> ForeignPtr Word8 -> Bool -> PicturebitmapOfByteString :: Int -> Int -> ByteString -> Bool -> Picture


使用JuicyPixels测试

import Data.ByteString as B
import System.IO as A

import Codec.Picture.Png
import Graphics.Gloss.Interface.Pure.Game


main = do
    png <- B.readFile "samus.png"
    let img = decodePng png
    case img of
        Left x -> A.putStrLn x
        Right x -> do
            let bmp = encodeDynamicPng x
            case bmp of
                Left x -> A.putStrLn x
                Right x -> do
                    let pic = bitmapOfByteString 29 52 x True
                    game pic

game pic
    =  play
        (InWindow "Test" (700, 500) (10, 10))
        white
        30
        pic
        draw
        (const id)
        (const id)

draw bmp
    = bmp

所有操作都成功了,但是图片完全不一样。


另外,作为一种风格上的点,需要注意的是 \_ -> idconst id 是相同的。 - huon
就我所知,我已经为此编写了gloss-juicy - Alp Mestanogullari
太好了,看起来更完善了;我会尝试使用你的包!对于大多数严肃的Gloss项目来说,这绝对是必备的。 - L01man
3个回答

4

这就是我创建 JuicyPixel-repa 的原因。你可以将图像以 Repa 数组的形式读入并转换,就像我在 gloss-osm 中所做的那样,转换成一个 Picture

repaToPicture :: Bool -> Array F.F DIM3 Word8 -> (Int, Int, Picture)
repaToPicture b arr =
let fptr = F.toForeignPtr arr
bs = BI.fromForeignPtr fptr 0 len
in (col, row, bitmapOfByteString row col bs b)
 where
  len = row * col * depth
  (Z :. row :. col :. depth) = extent arr

或者,您可以直接使用JuicyPixels,针对DynamicImage类型进行操作,并从包含的Image中获取底层的imgData


谢谢,我尝试了第二种解决方案。我没有使用imgData,因为我不知道如何使用,但是将其转换为 BMP ByteString,它并没有显示我的图片,而是散布了蓝色和粉色的像素。我已将代码添加到问题中。 - L01man
我成功使用了你的repaToPicture。我把代码放在问题里了。Bool参数是什么?为什么它返回(Int, Int, Picture)而不是Picture?生成的Picture是“错误”的,所以我做了这个:let (w, h, pic) = repaToPicture True repa; Bitmap _ _ bmp _ = pic in Bitmap w h bmp True。 现在,图像显示出来了,但是它逆时针旋转了180度。 - L01man
抱歉,最近忙得不可开交,但如果你还需要帮助的话,我会尽量在这个周末的时候帮你解决。这个布尔值确定图片是否会被你的OpenGL系统缓存。至于旋转,你可以使用repa中的backpermute函数(链接:http://www.haskell.org/haskellwiki/Numeric_Haskell:_A_Repa_Tutorial)。 - Thomas M. DuBuisson
非常感谢您的帮助!这个教程非常有用:它解释得足够清楚,让我能够修改代码并使其正常工作,并提供了一个可直接使用的函数。在处理表示问题时遇到了一些麻烦,但是computeS起到了关键作用。另外,我之前说图像旋转了180度,实际上是垂直翻转,所以我只需将y - j - 1改为j,然后它就正常工作了。 - L01man

3
尽管我并非独自找到答案,但由于Thomas的回答,我会将其发布在此处而非问题内部。
作为提醒,目标是将BMP文件转换为Gloss图像,因此我编写了一个名为的函数。我将其放在一个模块中,因为它使用了另外两个函数,并需要许多导入。 另外,Thomas回答中的repaToPicture在这里略有不同。
module PngToPic
    (pngToPic)
    where

import Data.ByteString as B
import Data.Word

import Codec.Picture.Png
import Codec.Picture.Repa
import qualified Data.ByteString.Internal as BI
import Data.Array.Repa ((:.)(..), Z, Z(..), extent, DIM3, Array)
import qualified Data.Array.Repa as R
import qualified Data.Array.Repa.Repr.ForeignPtr as F
import Graphics.Gloss.Data.Picture


pngToPic :: ByteString -> Picture
pngToPic png
    = let
        Right img -- unsafe
            = decodePng png
        repa
            = imgData (convertImage img :: Img RGBA)
    in repaToPicture True repa

repaToPicture :: Bool -> Array F.F DIM3 Word8 -> Picture
repaToPicture b arr
    = bitmapOfByteString row col bs b
    where
        bs
            = BI.fromForeignPtr fptr 0 (R.size sh)
        fptr
            = F.toForeignPtr arr'
        sh@(Z :. col :. row :. depth)
            = extent arr'
        arr'
            = flipVert arr

flipVert :: Array F.F DIM3 Word8 -> Array F.F DIM3 Word8
flipVert g
    = R.computeS $ R.backpermute e flop g
    where
        e@(Z :. x :. y :. _)
            = extent g
        flop (Z :. i         :. j         :. k)
            = Z :. x - i - 1 :. j :. k

你可以像这样使用它:
import Data.ByteString as B

import Graphics.Gloss.Interface.Pure.Game

import PngToPic

main = do
    png <- B.readFile "someImage.png"
    game $ pngToPic png

game pic
    = play
        (InWindow "Test" (700, 500) (10, 10))
        white
        30
        pic
        id
        (const id)
        (const id)

图片会显示在窗口的中央。


谢谢你的帮助和赞美。我想要创建一个更通用的函数,例如fileFormatToGlossPicture,因为它很容易覆盖所有情况,并将其制作成一个包。 - L01man

0

对于在2017年偶然发现这篇文章的人来说,这个问题已经变得容易得多了!由于上面的示例代码似乎不再起作用,我花了一些时间才弄明白。下面是我的函数,用于将PNG读入图片:

import Codec.Picture.Repa (readImageRGBA, toByteString, reverseColorChannel)
import Graphics.Gloss

readPng :: FilePath -> Int -> Int -> IO Picture
readPng path w h = do
  (Right img) <- readImageRGBA path
  let bs = toByteString $ reverseColorChannel img
  return $ bitmapOfByteString w h (BitmapFormat TopToBottom PxRGBA) bs True

这个能在Graphics.Gloss.Interface.Pure.Game的Play函数中使用吗?我试过了,但是出现了“Couldn't match expected type Picture' with actual type Int -> Int -> IO Picture'”错误。也许我做错了什么...有没有办法将IO Picture转换成Picture? - chr0m1ng
@alitalvez 这是一个 IO 类型,因为它需要从文件系统中读取。在 Haskell 中,你不能仅仅通过一个纯函数将 IO Picture 转换成 Picture,因为它会与文件系统交互。所以我认为你有两个选择:要么在初始化游戏之前运行这段代码(比如 thePicture <- readPng "/mypath.png"),要么切换到 Graphics.Gloss.Interface.IO.Game 在你的游戏中使用它。我建议先尝试前者! - David Ackerman

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