6

C# でイメージ サービスを作成しました。このサービスは、ベース レイヤー イメージ (JPG) を受け取り、もう 1 つ透明な PNG (32 ビット) をレイヤー化し、最終的な JPG イメージを出力します。この関数から最後のミリ秒をすべて絞り出そうとしていますが、GDI+ の DrawImage 呼び出しでコードがボトルネックになっています。マネージ コードは次のとおりです。

// Load base image and create graphics
Image image = LoadImage(renderSettings.RenderedImageDirectory + baseLayer);

Graphics graphics = Graphics.FromImage(image);              
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; 
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighSpeed;

// Draw additional layers to final image
for (int i = 1; i < renderLayers.Count; i++) {
    // SLOW -- LoadImage just a utility method that returns an Image from disk or cache
    graphics.DrawImage(LoadImage(renderSettings.RenderedImageDirectory + renderLayers[i]), 0, 0, image.Width, image.Height);            
}

if (graphics != null) graphics.Dispose();

ここで、P/Invoke で GDI を直接呼び出すことによって得られるパフォーマンスの向上について読み、DrawImage 呼び出しを置き換えることを試みました。単体テストを作成して、JPG をロードし、その上に 1 つの透明な PNG を重ねるという同じ機能を再現しようとしました。

参照: http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/29582142-0068-40dd-bd99-4b3883a76350

Bitmap sourceImage = new Bitmap("c:\\base.jpg");
Bitmap overlayImage = new Bitmap("c:\\layer1.png");

// NOTE: ImageHelper is a utility class containing all the P/Invoke stuff

// Get source image in memory
Graphics sourceImageGraphics = Graphics.FromImage(sourceImage);
IntPtr sourceImageHDC = sourceImageGraphics.GetHdc();
IntPtr sourceImageCDC = ImageHelper.CreateCompatibleDC(sourceImageHDC);
IntPtr sourceImageHandle = sourceImage.GetHbitmap();
ImageHelper.SelectObject(sourceImageCDC, sourceImageHandle);

// Get overlay image in memory
Graphics overlayImageGraphics = Graphics.FromImage(overlayImage);
IntPtr overlayImageHDC = overlayImageGraphics.GetHdc();
IntPtr overlayImageCDC = ImageHelper.CreateCompatibleDC(overlayImageHDC);
IntPtr overlayImageHandle = overlayImage.GetHbitmap();

ImageHelper.SelectObject(overlayImageCDC, overlayImageHandle);
ImageHelper.BitBlt(sourceImageHDC, 0, 0, sourceImage.Width, sourceImage.Height, overlayImageCDC, 0, 0, ImageHelper.TernaryRasterOperations.SRCAND);
ImageHelper.AlphaBlend(sourceImageHDC, 0, 0, sourceImage.Width, sourceImage.Height, overlayImageCDC, 0, 0, sourceImage.Width, sourceImage.Height, new ImageHelper.BLENDFUNCTION(ImageHelper.AC_SRC_OVER, 0, 0xff, ImageHelper.AC_SRC_ALPHA));

// Release source Image memory.
ImageHelper.DeleteDC(sourceImageCDC);
ImageHelper.DeleteObject(sourceImageHandle);
sourceImageGraphics.ReleaseHdc(sourceImageHDC);
sourceImageGraphics.Dispose();

// Release overlay Image memory.
ImageHelper.DeleteDC(overlayImageCDC);
ImageHelper.DeleteObject(overlayImageHandle);
overlayImageGraphics.ReleaseHdc(overlayImageHDC);
overlayImageGraphics.Dispose();

// Save to jpg
sourceImage.Save("c:\\output.jpg", ImageFormat.Jpeg);

しかし、これは階層化されたイメージを生成できません。ベースJPGなしのPNGのみ。どうすればいいですか?ストレート GDI に関して言えば、私は自分のリーグから少し外れています。

4

2 に答える 2

8

最終的にSharpDXを使用して、WIC と Direct2d API の両方にアクセスしました。結果は控えめに言っても印象的です。Direct2d で合成すると、パフォーマンスが GDI+ よりも 400 ~ 500% も向上しています。

また、GDI+ と Task Parallel Library を試して、画像を 4 つの象限に分割し、各コアで合成作業を行いました。結果は、SharpDX を使用した場合ほど重要ではありませんでした。

これが私が最終的に使用したコードです。「renderSettings」への参照は単なる構成オブジェクトです。renderLayer イメージ リストとともに、必要に応じて置き換えます。

/* SharpDX */
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.DirectWrite;
using SharpDX.DXGI;
using SharpDX.IO;
using SharpDX.WIC;
using AlphaMode = SharpDX.Direct2D1.AlphaMode;
using WicBitmap = SharpDX.WIC.Bitmap;
using D2DPixelFormat = SharpDX.Direct2D1.PixelFormat;
using WicPixelFormat = SharpDX.WIC.PixelFormat;
using Rectangle = System.Drawing.Rectangle;
using Bitmap = System.Drawing.Bitmap;

public Image FlattenImageDirect2d()
{

    List<string> renderLayers = new List<string>()
    {
        "image1.jpg", "image1.png", "image2.png", "image3.png", "image4.png", "image5.png", "image6.png", "image7.png"
    };

    // Base image
    string baseLayer = renderLayers[0];

    // Create WIC and D2D factories
    var wicFactory = new ImagingFactory();
    var ddFactory = new SharpDX.Direct2D1.Factory();

    // Get image size using WIC
    int baseWidth, baseHeight;
    using (var wicStream = new WICStream(wicFactory, renderDirectory + baseLayer, NativeFileAccess.Read)) {
        var jpegDecoder = new JpegBitmapDecoder(wicFactory);
        jpegDecoder.Initialize(wicStream, DecodeOptions.CacheOnDemand);
        var frame = jpegDecoder.GetFrame(0);
        baseWidth = frame.Size.Width;
        baseHeight = frame.Size.Height;
        frame.Dispose();
        jpegDecoder.Dispose();
    }

    // Resize image?
    bool resizeImage = (baseWidth != renderSettings.RenderWidth) || (baseHeight != renderSettings.RenderHeight);

    // Bitmaps and render target settings
    var wicBitmap = new WicBitmap(wicFactory, renderSettings.RenderWidth, renderSettings.RenderHeight, SharpDX.WIC.PixelFormat.Format32bppBGR, BitmapCreateCacheOption.CacheOnLoad);
    var renderTargetProperties = new RenderTargetProperties(RenderTargetType.Default, new D2DPixelFormat(Format.Unknown, AlphaMode.Unknown), 0, 0, RenderTargetUsage.None, FeatureLevel.Level_DEFAULT);
    var wicRenderTarget = new WicRenderTarget(ddFactory, wicBitmap, renderTargetProperties);

    // Create bitmap render target used to draw all images to
    SharpDX.Direct2D1.BitmapRenderTarget bitmapRenderTarget = new SharpDX.Direct2D1.BitmapRenderTarget(wicRenderTarget, CompatibleRenderTargetOptions.None, new D2DPixelFormat(Format.Unknown, AlphaMode.Premultiplied));

    // Draw render layers
    for (int i = 0; i < renderLayers.Count; i++) {
        // First layer is always a jpeg, all other subsequent layers are png's
        ImageFormat imageFormat = (i == 0) ? ImageFormat.Jpeg : ImageFormat.Png;

        using (SharpDX.WIC.BitmapSource bitmapSource = LoadWicBitmap(wicFactory, renderDirectory + renderLayers[i], imageFormat, resizeImage, renderSettings.RenderWidth, renderSettings.RenderHeight)) {

            // Convert WIC pixel format to D2D1 format
            var formatConverter = new FormatConverter(wicFactory);
            formatConverter.Initialize(bitmapSource, SharpDX.WIC.PixelFormat.Format32bppPBGRA, BitmapDitherType.None, null, 0f, BitmapPaletteType.MedianCut);

            // Create direct 2d bitmap from wic bitmap
            SharpDX.Direct2D1.Bitmap direct2DBitmap = SharpDX.Direct2D1.Bitmap.FromWicBitmap(bitmapRenderTarget, formatConverter);

            // Draw direct2d image to bitmap render target
            wicRenderTarget.BeginDraw();
            wicRenderTarget.DrawBitmap(direct2DBitmap, 1.0f, SharpDX.Direct2D1.BitmapInterpolationMode.Linear);
            wicRenderTarget.EndDraw();

            // Clean up
            formatConverter.Dispose();
            direct2DBitmap.Dispose();
        }
    }

    // Final image data
    byte[] imageData;

    // Create streams to write output to. 
    using (var memoryStream = new MemoryStream()) {
        using (var wicStream = new WICStream(wicFactory, memoryStream)) {

            // Encode wic bitmap
            var encoder = new JpegBitmapEncoder(wicFactory);
            encoder.Initialize(wicStream);

            var frameEncoder = new BitmapFrameEncode(encoder);
            frameEncoder.Initialize();
            frameEncoder.SetSize(renderSettings.RenderWidth, renderSettings.RenderHeight);
            frameEncoder.PixelFormat = WicPixelFormat.FormatDontCare;
            frameEncoder.WriteSource(wicBitmap);
            frameEncoder.Commit();
            encoder.Commit();

            // Set image data
            memoryStream.Position = 0;
            imageData = memoryStream.ToArray();

            // Clean up 
            frameEncoder.Dispose();
            encoder.Dispose();
            wicBitmap.Dispose();
            wicRenderTarget.Dispose();
            bitmapRenderTarget.Dispose();
            ddFactory.Dispose();
            wicFactory.Dispose();
            frameEncoder = null;
            encoder = null;
            wicBitmap = null;
            wicRenderTarget = null;
            bitmapRenderTarget = null;                  
            ddFactory = null;
            wicFactory = null;
        }
    }

    return Image.FromStream(new MemoryStream(imageData));
}

private BitmapSource LoadWicBitmap(ImagingFactory wicFactory, string path, ImageFormat imageFormat, bool resize, int resizeWidth = 0, int resizeHeight = 0)
{
    PngBitmapDecoder pngDecoder;
    JpegBitmapDecoder jpegDecoder;
    BitmapFrameDecode bitmapFrameDecode;

    var stream = new WICStream(wicFactory, path, NativeFileAccess.Read);

    // Load the appropriate decoder
    if (imageFormat == ImageFormat.Jpeg) {
        jpegDecoder = new JpegBitmapDecoder(wicFactory);
        jpegDecoder.Initialize(stream, DecodeOptions.CacheOnLoad);
        bitmapFrameDecode = jpegDecoder.GetFrame(0);
        jpegDecoder.Dispose();
    }
    else {
        pngDecoder = new PngBitmapDecoder(wicFactory);
        pngDecoder.Initialize(stream, DecodeOptions.CacheOnDemand);
        bitmapFrameDecode = pngDecoder.GetFrame(0);
        pngDecoder.Dispose();
    }

    // Clean up
    stream.Dispose();

    // Resize if necessary
    if (resize) {
        // Prepare scaler
        var scaler = new BitmapScaler(wicFactory);
        scaler.Initialize(bitmapFrameDecode, resizeWidth, resizeHeight, SharpDX.WIC.BitmapInterpolationMode.Fant);
        return (BitmapSource)scaler;
    }

    return (BitmapSource)bitmapFrameDecode;
}
于 2012-03-01T18:53:06.670 に答える
-1

これは機能するはずです:

    private Bitmap GetImage() {
        //##################### Get the Bitmaps ############################
        Bitmap sourceImage = new Bitmap("c:\\1.png");
        Bitmap overlayImage = new Bitmap("c:\\2.png");

        //##################### Get Hdc from baselayer ############################
        Graphics sourceImageGraphics = Graphics.FromImage(sourceImage);
        IntPtr sourceImageHDC = sourceImageGraphics.GetHdc();

        //##################### Get Cdc from second layer ############################
        IntPtr overlayImageCDC = CreateCompatibleDC(sourceImageHDC);
        IntPtr overlayImageHandle = overlayImage.GetHbitmap();
        SelectObject(overlayImageCDC, overlayImageHandle);

        /*
         * BitBlt from sourceImage is not neccessary,
         * because Graphics.FromImage(sourceImage) already did it for you
        */

        //##################### Draw the second layer ############################
        AlphaBlend(sourceImageHDC, 0, 0, overlayImage.Width, overlayImage.Height, overlayImageCDC, 0, 0, overlayImage.Width, overlayImage.Height, new BLENDFUNCTION(AC_SRC_OVER, 0, 0xff, AC_SRC_ALPHA));

        //##################### Release everthing ############################
        sourceImageGraphics.ReleaseHdc(sourceImageHDC);
        sourceImageGraphics.Dispose();

        DeleteDC(overlayImageCDC);
        DeleteObject(overlayImageHandle);

        //##################### Return Image ############################
        return sourceImage;
    }
于 2011-10-11T15:04:46.247 に答える