2016.10.14. gamma cygni 20160815data reprocessed z final

The gamma Cygni region, reprocessed on 2016-10-14

I wasn’t satisfied with the outcome of my star handling program so I rewrote it. The goal was to obtain an image that is as technically accurate as possible, while contains less artifacts and it is more appealing than the original.

I recorded with the Canon 1100D mod 21×150 sec, ISO 1600, with the lens Canon EF 200mm f2.8L II USM dialed to F/4, mounted on the HEQ5. The main feature is the red glow of the ionized hydrogen region around γ Cygni (the brightest star in the frame) with dramatic gaps caused by clouds of dark nebulae. The Crescent Nebula is also there. The object is also known as NGC 6888, Caldwell 27, Sharpless 105, and is a nebula blown by the Wolf-Rayet star WR 136 (HD 192163) expected to go supernova in the next 100k years.

 

The program does the following: 1) removes panda eyes from around bright stars, 2) bright areas (bright stars and their halos) are replaced with black, 3) blacks get healed, 4) fainter, star like areas are replaced with black, 5) blacks get healed, 6) the original and the output-5 is mixed with a lighter than filter. Log and output files are written along the way as shown in the source code. To get the star-reduce picture, the original and the output-5 images need to be combined in a photo editing software using various filters and layering.

As I usually do, I publish the samples and the steps I took to get the final picture.

raw

raw

stack

stack

colors

colors

dropped all stars, both luminous and faint

dropped all stars, both luminous and faint

recombined

recombined

 

 
Left: Before star handling
Right: After star handling
 
 
Left: crop1: before
Right: crop1: after
 
 
Left: crop2: before
Right: crop2: after
 

 

 
Left: crop3: before
Right: crop3: after
 
 
Left: crop4: before
Right: crop4: after
 
The gamma Cygni region, reprocessed on 2016-10-14

The gamma Cygni region, reprocessed on 2016-10-14

The starheal code I wrote and used

<?php



class Reporter {
    function report($s){
       $s = $s.'<br />';
       $s = str_replace("<br /><br />", "<br />", $s);
       $s = date('Y.m.d. H:i:s ').$s;
       echo $s;
       $s = str_replace("<br />", "\r\n", $s)."\r\n";
       $s = str_replace("\r\n\r\n", "\r\n", $s);
       file_put_contents('log.txt', $s, FILE_APPEND | LOCK_EX);
    }
}

//============================================================================

class ImageOfStarheal {     
    public $image = false;
    public $image_sx = -1;
    public $image_sy = -1;
    public $pixel_buffer = array();
    public $getpixel_buffer = array();
    
    public $reporter = false;
    public $last_saved_to_filename = '';
    
    public $getpixel_buffersize = 0;    
    
    function __construct(){
       $this->reporter = new Reporter;       
    }
    function report($s){
       $this->reporter->report($s);
    }



    function int2rgb($i){
       if (is_array($i)){
          return $i;
       }
       $r = $i >> 16;
       $g = ($i >> 8) %256;
       $b = $i % 256;
       return  array($r, $g, $b);
    }
    
    function rgb2int($r, $g=false, $b=false){
       if (is_array($r)){
         $b = $r[2];
         $g = $r[1];
         $r = $r[0];
       }
       return $r * 256*256 + $g*256 + $b;
    }
    //file operations    
    function load_image_from_file($filename){ 
       $this->image = imagecreatefromstring(file_get_contents($filename)); 
       $this->image_sx = imagesx($this->image);
       $this->image_sy = imagesy($this->image);
    }
    
    function save_image_to_file($filename, $quality = 100){
       $this->flushpixels();
       imagejpeg($this->image, $filename, $quality);
       $this->last_saved_to_filename = $filename;
    }
    function last_saved_image(){
       return $this->last_saved_to_filename;
    }
    
    //pixel operations
    function setpixel($x, $y, $p){
       return $this->putpixel($x, $y, $p);
    }    
    function putpixel($x, $y, $p){
       if ($x >= $this->image_sx){ return 0; };
       if ($y >= $this->image_sy){ return 0; };
       if ($x < 0 ) { return 0; };
       if ($y < 0 ) { return 0; };
       imagesetpixel($this->image, $x, $y, $p);
       $this->getpixel_buffer_set($x, $y, $p);
       return 1;
    }
    function getpixel_buffer_set($x, $y, $p){
       if ($this->getpixel_buffersize == 0){
          return ;
       }
       foreach ($this->getpixel_buffer as &$get_pix){
          if (($get_pix[0] == $x) AND ($get_pix[1] == $y)){
              $get_pix = array($x, $y, $p);
              return ;
          }
       }
       if (count($this->getpixel_buffer) > $this->getpixel_buffersize){
          $this->getpixel_buffer = array_slice($this->getpixel_buffer, round($this->getpixel_buffersize / 10), count($this->getpixel_buffer));
       }
       $this->getpixel_buffer[] = array($x, $y, $p);
    }
    function getpixel($x, $y, $default = 0){
       if ($x >= $this->image_sx){ return $default; };
       if ($y >= $this->image_sy){ return $default; };
       if ($x < 0 ) { return $default; };
       if ($y < 0 ) { return $default; };
       
       if ($this->getpixel_buffersize!=0){
          for ($q=0; $q<count($this->getpixel_buffer); $q++){
             if (($this->getpixel_buffer[$q][0] == $x) AND ($this->getpixel_buffer[$q][1] == $y)){
                 return $this->getpixel_buffer[$q][2];
             }
          }
       };          
       $pix = imagecolorat($this->image, $x, $y);
       $this->getpixel_buffer_set($x, $y, $pix);
       return $pix;
    }
    function pushpixel($x, $y, $p){
       //check if there is something to overwrite
       foreach ($this->pixel_buffer as &$pix){
          if (($pix[0] == $x) AND ($pix[1] == $y)){
             $pix = array($x, $y, $p);
             return 1;
          }
       }
       $this->pixel_buffer[] = array($x, $y, $p);
       
       if (count($this->pixel_buffer) > 5000){
          $this->flushpixels();
       }
    }
    function flushpixels(){
       foreach ($this->pixel_buffer as $pix){
          $this->putpixel($pix[0], $pix[1], $pix[2]);
       }
       $this->pixel_buffer = array();
    }
    function getrgbpixel($x, $y){
       return $this->int2rgb($this->getpixel($x, $y));
    }
    
    function luminance($x_or_pix, $y=-1){
       if ($y === -1){
          if (!is_array($x_or_pix)){
             $x_or_pix = $this->int2rgb($x_or_pix);
          }
       }else{
          $x_or_pix = $this->getrgbpixel($x_or_pix, $y);
       }
       return floor(($x_or_pix[0] + $x_or_pix[1] + $x_or_pix[1] + $x_or_pix[2]) / 4);
    }
    
    function pushpoint($p, $col){
       $this->pushpixel($p[0], $p[1], $col);
    }
    function filled_circle($point, $radius, $col){
       // $col = 0x00FF00;
       if ($radius > 4){
          imagefilledellipse($this->image, $point[0], $point[1], $radius, $radius, $col);
          return ;
       }
       if ($radius > 3){
          imagefilledellipse($this->image, $point[0]+1, $point[1]+0, $radius, $radius, $col);
          imagefilledellipse($this->image, $point[0]-1, $point[1]+0, $radius, $radius, $col);
          imagefilledellipse($this->image, $point[0]+0, $point[1]-1, $radius, $radius, $col);
          imagefilledellipse($this->image, $point[0]+0, $point[1]+1, $radius, $radius, $col);
          return ;
       }
       if ($radius == 3){
          imagefilledrectangle($this->image, $point[0]-$radius, $point[1]-$radius+2, $point[0]+$radius, $point[1]+$radius-2, $col);
          imagefilledrectangle($this->image, $point[0]-$radius+2, $point[1]-$radius, $point[0]+$radius-2, $point[1]+$radius, $col);
          imagefilledellipse($this->image, $point[0], $point[1], $radius, $radius, $col);
       }
       if ($radius == 2){
          imagefilledrectangle($this->image, $point[0]-$radius, $point[1]-$radius+1, $point[0]+$radius, $point[1]+$radius-1, $col);
          imageline($this->image, $point[0]-$radius+1, $point[1]-$radius, $point[0]+$radius-1, $point[1]-$radius, $col);
          imageline($this->image, $point[0]-$radius+1, $point[1]+$radius, $point[0]+$radius-1, $point[1]+$radius, $col);
       }
       if ($radius == 1){
          imageline($this->image, $point[0], $point[1]-1, $point[0], $point[1]+1, $col);
          imageline($this->image, $point[0]-1, $point[1], $point[0]+1, $point[1], $col);
       }
       if ($radius == 0){
          $this->pushpoint($point, $col);
       }
    }
    //geometry
    function distance($p1, $p2){
       $dx = $p1[0]-$p2[0];
       $dy = $p1[1]-$p2[1];
       return sqrt($dx*$dx + $dy*$dy);
    }
    function area($topleft, $bottomright){
       return ($bottomright[0] - $topleft[0])*($bottomright[1]-$topleft[1]); 
    }
    function pointup($p){
       $p[1] = $p[1] - 1; 
       return $p;
    }
    function pointdown($p){
       $p[1] = $p[1] + 1; 
       return $p;
    }
    function pointleft($p){
       $p[0] = $p[0] - 1; 
       return $p;
    }
    function pointright($p){
       $p[0] = $p[0] + 1; 
       return $p;
    }
    
    //filter
    function lighterthan($filename = "input.jpg"){
       $i2 = imagecreatefromstring(file_get_contents($filename));
       for ($x = 0; $x<imagesx($i2); $x++){
          for ($y = 0; $y<imagesy($i2); $y++){
             $p2 = imagecolorat($i2, $x, $y);
             $p1 = $this->getpixel($x, $y);
             if ($this->luminance($p2) > $this->luminance($p1)){
                $this->putpixel($x, $y, $p2);
             }
          };
       }
       imagedestroy($i2);
    }   

    function number_with_digits($n, $d = 3){
       $n = ''.$n;
       while (strlen($n) < $d){
          $n = '0'.$n;
       }
       return $n;
    }
};

//============================================================================

class Starheal extends ImageOfStarheal {    
    private $context_radius_of_healing = 10;
    private $kill_above_this_luminosity = 170;
    private $kill_radius = 2;
    private $time_limit = 32;
    
    private $start_time = 0;
    private $last_file_check = 0;
    
    private $nullpix = 0;
    private $aroundkill_above_this_luminosity = 220;
    
    private $area_counter = 0;
    private $stage_counter = 0;
    private $heal_counter = 1;
    private $number_of_pixels_to_heal = 0;
    
    private $healing_inited = false;
    
    private $save_a_stage_at_every = 25000;
    private $stages_count = 25;
    
    
    function __construct($t_limit = -1){
       parent::__construct();
       $this->start_time = date("U");
       if ($t_limit !== -1){
         $this->time_limit = $t_limit;
       }
       set_time_limit($this->time_limit);
       ini_set('memory_limit', '2048M'); //2 giga
       //if called from browser, chrome's tab optimization might f things up, so close the tab
       ignore_user_abort(true);       
              
       $this->last_file_check = $this->start_time;
       @unlink('output_8_premature.jpg');

       $this->report('======================= START ======================');       
       $this->area_counter = 0;
       $this->stage_counter = 0;
       $this->number_of_pixels_to_heal = 0;       
    }
    

    //shutdown
    function has_time($sec = -1, $now = -1){
       if ($now === -1){
          $now = date("U");
       };          
       if ($sec === -1){
          $sec = 3;
       }
       $been_running_for = $now - $this->start_time;
       return ($been_running_for + $sec < $this->time_limit);
    }
    
    function check_the_time($message=""){
       $now = date("U");
       if ($now - $this->last_file_check > 10){
          if (!file_exists("run.txt")){
             if ($message!=""){
                $message = ", ".$message;
             }
             $this->premature_death("run.txt missing".$message);
          }
          $this->last_file_check = $now;
       }           
       if (!$this->has_time(3, $now)){
           if ($message!=""){
                $message = ", ".$message;
           }
           $this->premature_death("running out of time".$message);       
       }
    } 
    function premature_death($message = ""){
       $this->save_image_to_file('output_8_premature.jpg');
       if ($message!=""){
          $this->report("message: ".$message);
       };          
       die();
    }
    
    //star operations
    function kill_points_with_radius($points_, $radius = -1){
       $this->report('kill points: start sorting (count='.count($points_).')');
       usort($points_, function ($a, $b){ 
          if ($a[0] == $b[0]) {
             if ($a[1] == $b[1]){ 
                return 0;
             };                
             return ($a[1] < $b[1]) ? -1 : 1;       
          }
          return ($a[0] < $b[0]) ? -1 : 1;       
       });
       $this->report('kill points: done sorting (count='.count($points_).')');
       
       $prev = array(-1, -1);
       
    
       $this->flushpixels();
       if ($radius == -1){
          $radius = $this->kill_radius;
       }
       $last_one = array(-3, -3);
       
       foreach ($points_ as $point){
          if (!(($point[0] == $last_one[0]) AND ($point[1] == $last_one[1]))){
             $this->filled_circle($point, $radius, $this->nullpix);
             $last_one = $point;
          };              
       };          
    }
    
    function is_the_pixel_color_starlike($x, $y){
       $pix = $this->getpixel($x, $y, 0);
       $rgb = $this->int2rgb($pix);
       $max = max($rgb);
       $min = min($rgb);
       $luminance = $this->luminance($pix);
       if ($luminance > $this->kill_above_this_luminosity){
         if ($max - $min > 30){
            //this pixel has a color, should be kept
            return 0;
         }else{
            if ($luminance > $this->aroundkill_above_this_luminosity){
               return 1;
            }else{
               return 1;
            };                
         }             
       }else{
         return 0;
       }
    }

    function kill_the_panda_eyes__(){
      /*
       $above_ = $this->aroundkill_above_this_luminosity;
       //$above_ = 200;
       $below_ = 50;
       $margin = 1;
       $out =0x00FF00;  
       $map = array();
       
       for ($x = $margin; $x < $this->image_sx-$margin; $x++){
          for ($y = $margin; $y < $this->image_sy-$margin; $y++){
             $pix = $this->getpixel($x, $y, 0);
             $luminance = $this->luminance($pix);
             if ($luminance > $above_){
                 //go radial
                 $old_coord_x = -1;
                 $old_coord_y = -1;
                 for ($angle = 0; $angle < M_PI*2; $angle = $angle + 0.1){
                   $line = array();
                   for ($radius = 1; $radius < 5; $radius++){
                       $coord_x = round($x + $radius*cos($angle));
                       $coord_y = round($y + $radius*sin($angle));
                       if (! (($coord_x == $old_coord_x) AND ($coord_y == $old_coord_y))){
                          if (! (($coord_x==$x)AND($coord_y == $y)) ){
                             $old_coord_x = $coord_x;
                             $old_coord_y = $coord_y;
                             $p = $this->getpixel($coord_x, $coord_y);
                             if ($p!=$out){
                                $line[] = array($coord_x, $coord_y, $this->luminance($p));
                             };                                
                          }
                       }
                   };                       
                   if (count($line) > 3){                          
                      $prev = $line[0][2];
                      $drop = 0;
                      $rize = 0;
                      for ($q=1; $q<count($line); $q++){
                         if ($prev - $line[$q][2] > 180){
                            $drop++;
                         }
                         if ($line[$q][2] - $prev > 100){
                            $rize++;
                         }
                         $prev = $line[$q][2];
                      }
                      if (($rize > 0) AND ($drop > 0) ){
                         $map[] = array($x, $y);
                      };
                   }
                 }
            }
        }
       }
       $this->kill_points_with_radius($map, 2);
       */
    }
    function kill_the_panda_eyes(){
       //this is a wrapper method
       $this->report("starting panda eye removal");
       $this->kill_the_panda_eyes_();
       $this->report("done panda eye removal");
    }
    function kill_the_panda_eyes_($topleft = -1, $bottomright = -1){
       $this->topleft_bottomright($topleft, $bottomright);
       $a = $this->area($topleft, $bottomright);
       if ($a > 300*300){
          $wi = floor(($bottomright[0] - $topleft[0]) /2);
          $hi = floor(($bottomright[1] - $topleft[1]) /2);
          
          //top left
          $this->kill_the_panda_eyes_(
            array($topleft[0]    + 0*$wi, $topleft[1]     + 0*$hi), 
            array($bottomright[0]- 1*$wi, $bottomright[1] - 1*$hi)
          );          

          //top right
          $this->kill_the_panda_eyes_(
            array($topleft[0]    + 1*$wi, $topleft[1]     + 0*$hi), 
            array($bottomright[0]- 0*$wi, $bottomright[1] - 1*$hi)
          );      

          //bottom left
          $this->kill_the_panda_eyes_(
            array($topleft[0]    + 0*$wi, $topleft[1]     + 1*$hi), 
            array($bottomright[0]- 1*$wi, $bottomright[1] - 0*$hi)
          );          

          //bottom right
          $this->kill_the_panda_eyes_(
            array($topleft[0]    + 1*$wi, $topleft[1]     + 1*$hi), 
            array($bottomright[0]- 0*$wi, $bottomright[1] - 0*$hi)
          );          
          
          return ;          
       }
       $this->report("starting to panda-eye an area (".$topleft[0].', '.$topleft[1].'), ('.$bottomright[0].', '.$bottomright[1].'), area = '.$a);
       
       $above_ = $this->aroundkill_above_this_luminosity;
       //$above_ = 200;
       $below_ = 90;
       $out =0x00FF00;  
       $map = array();
       
       for ($x = $topleft[0]; $x <= $bottomright[0]; $x++){
          for ($y = $topleft[1]; $y <= $bottomright[1]; $y++){
             $pix = $this->getpixel($x, $y, $out);
             $luminance = $this->luminance($pix);
             if ($luminance > $above_){
               $context = 2;
               for ($cx = -$context; $cx <= $context; $cx++){
                  for ($cy = -$context; $cy <= $context; $cy++){
                    $cpix = $this->getpixel($x+$cx, $y+$cy, $out);
                    if ($cpix!=$out){                       
                       $cluminance = $this->luminance($cpix);
                       if ($cluminance < $below_){
                          $map[] = array($x+$cx, $y+$cy);
                       };                           
                    };
                  };
               }                     
             }
          }
       }    
       $this->kill_points_with_radius($map, 1);
       $this->report("done panda-eying an area (".$topleft[0].', '.$topleft[1].'), ('.$bottomright[0].', '.$bottomright[1].')');
    }
    function kill_luminous_stars(){
       $this->report("start killing off the highlights ie luminous stars");
       $this->flushpixels();
       $star_map = array();
       for ($x = 0; $x < $this->image_sx; $x++){
          for ($y = 0; $y < $this->image_sy; $y++){
             $s = $this->is_the_pixel_color_starlike($x, $y);
             if ($s > 0){
                $star_map[] = array($x, $y);
             }
             /* if ($s > 1){
                $er = $s-1;
                for ($r1 = $er; $r1 <= $er; $r1++)
                   for ($r2 = $er; $r2 <= $er; $r2++)
                      $star_map[] = array($x+$r1, $y+$r2);
             } */
          }
       };       
       $this->report("done mapping highlights ie luminous stars");
       $this->kill_points_with_radius($star_map, 2);
       $this->report("done killing off the highlights ie luminous stars");
   }
   function kill_faint_stars(){
       $this->report("start killing faint stars");
       //assuming that the luminous stars have already been killed
       $lumidelta = 25;
       $max_coldelta = 45;
       $radius = 3;
       $kill = array();
       $divider = 10;
       $progress = round($this->image_sx / $divider);
       $ko = 0;
       for ($x =0; $x < $this->image_sx; $x++){
           if (($x+1) % $progress == 0){
               $ko++;
               $this->report("no_smallerstars_".$this->number_with_digits($ko)."_of_".$this->number_with_digits($divider).".txt");              
           }
           for ($y = 0; $y < $this->image_sy; $y++){
              $p = $this->getpixel($x, $y);
              if ($p > 0){
                $rgb = $this->int2rgb($p);
                $average = floor(($rgb[0] + $rgb[1]*2 + $rgb[2])/4);
                $coldelta = 0;
                for ($j =0; $j<=2; $j++){
                   $coldelta = max($coldelta, abs($rgb[$j] - $average));
                }
                //based on hue, this could be a star, check its context
                if ($coldelta < $max_coldelta){             
                   $context = array();
                   $context_is_lighter = false;
                   for ($tx = -$radius; $tx <= $radius; $tx++){
                      for ($ty = -$radius; $ty <= $radius; $ty++){
                         if ((abs($tx)==$radius)AND(abs($ty)==$radius)){
                            //round the corners
                         }else{
                            $k = $this->getpixel($x+$tx, $y+$ty);
                            $k_rgb = $this->int2rgb($k);
                            $k_average = floor(($k_rgb[0] + $k_rgb[1]*2 + $k_rgb[2])/4);
                            if ($k_average > $average){
                               $context_is_lighter = true;
                            }
                            $context[] = $k;
                         }                  
                      }
                   }
                   $fainter = 0;
                   $black = 0;
                   if (!$context_is_lighter){
                       $context_looks_good = 0;                 
                       foreach ($context as $c) if (($c!=0)OR(true)) {
                           if ($c ==0 ){
                              $black++;
                           } 
                           $c_rgb = $this->int2rgb($c);
                           $c_average = floor(($c_rgb[0] + $c_rgb[1]*2 + $c_rgb[2])/4);
                           $c_coldelta = 0;
                           for ($j =0; $j<=2; $j++){
                              $c_coldelta = max($c_coldelta, abs($c_rgb[$j] - $c_average));
                           }                        
                           $is_fainter = ($c_average < $average-$lumidelta);
                           if ($is_fainter){
                              $fainter++;
                           }
                           if (((abs($c_coldelta - $coldelta) < 15) AND (abs($c_average - $average) < 5)) OR ($is_fainter)){
                              $context_looks_good++;
                           }
                       }                     
                       if ((abs(count($context) - $context_looks_good) < max(2, count($context) / 4)) AND ($fainter > 0) AND ($black < count($context) / 3)){
                           $kill[] = array($x, $y);
                       }
                   };
                }//pixel is grey enough
            }//pixel not black    
         }
      }
      $this->report("killing faint stars - mapping done");
      $this->kill_points_with_radius($kill, $radius);   
      $this->report("done killing faint stars");
    }
       
    //healer operations
    function add_pixel_to_heal_context_if_fits($p1, $p2, &$context){
        $d = $this->distance($p1, $p2);
        if ($d < $this->context_radius_of_healing){
            $pix = $this->getpixel($p2[0], $p2[1], $this->nullpix);
            if ($pix > $this->nullpix){
                $context[] = array($pix, $d);
            }
        }
    }
    function heal_a_pixel($x, $y, $min_context = 8){
       if ($x >= $this->image_sx){ return 0; };
       if ($y >= $this->image_sy){ return 0; };
       if ($x < 0 ) { return 0; };
       if ($y < 0 ) { return 0; };
       
       if (($x === 0) OR ($x === $this->image_sx-1)){
          $min_context = $min_context - 3;
       }
       if (($y === 0) OR ($y === $this->image_sy-1)){
          $min_context = $min_context - 3;
       }
       $min_context = max($min_context, 3);
       
       $context = array();//pixel, distance
       
       $p = $this->getpixel($x, $y);
       
       if ($p > $this->nullpix){
          return 0;
       }
       $p1 = array($x, $y);
       
       // go radially to find the first nonzero pixel      
       for ($radius = 1; $radius <= $this->context_radius_of_healing; $radius++){
          $angle_adder = 0.05;
          $latest_coords = array(-1, -1);
          if (count($context) < 10){
              for ($angle = 0; $angle < 2*M_PI; $angle = $angle + $angle_adder){
                    $cx = round($radius * cos($angle));
                    $cy = round($radius * sin($angle));
                    $coords = array($x+$cx, $y+$cy);
                    if (!(($cx==0)AND($cy==0))){
                       if (($coords[0]!=$latest_coords[0]) AND ($latest_coords[1]!=$coords[1])){
                          $latest_coords = $coords;
                          $this->add_pixel_to_heal_context_if_fits($p1, $coords, $context);
                       };                      
                    }
                  
              };                    
          };
       };
       //sort by distance
       usort($context, function ($a, $b){
           if ($a[1] == $b[1]) {
               return 0;
           }
           return ($a[1] < $b[1]) ? -1 : 1;
       });
       $context = array_slice($context, 0, 8);
       
       if (count($context) < $min_context){
          return 0;
       }
       
       if (count($context) < 1){
          return 0;
       }
              
       $dist_min = $context[0][1];
       
       $r = array();
       $g = array();
       $b = array();
       
       foreach ($context as $c){
          $c[1] = $c[1] - $dist_min +1;
          while ($c[1] <= $this->context_radius_of_healing){
             $rgb = $this->int2rgb($c[0]);
             $r[] = $rgb[0];
             $g[] = $rgb[1];
             $b[] = $rgb[2];
             $c[1]++;
          }          
       }
       
       $newpix = array(round(array_sum($r) / count($r)), round(array_sum($g) / count($g)), round(array_sum($b) / count($b)));
       
       $this->pushpixel($x, $y, $this->rgb2int($newpix));
       return 1;
    }
    function topleft_bottomright(&$topleft, &$bottomright){
       if ($topleft === false){
          $topleft = array(0,0);
       }
       if ($topleft === -1){
          $topleft = array(0,0);
       }
       if ($bottomright === false){
          $bottomright = array($this->image_sx -1, $this->image_sy -1);
       };
       if ($bottomright === -1){
          $bottomright = array($this->image_sx -1, $this->image_sy -1);
       }
    }
    function count_of_pixels_to_heal($topleft = -1, $bottomright = -1){
       $this->topleft_bottomright($topleft, $bottomright);
       $t = 0;
       $this->flushpixels();
       for ($x = $topleft[0]; $x<=$bottomright[0]; $x++){
          for ($y =$topleft[1]; $y<=$bottomright[1]; $y++){
             if ($this->getpixel($x, $y) == $this->nullpix){
                $t++;
             }
          }
       }
       return $t;
    }
    function heal_an_area($topleft = -1, $bottomright = -1){
       $this->init_healing();
       $this->topleft_bottomright($topleft, $bottomright);
       $a = $this->area($topleft, $bottomright);
       if ($a > 300*300){
          $wi = floor(($bottomright[0] - $topleft[0]) /2);
          $hi = floor(($bottomright[1] - $topleft[1]) /2);

          //top left
          $this->heal_an_area(
            array($topleft[0]    + 0*$wi, $topleft[1]     + 0*$hi), 
            array($bottomright[0]- 1*$wi, $bottomright[1] - 1*$hi)
          );          

          //top right
          $this->heal_an_area(
            array($topleft[0]    + 1*$wi, $topleft[1]     + 0*$hi), 
            array($bottomright[0]- 0*$wi, $bottomright[1] - 1*$hi)
          );      

          //bottom left
          $this->heal_an_area(
            array($topleft[0]    + 0*$wi, $topleft[1]     + 1*$hi), 
            array($bottomright[0]- 1*$wi, $bottomright[1] - 0*$hi)
          );          

          //bottom right
          $this->heal_an_area(
            array($topleft[0]    + 1*$wi, $topleft[1]     + 1*$hi), 
            array($bottomright[0]- 0*$wi, $bottomright[1] - 0*$hi)
          );          
          
          return ;          
       }
       $this->report("starting to heal area (".$topleft[0].', '.$topleft[1].'), ('.$bottomright[0].', '.$bottomright[1].'), a='.$a);
       $list_of_x = range($topleft[0], $bottomright[0]);
       $list_of_y = range($topleft[1], $bottomright[1]);
       $points = array();
       foreach ($list_of_x as $x){
          foreach ($list_of_y as $y){
             $points[] = array($x, $y); 
          }
       }
       shuffle($points);
       
       $to_heal = $this->count_of_pixels_to_heal($topleft, $bottomright);
       
       $nodelta = 0;
       while ($to_heal > 0){
           $before = $to_heal;
           foreach ($points as $p){
             $v = $this->heal_a_pixel($p[0], $p[1]); 
             $to_heal = $to_heal - $v;
             $this->number_of_pixels_to_heal = $this->number_of_pixels_to_heal-$v;
             if ($v == 1){
                if ($this->number_of_pixels_to_heal % $this->save_a_stage_at_every == 0){
                   $this->save_image_to_file('output_'.$this->heal_counter.'_stage_healed_'.$this->number_with_digits($this->stage_counter).'.jpg');
                   $this->stage_counter++;
                }
             }
             $this->check_the_time("healing_an_area");
           }
           $this->flushpixels();
           $to_heal = $this->count_of_pixels_to_heal($topleft, $bottomright);
           $delta = $before - $to_heal;
           if ($delta == 0){
              $nodelta++;
           }else{
              $nodelta = 0;
           }
           if ($nodelta > 2){            
              $to_heal = 0;
              $this->report("exiting healing round");
           }
       };           
       $this->flushpixels();
       $this->report("done healing area (".$topleft[0].', '.$topleft[1].'), ('.$bottomright[0].', '.$bottomright[1].')');
       $this->report("count of pixels to heal = ".$this->number_of_pixels_to_heal);
       // $this->save_image_to_file('output_1_area_healed_'.$this->number_with_digits($this->area_counter).'.jpg');
       $this->area_counter++;
    }
  
    function init_healing($force = false){
       if (!$force){
         if ($this->healing_inited){
            return ;
         }
       }else{
         $this->heal_counter++; 
       };           
       $this->healing_inited = true;
       $this->number_of_pixels_to_heal = $this->count_of_pixels_to_heal();
       $this->save_a_stage_at_every = round($this->number_of_pixels_to_heal / $this->stages_count);
       $this->report("count of pixels to heal = ".$this->number_of_pixels_to_heal);
    }
  
    function process_image($filename = "input.jpg"){
       $this->load_image_from_file($filename);
       $this->kill_the_panda_eyes();
       $this->save_image_to_file("input_1_no_panda.jpg");   
       $this->kill_luminous_stars();
       $this->save_image_to_file("input_2_no_luminous_stars.jpg");   
       $this->init_healing(true);
       $this->heal_an_area();  
       
       $this->save_image_to_file("output_z1_healed.jpg");    
       $this->kill_faint_stars();
       $this->save_image_to_file("output_z2_no_faint_stars.jpg");           
       $this->init_healing(true);
       $this->heal_an_area();  
       $this->save_image_to_file("output_z3_healed.jpg");    
       
       $this->lighterthan($filename);    
       $this->save_image_to_file("output_z4_lighterthan.jpg");       
    }
} 




?>

 

Usage example

<?php

   require_once("starheal.php");

   $h = new Starheal(86400);

   $h->process_image();
?>
<img src="input.jpg" id="i0" />
<img src="<?php echo $h->last_saved_image(); ?>?<?php echo mt_rand(); ?>" id="i1" style="display:none" />
<script>
setInterval(function (){
  var i1 = document.getElementById("i0");
  var i2 = document.getElementById("i1");
  if (i1.style.display==""){
     i1.style.display = "none";
     i2.style.display = "";
  }else{
     i2.style.display = "none";
     i1.style.display = "";
  }
}, 1000);
</script>
facebooktwittergoogle_plusredditpinterestlinkedinmail