如何将bytestring值转换为JSVAL



在模块 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包装器:

newtype ImageData = ImageData {unImageData :: GHCJS.Prim.JSVal}

i具有ByteString类型的值,该值始终具有4个字节,其中每个像素的RGBA值。如何将我的bytestring值转换为ghcjs.prim.jsval?

编辑:看起来我的原始答案过于GHC。添加了一个未经测试的修复程序,该修复程序可能适用于GHCJ。

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

您可以使用GHCJS.DOM.ImageData.newImageData来构造ImageData对象。它要求数据为GHCJS.DOM.Types.Uint8ClampedArray(这是RGBA格式的字节阵列(。

GHCJS.Buffer中有转换功能从ByteString s到Buffer S(通过fromByteString(以及从那里到键入的数组(例如getUint8Array(。他们直接在GHCJ下进行转换,即使在普通的GHC下,它们也将基本64转换作为中介机构,应该很快。不幸的是,不包括转换函数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错误,则它也应该在Pline 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安装,但是这是我使用JSADDLE WARP在平原GHC下进行测试的完整工作示例,这似乎可以正常工作(即,如果您指向Localhost:6868:它显示A 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

作为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-a-bytestring-value-value-to-a-jsval

这是一个实时演示的链接:https://dc25.github.io/stackoverflow __how-to-convert-a-bytestring-value-value-to-a-jsval/

您可以使用hoogle通过其类型签名ByteString -> GHCJS.Prim.JSVal查找功能。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

toJSString :: String -> JSVal

所以现在您只需要一个函数即可完成ByteString -> String

最新更新