我想在服务器端动态生成图像并将其发送到浏览器。
目前,我使用MemoryStream
将其转换为byte array
,然后通常使用api 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
部分。
谢谢!
答案 0 :(得分: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
)
答案 1 :(得分:1)
我假设您担心分配不必要的对象。我认为这种担忧值得称赞。
你可能会想到的是一个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
答案 2 :(得分:0)
这个问题更普遍地涉及将图像直接保存到.NET中的套接字,而不是特定于F#或Suave。在这个链接上有一些讨论,我认为基本上可以得出结论,你将首先考虑创建一个临时缓冲区,无论是通过MemoryStream还是在图像上调用.ToArray()。 Sending and receiving an image over sockets with C#