在模块 GHCJS.DOM.JSFFI.Generated.CanvasRenderingContext2D 中,函数putImageData
具有以下类型:
putImageData ::
Control.Monad.IO.Class.MonadIO m =>
CanvasRenderingContext2D
-> Maybe GHCJS.DOM.Types.ImageData -> Float -> Float -> m ()
第二个参数的类型为Maybe GHCJS.DOM.Types.ImageData
。
此类型在模块 GHCJS.DOM.Types 中定义为JSVal值的新类型包装:
newtype ImageData = ImageData {unImageData :: GHCJS.Prim.JSVal}
我有一个ByteString
类型的值,它总是有4个字节,每个像素的RGBA值。如何将我的ByteString值转换为GHCJS.Prim.JSVal?
答案 0 :(得分:3)
编辑:我的原始答案看起来过于GHC为中心。添加了一个可能适用于GHCJS的未经测试的修复。
编辑#2:为示例添加了我的stack.yaml
文件。
您可以使用GHCJS.DOM.ImageData.newImageData
构建ImageData
对象。它要求数据为GHCJS.DOM.Types.Uint8ClampedArray
(RGBA格式的字节数组)。
GHCJS.Buffer
从ByteString
到Buffer
s(通过fromByteString
)以及从那里到类型化数组(例如getUint8Array
)的转换函数。他们直接在GHCJS下进行转换,即使在简单的GHC下,他们也使用base64转换作为中介,这应该非常快。遗憾的是,转换函数getUint8ClampedArray
并未包含在内(对于简单的GHC,看起来fromByteString
似乎可能会被破坏 - 在jsaddle 0.8.3.0中,它调用了错误JavaScript辅助函数)。
对于普通GHC,以下似乎有效(第一行是从fromByteString
复制的,其中帮助者从明显不正确的h$newByteArrayBase64String
重命名):
uint8ClampedArrayFromByteString :: ByteString -> GHCJSPure (Uint8ClampedArray)
uint8ClampedArrayFromByteString bs = GHCJSPure $ do
buffer <- SomeBuffer <$> jsg1 "h$newByteArrayFromBase64String"
(decodeUtf8 $ B64.encode bs)
arrbuff <- ghcjsPure (getArrayBuffer (buffer :: MutableBuffer))
liftDOM (Uint8ClampedArray <$> new (jsg "Uint8ClampedArray") [pToJSVal arrbuff])
这是一个未经测试的GHCJS版本,可能有效。如果他们修复了上面提到的jsaddle错误,它也应该在简单的GHC下工作:
uint8ClampedArrayFromByteString :: ByteString -> GHCJSPure (Uint8ClampedArray)
uint8ClampedArrayFromByteString bs = GHCJSPure $ do
(buffer,_,_) <- ghcjsPure (fromByteString bs)
buffer' <- thaw buffer
arrbuff <- ghcjsPure (getArrayBuffer buffer')
liftDOM (Uint8ClampedArray <$> new (jsg "Uint8ClampedArray") [pToJSVal arrbuff])
我没有正在运行的GHCJS安装,但这是一个完整的工作示例,我在简单的GHC下使用JSaddle + Warp进行了测试,似乎工作正常(即,如果您将浏览器指向localhost:6868 ,它在画布元素上显示3x4图像:
module Main where
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Text.Encoding (decodeUtf8)
import qualified Data.ByteString.Base64 as B64 (encode)
import Language.Javascript.JSaddle (js, js1, jss, jsg, jsg1,
new, pToJSVal, GHCJSPure(..), ghcjsPure, JSM,
fromJSVal, toJSVal, Object)
import Language.Javascript.JSaddle.Warp (run)
import JSDOM.Types (liftDOM, Uint8ClampedArray(..), RenderingContext(..))
import JSDOM.ImageData
import JSDOM.HTMLCanvasElement
import JSDOM.CanvasRenderingContext2D
import GHCJS.Buffer (getArrayBuffer, MutableBuffer)
import GHCJS.Buffer.Types (SomeBuffer(..))
import Control.Lens ((^.))
main :: IO ()
main = run 6868 $ do
let smallImage = BS.pack [0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff, 0xff,0x00,0x00,0xff,
0x00,0x00,0x00,0xff, 0x00,0xff,0x00,0xff, 0x00,0x00,0x00,0xff,
0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff, 0x00,0x00,0xff,0xff,
0x00,0x00,0xff,0xff, 0x00,0x00,0x00,0xff, 0x00,0x00,0xff,0xff]
img <- makeImageData 3 4 smallImage
doc <- jsg "document"
doc ^. js "body" ^. jss "innerHTML" "<canvas id=c width=10 height=10></canvas>"
Just canvas <- doc ^. js1 "getElementById" "c" >>= fromJSVal
Just ctx <- getContext canvas "2d" ([] :: [Object])
let ctx' = CanvasRenderingContext2D (unRenderingContext ctx)
putImageData ctx' img 3 4
return ()
uint8ClampedArrayFromByteString :: ByteString -> GHCJSPure (Uint8ClampedArray)
uint8ClampedArrayFromByteString bs = GHCJSPure $ do
buffer <- SomeBuffer <$> jsg1 "h$newByteArrayFromBase64String"
(decodeUtf8 $ B64.encode bs)
arrbuff <- ghcjsPure (getArrayBuffer (buffer :: MutableBuffer))
liftDOM (Uint8ClampedArray <$> new (jsg "Uint8ClampedArray") [pToJSVal arrbuff])
makeImageData :: Int -> Int -> ByteString -> JSM ImageData
makeImageData width height dat
= do dat' <- ghcjsPure (uint8ClampedArrayFromByteString dat)
newImageData dat' (fromIntegral width) (Just (fromIntegral height))
为了构建这个,我使用了以下stack.yaml
:
resolver: lts-8.12
extra-deps:
- ghcjs-dom-0.8.0.0
- ghcjs-dom-jsaddle-0.8.0.0
- jsaddle-0.8.3.0
- jsaddle-warp-0.8.3.0
- jsaddle-dom-0.8.0.0
- ref-tf-0.4.0.1
答案 1 :(得分:2)
作为K.A. Buhr指出,在将ByteString
转换为Uint8ClampedArray
后,您可以将限制数组传递给newImageData
以获取所需的ImageData
对象。
您可以使用内联Javascript函数生成Uint8ClampedArray
。要通过Javascript FFI传递ByteString
,请使用Data.ByteString.useAsCStringLen
。
下面的代码显示了如何执行此操作。
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE JavaScriptFFI #-}
{-# LANGUAGE CPP #-}
import Reflex.Dom
import Data.Monoid ((<>))
import Control.Monad.IO.Class (liftIO)
import GHCJS.DOM.ImageData (newImageData)
import GHCJS.DOM.HTMLCanvasElement (getContext)
import GHCJS.DOM.JSFFI.Generated.CanvasRenderingContext2D (putImageData)
import GHCJS.DOM.Types (CanvasRenderingContext2D(..), castToHTMLCanvasElement, Uint8ClampedArray(..))
import Foreign.Ptr (Ptr)
import GHCJS.Types (JSVal)
import GHCJS.Marshal.Pure (pFromJSVal, pToJSVal)
import Data.Map (Map)
import Data.Text as T (Text, pack)
import Data.ByteString as BS (ByteString, pack, useAsCStringLen)
-- Some code and techniques taken from these sites:
-- http://lpaste.net/154691
-- https://www.snip2code.com/Snippet/1032978/Simple-Canvas-Example/
-- import inline Javascript code as Haskell function : jsUint8ClampedArray
foreign import javascript unsafe
-- Arguments
-- pixels : Ptr a -- Pointer to a ByteString
-- len : JSVal -- Number of pixels
"(function(){ return new Uint8ClampedArray($1.u8.slice(0, $2)); })()"
jsUint8ClampedArray :: Ptr a -> JSVal -> IO JSVal
-- takes pointer and length arguments as passed by useAsCStringLen
newUint8ClampedArray :: (Ptr a, Int) -> IO Uint8ClampedArray
newUint8ClampedArray (pixels, len) =
pFromJSVal <$> jsUint8ClampedArray pixels (pToJSVal len)
canvasAttrs :: Int -> Int -> Map T.Text T.Text
canvasAttrs w h = ("width" =: T.pack (show w))
<> ("height" =: T.pack (show h))
main = mainWidget $ do
-- first, generate some test pixels
let boxWidth = 120
boxHeight = 30
boxDataLen = boxWidth*boxHeight*4 -- 4 bytes per pixel
reds = take boxDataLen $ concat $ repeat [0xff,0x00,0x00,0xff]
greens = take boxDataLen $ concat $ repeat [0x00,0xff,0x00,0xff]
blues = take boxDataLen $ concat $ repeat [0x00,0x00,0xff,0xff]
pixels = reds ++ greens ++ blues
image = BS.pack pixels -- create a ByteString with the pixel data.
-- create Uint8ClampedArray representation of pixels
imageArray <- liftIO $ BS.useAsCStringLen image newUint8ClampedArray
let imageWidth = boxWidth
imageHeight = (length pixels `div` 4) `div` imageWidth
-- use Uint8ClampedArray representation of pixels to create ImageData
imageData <- newImageData (Just imageArray) (fromIntegral imageWidth) (fromIntegral imageHeight)
-- demonstrate the imageData is what we expect by displaying it.
(element, _) <- elAttr' "canvas" (canvasAttrs 300 200) $ return ()
let canvasElement = castToHTMLCanvasElement(_element_raw element)
elementContext <- getContext canvasElement ("2d" :: String)
let renderingContext = CanvasRenderingContext2D elementContext
putImageData renderingContext (Just imageData) 80 20
以下是指向存储库的链接,其中包含示例代码:https://github.com/dc25/stackOverflow__how-to-convert-a-bytestring-value-to-a-jsval
以下是现场演示的链接:https://dc25.github.io/stackOverflow__how-to-convert-a-bytestring-value-to-a-jsval/
答案 2 :(得分:1)
您可以使用hoogle按类型签名function generationMatrix(number) {
var coefficient = 1 / number;
var indexMass = Array(number);
var matrix = Array(number);
// Create an array with uniformly distributed mean values
for (var i = 0; i < number; i++) {
indexMass[i] = i;
var row = Array(number);
for (var j = 0; j < number; j++) {
row[j] = getRandom(coefficient / 2, coefficient);
}
matrix[i] = row;
}
// In (number-1) lines, we change the random element to such that the sum of the elements in the (row) is equl 1
for (var i = 0; i < number - 1; i++) {
var rand_index = indexMass.splice((Math.floor(Math.random() * indexMass.length)), 1);
matrix[i].splice(rand_index, 1);
var newElem = 1 - matrix[i].reduce((a, b) => a + b, 0);
matrix[i].splice(rand_index, 0, newElem);
}
// TReplace the remaining (row) with a (row) such that the sum of the elements in the columns is equl 1
for (var i = 0; i < number; i++) {
var sum = 0;
for (var j = 0; j < number; j++) {
if (j != indexMass[0])
sum += matrix[i][j];
}
matrix[indexMass[0]][i] = 1 - sum;
}
return matrix;
}
查找功能。 https://www.stackage.org/lts-8.11/hoogle?q=ByteString+-%3E+GHCJS.Prim.JSVal
结果如下: https://www.stackage.org/haddock/lts-8.11/ghcjs-base-stub-0.1.0.2/GHCJS-Prim.html#v:toJSString
ByteString -> GHCJS.Prim.JSVal
所以现在你只需要一个函数toJSString :: String -> JSVal
。