Есть ли способ передать изображение напрямую в проработанный поток ответов?
Я хочу динамически генерировать изображение на стороне сервера и отправлять его в браузер.
В настоящее время я использую 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#