Custom WPF ShaderEffect alpha channel has unusual behavior - wpf

I've got a fairly straightforward pixel shader—set alpha channel to zero and return.
sampler2D tex : register(s0);
float4 PS(float2 uv : TEXCOORD) : COLOR
{
float4 color = tex2D(tex, uv);
color.a = 0;
return color;
}
I'd assume this would cause the image it's applied to to be completely invisible. However that's not what appears to be happening. Instead, the resulting image will become invisible over a white background, but over a black background it'll be unchanged. It appears that this shader is somehow causing an "add" function to be called between the foreground and the background.
For example, the following code loads a foreground and background image, applies the above shader effect to the foreground, renders them to a bitmap, and writes the bitmap to file.
public sealed partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
void Button_Click(object sender, RoutedEventArgs e)
{
const int width = 1024;
const int height = 768;
var sz = new Size(width, height);
var background = new Image { Source = new BitmapImage(new Uri(#"c:\background.jpg")) };
background.Measure(sz);
background.Arrange(new Rect(sz));
var foreground = new Image { Source = new BitmapImage(new Uri(#"c:\foreground.jpg")), Effect = new Alpha() };
foreground.Measure(sz);
foreground.Arrange(new Rect(sz));
var target = new RenderTargetBitmap(width, height, 96d, 96d, PixelFormats.Default);
target.Render(background);
target.Render(foreground);
var jpg = new JpegBitmapEncoder();
jpg.Frames.Add(BitmapFrame.Create(target));
using (var fileStream = File.OpenWrite(#"c:\output.jpg"))
{
jpg.Save(fileStream);
}
}
}
// Standard ShaderEffect stuff here, nothing exciting.
public sealed class Alpha : ShaderEffect
{
static readonly PixelShader Shader = new PixelShader{UriSource = new Uri("pack://application:,,,/Alpha.ps", UriKind.RelativeOrAbsolute)};
public static readonly DependencyProperty InputProperty = RegisterPixelShaderSamplerProperty("Input", typeof(Alpha), 0);
public Alpha()
{
PixelShader = Shader;
UpdateShaderValue(InputProperty);
}
public Brush Input
{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}
}
This produces the following when applied to two of the Win7 sample pictures:
This is the same behavior I see on the screen when I apply the effect to one Image in XAML, with another Image or anything else behind it.
Note the image is the same if foreground and background are reversed, so if it's not "add", it's at least something commutative. I think it's "add".
Computers are usually right, so I assume this is user error, but why is setting alpha to zero not giving me a transparent image? And how do I get a transparent image if so? (I obviously want to do something more complex with the shader eventually (specifically greenscreen), but to get that to work I have to get this shader to work first, so don't just say "Set the Opacity property").

Gah, stackoverflow is better than google. The top "related question" had the answer. Handling alpha channel in WPF pixel shader effect

Related

Showing part of UserControl like CroppedBitmap

I can't English very well yet. please understand even if you can't understand me clearly.
I have huge data table in UserControl.xaml, but downscale this UserControl object showing whole in MainWindow.
I want same size datatable showing of partially UserControl in MainWindow.
Like this image display way:
<Image>
<Image.Source>
<CroppedBitmap Source="<path to source image>" SourceRect="20,20,50,50"/>
</Image.Source>
</Image>
Showing UserControl in MainWindow like a SourceRect.
If I understand you correctly, you have several options. The first way, and I think the easiest is to use ViewBox control.
1. ViewBox
The Viewbox control inherited from Decorator is used to stretch or scale a child element, but it is scaled proportionally, ie you can not set the him size such 300x100.
Example
<Viewbox Width="300"
Height="300">
<DataGrid>
...
</DataGrid>
</ViewBox>
The second way is to use a screen capture of your control, that you would like to show, and then if you want to use CroppedBitmap.
2. Capturing screen
I found a great article by Pete Brown on this subject here:
Capturing Screen Images in WPF using GDI, Win32 and a little WPF Interop Help
In this article is an example, and it looks like this:
ScreenCapture
class ScreenCapture
{
public static BitmapSource CaptureFullScreen(bool addToClipboard)
{
return CaptureRegion(
User32.GetDesktopWindow(),
(int)SystemParameters.VirtualScreenLeft,
(int)SystemParameters.VirtualScreenTop,
(int)SystemParameters.VirtualScreenWidth,
(int)SystemParameters.VirtualScreenHeight,
addToClipboard);
}
// capture a window. This doesn't do the alt-prtscrn version that loses the window shadow.
// this version captures the shadow and optionally inserts a blank (usually white) area behind
// it to keep the screen shot clean
public static BitmapSource CaptureWindow(IntPtr hWnd, bool recolorBackground, Color substituteBackgroundColor, bool addToClipboard)
{
Int32Rect rect = GetWindowActualRect(hWnd);
Window blankingWindow = null;
if (recolorBackground)
{
blankingWindow = new Window();
blankingWindow.WindowStyle = WindowStyle.None;
blankingWindow.Title = string.Empty;
blankingWindow.ShowInTaskbar = false;
blankingWindow.AllowsTransparency = true;
blankingWindow.Background = new SolidColorBrush(substituteBackgroundColor);
blankingWindow.Show();
int fudge = 20;
blankingWindow.Left = rect.X - fudge / 2;
blankingWindow.Top = rect.Y - fudge / 2;
blankingWindow.Width = rect.Width + fudge;
blankingWindow.Height = rect.Height + fudge;
}
// bring the to-be-captured window to capture to the foreground
// there's a race condition here where the blanking window
// sometimes comes to the top. Hate those. There is surely
// a non-WPF native solution to the blanking window which likely
// involves drawing directly on the desktop or the target window
User32.SetForegroundWindow(hWnd);
BitmapSource captured = CaptureRegion(
hWnd,
rect.X,
rect.Y,
rect.Width,
rect.Height,
true);
if (blankingWindow != null)
blankingWindow.Close();
return captured;
}
// capture a region of the full screen
public static BitmapSource CaptureRegion(int x, int y, int width, int height, bool addToClipboard)
{
return CaptureRegion(User32.GetDesktopWindow(), x, y, width, height, addToClipboard);
}
// capture a region of a the screen, defined by the hWnd
public static BitmapSource CaptureRegion(
IntPtr hWnd, int x, int y, int width, int height, bool addToClipboard)
{
IntPtr sourceDC = IntPtr.Zero;
IntPtr targetDC = IntPtr.Zero;
IntPtr compatibleBitmapHandle = IntPtr.Zero;
BitmapSource bitmap = null;
try
{
// gets the main desktop and all open windows
sourceDC = User32.GetDC(User32.GetDesktopWindow());
//sourceDC = User32.GetDC(hWnd);
targetDC = Gdi32.CreateCompatibleDC(sourceDC);
// create a bitmap compatible with our target DC
compatibleBitmapHandle = Gdi32.CreateCompatibleBitmap(sourceDC, width, height);
// gets the bitmap into the target device context
Gdi32.SelectObject(targetDC, compatibleBitmapHandle);
// copy from source to destination
Gdi32.BitBlt(targetDC, 0, 0, width, height, sourceDC, x, y, Gdi32.SRCCOPY);
// Here's the WPF glue to make it all work. It converts from an
// hBitmap to a BitmapSource. Love the WPF interop functions
bitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
compatibleBitmapHandle, IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
if (addToClipboard)
{
//Clipboard.SetImage(bitmap); // high memory usage for large images
IDataObject data = new DataObject();
data.SetData(DataFormats.Dib, bitmap, false);
Clipboard.SetDataObject(data, false);
}
}
catch (Exception ex)
{
throw new ScreenCaptureException(string.Format("Error capturing region {0},{1},{2},{3}", x, y, width, height), ex);
}
finally
{
Gdi32.DeleteObject(compatibleBitmapHandle);
User32.ReleaseDC(IntPtr.Zero, sourceDC);
User32.ReleaseDC(IntPtr.Zero, targetDC);
}
return bitmap;
}
// this accounts for the border and shadow. Serious fudgery here.
private static Int32Rect GetWindowActualRect(IntPtr hWnd)
{
Win32Rect windowRect = new Win32Rect();
Win32Rect clientRect = new Win32Rect();
User32.GetWindowRect(hWnd, out windowRect);
User32.GetClientRect(hWnd, out clientRect);
int sideBorder = (windowRect.Width - clientRect.Width)/2 + 1;
// sooo, yeah.
const int hackToAccountForShadow = 4;
Win32Point topLeftPoint = new Win32Point(windowRect.Left - sideBorder, windowRect.Top - sideBorder);
//User32.ClientToScreen(hWnd, ref topLeftPoint);
Int32Rect actualRect = new Int32Rect(
topLeftPoint.X,
topLeftPoint.Y,
windowRect.Width + sideBorder * 2 + hackToAccountForShadow,
windowRect.Height + sideBorder * 2 + hackToAccountForShadow);
return actualRect;
}
}
Example of using:
private void CaptureRegionButton_Click(object sender, RoutedEventArgs e)
{
CapturedImage.Source = ScreenCapture.CaptureRegion(100, 100, 500, 500, true);
}
private void CaptureScreenButton_Click(object sender, RoutedEventArgs e)
{
CapturedImage.Source = ScreenCapture.CaptureFullScreen(true);
}

Building custom TextBlock control in WPF

I have built custom WPF Control which unique function is displaying text. I tried using TextBlock from System.Windows.Controls namespace but it's not working for me (I have ~10000 strings with different position and too much memory loss). So I tried making my own control by inheriting FrameworkElement, overriding OnRender method which now contain single line:
drawingContext.DrawText(...);
But...
I get a little confusing result.
After comparing performance for 10000 objects, I realized that the time needed for creating and adding to Canvas is still ~10 sec, and memory usage for my application raises from ~32MB to ~60MB !!!
So no benefits at all.
Can anyone explain why this happens, and what is the other way to create simple (simple = allocate less memory, take less time to create) visual with two functions:
display text
set position (using thickness or TranslateTransform)
Thanks.
Check out AvalonEdit
Also not sure how you are storing the strings, but have you used StringBuilder before?
Here is my code (a little bit modified):
public class SimpleTextBlock : FrameworkElement
{
#region Static
private const double _fontSize = 12;
private static Point _emptyPoint;
private static Typeface _typeface;
private static LinearGradientBrush _textBrush;
public readonly static DependencyProperty TextWidthProperty;
static SimpleTextBlock()
{
_emptyPoint = new Point();
_typeface = new Typeface(new FontFamily("Sergoe UI"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
GradientStopCollection GSC = new GradientStopCollection(2);
GSC.Add(new GradientStop(Color.FromArgb(160, 255, 255, 255), 0.0));
GSC.Add(new GradientStop(Color.FromArgb(160, 180, 200, 255), 0.7));
_textBrush = new LinearGradientBrush(GSC, 90);
_textBrush.Freeze();
SimpleTextBlock.TextWidthProperty = DependencyProperty.Register(
"TextWidth",
typeof(double),
typeof(SimpleTextBlock),
new FrameworkPropertyMetadata(0.0d, FrameworkPropertyMetadataOptions.AffectsRender));
}
#endregion
FormattedText _formattedText;
public SimpleTextBlock(string text)
{
_formattedText = new FormattedText(text, System.Globalization.CultureInfo.InvariantCulture, FlowDirection.LeftToRight, _typeface, _fontSize, _textBrush);
}
public SimpleTextBlock(string text, FlowDirection FlowDirection)
{
_formattedText = new FormattedText(text, System.Globalization.CultureInfo.InvariantCulture, FlowDirection, _typeface, _fontSize, _textBrush);
}
protected override void OnRender(DrawingContext drawingContext)
{
_formattedText.MaxTextWidth = (double)GetValue(TextWidthProperty);
drawingContext.DrawText(_formattedText, _emptyPoint);
}
public double TextWidth
{
get { return (double)base.GetValue(TextWidthProperty); }
set { base.SetValue(TextWidthProperty, value); }
}
public double ActualTextWidth
{
get { return _formattedText.Width; }
}
public double ActualTextHeight
{
get { return _formattedText.Height; }
}
}
Since it sounds like we determined you should stylize a control like listbox, here are some examples of different things you can do:
Use Images as Items
Stylized and Binding
Honestly it all depends on what you want it to look like. WPF is great in how much control it gives you on how something looks.
Crazy example using a listbox to make the planet's orbits

WPF WriteableBitmap and effects

Im trying to look into using the WPF WriteableBitmap class to allow my application to apply an opacity mask to an image.
Basically I have a blue rectangle as an image, and another 100% transparent green rectangle image over the top of the blue one.
When the user moves their mouse over the green (transparent) image, I want to apply the opacity mask (perhaps using a simple ellipse) so that it looks like a green glow is occurring.
Im purposefully not doing this is XAML and standard WPF effects because I really need it to be super performant and I will eventually swap out the ellipse with a more advance blob...
Any thoughts??
Thanks!
I'm sorry, I don't quite understand your intentions. Maybe if I could see the image, I could answer correctly from start, but here is my first-maybe-wrong answer.
If you say super-performant, you probably want to look at pixel shaders. They are processed by GPU, supported by WPF in a form of a custom effect and are easy to implement. Also you can apply shaders to playing video, while it's hard to do with WritableBitmap.
To write a pixel shader, you need to have FX Compiler (fxc.exe) from DirectX SDK and Shazzam tool - WYSIWYG WPF Shaders compiler by Walt Ritscher.
When you get them both, go ahead and try the following HLSL code
float X : register(C0); // Mouse cursor X position
float Y : register(C1); // Mouse cursor Y position
float4 Color : register(C2); // Mask color
float R : register(C3); // Sensitive circle radius.
sampler2D implicitInputSampler : register(S0);
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 finalColor = tex2D(implicitInputSampler, uv);
if ( (uv.x - X) * (uv.x - X) + (uv.y - Y) * (uv.y - Y) < R*R)
{
finalColor = Color; // Blend/Change/Mask it as you wish here.
}
return finalColor;
}
This gives you the following C# effect:
namespace Shazzam.Shaders {
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Effects;
public class AutoGenShaderEffect : ShaderEffect {
public static DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(AutoGenShaderEffect), 0);
public static DependencyProperty XProperty = DependencyProperty.Register("X", typeof(double), typeof(AutoGenShaderEffect), new System.Windows.UIPropertyMetadata(new double(), PixelShaderConstantCallback(0)));
public static DependencyProperty YProperty = DependencyProperty.Register("Y", typeof(double), typeof(AutoGenShaderEffect), new System.Windows.UIPropertyMetadata(new double(), PixelShaderConstantCallback(1)));
public static DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(System.Windows.Media.Color), typeof(AutoGenShaderEffect), new System.Windows.UIPropertyMetadata(new System.Windows.Media.Color(), PixelShaderConstantCallback(2)));
public static DependencyProperty RProperty = DependencyProperty.Register("R", typeof(double), typeof(AutoGenShaderEffect), new System.Windows.UIPropertyMetadata(new double(), PixelShaderConstantCallback(3)));
public AutoGenShaderEffect(PixelShader shader) {
// Note: for your project you must decide how to use the generated ShaderEffect class (Choose A or B below).
// A: Comment out the following line if you are not passing in the shader and remove the shader parameter from the constructor
PixelShader = shader;
// B: Uncomment the following two lines - which load the *.ps file
// Uri u = new Uri(#"pack://application:,,,/glow.ps");
// PixelShader = new PixelShader() { UriSource = u };
// Must initialize each DependencyProperty that's affliated with a shader register
// Ensures the shader initializes to the proper default value.
this.UpdateShaderValue(InputProperty);
this.UpdateShaderValue(XProperty);
this.UpdateShaderValue(YProperty);
this.UpdateShaderValue(ColorProperty);
this.UpdateShaderValue(RProperty);
}
public virtual System.Windows.Media.Brush Input {
get {
return ((System.Windows.Media.Brush)(GetValue(InputProperty)));
}
set {
SetValue(InputProperty, value);
}
}
public virtual double X {
get {
return ((double)(GetValue(XProperty)));
}
set {
SetValue(XProperty, value);
}
}
public virtual double Y {
get {
return ((double)(GetValue(YProperty)));
}
set {
SetValue(YProperty, value);
}
}
public virtual System.Windows.Media.Color Color {
get {
return ((System.Windows.Media.Color)(GetValue(ColorProperty)));
}
set {
SetValue(ColorProperty, value);
}
}
public virtual double R {
get {
return ((double)(GetValue(RProperty)));
}
set {
SetValue(RProperty, value);
}
}
}
}
Now you can track mouse position, and set corresponding properties of your effect to trigger changes. One thing to note here: X and Y in HLSL code are ranged from 0 to 1. So you'll have to convert actual coordinates to percentages, before passing them to shader.
Things to read more about pixel shaders and WPF:
Reference for HLSL on MSDN.
Writing custom GPU-based Effects for WPF by Greg Schechter.
Hope this helps :)

c# winforms: Getting the screenshot image that has to be behind a control

I have c# windows form which have several controls on it, part of the controls are located one on another. I want a function that will take for input a control from the form and will return the image that has to be behind the control. for ex: if the form has backgroundimage and contains a button on it - if I'll run this function I'll got the part of backgroundimage that located behind the button. any Idea - and code?
H-E-L-P!!!
That's my initial guess, but have to test it.
Put button invisible
capture current screen
Crop screen captured to the clientRectangle of the button
Restablish button.
public static Image GetBackImage(Control c) {
c.Visible = false;
var bmp = GetScreen();
var img = CropImage(bmp, c.ClientRectangle);
c.Visible = true;
}
public static Bitmap GetScreen() {
int width = SystemInformation.PrimaryMonitorSize.Width;
int height = SystemInformation.PrimaryMonitorSize.Height;
Rectangle screenRegion = Screen.AllScreens[0].Bounds;
var bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
Graphics graphics = Graphics.FromImage(bitmap);
graphics.CopyFromScreen(screenRegion.Left, screenRegion.Top, 0, 0, screenRegion.Size);
return bitmap;
}
public static void CropImage(Image imagenOriginal, Rectangle areaCortar) {
Graphics g = null;
try {
//create the destination (cropped) bitmap
var bmpCropped = new Bitmap(areaCortar.Width, areaCortar.Height);
//create the graphics object to draw with
g = Graphics.FromImage(bmpCropped);
var rectDestination = new Rectangle(0, 0, bmpCropped.Width, bmpCropped.Height);
//draw the areaCortar of the original image to the rectDestination of bmpCropped
g.DrawImage(imagenOriginal, rectDestination, areaCortar, GraphicsUnit.Pixel);
//release system resources
} finally {
if (g != null) {
g.Dispose();
}
}
}
This is pretty easy to do. Each control on the form has a Size and a Location property, which you can use to instantiate a new Rectangle, like so:
Rectangle rect = new Rectangle(button1.Location, button1.Size);
To get a Bitmap that contains the portion of the background image located behind the control, you first create a Bitmap of the proper dimensions:
Bitmap bmp = new Bitmap(rect.Width, rect.Height);
You then create a Graphics object for the new Bitmap, and use that object's DrawImage method to copy a portion of the background image:
using (Graphics g = Graphics.FromImage(bmp))
{
g.DrawImage(...); // sorry, I don't recall which of the 30 overloads
// you need here, but it will be one that uses form1.Image as
// the source, and rect for the coordinates of the source
}
This will leave you with the new Bitmap (bmp) containing the portion of the background image underneath that control.
Sorry I can't be more specific in the code - I'm at a public terminal. But the intellisense info will tell you what you need to pass in for the DrawImage method.

Creating SelectionBorder: Bit in the face by decimal rounding?

I am currently implementing a class called SelectionBorder in WPF. It's derived from the Shape class.
It basically looks like this:
public class SelectionBorder : Shape
{
public Point StartPoint {get; set;}
public PointCollection Points {get; set;}
public double StrokeLength {get; set;}
protected override Geometry DefiningGeometry{
get{
//Magic!
}
}
}
The StartPoint and Points properties determine the corners of the border. The border is a typical stroked line border (one black stroke, one invisible stroke like that: - - - -)
The problem that I have now is that since the corner points are freely choosable it's pretty common that the count of strokes (meaning black and invisible strokes) is not even (in fact not even an integer) and therefore the first stroke looks longer than the others (visible in the picture). This maybe doesn't seem to be a big deal but I later want to animate the border so that the strokes circle round the content. When doing this animation the tiny flaw in the static view becomes clearly visible and in my opinion is highly disturbing.
alt text http://img14.imageshack.us/img14/2874/selectionborder.png
The problem is that I tried to determine a StrokeLength that gets as close to the original StrokeLength as possible and creates an even number of strokes. However the problem I've run into is that WPF (obviously) can't display the whole precision of a double decimal StrokeLength and therefore the resulting stroke number is uneven once again.
Is there any workaround for this problem? Do you probably have another solution for my problem?
Thanks in advance!
EDIT: I retested and reviewed the code after a little break for fitness today and after all it happens only on very big StrokeLengths. I plan to use StrokeLengths of 2 where the little animation jumping does matter much less than I originally thought.
You could make more than one corner "un-matched" in that regard. For example, instead of having one point be the "source" and "destination" of the animated dashes, you could pick 2 points. One would be the "source", dashes appearing to march away from it in 2 directions, and another point be the "destination", where dashes converge and disappear.
GIMP, for example, animates selection dashed lines in this way and seems to pick a point closest to the lower-left for the "source" and a point closest to the upper-right for the "destination".
You could come up with some other scheme, as well.
Just remember that while it may look disturbing to you, most users will not care.
I just found a way that makes it way easier to create such an animated SelectionBorder.
Instead of creating the animation by moving an self-created AnimationPoint through animation I just animated the StrokeDashOffset property natively provided by the Shape class and setting the StrokeDashArray to define the StrokeLength.
It would look like this in XAML:
<namespace:SelectionBorder StrokeDashArray="2" AnimationDuration="0:0:1" Stroke="Black" />
The class looks like this:
public class SelectionBorder : Shape
{
private DoubleAnimation m_Animation;
private bool m_AnimationStarted;
public SelectionBorder()
{
IsVisibleChanged += OnIsVisibleChanged;
}
protected void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (Visibility == Visibility.Visible)
{
StartAnimation();
}
else
{
StopAnimation();
}
}
public void StartAnimation()
{
if (m_AnimationStarted)
return;
if (m_Animation == null)
{
m_Animation = CreateAnimation();
}
BeginAnimation(StrokeDashOffsetProperty, m_Animation);
m_AnimationStarted = true;
}
protected virtual DoubleAnimation CreateAnimation()
{
DoubleAnimation animation = new DoubleAnimation();
animation.From = 0;
if (StrokeDashArray.Count == 0)
animation.To = 4;
else
animation.To = StrokeDashArray.First() * 2;
animation.Duration = AnimationDuration;
animation.RepeatBehavior = RepeatBehavior.Forever;
return animation;
}
public void StopAnimation()
{
if (m_AnimationStarted)
{
BeginAnimation(StrokeDashOffsetProperty, null);
m_AnimationStarted = false;
}
}
#region Dependency Properties
public Duration AnimationDuration
{
get { return (Duration)GetValue(AnimationDurationProperty); }
set { SetValue(AnimationDurationProperty, value); }
}
public static readonly DependencyProperty AnimationDurationProperty =
DependencyProperty.Register("AnimationDuration", typeof(Duration), typeof(SelectionBorder), new UIPropertyMetadata(new Duration(TimeSpan.FromSeconds(0.5))));
#endregion Dependency Properties
protected override Geometry DefiningGeometry
{
get
{
double width = (double.IsNaN(Width)) ? ((Panel)Parent).ActualWidth : Width;
double height = (double.IsNaN(Height)) ? ((Panel)Parent).ActualHeight : Height;
RectangleGeometry geometry = new RectangleGeometry(new Rect(0, 0, width, height));
return geometry;
}
}
}

Resources