我正在编写一个Haskell程序,从Knytt Stories世界文件中绘制大地图。我使用friday包来生成图像文件,并需要组合从精灵表中汇总的许多图形层。目前,我使用自己的丑陋函数来完成这个任务:
它只是将底层和顶层进行alpha合成,如下所示: 如果“底部”层是一种纹理,它将通过
渲染地图所需时间远远超过应有的时间。对于默认世界的地图,在
分析器(输出在此)显示程序大约77%的时间都花费在
将其削减似乎是一个良好的第一步。这似乎是一个非常简单的操作,但我找不到
import qualified Vision.Primitive as Im
import qualified Vision.Image.Type as Im
import qualified Vision.Image.Class as Im
import Vision.Image.RGBA.Type (RGBA, RGBAPixel(..))
-- Map a Word8 in [0, 255] to a Double in [0, 1].
w2f :: Word8 -> Double
w2f = (/255) . fromIntegral . fromEnum
-- Map a Double in [0, 1] to a Word8 in [0, 255].
f2w :: Double -> Word8
f2w = toEnum . round . (*255)
-- Compose two images into one. `bottom` is wrapped to `top`'s size.
compose :: RGBA -> RGBA -> RGBA
compose bottom top =
let newSize = Im.manifestSize top
bottom' = wrap newSize bottom
in Im.fromFunction newSize $ \p ->
let RGBAPixel rB gB bB aB = bottom' Im.! p
RGBAPixel rT gT bT aT = top Im.! p
aB' = w2f aB; aT' = w2f aT
ovl :: Double -> Double -> Double
ovl cB cT = (cT * aT' + cB * aB' * (1.0 - aT')) / (aT' + aB' * (1.0 - aT'))
(~*~) :: Word8 -> Word8 -> Word8
cB ~*~ cT = f2w $ w2f cB `ovl` w2f cT
aO = f2w (aT' + aB' * (1.0 - aT'))
in RGBAPixel (rB ~*~ rT) (gB ~*~ gT) (bB ~*~ bT) aO
它只是将底层和顶层进行alpha合成,如下所示: 如果“底部”层是一种纹理,它将通过
wrap
水平和垂直循环以适应顶部层的大小。
渲染地图所需时间远远超过应有的时间。对于默认世界的地图,在
-O3
下需要花费27分钟,尽管游戏本身明显可以在不到几毫秒的时间内呈现每个单独的屏幕。(上面链接的较小示例输出需要67秒,也太长了。)分析器(输出在此)显示程序大约77%的时间都花费在
compose
上。将其削减似乎是一个良好的第一步。这似乎是一个非常简单的操作,但我找不到
friday
中允许我这样做的本地函数。据说GHC应该擅长折叠所有的fromFunction
东西,但我不知道发生了什么。或者这个包只是超级慢吗?
这里是完整的可编译代码。
*” 操作两个单词,应该快速且不进行任何分配。但是,性能分析表明这并非如此,至少有些可疑。可能是由于Im.fromFunction
构建了 “*” 和其他函数的惰性求值。此外,测试方式可能会影响性能,该库可能在融合和摊销成本分析方面依赖很大,但这可能会被打破。 - user2407038RGBA
切换到RGBADelayed
可能会有很大的改善。你能用言语解释一下compose
应该计算什么吗?这并不是世界上最显而易见的事情。 - dfeuerRGBADelayed
,谢谢。 - Lynnwrap
也相当慢。您可以使用fromFunctionCached
来执行每行仅一个整数除法和每列仅一个整数除法,而不是每个像素两个整数除法。 - dfeuer