2016-04-05 16 views
0

Я заменил некоторые изворотливые подпрограммы GDI + с помощью SharpDX для загрузки бинарного изображения TIFF из потока, визуализации текста на нем и сохранения его обратно в формате TIFF в виде потока.SharpDX: BitmapFrameEncode.WriteSource занимает гораздо больше времени с некоторыми изображениями

Но код SharpDX занимает гораздо больше времени, чтобы сделать то же самое, и мне интересно, не делаю ли я что-то неправильно.

Как видно из образца здесь, у меня есть 2 различные функции:

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

Они в основном идентичны, и сделать примерно то же самое, кроме первого (RenderImageFromExistingImage) принимает существующий бинарный TIFF 1000x500 для использования как базовое изображение, а второе (SaveRenderedImage) создает растровое изображение WIC аналогичного размера с нуля.

Функция, которая принимает существующий файл занимает около 30-40ms выполнить, причем основная часть этого времени (~ 30ms), занимаемого BitmapFrameEncode.WriteSource. Эта функция эквивалентна заменяемому коду GDI +.

Функция, которая создает WicBitmap с нуля занимает 8-10ms выполнить, не занимая много времени в BitmapFrameEncode.WriteSource, что примерно такое же количество времени, как GDI + функции, которую она заменила. Единственное различие заключается в том, что эта функция не загружает предварительное изображение, которое мне нужно.

Почему BitmapFrameEncode.WriteSource (который, как представляется, тонкая оболочка вокруг IWICBitmapFrameEncode) так медленно BuildImageFromExistingImage, по сравнению с SaveRenderedImage?

Я думаю, что BuildImageFromExistingImage медленнее, потому что она делает дополнительную конверсию на подводящем изображения (с помощью FormatConverter), чтобы преобразовать его в формат пикселя, который D2D будет обрабатывать, и что штрафное время для этого делает не вступают в игру до BitmapFrameEncode.WriteSource происходит.

Есть ли что-то, что я делаю неправильно? Или WIC (компоненты обработки изображений Windows) просто медленны по сравнению с вызовами на основе GDI +?

В идеале мне нужен первый случай (BuildImageFromExistingImage), чтобы быть столь же быстро, как GDI + кода он заменил, и следовало ожидать, что должно можно сделать это так быстро, если не быстрее, чем GDI + кода оно предназначенный для замены.

ответ

-1

В Windows 7 и выше GDI + использует кодировщики и декодеры WIC, поэтому нет никакой разницы в действительном шаге кодирования. Если ваши два метода производят одни и те же пиксели, они будут кодироваться с одинаковой скоростью, и эта скорость будет такой же, как у GDI +.

Хотя может показаться, что WriteSource() является узким местом в вашем примере, это немного вводит в заблуждение.WIC использует ленивый конвейер для его обработки, что означает, что все выполняемые вами шаги задерживаются до тех пор, пока пиксели не будут запрошены с вызовом CopyPixels(). В вашем случае, так как вы никогда не вызываете CopyPixels(), этот вызов выполняется с помощью WriteSource(), и вся обработка выполняется в это время.

Я не очень хорошо знаком с SharpDX, но думаю, вы, вероятно, захотите использовать BitmapCreateCacheOption.CacheOnLoad при создании объекта WicBitmap, который вы используете для своего рисунка. Позволяя кодеру материализовать битмап, он будет делать одну линию сканирования за раз, что, вероятно, отрицательно скажется на производительности вашего чертежа. Я полагаю, что параметр CacheOnLoad немедленно реализует битмап, поэтому он будет декодироваться и преобразовываться в это время, а не во время кодирования. Если ничего другого, это может помочь вам изолировать узкое место.

Кроме того, я не знаю, приведет ли это к проблеме с производительностью, но BitmapPaletteType.MedianCut предназначен только для цветных пиксельных типов индексов. Как правило, вы должны использовать BitmapPaletteType.Custom, если вам не требуется квантование.

И, наконец, насколько я знаю, канонический формат пикселей для Direct2D равен PixelFormat.Format32bppPBGRA. Вы можете взять дополнительные накладные расходы, используя вариант PRGBA.

См. here для примера, делающего что-то похожее на ваше.

+0

Спасибо за советы и объяснения, я как-то понял, что 'WriteSource()' именно там, где все действительно произошло. 'CacheOnLoad' vs' CacheOnDemand' не имеет заметной разницы, также не 'Format32bppPBGRA' и' Format32bppPRGBA'. Я попробую 'BitmapPaletteType.Custom' и посмотрю, какие результаты я получу. –

+0

Заставляет ли 'CacheOnLoad' менять время? Я бы не ожидал, что это уменьшит общее время выполнения, но это должно повлиять на вызов «WriteSource». Кроме того, вы можете проверить значение 'pixFormat' после вашего вызова' bitmapFrameEncode.SetPixelFormat (ref pixFormat) '. Если формат входного изображения не поддерживается кодировщиком, он выполнит преобразование во время сохранения и сообщит вам формат, в который он будет преобразовываться. Это скажет вам, несет ли сама по себе стоимость конверсии. – saucecontrol