图像和像素渲染是Haskell最后的事情之一我无法选择一个足够有效的纯功能数据结构。为简单起见,我们来谈谈 1D
图像,因为那些可以很容易地扩展到n-d图像。我使用未装箱的矢量作为表示,并使用它们的可变视图进行渲染:
-- An 1D image is an unboxed vector of Pixels
type Position = Int
data Image = Image { size :: Position, buffer :: UV.Vector Pixel }
-- A sprite is a recipe to draw something to an Image
newtype Sprite = Sprite (forall s .
-> (Position -> Pixel -> ST s ()) -- setPixel
-> ST s ()) -- ST action that renders to Image
-- Things that can be rendered to screen provide a sprite
class Renderable a where
getSprite a :: a -> Sprite
-- `render` applies all sprites, mutably, in sequence. `O(P)` (where P = pixels to draw)
render :: [Sprite] -> Image -> Image
render draws (Image size buffer) = Image size $ runST $ do ...
这是我发现的CPU渲染效率最高的设计,但它相当难看。对于实现的纯功能结构 render
,显而易见的答案是使用地图来表示图像和列表 (Position, Pixel)
用来表示精灵。就像是:
-- 1D for simplicity
type Position = Int
-- An image is a map from positions to colors
type Image = Map Position Pixel
-- A sprite is, too, a map from positions to colors
type Sprite = [(Position, Pixel)]
-- Rendering is just inserting each pixel in sequence
-- (Not tested.)
render :: [Sprite] -> Image -> Image
render sprites image = foldr renderSprite image sprites
where renderSprite sprite image = foldr (uncurry insert) image sprites
这是 O(P * log(N))
(P =要渲染的像素,N =图像的大小)。它可能看起来像 log(N)
是不可避免的,但如果你仔细看, render
正在经历相同的路径 Image
几次(即,如果你插入位置0,然后在位置1,你一直向下运行到一片叶子,然后一直向上,然后一直向下到邻居叶子......)。这看起来像完全相同的模式 zippers
例如,可以改善渐近线,这让我怀疑 render
可以改进。 是否存在允许实现的纯功能数据结构 render
比...更好 O(P*log N)
?
注意:通过“纯粹的功能”,我特别指的是仅使用Haskell的代数数据类型的结构,即没有原生基元,例如 Int
和 Array
(尽管那些在技术上更纯粹地用作纯数据结构)。