The Perl Advent Calendar needs more articles for 2022. Submit your idea today!
=pod

=head1 NAME

examples/iv.pl - A image viewer program

=head1 FEATURES

Demonstrates usage of Prima image subsystem, in particular:

=over 4

=item *

Standard open dialog. Note it's behavior with the multi-frame images.

=item *

Standard save dialog. Note the graphic filters usage.

=item *

Image conversion routines.

=item *

Standard L<Prima::ImageViewer> class.

=back

Test the correct implementation of the internal image paint routines,
in particular on the paletted displays and the representation of 1-bit
images/icons with non-BW palette.

Note the mouse wheel interaction.

=cut

use strict;
use warnings;
use Prima qw(ImageViewer Dialog::ImageDialog MsgBox);
use Prima::Application name => "IV";

my $ico = Prima::Icon-> create;
$ico = 0 unless $ico-> load( 'hand.gif');


my $winCount  = 1;

my %iv_prf = (
	origin => [ 0, 0],
	growMode => gm::Client,
	quality => 1,
	name    => 'IV',
	valignment  => ta::Middle,
	alignment   => ta::Center,
	onMouseDown => \&iv_mousedown,
	onMouseUp   => \&iv_mouseup,
	onMouseMove => \&iv_mousemove,
	onMouseWheel => \&iv_mousewheel,
);

sub status
{
	my $iv = $_[0]-> IV;
	my $img = $iv-> image;
	my $str;
	if ( $img) {
		$str = $iv-> {fileName};
		$str =~ s/([^\\\/]*)$/$1/;
		$str = sprintf("%s (%dx%dx%d bpp)", $1,
			$img-> width, $img-> height, $img-> type & im::BPP);
	} else {
		$str = '.Untitled';
	}
	$_[0]-> text( $str);
	$::application-> name( $str);
}


sub menuadd
{
	unless ( $_[0]-> IV-> {menuadded}) {
		$_[0]-> {omenuID} = 'P';
		$_[0]-> {conversion} = ict::Optimized;
		$_[0]-> menu-> insert(
		[
			[ 'Reopen' => 'Ctrl+R' => '^R'       => \&freopen],
			[ '~New window...' => 'Ctrl+N' => '^N'       => \&fnewopen],
			[],
			[ '~Save'  => 'F2'     => 'F2'       => \&fsave],
			[ 'Save As...'                       => \&fsaveas],
		],
		'file', 1
		);
		$_[0]-> menu-> insert(
			[
			['~Edit' => [
				['~Copy' => 'Ctrl+Ins' => km::Ctrl|kb::Insert , sub {
					$::application-> Clipboard-> image($_[0]-> IV-> image)
				}],
				['~Paste' => 'Shift+Ins' => km::Shift|kb::Insert , sub {
					my $i = $::application-> Clipboard-> image;
					$_[0]-> IV-> image( $i) if $i;
					status($_[0]);
				}],
			]],
			['~Image' => [
				[ '~Convert to'=> [
					['~Monochrome' => sub {icvt($_[0],im::Mono)}],
					['~16 colors' => sub {icvt($_[0],im::bpp4)}],
					['~256 colors' => sub {icvt($_[0],im::bpp8)}],
					['~Grayscale' => sub {icvt($_[0],im::bpp8|im::GrayScale)}],
					['~RGB' => sub {icvt($_[0],im::RGB)}],
					['~Long' => sub {icvt($_[0],im::Long)}],
					[],
					['(N' => '~No halftoning' => sub {setconv(@_)}],
					['O' => '~Ordered' => sub {setconv(@_)}],
					['E' => '~Error diffusion' => sub {setconv(@_)}],
					[')*P' => 'O~ptimized' => sub {setconv(@_)}],
				]],
				['~Zoom' => [
					['~Normal ( 100%)' => 'Ctrl+Z' => '^Z' => sub{$_[0]-> IV-> zoom(1.0)}],
					['~Best fit' => 'Ctrl+Shift+Z' => km::Shift|km::Ctrl|ord('z') => sub { $_[0]->IV->apply_auto_zoom } ],
					[],
					['@abfit' => '~Auto best fit' => sub{ $_[0]->IV->autoZoom($_[2]) }],
					[],
					['25%' => sub{$_[0]-> IV-> zoom(0.25)}],
					['50%' => sub{$_[0]-> IV-> zoom(0.5)}],
					['75%' => sub{$_[0]-> IV-> zoom(0.75)}],
					['150%' => sub{$_[0]-> IV-> zoom(1.5)}],
					['200%' => sub{$_[0]-> IV-> zoom(2)}],
					['300%' => sub{$_[0]-> IV-> zoom(3)}],
					['400%' => sub{$_[0]-> IV-> zoom(4)}],
					['600%' => sub{$_[0]-> IV-> zoom(6)}],
					['1600%' => sub{$_[0]-> IV-> zoom(16)}],
					[],
					['~Increase' => '+' => '+' => sub{$_[0]-> IV-> zoom( $_[0]-> IV-> zoom * 1.1)}],
					['~Decrease' => '-' => '-' => sub{$_[0]-> IV-> zoom( $_[0]-> IV-> zoom / 1.1)}],
				]],
				['~Info' => 'Alt+F1' => '@F1' => \&iinfo],
			]],
			],
			'', 1,
		);
		$_[0]-> IV-> {menuadded}++;
	}
}

my $imgdlg;
sub create_image_dialog
{
	return $imgdlg if $imgdlg;
	$imgdlg  = Prima::Dialog::ImageOpenDialog-> create();
}

sub fdopen
{
	my $self = $_[0]-> IV;

	my $dlg = create_image_dialog( $self);
	my $i   = $dlg-> load( progressViewer => $self);

	if ( $i) {
		menuadd( $_[0]);
		$self-> image( $i);
		$self-> {fileName} = $dlg-> fileName;
		status( $_[0]);
	}
}

sub freopen
{
	my $self = $_[0]-> IV;
	my $i = Prima::Image-> new;
	$self-> watch_load_progress( $i);
	if ( $i-> load( $self-> {fileName}, loadExtras => 1)) {
		$self-> image( $i);
		status( $_[0]);
		message( $i->{extras}->{truncated} ) if defined $i->{extras}->{truncated};
	} else {
		message("Cannot reload ". $self-> {fileName}. ":$@");
	}
	$self-> unwatch_load_progress(0);
}

sub newwindow
{
	my ( $self, $filename, $i) = @_;
	my $w = Prima::Window-> create(
		onDestroy => \&iv_destroy,
		menuItems => $self-> menuItems,
		onMouseWheel => sub { iv_mousewheel( shift-> IV, @_)},
		size         => [ $i-> width + 50, $i-> height + 50],
	);
	$winCount++;
	$w-> insert( ImageViewer =>
		size   => [ $w-> size],
		%iv_prf,
	);
	$w-> IV-> image( $i);
	$w-> IV-> {fileName} = $filename;
	$w-> {omenuID} = $self-> {omenuID};
	$w->IV->{menuadded} = 1;
	$w->{conversion} = ict::Optimized;
	$w-> select;
	status($w);
}

sub fnewopen
{
	my $self = $_[0]-> IV;
	my $dlg  = create_image_dialog( $self);
	my $i    = $dlg-> load;

	newwindow( $_[0], $dlg-> fileName, $i) if $i;
}


sub fload
{
	my $self = $_[0]-> IV;
	my $f = $_[1];
	my $i = Prima::Image-> new;
	$self-> watch_load_progress( $i);

	if ( $i-> load( $f, loadExtras => 1)) {
		menuadd( $_[0]);
		my @sizes = ( $i-> size, map { $_ * 0.9 } $::application-> size);
		$self-> owner-> size( map {
			( $sizes[$_] > $sizes[$_ + 2]) ?  $sizes[$_ + 2] : $sizes[$_]
		} 0,1);
		$self-> image( $i);
		$self-> {fileName} = $f;
		status( $_[0]);
		message( $i->{extras}->{truncated} ) if defined $i->{extras}->{truncated};
	} else {
		message("Cannot load $f:$@");
	}

	$self-> unwatch_load_progress(0);
}


sub fsave
{
	my $iv = $_[0]-> IV;
	message('Cannot save '.$iv-> {fileName}. ":$@")
		unless $iv-> image-> save( $iv-> {fileName});
}

sub fsaveas
{
	my $iv = $_[0]-> IV;
	my $dlg  = Prima::Dialog::ImageSaveDialog-> create( image => $iv-> image);
	$iv-> {fileName} = $dlg-> fileName if $dlg-> save( $iv-> image);
	$dlg-> destroy;
}

sub setconv
{
	my ( $self, $menuID) = @_;
	return if $self-> {omenuID} eq $menuID;
	$self-> {omenuID}    = $menuID;
	$self-> {conversion} = (
	( $menuID eq 'N') ? ict::None : (
	( $menuID eq 'O') ? ict::Ordered : (
	( $menuID eq 'E') ? ict::ErrorDiffusion : ict::Optimized
	))
	);
}

sub icvt
{
	my $im = $_[0]-> IV-> image;
	$im-> set(
		conversion => $_[0]-> {conversion},
		type       => $_[1],
	);
	status( $_[0]);
	$_[0]-> IV-> palette( $im-> palette);
	$_[0]-> IV-> repaint;
}


sub iinfo
{
	my $i = $_[0]-> IV-> image;
	message_box(
		'',
		"File: ".$_[0]-> IV-> {fileName}."\n".
		"Width: ".$i-> width."\nHeight: ".$i-> height."\nBPP:".($i-> type&im::BPP)."\n".
		"Zoom: ".$_[0]-> IV-> zoom,
		0
	);
}

sub iv_mousedown
{
	my ( $self, $btn, $mod, $x, $y) = @_;
	return if $self-> {drag} || $btn != mb::Right;
	$self-> {drag}=1;
	$self-> {x} = $x;
	$self-> {y} = $y;
	$self-> {wasdx} = $self-> deltaX;
	$self-> {wasdy} = $self-> deltaY;
	$self-> capture(1);
	$self-> pointer( $ico) if $ico;
}

sub iv_mouseup
{
	my ( $self, $btn, $mod, $x, $y) = @_;
	return unless $self-> {drag} && $btn == mb::Right;
	$self-> {drag}=0;
	$self-> capture(0);
	$self-> pointer( cr::Default) if $ico;
}

sub iv_mousemove
{
	my ( $self, $mod, $x, $y) = @_;
	return unless $self-> {drag};
	my ($dx,$dy) = ($x - $self-> {x}, $y - $self-> {y});
	$self-> deltas( $self-> {wasdx} - $dx, $self-> {wasdy} + $dy);
}

sub iv_mousewheel
{
	my ( $self, $mod, $x, $y, $z) = @_;
	$z = (abs($z) > 120) ? int($z/120) : (($z > 0) ? 1 : -1);
	my $xv = $self-> bring(($mod & km::Shift) ? 'VScroll' : 'HScroll');
	return unless $xv;
	$z *= ($mod & km::Ctrl) ? $xv-> pageStep : $xv-> step;
	if ( $mod & km::Shift) {
		$self-> deltaX( $self-> deltaX - $z);
	} else {
		$self-> deltaY( $self-> deltaY - $z);
	}
}


sub iv_destroy
{
	$winCount--;
	$::application-> close unless $winCount;
}

my $w = Prima::Window-> create(
	size => [ 300, 300],
	onDestroy => \&iv_destroy,
	onMouseWheel => sub { iv_mousewheel( shift-> IV, @_)},
	menuItems => [
	[ file => '~File' => [
		[ '~Open' =>  'F3'     => kb::F3     , \&fdopen],
		[],
		[ 'E~xit' => 'Alt+X' => '@X' => sub {$::application-> close}],
	]],
	],
);

$w-> insert( ImageViewer =>
	size   => [ $w-> size],
	%iv_prf,
);
status($w);

if ( @ARGV && $ARGV[0] =~ /^-z(\d+(\.\d*)?)$/) {
	$w-> IV-> zoom($1);
	shift @ARGV;
}
fload( $w, $ARGV[0]), shift if @ARGV;
for ( @ARGV) {
	my $i = Prima::Image-> load($_);
	message("Cannot load $_:$@"), next unless $i;
	newwindow( $w, $_, $i);
}

run Prima;