2016-04-05 7 views
0

Ich hatte einige zwielichtige GDI + Routinen mit SharpDX ersetzt, um ein bitonales TIFF-Bild aus einem Stream zu laden, Text darauf zu rendern und dann wieder als Stream in das TIFF-Format zu speichern.SharpDX: BitmapFrameEncode.WriteSource dauert viel länger mit einigen Bildern

Aber der SharpDX-Code dauert viel länger, um das Gleiche zu tun, und ich frage mich, ob ich etwas falsch mache.

Wie Sie aus der Probe hier sehen können, habe ich 2 verschiedene Funktionen:

  • 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(); 
          } 
         } 
        } 
    } 
    

Sie sind meist identisch und machen etwa die Dasselbe gilt, bis auf das erste (RenderImageFromExistingImage), das ein vorhandenes 1000x500 bitonales TIFF-Bild verwendet als das Basisbild, und das zweite (SaveRenderedImage) erstellt eine WIC-Bitmap mit ähnlicher Größe von Grund auf neu.

Die Funktion, die ein vorhandenes Bild nimmt dauert etwa 30-40ms auszuführen, wobei der Großteil dieser Zeit (~ 30 ms) durch BitmapFrameEncode.WriteSource aufgenommen. Diese Funktion entspricht dem GDI + -Code, der ersetzt wurde.

Die Funktion, die eine von Grund auf neu erstellt WicBitmap 8-10ms nimmt in BitmapFrameEncode.WriteSource erhebliche Zeit auszuführen, ohne dabei, die in etwa die gleiche Zeit wie die GDI + Funktion ist, die es ersetzt. Der einzige Unterschied ist, dass diese Funktion kein bestehendes Bild lädt, was ich brauche.

Warum ist BitmapFrameEncode.WriteSource so langsam in BuildImageFromExistingImage, im Vergleich zu SaveRenderedImage (die eine dünne Hülle um IWICBitmapFrameEncode zu sein scheint)?

Meine Vermutung ist, dass BuildImageFromExistingImage langsamer ist, weil es eine zusätzliche Konvertierung auf dem eingehenden Bild tut (FormatConverter verwenden), um es zu einem Pixel-Format zu konvertieren, die D2D behandelt, und dass die Zeitstrafe für das Tun dies tut nicht ins Spiel kommen, bis BitmapFrameEncode.WriteSource passiert.

Gibt es etwas, was ich falsch mache? Oder sind WIC (Windows Imaging Components) nur langsam im Vergleich zu GDI + -Anrufen?

Idealerweise muss ich den ersten Fall (BuildImageFromExistingImage) so schnell wie die GDI + -Code es ersetzt werden, und würde erwarten, dass es sollte möglich sein, es so schnell zu machen, wenn nicht schneller, als die GDI + -Code ist soll ersetzt werden.

Antwort

-1

Unter Windows 7 und höher werden in GDI + die WIC-Encoder und -Decoder verwendet, sodass beim eigentlichen Codierungsschritt kein Unterschied besteht. Wenn Ihre beiden Methoden dieselben Pixel erzeugen, werden sie mit der gleichen Geschwindigkeit codiert, und diese Geschwindigkeit entspricht der von GDI +.

Obwohl es scheint, WriteSource() ist der Flaschenhals in Ihrem Beispiel, das ist ein bisschen irreführend.WIC verwendet für die Verarbeitung eine Lazy-Pipeline. Dies bedeutet, dass alle von Ihnen ausgeführten Schritte verzögert werden, bis die Pixel mit einem Aufruf an CopyPixels() angefordert werden. In Ihrem Fall, da Sie CopyPixels() sich nie nennen, wird dieser Anruf von der WriteSource() gemacht, und die gesamte Verarbeitung zu diesem Zeitpunkt durchgeführt.

SharpDX ist mir nicht wirklich bekannt, aber ich schätze, Sie möchten wahrscheinlich BitmapCreateCacheOption.CacheOnLoad verwenden, wenn Sie das WicBitmap-Objekt erstellen, das Sie für Ihre Zeichnung verwenden. Wenn Sie dem Encoder erlauben, die Bitmap zu materialisieren, bedeutet dies, dass es jeweils eine Scanlinie gleichzeitig ausführt, was sich wahrscheinlich negativ auf die Zeichnungsleistung auswirkt. Ich glaube, dass die CacheOnLoad-Option die Bitmap sofort materialisiert, so dass sie zu diesem Zeitpunkt dekodiert und konvertiert wird, anstatt während der Codierung. Nicht zuletzt hilft Ihnen das, den Engpass zu isolieren.

Ich weiß auch nicht, ob dies zu einem Leistungsproblem führt, aber BitmapPaletteType.MedianCut ist nur für Indexfarbpixeltypen gedacht. Normalerweise würden Sie BitmapPaletteType.Custom verwenden, wenn Sie keine Quantisierung benötigen.

Und schließlich, soweit ich weiß, ist das kanonische Pixelformat für Direct2D PixelFormat.Format32bppPBGRA. Durch die Verwendung der PRGBA-Variante können zusätzliche Kosten entstehen.

Siehe here für ein Beispiel etwas ähnlich wie bei Ihnen zu tun.

+0

Danke für die Tipps und Erklärungen, ich irgendwie dachte, dass 'WriteSource()' gerade ist, wo alles wirklich passiert ist. 'CacheOnLoad' vs' CacheOnDemand' keinen nennenswerten Unterschied machen, ebenso wenig wie 'Format32bppPBGRA' vs' Format32bppPRGBA'. Ich werde 'BitmapPaletteType.Custom' versuchen und sehen, welche Ergebnisse ich bekommen. –

+0

Verschiebt der CacheOnLoad das Timing überhaupt? Ich würde nicht erwarten, dass dadurch die Ausführungszeit insgesamt abnimmt, aber es sollte einige Auswirkungen auf den 'WriteSource'-Aufruf haben. Vielleicht möchten Sie auch den Wert von 'pixFormat' nach dem Aufruf von' bitmapFrameEncode.SetPixelFormat (ref pixFormat) 'überprüfen. Wenn das Format Ihres Eingabebildes vom Encoder nicht unterstützt wird, wird beim Speichern eine Konvertierung vorgenommen und Ihnen wird das Format mitgeteilt, in das konvertiert wird. Das würde Ihnen sagen, ob der Speicher selbst einen Umwandlungsaufwand verursacht. – saucecontrol

Verwandte Themen