如何将ByteString值转换为JSVal

时间:2017-04-23 13:29:39

标签: haskell ghcjs ghcjs-dom

在模块 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?

3 个答案:

答案 0 :(得分:3)

编辑:我的原始答案看起来过于GHC为中心。添加了一个可能适用于GHCJS的未经测试的修复。

编辑#2:为示例添加了我的stack.yaml文件。

您可以使用GHCJS.DOM.ImageData.newImageData构建ImageData对象。它要求数据为GHCJS.DOM.Types.Uint8ClampedArray(RGBA格式的字节数组)。

GHCJS.BufferByteStringBuffer 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