#!/usr/bin/perl # # m256 PerlMagick Composite Utility # # composite processing is performed to the sequence of an image. # A perlmagcik(Image-Magick) module and tk module are required for this perl script. # # This script creates a temporary file in $tmp_dir. # If $tmp_dir="", the directory where the script exists is used. # # This comment was described by automatic translation software. # # # use: perl m256pmcu.pl # # Author : mieki256 ( http://www.geocities.jp/mieki256jp/ ) # OS : WindowsXP Home SP2 # Perl : ActivePerl 5.8.8 build 819 # # 2003/03/13-2003/03/14 v0.01 # 2003/03/16 v0.02 add child process. # 2007/02/27 v0.03 composite preview bug fix. use Tk; use Image::Magick; use File::Basename; use MIME::Base64; # ---------- config # MS-Windows ? (0:not MS-Windows / 1:MS-Windows) $ms_windows = 1; # use font name $fontname1 = 'Terminal 10'; # default open directory $path_name = ""; # jpeg output quality (0-100) (Image-Magick default=75) $jpg_quality_value = 100; # png output quality ( zlib compression level(1-9) * 10 + filter type(0-7?) ) (Image-Magick default=75) $png_quality_value = 95; # open/save image file extension filter $imgfile_filter = [['Image Files', ['.png','.bmp','.tga','.gif','.jpg','.ppm']],['All Files','*',]]; @suffix_list = ('\.png','\.bmp','\.tga','\.gif','\.jpg','\.ppm'); # frame number init $frame_start = 1; $frame_end = 1; $frame_pre = 1; # preview image window default size $preview_w = 200; $preview_h = 200; # compose type list - column setting $compose_opt_w = 6; $compose_opt_column_minsize = 90; # compose type list @compose_opt_list = ( 'Over', 'In', 'Out', 'Atop', 'Xor', 'Plus', 'Minus', 'Add', 'Subtract', 'Difference', 'Multiply', 'Bumpmap', 'Copy', 'CopyRed', 'CopyGreen', 'CopyBlue', 'CopyMatte', 'Dissolve', 'Clear', 'Displace', 'Modulate', 'Threshold' ); # output image type @output_image_type = ( 'TrueColorMatte', 'TrueColor', 'GrayscaleMatte', 'Grayscale', 'PaletteMatte', 'Palette', 'ColorSeparationMatte', 'ColorSeparation', 'Bilevel', 'Optimize' ); # --- temporary image file name is "$tmp_dir$tmp_basefile[0-2].$ext" # temporary dir #$tmp_dir = "f:/home/perltk/"; $tmp_dir = ""; # temporary file base name $tmp_basefile = "____m256pmcu_tmp"; # temporary file extension. (support gif/xpm/ppm) # $ext = 'xpm'; $ext = 'ppm'; # temporary file useing. (temporary file extension is gif or xpm, a setup to OFF is possible.) $fg_fileload = 1; # ---------- config end $script_title = 'm256 PerlMagick Composite Utility v 0.03'; # temporary directory init $scriptname = $0; $tmp_dir = (dirname($scriptname).'/') if $tmp_dir eq ""; @file_kind = ('Foreground','Background','Composite '); @seqlist = (); # --- composite image output set value # Unknown about the effect of opacity. It seems not to operate. :-( $opacity_value = 100; $opacity_scale_display = 0; $matte_fg = 0; $adjoin_fg = 0; # --- composite image output set value end # child process work $child_exec = 0; $child_infile1 = ""; $child_infile2 = ""; $child_outfile = ""; $child_seq0 = 0; $child_seq1 = 0; $child_seq2 = 0; $child_startframe = 0; $child_endframe = 0; $child_compose_type = ''; $child_nowframe = 0; $child_ext_type = ''; # ---------- widget setting ---------- # Main Window $mw = MainWindow->new(); $mw->optionAdd( '*font' => $fontname1 ); $mw->title( $script_title ); $mw->protocol('WM_DELETE_WINDOW', \&endjob ); # Menu widget $menu = $mw->Menu(); $mw->configure( -menu => $menu ); $menu->cascade(-label => 'File', -under => 0); $menu->cascade(-label => 'Composite', -under => 0); $item1 = $menu->Menu(-tearoff => 'no'); $menu->entryconfigure("File", -menu => $item1); $item1->add('command', -label => "Foreground Image File Select", -under => 0, -command => [\&FileSelectLoad,0] ); $item1->add('command', -label => "Background Image File Select", -under => 0, -command => [\&FileSelectLoad,1] ); $item1->add('separator'); $item1->add('command', -label => "Composite Image File Select", -under => 0, -command => [\&FileSelectLoad,2] ); $item1->add('separator'); $item1->add('command', -label => "Exit", -under => 0, -command => \&endjob ); $item2 = $menu->Menu(-tearoff => 'no'); $menu->entryconfigure("Composite", -menu => $item2); $item2->add('command', -label => "Preview", -under=>0,-command=>\&CompositePreview); $item2->add('command', -label => "Execute", -under=>0,-command=>\&CompositeExecute); # --- North Frame # File select Entry widget $fr_fsel_master = $mw->Frame()->grid(-row=>0,-column=>0,-sticky=>'ew'); for ( $i=0; $i<3; $i++) { $fr_fsel[$i] = $fr_fsel_master->Frame(-relief => 'groove', -borderwidth => 2)->pack(-side => 'top', -padx => 4, -pady => 4, -expand => 'yes', -fill => 'x'); $fr_fsel[$i]->Label(-text=>$file_kind[$i])->pack(-side => 'left', -padx => 2, -pady => 2); $fsel_ent[$i] = $fr_fsel[$i]->Entry()->pack(-side => 'left', -padx => 2, -pady => 2, -expand => 'yes', -fill => 'x'); $fsel_ent[$i]->delete(0,'end'); $fr_fsel[$i]->Button(-text => 'select',-command=>[\&FileSelectLoad,$i])->pack(-side => 'left', -padx => 2, -pady => 2); $seq_chkbtn[$i] = $fr_fsel[$i]->Checkbutton(-text=>'sequence',-variable=>\$fg_seq[$i],-onvalue=>1,-offvalue=>0,-command=>[\&Sequence_Checkbutton,$i])->pack(-side => 'left', -padx => 2, -pady => 2); $seq_chkbtn[$i]->deselect; } # --- South Frame $fr_south = $mw->Frame()->grid(-row=>2,-column=>0,-sticky=>'ew'); # compose option Radiobutton widget $fr5 = $fr_south->Frame(-relief=>'ridge',-borderwidth=>2)->pack(-side =>'top',-padx=>2,-pady=>2); $fr5->Label(-text => 'compose type')->grid(-row => 0, -column => 0, -columnspan => $compose_opt_w + 1); $i = 0; $x = 0; $y = 1; foreach $opt (@compose_opt_list) { $radio[$i] = $fr5->Radiobutton(-text=>$opt,-variable=>\$opt_compose,-value=>$opt,-command=>sub { &SetStatusMessage("Set Compose Type = '$opt_compose'"); } ); $radio[$i]->grid(-row => $y, -column => $x, -sticky => 'w'); $i++; if (++$x > $compose_opt_w ) { $y++; $x = 0 }; } $radio[0]->select; for ( $i=0; $i<=$compose_opt_w; $i++ ) { $fr5->gridColumnconfigure($i, -minsize => $compose_opt_column_minsize ); } # opacity scale widget $fr_opa = $fr_south->Frame(-relief=>'ridge',-borderwidth=>2)->pack(-side =>'top',-padx=>2,-pady=>2,-ipadx=>3,-ipady=>3,-expand=>'no',-fill=>'none'); if ( $opacity_scale_display ) { $opa_scale = $fr_opa->Scale(-from=>0,-to=>100,-label=>'Opacity(An effect is unknown.)',-variable=>\$opacity_value,-bigincrement=>10,-length=>300,-orient=>'horizontal',-sliderlength=>10,-width=>10,-resolution=>1,-tickinterval=>10)->pack(-side=>'left',-expand=>'no',-fill=>'none'); $opa_scale->set(100); } # matte with $fr_opa->Checkbutton(-text=>'with Alpha channel',-variable=>\$matte_fg,-onvalue=>1,-offvalue=>0)->pack(-side => 'left', -padx => 2, -pady => 2)->select; # frame number Entry widget $fr4 = $fr_south->Frame(-relief=>'groove',-borderwidth=>2)->pack(-side=>'top',-padx=>2,-pady=>2,-expand=>'no',-fill=>'none'); $fr4->Label(-text => 'Frame : Start') ->pack(-side => 'left', -padx => 4, -pady => 4); $entry_frame_start = $fr4->Entry(-width => 8) -> pack(-side => 'left', -padx => 1, -pady => 4); $entry_frame_start->delete(0,'end'); $entry_frame_start->insert(0,$frame_start); $fr4->Label(-text => 'End') -> pack(-side => 'left', -padx => 4, -pady => 4); $entry_frame_end = $fr4->Entry(-width => 8) -> pack(-side => 'left', -padx => 1, -pady => 4); $entry_frame_end->delete(0,'end'); $entry_frame_end->insert(0,$frame_end); $fr4->Label(-text => ' / Preview')->pack(-side => 'left', -padx => 4, -pady => 4); $entry_frame_pre = $fr4->Entry(-width => 8) -> pack(-side => 'left', -padx => 1, -pady => 4); $entry_frame_pre->delete(0,'end'); $entry_frame_pre->insert(0,$frame_pre); $prebtn = $fr4->Button(-text => 'Composite Preview',-command=>\&CompositePreview)->pack(-side =>'left',-padx=>4,-pady=>4); $execbtn = $fr4->Button(-text => 'Composite Execute',-command=>\&CompositeExecute)->pack(-side =>'left',-padx=>4,-pady=>4); $exitbtn = $fr4->Button(-text => 'Exit',-command=>\&endjob)->pack(-side =>'left',-padx=>4,-pady=>4); &UpdateState_FrameNumberEntry(0); # StatusBar $status = $fr_south->Label(-textvariable=>\$status_msg,-relief => 'sunken',-borderwidth=>2,-anchor=>'w'); $status->pack(-side=>'top',-anchor=>'sw',-expand=>'yes',-fill=>'x'); # --- center Frame # Canvas widget $fr_canvas = $mw->Frame(-relief => 'groove', -borderwidth => 2)->grid(-row=>1,-column=>0,-sticky=>'news'); for ( $i=0; $i<3; $i++ ) { $fr_canvas->Label(-text=>$file_kind[$i])->grid(-row=>0,-column=>$i); $canvas[$i] = $fr_canvas->Scrolled('Canvas', -width=>$preview_w, -height=>$preview_h,-scrollbars=>'se',-relief=>'groove',-borderwidth=>2,-background=>'white')->grid(-row=>1,-column=>$i,-sticky=>'news'); $canvas[$i]->create('text',$preview_w/2,$preview_h/4,text=>'Not Load',-font=>$fontname1); $fr_canvas->gridColumnconfigure($i,-weight=>1); } $fr_canvas->gridRowconfigure(1,-weight=>1); $mw->gridColumnconfigure(0,-weight=>2); $mw->gridRowconfigure(1,-weight=>2); # ---------- widget setting end ---------- &SetStatusMessage("Script name=[$0] / Temporary Dir=[$tmp_dir]"); MainLoop(); # ---------- subroutine ---------- # file select button callback function sub FileSelectLoad { my($kind) = @_; my $filename; my $opendir = $path_name; $opendir =~ s/\//\\/g if $ms_windows; &SetStatusMessage("Open Dir Path = $opendir"); if ( $kind != 2 ) { $filename = $mw->getOpenFile(-title=>"Select $file_kind[$kind] File", -filetypes =>$imgfile_filter,-initialdir=>$opendir ); } else { $filename = $mw->getSaveFile(-title=>"Select $file_kind[$kind] File", -filetypes =>$imgfile_filter,-initialdir=>$opendir ); } if( $filename ){ $path_name = dirname($filename).'/'; &SetStatusMessage("Select $file_kind[$kind] File."); &ReadAndPreviewImageWindow($kind,$filename) if $kind != 2; $sel_filename[$kind] = $filename; $fsel_ent[$kind]->delete(0,'end'); $fsel_ent[$kind]->insert(0, $filename); if ( $fg_seq[$kind] ) { &UpdateState_FrameNumberEntry(&CheckSequenceUsed); &GetFrameRange($filename); } } } # read image file , preview image window to canvas (in:[0,1,2],filename) sub ReadAndPreviewImageWindow { my($kind,$filename) = @_; if ( $filename ne $fsel_ent[$kind]->get ) { undef($img[$kind]) if $img[kind]; $img[$kind] = Image::Magick->new; $img[$kind]->Read($filename); &PreviewImageWindowToCanvas($kind); } } # preview image window to canvas (in:[0,1,2]) sub PreviewImageWindowToCanvas { my($kind) = @_; my $w = $canvas[$kind]->width; my $h = $canvas[$kind]->height; my($img_w,$img_h) = $img[$kind]->get('width','height'); # convert format. (gif/xpm/ppm/pgm) $img[$kind]->Set(magick=>$ext); # $img[$kind]->Set(compression=>'None'); $img[$kind]->Set(depth=>'8'); my $blobs = join("", $img[$kind]->ImageToBlob()); $blobs = encode_base64($blobs) if ( $ext eq 'gif' && $fg_fileload == 0); $tmpfile = GetTempFilename($kind); if ( $fg_fileload ) { open(OUT,"> $tmpfile") || die "Error : Can't open $tmpfile"; binmode OUT; print OUT $blobs; close OUT; } if ( $ext eq 'xpm' ) { if ( $fg_fileload ) { # Processing time is long. $image = $mw->Pixmap(-file=>$tmpfile); } else { # Processing time is long. $image = $mw->Pixmap(-data=>$blobs); } } else { if ( $fg_fileload ) { # A response is good. But a temporary file is required. $image = $mw->Photo(-format=>$ext,-file=>$tmpfile); } else { # -data option is supporting only gif format. ppm format cannot be used. $image = $mw->Photo(-format=>$ext,-data=>$blobs); } } $canvas[$kind]->configure(-width=>$w,-height=>$h,-scrollregion=>[0,0,$img_w,$img_h]); $canvas[$kind]->createImage(0,0,-image=>$image,-anchor=>'nw'); } # get temporary filename (in:[0,1,2]) sub GetTempFilename { my($kind) = @_; return "$tmp_dir$tmp_basefile$kind.$ext"; } # get filename informatin (in:filename / out:name,dir,ext,basename) sub GetFilenameInfo { my($filename) = @_; local($name,$path,$type) = fileparse($filename,@suffix_list); $name =~ /(.*)\d{4}/; local $base = $1; return ($name,$path,$type,$base); } # dirctory search , get sequence filename list , get frame range , frmae number entry update sub GetFrameRange { my($filename) = @_; my @dirlist = (); my @seqlist = (); my @tmplist = (); my @numlist = (); my $min; my $max; my $dirpath = dirname($filename); opendir(INDIR,"$dirpath"); @dirlist = readdir(INDIR); closedir(INDIR); my($name,$path,$type,$basename) = &GetFilenameInfo($filename); foreach (@dirlist) { my $t = $_; tr/A-Z/a-z/; if ( /$basename\d{4}$type/ ) { push(@tmplist,$t); } } @seqlist = sort sortsub_by_number @tmplist; # get min,max foreach (@seqlist) { if ( /.*(\d{4})\..*$/ ) { my $t = int($1); push(@numlist,int($t)); } } $min = $numlist[0]; $max = $min; foreach (@numlist) { if ($_ < $min) { $min = $_; } elsif ($max < $_) { $max = $_; } } my $fg = 0; if ( $min < $frame_start ) { $frame_start = $min; $entry_frame_start->delete(0,'end'); $entry_frame_start->insert(0,$frame_start); } elsif ( $frame_end < $max ) { $frame_end = $max; $entry_frame_end->delete(0,'end'); $entry_frame_end->insert(0,$frame_end); } } # sort subroutine : get number by filename , compare sub sortsub_by_number { $a =~ /.*(\d{4})\..*$/; my $ta = int($1); $b =~ /.*(\d{4})\..*$/; my $tb = int($1); if ( $ta < $tb ) { -1; } elsif ( $ta == $tb ) { 0; } elsif ( $ta > $tb ) { 1; } } # get min and max frame number by filename list sub GetMinMaxFrameNumber_ByFilenameList { } # sequence check button callback function sub Sequence_Checkbutton { my($kind) = @_; if ( $kind != 2 ) { if ( ( $fg_seq[0] || $fg_seq[1] ) && $fg_seq[2] == 0) { $seq_chkbtn[2]->select; } } else { if ( $fg_seq[2] == 0 ) { $seq_chkbtn[0]->deselect if $fg_seq[0]; $seq_chkbtn[1]->deselect if $fg_seq[1]; } } &UpdateState_FrameNumberEntry(&CheckSequenceUsed); my $filename = $fsel_ent[$kind]->get; if ( -e $filename && $fg_seq[$kind] ) { &GetFrameRange($filename); } } # frame number entry enable / disable sub UpdateState_FrameNumberEntry { my($fg) = @_; if ( $fg ) { $entry_frame_start->configure(-background=>'white',-state=>'normal'); $entry_frame_end->configure(-background=>'white',-state=>'normal'); $entry_frame_pre->configure(-background=>'white',-state=>'normal'); } else { $entry_frame_start->configure(-background=>'gray',-state=>'disabled'); $entry_frame_end->configure(-background=>'gray',-state=>'disabled'); $entry_frame_pre->configure(-background=>'gray',-state=>'disabled'); } } sub CheckSequenceUsed { for ( $i=0; $i<3; $i++) { return(1) if $fg_seq[$i]; } 0; } # composite execute button callback function sub CompositeExecute { return if &Check_CompositeChildProcessExec; &CompositeExecuteChild($fsel_ent[0]->get,$fsel_ent[1]->get,$fsel_ent[2]->get,$fg_seq[0],$fg_seq[1],$fg_seq[2],$frame_start,$frame_end,$opt_compose); } sub CompositeExecuteChild { my($infile1,$infile2,$outfile,$seq0,$seq1,$seq2,$startframe,$endframe,$compose_type) = @_; my($name,$path,$type,$base) = &GetFilenameInfo($outfile); if ($type eq "" || $path eq "") { &WarningMessageBoxDisp("Error : Composite Filename Illigal."); return -1; } $type =~ /\.(.*)/; my $ext_type = $1; if ( $seq2 ) { # sequence if ( $startframe > $endframe ) { &WarningMessageBoxDisp("Error : Frame Number Illigal."); } else { &CompositeChildProcessStart($infile1,$infile2,$outfile,$seq0,$seq1,$seq2,$startframe,$endframe,$compose_type,$ext_type); } } else { # single frame my $fg = &CompositeSingleFrame($infile1,$infile2,$outfile,0,0,0,0,$compose_type,$ext_type); &SetStatusMessage("Composite Success. Output '$outfile'") if $fg == 0; } } # child proccess start. (sequence file composite only) sub CompositeChildProcessStart { my($infile1,$infile2,$outfile,$seq0,$seq1,$seq2,$startframe,$endframe,$compose_type,$ext_type) = @_; return if &Check_CompositeChildProcessExec; $child_infile1 = $infile1; $child_infile2 = $infile2; $child_outfile = $outfile; $child_seq0 = $seq0; $child_seq1 = $seq1; $child_seq2 = $seq2; $child_startframe = $startframe; $child_endframe = $endframe; $child_compose_type = $compose_type; $child_nowframe = $startframe; $child_ext_type = $ext_type; $child_exec = 1; $mw->after(50,\&CompositeChildProcess); } # child proccess being check (out:1=being,0=not be) sub Check_CompositeChildProcessExec { if ( $child_exec ) { &WarningMessageBoxDisp('Now, continues composite processing. Please, wait.'); return 1; } 0; } # child proccess sub CompositeChildProcess { if ( $child_nowframe <= $child_endframe ) { &CompositeSingleFrame($child_infile1,$child_infile2,$child_outfile,$child_seq0,$child_seq1,$child_seq2,$child_nowframe,$child_compose_type,$child_ext_type); MessageSet_CompositeWorking($child_nowframe,0); $child_nowframe ++; if ( $child_nowframe > $child_endframe ) { $child_nowframe--; $child_exec = 0; MessageSet_CompositeWorking($child_nowframe,1); } else { $mw->after(50,\&CompositeChildProcess); } } } # dialog message box display sub WarningMessageBoxDisp { my($msg) = @_; $mw->messageBox(-title=>'Warning',-message=>$msg,-type=>'ok',-icon=>'warning'); } # set message ... composite proccessing now , or proccessing end. sub MessageSet_CompositeWorking { my($framenow,$fg) = @_; my $graph_length = 40; my $msg; my $now = $framenow - $child_startframe + 1; my $len = $child_endframe - $child_startframe + 1; my $str_l = int( ($graph_length * $now) / $len ); if ( $fg ) { $msg = "composite processing success. [$now/$len] file."; } else { $msg = "continues composite processing [".('#' x $str_l).('_' x ($graph_length-$str_l))."] [$now/$len] "; } &SetStatusMessage($msg); } # get filename ... frame number related # (in:filename(xxx_yyyy.eee),frame_number / out:filename(xxx_zzzz.eee)) sub getFilenameRelateFrameNumber { my($filename,$framenumber) = @_; my $res_filename; my($name,$path,$type,$base) = &GetFilenameInfo($filename); $res_filename = sprintf("%s%s%04d%s",$path,$base,$framenumber,$type); return "$res_filename"; } # single frame composite , preview image window sub CompositePreview { for ( $kind=0; $kind < 2; $kind++ ) { my $filename = $fsel_ent[$kind]->get; $filename = getFilenameRelateFrameNumber($filename,int($entry_frame_pre->get)) if $fg_seq[$kind]; if ( !(-e $filename) ) { &SetStatusMessage("Error : Not Found File. '$filename'"); return -1; } undef($img[$kind]) if $img[$kind]; $img[$kind] = Image::Magick->new; $img[$kind]->Read($filename); &PreviewImageWindowToCanvas($kind); } $img[2] = $img[1]->Clone(); $img[2]->Composite(image=>$img[0],compose=>"$opt_compose",opacity=>$opacity_value); &PreviewImageWindowToCanvas(2); &SetStatusMessage("Preview / Compose Type = '$opt_compose' / opacity = $opacity_value"); } # single frame composite , output sub CompositeSingleFrame { my($infile1,$infile2,$outfilename,$sequence_in1,$sequence_in2,$sequence_out,$framenumber,$compose_type,$ext_type) = @_; my @infilename = ($infile1,$infile2); my @seqfg = ($sequence_in1,$sequence_in2); my @img; my $k; for ( $k=0; $k<2; $k++ ) { my $filename = $seqfg[$k]? getFilenameRelateFrameNumber($infilename[$k],int($framenumber)) : $infilename[$k]; if ( !(-e $filename) ) { &SetStatusMessage("Error : Input File Not Found. '$filename'"); return -1; } $img[$k] = Image::Magick->new; $img[$k]->Read($filename); } my $filename = $sequence_out? getFilenameRelateFrameNumber($outfilename,int($framenumber)) : $outfilename; $img[1]->Composite(image=>$img[0],compose=>"$compose_type",opacity=>$opacity_value); $ext_type =~ tr/A-Z/a-z/; $img[1]->Set(magick=>"$ext_type"); # $img[1]->Set(adjoin=>($adjoin_fg? 'True':'False')); # only UNIX $img[1]->Set(interlace=>'None'); $img[1]->Set(matte=>($matte_fg? 'True':'False')); # $img[1]->Set(map-limit=>256); # $img[1]->Set(memory-limit=>256); # $img[1]->Set(type=>'TrueColor'); if ( $ext_type =~ /png/ ) { $img[1]->Set(quality=>$png_quality_value); } elsif ( $ext_type =~ /jpg/ ) { $img[1]->Set(quality=>$jpg_quality_value); } $img[1]->Write("$filename"); undef $img[0]; undef $img[1]; 0; } # set status message sub SetStatusMessage { my($msg) = @_; $status_msg = $msg; } # exit job sub endjob { return if &Check_CompositeChildProcessExec; for ( $i=0; $i<3; $i++ ) { undef($img[$i]) if $img[$i]; my $tmpfile = GetTempFilename($i); unlink($tmpfile) if ( -e $tmpfile ); } exit; }