Please note the changed security model in Silverlight 5, which is a big bummer. My Silverlight MVP friend Morten wrote a few true words about it here.
As usual you can find a list of the samples on the project site and also get the code there.
private void BtnSnapshot_Click(object sender, RoutedEventArgs e)
{
if (saveFileDlg.ShowDialog().Value)
{
using (Stream dstStream = saveFileDlg.OpenFile())
{
SaveSnapshot(dstStream);
}
}
}The code is pretty obvious: A SaveFileDialog is shown and if the user enters a file name and hits OK, a stream to the file will be opened and passed to the SaveSnapshot method. There's only one think to keep in mind when using the SaveFileDialog.ShowDialog() method, it can only be called from user-initiated code like an event handler, otherwise a SecurityException is thrown.// Render Rectangle manually into WriteableBitmap
WriteableBitmap bmp = new WriteableBitmap(ViewportHost, null);
// Init buffer in FluxJpeg format
int w = bmp.PixelWidth;
int h = bmp.PixelHeight;
int[] p = bmp.Pixels;
byte[][,] pixelsForJpeg = new byte[3][,]; // RGB colors
pixelsForJpeg[0] = new byte[w, h];
pixelsForJpeg[1] = new byte[w, h];
pixelsForJpeg[2] = new byte[w, h];
// Copy WriteableBitmap data into buffer for FluxJpeg
int i = 0;
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
int color = p[i++];
pixelsForJpeg[0][x, y] = (byte)(color >> 16); // R
pixelsForJpeg[1][x, y] = (byte)(color >> 8); // G
pixelsForJpeg[2][x, y] = (byte)(color); // B
}
}
//Encode Image as JPEG
var colModel = new ColorModel { colorspace = ColorSpace.RGB };
var jpegImage = new FluxJpeg.Core.Image(colModel, pixelsForJpeg);
var encoder = new JpegEncoder(jpegImage, 95, dstStream);
encoder.Encode();The Rectangle's surrounding Grid "ViewportHost" is rendered into a WriteableBitmap and the WriteableBitmap's Pixels are copied into another buffer with a different format. The rendered image is then written as a JPEG encoded stream using the open source FJCore library which is distributed under the MIT License. I've found some code at Stackoverflow on how to use the library in combination with the WriteableBitmap, but I modified / shortened it. // Init capture devices
captureSource = new CaptureSource();
captureSource.VideoCaptureDevice = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
captureSource.AudioCaptureDevice = CaptureDeviceConfiguration.GetDefaultAudioCaptureDevice();
// Create video brush that preserves the aspect ratio and fill a Rectangle with it
VideoBrush videoBrush = new VideoBrush();
videoBrush.Stretch = Stretch.Uniform;
videoBrush.SetSource(captureSource);
Viewport.Fill = videoBrush;
// Ask user for permission
if (CaptureDeviceConfiguration.AllowedDeviceAccess
|| CaptureDeviceConfiguration.RequestDeviceAccess())
{
captureSource.Start();
}The edge detection pixel shader is applied to the "Viewport" Rectangle in the XAML. For a more intuitive behavior I've also attached a negative ScaleTransform to flip the x axis of the Rectangle. Otherwise the webcam output would be mirror-reversed by default.// Parameter
float2 TextureSize : register(C0);
// Sampler
sampler2D TexSampler : register(S0);
// Shader
float4 main(float2 texCoord : TEXCOORD) : COLOR
{
// Default color is fully transparent
float4 color = 0;
// Scale to int texture size
float row = texCoord.y * TextureSize.y * 0.5f;
// Calc diff between rounded half and half to get 0 or 0.5
float diff = round(row) - row;
float diffSq = diff * diff;
// Even or odd? Only even lines are sampled
if(diffSq < 0.1)
{
color = tex2D(TexSampler, texCoord);
}
return color;
}
// Parameter
float2 TextureSize : register(C0);
// Sampler
sampler2D TexSampler : register(S0);
// Shader
float4 main(float2 texCoord : TEXCOORD) : COLOR
{
// Default color is fully transparent
float4 color = 0;
// Scale to int texture size, add x and y
float2 vpos = texCoord * TextureSize * 0.5f;
float vposSum = vpos.x + vpos.y;
// Calc diff between rounded half and half to get 0 or 0.5
float diff = round(vposSum) - vposSum;
float diffSq = diff * diff;
// Even or odd? Only even pixels are sampled
if(diffSq < 0.1)
{
color = tex2D(TexSampler, texCoord);
}
return color;
}
In my latest WriteableBitmap Performance post I mentioned that the Silverlight 3 pixel shaders run really fast, although they are executed on the CPU and not on the GPU and that nice real-time effects can be done with it. A Silverlight 3 pixel shader could also be applied to any UIElement, thus the MediaElement too. The Silverlight MediaElement is mainly used to play music or video and with an attached shader many cool effects could be realized. As you might know I like nice effects and due to this passion I have coded another pixel shader for Silverlight. This time I have implemented a shader that simulates scratches, noise and other effects you might have seen in old movies.// Parameters
float ScratchAmount : register(C0);
float NoiseAmount : register(C1);
float2 RandomCoord1 : register(C2);
float2 RandomCoord2 : register(C3);
float Frame : register(C4);
// Static
static float ScratchAmountInv = 1.0 / ScratchAmount;
// Sampler
sampler2D TexSampler : register(S0);
sampler2D NoiseSampler : register(S1);
// Shader
float4 main(float2 uv : TEXCOORD) : COLOR
{
// Sample texture
float4 color = tex2D(TexSampler, uv);
// Add Scratch
float2 sc = Frame * float2(0.001f, 0.4f);
sc.x = frac(uv.x + sc.x);
float scratch = tex2D(NoiseSampler, sc).r;
scratch = 2 * scratch * ScratchAmountInv;
scratch = 1 - abs(1 - scratch);
scratch = max(0, scratch);
color.rgb += scratch.rrr;
// Calculate random coord + sample
float2 rCoord = (uv + RandomCoord1 + RandomCoord2) * 0.33;
float3 rand = tex2D(NoiseSampler, rCoord);
// Add noise
if(NoiseAmount > rand.r)
{
color.rgb = 0.1 + rand.b * 0.4;
}
// Convert to gray + desaturated Sepia
float gray = dot(color, float4(0.3, 0.59, 0.11, 0));
color = float4(gray * float3(0.9, 0.8, 0.6) , 1);
// Calc distance to center
float2 dist = 0.5 - uv;
// Random light fluctuation
float fluc = RandomCoord2.x * 0.04 - 0.02;
// Vignette effect
color.rgb *= (0.4 + fluc - dot(dist, dist)) * 2.8;
return color;
}private void CalculateColor(int x, int y)
{
// Normalize coordinates
double xn = x * TexSizeInv;
double yn = y * TexSizeInv;
// Overlayed sine circle rings
// I use member variables for argb,
// so I don't have to allocate and return a byte array in each call
// Ugly, but much faster than return new byte[]{ }
// Red
double d = (xn - C1.X) * (xn - C1.X) + (yn - C1.Y) * (yn - C1.Y);
r = (byte)((Math.Sin(d * Frequency)) > 0 ? 0 : 255);
// Blue
d = (xn - C2.X) * (xn - C2.X) + (yn - C2.Y) * (yn - C2.Y);
b = (byte)((Math.Sin(d * Frequency)) > 0 ? 0 : 255);
// Green fills the gaps
g = (byte)~(r | b);
}private void CalculateCenters()
{
// Nice sine circle movement
// Use normalized coordinates
C1.X = Math.Sin(framesCount * 0.02) * Half + Quarter;
C1.Y = Math.Sin(framesCount * 0.08) * Half + Quarter;
C2.X = Math.Sin(framesCount * 0.10) * Half + Quarter;
C2.Y = Math.Sin(framesCount * 0.04) * Half + Quarter;
}// Parameters
float2 C1 : register(C0);
float2 C2 : register(C1);
float Frequency : register(C2);
// Shader
float4 main(float2 p : TEXCOORD) : COLOR
{
// Overlayed sine circle rings
float4 color = 1;
// Red
float2 dist = uv - C1;
color.r = sin(dot(dist, dist) * Frequency) > 0 ? 0 : 1;
// Blue
dist = uv - C2;
color.b = sin(dot(dist, dist) * Frequency) > 0 ? 0 : 1;
// Green fills the gaps
color.g = color.r + color.b > 0 ? 0 : 1;
return color;
}Hello
I have been looking into Silverlight Graphics performance myself and was interested in the new SL3 features. So I read your blog entry for WriteableBitmap performance and was also interested in the new MediaSource managed codec abilities, which looked like another way to get pixels to the screen, but also audio as well which could be interesting for games etc.
So I took your source for the test sample and added two MediaSource tests, one using single threaded frame generation, the second using a background thread and a double buffer based on work from Pete Brown at on his Commodore 64 emulator in Silverlight.
I also reworked the UI with checkboxes to turn on/off tests as well as adding 3 new types of tests beyond the Circle interference to test performance of the rendering method vs the pixel generation. These include a random noise, simple scrolling line, and a no op. I also refactored some to make it easier to add new test types. I did not implement shader based versions of my tests, so if you run the shader test on anything but circle interference its basically a no op for now J.
I thought it might be an interesting addition to the sample as yet another way to generate dynamic bitmaps, the mediasource performs very well, although still behind WriteableBitmap.
Note for the non-double buffered version of MediaSource there is a padTime that can be tuned, it is currently set to 10ms and can be lowered to improve frame rate, but below a certain time it will get too short based on how faster your computer is and cause the media player to think it is losing frames and start skipping badly. I haven’t figured out a good way to compensate for this automatically yet, it has to do with how long the video render takes in addition to the pixel generation. The double buffer does not have this issue as it is a fixed frame rate set by frameTime.
I have attached the sample to this email, let me know what you think, and thanks for the great blog posts.
Justin Harrell
// Parameters
float Threshhold : register(C0);
float K00 : register(C1); // Kernel first column top
float K01 : register(C2); // Kernel first column middle
float K02 : register(C3); // Kernel first column bottom
float2 TextureSize : register(C4);
// Static Vars
static float ThreshholdSq = Threshhold * Threshhold;
static float2 TextureSizeInv = 1.0 / TextureSize;
static float K20 = -K00; // Kernel last column top
static float K21 = -K01; // Kernel last column middle
static float K22 = -K02; // Kernel last column bottom
sampler2D TexSampler : register(S0);
// Shader
float4 main(float2 uv : TEXCOORD) : COLOR
{
// Calculate pixel offsets
float2 offX = float2(TextureSizeInv.x, 0);
float2 offY = float2(0, TextureSizeInv.y);
// Sample texture
// Top row
float2 texCoord = uv - offY;
float4 c00 = tex2D(TexSampler, texCoord - offX);
float4 c01 = tex2D(TexSampler, texCoord);
float4 c02 = tex2D(TexSampler, texCoord + offX);
// Middle row
texCoord = uv;
float4 c10 = tex2D(TexSampler, texCoord - offX);
float4 c12 = tex2D(TexSampler, texCoord + offX);
// Bottom row
texCoord = uv + offY;
float4 c20 = tex2D(TexSampler, texCoord - offX);
float4 c21 = tex2D(TexSampler, texCoord);
float4 c22 = tex2D(TexSampler, texCoord + offX);
// Convolution
float4 sx = 0;
float4 sy = 0;
// Convolute X
sx += c00 * K00;
sx += c01 * K01;
sx += c02 * K02;
sx += c20 * K20;
sx += c21 * K21;
sx += c22 * K22;
// Convolute Y
sy += c00 * K00;
sy += c02 * K20;
sy += c10 * K01;
sy += c12 * K21;
sy += c20 * K02;
sy += c22 * K22;
// Add and apply Threshold
float4 s = sx * sx + sy * sy;
float4 edge = 1;
edge = 1 - float4( s.r <= ThreshholdSq,
s.g <= ThreshholdSq,
s.b <= ThreshholdSq,
0); // Alpha is always 1!
return edge;
}
The colors of the current pixel's neighbors are sampled from the image, which are then multiplied with the corresponding kernel value. The results are summed up, the threshold is applied and the new color is returned. I optimized the operation a bit more by calculating some static variables, using the squared threshold and leaving the calculation for the zero kernel column / row out.