2016-06-14 14 views
6

Desidero generare dinamicamente un'immagine sul lato server e inviarla al browser.C'è un modo per reindirizzare un'immagine direttamente ad un flusso di risposta soave?

Attualmente sto usando un MemoryStream per convertirlo in byte array e poi al solito suave api. Vedi sotto:

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") 

sento che la parte MemoryStream può essere evitato se soave ci sta permettendo al tubo direttamente nel flusso di risposta.

Grazie!

risposta

2

Che, fondamentalmente, fare questo:

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

Hi @henrik, il 'MemoryStream' ho voluto avoid.Basically mia aproach sta lavorando. Quello che sta facendo è dopo aver generato l'immagine 'Salva' su un' MemoryStream' che poi viene convertito in array e quindi inviato come risposta. La mia sensazione è che in qualche modo possa essere inviato direttamente come risposta. – Adrian

+0

Puoi mandare Suave in streaming, come ti ho mostrato sopra, oppure puoi scrivere byte nello zoccolo usando le funzioni di pari livello su 'transferStream'. Dipende dalla tua implementazione di Salva. – Henrik

+0

Non è la mia implementazione di 'Save', è il metodo di classe' System.Drawing.Image'. L'API '.net'. Non so come accedervi. – Adrian

0

Questa domanda si riferisce più in generale per il salvataggio di un'immagine direttamente ad una presa a NET, non proprio specifico per F # o Suave. C'è un po 'di discussione a questo link che penso fondamentalmente conclude che sarete bloccati prima con la creazione di un buffer temporaneo, tramite MemoryStream o chiamando .ToArray() sull'immagine. Sending and receiving an image over sockets with C#

+0

Ciao a tutti, per renderlo più chiaro, ciò che mi ha infastidito nella mia domanda iniziale era la necessità di utilizzare un oggetto aggiuntivo solo per ottenere l'accesso ai dati sottostanti di Bitmap. Quindi, mentre avevo già l'immagine in memoria, avevo bisogno di creare un oggetto di transizione, 'MemoryStream' solo per fare una copia dei dati come una matrice che poi sarebbe stata consegnata a Suave per consegnarla al browser. Spero di trovare qualcosa di simile alle pipe nodejs: https://nodejs.org/api/stream.html#stream_event_pipe – Adrian

1

Suppongo che tu sia preoccupato di allocare un oggetto non necessario. Penso che una tale preoccupazione sia lodevole.

Ciò che si è probabilmente dopo è un'API Bitmap in cui i byte dell'immagine PNG vengono forniti tramite Stream<byte> e l'API Bitmap viene quindi prodotta quando necessario dal socket.

System.Drawing non sembra supportare tale comportamento, possibilmente WIC (i wrapper .NET esistono attraverso l'eccellente libreria SharpDX).

Tuttavia, ciò significherebbe mantenere in vita oggetti potenzialmente costosi (bitmap, pennelli e così via) per la durata del trasferimento. L'array di byte potrebbe essere un modo più efficiente per archiviare il risultato.

Un altro approccio per evitare di allocare gli oggetti inutilmente è il loro caching. È reso un po 'più problematico perché gli oggetti System.Drawing sono mutabili e non sono sicuri da utilizzare da più thread. È tuttavia possibile creare una cache per thread utilizzando ThreadLocal.

Nel seguente codice di esempio, la maggior parte degli oggetti viene memorizzata nella cache per Thread. L'unico oggetto creato per chiamata a draw è l'array di byte restituito, ma probabilmente è un'archiviazione efficiente dei dati PNG (è possibile che le chiamate System.Drawing assegnino gli oggetti ma non ne abbiamo il controllo). Poiché non ho trovato un modo per ascoltare la "morte" di una discussione, significa che è importante disporre manualmente gli oggetti usando il metodo dispose quando il Thread non ha più bisogno degli oggetti.

Spero che questo è stato interessante

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