【发布时间】:2014-01-15 06:14:46
【问题描述】:
我开始学习 Haskell 并希望为 execrsice 解析 PPM 图像。 PPM 格式的结构相当简单,但也很棘手。它被描述为here。首先,我为 PPM Image 定义了一个类型:
data Pixel = Pixel { red :: Int, green :: Int, blue :: Int} deriving(Show)
data BitmapFormat = TextualBitmap | BinaryBitmap deriving(Show)
data Header = Header { format :: BitmapFormat
, width :: Int
, height :: Int
, colorDepth :: Int} deriving(Show)
data PPM = PPM { header :: Header
, bitmap :: [Pixel]
}
bitmap 应该包含整个图像。这是第一个挑战出现的地方 - 在 PPM 中包含实际图像数据的部分可以是文本或二进制(在标题中描述)。
对于文本位图,我编写了以下函数:
parseTextualBitmap :: String -> [Pixel]
parseTextualBitmap = map textualPixel . chunksOf 3 . wordsBy isSpace
where textualPixel (r:g:b:[]) = Pixel (read r) (read g) (read b)
不过,我不确定如何处理二进制位图。使用read 将数字的字符串表示形式转换为数字。我想将 "\x01" 转换为 Int 类型的 1。
第二个挑战是解析标题。我写了以下函数:
parseHeader :: String -> Header
parseHeader = constructHeader . wordsBy isSpace . filterComments
where
filterComments = unlines . map (takeWhile (/= '#')) . lines
formatFromText s
| s == "P6" = BinaryBitmap
| s == "P3" = TextualBitmap
constructHeader (format:width:height:colorDepth:_) =
Header (formatFromText format) (read width) (read height) (read colorDepth)
效果很好。现在我应该编写模块导出函数(我们称之为parsePPM),它获取整个文件内容(String),然后返回PPM。该函数应该调用parseHeader,确定位图格式,调用适当的parse(Textual|Binary)Bitmap,然后用结果构造一个PPM。一旦 parseHeader 返回,我应该从 parseHeader 停止的点开始解码位图。但是,我不知道字符串 parseHeader 停止的点。我能想到的唯一解决方案是,当元组的第二个元素是constructHeader(当前命名为_)检索到的余数时,parseHeader 将返回(Header,String),而不是Header。但我不太确定这是做事的“Haskell 方式”。
总结一下我的问题:
1.如何将二进制格式解码成Pixel的列表
2.如何知道header在哪一点结束
由于我自己学习 Haskell,我没有人真正审查我的代码,所以除了回答我的问题外,我还会对我的编码方式(编码风格、错误、做事的替代方式、等等……)。
【问题讨论】:
-
没有足够的时间给出完整的答案,但你应该看看Data.Binary.Get 和Data.ByteString.Lazy。
-
返回未解析的余数绝对是“Haskell 方式”。看看
Read类型类是如何根据ReadS类型类实现的。 -
@JonPurdy - 如果我理解正确,我不能将
String与ByteString混合使用,所以如果我想使用unpack,我必须使用ByteString 的readFile访问该文件。这不会影响我将标题视为文本的能力吗? -
我认为 PPM 规范中隐含的假设是 PPM 文件是 ASCII 字符/八位字节的文件 - 即。字节。我怀疑您是否会看到 UTF-16 编码的 PPM 文件。