I have an image with and have a few values to make it a perspective in Silverlight, but can't quite figure out what I need to do mathmatically to make it happen. The most important thing is I have an angle called a "Field of View" (FOV).
This is the normal picture:
For example:
X = 30° X = 30° X = 30° FOV = 30° FOV = 60° FOV = 120° X = 60° X = 60° X = 60° FOV = 30° FOV = 60° FOV = 120°
Any help would be appreciated to walk me through the math to reproduce these in Silverlight.
I think the problem everyone is encountering is that there needs to be a viewport shift along with the perspective transformation.
Try this out:
private void ApplyProjection()
{
double fovY = FOV * Math.PI / 180 / 2.0;
double translationZ = -image1.ActualHeight / Math.Tan(fovY / 2.0);
double theta = YourAngleX * Math.PI / 180.0;
Matrix3D centerImageAtOrigin = TranslationTransform(
-image1.ActualWidth / 2.0,
-image1.ActualHeight / 2.0, 0);
Matrix3D invertYAxis = CreateScaleTransform(1.0, -1.0, 1.0);
Matrix3D rotateAboutY = RotateYTransform(theta);
Matrix3D translateAwayFromCamera = TranslationTransform(0, 0, translationZ);
Matrix3D perspective = PerspectiveTransformFovRH(fovY,
image1.ActualWidth / image1.ActualHeight, // aspect ratio
1.0, // near plane
1000.0); // far plane
Matrix3D viewport = ViewportTransform(image1.ActualWidth, image1.ActualHeight);
Matrix3D m = centerImageAtOrigin * invertYAxis;
m = m * rotateAboutY;
m = m * translateAwayFromCamera;
m = m * perspective;
m = m * viewport;
Matrix3DProjection m3dProjection = new Matrix3DProjection();
m3dProjection.ProjectionMatrix = m;
image1.Projection = m3dProjection;
}
private Matrix3D TranslationTransform(double tx, double ty, double tz)
{
Matrix3D m = new Matrix3D();
m.M11 = 1.0; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
m.M21 = 0.0; m.M22 = 1.0; m.M23 = 0.0; m.M24 = 0.0;
m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
m.OffsetX = tx; m.OffsetY = ty; m.OffsetZ = tz; m.M44 = 1.0;
return m;
}
private Matrix3D CreateScaleTransform(double sx, double sy, double sz)
{
Matrix3D m = new Matrix3D();
m.M11 = sx; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
m.M21 = 0.0; m.M22 = sy; m.M23 = 0.0; m.M24 = 0.0;
m.M31 = 0.0; m.M32 = 0.0; m.M33 = sz; m.M34 = 0.0;
m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;
return m;
}
private Matrix3D RotateYTransform(double theta)
{
double sin = Math.Sin(theta);
double cos = Math.Cos(theta);
Matrix3D m = new Matrix3D();
m.M11 = cos; m.M12 = 0.0; m.M13 = -sin; m.M14 = 0.0;
m.M21 = 0.0; m.M22 = 1.0; m.M23 = 0.0; m.M24 = 0.0;
m.M31 = sin; m.M32 = 0.0; m.M33 = cos; m.M34 = 0.0;
m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;
return m;
}
private Matrix3D RotateZTransform(double theta)
{
double cos = Math.Cos(theta);
double sin = Math.Sin(theta);
Matrix3D m = new Matrix3D();
m.M11 = cos; m.M12 = sin; m.M13 = 0.0; m.M14 = 0.0;
m.M21 = -sin; m.M22 = cos; m.M23 = 0.0; m.M24 = 0.0;
m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
m.OffsetX = 0.0; m.OffsetY = 0.0; m.OffsetZ = 0.0; m.M44 = 1.0;
return m;
}
private Matrix3D PerspectiveTransformFovRH(double fieldOfViewY, double aspectRatio, double zNearPlane, double zFarPlane)
{
double height = 1.0 / Math.Tan(fieldOfViewY / 2.0);
double width = height / aspectRatio;
double d = zNearPlane - zFarPlane;
Matrix3D m = new Matrix3D();
m.M11 = width; m.M12 = 0; m.M13 = 0; m.M14 = 0;
m.M21 = 0; m.M22 = height; m.M23 = 0; m.M24 = 0;
m.M31 = 0; m.M32 = 0; m.M33 = zFarPlane / d; m.M34 = -1;
m.OffsetX = 0; m.OffsetY = 0; m.OffsetZ = zNearPlane * zFarPlane / d; m.M44 = 0;
return m;
}
private Matrix3D ViewportTransform(double width, double height)
{
Matrix3D m = new Matrix3D();
m.M11 = width / 2.0; m.M12 = 0.0; m.M13 = 0.0; m.M14 = 0.0;
m.M21 = 0.0; m.M22 = -height / 2.0; m.M23 = 0.0; m.M24 = 0.0;
m.M31 = 0.0; m.M32 = 0.0; m.M33 = 1.0; m.M34 = 0.0;
m.OffsetX = width / 2.0; m.OffsetY = height / 2.0; m.OffsetZ = 0.0; m.M44 = 1.0;
return m;
}
This will create the appropriate perspective shift and match what PowerPoint is producing.
This code was adapted from MSDN.
It looks like you are treating your source picture as having one of several possible fields of view, e.g. as if taken with a wide-angle lens for the 120° or a zoom lens for the 30°. You are then trying to reproduce the aspect ratio of the original scene when displayed. Is this correct?
If so the procedure you actually want to stretch the picture horizontally to restore the implicit width, before rotating it with the perspective transform. That would mean you are actually trying to solve 2 separate (simpler) maths problems here e.g.:
The difficulty I have is that the example photos do not indicate any specific rules about the display. The display widths all vary so I cannot work out what your end result is intended to be. If you can provide more information I should be able to provide specific calculations.
Ok, based on your use of the Perspective settings in PowerPoint, the 2 required steps are indeed:
The first calculation is very simple. You need to set the scale to Cosine(X-angle). The second is an estimate as the Powerpoint perspective angle does not seem to relate to rotation.
I have provided a full sample XAML and code-behind below to generate the app shown*.
***Note: there is a serious flaw in that Projection Transforms are not able to distort the image to the degree you require. I am trying Matrix3DProjection instead, solution will follow **
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="PerspectivePhotosTest.PerspectivePhotos"
d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Image x:Name="SampleImage" Height="101" Source="Image1.png" Stretch="Fill" VerticalAlignment="Center" HorizontalAlignment="Center" Width="128" RenderTransformOrigin="0.5,0.5">
<Image.Projection>
<PlaneProjection x:Name="Rotation" RotationY="0"/>
</Image.Projection>
<Image.RenderTransform>
<CompositeTransform x:Name="Scale" ScaleX="1"/>
</Image.RenderTransform>
</Image>
<Grid HorizontalAlignment="Left" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text=""X-Angle"" VerticalAlignment="Top" HorizontalAlignment="Right"/>
<TextBox x:Name="XAngleTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=XValueSlider}" TextChanged="XAngleTextBox_TextChanged"/>
<Slider x:Name="XValueSlider" Grid.Row="1" Grid.ColumnSpan="2" LargeChange="10" Maximum="80" SmallChange="1"/>
<TextBlock Text="Perspective Angle" VerticalAlignment="Top" Grid.Row="2" HorizontalAlignment="Right"/>
<TextBox x:Name="PerspectiveAngleTextBox" d:LayoutOverrides="Height" Grid.ColumnSpan="2" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="40" Text="{Binding Value, ElementName=PerspectiveSlider}" TextChanged="PerspectiveAngleTextBox_TextChanged"/>
<Slider x:Name="PerspectiveSlider" Grid.Row="3" Grid.ColumnSpan="2" Maximum="120" SmallChange="1" LargeChange="10"/>
</Grid>
</StackPanel>
</Grid>
</UserControl>
Code behind:
using System;
using System.Windows.Controls;
namespace PerspectivePhotosTest
{
public partial class PerspectivePhotos : UserControl
{
public PerspectivePhotos()
{
InitializeComponent();
}
private void XAngleTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
Scale.ScaleX = CalcScale(DegreeToRadian(double.Parse(XAngleTextBox.Text)));
}
private void PerspectiveAngleTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
Rotation.RotationY = CalcTransform(double.Parse(PerspectiveAngleTextBox.Text));
}
private double CalcScale(double angleInRadians)
{
return Math.Cos(angleInRadians) * 2 + 0.3;
}
private double CalcTransform(double angleInDegrees)
{
return angleInDegrees / 2;
}
private double DegreeToRadian(double angle)
{
return Math.PI * angle / 180.0;
}
}
}
This should give you a handle test framework to try out variations. I re-factored the calculation steps to make it more obvious.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With