Saturday, 15 September 2012

Exporting an image from a DirectX 2D Image in Windows 8 C#/XAML Using SharpDX - PART 2


In my previous article I talked about how to save and image created by and Effect. I’ve been doing more work on my own project and worked out how to render a 2D drawing and export it.
The trick is to re-draw the image onto a bitmap.
If you create a render method like this one which draws a gradient:

private void Render(DeviceContext context2D)
{
  context2D.BeginDraw();

  var size = context2D.PixelSize;

  // Add brush fill
  var stops = new GradientStopCollection(context2D, new GradientStop[] {
  new GradientStop() { Color = Colors.Green, Position = 0 },
  new GradientStop() { Color = Colors.Yellow, Position = 1f }});

  var brushProps = new LinearGradientBrushProperties();
          brushProps.StartPoint = new DrawingPointF(size.Width / 2, 0);
            brushProps.EndPoint = new DrawingPointF(size.Width / 2, size.Height);

  var brush = new LinearGradientBrush(context2D, brushProps, stops);
  context2D.FillRectangle(new RectangleF(0, 0, (float)size.Width, (float)size.Height), brush);

  context2D.EndDraw();
}

It  can be used by the SurfaceImageSource’s render event and then reused for the export. To do the save, a clean 2D Device and DeviceContext needs creating from the default 3D device, then an image is created and the drawing rendered onto it:

public async void Save()
{
  // Get 3D device for creating 2D device
  var device3D =
  new SharpDX.Direct3D11.Device(SharpDX.Direct3D.DriverType.Hardware,
                    SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport);

  // Get DXGI device
  var dxgiDevice2 = device3D.QueryInterface<SharpDX.DXGI.Device2>();           
 
  // Create new 2D device
  var device2D = new Device(dxgiDevice2);
  var context2D = new DeviceContext(device2D, DeviceContextOptions.None);

  // Create image
  SharpDX.Direct2D1.PixelFormat format = new SharpDX.Direct2D1.PixelFormat(SharpDX.DXGI.Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied);
  SharpDX.Direct2D1.BitmapProperties1 props = new BitmapProperties1(format,
                Windows.Graphics.Display.DisplayProperties.LogicalDpi,
                Windows.Graphics.Display.DisplayProperties.LogicalDpi,
                BitmapOptions.Target | BitmapOptions.CannotDraw);
  SharpDX.Direct2D1.Bitmap1 saveImage = new Bitmap1(context2D,   this._deviceManager.ContextDirect2D.PixelSize, props);
  context2D.Target = saveImage;

  // Render
  this.Render(context2D);

Now we have finally caught our drawing and we can export it in the same way as the last article:

// Launch file picker
Windows.Storage.Pickers.FileSavePicker picker = new Windows.Storage.Pickers.FileSavePicker();
picker.FileTypeChoices.Add("Png", new List<string>() { ".png" });
var file = await picker.PickSaveFileAsync();
if (file == null)
  return;

// Create imaging factory
var factory = new ImagingFactory();

// Get stream
var fStream = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);          
MemoryStream ms = new MemoryStream(100000);
ms.Position = 0;

// Create a WIC outputstream
WICStream stream = new WICStream(factory, ms);

var size = context2D.PixelSize;

// Initialize a Jpeg encoder with this stream
var wicBitmapEncoder = new SharpDX.WIC.PngBitmapEncoder(factory, BitmapEncoderGuids.Png);
wicBitmapEncoder.Initialize(stream);

// Create a Frame encoder
var wicFrameEncoder = new BitmapFrameEncode(wicBitmapEncoder);
wicFrameEncoder.Initialize();

// Create image encoder
ImageEncoder wicImageEncoder;
SharpDX.WIC.ImagingFactory2 factory2 = new ImagingFactory2();
factory2.CreateImageEncoder(device2D, out wicImageEncoder);

var imgParams = new ImageParameters();
imgParams.PixelFormat = format;
imgParams.PixelHeight = (int)size.Height;
imgParams.PixelWidth = (int)size.Width;
wicImageEncoder.WriteFrame(saveImage, wicFrameEncoder, ref imgParams);

// Commit changes
wicFrameEncoder.Commit();
wicBitmapEncoder.Commit();

byte[] buffer = new byte[ms.Length];
ms.Position = 0;
await ms.ReadAsync(buffer, 0, (int)ms.Length);
var opStream = fStream.AsStreamForWrite();
await opStream.WriteAsync(buffer, 0, buffer.Length);
await opStream.FlushAsync();

// Dispose
wicFrameEncoder.Dispose();
wicBitmapEncoder.Dispose();
stream.Dispose();
ms.Dispose();
opStream.Dispose();
factory.Dispose();

Well, I have a few more issues of my own to solve, but this is all the tough stuff done!

4 comments:

  1. How can I render text inside of an image using SharpDX?? I want to render a final picture like a meme, two text boxes, one at the bottom and one at the top. Thanks

    ReplyDelete
  2. Take a look at the SharpDX DirectWrite samples

    ReplyDelete
  3. Please let us know the sharpdx version and the headers you included... in SharpDX things like "Device" are defined in numerous places... I can't make your code work!

    Even better, post the full source =) ... thanks for this article anyhow.

    ReplyDelete
  4. V2.4.2

    Usings:
    using SharpDX;
    using SharpDX.Direct2D1;
    using SharpDX.DirectWrite;
    using SharpDX.WIC;

    ReplyDelete