Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does one use a memory stream instead of files when rendering Direct2D images via SharpDX?

The setup

Consider the given scratch program that uses SharpDX, a managed wrapper for Direct* libraries, to render a bitmap and save it as a PNG:

namespace ConsoleApplication5
{
    using System;
    using System.Diagnostics;
    using System.IO;
    using SharpDX;
    using SharpDX.Direct2D1;
    using SharpDX.DirectWrite;
    using SharpDX.DXGI;
    using SharpDX.IO;
    using SharpDX.WIC;
    using AlphaMode = SharpDX.Direct2D1.AlphaMode;
    using Bitmap = SharpDX.WIC.Bitmap;
    using D2DPixelFormat = SharpDX.Direct2D1.PixelFormat;
    using WicPixelFormat = SharpDX.WIC.PixelFormat;

    class Program
    {
        static void Main(string[] args)
        {
            var width = 400;
            var height = 100;
            var pixelFormat = WicPixelFormat.Format32bppBGR;

            var wicFactory = new ImagingFactory();
            var dddFactory = new SharpDX.Direct2D1.Factory();
            var dwFactory = new SharpDX.DirectWrite.Factory();

            var wicBitmap = new Bitmap(
                wicFactory,
                width,
                height,
                pixelFormat,
                BitmapCreateCacheOption.CacheOnLoad);

            var renderTargetProperties = new RenderTargetProperties(
                RenderTargetType.Default,
                new D2DPixelFormat(Format.Unknown, AlphaMode.Unknown),
                0,
                0,
                RenderTargetUsage.None,
                FeatureLevel.Level_DEFAULT);
            var renderTarget = new WicRenderTarget(
                dddFactory,
                wicBitmap,
                renderTargetProperties)
            {
                TextAntialiasMode = TextAntialiasMode.Cleartype
            };

            renderTarget.BeginDraw();

            var textFormat = new TextFormat(dwFactory, "Consolas", 48) 
            {
                TextAlignment = TextAlignment.Center, 
                ParagraphAlignment = ParagraphAlignment.Center
            };
            var textBrush = new SolidColorBrush(
                renderTarget,
                Colors.Blue);

            renderTarget.Clear(Colors.White);
            renderTarget.DrawText(
                "Hi, mom!",
                textFormat,
                new RectangleF(0, 0, width, height),
                textBrush);

            renderTarget.EndDraw();

            var stream = new WICStream(
                wicFactory,
                "test.png",
                NativeFileAccess.Write);

            var encoder = new PngBitmapEncoder(wicFactory);
            encoder.Initialize(stream);

            var frameEncoder = new BitmapFrameEncode(encoder);
            frameEncoder.Initialize();
            frameEncoder.SetSize(width, height);
            frameEncoder.PixelFormat = WicPixelFormat.FormatDontCare;
            frameEncoder.WriteSource(wicBitmap);
            frameEncoder.Commit();

            encoder.Commit();

            frameEncoder.Dispose();
            encoder.Dispose();
            stream.Dispose();

            Process.Start(Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "test.png")));
        }
    }
}

Running this program gives you a "test.png" file in the program's working directory with the following beautiful image:

Output Image

The question

Awesome, I've just rendered an image using Direct2D instead of GDI+, which is supposedly more supported in the context of an ASP.NET application. Plus, Direct2D is the new hotness.

Let's say that I wanted to write a function that rendered such an image and returned the PNG as a Stream or a byte[] array, performing the entire rendering and encoding operation in memory instead of writing to the file system. This is for a responding to a Web request; makes sense just to stream it out straight to the browser without going through the file system.

In GDI+, I could do this with a MemoryStream pretty easily, but I can't figure out how to use DataStream in SharpDX to my advantage without knowing the size of the buffer:

        var bufferSize = 1024 * 3; // how do I know?
        var buffer = new DataStream(
            bufferSize,
            true,
            true);
        var stream = new WICStream(
            wicFactory,
            buffer);
  • Do I have to P/Invoke to CreateStreamOnHGlobal and use that IntPtr to build my DataStream?
  • Is there some overload of DataStream that I am missing?
  • Is there an easy way to pre-calculate the necessary buffer needed to hold the encoded PNG image?
  • Or should I just get over going through the file system?

Thanks for any help!

like image 1000
Nicholas Piasecki Avatar asked Feb 05 '12 18:02

Nicholas Piasecki


People also ask

What is a memory stream?

MemoryStream encapsulates data stored as an unsigned byte array. The encapsulated data is directly accessible in memory. Memory streams can reduce the need for temporary buffers and files in an application. The current position of a stream is the position at which the next read or write operation takes place.

What is the difference between MemoryStream and FileStream?

As the name suggests, a FileStream reads and writes to a file whereas a MemoryStream reads and writes to the memory. So it relates to where the stream is stored.


2 Answers

The author of the library added this as a feature.

I'll leave the question around as I think the code provides a useful Direct2D sample for people.

like image 174
Nicholas Piasecki Avatar answered Sep 18 '22 04:09

Nicholas Piasecki


If anyone is looking to do this in asp.net:

var memStream = new MemoryStream();
var wicStream = new WICStream(wicFactory, memStream);

//Encode wic bitmap
var encoder = new PngBitmapEncoder(wicFactory);
encoder.Initialize(wicStream);
var frameEncoder = new BitmapFrameEncode(encoder);
frameEncoder.Initialize();
frameEncoder.SetSize(width, height);
var format = WicPixelFormat.FormatDontCare;
frameEncoder.SetPixelFormat(ref format);
frameEncoder.WriteSource(wicBitmap);
frameEncoder.Commit();
encoder.Commit();

//Clean-up
frameEncoder.Dispose();
encoder.Dispose();
wicStream.Dispose();

imgBackdrop.ImageUrl = "data:image/png;base64," + Convert.ToBase64String(memStream.ToArray(), 0, memStream.ToArray().Length);
like image 36
Family Avatar answered Sep 20 '22 04:09

Family