#!/usr/local/bin/perl # THIS IS OUTDATED AND WILL NOT RUN WITH CURRENT GIMP VERSIONS! ###################################################################### # This program is an automatic way of scaling and tiling lots of # images in order to create an index image. # # Todo: # - Add more layout algorithms. (fixed grid, dynamic grid, better # orphan handling) # - Add more decoration algorithms (e.g. negative strip, slides, # sunken windows). # - Create a way of supplying a template HTML index file into which # the image map will be added. # - Create a way of supplying a template HTML file in which # full size images will be displayed. This file may include # image annotations that come from the input file. # - Define format of input file. This file should include # annotations on/below images, comments in the full size html # file, perhaps baloon text on top of images. # - Make all parameters of tiling algorithms into options. # - Change background of simple-shadow algorithm. # - Option for either keeping image in gimp, or flattening and # saving to disk. # - Add a modular way of adding new layout and decoration algorithms. # # Availability: # - You can always find the latest version of this script at # http://imagic.weizmann.ac.il/~dov/gimp/perl # # Author: # Dov Grobgeld # # Bugs: # - -padx px means at least px and not exactly px. This should # be fixed. # # Version: 0.11 ###################################################################### use Gimp; # Defaults $max_height = 64; $layout_width = 600; $bgcolor = [194,194,194]; $decoration = 'drop-shadow'; $gutter_x = 15; $gutter_y = 15; $pad_x = 20; $pad_y = 20; chop($PWD = `pwd`); # Routine for parsing the input format sub get_next_record { return (undef,undef) if eof(IN); local $_ = ; /^(\S+)\s+(.*)/; return ($1,$2); } # Load an image and return its id sub load_img { my($fn) = shift; my($max_height) = shift; $fn = "$PWD/$fn" unless $fn=~ m:^/:; my ($img); $img = gimp_file_load(RUN_NONINTERACTIVE,$fn,$fn); my ($w, $h) = ($img->width, $img->height); # Resize the img if ($h > $max_height) { my $scale = $max_height/$h; my $new_w = int($w * $scale+0.5); my $new_h = int($h * $scale+0.5); # print "New w,h = ($new_w, $new_h)\n"; gimp_image_scale($img, $new_w, $new_h); } return $img; } ###################################################################### # hbox/vbox algorithm. This algorithm is much like a text flowing # algorithm with centered paragraphs. ###################################################################### sub hbox_vbox_add_row { my($imgs, $layout_width, $row_start_idx, $row_end_idx, $ypos, $gutter_x) = @_; my(@row_layout); # Calculate teh row width my $row_width = 0; my $row_height = 0; for $i ($row_start_idx..$row_end_idx) { my($w,$h) = ($imgs->[$i]->width, $imgs->[$i]->height); $row_width += $gutter_x + $w; $row_height = $h if $h > $row_height; } $row_width -= $gutter_x; # Do the layout my $xpos = ($layout_width-$row_width)/2; for $i ($row_start_idx..$row_end_idx) { my $y = $ypos + ($row_height-$imgs->[$i]->height)/2; push(@row_layout, [$imgs->[$i], $xpos, $y]); $xpos+= $gutter_x + $imgs->[$i]->width; } return(@row_layout); } sub hbox_vbox_create_layout { my($imgs) = @_; my(@layout); # The positions of all the images # A simple maximal row algorithm my ($row_start_idx, $row_end_idx); my $ypos = $pad_y; my $xpos = 0; print "imsg->[0] = $imgs->[0]\n"; my $row_height = $imgs->[0]->height(); my $i; foreach $img_idx (0..@$imgs-1) { print "Layouting image #$img_idx\n"; my $w = $imgs->[$img_idx]->width(); my $h = $imgs->[$img_idx]->height(); # print "row_width = $row_width\n"; # Check for the creation of a new row if ($row_width + $pad_x * 2 + $w > $layout_width) { push(@layout, hbox_vbox_add_row($imgs, $layout_width, $row_start_idx, $row_end_idx, $ypos, $gutter_x)); $total_width = $row_width if $row_width > $total_width; # Move to next row $ypos+= $gutter_y + $row_height; # Zero out various things $row_start_idx = $row_end_idx+1; $row_end_idx = $row_start_idx; $row_width = 0; if ($row_start_idx < @imgs) { $row_width = $imgs->[$row_start_idx]->width; $row_height = $imgs->[$row_start_idx]->height; $xpos = 0; } } else { $row_width += $gutter_x + $w; $row_end_idx = $img_idx; $row_height = $h if $h > $row_height; } } if ($row_start_idx < @imgs) { push(@layout, hbox_vbox_add_row($imgs, $layout_width, $row_start_idx, $row_end_idx, $ypos, $gutter_x)); $total_width = $row_width if $row_width > $total_width; $ypos+= $row_height; } $total_width = $layout_width; $total_height = $ypos + $pad_y; return ($total_width, $total_height, \@layout); } ###################################################################### # The decoration_drop_shadow creates one layer with the images # and puts a drop shadow behind them. ###################################################################### sub decoration_drop_shadow { my($layout) = shift; $shadow_xoffs = 7; $shadow_yoffs = 7; # Put them on a row $tiled_img = gimp_image_new($total_width, $total_height, RGB); $tiled_drw = gimp_layer_new($tiled_img, $total_width, $total_height, RGB_IMAGE, "Tiled", 100, NORMAL_MODE); $tiled_shadow = gimp_layer_new($tiled_img, $total_width, $total_height, RGB_IMAGE, "Shadow", 50, NORMAL_MODE); $tiled_background = gimp_layer_new($tiled_img, $total_width, $total_height, RGB_IMAGE, "Background", 100, NORMAL_MODE); # Create masks $tiled_drw_msk = $tiled_drw->create_mask(1); $tiled_shadow_msk = $tiled_shadow->create_mask(1); # Make sure respective images have alpha channels $tiled_drw->layer_add_alpha(); $tiled_shadow->layer_add_alpha(); # Connect masks to respective layers $tiled_img->add_layer_mask($tiled_drw, $tiled_drw_msk); $tiled_img->add_layer_mask($tiled_shadow, $tiled_shadow_msk); # Fill all the layers with some contents gimp_palette_set_background([128,128,128]); $tiled_drw->fill(BG_IMAGE_FILL); gimp_palette_set_background($bgcolor); $tiled_background->fill(BG_IMAGE_FILL); if ($bgpattern) { print "Setting pattern\n"; gimp_patterns_set_pattern($bgpattern); $tiled_img->bucket_fill($tiled_background, PATTERN_BUCKET_FILL, NORMAL, 100, 0, FALSE, 0,0); } gimp_palette_set_background([0, 0, 0]); # Shadow color $tiled_shadow->fill(BG_IMAGE_FILL); # Add all the layers to the image $tiled_img->add_layer($tiled_background,-1); $tiled_img->add_layer($tiled_shadow,-1); $tiled_img->add_layer($tiled_drw,-1); gimp_display_new($tiled_img); my $xpos = 0; # Set color for drawing in mask gimp_palette_set_background([255, 255, 255]); for $ly_idx (0..@$layout-1) { my ($img, $xpos, $ypos) = @{$layout->[$ly_idx]}; ($layer) = @{gimp_image_get_layers($img)}; my($w,$h) = ($img->width, $img->height); $img->selection_all(); $img->edit_copy($layer); $tiled_img->rect_select($xpos, $ypos, $w, $h, 0, 0, 0); $tiled_img->edit_paste($tiled_drw, 0) ->floating_sel_anchor; # why is the selection cleared? $tiled_img->rect_select($xpos, $ypos, $w, $h, 0, 0, 0); $tiled_img->edit_fill($tiled_drw_msk); # why is the selection cleared? $tiled_img->rect_select($xpos+$shadow_xoffs, $ypos+$shadow_yoffs, $w, $h, 0, 0, 0); $tiled_img->edit_fill($tiled_shadow_msk); $tiled_img->selection_none(); } # Blur the shadow plug_in_gauss_rle(1, $tiled_img, $tiled_shadow_msk, 7, 1, 1); # Apply the shadow mask $tiled_img->remove_layer_mask($tiled_shadow, APPLY); } sub decoration_sunken_windows { my($layout) = shift; $shadow_xoffs = 7; $shadow_yoffs = 7; # Create needed image and layers $tiled_img = gimp_image_new($total_width, $total_height, RGB); $tiled_drw = gimp_layer_new($tiled_img, $total_width, $total_height, RGB_IMAGE, "Tiled", 100, NORMAL_MODE); $tiled_punch_layer = gimp_layer_new($tiled_img, $total_width, $total_height, RGB_IMAGE, "Punched", 100, NORMAL_MODE); $tiled_punch_stencil = gimp_layer_new($tiled_img, $total_width, $total_height, RGB_IMAGE, "Punch mask", 100, NORMAL_MODE); # Create masks $tiled_punch_mask = $tiled_punch_layer->create_mask(0); # Make sure respective images have alpha channels $tiled_punch_layer->layer_add_alpha(); # Connect masks to respective layers $tiled_img->add_layer_mask($tiled_punch_layer, $tiled_punch_mask); # Fill all the layers with some contents gimp_palette_set_background([128,128,128]); $tiled_drw->fill(BG_IMAGE_FILL); gimp_palette_set_background($bgcolor); $tiled_punch_layer->fill(BG_IMAGE_FILL); if ($bgpattern) { print "Setting pattern\n"; gimp_patterns_set_pattern($bgpattern); $tiled_img->bucket_fill($tiled_punch_layer, PATTERN_BUCKET_FILL, NORMAL, 100, 0, FALSE, 0,0); } gimp_palette_set_background([255, 255, 255]); # Punch stencil $tiled_punch_stencil->fill(BG_IMAGE_FILL); # Add all the layers to the image $tiled_img->add_layer($tiled_punch_stencil,-1); $tiled_img->add_layer($tiled_drw,-1); $tiled_img->add_layer($tiled_punch_layer,-1); gimp_display_new($tiled_img); my $xpos = 0; # Set color for drawing in mask gimp_palette_set_background([0, 0, 0]); for $ly_idx (0..@$layout-1) { my ($img, $xpos, $ypos) = @{$layout->[$ly_idx]}; ($layer) = @{gimp_image_get_layers($img)}; my($w,$h) = ($img->width, $img->height); $img->selection_all(); $img->edit_copy($layer); $tiled_img->rect_select($xpos, $ypos, $w, $h, 0, 0, 0); $tiled_img->edit_paste($tiled_drw, 0) ->floating_sel_anchor; # why is the selection cleared? $bw = 3; $tiled_img->rect_select($xpos-$bw, $ypos-$bw, $w+2*$bw, $h+2*$bw, 0, 0, 0); $tiled_img->edit_fill($tiled_punch_stencil); # why is the selection cleared? $tiled_img->selection_none(); $tiled_img->rect_select($xpos, $ypos, $w, $h, 0, 0, 0); $tiled_img->edit_fill($tiled_punch_mask); $tiled_img->selection_none(); } # Blur the punch stencil plug_in_gauss_rle(1, $tiled_img, $tiled_punch_stencil, 7, 1, 1); # Bump map plug_in_bump_map(1, $tiled_img, $tiled_punch_layer, $tiled_punch_stencil, 135, 45, 4,0,0,0,0,1,0, SPHERICAL); # Apply the shadow mask $tiled_img->remove_layer_mask($tiled_punch_layer, APPLY); } sub delete_images { my $imgs = shift; foreach $img (@$imgs) { $img->delete(); } } ###################################################################### # Net is where main continues after it has connected to # gimp. ###################################################################### sub net { open(IN, shift(@ARGV)); # Read the file list while(($fn,$descr) = get_next_record()) { last unless $fn; next unless -e $fn; print "fn = $fn\n"; push(@imgs, load_img($fn, $max_height)); push(@filenames, $fn); } print "Done reading ", scalar(@imgs), " images\n"; # Now create a layout of the images. The layout algorithm # should really be parameterized. my ($total_width, $total_height, $layout) = hbox_vbox_create_layout(\@imgs); print "total_size = ($total_width $total_height)\n"; # This is an example decoration. Others will be created in the future if ($decoration eq "drop-shadow") { decoration_drop_shadow($layout); } elsif ($decoration eq "sunken-windows") { decoration_sunken_windows($layout); } else { delete_images(\@imgs); die "Unknown decoration $decoration!\n"; } $tiled_img->flatten() if $do_flatten; gimp_displays_flush(); # Now create the index file if ($index_file) { open(INDEX, ">$index_file"); for $idx (0..@filenames-1) { my ($img, $xpos, $ypos) = @{$layout->[$idx]}; my($w,$h) = ($img->width, $img->height); printf INDEX "%s %.0f %.0f %.0f %.0f %s\n", $filenames[$idx], $xpos, $ypos, $xpos+$w, $ypos+$h, $descr; } close(INDEX); } # Clean up delete_images(\@imgs); } # Parse command line arguments while($_ = $ARGV[0], /^-/) { shift; /^-help/ and do { print <<__; exit; }; make-img-map - Make an image map from a list of images Syntax: gimp-make-img-map [-max_height mh] [-htmlindex hi] [-layoutwidth lw] [-flatten] [-bgcolor clr] [-bgpattern ptn] list Description: gimp-make-img-map communicates with Gimp through the Perl Net-Server and automizes the process of combining a list of images into an image map for use e.g. within a HTML page. Options: -max_height mh Set max height of images. (Default $max_height) -index if Create an index file mapping filename to bounding box coordinates in output image, where if is the name of the index file. The index file may e.g. be translated by a subsequent program into a html index file. -layoutwidth lw Set total width of layout. (Default $layout_width) -flatten Flatten the final image. -bgcolor Set bg color. -bgpattern Set bg pattern. Overrides the bgcolor. -padx px Extra space around all images in x-direction. (Default $pad_x) -pady py Extra space around all images in y-direction. (Default $pad_y) -gutterx gx Space between images in x-direction. (Default $gutter_x) -gutterx gy Space between images in y-direction. (Default $gutter_y) -decoration alg Choose algorithm for drawing the decoration in the layout. Known algorithms are: drop-shadow sunken-windows Default is 'drop_shadow'. __ /^-max_height/ and do { $max_height = shift; next; }; /^-index/ and do { $index_file = shift; next; }; /^-layoutwidth/ and do { $layout_width = shift; next; }; /^-flatten/ and do { $do_flatten++; next; }; /^-bgcolor/ and do { $background = shift; next; }; /^-bgpattern/ and do { $bgpattern = shift; next; }; /^-decoration/ and do { $decoration = shift; next; }; /^-gutterx/ and do { $gutter_x = shift; next; }; /^-guttery/ and do { $gutter_y = shift; next; }; /^-padx/ and do { $pad_x = shift; next; }; /^-pady/ and do { $pad_x = shift; next; }; die "Unknown option $_!\n"; } # Translate background into a color according to the X11 color dbase. exit main;