Advertisement
r57shell

bitmap2md r57shell version

Jun 10th, 2013
168
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Perl 9.88 KB | None | 0 0
  1. #!/usr/bin/perl
  2.  
  3. use strict;
  4. use warnings;
  5. use Getopt::Long;
  6. use Image::Magick;
  7. use POSIX qw(ceil);
  8. use Algorithm::Cluster qw/kcluster/;
  9.  
  10. my %options = (
  11.   resize        => '',
  12.   dither        => 0,
  13.   tiles         => 0,
  14.   colors        => 60,
  15. );
  16.  
  17. my $quantum = Image::Magick->QuantumDepth;
  18.  
  19. #################################################
  20. # Options parsing
  21. #################################################
  22. exit(1) unless GetOptions(
  23.   'resize:s'            => \$options{resize},
  24.   'dither=i'            => \$options{dither},
  25.   'colors=i'            => \$options{colors},
  26.   'tiles=i'             => \$options{tiles},
  27.   'sample=s'            => \$options{sample},
  28.   'output=s'            => \$options{output},
  29.   'help'                => \$options{help},
  30. );
  31.  
  32. if ($options{help}) {
  33.   print <<EOF;
  34. Usage: $0 <options> input_file.(png|jpg|gif|bmp)
  35. Options:
  36.   --resize WxH    : resize the input image to WxH pixels (W and H must be multiples of 8)
  37.   --dither type   : use dithering (0: None, 1: Riemersma, 2: Floyd-Steinberg)
  38.   --tiles type    : tiles 0:local dithering 1:global dithering
  39.   --colors N      : number of colors in the output image (default to 60)
  40.   --sample file   : write a sample image to file
  41.   --help          : prints this message
  42. EOF
  43.   exit(0);
  44. }
  45.  
  46. my $filename = $ARGV[0];
  47.  
  48.  
  49. #################################################
  50. # Options checking
  51. #################################################
  52. die("No filename given") unless defined $filename;
  53.  
  54. die("Number of colors (-colors) must be between 1 and 60")
  55.   unless ($options{colors} > 0 and $options{colors} < 61);
  56.  
  57.  
  58. #################################################
  59. # Reads a file from disk and use it to create an ImageMagick object.
  60. sub open_image {
  61.   my $file = shift;
  62.   my $image = Image::Magick->new;
  63.   open(IMG, $file) or die "Can't open $file : $!";
  64.   close(IMG);
  65.   $image->Read($file);
  66.   return $image;
  67. }
  68.  
  69.  
  70. #################################################
  71. # Resize an ImageMagick image.
  72. sub resize {
  73.   my $image = shift;
  74.   my %parameters = @_;
  75.   my $ret = $image->Resize(%parameters);
  76.   die $ret if $ret;
  77. }
  78.  
  79.  
  80. #################################################
  81. # Breaks an ImageMagick image into an array of 8x8 ImageMagick images.
  82. sub image_to_tiles {
  83.   my $image = shift;
  84.  
  85.   my $width  = $image->Get('width');
  86.   my $height = $image->Get('height');
  87.  
  88.   my $nb_tiles_x = $width / 8;
  89.   my $nb_tiles_y = $height / 8;
  90.  
  91.   my @tiles;
  92.   my $count = 0;
  93.   for my $y (0 .. $nb_tiles_y - 1) {
  94.     for my $x (0 .. $nb_tiles_x - 1) {
  95.       my $x_offset = $x*8;
  96.       my $y_offset = $y*8;
  97.       my $copy = $image->Clone();
  98.       $copy->Crop("8x8+$x_offset+$y_offset");
  99.       push @tiles, $copy;
  100.     }
  101.   }
  102.  
  103.   return \@tiles;
  104. }
  105.  
  106.  
  107. #################################################
  108. # Computes a 3-dimensional vector from the histogram of a tile.
  109. sub tile_to_vector {
  110.   my $image = shift;
  111.  
  112.   my @vec = (0, 0, 0);
  113.   my @hist = $image->Histogram();
  114.   while (my @rgboc = splice(@hist, 0, 5)) {
  115.     for my $i (1 .. $rgboc[4]) {
  116.       $vec[0] += $rgboc[0];
  117.       $vec[1] += $rgboc[1];
  118.       $vec[2] += $rgboc[2];
  119.     }
  120.   }
  121.  
  122.   return @vec;
  123. }
  124.  
  125.  
  126. #################################################
  127. # Partition a set of tiles into k clusters. k is the number of palettes needed to
  128. # hold the desired number of colors. The tiles are sorted according to the k-means
  129. # algorithm.
  130. sub tiles_into_clusters {
  131.   my $tiles       = shift;
  132.   my $nb_clusters = shift;
  133.  
  134.   my (@orfdata, @mask, @weight);
  135.  
  136.   @weight = (1, 1, 1);
  137.  
  138.   my $ind = 0;
  139.   for my $tile (@{ $tiles }) {
  140.     my @vec = tile_to_vector($tile);
  141.     push @orfdata, \@vec;
  142.     $mask[$ind] = [ 1, 1, 1 ];
  143.     $ind++;
  144.   }
  145.  
  146.   my ($clusters, $error, $found) = kcluster(
  147.     nclusters => $nb_clusters,
  148.     transpose => 0,
  149.     npass     => 100,
  150.     method    => 'a',
  151.     dist      => 'e',
  152.     data      => \@orfdata,
  153.     mask      => \@mask,
  154.     weight    => \@weight,
  155.   );
  156.  
  157.   return $clusters;
  158. }
  159.  
  160.  
  161. #################################################
  162. # Build an ImageMagick image with 512 colors (similar to the palette of the MegaDrive).
  163. sub build_512_colors_image {
  164.   my $image = Image::Magick->new;
  165.   $image->Set(size => '512x1');
  166.   $image->ReadImage('canvas:white');
  167.  
  168.   my @t = (0, 49, 87, 119, 146, 174, 206, 255);
  169.  
  170.   my $x = 0;
  171.   for my $r (@t) {
  172.     for my $g (@t) {
  173.       for my $b (@t) {
  174.         $image->SetPixel(x => $x++, y => 0, color => [ $r/255, $g/255, $b/255 ]); # normalized colors
  175.       }
  176.     }
  177.   }
  178.  
  179.   return $image;
  180. }
  181.  
  182.  
  183. #################################################
  184. # Converts an RGB value to two bytes (MD format).
  185. sub rgb_to_md_color {
  186.   my $r = shift;
  187.   my $g = shift;
  188.   my $b = shift;
  189.  
  190.   my $sb = $quantum - 3;
  191.   my $bin = sprintf(
  192.     '0000%03b0%03b0%03b0',
  193.     $b >> $sb,
  194.     $g >> $sb,
  195.     $r >> $sb,
  196.   );
  197.  
  198.   my $hex = sprintf('%04X', oct("0b$bin"));
  199.   return $hex;
  200. }
  201.  
  202.  
  203. #################################################
  204. # Return the index of a color in a palette, or add the color to the palette
  205. # if it hasn't been added yet.
  206. sub add_color_to_palette {
  207.   my $palette = shift;
  208.   my $color   = shift;
  209.  
  210.   return $palette->{colors}->{$color} if defined $palette->{colors}->{$color};
  211.  
  212.   $palette->{colors}->{$color} = ++$palette->{maxindex};
  213. }
  214.  
  215.  
  216. #################################################
  217. sub tiles_to_asm {
  218.   my $tiles       = shift;
  219.   my $clusters    = shift;
  220.   my $nb_clusters = shift;
  221.  
  222.   open(OUTPUT, '>', $options{output}) or die "Can't open $options{output} for writing : $!";
  223.  
  224.   my @palettes;
  225.   my %color_indexes;
  226.  
  227.   # Do not add the transparent color (index 0) here, so there is no conflict with the black color.
  228.   for my $num (0 .. $nb_clusters-1) {
  229.     $palettes[$num]->{colors} = {};
  230.     $palettes[$num]->{maxindex} = 0;
  231.   }
  232.  
  233.   for my $ind (0 .. $#$tiles) {
  234.     my $t = $tiles->[$ind];
  235.     my $palette_num = $clusters->[$ind];
  236.     my @pixels = $t->GetPixels(map => 'RGB', width => 8, height => 8);
  237.     print OUTPUT "| Tile #$ind ; palette #$palette_num\n";
  238.     while (my @l = splice(@pixels, 0, 8*3)) {
  239.       print OUTPUT ".long 0x";
  240.       while (my @p = splice(@l, 0, 3)) {
  241.         my $color = rgb_to_md_color($p[0], $p[1], $p[2]);
  242.         my $color_index = sprintf('%X', add_color_to_palette($palettes[$palette_num], $color));
  243.         print OUTPUT $color_index;
  244.       }
  245.       print OUTPUT "\n";
  246.     }
  247.     print OUTPUT "\n";
  248.   }
  249.  
  250.   for my $p (@palettes) {
  251.     if (keys(%{ $p->{colors} }) > 15) {
  252.       die "Bug : a palette contains more than 15 colors, this shouldn't happen.";
  253.     }
  254.   }
  255.  
  256.   for my $ind (0 .. $#palettes) {
  257.     my $p = $palettes[$ind];
  258.     print OUTPUT "| Palette #$ind\n";
  259.     print OUTPUT ".word 0x0000\n";
  260.     for my $color (sort { $p->{colors}->{$a} <=> $p->{colors}->{$b} } keys %{ $p->{colors} }) {
  261.       print OUTPUT ".word 0x$color\n";
  262.     }
  263.     for my $i ($p->{maxindex}+1 .. 15) {
  264.       print OUTPUT ".word 0x0000 | unused\n";
  265.     }
  266.     print OUTPUT "\n";
  267.   }
  268.  
  269.   close OUTPUT;
  270. }
  271.  
  272.  
  273.  
  274. #################################################
  275. #                Main script                    #
  276. #################################################
  277. $|++; # unbuffered stdout
  278. my $image = open_image($filename);
  279. my $colorsimage = build_512_colors_image();
  280.  
  281. if ($options{resize}) {
  282.   resize($image, geometry => $options{resize}, filter => 'Lanczos');
  283. }
  284.  
  285. my $width  = $image->Get('width');
  286. my $height = $image->Get('height');
  287.  
  288. if ($width % 8 or $height % 8) {
  289.   die "The width and height of the image must be a multiple of 8";
  290. }
  291.  
  292. my $nb_tiles_x = $width / 8;
  293. my $nb_tiles_y = $height / 8;
  294. my $nb_tiles = $nb_tiles_x * $nb_tiles_y;
  295. print "Ouput : ${width}x$height pixels, ${nb_tiles_x}x$nb_tiles_y tiles.\n";
  296.  
  297. my $nb_clusters = ceil($options{colors} / 15);
  298.  
  299. print "Sorting tiles... ";
  300. my $tiles_ref = image_to_tiles($image);
  301. my $clusters = tiles_into_clusters($tiles_ref, $nb_clusters);
  302. print "done.\n";
  303.  
  304. my @montages;
  305. for my $i (1 .. $nb_clusters) {
  306.   push @montages, Image::Magick->new;
  307. }
  308.  
  309. my @tid;
  310. for my $ind (0 .. $#$tiles_ref) {
  311.   my $num = $clusters->[$ind];
  312.   my $im = $montages[$num];
  313.   push @$im, $tiles_ref->[$ind];
  314.   push @tid, $#$im;
  315. }
  316.  
  317. $options{ditherm} = "Riemersma";
  318. $options{ditherm} = "Floyd-Steinberg" if ($options{dither} == 2);
  319.  
  320. print "Global quantization... ";
  321. my @dimage;
  322. my @montages_flat;
  323. for my $i (0 .. $#montages) {
  324.   $montages_flat[$i] = $montages[$i]->Montage(geometry => '8x8', tile => '1x');
  325.   $montages_flat[$i]->Quantize(colors => 15, dither => $options{dither}, "dither-method" => $options{ditherm});
  326.   $montages_flat[$i]->Remap(image => $colorsimage, dither => 0);
  327.  
  328.   $dimage[$i] = $image->Clone();
  329.   $dimage[$i]->Remap(image => $montages_flat[$i], dither => $options{dither}, "dither-method" => $options{ditherm});
  330. }
  331.  
  332. print " done.\n";
  333.  
  334. print "Remapping palette of each tile :\n";
  335. my @stars = ('*') x 51;
  336. print "0%                                             100%\n";
  337. for my $y (0 .. $nb_tiles_y - 1) {
  338.   for my $x (0 .. $nb_tiles_x - 1) {
  339.     my $ind = $y*$nb_tiles_x+$x;
  340.     my $num = $clusters->[$ind];
  341.     my $im =  $dimage[$num];
  342.     my $x_offset = $x*8;
  343.     my $y_offset = $y*8;
  344.     if ($options{tiles} == 0) { #local
  345.       $im = $montages_flat[$num];
  346.       $x_offset = 0;
  347.       $y_offset = $tid[$ind]*8;
  348.     }
  349.     my $copy = $im->Clone();
  350.     $copy->Crop("8x8+$x_offset+$y_offset");
  351.     $tiles_ref->[$ind] = $copy;
  352.     print shift(@stars) unless (!@stars or $ind % int($nb_tiles/50));
  353.   }
  354. }
  355. map { print $_ } @stars;
  356. print "\n";
  357.  
  358. if ($options{sample}) {
  359.   my $final_image = Image::Magick->new;
  360.   for my $tile (@{ $tiles_ref }) {
  361.     push @$final_image, $tile;
  362.   }
  363.   my $q = $final_image->Montage(geometry => '8x8', tile => "${nb_tiles_x}x$nb_tiles_y");
  364.   $q->Write($options{sample});
  365.   print "Sample image written to $options{sample}\n";
  366. }
  367.  
  368. if ($options{output}) {
  369.   tiles_to_asm($tiles_ref, $clusters, $nb_clusters);
  370.   print "ASM code written to $options{output}\n";
  371. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement