Hình ảnh và hiển thị pixel là một trong những điều cuối cùng trong Haskell tôi không thể chọn cấu trúc dữ liệu hoàn toàn có chức năng hiệu quả. Để đơn giản, hãy nói về các hình ảnh 1D
vì những hình ảnh này có thể dễ dàng mở rộng thành hình ảnh n-d. Tôi đang sử dụng vectơ không có hộp bọc như các đại diện và xem có thể thay đổi họ cho rendering:Cấu trúc dữ liệu hoàn toàn chức năng thực hiện hiệu quả việc hiển thị hình ảnh là gì?
-- 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 ...
Đây là thiết kế performant nhất cho CPU rendering tôi thấy, nhưng nó là khá xấu xí. Đối với một cấu trúc hoàn toàn chức năng thực hiện render
, câu trả lời rõ ràng sẽ là sử dụng một bản đồ để đại diện cho Hình ảnh và danh sách các cặp (Position, Pixel)
để đại diện cho sprite. Một cái gì đó như:
-- 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
Đây là O(P * log(N))
(P = pixel để hiển thị, N = kích thước của hình ảnh). Có thể thấy rằng log(N)
không thể tách rời, nhưng nếu bạn thấy nó một cách cẩn thận, render
đang di chuyển cùng một đường dẫn qua Image
nhiều lần (ví dụ, nếu bạn chèn ở vị trí 0, sau đó ở vị trí 1, bạn đang chạy hết cỡ xuống một chiếc lá , sau đó tất cả các con đường lên, sau đó tất cả các cách xuống lá láng giềng ...). Điều đó có vẻ giống như mô hình chính xác mà ví dụ như zippers
, có thể cải thiện tiệm cận, dẫn tôi đến nghi ngờ render
có thể được cải thiện. Có cấu trúc dữ liệu hoàn toàn chức năng nào cho phép triển khai render
tốt hơn O(P*log N)
không?
Lưu ý: do "hoàn toàn chức năng", tôi đặc biệt có nghĩa là một cấu trúc mà không sử dụng các kiểu dữ liệu đại số Haskell của mình, nghĩa là không có nguyên thủy có nguồn gốc như Int
và Array
(mặc dù những được phục vụ về mặt kỹ thuật như cấu trúc dữ liệu tinh khiết Nether ít).
Làm thế nào về 'Array' hoặc 'UArray'? –
Điều này không thực sự trả lời câu hỏi của bạn, nhưng tôi vẫn muốn hỏi. Trong cách tiếp cận đầu tiên của bạn, Sprite tương đương với một danh sách các cuộc gọi đến 'Position -> Pixel -> ST s()'. Không thể thay đổi thành '[(Vị trí, Pixel)]' để làm cho nó đẹp hơn trong khi vẫn giữ được hiệu suất như nhau? – madjar
Theo như kỹ năng của tôi đi, không. Vấn đề là 'Position -> Pixel -> ST()' luôn sử dụng không gian cố định. Để làm việc với các danh sách, bạn phải luôn luôn đếm mọi danh sách sẽ được hợp nhất và biên dịch thành một vòng lặp không gian liên tục. Có lẽ nó chỉ là tôi, nhưng tôi không thể làm cho nó để thực hiện giống nhau ngay cả với các mẫu vẽ đơn giản ... – MaiaVictor