2016-06-14 34 views
6

我想動態生成服務器端的圖像並將其發送到瀏覽器。有沒有辦法將圖像直接傳遞給一個和藹的響應流?

當前我使用的是MemoryStream將其轉換爲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") 

我覺得MemoryStream部分可如果倜儻使我們能夠直接管到響應流是可以避免的。

謝謝!

回答

2

你基本上做到這一點:

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

嗨@henrik的'MemoryStream'我想avoid.Basically我的aproach工作。所做的是在將圖像'Save'保存到'MemoryStream'後,然後將其轉換爲數組,然後作爲響應發送。我的感覺是,它可以作爲迴應直接發送。 – Adrian

+0

您可以手動Suave一個流,就像我上面顯示的那樣,或者您可以使用同級函數'transferStream'自己將字節寫入套接字。這取決於您的保存實施。 – Henrik

+0

這不是我的'Save'實現,它是'System.Drawing.Image'類方法。 '.net' api。我不知道如何訪問它。 – Adrian

0

這個問題涉及更普遍,直接保存圖像在.NET中的插座,沒有真正具體到F#或倜儻。在這個鏈接上有一些討論,我認爲基本上你得先創建一個臨時緩衝區,無論是通過MemoryStream還是在圖像上調用.ToArray()。 Sending and receiving an image over sockets with C#

+0

大家好,爲了讓它更清晰,在我原來的問題中,困擾我的是需要使用額外的對象來訪問位圖底層數據。所以,在內存中已經有了鏡像的時候,我需要創建一個轉換對象,'MemoryStream'只是將數據複製成一個數組,然後交給Suave將其傳送給瀏覽器。 我希望找到類似於nodejs管道的東西:https://nodejs.org/api/stream.html#stream_event_pipe – Adrian

1

我假設你關心分配一個不必要的對象。我認爲這樣的擔憂是值得讚揚的。

您可能之後是一個位圖API,其中通過Stream<byte>提供PNG圖像的字節,然後在套接字需要時生成位圖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 
相關問題