--- D:/Dokumente und Einstellungen/Administrator/Desktop/canvasactions.inc	Tue May 19 20:03:30 2009
+++ D:/htdocs/mesport/sites/all/modules/contrib/imagecache_actions/canvasactions.inc	Tue Sep 01 01:40:13 2009
@@ -606,77 +606,340 @@
     return TRUE;
   }
   
+  // read settings
   $width = $image->info['width'];
   $height = $image->info['height'];
   $radius = $action['radius'];
   $independent_corners = $action['independent_corners_set']['independent_corners'];
   $radii = $action['independent_corners_set']['radii'];
-  $diameter = array();
-
-  if ($action['antialias']) {
-    $width = $width * 3;
-    $height = $height * 3;
-    $radius = $radius * 3;
-    foreach ($radii as $corner => $corner_radius) {
-      $radii[$corner] = $radii[$corner] * 3;
+  
+  $im = &$image->resource;
+  // prepare drawing on the alpha channel
+  imagesavealpha($im, TRUE);
+  imagealphablending($im, false);
+  
+  static $_cache = array();
+  
+  foreach ($radii as $key => $r) {
+    
+    if (!$independent_corners) {
+      // use the all-the-same radius setting
+      $r = $radius;
+    }
+    // key can be 'tl', 'tr', 'bl', 'br'.
+    $is_bottom = ($key{0}=='b');
+    $is_right = ($key{1}=='r');
+    
+    // circle to calculate alpha values
+    if (!isset($_cache[$r])) {
+      $_cache[$r] = new canvasactions_Circle($r);
+    }
+    $circle = $_cache[$r];
+    
+    // dx and dy are in "continuous coordinates",
+    // and mark the distance of the pixel middle to the image border.
+    for ($dx = 0.5; $dx < $r; ++$dx) {
+      
+      // ix and iy are in discrete pixel indices,
+      // counting from the top left
+      $ix = floor($is_right ? $width - $dx : $dx);
+        
+      list($yRange0, $yRange1) = $circle->criticalRange($r-$dx, array(0, 0));
+      // need to swap both
+      $dy0 = $r - $yRange1;
+      $dy1 = $r - $yRange0;
+      
+      // pixels that are clearly outside
+      for ($dy = 0.5; $dy < $dy0; ++$dy) {
+        
+        // pixel coordinates
+        $iy = floor($is_bottom ? $height - $dy : $dy);
+        
+        // color lookup at ($ix, $iy).
+        // Keys are 'red', 'green', 'blue', 'alpha'.
+        $color = imagecolorat($im, $ix, $iy);
+        $rgba = imagecolorsforindex($im, $color);
+        
+        // set the pixel as fully transparent
+        $color = imagecolorallocatealpha($im, $rgba['red'], $rgba['green'], $rgba['blue'], 127);
+        imagesetpixel($im, $ix, $iy, $color);
+      }
+      
+      // pixels that are partially inside
+      for ($dy = $dy0 + 0.5; $dy < $dy1; ++$dy) {
+        
+        // pixel coordinates
+        $iy = floor($is_bottom ? $height - $dy : $dy);
+        
+        // find opacity
+        $opacity = $circle->inside($r - $dx, $r - $dy);
+        
+        // color lookup at ($ix, $iy).
+        $color = imagecolorat($im, $ix, $iy);
+        $rgba = imagecolorsforindex($im, $color);
+        
+        if (isset($rgba['alpha'])) {
+          $alpha = 127 - round($opacity * (127 - $rgba['alpha']));
+        } else {
+          $alpha = 127 - round($opacity * 127);
+        }
+        // value should not be more than 127, and not less than 0.
+        $alpha = ($alpha > 127) ? 127 : (($alpha < 0) ? 0 : $alpha);
+        $color = imagecolorallocatealpha($im, $rgba['red'], $rgba['green'], $rgba['blue'], $alpha);
+        imagesetpixel($im, $ix, $iy, $color);
+      }
+      
+      // pixels that are inside don't need any work.
     }
   }
+  
+  return TRUE;
+}
+
 
-  foreach ($radii as $corner => $corner_radius) {
-    if(!$independent_corners){
-      // Use the unique maths for independant corners, 
-      // even if they are all the same.
-      $radii[$corner] = $radius;
-    }
-    $diameter[$corner] = $radii[$corner] * 2;
-  }
-  
-  // Create a mask with rounded corners
-  $mask = imagecreatetruecolor($width, $height);
-
-  // Is using the toolkit really worth the bother?
-  // Just doing it in an attempt to be consistent
-  $mask_image = (object)array(
-    'resource' => &$mask, 
-    'info' => array('width' => $width, 'height' => $height, 'extension' => 'png'), 
-    'toolkit' => $image->toolkit,
-    'resource' => &$mask,
-  );
-
-  // Start with a blank slate
-  $background = imagecolorallocatealpha($mask, 255, 255, 255, 127);
-  imagesavealpha($mask, TRUE);
-  imagealphablending($mask, false);
-  imagefilledrectangle($mask, 0, 0, $width, $height, $background);
-  // Place solid lumps on it
-  $foreground = imagecolorallocatealpha($mask, 0, 0, 0, 0);
-
-  // Place blobs in the corners
-  ImageFilledEllipse($mask, $radii['tl'], $radii['tl'], $diameter['tl'], $diameter['tl'], $foreground);
-  ImageFilledEllipse($mask, $width-$radii['tr'], $radii['tr'], $diameter['tr'], $diameter['tr'], $foreground);
-  ImageFilledEllipse($mask, $radii['bl'], $height-$radii['bl'], $diameter['bl'], $diameter['bl'], $foreground);
-  ImageFilledEllipse($mask, $width-$radii['br'], $height-$radii['br'], $diameter['br'], $diameter['br'], $foreground);
-  
-  // Block out the middle
-  ImageFilledRectangle($mask, $radii['tl'], 0, $width*0.5, $height*0.5, $foreground);
-  ImageFilledRectangle($mask, 0, $radii['tl'], $width*0.5, $height*0.5, $foreground);
-  ImageFilledRectangle($mask, $width*0.5, 0, $width-$radii['tr'], $height*0.5, $foreground);
-  ImageFilledRectangle($mask, $width*0.5, $radii['tr'], $width, $height*0.5, $foreground);
-  ImageFilledRectangle($mask, 0, $height*0.5, $width*0.5, $height-$radii['bl'], $foreground);
-  ImageFilledRectangle($mask, $radii['bl'], $height*0.5, $width*0.5, $height, $foreground);
-  ImageFilledRectangle($mask, $width*0.5, $height*0.5, $width, $height-$radii['br'], $foreground);
-  ImageFilledRectangle($mask, $width*0.5, $height*0.5, $width-$radii['br'], $height, $foreground);
-
-  if ($action['antialias']) {
-    // Use toolkit so scale down again. 
-    imageapi_image_scale($mask_image, $width/3, $height/3);
-    $mask = $mask_image->resource;
+/**
+ * Calculate the transparency value for a rounded corner pixel
+ * 
+ * @param $x
+ *   distance from pixel center to image border (left or right)
+ *   should be an integer + 0.5
+ *   
+ * @param $y
+ *   distance from pixel center to image border (top or bottom)
+ *   should be an integer + 0.5
+ *   
+ * @param $r
+ *   radius of the rounded corner
+ *   should be an integer
+ *   
+ * @return float
+ *   opacity value between 0 (fully transparent) and 1 (fully opaque).
+ */
+function _canvasactions_roundedcorners_pixel_opacity($x, $y, $r) {
+  
+  static $_cache = array();
+  if (!isset($_cache[$r])) {
+    $_cache[$r] = new canvasactions_Circle($r);
   }
-  // Now we have a mask. Merge to get a result
-  canvasactions_mask($image, $mask_image);
+  $circle = $_cache[$r];
+  return $circle->inside($r-$x, $r-$y);
+}
+
+
+
+/**
+ * Object to pre-calculate how much of a pixel area is within a circle.
+ * The math should have the same effect as infinite antialiasing
+ * (of course limited by floating point accuracy)
+ */
+class canvasactions_Circle {
   
-  return TRUE;
+  protected $_matrix = array();  // sparse matrix
+  protected $_r;  // radius
+  protected $_r_2; // square pre-calculated
+  
+  
+  /**
+   * Constructor.
+   * 
+   * @param $r int
+   *   the radius
+   */
+  function __construct($r) {
+    $r = (int)$r;
+    $this->_r = $r;
+    $r_2 = $r * $r;
+    $this->_r_2 = $r_2;
+    // calculate intersections
+    $sort[] = 0;
+    $intersections[] = array(0, $r, 0);
+    for($x=1; $x<$r; ++$x) {
+      $y = sqrt($r_2 - $x*$x);
+      // the angle..
+      $phi = asin(1.*$x/$r);
+      $sort[] = $x;
+      $intersections[] = array($x, $y, $phi);
+      // transposed intersection
+      $sort[] = $y;
+      $intersections[] = array($y, $x, 0.5 * pi() - $phi);
+    }
+    $sort[] = $r;
+    $intersections[] = array($r, 0, 0.5 * pi());
+    // sorting heaven
+    array_multisort($sort, $intersections);
+    // scan intervals between intersection points
+    $n = count($intersections);
+    list($x1, $y1, $phi1) = $intersections[0];
+    for($i=1; $i<$n; ++$i) {
+      list($x0, $y0, $phi0) = array($x1, $y1, $phi1);
+      list($x1, $y1, $phi1) = $intersections[$i];
+      if ($x0 > $y0) {
+        // only scan 1/8 circle
+        break;
+      }
+      if ($x0 == (int)$x0 && $x0*$x0 + $y1*$y1 == $r_2) {
+        // avoid the float/int trap!
+        // we clearly have two identical intersection points.
+        // we can skip this interval.
+        // use the more accurate int-valued $x0 for next interval
+        $x1 = $x0;
+        continue;
+      }
+      if ($y0 == (int)$y0 && $x1*$x1 + $y0*$y0 == $r_2) {
+        // same float/int trap
+        // use the more accurate int-valued $y0 for next interval
+        $y1 = $y0;
+        continue;
+      }
+      // calculate pixel coordinates
+      // we can always assume that $x0 < $x1, and $y0 > $y1, and $phi0 < $phi1
+      $ix = floor($x0);
+      $iy = floor($y1);
+      // calculate pixel area inside the circle
+      if ($x0 == (int)$x0) {
+        if ($x1 == (int)$x1) {
+          $area = 0.5 * ($y0 + $y1) - $iy;
+        } else {
+          // assume that $y1 == (int)$y1
+          $area = 0.5 * ($y0 - $y1) * ($x1 - $x0);
+        }
+      } else {
+        // assume that $y0 == (int)$y0
+        if ($x1 == (int)$x1) {
+          $area = 1 - 0.5 * ($y0 - $y1) * ($x1 - $x0);
+        } else {
+          // assume that $y1 == (int)$y1
+          // actually we should never get here,
+          // if we only scan 1/8 of the circle..
+          $area = 0.5 * ($x1 + $x0) - $ix;
+        }
+      }
+      
+      // add area of circular segment
+      $d_phi = $phi1 - $phi0;
+      $segment_area = 0.5 * $r_2 * ($d_phi - sin($d_phi));
+      $area += $segment_area;
+      
+      // store it (full symmetry!)
+      $this->_matrix[$ix][$iy] = $area;
+      $this->_matrix[$iy][$ix] = $area;
+    }
+    // sort the matrix
+    ksort($this->_matrix);
+    foreach ($this->_matrix as &$row) {
+      ksort($row);
+    }
+  }
+  
+  /**
+   * determine how much of the pixel is inside the circle
+   * 
+   * @param $x float (ideally int + 0.5)
+   *   x-distance of pixel mid-point to the circle mid-point
+   * 
+   * @param $y float (ideally int + 0.5)
+   *   y-distance of pixel mid-point to the circle mid-point
+   * 
+   * @return float
+   *   value from 0 to 1, where 1 is totally inside
+   */
+  function inside($x, $y) {
+    // symmetry
+    $x = ($x < 0) ? -$x : $x;
+    $y = ($y < 0) ? -$y : $y;
+    if ($x > $y) {
+      $swap = $y;
+      $y = $x;
+      $x = $swap;
+    }
+    // normalize
+    $ix = floor($x);
+    $x = $ix + 0.5;
+    $iy = floor($y);
+    $y = $iy + 0.5;
+    if (isset($this->_matrix[$ix][$iy])) {
+      // partially inside
+      return $this->_matrix[$ix][$iy];
+    } else if ($x*$x + $y*$y > $this->_r_2) {
+      // outside
+      return 0;
+    } else {
+      // inside
+      return 1;
+    }
+  }
+  
+  
+  /**
+   * determine how much of the pixel is outside the circle
+   * 
+   * @param $x float (ideally int + 0.5)
+   *   x-distance of pixel mid-point to the circle mid-point
+   * 
+   * @param $y float (ideally int + 0.5)
+   *   y-distance of pixel mid-point to the circle mid-point
+   * 
+   * @return float
+   *   value from 0 to 1, where 1 is totally outside
+   */
+  function outside($x, $y) {
+    return 1-$this->inside($x, $y);
+  }
+  
+  
+  /**
+   * determine which pixels in a row contain a part of the circle border
+   * 
+   * @param $x float (ideally int + 0.5)
+   *   x-distance of pixel mid-point to the circle mid-point
+   *   can be an y-distance instead,
+   *   but in this case the result will be an x-range
+   *
+   * @param $otherwise mixed
+   *   return value if $x is out of range.
+   * 
+   * @return array(int, int)
+   *   y-range of pixels
+   */
+  public function criticalRange($x, $otherwise = null) {
+    // symmetry
+    $x = ($x < 0) ? -$x : $x;
+    $ix = floor($x);
+    if (isset($this->_matrix[$ix])) {
+      $range = array_keys($this->_matrix[$ix]);
+      return array($range[0], end($range) + 1);
+    } else {
+      // out of range.
+      return $otherwise;
+    }
+  }
+  
+  
+  
+  /**
+   * determine which pixels in a row contain a part of the circle border
+   * 
+   * @param $x float (ideally int + 0.5)
+   *   x-distance of pixel mid-point to the circle mid-point
+   *   can be an y-distance instead,
+   *   but in this case the result will be an x-range
+   * 
+   * @return array(int, int)
+   *   y-range of pixels
+   */
+  public function criticalPixels($x) {
+    // symmetry
+    $x = ($x < 0) ? -$x : $x;
+    $ix = floor($x);
+    if (isset($this->_matrix[$ix])) {
+      return $this->_matrix[$ix];
+    } else {
+      // out of range.
+      return array();
+    }
+  }
 }
+
 
 /**
  * Given a mask image resource object in the $action, use the alpha values from
