trying to render an equirectangular panorama - c#

I have an equirectangular panorama source image which is 360 degrees of longitude and 120 degrees of latitude.
I want to write a function which can render this, given width and height of the viewport and a rotation in longitude. I want to can my output image so that it's the full 120 degrees in height.
has anyone got any pointers? I can't get my head around the maths on how to transform from target coordinates back to source.
thanks
slip
Here is my code so far:- (create a c# 2.0 console app, add a ref to system.drawing)
static void Main(string[] args)
{
Bitmap src = new Bitmap(#"C:\Users\jon\slippyr4\pt\grid2.jpg");
// constant stuff
double view_width_angle = d2r(150);
double view_height_angle = d2r(120);
double rads_per_pixel = 2.0 * Math.PI / src.Width;
// scale everything off the height
int output_image_height = src.Width;
// compute radius (from chord trig - my output image forms a chord of a circle with angle view_height_angle)
double radius = output_image_height / (2.0 * Math.Sin(view_height_angle / 2.0));
// work out the image width with that radius.
int output_image_width = (int)(radius * 2.0 * Math.Sin(view_width_angle / 2.0));
// source centres for later
int source_centre_x = src.Width / 2;
int source_centre_y = src.Height / 2;
// work out adjacent length
double adj = radius * Math.Cos(view_width_angle / 2.0);
// create output bmp
Bitmap dst = new Bitmap(output_image_width, output_image_height);
// x & y are output pixels offset from output centre
for (int x = output_image_width / -2; x < output_image_width / 2; x++)
{
// map this x to an angle & then a pixel
double x_angle = Math.Atan(x / adj);
double src_x = (x_angle / rads_per_pixel) + source_centre_x;
// work out the hypotenuse of that triangle
double x_hyp = adj / Math.Cos(x_angle);
for (int y = output_image_height / -2; y < output_image_height / 2; y++)
{
// compute the y angle and then it's pixel
double y_angle = Math.Atan(y / x_hyp);
double src_y = (y_angle / rads_per_pixel) + source_centre_y;
Color c = Color.Magenta;
// this handles out of range source pixels. these will end up magenta in the target
if (src_x >= 0 && src_x < src.Width && src_y >= 0 && src_y < src.Height)
{
c = src.GetPixel((int)src_x, (int)src_y);
}
dst.SetPixel(x + (output_image_width / 2), y + (output_image_height / 2), c);
}
}
dst.Save(#"C:\Users\slippyr4\Desktop\pana.jpg");
}
static double d2r(double degrees)
{
return degrees * Math.PI / 180.0;
}
With this code, i get the results i expect when i set my target image width to 120 degrees. I see the right curvature of horizontal lines etc, as below, and when i try it with a real-life equirectangular panorama, it looks like commercial viewers render.
But, when i make the output image wider, it all goes wrong. You start to see the invalid pixels in a parabola top and bottom at the centre, as shown here with the image 150 degrees wide by 120 degrees high:-
What commericial viewers seem to do is sort of zoom in - so the in the centre, the image is 120 degrees high and therefore at the sides, more is clipped. and therfore, there is no magenta (ie, no invalid source pixels).
But i can't get my head around how to do that in the maths.
This isn't homework, it's a hobby project. hence why i am lacking the understanding of what is going on!. Also, please forgive the severe inefficeincy of the code, i will optimise it when i have it working propertly.
thanks again

Related

Leaflet Maps with Custom Tiles Zoom offset x y

I'm trying to use leaflet to render large images using x,y coordinates like so:
var map = L.map('map', {
crs: L.CRS.Simple,
attributionControl: false,
reuseTiles:true,
}).setView([0, 0], 1);
The problem is that when I zoom I seem to get an offset. So as I continually zoom in the map appears to shift.
I am drawing the image on the backend using C# and GDI+ so it's quite possible that I am getting code this wrong:
private void DrawLine(int x, int y, int z, int squareSize, Graphics g, Shape shape, Pen drawPen)
{
Line line = (Line)shape;
var scalingFactor = 0.1;
var zoom = (z * (scalingFactor));
double startScaledX = (line.StartPoint.X * zoom) + ((squareSize * -1) * x);
double startScaledY = (line.StartPoint.Y * -1 * zoom) + ((squareSize * -1) * y);
double endScaledX = (line.EndPoint.X * zoom) + ((squareSize * -1) * x);
double endScaledY = (line.EndPoint.Y * -1 * zoom) + ((squareSize * -1) * y);
var width = Math.Abs(endScaledX - startScaledX);
var height = Math.Abs(endScaledY - startScaledY);
var startPoint = new System.Drawing.PointF((float)startScaledX, (float)startScaledY);
var endPoint = new System.Drawing.PointF((float)endScaledX, (float)endScaledY);
var rectDrawBounds = (new RectangleF((float)startScaledX, (float)startScaledY, (float)width, (float)height));
var rectTileBounds = new RectangleF(0, 0, 256, 256);
g.DrawLine(drawPen, startPoint, endPoint);
}
I have noticed that if I zoom in and out at [0,0] then the zoom works perfectly. Everything else seems to shift the map.
I would appreciate any help that you can offer.
In Leaflet's L.CRS.Simple, the map scale grows by a factor of 2 every zoom level. In other words:
scale = 2**z;
or
scale = Math.pow(2,z);
or
scale = 1<<z;
or
At zoom level 0, a 256-pixel tile covers 256 map units. One map unit spans over 1 pixel.
At zoom level 1, a 256-pixel tile covers 128 map units. One map unit spans over 2 pixels.
At zoom level 2, a 256-pixel tile covers 64 map units. One map unit spans over 4 pixels.
At zoom level n, a 256-pixel tile covers 256/2n map units. One map unit spans over 2n pixels.
For reference, see https://github.com/Leaflet/Leaflet/blob/master/src/geo/crs/CRS.Simple.js
Fix your z, scalingFactor and zoom calculations and relationships accordingly.

Lines and shapes drawn at incorrect angles

I have a c# program where I need to draw some simple 2D objects on the canvas.
One of these involves drawing a rectangle and lines where I know the start point, the length and I have to calculate the end position. So I have the following code;
private void CalculateEndPoint()
{
double angle = Helper.deg2rad((double)this.StartAngle);
int x = this.StartPoint.X + (int)(Math.Cos(angle) * this.Length * -1);
int y = this.StartPoint.Y + (int)(Math.Sin(angle) * this.Length);
this.EndPoint = new Point(x, y);
}
Now this seems to work OK to calculate the end points. The issue I have is with the angle (this.StartAngle), the value I specify seems not to be how it is drawn and I seem to have the following;
Where as I'm expecting 0 at the top, 90 on the right, 180 at the bottom etc.
So to get a shape to draw straight down the canvas I have to specify 90 degrees, where as I would expect to specify 180.
Have I done something wrong? Or is it just a lack of understanding?
You should change your CalculateEndPoint function to have that:
private static void CalculateEndPoint(double dec)
{
double angle = (Math.PI / 180) * (this.StartAngle + 90); // add PI / 2
int x = StartPoint.X + (int)(Math.Cos(angle) * Length * -1);
double angle2 = (Math.PI / 180) * (this.StartAngle - 90); // minus PI / 2
int y = StartPoint.Y + (int)(Math.Sin(angle2) * Length);
EndPoint = new Point(x, y);
}
Actually, 0 should be on the right. You are multiplying the x-coordinate by -1, so you're moving it to the left.
Just remember these 2 rules:
- The cosine of the angle is the x-coordinate of the unit circle.
- The sine of the angle is the y-coordinate of the unit circle.
Since cos(0) = 1 and sin(0) = 0, the coordinate corresponding to angle 0 is (1, 0).
Whether 90 is on top or on the bottom depends on the canvas.
Some applications/frameworks consider y-coordinate 0 to be at the top of the canvas. That means you go clockwise around the circle and 90 will be at the bottom.
If y-coordinate 0 is at the bottom of the canvas, you go counter-clockwise and 90 will be at the top.

Problems with centering scene

I have to display stl models with openGL. (SharpGL.) I'd like to set the initial view, so that the model is at the center of the screen and approximately fills it. I've calculated the bounding cube of the models and set the view like this: (sceneBox is a Rect3D - it stores the location of the left-back-bottom corner and the sizes)
// Calculate viewport properties
double left = sceneBox.X;
double right = sceneBox.X + sceneBox.SizeX;
double bottom = sceneBox.Y;
double top = sceneBox.Y + sceneBox.SizeY;
double zNear = 1.0;
double zFar = zNear + 3 * sceneBox.SizeZ;
double aspect = (double)this.ViewportSize.Width / (double)this.ViewportSize.Height;
if ( aspect < 1.0 ) {
bottom /= aspect;
top /= aspect;
} else {
left *= aspect;
right *= aspect;
}
// Create a perspective transformation.
gl.Frustum(
left / ZoomFactor,
right / ZoomFactor,
bottom / ZoomFactor,
top / ZoomFactor,
zNear,
zFar);
// Use the 'look at' helper function to position and aim the camera.
gl.LookAt(
0, 0, 2 * sceneBox.SizeZ,
sceneBox.X + 0.5 * sceneBox.SizeX, sceneBox.Y + 0.5 * sceneBox.SizeY, sceneBox.Z - 0.5 * sceneBox.SizeZ,
0, 1, 0);
This works nice with my small, hand-made test model: (it has a box size of 2*2*2 units)
This is exactly what I want. (The yellow lines show the bounding box)
But, when I load an stl model, which is about 60*60*60 units big, I get this:
It's very small and too far up.
What should I change to make it work?
Here's the full thing: https://dl.dropbox.com/u/17798054/program.zip
You can find this model in the zip as well. The quoted code is in KRGRAAT.SZE.Control.Engine.GLEngine.UpdateView()
Apparently the problem are the arguments you are using in lookAt function. If you have calculated bounding cube all you need to do is to place it in the distance (eyeZ) from the camera of
sizeX/tan(angleOfPerspective)
where sizeX is width of Quad of which cube is built, angleOfPerspective is first parameter of GlPerspective of course having centerX == posX == centreX of the front quad and centerY == posY == centreY of the front quad and frustum is not necessary
lookAt reference http://www.opengl.org/sdk/docs/man2/xhtml/gluLookAt.xml
So, to clarify Arek's answer, this is how I fixed it:
// Calculate viewport properties
double zNear = 1.0;
double zFar = zNear + 10 * sceneBox.SizeZ; // had to increase zFar
double aspect = (double)this.ViewportSize.Width / (double)this.ViewportSize.Height;
double angleOfPerspective = 60.0;
double centerX = sceneBox.X + 0.5 * sceneBox.SizeX;
double centerY = sceneBox.Y + 0.5 * sceneBox.SizeY;
double centerZ = sceneBox.Z + 0.5 * sceneBox.SizeZ;
// Create a perspective transformation.
gl.Perspective( // swapped frustum for perspective
angleOfPerspective / ZoomFactor, // moved zooming here
aspect,
zNear,
zFar);
// Use the 'look at' helper function to position and aim the camera.
gl.LookAt(
centerX, centerY, sceneBox.SizeX / Math.Tan(angleOfPerspective), // changed eye position
centerX, centerY, -centerZ,
0, 1, 0);

How to retrieve zoom factor of a WinForms PictureBox?

I need the precise position of my mouse pointer over a PictureBox.
I use the MouseMove event of the PictureBox.
On this PictureBox, I use the "zoom" property to show an image.
What is the correct way for getting the position of the mouse on the original (unzoomed) image?
Is there a way to find the scale factor and use it?
I think need to use imageOriginalSize/imageShowedSize to retrieve this scale factor.
I use this function:
float scaleFactorX = mypic.ClientSize.Width / mypic.Image.Size.Width;
float scaleFactorY = mypic.ClientSize.Height / mypic.Image.Size.Height;
Is possible to use this value to get the correct position of the cursor over the image?
I had to solve this same problem today. I wanted it to work for images of any width:height ratio.
Here's my method to find the point 'unscaled_p' on the original full-sized image.
Point p = pictureBox1.PointToClient(Cursor.Position);
Point unscaled_p = new Point();
// image and container dimensions
int w_i = pictureBox1.Image.Width;
int h_i = pictureBox1.Image.Height;
int w_c = pictureBox1.Width;
int h_c = pictureBox1.Height;
The first trick is to determine if the image is a horizontally or vertically larger relative to the container, so you'll know which image dimension fills the container completely.
float imageRatio = w_i / (float)h_i; // image W:H ratio
float containerRatio = w_c / (float)h_c; // container W:H ratio
if (imageRatio >= containerRatio)
{
// horizontal image
float scaleFactor = w_c / (float)w_i;
float scaledHeight = h_i * scaleFactor;
// calculate gap between top of container and top of image
float filler = Math.Abs(h_c - scaledHeight) / 2;
unscaled_p.X = (int)(p.X / scaleFactor);
unscaled_p.Y = (int)((p.Y - filler) / scaleFactor);
}
else
{
// vertical image
float scaleFactor = h_c / (float)h_i;
float scaledWidth = w_i * scaleFactor;
float filler = Math.Abs(w_c - scaledWidth) / 2;
unscaled_p.X = (int)((p.X - filler) / scaleFactor);
unscaled_p.Y = (int)(p.Y / scaleFactor);
}
return unscaled_p;
Note that because Zoom centers the image, the 'filler' length has to be factored in to determine the dimension that is not filled by the image. The result, 'unscaled_p', is the point on the unscaled image that 'p' correlates to.
Hope that helps!
If I have understood you correctly I believe you would want to do something of this nature:
Assumption: the PictureBox fits to the image width/height, there is no space between the border of the PictureBox and the actual image.
ratioX = e.X / pictureBox.ClientSize.Width;
ratioY = e.Y / pictureBox.ClientSize.Height;
imageX = image.Width * ratioX;
imageY = image.Height * ratioY;
this should give you the points ot the pixel in the original image.
Here is a simple function to solve this:
private Point RemapCursorPosOnZoomedImage(PictureBox pictureBox, int x, int y, out bool isInImage)
{
// original size of image in pixel
float imgSizeX = pictureBox.Image.Width;
float imgSizeY = pictureBox.Image.Height;
// current size of picturebox (without border)
float cSizeX = pictureBox.ClientSize.Width;
float cSizeY = pictureBox.ClientSize.Height;
// calculate scale factor for both sides
float facX = (cSizeX / imgSizeX);
float facY = (cSizeY / imgSizeY);
// use smaller one to fit picturebox zoom layout
float factor = Math.Min(facX, facY);
// calculate current size of the displayed image
float rSizeX = imgSizeX * factor;
float rSizeY = imgSizeY * factor;
// calculate offsets because image is centered
float startPosX = (cSizeX - rSizeX) / 2;
float startPosY = (cSizeY - rSizeY) / 2;
float endPosX = startPosX + rSizeX;
float endPosY = startPosY + rSizeY;
// check if cursor hovers image
isInImage = true;
if (x < startPosX || x > endPosX) isInImage = false;
if (y < startPosY || y > endPosY) isInImage = false;
// remap cursor position
float cPosX = ((float)x - startPosX) / factor;
float cPosY = ((float)y - startPosY) / factor;
// create new point with mapped coords
return new Point((int)cPosX, (int)cPosY);
}

Calculate coordinates of a regular polygon's vertices

I am writing a program in which I need to draw polygons of an arbitrary number of sides, each one being translated by a given formula which changes dynamically. There is some rather interesting mathematics involved but I am stuck on this probelm.
How can I calculate the coordinates of the vertices of a regular polygon (one in which all angles are equal), given only the number of sides, and ideally (but not neccessarily) having the origin at the centre?
For example: a hexagon might have the following points (all are floats):
( 1.5 , 0.5 *Math.Sqrt(3) )
( 0 , 1 *Math.Sqrt(3) )
(-1.5 , 0.5 *Math.Sqrt(3) )
(-1.5 , -0.5 *Math.Sqrt(3) )
( 0 , -1 *Math.Sqrt(3) )
( 1.5 , -0.5 *Math.Sqrt(3) )
My method looks like this:
void InitPolygonVertexCoords(RegularPolygon poly)
and the coordinates need to be added to this (or something similar, like a list):
Point[] _polygonVertexPoints;
I'm interested mainly in the algorithm here but examples in C# would be useful. I don't even know where to start. How should I implement it? Is it even possible?!
Thank you.
for (i = 0; i < n; i++) {
printf("%f %f\n",r * Math.cos(2 * Math.PI * i / n), r * Math.sin(2 * Math.PI * i / n));
}
where r is the radius of the circumsribing circle. Sorry for the wrong language No Habla C#.
Basically the angle between any two vertices is 2 pi / n and all the vertices are at distance r from the origin.
EDIT:
If you want to have the center somewher other than the origin, say at (x,y)
for (i = 0; i < n; i++) {
printf("%f %f\n",x + r * Math.cos(2 * Math.PI * i / n), y + r * Math.sin(2 * Math.PI * i / n));
}
The number of points equals the number of sides.
The angle you need is angle = 2 * pi / numPoints.
Then starting vertically above the origin with the size of the polygon being given by radius:
for (int i = 0; i < numPoints; i++)
{
x = centreX + radius * sin(i * angle);
y = centreY + radius * cos(i * angle);
}
If your centre is the origin then simply ignore the centreX and centreY terms as they'll be 0,0.
Swapping the cos and sin over will point the first point horizontally to the right of the origin.
Sorry, I dont have a full solution at hand right now, but you should try looking for 2D-Rendering of Circles. All classic implementations of circle(x,y,r) use a polygon like you described for drawing (but with 50+ sides).
Say the distance of the vertices to the origin is 1. And say (1, 0) is always a coordinate of the polygon.
Given the number of vertices (say n), the rotation angle required to position the (1, 0) to the next coordinate would be (360/n).
The computation required here is to rotate the coordinates. Here is what it is; Rotation Matrix.
Say theta = 360/n;
[cos(theta) -sin(theta)]
[sin(theta) cos(theta)]
would be your rotation matrix.
If you know linear algebra you already know what i mean. If dont just have a look at Matrix Multiplication
One possible implementation to generate a set of coordinates for regular polygon is to:
Define polygon center, radius and first vertex1. Rotate the vertex n-times2 at an angle of: 360/n.
In this implementation I use a vector to store the generated coordinates and a recursive function to generate them:
void generateRegularPolygon(vector<Point>& v, Point& center, int sidesNumber, int radius){
// converted to radians
double angRads = 2 * PI / double(sidesNumber);
// first vertex
Point initial(center.x, center.y - radius);
rotateCoordinate(v, center, initial, angRads, sidesNumber);
}
where:
void rotateCoordinate(vector<Point>& v, Point& axisOfRotation, Point& initial, double angRads, int numberOfRotations){
// base case: number of transformations < 0
if(numberOfRotations <= 0) return;
else{
// apply rotation to: initial, around pivot point: axisOfRotation
double x = cos(angRads) * (initial.x - axisOfRotation.x) - sin(angRads) * (initial.y - axisOfRotation.y) + axisOfRotation.x;
double y = sin(angRads) * (initial.x - axisOfRotation.x) + cos(angRads) * (initial.y - axisOfRotation.y) + axisOfRotation.y;
// store the result
v.push_back(Point(x, y));
rotateCoordinate(v, axisOfRotation, Point(x,y), angRads, --numberOfRotations);
}
}
Note:
Point is a simple class to wrap the coordinate into single data structure:
class Point{
public:
Point(): x(0), y(0){ }
Point(int xx, int yy): x(xx), y(yy) { }
private:
int x;
int y;
};
1 in terms of (relative to) the center, radius. In my case the first vertex is translated from the centre up horizontally by the radius lenght.
2 n-regular polygon has n vertices.
The simple method is:
Let's take N-gone(number of sides) and length of side L. The angle will be T = 360/N.
Let's say one vertices is located on origin.
* First vertex = (0,0)
* Second vertex = (LcosT,LsinT)
* Third vertex = (LcosT+Lcos2T, LsinT+Lsin2T)
* Fourth vertex = (LcosT+Lcos2T+Lcos3T, LsinT+Lsin2T+Lsin3T)
You can do in for loop
hmm if you test all the versions that are listed here you'll see that the implementation is not good. you can check the distance from the center to each generated point of the polygon with : http://www.movable-type.co.uk/scripts/latlong.html
Now i have searched a lot and i could not find any good implementation for calculating a polyogon using the center and the radius...so i went back to the math book and tried to implement it myself. In the end i came up with this...wich is 100% good:
List<double[]> coordinates = new List<double[]>();
#region create Polygon Coordinates
if (!string.IsNullOrWhiteSpace(bus.Latitude) && !string.IsNullOrWhiteSpace(bus.Longitude) && !string.IsNullOrWhiteSpace(bus.ListingRadius))
{
double lat = DegreeToRadian(Double.Parse(bus.Latitude));
double lon = DegreeToRadian(Double.Parse(bus.Longitude));
double dist = Double.Parse(bus.ListingRadius);
double angle = 36;
for (double i = 0; i <= 360; i += angle)
{
var bearing = DegreeToRadian(i);
var lat2 = Math.Asin(Math.Sin(lat) * Math.Cos(dist / earthRadius) + Math.Cos(lat) * Math.Sin(dist / earthRadius) * Math.Cos(bearing));
var lon2 = lon + Math.Atan2(Math.Sin(bearing) * Math.Sin(dist / earthRadius) * Math.Cos(lat),Math.Cos(dist / earthRadius) - Math.Sin(lat) * Math.Sin(lat2));
coordinates.Add(new double[] { RadianToDegree(lat2), RadianToDegree(lon2) });
}
poly.Coordinates = new[] { coordinates.ToArray() };
}
#endregion
If you test this you'll see that all the points are at the exact distance that you give ( radius ). Also don't forget to declare the earthRadius.
private const double earthRadius = 6371.01;
This calculates the coordinates of a decagon. You see the angle used is 36 degrees. You can split 360 degrees to any number of sides that you want and put the result in the angle variable.
Anyway .. i hope this helps you #rmx!

Categories

Resources