Algorithm to generate radial gradient - c++

I have this algorithm here:
pc = # the point you are coloring now
p0 = # start point
p1 = # end point
v = p1 - p0
d = Length(v)
v = Normalize(v) # or Scale(v, 1/d)
v0 = pc - p0
t = Dot(v0, v)
t = Clamp(t/d, 0, 1)
color = (start_color * t) + (end_color * (1 - t))
to generate point to point linear gradients. It works very well for me. I was wondering if there was a similar algorithm to generate radial gradients. By similar, I mean one that solves for color at point P rather than solve for P at a certain color (where P is the coordinate you are painting).
Thanks

//loop through vector
//x and y px position
int x = i%w;
int y = i/w;
float d = distance(center,int2(x,y));
//if within the grad circle
if(d < radius)
{
//somehow set v[i] alpha to this:
float a = d/r;
}

Linerise over atan2(dy,dx) where dx is x-center, and dy is y-center.
cx # center x
cy # center y
r1 # ring is defined by two radius
r2 # r1 < r2
c1 # start color
c2 # stop color
ang # start angle
px # currect point x,y
py
if( px^2 + py^2 <= r2^2 AND px^2 + py^2 >= r1^2 ) # lies in ring?
t= atan2(py-cy,px-cx)+ang
t= t+ pi # atan2 is from -pi to pi
if (t > 2* pi) # it might over 2pi becuse of +ang
t=t-2*pi
t=t/(2*pi) # normalise t from 0 to 1
color = (c1 * t) + (c2 * (1 - t))
Problem whit this algorhitm is that ang is actualy wrong and should be rotated by pi and normalized between 0 and 2pi.

Based on the comment, what you want can still be viewed as a linear gradient -- i.e. you have a line from the center to the outside of the circle, and you have a linear gradient along that line. As such, the calculation is virtually identical to what you already had.
Edit: Okay, apparently I misunderstood what you want. To figure a gradient running around a radius, you still basically linearize it -- figure out the circumference at that radius (2*Pi*R), and then do a linear interpolation along a line of that length.

Related

How to check if a point in a triangle (or on it's edge)

I'm trying to write an algorithm to determine if point is located inside a triangle or on it's edge in 3D coordinate space.
For example, I try to reach such results for different cases
I've figured out how to check if point P inside the triangle, I calculated normal vectors for triangles ABP, BCP, CAP and checked if they are similar.
Can someone explain how to check if a point is on the edge of a triangle (but not outside of a triangle)? You can provide formulas or code as you wish.
Make vectors:
r = p - A (r.x = p.x - A.x, r.y = p.y - A.y, r.z = p.z - A.z)
s = B - A
q = C - A
Calculate normal to ABC plane:
n = s x q (vector product)
Check if p lies in ABC plane using dot product:
dp = n.dot.r
If dp is zero (or has very small value like 1.0e-10 due to the floating point errors, then p is in the plane, and we can continue
Decompose vector p by base vectors s and q. At first check if z-component of normal (n.z) is non-zero. If so, use the next pair of equations (otherwise choose equations for x/z or y/z components):
px = a * sx + b * qx
py = a * sy + b * qy
Solve this system
a = (sy * qx - sx * qy) / (py * qx - px * qy)
b = (px - a * sx) / qx
If resulting coefficients a and b fulfill limits:
a >= 0
b >= 0
a + b <= 1.0
then point p lies in triangle plane inside it.

Color image boundary based on local curvature

I am searching for an algorithm (using OpenCV C or C++) which does this:
Given the boundary image, I want to find the local curvature at all points and color map it, which is what is done in the image displayed above. I got this image from Wikipedia but haven't been able to find out a way to color the boundary in this way. Kindly let me know how it can be done.
If you observe the boundary, red denotes boundary has high slope, yellow shows that the boundary is almost linear.
How can this be done?
Edit
Just to give you an idea of how I was trying to do this since two days:
I used the openCV functions convexHull and convexityDefects but realized that I am going in the wrong direction. I have to work only on the contours/boundaries of the binary image.
You can solve the problem by fitting a path of cubic Bezier curves to the boundary, then taking the curvature analytically.
[elaborated]
The boundary consists of a list of points in x, y at pixel centres, each point 1px or root 2 px form the next in the list. You need to fit a smooth cubic Bezier path to this, using a technique by Schnider in Graphics Gems (Gems 1, pp 612, An algorithm for Fitting digitized curves).
The step along the curve taking tiny steps which are always sub-pixel, and
take the curvature using
double BezierCurve::Curvature(double t) const
{
// Nice mathematically perfect formula
//Vector2 d1 = Tangent(t);
//Vector2 d2 = Deriv2(t);
//return (d1.x * d2.y - d1.y * d2.x) / pow(d1.x * d1.x + d1.y * d1.y, 1.5);
// Get the cubic coefficients like this, I store them in the Bezier
// class
/*
a = p3 + 3.0 * p1 - 3.0 * p2 - p0;
b = 3.0 * p0 - 6.0 * p1 + 3.0 * p2;
c = 3.0 * p1 - 3.0 * p0;
d = p0;
*/
double dx, dy, ddx, ddy;
dx = 3 * this->ax * t*t + 2 * this->bx * t + this->cx;
ddx = 6 * this->ax * t + 2 * this->bx;
dy = 3 * this->ay * t*t + 2 * this->by * t + this->cy;
ddy = 6 * this->ay * t + 2 * this->by;
if (dx == 0 && dy == 0)
return 0;
return (dx*ddy - ddx*dy) / ((dx*dx + dy*dy)*sqrt(dx*dx + dy*dy));
}
OpenCV findContours used with mode= CV_RETR_EXTERNAL and method= CV_CHAIN_APPROX_NONE will give you all boundary pixels ordered such as two subsequent points are neighbors.
To get the radius of a circumference by three points, there are a lot of info in the Web. Because you only need the radius, not the center, this stackexchange answer is fast.
In pseudo code:
vector_of_points = OpenCV::findContours(...)
p1 = vector start
p2, p3 are next points in vector
//boundary is circular, so in the first loop pass we must adjust
p2 = next point
p3 = last point
//Use p1 as our iterator
while ( p1 <= vector.end )
{
//curvature
radius = calculateRadius(p1, p2, p3)
//set color for pixel p2
setColor(p, radius)
increment p1, p2, p3
adjust for start point = end point
}

How to fit a 2D ellipse to given points

I would like to fit a 2D array by an elliptic function: (x / a)² + (y / b)² = 1 ----> (and so get the a and b)
And then, be able to replot it on my graph.
I found many examples on internet, but no one with this simple Cartesian equation. I probably have searched badly ! I think a basic solution for this problem could help many people.
Here is an example of the data:
Sadly, I can not put the values... So let's assume that I have an X,Y arrays defining the coordinates of each of those points.
This can be solved directly using least squares. You can frame this as minimizing the sum of squares of quantity (alpha * x_i^2 + beta * y_i^2 - 1) where alpha is 1/a^2 and beta is 1/b^2. You have all the x_i's in X and the y_i's in Y so you can find the minimizer of ||Ax - b||^2 where A is an Nx2 matrix (i.e. [X^2, Y^2]), x is the column vector [alpha; beta] and b is column vector of all ones.
The following code solves the more general problem for an ellipse of the form Ax^2 + Bxy + Cy^2 + Dx +Ey = 1 though the idea is exactly the same. The print statement gives 0.0776x^2 + 0.0315xy+0.125y^2+0.00457x+0.00314y = 1 and the image of the ellipse generated is also below
import numpy as np
import matplotlib.pyplot as plt
alpha = 5
beta = 3
N = 500
DIM = 2
np.random.seed(2)
# Generate random points on the unit circle by sampling uniform angles
theta = np.random.uniform(0, 2*np.pi, (N,1))
eps_noise = 0.2 * np.random.normal(size=[N,1])
circle = np.hstack([np.cos(theta), np.sin(theta)])
# Stretch and rotate circle to an ellipse with random linear tranformation
B = np.random.randint(-3, 3, (DIM, DIM))
noisy_ellipse = circle.dot(B) + eps_noise
# Extract x coords and y coords of the ellipse as column vectors
X = noisy_ellipse[:,0:1]
Y = noisy_ellipse[:,1:]
# Formulate and solve the least squares problem ||Ax - b ||^2
A = np.hstack([X**2, X * Y, Y**2, X, Y])
b = np.ones_like(X)
x = np.linalg.lstsq(A, b)[0].squeeze()
# Print the equation of the ellipse in standard form
print('The ellipse is given by {0:.3}x^2 + {1:.3}xy+{2:.3}y^2+{3:.3}x+{4:.3}y = 1'.format(x[0], x[1],x[2],x[3],x[4]))
# Plot the noisy data
plt.scatter(X, Y, label='Data Points')
# Plot the original ellipse from which the data was generated
phi = np.linspace(0, 2*np.pi, 1000).reshape((1000,1))
c = np.hstack([np.cos(phi), np.sin(phi)])
ground_truth_ellipse = c.dot(B)
plt.plot(ground_truth_ellipse[:,0], ground_truth_ellipse[:,1], 'k--', label='Generating Ellipse')
# Plot the least squares ellipse
x_coord = np.linspace(-5,5,300)
y_coord = np.linspace(-5,5,300)
X_coord, Y_coord = np.meshgrid(x_coord, y_coord)
Z_coord = x[0] * X_coord ** 2 + x[1] * X_coord * Y_coord + x[2] * Y_coord**2 + x[3] * X_coord + x[4] * Y_coord
plt.contour(X_coord, Y_coord, Z_coord, levels=[1], colors=('r'), linewidths=2)
plt.legend()
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
Following the suggestion by ErroriSalvo, here is the complete process of fitting an ellipse using the SVD. The arrays x, y are coordinates of the given points, let's say there are N points. Then U, S, V are obtained from the SVD of the centered coordinate array of shape (2, N). So, U is a 2 by 2 orthogonal matrix (rotation), S is a vector of length 2 (singular values), and V, which we do not need, is an N by N orthogonal matrix.
The linear map transforming the unit circle to the ellipse of best fit is
sqrt(2/N) * U * diag(S)
where diag(S) is the diagonal matrix with singular values on the diagonal. To see why the factor of sqrt(2/N) is needed, imagine that the points x, y are taken uniformly from the unit circle. Then sum(x**2) + sum(y**2) is N, and so the coordinate matrix consists of two orthogonal rows of length sqrt(N/2), hence its norm (the largest singular value) is sqrt(N/2). We need to bring this down to 1 to have the unit circle.
N = 300
t = np.linspace(0, 2*np.pi, N)
x = 5*np.cos(t) + 0.2*np.random.normal(size=N) + 1
y = 4*np.sin(t+0.5) + 0.2*np.random.normal(size=N)
plt.plot(x, y, '.') # given points
xmean, ymean = x.mean(), y.mean()
x -= xmean
y -= ymean
U, S, V = np.linalg.svd(np.stack((x, y)))
tt = np.linspace(0, 2*np.pi, 1000)
circle = np.stack((np.cos(tt), np.sin(tt))) # unit circle
transform = np.sqrt(2/N) * U.dot(np.diag(S)) # transformation matrix
fit = transform.dot(circle) + np.array([[xmean], [ymean]])
plt.plot(fit[0, :], fit[1, :], 'r')
plt.show()
But if you assume that there is no rotation, then np.sqrt(2/N) * S is all you need; these are a and b in the equation of the ellipse.
You could try a Singular Value Decomposition of the data matrix.
https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.linalg.svd.html
First center the data by subtracting mean values of X,Y from each column respectively.
X=X-np.mean(X)
Y=Y-np.mean(Y)
D=np.vstack(X,Y)
Then, apply SVD and extract
-eigenvalues (members of s) -> axis length
-eigenvectors(U) -> axis orientation
U, s, V = np.linalg.svd(D, full_matrices=True)
This should be a least-squares fit.
Of course, things can get more complicated than this, please see
https://www.emis.de/journals/BBMS/Bulletin/sup962/gander.pdf

Direction of shortest rotation between two vectors

my question is regarding working out the direction of the smallest angle between two vectors in 2D. I am making a game in C++ where one of the obstacles is a heat seeking missile launcher. I have it working by calculating the vector between the target and bullet, normalising the vector and then multiplying it by its speed. However, I am now coming back to this class to make it better. Instead of instantly locking onto the player I want it to only do so only when the bullets vector is within a certain angle (the angle between the bullets vector and the vector bulletloc->target). Otherwise I want it to slowly pan towards the target by a degrees thus giving the player enough space to avoid it. I have done all this (in a vb.net project so i could simplify the problem, work it out then re write in in C++). However the bullet always rotates clockwise towards the target even if the quickest route would be counter clockwise. So the problem is working out the direction to apply the rotation in so the smallest angle is covered. Here is my code so you can try and see what I am describing:
Function Rotate(ByVal a As Double, ByVal tp As Point, ByVal cp As Point, ByVal cv As Point)
'params a = angle, tp = target point, cp = current point, cv = current vector of bullet'
Dim dir As RotDir 'direction to turn in'
Dim tv As Point 'target vector cp->tp'
Dim d As Point 'destination point (d) = cp + vector'
Dim normal As Point
Dim x1 As Double
Dim y1 As Double
Dim VeritcleResolution As Integer = 600
tp.Y = VeritcleResolution - tp.Y 'modify y parts to exist in plane with origin (0,0) in bottom left'
cp.Y = VeritcleResolution - cp.Y
cv.Y = cv.Y * -1
tv.X = tp.X - cp.X 'work out cp -> tp'
tv.Y = tp.Y - cp.Y
'calculate angle between vertor to target and vecrot currntly engaed on'
Dim tempx As Double
Dim tempy As Double
tempx = cv.X * tv.X
tempy = cv.Y * tv.Y
Dim DotProduct As Double
DotProduct = tempx + tempy 'dot product of cp-> d and cp -> tp'
Dim magCV As Double 'magnitude of current vector'
Dim magTV As Double 'magnitude of target vector'
magCV = Math.Sqrt(Math.Pow(cv.X, 2) + Math.Pow(cv.Y, 2))
magTV = Math.Sqrt(Math.Pow(tv.X, 2) + Math.Pow(tv.Y, 2))
Dim VectorAngle As Double
VectorAngle = Acos(DotProduct / (magCV * magTV))
VectorAngle = VectorAngle * 180 / PI 'angle between cp->d and cp->tp'
If VectorAngle < a Then 'if the angle is small enough translate directly towards target'
cv = New Point(tp.X - cp.X, tp.Y - cp.Y)
magCV = Math.Sqrt((cv.X ^ 2) + (cv.Y ^ 2))
If magCV = 0 Then
x1 = 0
y1 = 0
Else
x1 = cv.X / magCV
y1 = cv.Y / magCV
End If
normal = New Point(x1 * 35, y1 * 35)
normal.Y = normal.Y * -1
cv = normal
ElseIf VectorAngle > a Then 'otherwise smootly translate towards the target'
Dim x As Single
d = New Point(cp.X + cv.X, cp.Y + cv.Y)
a = (a * -1) * PI / 180 'THIS LINE CONTROL DIRECTION a = (a*-1) * PI / 180 would make the rotation counter clockwise'
'rotate the point'
d.X -= cp.X
d.Y -= cp.Y
d.X = (d.X * Cos(a)) - (d.Y * Sin(a))
d.Y = (d.X * Sin(a)) + (d.Y * Cos(a))
d.X += cp.X
d.Y += cp.Y
cv.X = d.X - cp.X
cv.Y = d.Y - cp.Y
cv.Y = cv.Y * -1
End If
Return cv
End Function
One idea I had was to work out the bearing of the two vectors and if the difference is greater than 180 degrees, rotate clockwise otherwise rotate counter clockwise, any ideas would be helpful. Thanks.
EDIT: I would like to add that this site is very helpful. I often use questions posed by others to solve my own problems and I want to take the chance to say thanks.
As you've written in your code, the angle between two (normalized) vectors is the inverse cosine of their dot product.
To get a signed angle, you can use a third vector representing the normal of the plane that the other two vectors lie on -- in your 2D case, this would be a 3D vector pointing straight "up", say (0, 0, 1).
Then, take the cross-product of the first vector (the one you want the angle to be relative to) with the second vector (note cross-product is not commutative). The sign of the angle should be the same as the sign of the dot product between the resulting vector and the plane normal.
In code (C#, sorry) -- note all vectors are assumed to be normalized:
public static double AngleTo(this Vector3 source, Vector3 dest)
{
if (source == dest) {
return 0;
}
double dot; Vector3.Dot(ref source, ref dest, out dot);
return Math.Acos(dot);
}
public static double SignedAngleTo(this Vector3 source, Vector3 dest, Vector3 planeNormal)
{
var angle = source.AngleTo(dest);
Vector3 cross; Vector3.Cross(ref source, ref dest, out cross);
double dot; Vector3.Dot(ref cross, ref planeNormal, out dot);
return dot < 0 ? -angle : angle;
}
This works by taking advantage of the fact that the cross product between two vectors yields a third vector which is perpendicular (normal) to the plane defined by the first two (so it's inherently a 3D operation). a x b = -(b x a), so the vector will always be perpendicular to the plane, but on a different side depending on the (signed) angle between a and b (there's something called the right-hand rule).
So the cross product gives us a signed vector perpendicular to the plane which changes direction when the angle between the vectors passes 180°. If we know in advance a vector perpendicular to the plane which is pointing straight up, then we can tell whether the cross product is in the same direction as that plane normal or not by checking the sign of their dot product.
Based on #Cameron's answer, here is the python translation i've used:
As bonus, i've added the signed_angle_between_headings function to directly return the 'quickest' turn angle between two north-referenced headings.
import math
import numpy as np
def angle_between_vectors(source, dest):
if np.array_equal(source, dest):
return 0
dot = np.dot(source, dest)
return np.arccos(dot)
def signed_angle_from_to_vectors(source, dest, plane_normal):
angle = angle_between_vectors(source, dest)
cross = np.cross(source, dest)
dot = np.dot(cross, plane_normal)
return -angle if dot < 0 else angle
def signed_angle_between_headings(source_heading, destination_heading):
if source_heading == destination_heading:
return 0
RAD2DEGFACTOR = 180 / math.pi
source_heading_rad = source_heading / RAD2DEGFACTOR
dest_heading_rad = destination_heading / RAD2DEGFACTOR
source_vector = np.array([np.cos(source_heading_rad), np.sin(source_heading_rad), 0])
dest_vector = np.array([np.cos(dest_heading_rad), np.sin(dest_heading_rad), 0])
signed_angle_rad = signed_angle_from_to_vectors(source_vector, dest_vector, np.array([0,0,1]))
return signed_angle_rad * RAD2DEGFACTOR

Creating a linear gradient in 2D array

I have a 2D bitmap-like array of let's say 500*500 values. I'm trying to create a linear gradient on the array, so the resulting bitmap would look something like this (in grayscale):
(source: showandtell-graphics.com)
The input would be the array to fill, two points (like the starting and ending point for the Gradient tool in Photoshop/GIMP) and the range of values which would be used.
My current best result is this:
alt text http://img222.imageshack.us/img222/1733/gradientfe3.png
...which is nowhere near what I would like to achieve. It looks more like a radial gradient.
What is the simplest way to create such a gradient? I'm going to implement it in C++, but I would like some general algorithm.
This is really a math question, so it might be debatable whether it really "belongs" on Stack Overflow, but anyway: you need to project the coordinates of each point in the image onto the axis of your gradient and use that coordinate to determine the color.
Mathematically, what I mean is:
Say your starting point is (x1, y1) and your ending point is (x2, y2)
Compute A = (x2 - x1) and B = (y2 - y1)
Calculate C1 = A * x1 + B * y1 for the starting point and C2 = A * x2 + B * y2 for the ending point (C2 should be larger than C1)
For each point in the image, calculate C = A * x + B * y
If C <= C1, use the starting color; if C >= C2, use the ending color; otherwise, use a weighted average:
(start_color * (C2 - C) + end_color * (C - C1))/(C2 - C1)
I did some quick tests to check that this basically worked.
In your example image, it looks like you have a radial gradient. Here's my impromtu math explanation for the steps you'll need. Sorry for the math, the other answers are better in terms of implementation.
Define a linear function (like y = x + 1) with the domain (i.e. x) being from the colour you want to start with to the colour your want to end with. You can think of this in terms of a range the within Ox0 to OxFFFFFF (for 24 bit colour). If you want to handle things like brightness, you'll have to do some tricks with the range (i.e. the y value).
Next you need to map a vector across the matrix you have, as this defines the direction that the colours will change in. Also, the colour values defined by your linear function will be assigned at each point along the vector. The start and end point of the vector also define the min and max of the domain in 1. You can think of the vector as one line of your gradient.
For each cell in the matrix, colours can be assigned a value from the vector where a perpendicular line from the cell intersects the vector. See the diagram below where c is the position of the cell and . is the the point of intersection. If you pretend that the colour at . is Red, then that's what you'll assign to the cell.
|
c
|
|
Vect:____.______________
|
|
I'll just post my solution.
int ColourAt( int x, int y )
{
float imageX = (float)x / (float)BUFFER_WIDTH;
float imageY = (float)y / (float)BUFFER_WIDTH;
float xS = xStart / (float)BUFFER_WIDTH;
float yS = yStart / (float)BUFFER_WIDTH;
float xE = xEnd / (float)BUFFER_WIDTH;
float yE = yEnd / (float)BUFFER_WIDTH;
float xD = xE - xS;
float yD = yE - yS;
float mod = 1.0f / ( xD * xD + yD * yD );
float gradPos = ( ( imageX - xS ) * xD + ( imageY - yS ) * yD ) * mod;
float mag = gradPos > 0 ? gradPos < 1.0f ? gradPos : 1.0f : 0.0f;
int colour = (int)( 255 * mag );
colour |= ( colour << 16 ) + ( colour << 8 );
return colour;
}
For speed ups, cache the derived "direction" values (hint: premultiply by the mag).
There are two parts to this problem.
Given two colors A and B and some percentage p, determine what color lies p 'percent of the way' from A to B.
Given a point on a plane, find the orthogonal projection of that point onto a given line.
The given line in part 2 is your gradient line. Given any point P, project it onto the gradient line. Let's say its projection is R. Then figure out how far R is from the starting point of your gradient segment, as a percentage of the length of the gradient segment. Use this percentage in your function from part 1 above. That's the color P should be.
Note that, contrary to what other people have said, you can't just view your colors as regular numbers in your function from part 1. That will almost certainly not do what you want. What you do depends on the color space you are using. If you want an RGB gradient, then you have to look at the red, green, and blue color components separately.
For example, if you want a color "halfway between" pure red and blue, then in hex notation you are dealing with
ff 00 00
and
00 00 ff
Probably the color you want is something like
80 00 80
which is a nice purple color. You have to average out each color component separately. If you try to just average the hex numbers 0xff0000 and 0x0000ff directly, you get 0x7F807F, which is a medium gray. I'm guessing this explains at least part of the problem with your picture above.
Alternatively if you are in the HSV color space, you may want to adjust the hue component only, and leave the others as they are.
void Image::fillGradient(const SColor& colorA, const SColor& colorB,
const Point2i& from, const Point2i& to)
{
Point2f dir = to - from;
if(to == from)
dir.x = width - 1; // horizontal gradient
dir *= 1.0f / dir.lengthQ2(); // 1.0 / (dir.x * dir.x + dir.y * dir.y)
float default_kx = float(-from.x) * dir.x;
float kx = default_kx;
float ky = float(-from.y) * dir.y;
uint8_t* cur_pixel = base; // array of rgba pixels
for(int32_t h = 0; h < height; h++)
{
for(int32_t w = 0; w < width; w++)
{
float k = std::clamp(kx + ky, 0.0f, 1.0f);
*(cur_pixel++) = colorA.r * (1.0 - k) + colorB.r * k;
*(cur_pixel++) = colorA.g * (1.0 - k) + colorB.g * k;
*(cur_pixel++) = colorA.b * (1.0 - k) + colorB.b * k;
*(cur_pixel++) = colorA.a * (1.0 - k) + colorB.a * k;
kx += dir.x;
}
kx = default_kx;
ky += dir.y;
}
}