我正在编写一个从big maps世界文件中提取Knytt Stories的Haskell程序。我使用friday
包来制作图像文件,我需要编写我从spritesheets放在一起的许多图形图层。现在,我使用自己的丑陋功能:
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
它简单地对底层和顶层进行α合成,如下所示:
如果“底部”图层是纹理,它将水平和垂直环绕(wrap
)以适合顶层的大小。
渲染地图需要的时间远远超过预期。渲染游戏附带的默认世界的地图在-O3
处需要 27分钟,即使游戏本身可以在不到几毫秒的时间内清晰地呈现每个单独的屏幕。 (上面链接的较小的示例输出见上文需要67秒;也太长了。)
分析器(输出为here)表示该程序在compose
中花费了大约77%的时间。
削减这个似乎是一个很好的第一步。这似乎是一个非常简单的操作,但我找不到friday
中允许我这样做的本机函数。据说GHC应该擅长折叠所有fromFunction
内容,但我不知道发生了什么。或者包裹是否超级慢?
答案 0 :(得分:1)
正如我在评论中所述,我制作的MCE表现不错,并没有产生任何有趣的输出:
module Main where
import qualified Vision.Primitive as Im
import Vision.Primitive.Shape
import qualified Vision.Image.Type as Im
import qualified Vision.Image.Class as Im
import Vision.Image.RGBA.Type (RGBA, RGBAPixel(..))
import Vision.Image.Storage.DevIL (load, save, Autodetect(..), StorageError, StorageImage(..))
import Vision.Image (convert)
import Data.Word
import System.Environment (getArgs)
main :: IO ()
main = do
[input1,input2,output] <- getArgs
io1 <- load Autodetect input1 :: IO (Either StorageError StorageImage)
io2 <- load Autodetect input2 :: IO (Either StorageError StorageImage)
case (io1,io2) of
(Left err,_) -> error $ show err
(_,Left err) -> error $ show err
(Right i1, Right i2) -> go (convert i1) (convert i2) output
where
go i1 i2 output =
do res <- save Autodetect output (compose i1 i2)
case res of
Nothing -> putStrLn "Done with compose"
Just e -> error (show (e :: StorageError))
-- Wrap an image to a given size.
wrap :: Im.Size -> RGBA -> RGBA
wrap s im =
let Z :. h :. w = Im.manifestSize im
in Im.fromFunction s $ \(Z :. y :. x) -> im Im.! Im.ix2 (y `mod` h) (x `mod` w)
-- 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
此代码加载两个图像,应用您的撰写操作,并保存生成的图像。这几乎是立即发生的:
% ghc -O2 so.hs && time ./so /tmp/lambda.jpg /tmp/lambda2.jpg /tmp/output.jpg && o /tmp/output.jpg
Done with compose
./so /tmp/lambda.jpg /tmp/lambda2.jpg /tmp/output.jpg 0.05s user 0.00s system 98% cpu 0.050 total
如果您有替代MCE,请发布。你的完整代码对我来说太微不足道了。