mirror of
https://github.com/daniel5151/ANESE.git
synced 2025-04-02 10:32:00 -04:00
487 lines
13 KiB
PHP
Executable file
Vendored
487 lines
13 KiB
PHP
Executable file
Vendored
<?php
|
|
|
|
require 'nes_palette.php';
|
|
|
|
$SOURCE_IMAGE = $argv[1];
|
|
$RESULT_IMAGE = $argv[2];
|
|
$RESULT_DATA = $argv[3];
|
|
$MAX_ROUNDS = (int)$argv[4];
|
|
|
|
// Convert gamma-corrected RGB into linear RGB
|
|
function GammaRGB($rgb)
|
|
{
|
|
$r = ($rgb >> 16) & 0xFF;
|
|
$g = ($rgb >> 8) & 0xFF;
|
|
$b = ($rgb >> 0) & 0xFF;
|
|
$y = ($r*299 + $g*587 + $b*114);// / 255e3;
|
|
$r = pow(($r/255.0), 2.2);
|
|
$g = pow(($g/255.0), 2.2);
|
|
$b = pow(($b/255.0), 2.2);
|
|
return Array( $r, $g, $b, $y, $rgb );
|
|
}
|
|
|
|
function MakeCombinations($palette)
|
|
{
|
|
$result = Array();
|
|
/*
|
|
foreach($palette as $c1 => $rgb1)
|
|
{
|
|
// Make 1-color combinations
|
|
$result[] = Array( Array($c1), $rgb1 );
|
|
// Make 2-color combinations
|
|
foreach($palette as $c2 => $rgb2)
|
|
if($c2 >= $c1)
|
|
{
|
|
if($c2 > $c1)
|
|
$result[] = Array( Array($c1,$c2), Array( ($rgb1[0]+$rgb2[0]),
|
|
($rgb1[1]+$rgb2[1]),
|
|
($rgb1[2]+$rgb2[2]) ) );
|
|
foreach($palette as $c3 => $rgb3)
|
|
{
|
|
if($c3 >= $c2)
|
|
{
|
|
if($c3 > $c2 || $c2 != $c1)
|
|
{
|
|
$result[] = Array( Array($c1,$c2,$c3), Array( ($rgb1[0]+$rgb2[0]+$rgb3[0]),
|
|
($rgb1[1]+$rgb2[1]+$rgb3[1]),
|
|
($rgb1[2]+$rgb2[2]+$rgb3[2]) ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
/**/
|
|
foreach($palette as $c1 => $rgb1)
|
|
foreach($palette as $c2 => $rgb2) if($c2 >= $c1)
|
|
foreach($palette as $c3 => $rgb3) if($c3 >= $c2)
|
|
foreach($palette as $c4 => $rgb4) if($c4 >= $c3)
|
|
$result[] = Array( Array($c1,$c2,$c3,$c4), Array( ($rgb1[0]+$rgb2[0]+$rgb3[0]+$rgb4[0]),
|
|
($rgb1[1]+$rgb2[1]+$rgb3[1]+$rgb4[1]),
|
|
($rgb1[2]+$rgb2[2]+$rgb3[2]+$rgb4[2]) ) );
|
|
/**/
|
|
/*
|
|
foreach($palette as $c1 => $rgb1)
|
|
foreach($palette as $c2 => $rgb2) if($c2 >= $c1)
|
|
foreach($palette as $c3 => $rgb3) if($c3 >= $c2)
|
|
foreach($palette as $c4 => $rgb4) if($c4 >= $c3)
|
|
foreach($palette as $c5 => $rgb5) if($c5 >= $c4)
|
|
foreach($palette as $c6 => $rgb6) if($c6 >= $c5)
|
|
foreach($palette as $c7 => $rgb7) if($c7 >= $c6)
|
|
foreach($palette as $c8 => $rgb8) if($c8 >= $c7)
|
|
$result[] = Array( Array($c1,$c2,$c3,$c4,$c5,$c6,$c7,$c8),
|
|
Array( ($rgb1[0]+$rgb2[0]+$rgb3[0]+$rgb4[0]+$rgb5[0]+$rgb6[0]+$rgb7[0]+$rgb8[0]),
|
|
($rgb1[1]+$rgb2[1]+$rgb3[1]+$rgb4[1]+$rgb5[1]+$rgb6[1]+$rgb7[1]+$rgb8[1]),
|
|
($rgb1[2]+$rgb2[2]+$rgb3[2]+$rgb4[2]+$rgb5[2]+$rgb6[2]+$rgb7[2]+$rgb8[2]) ) );
|
|
*/
|
|
/*
|
|
foreach($palette as $c1 => $rgb1)
|
|
foreach($palette as $c2 => $rgb2) if($c2 >= $c1)
|
|
$result[] = Array( Array($c1,$c2), Array( ($rgb1[0]+$rgb2[0]),
|
|
($rgb1[1]+$rgb2[1]),
|
|
($rgb1[2]+$rgb2[2]) ) );
|
|
*//*
|
|
foreach($palette as $c1 => $rgb1)
|
|
{
|
|
// Make 1-color combinations
|
|
$result[] = Array( Array($c1), $rgb1 );
|
|
}
|
|
*/
|
|
return $result;
|
|
}
|
|
function ColorDifference($a, $b)
|
|
{
|
|
#$c0 = pow($a[0] - $b[0], 1/2.2);
|
|
#$c1 = pow($a[1] - $b[1], 1/2.2);
|
|
#$c2 = pow($a[2] - $b[2], 1/2.2);
|
|
$c0 = ($a[0] - $b[0]);
|
|
$c1 = ($a[1] - $b[1]);
|
|
$c2 = ($a[2] - $b[2]);
|
|
return $c0*$c0 + $c1*$c1 + $c2*$c2;
|
|
}
|
|
|
|
function DitherPixel($pixel, $palette, $combinations, $n_elements = 4)
|
|
{
|
|
$result = Array();
|
|
#print_r($palette);
|
|
|
|
while(count($result) < $n_elements)
|
|
{
|
|
$color_so_far = Array(0,0,0);
|
|
foreach($result as $index)
|
|
for($p=0; $p<3; ++$p)
|
|
$color_so_far[$p] += $palette[$index][$p];
|
|
|
|
$best_difference = null;
|
|
$chosen = null;
|
|
|
|
$n_sofar = count($result);
|
|
|
|
// For each combination, see how it would help
|
|
foreach($combinations as $info)
|
|
{
|
|
$n_mix = count($info[0]);
|
|
$limit = (int)(($n_elements - $n_sofar) / $n_mix);
|
|
for($n=1; $n<= $limit ; ++$n)
|
|
{
|
|
$mul = 1 / ($n_sofar + $n_mix * $n); // How to achieve a mathematical mean
|
|
$test = Array
|
|
(
|
|
($color_so_far[0] + $info[1][0] * $n) * $mul,
|
|
($color_so_far[1] + $info[1][1] * $n) * $mul,
|
|
($color_so_far[2] + $info[1][2] * $n) * $mul
|
|
);
|
|
$difference = ColorDifference($pixel, $test);
|
|
if(!isset($best_difference) || $difference < $best_difference)
|
|
{
|
|
$best_difference = $difference;
|
|
$chosen = $info;
|
|
}
|
|
}
|
|
}
|
|
// Add the chosen color(s) into the mix
|
|
foreach($chosen[0] as $c)
|
|
$result[] = $c;
|
|
}
|
|
// Sort the result according to luma
|
|
usort($result,
|
|
function($a,$b)use($palette)
|
|
{ return $palette[$a][3]-$palette[$b][3]; });
|
|
return $result;
|
|
}
|
|
|
|
// Dither given image using the given palette
|
|
function DitherImage($pixels, $palette, $combinations, $fn)
|
|
{
|
|
global $dither8x8;
|
|
$cache = Array();
|
|
|
|
$wid = 0;
|
|
$hei = count($pixels);
|
|
foreach($pixels as $y => $xspan)
|
|
{
|
|
$wid = count($xspan);
|
|
break;
|
|
}
|
|
|
|
$im2 = ImageCreateTrueColor($wid, $hei);
|
|
$result = Array();
|
|
|
|
$count = 16;
|
|
$uncount = 1/$count;
|
|
$ypmask = 7;
|
|
$xpmask = 7;
|
|
|
|
foreach($pixels as $y => $xspan)
|
|
{
|
|
printf("\rQuantizing... %d/%d", $y, $hei); flush();
|
|
foreach($xspan as $x => $pixel)
|
|
{
|
|
$rgb = $pixel[4];
|
|
$mix = &$cache[$rgb];
|
|
if(!isset($mix))
|
|
{
|
|
$mix = DitherPixel($pixel, $palette, $combinations, $count);
|
|
}
|
|
|
|
$yp = ($y % 8) & $ypmask;
|
|
$xp = ($x % 8) & $xpmask;
|
|
|
|
#print_r($mix);
|
|
|
|
$choose = $mix[ (int) ($dither8x8[$yp][$xp] * $count / 64) ];
|
|
|
|
#$choose = $mix[ $dither8x8[$y % 8][$x % 8] >> (6-2) ];
|
|
|
|
ImageSetPixel($im2, $x,$y, $palette[$choose][4]);
|
|
|
|
$m = Array(0,0,0);
|
|
foreach($mix as $c)
|
|
{
|
|
$m[0] += $palette[$c][0];
|
|
$m[1] += $palette[$c][1];
|
|
$m[2] += $palette[$c][2];
|
|
}
|
|
$m[0] *= $uncount;
|
|
$m[1] *= $uncount;
|
|
$m[2] *= $uncount;
|
|
$m[5] = $choose;
|
|
$result[$y][$x] = $m;
|
|
unset($mix);
|
|
}
|
|
}
|
|
ImagePng($im2, $fn);
|
|
$result[-1] = $im2;
|
|
return $result;
|
|
}
|
|
|
|
|
|
// Load NES palette
|
|
$palette = Array();
|
|
for($c=0; $c<64; ++$c)
|
|
{
|
|
$signal = GenNTSCsignal($c, 0, 12);
|
|
$rgb = DecodeNTSCsignal($signal, 0,12, 3.9);
|
|
$palette[$c] = GammaRGB($rgb);
|
|
}
|
|
|
|
// Generate 8x8 ordered dithering matrix
|
|
$dither8x8 = Array();
|
|
for($y=0; $y<8; ++$y)
|
|
for($x=0; $x<8; ++$x)
|
|
{
|
|
$q = $x ^ $y;
|
|
$p = (($x & 4)>>2) + (($x & 2)<<1) + (($x&1)<<4);
|
|
$q = (($q & 4)>>1) + (($q & 2)<<2) + (($q&1)<<5);
|
|
$dither8x8[$y][$x] = $p+$q;
|
|
}
|
|
|
|
/*for($y=0; $y<8; ++$y)
|
|
{
|
|
for($x=0; $x<8; ++$x)
|
|
printf("%3d", ((int)($dither8x8[$y][$x] * 16 / 64)));
|
|
print "\n";
|
|
}
|
|
exit;*/
|
|
|
|
// Load source image
|
|
|
|
$im = ImageCreateFromPng($SOURCE_IMAGE);
|
|
$sx = ImageSx($im);
|
|
$sy = ImageSy($im);
|
|
/* Convert the image into a gamma-corrected array of pixels */
|
|
$pixels = Array();
|
|
for($y=0; $y<240; ++$y)
|
|
for($x=0; $x<$sx; ++$x)
|
|
$pixels[$y][$x] = GammaRGB(0x000000);
|
|
|
|
for($y=0; $y<$sy; ++$y)
|
|
for($x=0; $x<$sx; ++$x)
|
|
$pixels[$y+6][$x] = GammaRGB(ImageColorAt($im, $x,$y));
|
|
|
|
$sy += 16;
|
|
|
|
|
|
/* Step 1:
|
|
Create four 4-color palettes that best represent the input image
|
|
in total. Since the 0th color of the palettes is always black,
|
|
we have four 3-color palettes actually.
|
|
|
|
For now, we'll go with a simplified algorithm. Hardcoded palette!
|
|
|
|
Later, we'll see if the image is refined by substituting some colors.
|
|
*/
|
|
|
|
$f = function($i)
|
|
{
|
|
global $palette;
|
|
$p = $palette[$i];
|
|
$p[5] = $i;
|
|
return $p;
|
|
};
|
|
$palettes = Array
|
|
(
|
|
/* // For mountains
|
|
0 => Array($f(0x0F),$f(0x21),$f(0x11),$f(0x30)), // grayscale
|
|
1 => Array($f(0x0F),$f(0x21),$f(0x28),$f(0x30)), // greens against gray
|
|
2 => Array($f(0x0F),$f(0x02),$f(0x1A),$f(0x21)), // edge between gray and blue
|
|
3 => Array($f(0x0F),$f(0x0A),$f(0x28),$f(0x33)) // light greens against white
|
|
*/
|
|
/* / I don't approve this...
|
|
0 => Array($f(0x0F),$f(0x2C),$f(0x11),$f(0x30)), // grayscale
|
|
1 => Array($f(0x0F),$f(0x22),$f(0x28),$f(0x30)), // greens against gray
|
|
2 => Array($f(0x0F),$f(0x02),$f(0x2A),$f(0x21)), // edge between gray and blue
|
|
3 => Array($f(0x0F),$f(0x19),$f(0x28),$f(0x22)) // light greens against white
|
|
*/
|
|
// For Thomas Kinkade's #50
|
|
0 => Array($f(0x0F),$f(0x10),$f(0x02),$f(0x30)),
|
|
1 => Array($f(0x0F),$f(0x33),$f(0x38),$f(0x30)),
|
|
2 => Array($f(0x0F),$f(0x37),$f(0x28),$f(0x3D)),
|
|
3 => Array($f(0x0F),$f(0x3B),$f(0x17),$f(0x36))
|
|
);
|
|
$combinations = Array
|
|
(
|
|
0 => MakeCombinations($palettes[0]),
|
|
1 => MakeCombinations($palettes[1]),
|
|
2 => MakeCombinations($palettes[2]),
|
|
3 => MakeCombinations($palettes[3])
|
|
);
|
|
|
|
/* Dither the image fully using all of the four palettes. */
|
|
$dithereds = Array();
|
|
for($p=0; $p<4; ++$p)
|
|
{
|
|
$dithereds[$p] = DitherImage($pixels, $palettes[$p], $combinations[$p], "out{$p}.png");
|
|
print "\nwrote out$p.png\n";
|
|
}
|
|
|
|
$best_totaldiff = null;
|
|
$tried = Array();
|
|
|
|
for($round=0; $round<$MAX_ROUNDS; ++$round)
|
|
{
|
|
$s = '';
|
|
for($p=0; $p<4; ++$p)
|
|
for($n=0; $n<4; ++$n)
|
|
$s .= chr( $palettes[$p][$n][5] );
|
|
$tried[$s] = true;
|
|
|
|
$attributetable = Array();
|
|
|
|
$total_diff = 0;
|
|
/* For each 16x16 block, choose the best representation from the source images */
|
|
$im2 = ImageCreateTrueColor($sx,$sy);
|
|
for($y=0; $y<$sy; $y+=16)
|
|
{
|
|
print "\rChoosing tiles, $y/$sy";
|
|
for($x=0; $x<$sx; $x+=16)
|
|
{
|
|
$bestdiff = null;
|
|
$chosen = 0;
|
|
|
|
$downx = 1;
|
|
$downy = 1;
|
|
$downmul = 1 / ($downx * $downy);
|
|
for($p=0; $p<4; ++$p)
|
|
{
|
|
$diff = 0;
|
|
for($py=0; $py<16; $py += $downy)
|
|
for($px=0; $px<16; $px += $downx)
|
|
{
|
|
if($downx != 1 || $downy != 1)
|
|
{
|
|
$a = Array(0,0,0);
|
|
$b = Array(0,0,0);
|
|
for($dy=0; $dy<$downy; ++$dy)
|
|
for($dx=0; $dx<$downx; ++$dx)
|
|
{
|
|
for($n=0; $n<3; ++$n)
|
|
{
|
|
$a[$n] += $dithereds[$p][$y+$py+$dy][$x+$px+$dx][$n];
|
|
$b[$n] += $pixels[$y+$py+$dy][$x+$px+$dx][$n];
|
|
}
|
|
}
|
|
for($n=0; $n<3; ++$n)
|
|
{
|
|
$a[$n] *= $downmul;
|
|
$b[$n] *= $downmul;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$a = $dithereds[$p][$y+$py][$x+$px];
|
|
$b = $pixels[$y+$py][$x+$px];
|
|
}
|
|
$diff += ColorDifference($a, $b);
|
|
}
|
|
if(!isset($bestdiff) || $diff < $bestdiff)
|
|
{
|
|
$bestdiff = $diff;
|
|
$chosen = $p;
|
|
}
|
|
}
|
|
$total_diff += $bestdiff;
|
|
ImageCopy($im2, $dithereds[$chosen][-1], $x,$y, $x,$y, 16,16);
|
|
for($py=0; $py<16; $py+=8)
|
|
for($px=0; $px<16; $px+=8)
|
|
$attributetable[$y+$py][$x+$px] = $chosen;
|
|
}
|
|
}
|
|
|
|
if(!isset($best_totaldiff) || $total_diff < $best_totaldiff)
|
|
{
|
|
$best_totaldiff = $total_diff;
|
|
$backup_palettes = $palettes;
|
|
for($p=0; $p<4; ++$p)
|
|
{
|
|
$dither_backups[$p] = $dithereds[$p];
|
|
$comb_backups[$p] = $combinations[$p];
|
|
}
|
|
|
|
print "\nGot a new good result (score: $total_diff) with palette:\n";
|
|
for($p=0; $p<4; ++$p)
|
|
{
|
|
print ".byte ";
|
|
for($n=0; $n<4; ++$n)
|
|
printf("$%02X,", $palettes[$p][$n][5]);
|
|
print "\n";
|
|
}
|
|
print "Writing $RESULT_IMAGE and $RESULT_DATA\n";
|
|
|
|
ImagePng($im2, $RESULT_IMAGE);
|
|
ImageDestroy($im2);
|
|
|
|
$fp = fopen($RESULT_DATA, 'w');
|
|
for($p=0; $p<4; ++$p)
|
|
{
|
|
fwrite($fp, ".palette ");
|
|
for($n=0; $n<4; ++$n)
|
|
fprintf($fp, "$%02X,", $palettes[$p][$n][5]);
|
|
fwrite($fp, "\n");
|
|
}
|
|
for($y=0; $y<$sy; $y+=8)
|
|
for($x=0; $x<$sx; $x+=8)
|
|
{
|
|
$p = $attributetable[$y][$x];
|
|
$s = '';
|
|
for($py=0; $py<8; ++$py)
|
|
for($px=0; $px<8; ++$px)
|
|
{
|
|
$d = $dithereds[$p][$y+$py][$x+$px][5];
|
|
/*$c = '?';
|
|
for($n=0; $n<4; ++$n)
|
|
if($d[5] == $palettes[$p][$n][5])
|
|
{ $c = $n; break; }*/
|
|
$s .= $d;
|
|
}
|
|
fprintf($fp, "%-4d %-4d %d %s\n", $x,$y, $p, $s);
|
|
}
|
|
fclose($fp);
|
|
}
|
|
|
|
// Issue a random change to one of the palettes
|
|
$palettes = $backup_palettes;
|
|
for($p=0; $p<4; ++$p)
|
|
{
|
|
$dithereds[$p] = $dither_backups[$p];
|
|
$combinations[$p] = $comb_backups[$p];
|
|
}
|
|
$n_attempts = 0;
|
|
do {
|
|
$which_palette = rand(0,3); $p = $which_palette;
|
|
$which_color = rand(1,3);
|
|
$current_color = $palettes[$p][$which_color][5];
|
|
|
|
if($n_attempts < 64)
|
|
{
|
|
$new_color = $current_color;
|
|
$new_color = (((($new_color >> 4) + rand(-1,1)) & 3) << 4)
|
|
| ((($new_color & 0x0F) + rand(13,15)) % 14);
|
|
}
|
|
else
|
|
{
|
|
$new_color = rand(0x0,0xD) | (rand(0,3)<<4);
|
|
}
|
|
$s = '';
|
|
for($q=0; $q<4; ++$q)
|
|
for($n=0; $n<4; ++$n)
|
|
$s .= chr( $palettes[$q][$n][5] );
|
|
$s[ $which_palette*4 + $which_color] = chr($new_color);
|
|
++$n_attempts;
|
|
|
|
} while($new_color == 0x1D || $new_color == 0x20
|
|
|| $new_color == 0x0D
|
|
|| $new_color == $current_color
|
|
|| isset($tried[$s]));
|
|
|
|
$palettes[$p][$which_color] = $f($new_color);
|
|
|
|
print "\rTrying for $p: ";
|
|
for($n=0; $n<4; ++$n)
|
|
printf("$%02X,", $palettes[$p][$n][5]);
|
|
print "\n";
|
|
|
|
$combinations[$p] = MakeCombinations($palettes[$p]);
|
|
$dithereds[$p] = DitherImage($pixels, $palettes[$p], $combinations[$p], "test{$p}.png");
|
|
}
|