Plane projection and scale causing bluring in silverlight - c#

Ok,
So I've tried to make an application which relies on images being scaled by an individual factor. These images are then able to be turned over, but the use of an animation working on the ProjectionPlane rotation.
The problem comes around when an image is both scaled and rotated. For some reason it starts bluring, where a non scaled image doesn't blur.
Also, if you look at the example image below (top is scaled and rotated, bottom is rotated) the projection of the top one doesn't even seem right. Its too horizontal.
http://img43.imageshack.us/img43/5923/testimages.png http://img43.imageshack.us/img43/5923/testimages.png
This this the code for the test app:
<UserControl x:Class="SilverlightApplication1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Canvas x:Name="LayoutRoot" Background="White">
<Border Canvas.Top="25" Canvas.Left="50">
<Border.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="3" ScaleY="3" />
</TransformGroup>
</Border.RenderTransform>
<Border.Projection>
<PlaneProjection RotationY="45"/>
</Border.Projection>
<Image Source="bw-test-pattern.jpg" Width="50" Height="40"/>
</Border>
<Border Canvas.Top="150" Canvas.Left="50">
<Border.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1" />
</TransformGroup>
</Border.RenderTransform>
<Border.Projection>
<PlaneProjection RotationY="45"/>
</Border.Projection>
<Image Source="bw-test-pattern.jpg" Width="150" Height="120"/>
</Border>
</Canvas>
</UserControl>
So if anyone could possible shed any light on why this may be happening, I'd very much appreciate it. Suggestions also welcome! :)
** Update **
Just to clarify, if the projection plane rotation is 0, the image becomes un-blurred, so its only during the rotation that the image is blurred.

The top image's width is set to 50 and the height to 40. So it is downscaled. Afterwards you scale it up to the right size 150, 120. I guess Silverlight scales the image down and doesn't store the original size due to performance optmization. Leave the Scale out and set the right width and height for the first image.

It looks like the top image is being filtered as it is being drawn. From your code you have:
<Image Source="bw-test-pattern.jpg" Width="50" Height="40"/>
for the top image and
<Image Source="bw-test-pattern.jpg" Width="150" Height="120"/>
for the bottom one. You have different image sizes so the top one might be being upscaled and therefore blurred as it interpolates the missing pixels.
I'm not familiar with silverlight so I don't know how you'd control the filtering options, but setting the top line above to the same as the bottom one might fix it.

Related

Zooming WPF canvas relative to the center rather than top left

I am writing a label-making program. In this program, I have a canvas with a white rectangle positioned in the center, with multiple objects that the user can resize, drag, etc on top of it. I also have an option for the user to zoom the canvas, which I accomplish using ScaleTransform via LayoutTransform. I want to have it so that, when the user zooms in, the canvas zooms in on the center, rather than relative to the top right.
Here's a demonstration:
Currently, the canvas zooms like this:
I need it to zoom like this:
How can I accomplish this task, without re-positioning the elements in the canvas when it is zoomed?
Note: I'm using LayoutTransform, since I must embed this in a ScrollViewer. RenderTransform accomplishes this, but won't let the user scroll when canvas elements exceed the visible canvas bounds.
You should be able to accomplish the same thing as RenderTransformOrigin, by using a transform that combines two translations around the scaling. E.g. using TransformGroup, or combining matrices for a MatrixTransform.
Specifically: translate the center point (X,Y) to (0,0) by using translation offsets of -X and -Y, perform the scale transform, and then translate (0,0) back to the original point (X, Y) using translation offsets of X and Y.
It's hard for me to know for sure without having a specific code example, but I have a vague sense that it ought to be possible to use RenderTransform, perhaps applied differently than you'd prefer (e.g. adding a new container to the hierarchy and applying it to that). But assuming RenderTransform simply won't work, the above should.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="600" Width="600">
<Grid>
<ScrollViewer>
<Border>
<Border.LayoutTransform>
<TransformGroup>
<ScaleTransform ScaleX="2.0" ScaleY="2.0"/>
</TransformGroup>
</Border.LayoutTransform>
<Canvas Background="Red" Width="500" Height="500" VerticalAlignment="Center" HorizontalAlignment="Center">
<Canvas.RenderTransform>
<TransformGroup>
<TranslateTransform X="0" Y="-100"/>
</TransformGroup>
</Canvas.RenderTransform>
<Rectangle Canvas.Left="200" Canvas.Top="200" Width="100" Height="100" Fill="Yellow"/>
</Canvas>
</Border>
</ScrollViewer>
</Grid>
</Window>

Border around a rotating image

Basically I'm trying to do something whereby a WPF image is inside a WPF border, and periodically I rotate the image by changing the RotateTransform Angle property.
The problem is, when I rotate the image, the border doesn't rotate, or attempt to change to fit the new shape of the picture. I've tried setting it's Alignment properties to stretch, and even binding the height/width of the border to that of the image, but no luck. I suspect the problem is that, when I rotate the image, it doesn't actually change the height or width of the Image object, so of course the border doesn't know what to do.
Is there a better way to rotate the image that would allow the border to resize, or if not, how do I get the border to resize correctly, given that I'm changing the RotateTransform Angle.
Thanks!
You can use the LayoutTransform instead of RenderTransform for this. If you try changing the angle of rotation you'll see the border changes size to accommodate it. (Think this is what you're asking? If you actually want the border to rotate then you can just rotate that instead of the image)
<Window x:Class="rotate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Border VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Black" BorderThickness="1">
<Grid Background="Blue" Width="80" Height="80">
<Grid.LayoutTransform>
<RotateTransform Angle="10"/>
</Grid.LayoutTransform>
</Grid>
</Border>
</Grid>
</Window>
Use LayoutTransform instead of RenderTranform.
RenderTransform only does a visual transformation of the control and is applied after measuring and arranging Controls. Therefore it doesn't affect the size seen by other controls.
LayoutTransform really affects the layout of the object. It's applied before measuring and arranging control, so the other control see a change in the size.
Caution: LayoutTransform is much slower and won't usually give a smooth animation.
<Border BorderThickness="5" BorderBrush="Red" HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Width="50" Height="50">
<Image.LayoutTransform>
<RotateTransform Angle="45" />
</Image.LayoutTransform>
</Image>
</Border>

RenderTransform RotateTransform Blurry Image

I have the following custom UserControl that represents a card in my application:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:Core="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Namespace="clr-namespace:MyApp" xmlns:Properties="clr-namespace:MyApp.Properties" Core:Class="MyApp.Card" Height="176" RenderTransformOrigin="0.5,0.5" Width="83" UseLayoutRounding="True">
<UserControl.LayoutTransform>
<TransformGroup>
<RotateTransform/>
<ScaleTransform/>
</TransformGroup>
</UserControl.LayoutTransform>
<UserControl.RenderTransform>
<TransformGroup>
<RotateTransform/>
<ScaleTransform/>
</TransformGroup>
</UserControl.RenderTransform>
<UserControl.Resources>
<Namespace:ImagesConverter Core:Key="ImagesConverter"/>
</UserControl.Resources>
<Canvas Core:Name="Layout">
<Image Core:Name="Image" HorizontalAlignment="Left" Source="{Binding Source={Core:Static Properties:Resources.Cards}, Converter={StaticResource ImagesConverter}}" Stretch="None" VerticalAlignment="Top">
<Image.Clip>
<RectangleGeometry Core:Name="Clipping" Rect="0,0,83,176"/>
</Image.Clip>
<Image.RenderTransform>
<TranslateTransform Core:Name="Translation" X="0" Y="0"/>
</Image.RenderTransform>
</Image>
<Rectangle Core:Name="Highlight" Canvas.Left="-2" Canvas.Top="-2" Height="180" Opacity="0.7" Stroke="#FFFFF500" StrokeThickness="3" Visibility="Collapsed" Width="87"/>
</Canvas>
</UserControl>
As you can see... I have a bit PNG image containing all the card faces and then, when I create a new card passing Suit and Rank enum values in constructor, I calculate the correct clipping rectagle and translation for the image.
Everything works like a charm... except when I try to animate my card with a Storyboard that requires a 90° rotation. Here is my code (the Storyboard is defined in MainWindow.Resources):
<DoubleAnimation BeginTime="00:00:00.4" Duration="00:00:00.2" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)" To="90"/>
And here is the result:
I can't understand what's going on... but the images gets somehow stretched and becomes very blurry as you can see. I tried using UseLayoutRounding="True" in my card control and also SnapsToDevicePixels="True" in my MainWindow as suggested somewhere else... but it's not working!
Of course... if I rotate the card using LayoutTransform instead of RenderTransform everything works perfectly and the card is not blurry... but I can't make the card rotate around it's center and my animation requires a 90° rotation from the center. Animating Canvas.Top for half of the card height together with layout rotation looks to me like a very unbrilliant solution... and it also makes my animation looks very bad.
Can you suggest me a solution please?
[EDIT] I tried to use RenderOptions.BitmapScalingMode="NearestNeighbor" and RenderOptions.EdgeMode="Aliased"... but it becomes even worse:
The problem was that the underlying image, and so the layout, had an odd value as Width (83). RenderTransformOrigin, even if set to "0.5,0.5", was probably rounding up or down that value resulting in a very bad rendering.
Changing both the image and layout Width to a even value (82), totally resolved the problem.
You could try changeing the RenderOptions on the Image and see if you can find a setting that works, I have added an example of settings I use for this exact situation in my application
RenderOptions.BitmapScalingMode="NearestNeighbor"
RenderOptions.EdgeMode="Aliased"
Example:
<Image RenderOptions.BitmapScalingMode="NearestNeighbor"
RenderOptions.EdgeMode="Aliased"
Core:Name="Image" HorizontalAlignment="Left" Source="{Binding Source={Core:Static Properties:Resources.Cards}, Converter={StaticResource ImagesConverter}}" Stretch="None" VerticalAlignment="Top">
<Image.Clip>
<RectangleGeometry Core:Name="Clipping" Rect="0,0,83,176"/>
</Image.Clip>
<Image.RenderTransform>
<TranslateTransform Core:Name="Translation" X="0" Y="0"/>
</Image.RenderTransform>
</Image>
You indicated that using LayoutTransform works perfectly without blurring the image. Have you tried using it and specifying the center as the pivot of rotation?
Something like:
<Image ...>
<Image.LayoutTransform>
<RotateTransform CenterX="0.5" CenterY="0.5" Angle="90"/>
</Image.LayoutTransform>
</Image>
As #Tommaso Belluzzo said, this is cause from RenderTransformOrigin="0.5,0.5"
Your answer above really solved my question.
Allow me to show my code as following:
System.Drawing.Bitmap bm = null;
//RotateTransform(angle) with RenderTransformOrigin="0.5,0.5" might cause blurry if the width or height is odd,
using (System.Drawing.Bitmap bitmap = (System.Drawing.Bitmap)System.Drawing.Image.FromFile(path))
{
int width = bitmap.Width;
int height = bitmap.Height;
if (0 != width % 2) width ++;
if (0 != height % 2) height ++;
bm = new System.Drawing.Bitmap(width, height);
bm.SetResolution(96, 96);
using (System.Drawing.Graphics g = System.Drawing. Graphics.FromImage(bm))
{
//I don't want to do anything which may modify(resize) the original image.
g.Clear(System.Drawing.Color.White);
g.DrawImage(bitmap, 0, 0, bitmap.Width, bitmap.Height);
}
//bm.Save(directory+#"\tmp.bmp", ImageFormat.Bmp);
}
//.....(show bm)

Image not displayed pixel to pixel

I'm puzzled.
I have created a PNG image that to be a divider in my application display and the image is 3 pixels high like this:
However when I display it on my C# WPF window and run it, the divider is shown to be about 4 pixels instead, with the colours being off.
Code:
<StackPanel Height="3">
<StackPanel.Background>
<ImageBrush ImageSource="Assets/divider.png" Stretch="None"
AlignmentX="Left" AlignmentY="Top" Viewport="0,0,20,3"
ViewportUnits="Absolute" TileMode="FlipX" />
</StackPanel.Background>
</StackPanel>
Output and Zoom:
What I've thought would be that the image is "stretched" (despite Stretch="None") or there is some mechanism to optimize the image, so I did away with the image and wrote it with 3 rectangles with the same color and 1 pixel height each like this:
<StackPanel Height="3" Orientation="Vertical">
<Rectangle Fill="#484848" Height="1" />
<Rectangle Fill="#222" Height="1" />
<Rectangle Fill="#484848" Height="1" />
</StackPanel>
However the results were similar: 4 pixels and colours run off (not exact colours as specified):
Other than pixels being off, the colours are off too. My colours are supposed to be #484848, #222222, #484848. When displayed on the WPF window, the shades goes off a little (too many shades of grey!)
I need the separator to fill the window with too as the window gets resized. I'm a perfectionist and I want that 3 pixel height separator to be pixel perfect. Any solution to solve that problem?
With thanks to #Brian and #Sisyphe, I am able to display the lines properly. However this is still a solution done through Rectangle displays and I reckon there's a better solution.
Here's my current solution:
<StackPanel HorizontalAlignment="Stretch" Height="3">
<Rectangle Fill="#484848" Height="1" RenderOptions.EdgeMode="Aliased" />
<Rectangle Fill="#222222" Height="1" RenderOptions.EdgeMode="Aliased" />
<Rectangle Fill="#484848" Height="1" RenderOptions.EdgeMode="Aliased" />
</StackPanel>

DropShadowEffect on Rectangle with slight transparency - how to make shadow not show inside fill?

I have a DropShadowEffect on a Rectangle with a fill that's slightly transparent. The problem is, the drop shadow also shows up inside the fill, which is what I don't want. Anyways to solve this? I tried this, but it doesn't work for me :/
This is very tricky.
Okay, I don't know the exact answer. But here is what will give you almost the desired effect. Try it.
This is a sample Grid with yellow background. I've drawn two intersecting rectangles of 100x100 dimension on it. You may need to customize the size according to your need. One rectangle is gray rectangle (to show shadow), and the other is a red rectangle (the actual semi-transparent rectangle that you want to display). The shadow depth has been hard coded as 5 pixels here. Please customize it at:
RectangleGeometry Rect="5,5,100,100"
RectangleGeometry Rect="0,0,95,95"
So, the grid looks like:
<Grid Background="Yellow">
<!-- A rectangle for shadow -->
<Rectangle Fill="Gray" Width="100" Height="100" Opacity=".7">
<Rectangle.Clip>
<CombinedGeometry GeometryCombineMode="Exclude">
<CombinedGeometry.Geometry1>
<RectangleGeometry Rect="5,5,100,100"/>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<RectangleGeometry Rect="0,0,95,95"/>
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Rectangle.Clip>
<Rectangle.Effect>
<!-- For nice soft shadow effect -->
<BlurEffect Radius="5" />
</Rectangle.Effect>
</Rectangle>
<!-- Actual rectangle which is translucent -->
<Rectangle Fill="Red" Width="100" Height="100" Opacity=".6" >
<Rectangle.Clip>
<RectangleGeometry Rect="0,0,95,95"/>
</Rectangle.Clip>
</Rectangle>
</Grid>
Update (8-Nov-11):
You can replace the hard-coded width and height by binding them to parent's width and height. Check this SO topic for multiple binding which you will need. More study material on binding: here.
An example of how the XAML snippet will look like is:
<Rectangle Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}"
Height="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualHeight}">
</Rectangle>
Since I'm not the expert on data binding, you need to research on your own from here. I feel that you will need your own value-converters for assigning special width ad height (ActualWidth - ShadowDepth kind of stuff (ShadowDepth being 5 pixels here)).

Categories