简体   繁体   English

`friday`包非常慢

[英]`friday` package is very slow

I'm writing a Haskell program that draws big maps from Knytt Stories world files. 我正在编写一个Haskell程序,它从Knytt Stories世界文件中绘制大地图 I use the friday package to make image files, and I need to compose the many graphics layers that I put together from spritesheets. 我使用friday包来制作图像文件,我需要编写我从spritesheets放在一起的许多图形层。 Right now, I use my own ugly function for this: 现在,我使用自己的丑陋功能:

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

It simply alpha-composites a bottom layer and a top layer, like so: 它只是简单地复合底层和顶层,如下所示:

在此输入图像描述

If the “bottom” layer is a texture, it will be looped horizontally and vertically (by wrap ) to fit the top layer's size. 如果“底部”图层是纹理,它将水平和垂直(通过wrap )循环以适合顶层的大小。


Rendering a map takes far, far longer than it should. 渲染地图需要的时间远远超过应有的时间。 Rendering the map for the default world that comes with the game takes 27 minutes at -O3 , even though the game itself can clearly render each separate screen in less than a couple of milliseconds. 渲染游戏附带的默认世界的地图需要27分钟 -O3 ,即使游戏本身可以在不到几毫秒的时间内清晰地呈现每个单独的屏幕。 (The smaller example output I linked above see above takes 67 seconds; also far too long.) (上面链接的较小的示例输出见上文需要67秒;也太长了。)

The profiler (output is here ) says the program spends about 77% of its time in compose . 分析器(输出在这里 )说该程序花费大约77%的时间在compose

Cutting this down seems like a good first step. 减少这个似乎是一个很好的第一步。 It seems like a very simple operation, but I can't find a native function in friday that lets me do this. 这似乎是一个非常简单的操作,但我在friday找不到本机函数让我这样做。 Supposedly GHC should be good at collapsing all of the fromFunction stuff, but I don't know what's going on. 据说GHC应该擅长折叠所有的fromFunction东西,但我不知道发生了什么。 Or is the package just super slow? 或者包装是否超级慢?

Here's the full, compileable code. 这是完整的可编译代码。

As I stated in my comment, the MCE I made performs fine and does not yield any interesting output: 正如我在评论中所说,我制作的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

This code loads two images, applies your compose operation, and saves the resulting image. 此代码加载两个图像,应用您的撰写操作,并保存生成的图像。 This happens almost instantly: 这几乎是立即发生的:

% 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

If you have an alternate MCE then please post it. 如果您有替代MCE,请发布。 Your complete code was too non-minimal for my eyes. 你的完整代码对我来说太微不足道了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM