1 ? $argv[1] : null; if(!$FILE || !file_exists("$BWDIR/$FILE") || !file_exists("$COLDIR/$FILE")) { echo "Usage: php colorpages.php filename\n"; echo "Filename or '$BWDIR/$FILE' or '$COLDIR/$FILE' is missing.\n"; echo "Make ScanTailor planned output to '$BWDIR' and a color copy to '$COLDIR'.\n\n"; exit; } //$FILE = "huspastetom"; $im = imagecreatefrompng("$BWDIR/$FILE"); $imthin = imagecreatefrompng("$BWDIR/$FILE"); echo("Width: ".($w = imagesx($imthin))."\n"); echo("Height: ".($h = imagesy($imthin))."\n"); echo("ColorsTotal: ".($cols = imagecolorstotal($imthin))."\n"); echo("TrueColor: ".(($tru = imageistruecolor($imthin)) ? "yes" : "no")."\n"); // FIXME: one file load could be avoided because the original pic is also loaded at the end! // So the original loaded here could be applied with convolution matrix maybe. // Target colors to produce $TARGETS = array( array( // Black 'mred'=>0, 'mgreen'=>0, 'mblue'=> 0, // match to color 'pred'=>0, 'pgreen'=>0, 'pblue'=> 0, // paint to color 'dist'=>0.65, // allow large difference 'fill'=>0) // no need to fill, this is the incoming pixel exactly! /* ,array( //8a3d34 titles in Elmira book 'mred'=>138 , 'mgreen'=>61 , 'mblue'=>52, 'pred'=>255 , 'pgreen'=>0 , 'pblue'=>0, 'dist'=>0.7, // allow small difference 'fill'=>1) */ /* ,array( // 3d89e1 cov2-8 test 'mred'=>61, 'mgreen'=>137, 'mblue'=> 225, 'pred'=>61, 'pgreen'=>137, 'pblue'=> 225, 'dist'=>0.7, 'fill'=>1) */ ,array( // ab2738 commodore újság piros 'mred'=>171, 'mgreen'=>39, 'mblue'=>56, 'pred'=>255 , 'pgreen'=>0 , 'pblue'=>0, 'dist'=>1, // allow small difference 'fill'=>1) ,array( // 1a729c commodore ujsag kek 'mred'=>26, 'mgreen'=>114, 'mblue'=>156, 'pred'=>26, 'pgreen'=>114, 'pblue'=>156, 'dist'=>1, 'fill'=>1) ,array( // 2c5e4c commodore ujsag zold 'mred'=>40, 'mgreen'=>100, 'mblue'=>103, 'pred'=>44, 'pgreen'=>94, 'pblue'=>76, 'dist'=>0.4, 'fill'=>1) // SARGA: fff458 kb ); // Convert target colors to Lab foreach($TARGETS as &$t) { $lab = rgb2lab($t['mred'], $t['mblue'], $t['mgreen']); $t['L'] = $lab[0]; $t['a'] = $lab[1]; $t['b'] = $lab[2]; } /* * PAPER colors * that are considered background (for main "$im" and for convlusion matrix reduced "$imthin") * $im is loaded as b&w but then converted to true color * $imthin is a copy of the same, but matrix-op & save results in 1 black and 254 white palette entries */ $PAPER = hexdec("FFFFFF");//FIXME paper == bg?? == index[0] color in PNG spec??? $BLACK = 0; $THINPAPER = hexdec("FFFFFF"); // FIXME: get paper color better way /* * $FWLOOK value should depend on side of letters * E.g. if letter roundingns and lines of this shape "/" * have horzintal pixel difference more than 1-2 pixels * you may want to increase this. E.g. very high DPI images! * This is how much forward (by one steps) and one pixel up * to reuse existing color of the letters from previous line if found. */ $FWLOOK = 5; // Look 5 forward 1 up to avoid stripes $palette = array(); // Will reserve used colors // Dump original file palette (should be black & white) if(!$tru) { echo("Palette\n"); for($i = 0; $i < $cols; $i++) { $c = imagecolorsforindex($im, $i); echo(" $i "); $hexcol = color2hex($c); echo("#".$hexcol); if($c['alpha'] != 0) { echo(" (".$c['alpha'].")"); } echo("\n"); } } else { //throw new Exception("Need indexed image for best thin-line convlution matrix filter."); } //goto SKIPALL; // Apply convlution filter to thinner lines (to avoid averaging color on letter edge) echo("Making thin image with convlution matrix.\n"); $filter_thin = array(array(1,1,1), array(1,0,1), array(1,1,1)); if(!$tru && !imagepalettetotruecolor($imthin)) { throw new Exception("Failed to convert to true color."); } imageconvolution($imthin, $filter_thin, 1, 127); imagepng($imthin, "convolution.$FILE", 9); // FIXME: php 5.5 but docs contain an implementation if needed! echo("Converting main image to true color."); if(!$tru && !imagepalettetotruecolor($im)) { throw new Exception("Failed to convert to true color."); } echo("TrueColor: ".(imageistruecolor($im) ? "yes" : "no")."\n"); $newcol = null; $col = null; /* * $mistakes are sets of colors that represent one letter * (mistake becuase letters should be single color after idenitfying, but keeping * mistakenly filled parts is more effective than re-coloring) * Array of lists: each list contains all colors that has to be colored to be the same */ $mistakes = array(); $mistake_cache = array(); // col1_col2 and col2_col1 hash-set to avoid repeatedly processing $line = array(array(), array()); // Caches line pixels for quick look up to previous line $l = 0; // Which line buf to use 0 or 1 being filled, the other stores previous line $SHIT = 0; /* * IDENTIFY SPOTS (or letters) * This fills each letter with 1-2 colors, that will be their "ID". * Each non-joined painted area will be one "entity" for further processing * and will be recolored with a solid - later decided - color at the end. */ for($y = 0; $y < $h; $y++) { $isin = false; // Is in a letter (non-bg)? $len = 0; // Filled area length in the line (restarts at reaching next bg pixel) $flood_y = false; // Paint taken from prev. line? $l = $y % 2; $lv = 1 - $l; $line[$l] = array(); for($x = 0; $x < $w; $x++){ $point = imagecolorat($im, $x, $y); // Replace black pixels if($point == $BLACK) { // Check if above us is also filled if($y > 0 && array_key_exists($x, $line[$lv])) { $upcol = $line[$lv][$x]; // FIXME: this $line optimization didn't make it faster, even smaller a bit 51s vs 48s //$upcol = imagecolorat($im, $x, $y - 1); // if($upcol != $PAPER) { // Take upper color if we have no pen yet if($len == 0) { $newcol = $upcol; $flood_y = true; } elseif($upcol != $newcol) { // Remember these two as same letter's colors addmistake($newcol, $upcol); } // } } // Start a new flood if(!$isin) { $len = 1; $isin = true; if(!$flood_y) { // Look a bit forward and up to avoid making many new colors for letter parts like: "/" for($fw = 0; $fw < $FWLOOK; $fw++) { $fwcol = imagecolorat($im, $x + $fw, $y); if($fwcol == $PAPER) { break; } else { $fwupcol = imagecolorat($im, $x + $fw, $y - 1); if($fwupcol != $PAPER) { $flood_y = true; $newcol = $fwupcol; break; } } } if(!$flood_y) { // Really make new unique color $col = randomcol(); $newcol = imagecolorallocate($im, $col['red'], $col['green'], $col['blue']); if($newcol != $col['dec']) { throw new Exception("ez valahogy máshogy van"); } if($newcol === false) { throw new Exception("Cannot create new color for some reason..."); } } } } else { $len++; } if(!imagesetpixel($im, $x, $y, $newcol)) { throw new Exception("Cannot set pixel for some reason...x=$x y=$y pixel=$newcol."); } $line[$l][$x] = $newcol; } elseif($isin) { $isin = false; $flood_y = false; $len = 0; } } echo($y." "); } // Fix mistakenly filled areas echo("\nMistakes: ".count($mistakes)."\n"); // Reindex mistakes array for fast lookup // Instead lists of same-letter colors, the $repaint array contains // any of the letter's color as KEY and VALUE is the single unquie (ID) color of it // (which is anyway the first color from the list - that is also added as self-refrence) $repaint = array(); $errors = array(); for($i = 0; $i < count($mistakes); $i++) { echo($i.": "); // Will paint every color to be the same as first color in the list for($j = 0; $j < count($mistakes[$i]); $j++) { $col = $mistakes[$i][$j]; echo("#".dec2hex($col)." "); if(array_key_exists($col, $repaint)) { throw new Exception("Repeated colors, someting is not OK. Color: ".dec2hex($col).", in $i."); $errors[] = $col; } $repaint[$col] = $mistakes[$i][0]; } echo("\n"); } //var_dump($errors); imagepng($im, "pass1.$FILE", 9); /* Not needed since final coloring can use the repaint array! * But keep for later debugging, saving the intermediate file, etc // Fill multi-color letters with single color for($y = 0; $y < $h; $y++) { for($x = 0; $x < $w; $x++){ $rgb = imagecolorat($im, $x, $y); if($rgb != $PAPER) { if(array_key_exists($rgb, $repaint)) { if(!imagesetpixel($im, $x, $y, $repaint[$rgb])) { throw new Exception("Cannot set pixel for some reason...x=$x y=$y pixel=$rgb."); } } } } } if(!imagepng($im, "$FILE.out.png", 9)) { throw Exception("Failed to save resulting image."); } */ SKIPALL: // Load original color file $ic = imagecreatefrompng("$COLDIR/$FILE"); echo("Width: ".($w = imagesx($ic))."\n"); echo("Height: ".($h = imagesy($ic))."\n"); echo("ColorsTotal: ".($cols = imagecolorstotal($ic))."\n"); echo("TrueColor: ".(($tru = imageistruecolor($ic)) ? "yes" : "no")."\n"); // Original black & white needed $imorig = imagecreatefrompng("$BWDIR/$FILE"); if(!imagepalettetotruecolor($imorig)) { throw new Exception("Failed to convert to true color."); } // Calculate average original color of each letter and decide a final color for it! $coloravg = array(); for($y = 0; $y < $h; $y++) { // Mark all colors unseen in this line foreach(array_keys($coloravg) as $key) { $coloravg[$key]['inrow'] = 'n'; } // Collect all letters' pixels in the line and their original color for($x = 0; $x < $w; $x++) { $rgb = imagecolorat($im, $x, $y); if($rgb != $PAPER) { // Use main "ID" color of the letter if it has various bad fills if(array_key_exists($rgb, $repaint)) { $rgb = $repaint[$rgb]; } // Start the array to collect colors and keep first "y" coordinate if(!array_key_exists($rgb, $coloravg)) { $coloravg[$rgb] = array(); $coloravg[$rgb]['first'] = $y; // First line for this spot $coloravg[$rgb]['colors'] = array(); // First line for this spot $coloravg[$rgb]['minx'] = $x; $coloravg[$rgb]['maxx'] = $x; } $coloravg[$rgb]['inrow'] = 'y'; // Remember bounding box for fill if($coloravg[$rgb]['minx'] > $x) { $coloravg[$rgb]['minx'] = $x; } if($coloravg[$rgb]['maxx'] < $x) { $coloravg[$rgb]['maxx'] = $x; } // Collect original colors if "thin" image also has a pixel here // (that avoids pixles on the letter edges) $colthin = imagecolorat($imthin, $x, $y); if($colthin != $THINPAPER) { // Only those count in avg, which are on "thin" image too // FIXME: may leave array empty if letter disappeared on "thin" $col = imagecolorat($ic, $x, $y); $coloravg[$rgb]['colors'][] = $col; } } } // Process letters that didn't continue in this row foreach(array_keys($coloravg) as $key) { // No inrow key in null-ed array values if(array_key_exists('inrow', $coloravg[$key]) && $coloravg[$key]['inrow'] == 'n') { $rtotal = 0; $gtotal = 0; $btotal = 0; $count = 0; // Count avg color of each stored color data of this 'ID' (letter, spot) foreach($coloravg[$key]['colors'] as $val) { $c = dec2col($val); $rtotal += $c['red']; $gtotal += $c['green']; $btotal += $c['blue']; $count++; } $red = ($count ? round($rtotal / $count) : 0); $green = ($count ? round($gtotal / $count) : 0); $blue = ($count ? round($btotal / $count) : 0); // Find nearest color to the average from predefined set of target colors $dist = 10000; $target = null; foreach($TARGETS as $t) { // Lab color difference $lab = rgb2lab($red, $green, $blue); $d = pow(pow($t['L'] - $lab[0], 2) + pow($t['a'] - $lab[1], 2) + pow($t['b'] - $lab[2], 2), 0.5); $d *= $t['dist']; //echo("DIST: $d\n"); if($d < $dist) { $target = $t; $dist = $d; } } // Online those colors fill that are different from original image if($target['fill']) { $newcol = imagecolorallocate($imorig, $target['pred'], $target['pgreen'], $target['pblue']); // Fill from first occurrence till this y row for($fy = $coloravg[$key]['first']; $fy < $y; $fy++) { for($fx = $coloravg[$key]['minx']; $fx <= $coloravg[$key]['maxx']; $fx++) { $rgb = imagecolorat($im, $fx, $fy); // Find any various fills of multi-colored letters if(array_key_exists($rgb, $repaint)) { $rgb = $repaint[$rgb]; } if($rgb == $key) { if(!imagesetpixel($imorig, $fx, $fy, $newcol)) { throw new Exception("Cannot set pixel for some reason...x=$x y=$y pixel=$rgb."); } } } } } unset($coloravg[$key]); } } echo($y." "); } if(!is_dir($OUTDIR)) { mkdir($OUTDIR); } if(!imagepng($imorig, "$OUTDIR/$FILE", 9)) { throw Exception("Failed to save resulting image."); } imagedestroy($im); imagedestroy($ic); echo"SHIT:".$SHIT."\n"; // FUNCTIONS // function pad($num) { return strlen($num) >= 2 ? $num : "0".$num; } // R G B color object to HTML color string function color2hex($c) { return pad(dechex($c['red'])).pad(dechex($c['green'])).pad(dechex($c['blue'])); } // Decimal true color to HTML color string function dec2hex($rgb) { return color2hex(dec2col($rgb)); } // Decimal true color to R G B values. function dec2col($rgb) { $c = array(); $c['red'] = ($rgb >> 16) & 0xFF; $c['green'] = ($rgb >> 8) & 0xFF; $c['blue'] = $rgb & 0xFF; return $c; } // Returns unique color (array with 'red' 'green' 'blue' and 'dec' keys). function randomcol() { global $palette,$SHIT; do { $c = array(); $c['red'] = rand(1,254); // Never black or white, that's the input palette $c['green'] = rand(1,254); $c['blue'] = rand(1,254); $c['dec'] = $c['red'] * 256*256 + $c['green'] * 256 + $c['blue']; $SHIT++; } while(array_key_exists($c['dec'], $palette)); $palette[$c['dec']] = 1; $SHIT--; return $c; } // $c1 and $c2 must be re-filled euqally // and also equally with any already added instances of these function addmistake($c1, $c2) { global $mistakes, $mistake_cache; // Already called with this data (or backwards?) if(array_key_exists($c1."_".$c2, $mistake_cache) || array_key_exists($c2."_".$c1, $mistake_cache)) { return false; } $mistake_cache[$c1."_".$c2] = 1; // Remember this call for speed $mistake_cache[$c2."_".$c1] = 1; //echo("\nAdd: #".dec2hex($c1).", #".dec2hex($c2)."\n"); $isin = -1; $deletelist = array(); // Loop lists of mistakes and if either C1 or C2 is found add the other to same list for($i = 0; $i < count($mistakes); $i++) { if(in_array($c1, $mistakes[$i])) { // C1 is on a list already, add C2 to same if(!in_array($c2, $mistakes[$i])) { array_push($mistakes[$i], $c2); } $isin = $i; break; } elseif(in_array($c2, $mistakes[$i])) { // C2 is on a list already, add C1 to same array_push($mistakes[$i], $c1); $isin = $i; break; } } // Merge lists if necessary // Either color may be found in later array too, then those must be merged! // Consider two lines going downwards building separte arrays and they join at bottom if($isin > -1) { for($i = 0; $i < count($mistakes); $i++) { if($i == $isin) { continue; } if(in_array($c1, $mistakes[$i]) || in_array($c2, $mistakes[$i])) { //echo("\nMERGE: $i. into $isin.\n"); // Copy the values foreach($mistakes[$i] as $col) { if(!in_array($col, $mistakes[$isin])) { array_push($mistakes[$isin], $col); } } // Drop other array $deletelist[] = $i; } } } // Delete merged arrays if(count($deletelist) > 0) { // Delete backwards becuase array will become shorter for($i = count($deletelist)-1; $i >= 0; $i--) { unset($mistakes[$deletelist[$i]]); } $mistakes = array_values($mistakes); // Remove null-ed values from the array } // Neither C1 or C2 was in a list yet, so start a new list if($isin == -1) { $mistakes[] = array($c1, $c2); } } // http://www.f4.fhtw-berlin.de/~barthel/ImageJ/ColorInspector//HTMLHelp/farbraumJava.htm // http://stackoverflow.com/questions/4593469/java-how-to-convert-rgb-color-to-cie-lab function rgb2lab($R, $G, $B) { $lab = array(); //http://www.brucelindbloom.com //float r, g, b, X, Y, Z, fx, fy, fz, xr, yr, zr; //float Ls, as, bs; $eps = 216 /24389 ; $k = 24389 /27 ; $Xr = 0.964221; // reference white D50 $Yr = 1.0; $Zr = 0.825211; // RGB to XYZ $r = $R/255; //R 0..1 $g = $G/255; //G 0..1 $b = $B/255; //B 0..1 // assuming sRGB (D65) if ($r <= 0.04045) $r = $r/12; else $r = pow(($r + 0.055) / 1.055, 2.4); if ($g <= 0.04045) $g = $g/12; else $g = pow(($g + 0.055) / 1.055, 2.4); if ($b <= 0.04045) $b = $b/12; else $b = pow(($b + 0.055) / 1.055, 2.4); $X = 0.436052025 * $r + 0.385081593 * $g + 0.143087414 * $b; $Y = 0.222491598 * $r + 0.71688606 * $g + 0.060621486 * $b; $Z = 0.013929122 * $r + 0.097097002 * $g + 0.71418547 * $b; // XYZ to Lab $xr = $X / $Xr; $yr = $Y / $Yr; $zr = $Z / $Zr; if ( $xr > $eps ) $fx = pow($xr, 1/3); else $fx = (($k * $xr + 16) / 116); if ( $yr > $eps ) $fy = pow($yr, 1/3); else $fy = (($k * $yr + 16) / 116); if ( $zr > $eps ) $fz = pow($zr, 1/3); else $fz = (($k * $zr + 16) / 116); $Ls = (116 * $fy) - 16; $as = 500 * ($fx - $fy); $bs = 200 * ($fy - $fz); $lab[0] = round(2.55 * $Ls + 0.5); $lab[1] = round($as + 0.5); $lab[2] = round($bs + 0.5); return $lab; } ?>