# $Id: iterm.pl,v 1.12 2004/08/18 08:22:21 dk Exp $ use strict; use Prima qw(Application MsgBox ComboBox ImageViewer ImageDialog); $::application-> name( 'ITerm'); $::application-> autoClose(0); my $w; my $HOME = defined( $ENV{HOME}) ? $ENV{HOME} : '.'; my $fdo; my $fds; my $vec = ''; my $codecID = 0; use IPA::Global qw(/./); use IPA::Local qw(/./); use IPA::Geometry qw(/./); use IPA::Misc qw(/./); use IPA::Point qw(/./); use IPA::Morphology qw(/./); use vars qw($i $j @i @windows $last_image); $i = Prima::Image-> create; $j = Prima::Image-> create; package main; # user routines sub new { eval "\$$_[0] = Prima::Image-> create();"; } sub show { my $img = shift; $img = $i unless defined $img; show_error('Nothing to show'), return unless defined $img; $w-> Image-> image( $img); } sub clearhistory { $w-> Input-> List-> items([]); } sub show_error { $w-> Status-> text( $_[0]); } sub load { my $x = Prima::Image-> load( @_, loadExtras => 1); my $codec = $codecID = $x-> {extras}-> {codecID}; delete $x-> {extras}; $x-> {extras}-> {codecID} = $codec; if ( $x) { $last_image = $i if $i; $i = $x; } else { show_error( "error loading: $@"); } } sub save { my $img = shift; $img = $i unless defined $img; show_error('Nothing to save'), return unless defined $img; my @codeco = exists ( $img-> {codecs}-> {codecID} ) ? () : ( 'codecID', $codecID ); $img-> save( @_, @codeco); show_error("$@") if $@; } sub invert { my $img = shift; $img = $i unless defined $img; show_error('No image'), return unless defined $img; $img-> resample( $i-> rangeLo, $i-> rangeHi, $i-> rangeHi, $i-> rangeLo); } sub stretch { my $img = shift; $img = $i unless defined $img; show_error('No image'), return unless defined $img; $img-> resample( $i-> rangeLo, $i-> rangeHi, 0, (1 << ( $img-> type & im::BPP)) - 1); } sub info { my $img = shift; $img = $i unless defined $img; show_error('No image'), return unless defined $img; show_error( sprintf("%d x %d, %d bits %s", $img-> size, $img-> type & im::BPP, ( (($img-> type & im::GrayScale) ? 'gray ' : '') . (($img-> type & im::RealNumber) ? 'float ' : '') . (($img-> type & im::ComplexNumber) ? 'complex ' : '') . (($img-> type & im::TrigComplexNumber) ? 'trig ' : '') ) )); } sub quit { $::application-> close; } sub zoom { $w-> Image-> zoom( $_[0]); } sub undo { if ( $last_image) { $i = $last_image; $last_image = undef; } else { show_error ( "No undo image" ); } } sub window { my $z; for ( $z = 0; $z < 32000; $z++) { next if vec( $vec, $z, 1); vec( $vec, $z, 1) = 1; last; } my $w = Prima::Window-> create( name => "Image \$i[$z]", menuItems => [['~Image' => [ [ '~Import' , 'Shift+Ins' , km::Shift | kb::Insert , sub { $_[0]-> Image-> image( $i[ $_[0]->{id}] = $i-> dup); }], [ '~Export' , 'Ctrl+Ins' , km::Ctrl | kb::Insert , sub { my $x = $_[0]-> Image-> image; return unless $x; $last_image = $i if $i; $i = $x-> dup; show; }], ], ]], onDestroy => sub { vec( $vec, $_[0]-> {id}, 1) = 0; }, ); $windows[$z] = $w; $w-> {id} = $z; $w-> insert( ImageViewer => origin => [0,0], size => [$w-> size], hScroll => 1, vScroll => 1, name => 'Image', quality => 1, growMode => gm::Client, ); $w-> select; return $w; } sub dup { my $img = shift; $img = $i unless defined $img; show_error('Nothing to dup'), return unless defined $img; my $w = window(); $w-> Image-> image( $i[$w->{id}] = $img-> dup); } sub help { $::application-> open_help('iterm'); } $w = Prima::Window-> create( name => $::application-> name, font => { size => 12 }, onDestroy => sub { $::application-> close; }, menuItems => [['~Image' => [ [ '~Open' => 'F3' => 'F3' => sub { $fdo = Prima::ImageOpenDialog-> create unless $fdo; my $x = $fdo-> load; return unless $x; my $codec = $codecID = $x-> {extras}-> {codecID}; delete $x-> {extras}; $x-> {extras}-> {codecID} = $codec; $last_image = $i if $i; $i = $x; show; }], [ '~Save as' => 'F2' => 'F2' => sub { return unless $i; $fds = Prima::ImageSaveDialog-> create unless $fds; $i-> {extras}-> { codecID} = $codecID unless exists $i-> {extras}-> { codecID}; $fds-> save( $i); }], [], ['~Duplicate' => 'Ctrl+D' => '^D' => sub { dup(); } ], ]], [], ['Help' => \&help ], ], ); sub command { my $cmd = $_[0]; show_error( ""); if ( $cmd =~ /^(ls|pwd)/) { my @ret = split("\n", `$cmd`); return unless scalar @ret; show_error( $ret[0]); print map { "$_\n" } @ret; return; } if ( $cmd =~ /^cd\s*($|\S.*$)/) { my $r = length $1 ? $1 : '.'; chdir $1; command('pwd'); return; } if ( $cmd =~ /^my\s+([^\s=]+)/) { eval "use vars '$1'"; $cmd =~ s/^my\s+//; } my @ret; eval "{\@ret = $cmd}; die \$\@ if \$\@;"; show_error($@), return if $@; my $ifound; for ( @ret) { my $z = $_; next unless defined $z; next unless eval { Prima::Object::alive( $z); }; next unless $z-> isa( 'Prima::Image'); if ( $ifound) { my $w = window(); $w-> Image-> image( $i[$w->{id}] = $z); } else { $last_image = $i if $i; $i = $z-> dup; $ifound = 1; } } $i = Prima::Image-> create if !defined $i || !$i-> isa( 'Prima::Image'); $j = Prima::Image-> create if !defined $j || !$i-> isa( 'Prima::Image'); show; print "\n" if $cmd =~ /^print/; } $w-> insert( Label => width => $w-> width, height => $w-> font-> height, bottom => 0, left => 0, name => 'Status', text => '', growMode => gm::Floor, ); my @li = ('quit'); if ( open F, "$HOME/.iterm-list") { @li = map { chomp; $_ } ; close F; } $w-> insert( ComboBox => style => cs::DropDown, width => $w-> width, bottom => $w-> Status-> top + 2, left => 0, height => $w-> font-> height + 4, text => '', name => 'Input', growMode => gm::Floor, editProfile => { onKeyDown => sub { my ( $self, $code, $key, $mod) = @_; if (( $key == kb::Enter) && (( $mod & km::Ctrl & km::Shift & km::Alt) == 0)) { my $i = $self-> owner-> List-> items; my $t = $self-> text; $t =~ s/^\s*//; $t =~ s/\s*$//; return unless length $t; my $found = 0; my $ix = 0; for ( @$i ) { $found = 1, last if $_ eq $t; $ix++; } $self-> owner-> List-> delete_items( $ix) if $found; $self-> owner-> List-> insert_items( 0, $t); $self-> text(''); command( $t); } }, }, listProfile => { items => \@li, onDestroy => sub { if ( open F, "> $HOME/.iterm-list") { print F map { "$_\n" } @{$_[0]-> items}; close F; } }, }, ); $w-> insert( ImageViewer => left => 0, bottom => $w-> Input-> top + 2, width => $w-> width, top => $w-> height, hScroll => 1, vScroll => 1, name => 'Image', quality => 1, growMode => gm::Client, onMouseMove => sub { my ( $self, $btn, $x, $y) = @_; return unless $btn & km::Shift; my @x = map {int} $w-> Image-> screen2point(@_[2,3] ); my $pix = $i-> pixel(@x); my $t = $i-> type; return if $pix == cl::Invalid; my @p = (( $t & ( im::ComplexNumber | im::TrigComplexNumber)) ? @$pix : $pix); my $fmt; if ( $t & ( im::ComplexNumber | im::TrigComplexNumber)) { $fmt = '%g %g'; } elsif ( $t & im::RealNumber) { $fmt = '%g'; } elsif ( $t == im::Short || $t == im::Long) { $fmt = '%d'; } elsif (( $t & im::BPP) < 24) { $fmt = "%02x"; } else { $fmt = "%06x"; } show_error( sprintf "[$x[0],$x[1]] $fmt", @p); } ); do "$HOME/.iterm-startup"; Prima::MsgBox::message( $@) if @$ ; command( 'load "' . quotemeta($ARGV[0]) . '"') if @ARGV; $w-> Input-> select; while ($::application) { eval { run Prima }; Prima::MsgBox::message( "$@") if $::application && $@; } __END__ =pod =head1 NAME iterm - the interactive tool for IPA library =head1 DESCRIPTION iterm is a mostly command-line tool for basic image processing. It has terminal representation, where the main window is capable of viewing the image and accepting commands. The command syntax is pure perl, plus all functions available in the IPA library ( see L ) and Prima toolkit ( see L), and some specific iterm commands. =head1 USAGE =head2 Design iterm defines several scalars for the user needs. The main window shows the content of the scalar $i, if it is an image. If the additional windows are opened, they correspond to images in array @i. The array @i is filled automatically, and the index is shown on the additional new window titles. The additional windows are stored in array @windows, under same indexes. The main window is stored in scalar $w. The input line is used to enter perl code. If the code returns and newly created image, it is stored into $i, and the old value of $i is discarded. If the code returns more than on image, the additional windows opened automatically. If the code throws an exception, its first line is shown on the status line, and the whole message is printed to stderr. To see pixel value under mouse cursor, hold the Shift key. =head2 Interactive commands There are several interactive commands, present on the window menus. The main and the additional windows have different sets of interactive commands. =over =item Open Presents a file selection dialog, where the image file is to be selected and its content loaded into variable $i and displayed in the main window. =item Save as Opens a file save dialog, where the content of $i can be stored on disk. =item Duplicate Creates a new additional window and copies $i into it. The newly created image is stored into @i array, and the new window into @windows array. The indexes of these are equal and shown on the window's title. =item Export Note: only for additional windows Copies the content of the image into $i =item Import Note: only for additional windows Copies content of $i into the image. =back =head2 iterm commands These are commands, specific to iterm. =over =item new VAR Assigns new variable $VAR to an empty image =item show IMAGE Assigns IMAGE to $i and displays it. =item clearhistory Flushes the command history =item load FILE [ options ] Loads image FILE into $i and displays the image. FILE must be a quoted string. =item reverse IMAGE Reverses image =item stretch IMAGE Stretches image data to fit the whole range. For the histogram equalization see L =item save IMAGE, FILE [ options ] Stores IMAGE object into FILE. =item quit Exits iterm. =item zoom SCALE Selects zoom for window $w. There are no shortcuts for selecting zoom for the additional windows, but this can be achieved by entering the following code: $windows[$NUM]-> Image-> zoom($SCALE) =item window Opens a new, empty additional window. =item dup See L =item undo Reverts $i to its last value =item my VARIABLE [ = VALUE ] Declare lexical VARIABLE and assign VALUE to it. =back =head2 Example To load an image: load 'image.gif' Convert to 8-bit grayscale: $i-> type( im::Byte) Perform dilation: dilate $i Copy to new window dup Erode the new image erode $i[0] Display the difference subtract $i, $i[0] Note: iterm never asks if the changed images are to be saved. =head1 FILES ~/.iterm-list - the command history =head1 SEE ALSO =over =item * L - the image processing library =item * L - perl graphic toolkit =back =head1 AUTHOR Dmitry Karasik Edmitry@karasik.eu.orgE =cut