I had replaced some dodgy GDI+ routines with SharpDX to load a bitonal TIFF image from a Stream, render text on it, then save it back to TIFF format as a Stream.
But the SharpDX code is taking much longer to do the same thing, and I'm wondering if I am doing something wrong.
As you can see from the sample here, I have 2 different functions:
- RenderImageFromExistingImage
SaveRenderedImage
using System; using System.Diagnostics; using System.IO; using SharpDX; using SharpDX.Direct2D1; using SharpDX.DirectWrite; using SharpDX.DXGI; using SharpDX.WIC; using Factory = SharpDX.Direct2D1.Factory; using FactoryType = SharpDX.Direct2D1.FactoryType; using PixelFormat = SharpDX.WIC.PixelFormat; using WicBitmap = SharpDX.WIC.Bitmap; public class ImageCreator2 { private static ImagingFactory _wicFactory; private static Factory _d2DFactory; private static SharpDX.DirectWrite.Factory _dwFactory; private int _imageWidth = 1000, _imageHeight = 500; private readonly int _imageDpi = 96; public ImageCreator2() { _wicFactory = new ImagingFactory(); _d2DFactory = new Factory(FactoryType.SingleThreaded); _dwFactory = new SharpDX.DirectWrite.Factory(SharpDX.DirectWrite.FactoryType.Shared); } private void RenderImage(WicRenderTarget renderTarget) { using (var blackBrush = new SolidColorBrush(renderTarget, Color4.Black)) using (var tformat = new TextFormat(_dwFactory, "Arial", 30f)) using (var tformat2 = new TextFormat(_dwFactory, "Arial", 11f)) { renderTarget.BeginDraw(); renderTarget.Clear(Color.White); renderTarget.DrawText("TEST", tformat, new RectangleF(300f, 30f, 100f, 20f), blackBrush); renderTarget.DrawText("MORE TEST", tformat2, new RectangleF(30f, 150f, 100f, 20f), blackBrush); renderTarget.DrawLine(new Vector2(0f, 25f), new Vector2(500f, 25f), blackBrush); renderTarget.DrawLine(new Vector2(0f, 210f), new Vector2(500f, 210f), blackBrush); renderTarget.EndDraw(); } } public void BuildImageFromExistingImage(byte[] image, Stream systemStream) { using (var checkStream = new MemoryStream(image)) using ( var inDecoder = new BitmapDecoder(_wicFactory, checkStream, DecodeOptions.CacheOnDemand)) using (var converter = new FormatConverter(_wicFactory)) { if (inDecoder.FrameCount > 0) { using (var frame = inDecoder.GetFrame(0)) { converter.Initialize(frame, PixelFormat.Format32bppPRGBA, BitmapDitherType.None, null, 0.0f, BitmapPaletteType.MedianCut); _imageWidth = converter.Size.Width; _imageHeight = converter.Size.Height; } } else { throw new Exception(); } var renderProperties = new RenderTargetProperties( RenderTargetType.Software, new SharpDX.Direct2D1.PixelFormat(Format.Unknown, AlphaMode.Unknown), _imageDpi, _imageDpi, RenderTargetUsage.None, FeatureLevel.Level_DEFAULT); using (var wicBitmap = new WicBitmap( _wicFactory, converter, BitmapCreateCacheOption.CacheOnDemand)) using ( var renderTarget = new WicRenderTarget(_d2DFactory, wicBitmap, renderProperties)) { RenderImage(renderTarget); using ( var encoder = new BitmapEncoder(_wicFactory, ContainerFormatGuids.Tiff)) { encoder.Initialize(systemStream); using (var bitmapFrameEncode = new BitmapFrameEncode(encoder)) { var pixFormat = PixelFormat.Format32bppPRGBA; bitmapFrameEncode.Initialize(); bitmapFrameEncode.SetSize(_imageWidth, _imageHeight); bitmapFrameEncode.SetResolution(96, 96); bitmapFrameEncode.SetPixelFormat(ref pixFormat); //This takes 30-40ms per image. var watch = new Stopwatch(); try { watch.Start(); bitmapFrameEncode.WriteSource(wicBitmap); } finally { watch.Stop(); } Console.WriteLine("Saved real image in {0} ms.", watch.Elapsed.TotalMilliseconds); bitmapFrameEncode.Commit(); } encoder.Commit(); } } } } public void SaveRenderedImage(Stream systemStream) { var renderProperties = new RenderTargetProperties( RenderTargetType.Default, new SharpDX.Direct2D1.PixelFormat(Format.Unknown, AlphaMode.Unknown), _imageDpi, _imageDpi, RenderTargetUsage.None, FeatureLevel.Level_DEFAULT); using (var wicBitmap = new WicBitmap( _wicFactory, _imageWidth, _imageHeight, PixelFormat.Format32bppBGR, BitmapCreateCacheOption.CacheOnDemand )) using (var renderTarget = new WicRenderTarget(_d2DFactory, wicBitmap, renderProperties)) { RenderImage(renderTarget); using ( var encoder = new BitmapEncoder(_wicFactory, ContainerFormatGuids.Tiff)) { encoder.Initialize(systemStream); using (var bitmapFrameEncode = new BitmapFrameEncode(encoder)) { bitmapFrameEncode.Initialize(); bitmapFrameEncode.SetSize(_imageWidth, _imageHeight); bitmapFrameEncode.SetResolution(_imageDpi, _imageDpi); //This takes 8-10ms per image. var watch = new Stopwatch(); try { watch.Start(); bitmapFrameEncode.WriteSource(wicBitmap); } finally { watch.Stop(); } Console.WriteLine("Saved generated image in {0} ms.", watch.Elapsed.TotalMilliseconds); bitmapFrameEncode.Commit(); } encoder.Commit(); } } } }
They are mostly identical, and do roughly the same thing, except the first one (RenderImageFromExistingImage) takes in an existing 1000x500 bitonal TIFF image to use as the base image, and the second one (SaveRenderedImage) creates a similarly-sized WIC Bitmap from scratch.
The function that takes an existing image takes about 30-40ms to execute, with the bulk of that time (~30ms) taken up by BitmapFrameEncode.WriteSource. This function is equivalent to the GDI+ code that was replaced.
The function that creates a WicBitmap from scratch takes 8-10ms to execute, without taking significant time in BitmapFrameEncode.WriteSource, which is roughly the same amount of time as the GDI+ function which it replaced. The only difference is this function is not loading a prexisting image, which is what I need.
Why is BitmapFrameEncode.WriteSource (which appears to be a thin wrapper around IWICBitmapFrameEncode) so slow in BuildImageFromExistingImage, compared to SaveRenderedImage?
My guess is that BuildImageFromExistingImage is slower because it is doing an extra conversion on the incoming image (using FormatConverter), to convert it to a pixel format that D2D will handle, and that the time penalty for doing this does not come into play until BitmapFrameEncode.WriteSource happens.
Is there something I am doing wrong? Or are WIC (Windows Imaging Components) just slow compared to GDI+-based calls?
Ideally I need the first case (BuildImageFromExistingImage) to be as fast as the GDI+ code it replaced, and would expect that it should be possible to make it as fast, if not faster, than the GDI+ code it is intended to replace.