Core PHP

Graphing a y = f(x) function with Antialiaing

This PHP program demonstrates how to draw a graph of function of the form y = f(x) with the Graphics Draw, GD, library.

CGraphYEqFX.php

<?php

class CGraphYEqFX {
  // The range of x and y values displayed as [LowX, LowY, HighX, HighY]
  private $mdaRange = [0.0, 0.0, 0.0, 0.0];
  // The GD image object
  private $mqImage;
  private $mqaBkgdColor = array('R' => 0xFF, 'G' => 0xFF, 'B' => 0xFF);
  private $mqaAxisColor = array('R' => 0xC0, 'G' => 0xC0, 'B' => 0xC0);
  private $mqaGridColor = array('R' => 0xF8, 'G' => 0xF8, 'B' => 0xF8);
  private $mqaGraphColor = array('R' => 0x00, 'G' => 0xFF, 'B' => 0x00);
  private $mqaAsymColor = array('R' => 0xE8, 'G' => 0xE8, 'B' => 0xE8);
  public function __construct($iImgW, $iImH, $dLRX, $dLRY, $dHRX, $dHRY) {
    $this->mqImage = ImageCreateTrueColor($iImgW, $iImH);
    $this->mdaRange[0] = $dLRX;
    $this->mdaRange[1] = $dLRY;
    $this->mdaRange[2] = $dHRX;
    $this->mdaRange[3] = $dHRY;
  }
  public function __destruct() {
    Header("Content-type: image/png");
    ImagePNG($this->mqImage);
    ImageDestroy($this->mqImage);
  }
  public function SetRangeX($dLow, $dHigh) {
    $this->mdaRange[0] = $dLow;
    $this->mdaRange[2] = $dHigh;
  }
  public function SetRangeY($dLow, $dHigh) {
    $this->mdaRange[1] = $dLow;
    $this->mdaRange[3] = $dHigh;
  }
  public function DrawGridLines($dDeltaX, $dDeltaY) {
    $qBkgdColor = ImageColorExactAlpha($this->mqImage, $this->mqaBkgdColor['R'], $this->mqaBkgdColor['G'], $this->mqaBkgdColor['B'], 0);
    ImageFill($this->mqImage, 0, 0, $qBkgdColor);
	$iWidth = ImageSX($this->mqImage);
	$iHeight = ImageSY($this->mqImage);
	// Draw the vertical lines
	$qaPosAlphaX = $this->GetGridAlphaSamples($iWidth, $dDeltaX, $this->mdaRange[0], $this->mdaRange[2]);
    $iSize = count($qaPosAlphaX);
    for($j = 1; $j < $iSize; ++$j) {
      $qPixel = ImageColorExactAlpha($this->mqImage, $this->mqaGridColor['R'], $this->mqaGridColor['G'], $this->mqaGridColor['B'], $qaPosAlphaX[$j][1]);
      for ($i = 0; $i < $iHeight; ++$i) {
        ImageSetPixel($this->mqImage, $qaPosAlphaX[$j][0], $i, $qPixel);
      }
    }
    // Draw the horizontal lines
    $qaPosAlphaY = $this->GetGridAlphaSamples($iHeight, $dDeltaY, $this->mdaRange[1], $this->mdaRange[3], true);
    $iSize = count($qaPosAlphaY);
    for($j = 1; $j < $iSize; ++$j) {
      $qPixel = ImageColorExactAlpha($this->mqImage, $this->mqaGridColor['R'], $this->mqaGridColor['G'], $this->mqaGridColor['B'], $qaPosAlphaY[$j][1]);
      for ($i = 0; $i < $iWidth; ++$i) {
        ImageSetPixel($this->mqImage, $i, $qaPosAlphaY[$j][0], $qPixel);
      }
    }
    // Draw the axes last so that they write over the grid lines
    // Draw the vertical axis (y-axis)
    if ($qaPosAlphaX[0] != null) {
      foreach($qaPosAlphaX[0] as &$qPosAlphaX) {
        $qPixel = ImageColorExactAlpha($this->mqImage, $this->mqaAxisColor['R'], $this->mqaAxisColor['G'], $this->mqaAxisColor['B'], $qPosAlphaX[1]);
        for ($i = 0; $i < $iHeight; ++$i) {
          ImageSetPixel($this->mqImage, $qPosAlphaX[0], $i, $qPixel);
        }
      }
    }
    // Draw the horizontal axis (x-axis)
    if ($qaPosAlphaY[0] != null) {
      foreach($qaPosAlphaY[0] as &$qPosAlphaY) {
        $qPixel = ImageColorExactAlpha($this->mqImage, $this->mqaAxisColor['R'], $this->mqaAxisColor['G'], $this->mqaAxisColor['B'], $qPosAlphaY[1]);
        for ($i = 0; $i < $iWidth; ++$i) {
          ImageSetPixel($this->mqImage,  $i, $qPosAlphaY[0], $qPixel);
        }
      }
    }
  }
  // This function is used to get the alpha values for drawing grid lines along an axis
  // These are stored as [$pixel locations, alpha values] for coloring lines
  // At the zero index, the values are are stored for drawing the axis at the zero value
  private function GetGridAlphaSamples($iPixels, $dDeltaPerLine, $dLow, $dHigh, $dReverse = false) {
    $qaPosAlpha = [null];
    $dDelta = $dHigh - $dLow;
    $dLeastLine = (floor($dLow/$dDeltaPerLine) + 1.0)*$dDeltaPerLine;
    for ($dLine = $dLeastLine; $dLine <= $dHigh; $dLine += $dDeltaPerLine) {
      $dPixelLineLoc = ($dReverse ? (($dHigh - $dLine)/$dDelta)*$iPixels : (($dLine - $dLow)/$dDelta)*$iPixels);
      // Pixel locations are at half pixel locations: Index 0 is at .5
      // If we are in [.5,1.5), we want the pixels at index 0 and 1
      $iFirstPixel = floor($dPixelLineLoc - .5);
      $iLastpixel = (($iFirstPixel + 1) < $iPixels - 1) ? ($iFirstPixel + 1) : ($iPixels - 1);
      $iFirstPixel = ($iFirstPixel >= 0) ? $iFirstPixel : 0;
      $bIsAxis = (abs($dLine) < $dDeltaPerLine/2);
      for ($iPixel = $iFirstPixel; $iPixel <= $iLastpixel; ++$iPixel) {
        $dPixelDistance = abs($dPixelLineLoc - ($iPixel + .5));
        $dFill = pow($dPixelDistance, 1.0);
        if ($bIsAxis) {
          $qaPosAlpha[0][] = [$iPixel, (127 - 127*$dFill)];
        } else {
          $qaPosAlpha[] = [$iPixel, (127 - 127*$dFill)];
        }
      }
    }
    return $qaPosAlpha;
  }

  public function GraphFunction($sFunction) {
    // There are three spaces: Function, Image, Pixel. Pixel and Image are the same, but pixels must be offset by a half unit and Y is upside-down.
    // Function to Image (FnX, FnY) => (ImX, ImY) : ImX = ((FnX - FnLowX)/FnDeltaX)*ImWidth, ImY = ((FnHighY - FnY)/FnDeltaY)*ImHeight
    // Image To Function (ImX, ImY) => (FnX, FnY) : FnX = FnLowX + (ImX/ImWidth)*FnDeltaX, FnY = FnHighY - (ImY/ImHeight)*FnDeltaY
    // Pixel to Image (PxX, PxY) => (ImX, ImY) : ImX = PxX + .5, ImY = PxY + .5
    // Image to Pixel (ImX, ImY) => (PxX, PxY) : PxX = floor(ImX), PxY = floor(ImY)
    $iImWidth = ImageSX($this->mqImage);
	$iImHeight = ImageSY($this->mqImage);
	// An array for holding all of the geometric information for the algorithm
	// The three points Prev, Curr, and Next are the locations of the function values for the nearby pixel columns in image space
	// The two vectors are from one point to the next and are stored as unit vectors with a magnitude
	// Format [PrevX, PrevY, V1X, V1Y, V1Mag, CurrX, CurrY, V2X, V2Y, V2Mag, NextX, NextY]
	$daA = [0.0, 0.0,  0.0, 0.0, 0.0,  0.0, 0.0,  0.0, 0.0, 0.0,  0.0, 0.0];
	// The min and max y values are the min and max pixel values of each line segment between the points
	// MinPix1Y, MaxPix1Y, MinPix2Y, MaxPix2Y
	$diM = [0, 0,  0, 0];
	$daR = $this->mdaRange;
	$daI = [(float)$iImWidth, (float)$iImHeight];
	// Assign the first and second points and associated values. These are at the previous and current pixel locations: -.5 and .5
	$daA[0] = -.5;
	// Convert the x value from image space to function space. Apply the function to get the y value. Then convert the y back to image space.
	$daA[1] = ( ($daR[3] - $sFunction($daR[0] + ($daA[0]/$daI[0])*($daR[2]-$daR[0])) )/($daR[3] - $daR[1]) )*$daI[1];
	$daA[5] = .5;
	$daA[6] = ( ($daR[3] - $sFunction($daR[0] + ($daA[5]/$daI[0])*($daR[2]-$daR[0])) )/($daR[3] - $daR[1]) )*$daI[1];
	// Calculate the vector between the first two points
	$daA[2] = $daA[5] - $daA[0];
	$daA[3] = $daA[6] - $daA[1];
	$daA[4] = sqrt($daA[2]*$daA[2] + $daA[3]*$daA[3]);
	$daA[2] /= $daA[4];
	$daA[3] /= $daA[4];
	// Calculate the min and max Y as pixels
	if ($daA[1] > $daA[6]) {
      $diM[0] = floor($daA[6] + .5*(($daA[2] < 0) ? $daA[2] : -$daA[2])); // Min - only half of the perp unit vector y is added
      $diM[1] = ceil($daA[1] + .5*(($daA[2] > 0) ? $daA[2] : -$daA[2])); // Max
    } else {
      $diM[0] = floor($daA[1] + .5*(($daA[2] < 0) ? $daA[2] : -$daA[2])); // Min
      $diM[1] = ceil($daA[6] + .5*(($daA[2] > 0) ? $daA[2] : -$daA[2])); // Max
    }
    // Clamp the pixel values to the range 0 to height - 1. If the max is less than 0, set it to -1 so that no pixels are drawn. Ditto for Min > height - 1
    $diM[0] = ($diM[0] < 0) ? 0 : (($diM[0] > $iImHeight - 1) ? $iImHeight : $diM[0]);
    $diM[1] = ($diM[1] > $iImHeight - 1) ? $iImHeight - 1 : (($diM[1] < 0) ? -1 : $diM[1]);

    // Calculate the value at each horizontal pixel
    for ($iPxX = 0; $iPxX < $iImWidth; ++$iPxX) {
      $daA[10] = $iPxX + 1.5;
      $daA[11] = ( ($daR[3] - $sFunction($daR[0] + ($daA[10]/$daI[0])*($daR[2]-$daR[0])) )/($daR[3] - $daR[1]) )*$daI[1];
	  // Calculate the vector between the first two points
	  $daA[7] = $daA[10] - $daA[5];
	  $daA[8] = $daA[11] - $daA[6];
	  $daA[9] = sqrt($daA[7]*$daA[7] + $daA[8]*$daA[8]);
	  $daA[7] /= $daA[9];
	  $daA[8] /= $daA[9];
	  // Calculate the min and max Y as pixels
	  if ($daA[6] > $daA[11]) {
        $diM[2] = floor($daA[11] + .5*(($daA[7] < 0) ? $daA[7] : -$daA[7])); // Min - only half of the perp unit vector y is added
        $diM[3] = ceil($daA[6] + .5*(($daA[7] > 0) ? $daA[7] : -$daA[7])); // Max
      } else {
        $diM[2] = floor($daA[6] + .5*(($daA[7] < 0) ? $daA[7] : -$daA[7])); // Min
        $diM[3] = ceil($daA[11] + .5*(($daA[7] > 0) ? $daA[7] : -$daA[7])); // Max
      }
      $diM[2] = ($diM[2] < 0) ? 0 : (($diM[2] > $iImHeight - 1) ? $iImHeight : $diM[2]);
      $diM[3] = ($diM[3] > $iImHeight - 1) ? $iImHeight - 1 : (($diM[3] < 0) ? -1 : $diM[3]);
      // Get the overall min and max
      $iPxMinY = ($diM[0] < $diM[2]) ? $diM[0] : $diM[2];
      $iPxMaxY = ($diM[1] > $diM[3]) ? $diM[1] : $diM[3];
      // Get a position of the corner of the each line segment P + .5*PerpV where Perp = (y, -x)
      $daLC = [$daA[0] + .5*$daA[3], $daA[1] - .5*$daA[2], $daA[5] + .5*$daA[8], $daA[6] - .5*$daA[7]];

      // This is for graphing veertical asymtotes
      if ($iPxMinY == 0 && $iPxMaxY == $iImHeight - 1) {
        for ($iPxY = $iPxMinY; $iPxY <= $iPxMaxY; $iPxY += 12) {
          $qPixel = ImageColorExactAlpha($this->mqImage, 0, 0, 0, 96);
          ImageSetPixel($this->mqImage, $iPxX, $iPxY, $qPixel);
          ImageSetPixel($this->mqImage, $iPxX, $iPxY + 1, $qPixel);
          ImageSetPixel($this->mqImage, $iPxX, $iPxY + 2, $qPixel);
          ImageSetPixel($this->mqImage, $iPxX, $iPxY + 3, $qPixel);
        }
      } else {
        // Run from Min Y to Max Y to color the pixels of the current column
        for ($iPxY = $iPxMinY; $iPxY <= $iPxMaxY; ++$iPxY) {
          $dWeight = 0.0;
          for ($dOX = .1; $dOX < 1.0; $dOX += .2) { // .1, .3, .5, .7, .9
            // The x-coordinate in the line segment space. Subtract off the line segment corner point
            $daLX = [$iPxX + $dOX - $daLC[0], $iPxX + $dOX - $daLC[2]];
            for ($dOY = .1; $dOY < 1.0; $dOY += .2) {
              $daLY = [$iPxY + $dOY - $daLC[1], $iPxY + $dOY - $daLC[3]];
              // Get the projections of the line segment point onto the vectors and perps
              $dProj = $daLX[0]*$daA[2] + $daLY[0]*$daA[3];
              $dPerp = $daLY[0]*$daA[2] - $daLX[0]*$daA[3];
              if ($dProj >= 0.0 && $dProj <= $daA[4] && $dPerp >= 0.0 && $dPerp <= 1.0) {
                $dWeight += .04;
              } else {
                $dProj = $daLX[1]*$daA[7] + $daLY[1]*$daA[8];
                $dPerp = $daLY[1]*$daA[7] - $daLX[1]*$daA[8];
                if ($dProj >= 0.0 && $dProj <= $daA[9] && $dPerp >= 0.0 && $dPerp <= 1.0) {
                  $dWeight += .04;
                }
              }
            }
          }
          if ($dWeight > 0.0) {
            $qPixel = ImageColorExactAlpha($this->mqImage, $this->mqaGraphColor['R'], $this->mqaGraphColor['G'], $this->mqaGraphColor['B'],
              (127 - 127*pow($dWeight,1.5)));
            ImageSetPixel($this->mqImage, $iPxX, $iPxY, $qPixel);
		  }
        }
      }
      // Update the pixels, vector, and min and max y
      $daA[0] = $daA[5];
      $daA[1] = $daA[6];
      $daA[2] = $daA[7];
      $daA[3] = $daA[8];
      $daA[4] = $daA[9];
      $daA[5] = $daA[10];
      $daA[6] = $daA[11];
      $diM[0] = $diM[2];
      $diM[1] = $diM[3];
    }
  }
}

$qGraph = new CGraphYEqFX(800, 800, -2*M_PI, -2*M_PI, 2*M_PI, 2*M_PI);
$qGraph->DrawGridLines(M_PI/8, M_PI/8);
$qGraph->GraphFunction('tan');


?>
 

Output

 
 

© 2007–2024 XoaX.net LLC. All rights reserved.