SharpDX: BitmapFrameEncode.WriteSource занимает больше времени с некоторыми изображениями

Я заменил некоторые изворотливые подпрограммы GDI+ на SharpDX, чтобы загрузить битональное изображение TIFF из потока, отобразить на нем текст, а затем сохранить его в формате TIFF как поток.

Но код 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-40 мс, причем большая часть этого времени (~30 мс) занимает BitmapFrameEncode.WriteSource. Эта функция эквивалентна замененному коду GDI+.

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

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

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

Есть ли что-то, что я делаю не так? Или WIC (Windows Imaging Components) просто медленный по сравнению с вызовами на основе 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.

Смотрите здесь пример, делающий что-то похожее на ваше.

Другие вопросы по тегам