Search code examples
c#.net-3.5image-processingshader

Applying PixelShaders on offscreen Bitmaps


i'm currently experimenting using PixelShaders introduced with .net 3.5 sp1 to improve image processing performance. everything is much faster , but til yet i just had effects applied to some elements in my wpf forms, that i actually want to avoid.

we have a bunch of image processing functionality and i'd like to replace some othe the stuff piece by piece with pixel shaders to gain some performance. is there a way to apply such a pixel shader to an ImageSource without having to display it?


Solution

  • For who still needs this: I just created this article here that shows how to do it in WPF. http://www.codeproject.com/Articles/642151/Pixel-shaders-in-a-background-thread-in-WPF

    The relevant code copied below. it is from a class with some stored variables

    • Source: an ImageSource
    • DpiX, DpiY: doubles containing Dpi of source
    • img: WPF Image control
    • viewbox: WPF ViewBox control
    • WPF_DPI_X, WPF_DPI_Y: const doubles with value 96.0

    1.the Image img is embedded in a Viewbox (also off-screen)

    //prepare images
    img = new Image();
    img.Stretch = Stretch.None;
    viewbox = new Viewbox();
    viewbox.Stretch = Stretch.None;
    viewbox.Child = img; //control to render
    

    2.img and viewbox are sized to the correct proportions, also some layout functions are called on viewbox. this make the controls render with the shader applied.

    /// <summary>
    /// Loads the image and viewbox for off-screen rendering.
    /// </summary>
    public void LoadImage(double width, double height)
    {
        img.BeginInit();
        img.Width = width;
        img.Height = height;
        img.Source = Source;
        img.EndInit();
    
        viewbox.Measure(new Size(img.Width, img.Height));
        viewbox.Arrange(new Rect(0, 0, img.Width, img.Height));
        viewbox.UpdateLayout();
    } 
    

    3.And to get the contents of the image, a "screenshot" if you will:

    void SaveUsingEncoder(BitmapEncoder encoder, Stream stream)
    {
        RenderTargetBitmap bitmap = new RenderTargetBitmap((int)(img.Width * DpiX / WPF_DPI_X), (int)(img.Height * DpiY / WPF_DPI_Y), DpiX, DpiY, PixelFormats.Pbgra32);
        bitmap.Render(viewbox);
    
        BitmapFrame frame = BitmapFrame.Create(bitmap);
        encoder.Frames.Add(frame);
        encoder.Save(stream);
    } 
    

    Also, if you want to run this in a separate thread you will need to create a thread with

    thread.SetApartmentState(ApartmentState.STA);
    

    for more info and a demo project, see the article.