2016-06-14 2 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 моего ПОДХОДА работает. Делается это после создания образа 'Save' на« MemoryStream », который затем преобразуется в массив и затем отправляется как ответ. Я чувствовал, что так или иначе это может быть отправлено непосредственно в ответ. – Adrian

+0

Вы можете либо передать Suave поток, как я показал вам выше, либо вы сами можете написать байты в сокет, используя функции sibling для 'transferStream'. Это зависит от вашей реализации Save. – Henrik

+0

Это не моя реализация 'Save', это метод класса System.Drawing.Image. '.net' api. Я не знаю, как получить к нему доступ. – Adrian

0

Этот вопрос относится в более общем сохранении изображения непосредственно к розетке в .NET, на самом деле не специфичны для F # или Учтивый. Существует несколько дискуссий по этой ссылке, которые, как я думаю, в основном заканчиваются, вы будете задерживаться с созданием временного буфера первым, будь то через MemoryStream или вызовом .ToArray() на изображении. Sending and receiving an image over sockets with C#

+0

Привет всем, чтобы яснее, что меня беспокоило в моем первоначальном вопросе, было необходимость использовать дополнительный объект, чтобы получить доступ к базовым данным Bitmap. Итак, хотя у меня уже есть изображение в памяти, мне нужно было создать объект перехода - MemoryStream', чтобы сделать копию данных в виде массива, который затем должен передать Суаве, чтобы доставить его в браузер. Я надеялся найти что-то похожее на узловые трубы: https://nodejs.org/api/stream.html#stream_event_pipe – Adrian

1

Я предполагаю, что вас беспокоит выделение ненужного объекта. Я считаю, что такая озабоченность заслуживает похвалы.

Возможно, что вы, вероятно, после этого - это API-интерфейс Bitmap, в котором байты PNG-изображения предоставляются через Stream<byte>, а затем Bitmap API создает, когда это необходимо, сокетом.

System.Drawing похоже, что не поддерживает такое поведение, возможно WIC делает (оболочки .NET существуют через отличную библиотеку SharpDX).

Однако это будет означать сохранение потенциально дорогостоящих объектов (растровых изображений, кистей и т. Д.) В течение всего периода передачи. Байт-массив может быть более эффективным способом хранения результата.

Другой подход во избежание ненужного выделения объектов - это их кеширование. Это немного сложнее, потому что объекты System.Drawing являются изменяемыми и небезопасными для использования из нескольких потоков. Тем не менее вы можете создать кеш на поток с помощью ThreadLocal.

В примере кода большинство объектов кэшируется в Thread. Единственный объект, созданный за один вызов до draw, - это возвращаемый массив байтов, но это, вероятно, эффективное хранилище данных PNG (возможно, вызовы System.Drawing выделяют объекты, но мы не можем контролировать их). Поскольку я не понял способ прослушать «смерть» Thread, это означает, что важно вручную уничтожить объекты с помощью метода dispose, когда Thread больше не нуждается в объектах.

Надеется, что это было интересно

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