#haskell #image-processing #types #monads
#haskell #обработка изображений #типы #монады
Вопрос:
Я пытался выполнить некоторую обработку изображений, преобразовав список целых чисел в вектор пикселей (который имеет те же базовые значения, но разных типов) на основе существующей библиотеки JuicyPixels.
Но когда я попытался сделать свой pixel
s изображением, произошла ошибка: пиксель имеет тип Pixel8
, который является экземпляром класса type Pixel px
, но ghci сказал мне, что он не может соответствовать этим типам:
? Couldn't match type ‘px’ with ‘Pixel8’
‘px’ is a rigid type variable bound by
the type signature for:
makeListImg :: forall px.
Pixel px =>
Int -> Int -> [Int] -> Image px
at srcImageHandling.hs:63:1-69
Expected type: T.MutableImage s Pixel8 -> ST s (Image px)
Actual type: T.MutableImage (PrimState (ST s)) px
-> ST s (Image px)
? In the second argument of ‘(>>=)’, namely ‘T.unsafeFreezeImage’
In the expression:
makeListMutableImg (w, h) (lst2px8 lst) >>= T.unsafeFreezeImage
In an equation for ‘img’:
img
= makeListMutableImg (w, h) (lst2px8 lst) >>= T.unsafeFreezeImage
? Relevant bindings include
img :: ST s (Image px) (bound at srcImageHandling.hs:66:11)
makeListImg :: Int -> Int -> [Int] -> Image px
(bound at srcImageHandling.hs:64:1)
|
66 | img = makeListMutableImg (w, h) (lst2px8 lst) >>= T.unsafeFreezeImage
| ^^^^^^^^^^^^^^^^^^^
Мои коды (начиная со строки 63):
makeListImg :: forall px. Pixel px => Int -> Int -> [Int] -> Image px
makeListImg w h lst = runST img
where img :: ST s (Image px)
img = makeListMutableImg (w, h) (lst2px8 lst) >>= T.unsafeFreezeImage
lst2px8 :: [Int] -> [Pixel8]
lst2px8 = map toPixel8
makeListMutableImg :: forall m px. (Pixel px, PrimMonad m) => (Int, Int) -> [px] -> m (T.MutableImage (PrimState m) px)
makeListMutableImg (w, h) lst = T.MutableImage w h `liftM` vec
where elemSize = T.componentCount (undefined :: px)
vec = do
arr <- M.new (w * h * elemSize)
let drawLineFromList :: Int -> Int -> [px] -> m ()
drawLineFromList _ y _ | y >= h = return ()
drawLineFromList idx y list = column idx 0 list
where column :: Int -> Int -> [px] -> m ()
column i x l | x >= w = drawLineFromList i (y 1) l
column i x l@(head:tail) = do
T.unsafeWritePixel arr i head
column (i elemSize) (x 1) tail
drawLineFromList 0 0 lst
return arr
Это много кодов, и я действительно сожалею об этом, но у меня нет четкого представления о том, что я делаю; Я просто следую идее в библиотеке.
В функции так много монад и абстрактных слоев, поэтому я действительно не знаю, на чем мне следует сосредоточиться. Я хотел скопировать список в изменяемый вектор, MutableImage
а затем использовать unsafeFreezeImage
и runST
преобразовать его в an Image px
, но типы остановили меня. Что мне теперь делать и как я могу решить эту проблему?
Комментарии:
1.
forall px
означает, что ваш код должен работать с любымpx
, который может выбрать вызывающий вашу функцию, что может быть чем-то отличным отPixel8
. Если вы охватываете только случайpx=Pixel8
, не обещайте «allpx
es» в вашем типе, используйтеPixel8
вместо этого.
Ответ №1:
Я думаю, что лучшим подходом здесь является Image
прямое построение. Это просто обычный тип данных:
data Image a = Image { imageWidth :: !Int
, imageHeight :: !Int
, imageData :: Vector (PixelBaseComponent a) }
откуда Vector
Data.Vector.Storable
.
Для an ImageRGB8
, PixelBaseComponent ImageRGB8
is Word8
и Vector Word8
is расположены в порядке следования строк с последовательными компонентами для каждого пикселя. Судя по вашему коду, это то, как вы [Int]
уже устроены, поэтому ваша makeListImg
функция должна быть такой же простой, как:
import Codec.Picture
import qualified Data.Vector.Storable as VS
makeListImg :: Int -> Int -> [Int] -> Image PixelRGB8
makeListImg w h = Image w h . VS.fromList . map fromIntegral
Я не могу представить приложение, в котором makeListImg
была бы полезна полиморфная версия, но можно определить что-то вроде:
unsafeMakeListImg :: (Integral (PixelBaseComponent p), VS.Storable (PixelBaseComponent p))
=> Int -> Int -> [Int] -> Image p
unsafeMakeListImg w h = Image w h . VS.fromList . map fromIntegral
это будет достаточно общим для всех форматов изображений со встроенными пиксельными компонентами. Проблема с попыткой его использования (и причина, по которой я назвал его «небезопасным») заключается в том, что предполагаемый тип для p
будет определять достоверность структуры предоставленного списка [Int]
. Оригинал makeListImg
тоже небезопасен, но, по крайней мере, он небезопасен предсказуемым образом — вы знаете, что обязательным [Int]
аргументом всегда являются данные для Image PixelRGB8
.
Отдельный пример:
import Codec.Picture
import qualified Data.Vector.Storable as VS
makeListImg :: Int -> Int -> [Int] -> Image PixelRGB8
makeListImg w h = Image w h . VS.fromList . map fromIntegral
main = do
-- make a left-to-right red gradient
let foo = makeListImg 100 100 $ concat [[156 x,0,0] | x <- [0..99], y <- [0..99]]
saveJpgImage 75 "foo.jpg" $ ImageRGB8 foo
Комментарии:
1. Спасибо за ответ! Я бы попытался переопределить свою функцию по-вашему