Back in December 2015 my modded camera had the first Ha-enhanced light. As I keep most raws, I dag up those in order to test whether my processing abilities have evolved. One aspect is the healing algorithm I wrote to handle the dense star fields – ie: remove all highlights and their immediate context and fill the holes from the remaining image data. Canon 1100D mod, 20×110 sec, ISO 1600, Canon 200mm f/2.8 lens dialed to f/4.5, EQ3 mount, unguided. More than fairly dark sky of a place near Dangau Mare, Cluj county, Romania.
Finally, the process
The code I used to process this time
<!-- script to fill the blanks of input.jpg by extrapolating the context of the blank, saving the output as output*.jpg --> <div align="center"> <?php report("=============starting the script============="); $i = imagecreatefromstring(file_get_contents('input.jpg')); report("created the input image"); //settings $GLOBALS['minlumi'] = 35; $GLOBALS['maxlumi'] = 140; $GLOBALS['max_damage'] = 250; $GLOBALS['csillaglumi'] = 150; $GLOBALS["kill_highlight_radius"] = 2; //2 or 3 $stages_to_save = 25; set_time_limit(60000);//majdnem 24 ora :P ini_set('memory_limit', '2048M'); //2 giga, szoval legyszives ignore_user_abort(true); //======================================================================== 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); } //======================================================================== function numberaskilo($i){ while (strlen($i) < 3){ $i = "0".$i; } return $i; } //======================================================================== 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; } //======================================================================== function luminanceof($i_or_rgb){ if (!is_array($i_or_rgb)){ $i_or_rgb = int2rgb($i_or_rgb); } return floor(($i_or_rgb[0] + $i_or_rgb[1]*2 + $i_or_rgb[2]) / 4); } //======================================================================== function imagesetpixel_($i, $tx, $ty, $p){ if (($tx >= 0) && ($tx < imagesx($i)) && ($ty >= 0) && ($ty < imagesy($i))){ imagesetpixel($i, $tx, $ty, $p); }; } //======================================================================== function imagecolorat_($i, $tx, $ty){ if (($tx >= 0) && ($tx < imagesx($i)) && ($ty >= 0) && ($ty < imagesy($i))){ return imagecolorat($i, $tx, $ty); }; return 0; } //======================================================================== function maxcolororluminance($pix){ return luminanceof($pix); //// $pix = int2rgb($pix); $pix[] = floor(($pix[0] + $pix[1] + $pix[1] + $pix[2]) / 4); return max($pix); } //======================================================================== function heal_pixel_if_possible($i, $x, $y){ $xy = $x.'_'.$y; $pix = imagecolorat($i, $x, $y); if (maxcolororluminance($pix) >= $GLOBALS['minlumi']){ return false; }; $u = array(); for ($tx = -1; $tx < 2; $tx++){ for ($ty = -1; $ty < 2; $ty++){ if (($tx==0)&&($ty==0)){ // _self }else{ $p = imagecolorat_($i, $tx+$x, $ty+$y); $l = maxcolororluminance($p); if (($l < $GLOBALS['maxlumi']) && ($l >= $GLOBALS['minlumi'])){ $u[] = int2rgb($p); } }; } } if (count($u) >= $GLOBALS['mincontext']){ $uj = array(0,0,0); foreach ($u as $j){ for ($q=0; $q<3; $q++){ $uj[$q] = $uj[$q] + $j[$q]; }; } for ($q=0; $q<3; $q++){ $uj[$q] = floor($uj[$q] / count($u)); }; imagesetpixel($i, $x, $y, rgb2int($uj)); return true; } } //======================================================================== function handle_this_pixel(&$i, &$x, &$y, &$healed, &$save_stage_at_x_heals, &$st){ if (!file_exists("run.txt")){ imagejpeg($i, "output_8_premature.jpg", 95); die(); } if (heal_pixel_if_possible($i, $x, $y)){ $healed++; if ($healed % $save_stage_at_x_heals == 0){ imagejpeg($i, 'output_3_stage_'.numberaskilo($st).'.jpg', 95); $st++; } }; }; //======================================================================== function kill_starmap(&$i, &$star_map, $radius = -1){ if ($radius == -1){ $radius = $GLOBALS["kill_highlight_radius"]; }; foreach ($star_map as $star){ $min = -$radius; $max = $radius; for ($rx = $min; $rx <= $max; $rx++){ for ($ry = $min; $ry <= $max; $ry++){ if ( (($rx == $min) OR ($rx == $max)) AND (($ry == $min) OR ($ry == $max)) ){ //this is a corner, but lets make the hole a bit round }else{ imagesetpixel_($i, $star[0]+$rx, $star[1]+$ry, 0); } } } } } //======================================================================== function kill_off_the_highlights(&$i){ report("killing off the highlights"); $star_map = array(); for ($x = 0; $x < imagesx($i); $x++){ for ($y = 0; $y < imagesy($i); $y++){ $pix = imagecolorat($i, $x, $y); if (maxcolororluminance($pix) > $GLOBALS['csillaglumi']){ $star_map[] = array($x, $y); }; } } kill_starmap($i, $star_map); imagejpeg($i, "input_2_nohighlights.jpg", 100); report('done killing off the highlights'); }; //======================================================================== function kill_off_remaining_starlike_areas(&$i){ ////ha minden iranyba ugyanaz vagy kisebb, akkor jelolt $lumidelta = 25; $max_coldelta = 45; $radius = 3; $kill = array(); $oszto = 10; $progress = round(imagesx($i) / $oszto); $ko = 0; for ($x =0; $x < imagesx($i); $x++){ if (($x+1) % $progress == 0){ $ko++; file_put_contents("input_4_no_smallerstars_".numberaskilo($ko)."_of_".numberaskilo($oszto).".txt", 100); } if (!file_exists('run.txt')){ die("premature exit"); } for ($y = 0; $y < imagesy($i); $y++){ $p = imagecolorat($i, $x, $y); if ($p > 0){ $rgb = int2rgb($p); $atlag = floor(($rgb[0] + $rgb[1]*2 + $rgb[2])/4); $coldelta = 0; for ($j =0; $j<=2; $j++){ $coldelta = max($coldelta, abs($rgb[$j] - $atlag)); } //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 = imagecolorat_($i, $x+$tx, $y+$ty); $k_rgb = int2rgb($k); $k_atlag = floor(($k_rgb[0] + $k_rgb[1]*2 + $k_rgb[2])/4); if ($k_atlag > $atlag){ $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 = int2rgb($c); $c_atlag = 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_atlag)); } $is_fainter = ($c_atlag < $atlag-$lumidelta); if ($is_fainter){ $fainter++; } if (((abs($c_coldelta - $coldelta) < 15) AND (abs($c_atlag - $atlag) < 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 } } kill_starmap($i, $kill, $radius); imagejpeg($i, "input_6_no_smallerstars.jpg", 100); } //======================================================================== function try_loading_a_cache_if_exists(&$i){ $icv = 'input_9_carved.jpg'; //remove this cache, start from scratch if (file_exists($icv)){ unlink($icv); } if (!file_exists($icv)){ report('image input loaded, no cache<br />'); kill_off_the_highlights($i); kill_off_remaining_starlike_areas($i); report('saving the carved version<br />'); imagejpeg($i, $icv, 100); foreach (glob("*.jpg") as $filename){ echo '<img src="'.$filename.'?r='.mt_rand().'" />'; } //die(); }else{ report('image outpIt loaded from cache<br />'); imagedestroy($i); $i = imagecreatefromstring(file_get_contents($icv)); } } function filter_lighterthanoriginal(&$i){ //apply a "lighter than" filter, assuming the "to be replaced" areas were black $o = imagecreatefromstring(file_get_contents("input.jpg")); for ($x = 0; $x< imagesx($o); $x++){ for ($y =0; $y< imagesy($o); $y++){ $pixo = imagecolorat($o, $x, $y); $pixi = imagecolorat($i, $x, $y); if (luminanceof($pixi) > luminanceof($pixo)){ imagesetpixel($o, $x, $y, $pixi); } }; }; imagejpeg($o, 'output_z_final.jpg', 100); } //================================== main ================================== try_loading_a_cache_if_exists($i); //count the pixels to heal report('starting to count the pixels that need to be healed'); $pixels_to_heal = 0; $skip_y = array(); for ($y = imagesy($i)-1; $y > -1; $y--){ $th = 0; for ($x = imagesx($i)-1; $x > -1; $x--){ $pix = imagecolorat($i, $x, $y); if (maxcolororluminance($pix) < $GLOBALS['minlumi']){ $pixels_to_heal++; $th++; } } if ($th==0){ $skip_y[] = $y; report('nothing to heal in line '.$y.', adding to ignore list'); } } report('counted '.$pixels_to_heal.' pixels to heal, and save in '.$stages_to_save.' stages.<br />'); report(count($skip_y).' lines already being ignored'); $save_stage_at_x_heals = floor($pixels_to_heal / max($stages_to_save, 1))+1; //iterate and heal $healed = 0; $maxx = imagesx($i); $maxy = imagesy($i); //$maxx = 300; //$maxy = 300; //$iterations = 10; $st = 0; $lumi_delta = 255 - $GLOBALS["maxlumi"]; $healed_in_this_round = 0; $GLOBALS['maxlumi_orig'] = $GLOBALS["maxlumi"]; $skip_y_orig = $skip_y; $st2 = 0; $range_sx = array(); for ($sx = 0; $sx < imagesx($i); $sx = $sx + $GLOBALS['max_damage']){ $range_sx[] = $sx; }; shuffle($range_sx); $range_sy = array(); for ($sy = 0; $sy < imagesy($i); $sy = $sy + $GLOBALS['max_damage']){ $range_sy[] = $sy; }; shuffle($range_sy); //for random pairs of areas $pairs = array(); foreach ($range_sy as $sy){ foreach ($range_sx as $sx){ $pairs[] = array($sx, $sy); } }; shuffle($pairs); for ($main_iteration = 0; $main_iteration < 2; $main_iteration++){ foreach ($pairs as $pair){ $sx = $pair[0]; $sy = $pair[1]; $GLOBALS['mincontext'] = 5; //start high then fall down $GLOBALS["maxlumi"] = $GLOBALS["maxlumi_orig"]; $startx = $sx; $starty = $sy; $endy = min(imagesy($i), $starty + $GLOBALS['max_damage']); $endx = min(imagesx($i), $startx + $GLOBALS['max_damage']); $skip_y = $skip_y_orig; $to_heal_in_iteration = 1;//dummy $max_iterations = 50; // 10 still leaves holes for ($iterations = 0; $iterations < $max_iterations; $iterations++) if ($to_heal_in_iteration > 0){ $h1 = $healed; report("starting to heal [$startx, $starty, $endx, $endy]. Already healed = ".$healed.' of '.$pixels_to_heal.', '. $healed_in_this_round.' in the last round. Mincontext now = '.$GLOBALS['mincontext'].'<br />'); //checking lines that are ready $to_heal_in_iteration = 0; for ($y =$starty; $y<$endy; $y++) if (!in_array($y, $skip_y)){ $to_heal = 0; for ($x = $startx; $x<$endx; $x++){ $pix = imagecolorat($i, $x, $y); if (maxcolororluminance($pix) < $GLOBALS['minlumi']){ $to_heal++; $to_heal_in_iteration++; } } if ($to_heal == 0){ $skip_y[] = $y; report('added line '.$y.' to those that are ready and can be ignored<br />'); } }; $xrange = range($startx, $endx-1); shuffle($xrange); $yrange = range($starty, $endy-1); shuffle($yrange); $method = $iterations; //egyszer igy, egyszer ugy // don't rerun empty lines report('starting to handle pixels'); if ($method % 2 == 1){ report('method 1 running<br />'); foreach($yrange as $y) if (!in_array($y, $skip_y)){ foreach($xrange as $x){ handle_this_pixel($i, $x, $y, $healed, $save_stage_at_x_heals, $st); } } }else{ report('method 2 running<br />'); foreach($xrange as $x){ foreach($yrange as $y) if (!in_array($y, $skip_y)){ handle_this_pixel($i, $x, $y, $healed, $save_stage_at_x_heals, $st); } } } report('done'); //checking if the process' tolerance is low enough $healed_in_this_round = $healed - $h1; if ($healed_in_this_round < 100){ if ($GLOBALS['mincontext'] == 3){ report('few pixels healed, mincontext already small so increasing lumi tolerance<br />'); $GLOBALS['maxlumi'] = min(255, floor($GLOBALS['maxlumi']+$lumi_delta / 10)); }; if ($GLOBALS['mincontext'] > 3){ report('few pixels healed so decreased mincontext from '.$GLOBALS['mincontext'].'<br />'); $GLOBALS['mincontext']--; }; } /* if ($iterations > 1){ ////not doing this with the smaller areas if ($GLOBALS['mincontext'] > 4){ report('decreased mincontext from '.$GLOBALS['mincontext'].'<br />'); $GLOBALS['mincontext']--; } } */ if ($iterations > 3){ if ($GLOBALS['mincontext'] > 3){ report('decreased mincontext from '.$GLOBALS['mincontext'].'<br />'); $GLOBALS['mincontext']--; } } }; $st2++; imagejpeg($i, 'outpot_2_asregion_'.numberaskilo($st2).'.jpg'); };//for pairs };//main_iteration imagejpeg($i, 'output_8_before_lighterthan.jpg', 100); filter_lighterthanoriginal($i); // output a blinking interface to reveal the differences echo '<img id = "img1" src="input.jpg" width="800" />'; echo '<img id = "img2" src="output_z_final.jpg?'.mt_rand().'" width = "800" style="display:none"/>'; ?> </div> <script> setInterval(function (){ var a = document.getElementById("img1"); var b = document.getElementById("img2"); if (a.style.display!="none"){ a.style.display = "none"; b.style.display = ""; }else{ a.style.display = ""; b.style.display = "none"; } }, 1000); </script>





