I have a problem drawing different functions with PHP (GD, of course).
I managed to draw different functions but whenever the parameters of the function change - the function floats wherever it wants.
Let us say that I have a first function y=x^2 and I have to draw it from -5 to 5. This means that the first point would be at (-5;25). And I can move that to whatever point I want if I know that. But if I choose y=2x^2 with an interval x=(-5;5). The first point is at (-5;50). So I need help in calculating how to move any function to, let's say, (0;0).
The functions are parabola/catenary alike.
What you want to do is find the maximum boundaries of the graph you are making. To do this you have to check each inflection point as well as the range bounds. Store each coordinate pair in an array
Part 1 [Range Bounds]:
Collect the coordinates from the range bounds.
<?php
$ybound[] = f($minX);
$ybound[] = f($maxX);
Part 2 [Inflections]:
This part is more difficult. You can either have a series of equations to solve for inflections for each type of parabola, or you can just brute force it. To do this, just choose a small increment, (what ever your small increment is for drawing the line), I will use 0.1
<?php
for($x = $minX; $x <= $maxX; $x += 0.1) {
$ybound[] = f($x);
}
Note, if you brute force, you can skip Part 1, otherwise, it would be faster if you could figure out the inflections for the scope of your project
Part 3 [Min Max]:
Now you get the min and max values from the array of possible y values.
<?php
$minY = min($ybound);
$maxY = max($ybound);
Part 4 [Shift]:
Now that you have this, it should be very simple to adjust. You take the top left corner and set that to 0,0 by adjusting each new coordinate to that value.
<?php
$shiftX = -$minX;
$shiftY = $maxY;
With this info, you can also determine your image size
<?php
$imageX = $maxX - $minX;
$imageY = $maxY - $minY;
Then as you generate your coordinates, you will shift each one, by adding the shift value to the coordinate.
<?php
for($x = -$minX; $x <= $maxX; $x += 0.1) {
$ycoor = $shiftY - f($x);
$xcoor = $x + $shiftX;
//draw ...
}
Drawing the axis is also easy,
<?php
$xaxis = $shiftY;
$yaxis = $shiftX;
(I think I have all my signs correct. Forgive me if they are off)
You first need to determine the bounding box of your function. Then, you calculate the width and the height, and you normalize so it fits into a rectangle whose top left coordinate is (0,0). Maybe you will also need to scale the figure to get it at a specific size.
Related
I've been searching this for a long time now and I want to know if any of you has some kind of resource or knowledge of some kind of algorithm that can take an image and return the percentage of most significant colors in an image. But not any color, I want to make the percentages fit with a predefined constant palette of 12 colors (the same you use in image search on google for sorting).
However, the script I made works in the sense that it gets the colors that are most PRESENT, but not necessarily most significant.
For example, take this image of the first black hole. It has mostly only black with some smudges of red/white/yellow/brown colors. But in proportions these are considered as almost nothing by checking only their amount.
Example: red = 1%, yellow = 3% and black = 96% (not exact values but accurate).
The problem is that even though they are not the most in amount, they are clearly the MAIN colors in the image as for how the human eye sees them. Is there an algorithm for that or a technique? Thank you for reading.
Let's say your image has resolution WxH.
You say you have a palette of 12 colors.
In order to make an algorithm that sort this colors based on how much they appear in the image, you could create 3 variables:
A global counter counter of valid colors within the image;
array of frequencies with length equals to palette size;
array of colors representing the palette.
A possible algorithm could be:
for(int i = 0; i < W; i++){
for(int j = 0; j < H; j++){
Color dif = new Color(255, 255, 255, 1);
Color currDif;
int minIndex = -1;
for(int k = 0; k < palette.length; k++){
currDif = palette[k] - image[i][j];
if(dif > currDif){
dif = currDif;
minIndex = k;
}
}
if( CloseEnough(dif, palette[minIndex]) ){
frequency[minIndex]++;
counter++;
}
}
}
Then, to verify the percentages, one can just:
for(int i = 0; i < 12; i++){
print("Color i appears (Palette[i] / counter) %");
}
I considered if you find a color that is not close enough to any color in the palette, you ignore it, but obviously you can consider it by simply incrementing the counter anyway, so these colors will be the percentage left.
The functions CloseEnough and < are of your choice to make, but they could work like this:
bool CloseEnough(Color c1, Color c2){
return abs(c1.r - c2.r) < 30 && abs(c1.g - c2.g) < 30 && abs(c1.b - c2.b) < 30;
//note that 30 can be quite small
//this value can be modified based on testing and result quality
}
//the closer to black a color is, the smaller it will be
bool < (Color c1, Color c2){
return (c1.r + c1.g + c1.b) < (c2.r + c2.b + c2.g);
}
There may be better solutions out there, but hope it helps.
I'm working in php with 3D geometries(not the best choice,I know...).
I have K coplanar 3D points, also with x,y,z value. Together they form a polygon. I need to triangulate this polygon. I have already a working delaunay traingulation function which works for 2D Polygons.
So I want to rotate the given points, so that they lay on a plane parallel to the x,y plane. After that I can triangulated it using the x,y values. The following pseudocode shall describe how I want to get to this goal.
I build up the following code with reference on this (I'm usign the answer accepted from the OP): https://math.stackexchange.com/questions/180418/calculate-rotation-matrix-to-align-vector-a-to-vector-b-in-3d, but it doesn't work as I expected. In order to know if it worked, every mapped point shall then have the same 'z' value.
Here is the question, how do I get the correct rotation matrix? Or did I made a conceptual mistake?
function matrixRotationMapping(Point $p, Point $q, Point $r)
{
$normalPolygon =calculatePlaneNormal($p, $q, $r);
$v = crossProduct($normalPolygon, new Point(0, 0, 1));
$c = dotProduct($normalPolygon, new Point(0, 0, 1));
$matrix = buildRotationMatrix($v, $c);
return $matrix;
}
function buildRotationMatrix($v, $c)
{
$R2 = new Matrix(array(array(1, -$v->z, $v->y), array($v->z, 1, -$v->x), array(-$v->y, $v->x, 1)));
$costant = 1/(1+$c);
$R3 = multiplyMatrices($R2, $R2);
$R3 = multiplyMatricesWithFactor($R3, $costant);
$finalMatrix = sumMatrices($R2, $R3);
return $finalMatrix;
}
function calc2DMapping($points)
{
$rotationMatrix = matrixRotationMapping($points[0], $points[1], $points[2]);
foreach($points as $point)
{
$mappedPoint = $rotationMatrix->multiplyWithPoint($point);
$mappedPoints[] = new MappedPoint($mappedPoint);
}
}
I found another helpful description of the problem, but I wasn't able to implement it: Mapping coordinates from plane given by normal vector to XY plane
Thanks in advance for your attention.
You need basis vectors X,Y,Z first. So let take the mid point A and two distant points to it B,C (not on single line) from your data set first. The X,Y should lie in the plane and Z should be normal to it so:
X = B-A // any non zero vector inside plane
X = X / |X| // unit in size
Y = C-A // any non zero vector inside plane
(X.Y) != 0 // but not parallel to X !!!
Y = Y / |Y| // unit in size
Compute normal to the plane your points lie in and correct Y axis.
Z = X x Y // cross product gives you perpendicular vector
Y = Z x X // now all vectors are perpendicular and unit
So feed these 3 vectors to rotation part of your transform matrix and set origin to A. But as you need to go from your data set to the plane local coordinate you need inverse matrix (or use pseudo inverse based on transposing)
Anyway now with the basis vectors you can map your plane parametrically like this:
P(u,v) = A + u*X + v*Y
Where u,v = <-inf,+inf> are surface distances form A in X,Y directions. That can get handy sometimes. If you need to compute u,v from P then exploit dot product:
u = ((P-A).X) = dot(P-A,X)
v = ((P-A).Y) = dot(P-A,Y)
Which can be also used to transform to 2D instead of using matrix ...
I have a list of coordinate to be sorted with a spiral algorithm. My need is to start on the middle of the area and "touch" any coordinate.
To simplify this is the representation of the (unsorted) list of coordinates (x,y marked with a "dot" on following image).
CSV list of coordinates is available here.
X increase from left to right
Y increases from TOP to BOTTOM
Every coordinate is not adjacent to the following one but are instead distanciated by 1 or 2 dice (or more in certain case).
Starting from the center of the area, I need to touch any coordinate with a spiral movement:
to parse each coordinate I've drafted this PHP algorithm:
//$missing is an associative array having as key the coordinate "x,y" to be touched
$direction = 'top';
$distance = 1;
$next = '128,127'; //starting coordinate
$sequence = array(
$next;
)
unset($missing[$next]);
reset($missing);
$loopcount = 0;
while ($missing) {
for ($loop = 1; $loop <= 2; $loop++) {
for ($d = 1; $d <= $distance; $d++) {
list($x,$y) = explode(",", $next);
if ($direction == 'top') $next = ($x) . "," . ($y - 1);
elseif ($direction == 'right') $next = ($x + 1) . "," . ($y);
elseif ($direction == 'bottom') $next = ($x) . "," . ($y + 1);
elseif ($direction == 'left') $next = ($x - 1) . "," . ($y);
if ($missing[$next]) {
unset($missing[$next]); //missing is reduced every time that I pass over a coordinate to be touched
$sequence[] = $next;
}
}
if ($direction == 'top') $direction = 'right';
elseif ($direction == 'right') $direction = 'bottom';
elseif ($direction == 'bottom') $direction = 'left';
elseif ($direction == 'left') $direction = 'top';
}
$distance++;
}
but as coordinate are not equidistant from each other, I obtain this output:
As is clearly visible, the movement in the middle is correct whereas and accordingly with the coordinate position, at a certain instant the jump between each coordinate are not anymore coherent.
How can I modify my code to obtain an approach like this one, instead?
To simplify/reduce the problem: Imagine that dots on shown above image are cities that the salesman have to visit cirurarly. Starting from the "city" in the middle of the area, the next cities to be visited are the ones located near the starting point and located on North, East, Soutch and West of the starting point. The salesman cannot visit any further city unless all the adjacent cities in the round of the starting point hadn't been visited. All the cities must be visited only one time.
Algorithm design
First, free your mind and don't think of a spiral! :-) Then, let's formulate the algorithms constraints (let's use the salesman's perspective):
I am currently in a city and am looking where to go next. I'll have to find a city:
where I have not been before
that is as close to the center as possible (to keep spiraling)
that is as close as possible to my current city
Now, given these three constraints you can create a deterministic algorithm that creates a spiral (well at least for the given example it should, you probably can create cases that require more effort).
Implementation
First, because we can walk in any direction, lets generally use the Euclidean distance to compute distances.
Then to find the next city to visit:
$nextCost = INF;
$nextCity = null;
foreach ($notVisited as $otherCity) {
$cost = distance($current_city, $other_city) + distance($other_city, $centerCity);
if ($cost < $nextCost) {
$nextCost = $cost;
$nextCity = $otherCity;
}
}
// goto: $nextCity
Just repeat this until there are no more cities to visit.
To understand how it works, consider the following picture:
I am currently at the yellow circle and we'll assume the spiral up to this point is correct. Now compare the length of the yellow, pink and blue lines. The length of those lines is basically what we compute using the distance functions. You will find that in every case, the next correct city has the smallest distance (well, at least as long as we have as many points everywhere, you probably can easily come up with a counter-example).
This should get you started to implement a solution for your problem.
(Correctness) Optimization
With the current design, you will have to compare the current city to all remaining cities in each iteration. However, some cities are not of interest and even in the wrong direction. You can further optimize the correctness of the algorithm by excluding some cities from the search space before entering the foreach loop shown above. Consider this picture:
You will not want to go to those cities now (to keep spiraling, you shouldn't go backwards), so don't even take their distance into account. Albeit this is a little more complicated to figure out, if your data points are not as evenly distributed as in your provided example, this optimization should provide you a healthy spiral for more disturbed datasets.
Update: Correctness
Today it suddenly struck me and I rethought the proposed solution. I noticed a case where relying on the two euclidean distances might yield unwanted behavior:
It is easily possible to construct a case where the blue line is definitely shorter than the yellow one and thus gets preferred. However, that would break the spiral movement. To eliminate such cases we can make use of the travel direction. Consider the following image (I apologize for the hand-drawn angles):
The key idea is to compute the angle between the previous travel direction and the new travel direction. We are currently at the yellow dot and need to decide where to go next. Knowing the previous dot, we can obtain a vector representing the previous direction of the movement (e.g. the pink line).
Next, we compute the vector to each city we consider and compute the angle to the previous movement vector. If that vector is <= 180 deg (case 1 in the image), then the direction is ok, otherwise not (case 2 in the image).
// initially, you will need to set $prevCity manually
$prevCity = null;
$nextCost = INF;
$nextCity = null;
foreach ($notVisited as $otherCity) {
// ensure correct travel direction
$angle = angle(vectorBetween($prevCity, $currentCity), vectorBetween($currentCity, $otherCity));
if ($angle > 180) {
continue;
}
// find closest city
$cost = distance($current_city, $other_city) + distance($other_city, $centerCity);
if ($cost < $nextCost) {
$nextCost = $cost;
$nextCity = $otherCity;
}
}
$prevCity = $currentCity;
// goto: $nextCity
Pay attention to compute the angle and vectors correctly. If you need help on that, I can elaborate further or simply ask a new question.
The problem seems to be in the if-conditional when you missing traverse a co-ordinate, I.e because of rounding of the corners. A else conditional with a reverse to the previous calculation of the co-ordinate would fix it.
I have built a simple modular scale calculator, where I can enter a base number (say font size or line height) and an important number (maybe column width, page width, or another font size) and select a ratio (golden ratio for example) and the calculator will display a double stranded scale for use in page layout. see example below
I have been toying with the idea of allowing users to input points and picas and then displaying the scale in one or the other.
The problem is that picas are base 12 numbers (12 points to a pica), I figured if I could just convert the input (something like 16p6) to base 12 I could do the calculation and go from there.
I just can't work out how to do basic calculations in another base. I'm really just messing around to see what I can come up with, let me know if you think I'm barking up the wrong tree.
So my question is this how do I do calculations in base 12?
<?php
// basic modular scale calculation
$goldenRatio = 1.618;
$baseNumber = 16;
$i = 0;
while ($i <= 10) {
echo round($baseNumber,1). "<br>";
$baseNumber = $baseNumber * $goldenRatio;
$i++;
}
echo "<hr><br>";
// Attempt at base 12 calculation
$a=base_convert(16,10,12);
$b=base_convert(12,10,12);
$r = ($a*$b);
echo $a."*".$b."=";
echo $r;
I'm really just messing around to se what I can come up with, let me know if you think I'm barking up the wrong tree.
Update
To solve the problem of converting Picas to base points from a string like '12p6' I ended up using regex to first test if Picas and Points had been supplied the split the Picas and Points.
function isPica($data) {
if (preg_match('/^[0-9]+(?i)p([0-1]?[0-9])?$/i',$data)) {
return true;
}
return false;
}
function makePoints($data) {
$data = preg_replace('/^([0-9]+)((?i)p)(([0-1]?[0-9])?)$/i','$1.$3',$data);
$data = explode('.',$data);
$points = floor($data[0] * 12);
$points = $data[1] + $points;
return $points;
}
Modular Scale Calculator
Git Hub — Modular Scale Calculator
base_convert just converts the string representation. You can't do calculations using strings of numbers in base 12 in php. When dealing with imperial units, you usually have multiple "bases" to deal with. So it has to be done manually. When you're doing calculations, the base doesn't matter.
Convert all the different units to the smallest one (points). $a = 3*12 + 7;//3picas, 7points.
Do the calculations.
Convert back to original units.
$points = (int)$val % 12;
$picas = (int)($val / 12);
or
$picas = floor($val / 12);
$points = $val - 12*$picas;
I am rendering a waveform in PHP by downsampling it with the lame encoder and then drawing the waveform from the resulting data points. I am currently getting images like this:
What I would like to do is modify my code so that the apparent dynamic range of the waveform is essentially 'compressed'. To produce a waveform that looks more like this:
The equation I am currently using to render the height of each data point is as follows:-
// draw this data point
// relative value based on height of image being generated
// data values can range between 0 and 255
$v = (int) ( $data / 255 * $height );
// don't print flat values on the canvas if not necessary
if (!($v / $height == 0.5 && !$draw_flat))
// draw the line on the image using the $v value and centering it vertically on the canvas
imageline(
$img,
// x1
(int) ($data_point / DETAIL),
// y1: height of the image minus $v as a percentage of the height for the wave amplitude
$height * $wav - $v,
// x2
(int) ($data_point / DETAIL),
// y2: same as y1, but from the bottom of the image
$height * $wav - ($height - $v),
imagecolorallocate($img, $r, $g, $b)
);
With the actual amplitude being defined by the first line of this code:-
$v = (int) ( $data / 255 * $height );
Unfortunately my math skill is poor at best. What I need to do is essentially apply a 'curve' to the value of $v so that when the number input into the equation is lower, the resulting output is higher and as the input number is increased the equation reduces the amplification until finally when the input reaches 255 the output should also be 255. Also the curve should be such so that with an input of 0 the output is also 0.
I apologise if this is not clear but I am finding this question very hard to articulate with my limited math experience.
Perhaps a visual representation would help describe my intent:-
When the value of $v is either 0 or 255 the output of the equation should be exactly the input (0 or 255). However, when the input is a value inbetween, it should follow the resulting output of the curve above. (the above was only a rough drawing to illustrate.)
EDIT:
Based on Alnitiks 'pow' function solution I am now generating waveforms that look like this:-
Using the replacement equation for the $v variable as follows:-
$v = pow($data / 255.0, 0.4) * $height;
I have tried upping the 0.4 value but the result is still not as intended.
EDIT 2:
As requested here is a raw datadump of my $data variable:
Raw Data
This gets passed into the equation to return $v before being used to draw the waveform (you can see what I do to variable $v in the original code I posted above. $height is simple the number of pixels high I have set the image to render.
This data is a comma seperated list of values. I hope this helps. It appears your assertion that the mean value is 128 is correct. So far I have been unable to get my head around your correction for this. I'm afraid it is slightly beyond my current understanding.
With no math skills (and probably useful to have a speedy display):
You have 256 possible values. Create an array that contains the "dynamic" value for each of these values:
$dynamic = array(
0 => 0,
1 => 2,
...
);
That done, you can easily get the dynamic value:
$v = (int) ($dynamic[(int) $data / 255] * $height);
You might lose some precision, but it's probably useful.
Natural dynamic values are generated by the math sine and cosine functions, in PHP this sinDocs (and others linked there).
You can use a loop and that function to prefill the array as well and re-use the array so you have pre-computed values:
$sine = function($v)
{
return sin($v * 0.5 * M_PI);
};
$dynamic = array();
$base = 255;
for ($i = 0; $i <= $base; $i++)
{
$dynamic[$i] = $i/$base;
}
$dynamic = array_map($sine, $dynamic);
I use a variable function here, so you can write multiple and can easily test which one matches your needs.
You need something similar to gamma correction.
For input values x in the range 0.0 -> 1.0, take y = pow(x, n) when n should be in the range 0.2 - 0.7 (ish). Just pick a number that gives the desired curve.
As your values are in the range 0 -> 255 you will need to divide by 255.0, apply the pow function, and then multiply by 255 again, e.g.
$y = 255 * pow($x / 255.0, 0.4);
The pow formula satisfies the criteria that 0 and 1 map to themselves, and smaller values are "amplified" more than larger values.
Here's a graph showing gamma curves for n = 1 / 1.6, 1 / 2, 1 / 2.4 and 1 / 2.8, vs the sin curve (in red):
The lower the value of n, the more "compression" is applied to the low end, so the light blue line is the one with n = 1 / 2.8.
Note how the sin curve is almost linear in the range 0 to 0.5, so provides almost no low end compression at all.
If as I suspect your values are actually centered around 128, then you need to modify the formula somewhat:
$v = ($x - 128.0) / 128.0;
$y = 128 + 127 * sign($v) * pow(abs($v), 0.4);
although I see that the PHP developers have not included a sign function in the PHP library.
Simple downsampling is not going to give you a correct render, as it will leave the original signal with only low frequencies, whilst all frequencies contribute to amplitudes. So you need to build the peak data (min and max for a certain range of values) from the original waveform to visualize it. You shouldn't apply any non-linear functions to your data, as the waveform representation is linear (unlike gamma-compressed images).