PHP GD basic rectangle offset maths - php

I'm completely stuck with something that i believe is relatively simple to solve:
In PHP I am drawing filled rectangles on top of an image (using GD) - I want a small gap between each of them - which is specified in the code (gap is the same for each box) - I loop around until i've hit the max amount of rectangles on a row (e.g max columns) - and i've managed the horizontal gap offset but for the life of me can't work out the vertical gap maths.
Here is my code in it's nasty entirety:
http://pastebin.com/MHUqi0tG
But specifically PHP accepts two sets of coordinates to make a rectangle - two for the top left (x and y) and two for the bottom right (x and y).
Here is my code for that in particular:
$left_wall_of_box_x = ( $current_col * $box_size ) + $origin_x ;
if($last_block_x != $origin_x){
$left_wall_of_box_x = $last_block_x + $gap;
}
/*verticals coord 1*/
$left_wall_of_box_y = ( $current_row * $box_size ) + $origin_y;
$right_wall_x = $left_wall_of_box_x + $box_size ;
$right_wall_y = $left_wall_of_box_y + $box_size;
imagefilledrectangle($im, $left_wall_of_box_x, $left_wall_of_box_y, $right_wall_x, $right_wall_y, $red);
$gap = 2;
$origin_x & origin_y = the place i start drawing boxes from.
Output at the moment is nicely separated boxes on the horizontal axis but vertically they are hitting each other/merging together.
Any help obviously greatly appreciated - Tearing hair out on this.
Thanks

The mistake is here:
$left_wall_of_box_y = ( $current_row * $box_size ) + $origin_y;
Should be:
$left_wall_of_box_y = ( $current_row * ($box_size + $gap_vertical)) + $origin_y;
But your code is fragile and hard to read. It's fragile as you're making your calculations more be dependent on variables that represent subtly different things, and as there's more variables, it's harder to fit in your head - which is partly why you didn't spot the error.
So, rather than doing this:
while($x <= $total_boxes_to_draw)
{
if($current_col >= $cols)
{
//reset to start of row
}
}
Write your code like this.
$finished = false;
for ($y=0; ($y<$rows) && ($finished==false) ; $y++)
{
$yPosition = $origin_y + $y * ($box_size + $spacing_vertical);
for ($x=0 ; $x<$cols && ($finished==false) ; $x++)
{
$xPosition = $origin_x + $x * ($box_size + $spacing_horizontal);
//draw box at $xPosition, $yPosition
$boxesDrawn++;
if ($boxesDrawn >= $total_boxes_to_draw){
$finished = true;
}
}
}

You are adding a gap horizontally, but you also have to add it vertically.
A simple fix is subtracting the gap from the height of the rectangles you fill:
imagefilledrectangle($im, $left_wall_of_box_x, $left_wall_of_box_y,
$right_wall_x, $right_wall_y - gap, $red);

Related

How to quantify colors in an image like Google does (somewhat)?

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.

Bresenham's Line Algorithm in PHP

I am working on a text based (console) WW2 strategy game, set on a 2d square grid map. I want a method to calculate line of sight from one tile on the map to another. I've used this Java example to base my code off, this is what I've written:
public function plotLine($x0, $y0, $x1, $y1, $size)
{
$arr = $this->getEmptyMap($size);
$xDist = abs($x1 - $x0);
$yDist = -abs($y1 - $y0);
if($x0 < $x1) {
$xStep = 1;
} else {
$xStep = -1;
}
if($y0 < $y1) {
$yStep = 1;
} else {
$yStep = -1;
}
$plotError = $xDist + $yDist;
$arr[$x0][$y0] = 1;
while($x0 != $x1 || $y0 != $y1) {
// if(2 * $plotError > $yDist) {
// // Horizontal step
// $plotError += $yDist;
// $x0 += $xStep;
// }
// if(2 * $plotError < $xDist) {
// // Vertical step
// $plotError += $xDist;
// $y0 += $yStep;
// }
if(2 * $plotError - $yDist > $xDist - 2 * $plotError) {
// Horizontal step
$plotError += $yDist;
$x0 += $xStep;
} else {
// Vertical step
$plotError += $xDist;
$y0 += $yStep;
}
$arr[$x0][$y0] = 1;
}
$this->line = $arr;
}
Note: getEmptyMap just fills a multidimensional array with 0's.
Testresult using (0, 0, 4, 4, 4) as input:
1100
0110
0011
0001
I've tried to ways of mapping the line: one is the normal implementation that Franz D. used (currently commented out in my example above), the other is the modified implementation that Franz D. showed. Neither is giving me the result I'm looking for; a kind of "anti-aliasing". When a solder would look from 0,0 at 2,2 and there would be buildings at 1,2 and 2,1, whatever is at 2,2 should be blocked from sight. The commented out implementation would completely ignore the buildings, the modification does "hit" 2,1 but not 1,2. How would I adjust my code to "hit" both underneath the line and above the line?
The 'problem' you are facing happens because of the special edge case when looking at an exact diagonal. There are 'only' two (simple) possibilities of handling this:
1) A diagonal increments a horizontal and vertical tile at the same time. In your game, this would mean that units would be able to look at the diagonals, even if the cardinal directions would be blocked.
2) Choose between giving precedence to the horizontal tiles or the vertical tiles and only ever incrementing one of the two. This is the algorithm of Franz D. that you ended up writing and placing in your post. Here, the if-statement is true for diagonals, which means that the result will be:
1100
0110
0011
0001
If you want the verticals to have precedence, you can alter it to:
...
if(2 * $plotError - $yDist < $xDist - 2 * $plotError) {
// Vertical step
$plotError += $xDist;
$y0 += $yStep;
} else {
// Horizontal step
$plotError += $yDist;
$x0 += $xStep;
}
...
Note that both the bodies of the if/else are swapped, and the > was changed into < in the condition.
Now, the result will be:
1000
1100
0110
0011
If you want a unit to only be able to look to the diagonals if there is nothing blocking éither of the adjacent cardinals, the simplest solution would be to use both of above variants of the algorithm, and combine their results into one single array of tiles.
One final note: if you are only interested in the coordinates and not in their values (as seems to be the case for the use case you describe), it might be more (memory)efficient to use a simple array of extracted (x, y) coordinates instead of a two-dimensional array of the complete map which you afterwards loop over to extract all (x, y) coordinates where the result is a 1.
Good luck with the game!

FPDF height of a MultiCell Element

I use the FPDF library to export some document files as PDF. One document includes a list of strings which have a different length. I print all strings as $pdf->MultiCell(). Now I would like to have the current height of that MultiCell to have the same line spacing in case that they have just one line or more.
Code Example:
//MySQL Query
while($row = mysql_fetch_array($res) {
$pdf->SetXY(18, $x);
$pdf->MultiCell(80, 5, $rowr['text']); //text has one or more lines
$x = $x + 10; // Here I would prefer a solution to say: $x = $x + 2 + height of the MultiCell()
}
I had the exact same problem; I use FPDF to generate invoices and there are four cells per row with first cell being a MultiCell with varying height (default height is 5, but if order name is too long it adds another line to a total height of 10, and so on). The problem is that the remaining 3 cells have fixed height.
After looking for solutions it seems the only way is to write a complex function or use some third party tool. Since I want my app to be as light as possible I used the following approach to solve it which in my opinion is way simpler than external plugin.
Rows with details on the Invoice start at Y=95 so I use $Y=95;
before my while loop
My first cell (the MultiCell) is as follows:
$pdf->SetXY(10,$Y);
$pdf->MultiCell(60,5,$row['Name'],1,1,'L');
I use the FPDF's GetY() function to get current height and save it as H:
$H = $pdf->GetY();
If the MultiCell's height is 5 GetY() will give back 100, if the height is 10 GetY() gives back 105 and so on.
I add new variable $height:
$height= $H-$Y;
Which as result gives me precisely the height of the MultiCell.
I use $Y and $height to set current position and column height:
$pdf->SetXY(130,$Y);
$pdf->Cell(40,$height,$row['RowName'],1,1,'L');
Before finishing the while loop set give $Y the $H's value:
$Y=$H;
Entire loop looks as follows and works perfectly:
$Y= 95;
$query = mysqli_query($con,"SELECT * FROM table");
while($row = mysqli_fetch_array($query)) {
$pdf->SetXY(10,$Y);
$pdf->MultiCell(60,5,$row['ROW1'],1,1,'L');
$H = $pdf->GetY();
$height= $H-$Y;
$pdf->SetXY(70,$Y);
$pdf->Cell(60,$height,$row['ROW2'],1,1,'L');
$pdf->SetXY(130,$Y);
$pdf->Cell(40,$height,$row['ROW3'],1,1,'L');
$pdf->SetXY(170,$Y);
$pdf->Cell(30,$height,$row['ROW4'],1,1,'L');
$Y=$H;
}
If you have 2 MultiCell columns or more in each row it gets tricky but still can be solved in a similar manner.
I found interesting solution here - https://github.com/artkonekt/pdf-invoice/blob/master/src/InvoicePrinter.php#L469
$calculateHeight = new self;
$calculateHeight->addPage();
$calculateHeight->setXY(0, 0);
$calculateHeight->SetFont($this->font, '', 7);
$calculateHeight->MultiCell($this->firstColumnWidth, 3, $item['description'], 0, 'L', 1);
$descriptionHeight = $calculateHeight->getY() + $cellHeight + 2;
So, he literally create a 'temporary' PDF, add multicell, and then simply measure height (newY - oldY)
Also, keep in mind that if text goes to new line - height of cell will be = number_of_lines * $height (height passed to MultiCell as second parameter)
So, if you passed 5 as $height, and temporary PDF measure that cell will be 15, you can be sure that text will spread to 3 lines.
I'm coding in golang so I'll show some pseudo-code. I hope the accessible methods are the same in php as in golang.
There is a method called pdf.SplitLines(text, width). You will pass your string content and the desired width and it will return an array of strings that represents the lines that'll be computed to display that content.
With that its easy. In pseudo-code it could look like:
fontSize = 10;
lineHeight = 12;
targetWidth = 50;
pdf.SetFontSize(fontSize)
nLines = length(pdf.SplitLines(content, targetWidth));
multiCellHeight = nLines * lineHeight;
pdf.Multicell(targetWidth, lineHeight, content, ...)
The rendered MultiCell will have the exact same size as stored in multiCellHeight. This way you'll get the hight before rendering.
This is working because the passed height for the MultiCell is the lineHeight of each row. If you know the rows before rendering, you'll get the total height.
I'm sorry if this fails for any reason for php. Just let me know if that's the case.
Wouldn't it be easier to simply NOT use boarders when you print out the text.
You can just use $pdf->GetY(); to get the curent y value.
Then when you have printed all the text, you can use $pdf->GetY(); to get the height after each piece of text. Compare y values to see which one is the biggest.
All you need to do then is $pdf-> SetY($y); to the original y value and paint the boarders with $pdf->Cell() now that you know height and width;
That's how I'd do it.
Edit: tested it, and seems to work. Now sing with me - "you get the beeeeest of both worlds..." no?
$cellData = array();
$cellData[0] = array();
$cellData[0][0] = array();
$cellData[0][0]['text'] = 'Audiometry';
$cellData[0][0]['width'] = '47';
$cellData[0][1] = array();
$cellData[0][1]['text'] = 'Control of Noise at Work Regulations 2005.';
$cellData[0][1]['width'] = '47';
$cellData[0][2] = array();
$cellData[0][2]['text'] = 'Fit with restrictions (as detailed below) to work in a noise controlled zone.';
$cellData[0][2]['width'] = '47';
$cellData[0][3] = array();
$cellData[0][3]['text'] = 'Recommended review date: 2021-11-27
Referral: Referred to OHP';
$cellData[0][3]['width'] = '47';
setAllCellSizes($cellData, $pdf);
function setAllCellSizes($cellData, $pdf){
$y = $pdf->GetY();
$x = $pdf->GetX();
$largestCell = 0;
for($cordI = 0; $cordI < count($cellData); $cordI++){
$curX = 10;
for($cordJ = 0; $cordJ < count($cellData[$cordI]); $cordJ++){
$pdf->SetXY($curX,$y);
$pdf-> MultiCell($cellData[$cordI][$cordJ]['width'], 5, $cellData[$cordI][$cordJ]['text'], '0', 'L');
$curX = $curX + $cellData[$cordI][$cordJ]['width'];
$cellHeight = $pdf->GetY() - $y;
if($largestCell < $cellHeight){
$largestCell = $cellHeight;
}
}
$curX = 10;
for($cordJ = 0; $cordJ < count($cellData[$cordI]); $cordJ++){
$pdf->SetXY($curX,$y);
$pdf->Cell($cellData[$cordI][$cordJ]['width'],$largestCell,'',1,1,'L');
$curX = $curX + $cellData[$cordI][$cordJ]['width'];
}
}
return $cellData;
}

randomly generating colors with php

So I'm working on making my header change color everyday, and I was attempting to create this using a random color. There are 2 colors in the header and I am making them complimentary colors. the first color is generated randomly, and then the second is modified by changing the Hue via 150`. The problem is when certain colors are chosen, they could be either too vibrant or dark. I have a check running so that I can slightly control the brightness value, but there are still some colors that are too bright ( for instance extreme yellows ). I'l post my code below. Any help or suggestions is appreciated! Thanks!
// grab a random color on hue
$h = rand(0,360);
// color values 50-120 tend to be extremely bright,
// make adjustments to the S and L accordingly
// a better solution is available?
if ($h > 50 && $h < 120) {
$s = rand(60,80);
$l = rand(30,50);
} else {
$s = rand(60,90);
$l = rand(38,63);
}
// declare string to place as css in file for primary color
$randomColor = "hsl(". $h .",". $s ."%,". $l ."%)";
// declare degree for secondary color (30 = analogous, 150 = complimentary)
$degree = 150;
// point to secondary color randomly on either side of chart
$bool = rand(0,1);
if ($bool) {
$x = $degree;
} else {
$x = -$degree;
}
// set value of the new hue
$nh = $h + $degree;
// if the new hue is above 360 or below 0, make adjustments accordingly
if ($nh > 360) {
$nh -= 360;
}
if ($nh < 0 ) {
$nh = 360 - $nh;
}
// set the secondary color
$secondaryColor = "hsl(". abs($h + $x) .",". $s ."%,". $l ."%)";
This seems very simple and I'm sure there is a better method. I looked around, but all I noticed were the basic formula's via degrees for the hue etc. Thanks again!
This is really more of a question of which colors you deem acceptable for viewing. This certainly isn't an optimal solution but it's an approach that is readable at least (it's also slightly more random than your original, if you even care about that):
function randColor() {
return array( rand(0,360), rand(0,100), rand(0,100) );
}
function isAcceptableColor($colorArr) {
// return true if the color meets your criteria
}
do {
$color = randColor();
} while ( ! isAcceptableColor($color) );

Algorithm to add Color in Bezier curves

I'm playing with GD library for a while and more particuraly with Bezier curves atm.
I used some existant class which I modified a little (seriously eval()...). I found out it was a generic algorithm used in and convert for GD.
Now I want to take it to another level: I want some colors.
No problem for line color but with fill color it's harder.
My question is:
Is there any existant algorithm for that? I mean mathematical algorithm or any language doing it already so that I could transfer it to PHP + GD?
EDIT2
So, I tried #MizardX solution with a harder curve :
1st position : 50 - 50
final position : 50 - 200
1st control point : 300 - 225
2nd control point : 300 - 25
Which should show this :
And gives this :
EDIT
I already read about #MizardX solution. Using imagefilledpolygon to make it works.
But it doesn't work as expected. See the image below to see the problem.
Top graph is what I expect (w/o the blackline for now, only the red part).
Coordinates used:
first point is 100 - 100
final point is 300 - 100
first control point is 100 - 0
final control point is 300 - 200
Bottom part is what I get with that kind of algorithm...
Convert the Bezier curve to a polyline/polygon, and fill that. If you evaluate the Bezier polynomial at close enough intervals (~1 pixel) it will be identical to an ideal Bezier curve.
I don't know how familiar you are with Bezier curves, but here is a crash course:
<?php
// Calculate the coordinate of the Bezier curve at $t = 0..1
function Bezier_eval($p1,$p2,$p3,$p4,$t) {
// lines between successive pairs of points (degree 1)
$q1 = array((1-$t) * $p1[0] + $t * $p2[0],(1-$t) * $p1[1] + $t * $p2[1]);
$q2 = array((1-$t) * $p2[0] + $t * $p3[0],(1-$t) * $p2[1] + $t * $p3[1]);
$q3 = array((1-$t) * $p3[0] + $t * $p4[0],(1-$t) * $p3[1] + $t * $p4[1]);
// curves between successive pairs of lines. (degree 2)
$r1 = array((1-$t) * $q1[0] + $t * $q2[0],(1-$t) * $q1[1] + $t * $q2[1]);
$r2 = array((1-$t) * $q2[0] + $t * $q3[0],(1-$t) * $q2[1] + $t * $q3[1]);
// final curve between the two 2-degree curves. (degree 3)
return array((1-$t) * $r1[0] + $t * $r2[0],(1-$t) * $r1[1] + $t * $r2[1]);
}
// Calculate the squared distance between two points
function Point_distance2($p1,$p2) {
$dx = $p2[0] - $p1[0];
$dy = $p2[1] - $p1[1];
return $dx * $dx + $dy * $dy;
}
// Convert the curve to a polyline
function Bezier_convert($p1,$p2,$p3,$p4,$tolerance) {
$t1 = 0.0;
$prev = $p1;
$t2 = 0.1;
$tol2 = $tolerance * $tolerance;
$result []= $prev[0];
$result []= $prev[1];
while ($t1 < 1.0) {
if ($t2 > 1.0) {
$t2 = 1.0;
}
$next = Bezier_eval($p1,$p2,$p3,$p4,$t2);
$dist = Point_distance2($prev,$next);
while ($dist > $tol2) {
// Halve the distance until small enough
$t2 = $t1 + ($t2 - $t1) * 0.5;
$next = Bezier_eval($p1,$p2,$p3,$p4,$t2);
$dist = Point_distance2($prev,$next);
}
// the image*polygon functions expect a flattened array of coordiantes
$result []= $next[0];
$result []= $next[1];
$t1 = $t2;
$prev = $next;
$t2 = $t1 + 0.1;
}
return $result;
}
// Draw a Bezier curve on an image
function Bezier_drawfilled($image,$p1,$p2,$p3,$p4,$color) {
$polygon = Bezier_convert($p1,$p2,$p3,$p4,1.0);
imagefilledpolygon($image,$polygon,count($polygon)/2,$color);
}
?>
Edit:
I forgot to test the routine. It is indeed as you said; It doesn't give a correct result. Now I have fixed two bugs:
I unintentionally re-used the variable names $p1 and $p2. I renamed them $prev and $next.
Wrong sign in the while-loop. Now it loops until the distance is small enough, instead of big enough.
I checked the algorithm for generating a Polygon ensuring a bounded distance between successive parameter-generated points, and seems to work well for all the curves I tested.
Code in Mathematica:
pts={{50,50},{300,225},{300,25},{50,200}};
f=BezierFunction[pts];
step=.1; (*initial step*)
While[ (*get the final step - Points no more than .01 appart*)
Max[
EuclideanDistance ###
Partition[Table[f[t],{t,0,1,step}],2,1]] > .01,
step=step/2]
(*plot it*)
Graphics#Polygon#Table[f[t],{t,0,1,step}]
.
.
The algorithm could be optimized (ie. generate less points) if you don't require the same parameter increment between points, meaning you can chose a parameter increment at each point that ensures a bounded distance to the next.
Random examples:
Generate a list of successive points which lie along the curve (p_list)).
You create a line between the two end points of the curve (l1).
Then you are going to find the normal of the line (n1). Using this normal find the distance between the two furthest points (p_max1, and p_max2) along this normal (d1). Divide this distance into n discrete units (delta).
Now shift l1 along n1 by delta, and solve for the points of intersection (start with brute force and check for a solution between all the line segments in p_list). You should be able to get two points of intersection for each shift of l1, excepting boundaries and self intersection where you may have only have a single point. Hopefully the quad routine can have two points of the quad be at the same location (a triangle) and fill without complaint otherwise you'll need triangles in this case.
Sorry I didn't provide pseudo code but the idea is pretty simple. It's just like taking the two end points and joining them with a ruler and then keeping that ruler parallel to the original line start at one end and with successive very close pencil marks fill in the whole figure. You'll see that when you create your little pencil mark (a fine rectangle) that the rectangle it highly unlikely to use the points on the curve. Even if you force it to use a point on one side of the curve it would be quite the coincidence for it to exactly match a point on the other side, for this reason it is better to just calculate new points. At the time of calculating new points it would probably be a good idea to regenerate the curves p_list in terms of these points so you can fill it more quickly (if the curve is to stay static of course otherwise it wouldn't make any sense).
This answer is very similar to #MizardX's, but uses a different method to find suitable points along the Bezier for a polygonal approximation.
function split_cubic($p, $t)
{
$a_x = $p[0] + ($t * ($p[2] - $p[0]));
$a_y = $p[1] + ($t * ($p[3] - $p[1]));
$b_x = $p[2] + ($t * ($p[4] - $p[2]));
$b_y = $p[3] + ($t * ($p[5] - $p[3]));
$c_x = $p[4] + ($t * ($p[6] - $p[4]));
$c_y = $p[5] + ($t * ($p[7] - $p[5]));
$d_x = $a_x + ($t * ($b_x - $a_x));
$d_y = $a_y + ($t * ($b_y - $a_y));
$e_x = $b_x + ($t * ($c_x - $b_x));
$e_y = $b_y + ($t * ($c_y - $b_y));
$f_x = $d_x + ($t * ($e_x - $d_x));
$f_y = $d_y + ($t * ($e_y - $d_y));
return array(
array($p[0], $p[1], $a_x, $a_y, $d_x, $d_y, $f_x, $f_y),
array($f_x, $f_y, $e_x, $e_y, $c_x, $c_y, $p[6], $p[7]));
}
$flatness_sq = 0.25; /* flatness = 0.5 */
function cubic_ok($p)
{
global $flatness_sq;
/* test is essentially:
* perpendicular distance of control points from line < flatness */
$a_x = $p[6] - $p[0]; $a_y = $p[7] - $p[1];
$b_x = $p[2] - $p[0]; $b_y = $p[3] - $p[1];
$c_x = $p[4] - $p[6]; $c_y = $p[5] - $p[7];
$a_cross_b = ($a_x * $b_y) - ($a_y * $b_x);
$a_cross_c = ($a_x * $c_y) - ($a_y * $c_x);
$d_sq = ($a_x * $a_x) + ($a_y * $a_y);
return max($a_cross_b * $a_cross_b, $a_cross_c * $a_cross_c) < ($flatness_sq * $d_sq);
}
$max_level = 8;
function subdivide_cubic($p, $level)
{
global $max_level;
if (($level == $max_level) || cubic_ok($p)) {
return array();
}
list($q, $r) = split_cubic($p, 0.5);
$v = subdivide_cubic($q, $level + 1);
$v[] = $r[0]; /* add a point where we split the cubic */
$v[] = $r[1];
$v = array_merge($v, subdivide_cubic($r, $level + 1));
return $v;
}
function get_cubic_points($p)
{
$v[] = $p[0];
$v[] = $p[1];
$v = array_merge($v, subdivide_cubic($p, 0));
$v[] = $p[6];
$v[] = $p[7];
return $v;
}
function imagefilledcubic($img, $p, $color)
{
$v = get_cubic_points($p);
imagefilledpolygon($img, $v, count($v) / 2, $color);
}
The basic idea is to recursively split the cubic in half until the bits we're left with are almost flat. Everywhere we split the cubic, we stick a polygon point.
split_cubic splits the cubic in two at parameter $t. cubic_ok is the "are we flat enough?" test. subdivide_cubic is the recursive function. Note that we stick a limit on the recursion depth to avoid nasty cases really screwing us up.
Your self-intersecting test case:
$img = imagecreatetruecolor(256, 256);
imagefilledcubic($img, array(
50.0, 50.0, /* first point */
300.0, 225.0, /* first control point */
300.0, 25.0, /* second control point */
50.0, 200.0), /* last point */
imagecolorallocate($img, 255, 255, 255));
imagepng($img, 'out.png');
imagedestroy($img);
Gives this output:
I can't figure out how to make PHP nicely anti-alias this; imageantialias($img, TRUE); didn't seem to work.

Categories