2016-06-14 7 views
6

サーバー側で画像を動的に生成してブラウザに送信したいと考えています。イメージを直接suave応答ストリームにパイプする方法はありますか?

現在、私はMemoryStreamを使用してに変換してから通常の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

ました。何がしているのは、 'MemoryStream'で画像を' Save'した後、配列に変換してレスポンスとして送信することです。私の気持ちは、どういうわけか、それを直接の返信として送ることができるということでした。 – Adrian

+0

上記のように、Suaveストリームを渡すこともできますし、兄弟関数を使って 'transferStream'にバイトをソケットに書き込むこともできます。保存の実装によって異なります。 – Henrik

+0

'Save'の私の実装ではなく、' System.Drawing.Image'クラスのメソッドです。 '.net' API。私はそれにアクセスする方法を知らない。 – Adrian

0

この質問は、.NETのソケットに直接F#やサーブには実際に特定の画像を保存するには、一般に、より関連します。このリンクには、基本的に、MemoryStreamを使用するか、Imageの.ToArray()を呼び出すかのいずれかで、一時バッファの作成に悩まされていると思われるこのリンクでの議論があります。 Sending and receiving an image over sockets with C#

+0

こんにちは皆、私の元来の質問で私を悩ましていたのは、ビットマップの基礎データにアクセスするためだけに追加のオブジェクトを使う必要があったからです。そのため、既にイメージをメモリに入れている間に、データのコピーを配列として渡してから、Suaveに渡してブラウザに渡すために、単に「MemoryStream」というトランジションオブジェクトを作成する必要がありました。 私はnodejsパイプと類似のものを見つけることを望んでいました:https://nodejs.org/api/stream.html#stream_event_pipe – Adrian

1

あなたは不要なオブジェクトの割り当てを心配していると思います。私はそのような懸念が称賛であると思う。

あなたがたどり着いたのは、PNG画像のバイトがStream<byte>で提供されているビットマップAPIです。ビットマップAPIは、ソケットで必要なときに生成します。

System.Drawingは、このような動作をサポートしていないようです。可能であればWIC(.NETラッパーは優れたSharpDXライブラリを通じて存在します)。

しかし、これは、転送の間、潜在的に高価なオブジェクト(ビットマップ、ブラシなど)を有効に保つことを意味します。バイト配列は、結果を格納するためのより効率的な方法かもしれません。

オブジェクトを不必要に割り当てるのを避けるためのもう1つのアプローチは、それらをキャッシュすることです。 System.Drawingオブジェクトは変更可能であり、複数のスレッドから使​​用することが安全ではないため、もう少し問題があります。ただし、ThreadLocalを使用してスレッドごとにキャッシュを作成できます。

以下のサンプルコードでは、ほとんどのオブジェクトはThreadごとにキャッシュされています。 drawへの呼び出しごとに作成される唯一のオブジェクトは返されるバイト配列ですが、おそらくPNGデータの効率的な格納です(System.Drawingの呼び出しが可能ですが、それ以上の制御はありません)。スレッドの「死」を聞く方法を理解していないので、スレッドがオブジェクトを必要としなくなったときに、disposeメソッドを使用してオブジェクトを手動で破棄することが重要です。

ホープこれは、@henrikこんにちは、私は私のaproachが動作しているavoid.Basicallyたかっ `MemoryStream`興味深い

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 
関連する問題