Есть ли способ передать изображение напрямую в проработанный поток ответов?

Я хочу динамически генерировать изображение на стороне сервера и отправлять его в браузер.

В настоящее время я использую MemoryStream преобразовать его в byte array а затем обычный учтивый 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")

Я чувствую, что MemoryStream части можно избежать, если suave позволяет нам напрямую передавать поток ответов.

Спасибо!

3 ответа

Вы в основном делаете это:

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
)

Я предполагаю, что вы обеспокоены выделением ненужного объекта. Я думаю, что такая забота заслуживает высокой оценки.

Вероятно, вам нужен Bitmap API, в котором байты изображения PNG предоставляются через Stream<byte> и Bitmap 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

Этот вопрос в более общем плане относится к сохранению изображения непосредственно в сокет в.NET, а не к F# или Suave. По этой ссылке есть некоторые обсуждения, которые, я думаю, в основном заключают, что вы сначала застрянете при создании временного буфера, будь то с помощью MemoryStream или вызова. ToArray() для изображения. Отправка и получение изображения через сокеты с C#

Другие вопросы по тегам