Applying PixelShaders on offscreen Bitmaps
-
08-07-2019 - |
Question
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.
OTHER TIPS
Seeing you did not get any answer yet from C# experts, I will try to approach this from C++ DirectX developer perspective, hoping that if my answer is not useful, at least it will point you in the right direction. I know little about C# and nothing about how PixelShaders are supported, therefore chance is am complete wrong and what I write here does not apply at all. In such case feel free to comment or down vote as needed
What is generally done in C++ / DirectX to achive this is:
Preparation (done once)
- Create render target using CreateRenderTarget
- Create off-screen surface using CreateOffscreenPlainSurface
- Set render target surface using SetRenderTarget
- Create any other input resources needed (Textures, Vertex Buffers ...)
Rendering (done multiple times)
- Update input resources (textures, buffers) as needed
- Render
- Copy the contents of the render target into the off-screen surface via GetRenderTarget
- Lock the off-screen surface and read its content on the CPU