7

我想在服务器端动态生成图像并将其发送到浏览器。

目前我正在使用 aMemoryStream将其转换为byte array通常的 suave api。见下图:

let draw (text:string) =

  let redBr = new SolidBrush(Color.Red)
  let whiteBr = new SolidBrush(Color.White)
  let i = new Bitmap(600,400)
  let g = Graphics.FromImage i
  let f = new Font("Courier", float32 <| 24.0)
  g.FillRectangle(whiteBr, float32 <| 0.0, float32 <| 0.0, float32 <| 600.0, float32 <| 400.0 )
  g.DrawString(text, f, redBr, float32 <| 10.0, float32 <| 40.0)
  g.Flush()
  let ms = new System.IO.MemoryStream()
  i.Save(ms, ImageFormat.Png)
  ms.ToArray()

let app : WebPart =
  Writers.setMimeType "image/png"
  >=> (Successful.ok <| draw "bibi")

如果 suave 允许我们直接通过管道连接到响应流,我觉得这MemoryStream部分可以避免。

谢谢!

4

3 回答 3

4

你基本上这样做:

open System.IO
open Suave
open Suave.Sockets
open Suave.Sockets.Control

path "/byte-stream" >=> (fun ctx ->

  let write (conn, _) = socket {
    use ms = new MemoryStream()
    ms.Write([| 1uy; 2uy; 3uy |], 0, 3)
    ms.Seek(0L, SeekOrigin.Begin) |> ignore
    // do things here
    let! (_,conn) = asyncWriteLn (sprintf "Content-Length: %d\r\n" ms.Length) conn
    let! conn = flush conn
    do! transferStream conn ms
    return conn
  }

  { ctx with
      response =
        { ctx.response with
            status = HTTP_200.status
            content = SocketTask write } }
  |> succeed
)
于 2016-06-14T20:53:34.353 回答
1

我假设您担心分配不必要的对象。我认为这种担忧是值得称赞的。

您可能追求的是一个位图 API,其中通过提供 PNG 图像的字节,Stream<byte>然后位图 API 在套接字需要时生成。

System.Drawing似乎不支持这种行为,可能支持WIC(.NET 包装器通过优秀的SharpDX库存在)。

但是,这意味着在传输期间保持潜在的昂贵对象(位图、画笔等)处于活动状态。字节数组可能是存储结果的更有效方式。

为了避免不必要地分配对象的另一种方法是缓存它们。由于System.Drawing对象是可变的并且从多个线程中使用不安全,因此问题变得更加严重。但是,您可以使用ThreadLocal.

在下面的示例代码中,大多数对象都是按Thread. 每次调用创建的唯一对象draw是返回的字节数组,但这可能是 PNG 数据的有效存储(调用可能System.Drawing分配对象,但我们无法控制)。由于我没有找到一种方法来监听线程的“死亡”,这意味着dispose当线程不再需要对象时,使用该方法手动处理对象很重要。

希望这很有趣

open System
open System.Drawing
open System.Drawing.Imaging
open System.IO
open System.Threading

module BitmapCreator =
  module internal Details =
    let dispose (d : IDisposable) =
      if d <> null then
        try
          d.Dispose ()
        with
        | e -> () // TODO: log

    // state is ThreadLocal, it means the resources gets initialized once per thread
    let state =
      let initializer () =
        // Allocate all objects needed for work
        let font        = new Font("Courier", 24.0F)
        let red         = new SolidBrush(Color.Red)
        let white       = new SolidBrush(Color.White)
        let bitmap      = new Bitmap(600,400)
        let g           = Graphics.FromImage bitmap
        let ms          = new MemoryStream 1024
        // disposer should be called when Thread is terminating to reclaim
        //  resources as fast as possible
        let disposer () =
          dispose ms
          dispose g
          dispose bitmap
          dispose white
          dispose red
          dispose font
        font, red, white, bitmap, g, ms, disposer

      new ThreadLocal<_>(initializer)

  // Draws text on a bitmap and returns that as a byte array
  let draw text =
    // Grab the state for the current thread
    let font, red, white, bitmap, g, ms, _ = Details.state.Value

    g.FillRectangle(white, 0.0F, 0.0F, 600.0F, 400.0F)
    g.DrawString(text, font, red, 10.0F, 40.0F)
    g.Flush()

    // Resets the memory stream
    //  The capacity is preserved meaning as long as the generated
    //  images is equal or smaller in size no realloc is needed
    ms.Seek (0L, SeekOrigin.Begin) |> ignore
    ms.SetLength 0L

    bitmap.Save(ms, ImageFormat.Png)

    // Here a new array is allocated per call
    //  Depending on how FillRectangle/DrawString works this is hopefully our
    //  only allocation
    ms.ToArray()

  // Disposes all BitmapCreator resources held by the current thread
  let dispose () =
    let _, _, _, _, _, _, disposer = Details.state.Value
    disposer ()

[<EntryPoint>]
let main argv =
  // Saves some bitmaps to file, the name include the thread pid in order
  //  to not overwrite other threads' images
  let save () =
    let texts = [|"Hello"; "There"|]
    let tid   = Thread.CurrentThread.ManagedThreadId
    for text in texts do
      File.WriteAllBytes (sprintf "%s_%d.png" text tid, BitmapCreator.draw text)

  // Runs a in other thread, disposes BitmapCreator resources when done
  let runInOtherThread (a : unit -> unit) =
    let a () =
      try
        a ()
      finally
        BitmapCreator.dispose ()
    let thread = Thread a
    thread.Start ()
    thread.Join ()

  Environment.CurrentDirectory <- AppDomain.CurrentDomain.BaseDirectory

  try
    save () // Here we allocate BitmapCreator resources
    save () // Since the same thread is calling the resources will reused
    runInOtherThread save // New thread, new resources
    runInOtherThread save // New thread, new resources
  finally
    BitmapCreator.dispose ()

  0
于 2016-06-18T18:23:04.750 回答
0

这个问题更普遍地涉及将图像直接保存到 .NET 中的套接字,而不是真正特定于 F# 或 Suave。在这个链接上有一些讨论,我认为基本上可以得出结论,无论是通过 MemoryStream 还是在图像上调用 .ToArray() ,您都会首先陷入创建临时缓冲区的困境。 使用 C# 通过套接字发送和接收图像

于 2016-06-18T16:24:16.993 回答