Gimp

 view release on metacpan or  search on metacpan

examples/image_tile  view on Meta::CPAN

    # Create a cache of all of the cell samples from the original image for
    # this tile only.
    for(my $xcell=0;$xcell<$xcells;$xcell++) {
      for(my $ycell=0;$ycell<$ycells;$ycell++) {
	$ocells[$xcell][$ycell] =
	  substr($$oimage,
		 (($x*$xcells + $xcell)*$ytiles*$ycells+
		  $y*$ycells +
		  $ycell) * 3, 3);
      }
    }
    my $subimg;
    my $weight;
    # Loop through all available sub-images and find best fit for this
    # tile.
    while(($subimg,$weight)=each %wt_cache) {
      my $match = 0;
      for(my $xcell=0;$xcell<$xcells;$xcell++) {
	my $subfile = $subimg;
	for(my $ycell=0;$ycell<$ycells;$ycell++) {
	  # Cell samples are stored as packed 3-byte values
	  my($o1,$o2,$o3) = unpack 'CCC', $ocells[$xcell][$ycell];
	  my($n1,$n2,$n3) = unpack 'CCC',
		substr($tile_cache{$subfile},($xcell*$ycells+$ycell)*3,3);
	  # 2 methods of comparing: by RGB and by HSV. HSV seems to
	  # give a more accurate map, as it stresses the matching of light
	  # and darkness. We do some weighting of the HSV match so that
	  # we don't care about hue as much if saturation or value is
	  # low, and we don't care about saturation as much if value is low
	  # The net effect is that for a black pixel, you don't care
	  # what color hue tells you it is, because it's always black
	  my $c3_delta = abs($o3 - $n3);
	  my $c2_delta;
	  my $c1_delta;
	  if ($DO_HSV) {
	    # c1 == H, c2 == S, c3 == V
	    $c2_delta = abs($o2 - $n2) * $o3 / 255;
	    $c1_delta = hue_dist($o1,$n1)* 2 * ($o3*$o2/(255**2));
	  } else {
	    # c1 == R, c2 == G, c3 == B
	    $c2_delta = abs($o2 - $n2);
	    $c1_delta = abs($o1 - $n1);
	  }
	  # Keep a running score of the differences between samples for this
	  # sub-image vs. this tile from the orginal
	  $match += $c1_delta + $c2_delta + $c3_delta;
	}
      }
      # Weight for image duplicates.
      $match += $wt_cache{$subimg};

      if (!defined($minmatch) || $match < $minmatch) {
	$minmatch = $match;
	$matchid = $subimg;
      }
    }
    if (!defined($matchid)) {
      die("image_tile: No subimages selected!");
    }
    # Actually insert the selected image.
    overlay_image($drawable, $matchid,
		  $xtilewidth*$x, $ytileheight*$y,
		  $xtilewidth, $ytileheight);
    $wt_cache{$matchid} += $dupweight;
  }
  # Finish up.
  undef $db;
  untie %tile_cache;
  undef $wdb;
  untie %wt_cache;
  unlink($wt_file);
  unlink($cache_file) if $cleanup;
  Gimp::Progress->update(1);
  $image->undo_enable;
  ();
};

# Take IMAGE, XCELLS, YCELLS, TARGET_ASPECT.
# Works destructively on IMAGE, and returns a list of anon-lists which
# contain the color samples for the given IMAGE.
sub get_image_cells {
  my ($img, $xcells, $ycells, $target_aspect, $start_complete, $end_complete) = @_;
  # print "Target aspect: $target_aspect\n";
  my $file = $img->get_filename;
  # print "$file: ";
  my $width = $img->width;
  # print "width: $width ";
  my $height = $img->height;
  # print "height: $height\n";
  my $cells = "\0\0\0" x ($xcells * $ycells);
  return () if $width < 1 || $height < 1;

  # First crop to fit tiles
  match_aspect($img, $target_aspect, $width, $height);

  # Now, scale down to xcells by ycells for color sampling
  # NOTE: We will re-open this image later if it is chosen.
  #       This scaling is just to get color samples.
  $img->scale($xcells, $ycells);
  my $draw = $img->get_active_drawable;
  for(my $x=0;$x<$xcells;$x++) {
    if (defined($start_complete)) {
      Gimp::Progress->update(($start_complete+
			    ($end_complete-$start_complete)*$x/$xcells)/100);
    }
    for(my $y=0;$y<$ycells;$y++) {
      my $color = eval { $draw->pick_color($x, $y, FALSE, TRUE, 1.0) };
      next if ($@);
      my @c;
      if ($DO_HSV) {
	@c = rgb2hsv(@$color);
      } else {
	@c = @$color;
      }
      substr($cells,($x*$ycells+$y)*3,3) = pack('CCC',@c);
    }
  }
  return \$cells;
}

# Take IMAGE, TARGET_ASPECT, WIDTH (of image), HEIGHT (of image)
# Crops IMAGE to match aspect ratio of TARGET_ASPECT.
sub match_aspect {
  my ($img, $target_aspect, $width, $height) = @_;
  my $aspect = $width/$height;
  if ($aspect < $target_aspect) {
    my $oldheight = $height;
    $height = int($width / $target_aspect);
    # print "Image was $width X $oldheight, cropping to $width X $height\n";
    $img->crop($width, $height, 0, int(($oldheight-$height) / 2) );
  } elsif ($aspect > $target_aspect) {
    my $oldwidth = $width;
    $width = int($target_aspect * $height);
    # print "Image was $oldwidth X $height, cropping to $width X $height\n";
    $img->crop($width, $height, int(($oldwidth-$width) / 2), 0);
  }
}

# Take DRAWABLE, INFO, X, Y, WIDTH, HEIGHT
# Opens image referenced by INFO->{name} and scale/crop to fit in rectagnle
# described by X,Y,WIDTH,HEIGHT
sub overlay_image {
  my ($draw, $file, $x, $y, $width, $height) = @_;
  my $img = Gimp->file_load($file, $file);
  my $subwidth = $img->width;
  my $subheight = $img->height;
  match_aspect($img, $width/$height, $subwidth, $subheight);
  $img->scale($width, $height);
  $img->get_active_drawable->edit_copy;
  my $baseimg = $draw->get_image;
  $baseimg->select_rectangle(CHANNEL_OP_REPLACE, $x, $y, $width, $height);
  $draw->edit_paste(FALSE)->floating_sel_anchor;
  $img->delete;
}

# Take a Red, Green, Blue color value and return Hue, Saturation and Value
# RGB and HSV data should be in the range 0-255 (note Hue is usually
# represented as 0-360, but here is scaled to be 0-255).
sub rgb2hsv {
  my $r = shift;
  my $g = shift;
  my $b = shift;
  my($h,$s,$v);
  my $min = undef;
  my $max = 0;
  foreach my $color ($r, $g, $b) {
    $min = $color if !defined($min) || $min>$color;
    $max = $color if $color > $max;
  }
  $v = $max;
  $s = $max?int(($max-$min)/$max*255+0.5):0;
  if ($s == 0) {
    $h = 0;
  } else {
    my $d = $max - $min;
    if ($r == $max) {
      $h = ($g-$b)/$d;
    } elsif ($g == $max) {
      $h = 2+($b-$r)/$d;
    } else {
      $h = 4+($r-$g)/$d;
    }
    # This:
    # $h *= 60;
    # $h += 360 if $h < 0;
    # $h *= (256/360);
    # , simplified is this:
    $h= int(($h+($h<0?6:0)) * 128 / 3 + 0.5);
  }
  return ($h,$s,$v);
}

# Caclulate the "distance" between to HSV hue values in the range 0-255.
sub hue_dist {
  my $h1 = shift;
  my $h2 = shift;
  my $d = abs($h1-$h2);
  return($d>128?(256-$d):$d);
}

exit main;
__END__



( run in 0.639 second using v1.01-cache-2.11-cpan-39bf76dae61 )