1#  Created by:
2#     Anton Berezin  <tobez@tobez.org>
3#     Dmitry Karasik <dk@plab.ku.dk>
4use strict;
5use warnings;
6use Prima::ScrollWidget;
7
8package Prima::ImageViewer;
9use vars qw(@ISA);
10@ISA = qw( Prima::ScrollWidget);
11
12sub profile_default
13{
14	my $def = $_[0]-> SUPER::profile_default;
15	my %prf = (
16		autoZoom     => 0,
17		image        => undef,
18		imageFile    => undef,
19		stretch      => 0,
20		zoom         => 1,
21		zoomPrecision=> 100,
22		alignment    => ta::Left,
23		valignment   => ta::Bottom,
24		quality      => 1,
25	);
26	@$def{keys %prf} = values %prf;
27	return $def;
28}
29
30sub profile_check_in
31{
32	my ( $self, $p, $default) = @_;
33	$self-> SUPER::profile_check_in( $p, $default);
34	if ( defined $p-> {imageFile} && !defined $p-> {image})
35	{
36		$p-> {image} = Prima::Image-> create;
37		delete $p-> {image} unless $p-> {image}-> load($p-> {imageFile});
38	}
39}
40
41
42sub init
43{
44	my $self = shift;
45	for ( qw( image ImageFile))
46		{ $self-> {$_} = undef; }
47	for ( qw( alignment autoZoom quality valignment imageX imageY stretch))
48		{ $self-> {$_} = 0; }
49	for ( qw( zoom integralScreen integralImage))
50		{ $self-> {$_} = 1; }
51	$self-> {zoomPrecision} = 10;
52	my %profile = $self-> SUPER::init(@_);
53	$self-> { imageFile}     = $profile{ imageFile};
54	for ( qw( image zoomPrecision zoom autoZoom stretch alignment valignment quality)) {
55		$self-> $_($profile{$_});
56	}
57	return %profile;
58}
59
60
61sub on_paint
62{
63	my ( $self, $canvas) = @_;
64	my @size   = $self-> size;
65
66	$self-> rect_bevel( $canvas, Prima::rect->new(@size)->inclusive,
67		width  => $self-> {borderWidth},
68		panel  => 1,
69		fill   => $self-> {image} ? undef : $self->backColor,
70	);
71	return 1 unless $self->{image};
72
73	my @r = $self-> get_active_area( 0, @size);
74	$canvas-> clipRect( @r);
75	$canvas-> translate( @r[0,1]);
76	my $imY  = $self-> {imageY};
77	my $imX  = $self-> {imageX};
78	my $z = $self-> {zoom};
79	my $imYz = int($imY * $z);
80	my $imXz = int($imX * $z);
81	my $winY = $r[3] - $r[1];
82	my $winX = $r[2] - $r[0];
83	my $deltaY = ($imYz - $winY - $self-> {deltaY} > 0) ? $imYz - $winY - $self-> {deltaY}:0;
84	my ($xa,$ya) = ($self-> {alignment}, $self-> {valignment});
85	my ($iS, $iI) = ($self-> {integralScreen}, $self-> {integralImage});
86	my ( $atx, $aty, $xDest, $yDest);
87
88	if ( $self->{stretch}) {
89		$atx = $aty = $xDest = $yDest = 0;
90		$imXz = $r[2] - $r[0];
91		$imYz = $r[3] - $r[1];
92		goto PAINT;
93	}
94
95	if ( $imYz < $winY) {
96		if ( $ya == ta::Top) {
97			$aty = $winY - $imYz;
98		} elsif ( $ya != ta::Bottom) {
99			$aty = int(($winY - $imYz)/2 + .5);
100		} else {
101			$aty = 0;
102		}
103		$canvas-> clear( 0, 0, $winX-1, $aty-1) if $aty > 0;
104		$canvas-> clear( 0, $aty + $imYz, $winX-1, $winY-1) if $aty + $imYz < $winY;
105		$yDest = 0;
106	} else {
107		$aty   = -($deltaY % $iS);
108		$yDest = ($deltaY + $aty) / $iS * $iI;
109		$imYz = int(($winY - $aty + $iS - 1) / $iS) * $iS;
110		$imY = $imYz / $iS * $iI;
111	}
112
113	if ( $imXz < $winX) {
114		if ( $xa == ta::Right) {
115			$atx = $winX - $imXz;
116		} elsif ( $xa != ta::Left) {
117			$atx = int(($winX - $imXz)/2 + .5);
118		} else {
119			$atx = 0;
120		}
121		$canvas-> clear( 0, $aty, $atx - 1, $aty + $imYz - 1) if $atx > 0;
122		$canvas-> clear( $atx + $imXz, $aty, $winX - 1, $aty + $imYz - 1) if $atx + $imXz < $winX;
123		$xDest = 0;
124	} else {
125		$atx   = -($self-> {deltaX} % $iS);
126		$xDest = ($self-> {deltaX} + $atx) / $iS * $iI;
127		$imXz = int(($winX - $atx + $iS - 1) / $iS) * $iS;
128		$imX = $imXz / $iS * $iI;
129	}
130
131PAINT:
132	$canvas-> clear( $atx, $aty, $atx + $imXz, $aty + $imYz) if $self-> {icon};
133
134	return $canvas-> put_image_indirect(
135		$self-> {image},
136		$atx, $aty,
137		$xDest, $yDest,
138		$imXz, $imYz, $imX, $imY,
139		rop::CopyPut
140	);
141}
142
143sub on_keydown
144{
145	my ( $self, $code, $key, $mod) = @_;
146
147	return if $self->{stretch};
148
149	return unless grep { $key == $_ } (
150		kb::Left, kb::Right, kb::Down, kb::Up
151	);
152
153	my $xstep = int($self-> width  / 5) || 1;
154	my $ystep = int($self-> height / 5) || 1;
155
156	my ( $dx, $dy) = $self-> deltas;
157
158	$dx += $xstep if $key == kb::Right;
159	$dx -= $xstep if $key == kb::Left;
160	$dy += $ystep if $key == kb::Down;
161	$dy -= $ystep if $key == kb::Up;
162
163	$self-> deltas( $dx, $dy);
164}
165
166sub on_size
167{
168	my $self = shift;
169	$self->apply_auto_zoom if $self->{autoZoom};
170	$self->SUPER::on_size(@_);
171}
172
173sub apply_auto_zoom
174{
175	my $self = shift;
176	$self->hScroll(0);
177	$self->vScroll(0);
178	return unless $self->image;
179	my @szA = $self->image-> size;
180	my @szB = $self-> get_active_area(2);
181	my $xx = $szB[0] / $szA[0];
182	my $yy = $szB[1] / $szA[1];
183	$self-> zoom( $xx < $yy ? $xx : $yy);
184}
185
186sub set_auto_zoom
187{
188	my ( $self, $az ) = @_;
189	$self->{autoZoom} = $az;
190	$self->apply_auto_zoom if $az;
191}
192
193sub set_alignment
194{
195	$_[0]-> {alignment} = $_[1];
196	$_[0]-> repaint;
197}
198
199sub set_valignment
200{
201	$_[0]-> {valignment} = $_[1];
202	$_[0]-> repaint;
203}
204
205my @cubic_palette;
206
207sub set_image
208{
209	my ( $self, $img) = @_;
210	unless ( defined $img) {
211		$self-> {imageX} = $self-> {imageY} = 0;
212		$self-> limits(0,0);
213		$self-> palette([]);
214		$self-> repaint if defined $self-> {image};
215		$self-> {image} = $img;
216		return;
217	}
218
219	$self-> {image} = $img;
220	my ( $x, $y) = ($img-> width, $img-> height);
221	$self-> {imageX} = $x;
222	$self-> {imageY} = $y;
223	$x *= $self-> {zoom};
224	$y *= $self-> {zoom};
225	$self-> {icon}   = $img-> isa('Prima::Icon');
226	$self-> {bitmap} = $img-> isa('Prima::DeviceBitmap');
227	$self-> limits($x,$y) unless $self->{stretch};
228	if ( $self-> {quality}) {
229		my $do_cubic;
230
231		if ( $self-> {bitmap}) {
232			$do_cubic = $img-> type != dbt::Bitmap && $::application-> get_bpp == 8;
233		} else {
234			$do_cubic = ( $img-> type & im::BPP) > 8;
235		}
236
237		if ( $do_cubic) {
238			my $depth = $self-> get_bpp;
239			if (($depth > 2) && ($depth <= 8)) {
240				unless ( scalar @cubic_palette) {
241					my ( $r, $g, $b) = (6, 6, 6);
242					@cubic_palette = ((0) x 648);
243					for ( $b = 0; $b < 6; $b++) {
244						for ( $g = 0; $g < 6; $g++) {
245							for ( $r = 0; $r < 6; $r++) {
246								my $ix = $b + $g * 6 + $r * 36;
247								@cubic_palette[ $ix, $ix + 1, $ix + 2] =
248									map {$_*51} ($b,$g,$r);
249				}}}}
250				$self-> palette( \@cubic_palette);
251			}
252		} else {
253			$self-> palette( $img-> palette);
254		}
255	}
256	$self-> repaint;
257}
258
259sub set_image_file
260{
261	my ($self,$file,$img) = @_;
262	$img = Prima::Image-> create;
263	return unless $img-> load($file);
264	$self-> {imageFile} = $file;
265	$self-> image($img);
266}
267
268sub set_quality
269{
270	my ( $self, $quality) = @_;
271	return if $quality == $self-> {quality};
272	$self-> {quality} = $quality;
273	return unless defined $self-> {image};
274	$self-> palette( $quality ? $self-> {image}-> palette : []);
275	$self-> repaint;
276}
277
278sub zoom_round
279{
280	my ( $self, $zoom) = @_;
281	$zoom = 100 if $zoom > 100;
282	$zoom = 0.01 if $zoom <= 0.01;
283
284	my $mul = $self-> {zoomPrecision};
285	my $dv = int( $mul * ( $zoom - int( $zoom)) + 0.5);
286	$dv-- if ($dv % 2) and ( $dv % 5);
287	return int($zoom) + $dv / $mul;
288}
289
290sub set_zoom
291{
292	my ( $self, $zoom) = @_;
293
294	return if $self->{stretch};
295
296	$zoom = 100 if $zoom > 100;
297	$zoom = 0.01 if $zoom < 0.01;
298
299	my $mul = $self-> {zoomPrecision};
300	my $dv = int( $mul * ( $zoom - int( $zoom)) + 0.5);
301	$dv-- if ($dv % 2) and ( $dv % 5);
302	$zoom = int($zoom) + $dv / $mul;
303	$dv = 0 if $dv >= $mul;
304	my ($r,$n,$m) = (1,$mul,$dv);
305	while(1) {
306		$r = $m % $n;
307		last unless $r;
308		($m,$n) = ($n,$r);
309	}
310	return if $zoom == $self-> {zoom};
311
312	$self-> {zoom} = $zoom;
313	$self-> {integralScreen} = int( $mul / $n) * int( $zoom) + int( $dv / $n);
314	$self-> {integralImage}  = int( $mul / $n);
315
316	return unless defined $self-> {image};
317	my ( $x, $y) = ($self-> {image}-> width, $self-> {image}-> height);
318	$x *= $self-> {zoom};
319	$y *= $self-> {zoom};
320
321	$self-> limits($x,$y);
322	$self-> repaint;
323	$self-> {hScrollBar}-> set_steps( $zoom, $zoom * 10) if $self-> {hScroll};
324	$self-> {vScrollBar}-> set_steps( $zoom, $zoom * 10) if $self-> {vScroll};
325}
326
327sub set_zoom_precision
328{
329	my ( $self, $zp) = @_;
330
331	$zp = 10 if $zp < 10;
332	return if $zp == $self-> {zoomPrecision};
333
334	$self-> {zoomPrecision} = $zp;
335	$self-> zoom( $self-> {zoom});
336}
337
338sub set_stretch
339{
340	my ( $self, $s) = @_;
341	$s = $s ? 1 : 0;
342	return if $self->{stretch} == $s;
343	$self->{stretch} = $s;
344	$self->limits(0,0) if $s;
345	$self->repaint;
346}
347
348sub screen2point
349{
350	my $self = shift;
351	my @ret = ();
352	my ( $i, $wx, $wy, $z, $dx, $dy, $ha, $va) =
353		@{$self}{qw(indents winX winY zoom deltaX deltaY alignment valignment)};
354
355	my ($maxx, $maxy, $zx, $zy);
356
357	if ( $self->{stretch}) {
358		$dx = $dy = $maxx = $maxy = 0;
359		$zx = ($self->width  - $$i[2] - $$i[0]) / $self->{imageX};
360		$zy = ($self->height - $$i[3] - $$i[1]) / $self->{imageY};
361	} else {
362		$zx = $zy = $z;
363		$maxy = ( $wy < $self-> {limitY}) ? $self-> {limitY} - $wy : 0;
364		unless ( $maxy) {
365			if ( $va == ta::Top) {
366				$maxy += $self-> {imageY} * $z - $wy;
367			} elsif ( $va != ta::Bottom) {
368				$maxy += ( $self-> {imageY} * $z - $wy) / 2;
369			}
370		}
371
372		$maxx = 0;
373		if ( $wx > $self-> {limitX}) {
374			if ( $ha == ta::Right) {
375				$maxx += $self-> {imageX} * $z - $wx;
376			} elsif ( $ha != ta::Left) {
377				$maxx += ( $self-> {imageX} * $z - $wx) / 2;
378			}
379		}
380	}
381
382	while ( scalar @_) {
383		my ( $x, $y) = ( shift, shift);
384		$x += $dx - $$i[0];
385		$y += $maxy - $dy - $$i[1];
386		$x += $maxx;
387		push @ret, $x / $zx, $y / $zy;
388	}
389	return @ret;
390}
391
392sub point2screen
393{
394	my $self = shift;
395	my @ret = ();
396	my ( $i, $wx, $wy, $z, $dx, $dy, $ha, $va) =
397		@{$self}{qw(indents winX winY zoom deltaX deltaY alignment valignment)};
398
399	my ( $maxx, $maxy, $zx, $zy );
400	if ( $self->{stretch}) {
401		$dx = $dy = $maxx = $maxy = 0;
402		$zx = ($self->width  - $$i[2] - $$i[0]) / $self->{imageX};
403		$zy = ($self->height - $$i[3] - $$i[1]) / $self->{imageY};
404	} else {
405		$zx = $zy = $z;
406		$maxy = ( $wy < $self-> {limitY}) ? $self-> {limitY} - $wy : 0;
407		unless ( $maxy) {
408			if ( $va == ta::Top) {
409				$maxy += $self-> {imageY} * $z - $wy;
410			} elsif ( $va != ta::Bottom) {
411				$maxy += ( $self-> {imageY} * $z - $wy) / 2;
412			}
413		}
414
415		$maxx = 0;
416		if ( $wx > $self-> {limitX}) {
417			if ( $ha == ta::Right) {
418				$maxx += $self-> {imageX} * $z - $wx;
419			} elsif ( $ha != ta::Left) {
420				$maxx += ( $self-> {imageX} * $z - $wx) / 2;
421			}
422		}
423	}
424
425	while ( scalar @_) {
426		my ( $x, $y) = ( $zx * shift, $zy * shift);
427		$x -= $maxx + $dx - $$i[0];
428		$y -= $maxy - $dy - $$i[1];
429		push @ret, $x, $y;
430	}
431	return @ret;
432}
433
434sub autoZoom     {($#_)?$_[0]-> set_auto_zoom($_[1]):return $_[0]-> {autoZoom}}
435sub alignment    {($#_)?($_[0]-> set_alignment(    $_[1]))               :return $_[0]-> {alignment}    }
436sub valignment   {($#_)?($_[0]-> set_valignment(    $_[1]))              :return $_[0]-> {valignment}   }
437sub image        {($#_)?$_[0]-> set_image($_[1]):return $_[0]-> {image} }
438sub imageFile    {($#_)?$_[0]-> set_image_file($_[1]):return $_[0]-> {imageFile}}
439sub zoom         {($#_)?$_[0]-> set_zoom($_[1]):return $_[0]-> {zoom}}
440sub zoomPrecision{($#_)?$_[0]-> set_zoom_precision($_[1]):return $_[0]-> {zoomPrecision}}
441sub quality      {($#_)?$_[0]-> set_quality($_[1]):return $_[0]-> {quality}}
442sub stretch      {($#_)?$_[0]-> set_stretch($_[1]):return $_[0]-> {stretch}}
443
444sub PreviewImage_HeaderReady
445{
446	my ( $self, $image, $extras) = @_;
447	my $db;
448	eval {
449		$db = Prima::DeviceBitmap-> new(
450			width    => $image->width,
451			height   => $image->height,
452		);
453	};
454	return unless $db;
455
456	$self-> image($db);
457        $self-> image-> backColor(0);
458        $self-> image-> clear;
459	$self-> {__watch}->{preview} = 1;
460}
461
462sub PreviewImage_DataReady
463{
464	my ( $self, $image, $x, $y, $w, $h) = @_;
465	return unless exists $self-> {__watch} && $self->{__watch}->{preview};
466
467	# do not update if DataReady covers the whole image at once
468	return if $y == 0 and $x == 0 && $h == $image-> height && $w == $image-> width;
469
470	$self-> image-> put_image_indirect( $image, $x, $y, $x, $y, $w, $h, $w, $h, rop::CopyPut);
471	my @r = $self-> point2screen( $x, $y, $x + $w, $y + $h);
472	$self-> invalidate_rect(
473		(map { int($_) } @r[0,1]),
474		(map { int($_ + .5) + 1 } @r[2,3])
475	);
476	$self-> update_view;
477}
478
479sub watch_load_progress
480{
481	my ( $self, $image) = @_;
482
483	$self-> unwatch_load_progress(0);
484
485	my @ids =
486		$image-> add_notification( 'HeaderReady', \&PreviewImage_HeaderReady, $self),
487		$image-> add_notification( 'DataReady',   \&PreviewImage_DataReady,   $self)
488		;
489	$self-> {__watch} = {
490		notifications => \@ids,
491	};
492}
493
494sub unwatch_load_progress
495{
496	my ( $self, $clear_image) = @_;
497
498	return unless $self-> {__watch};
499
500	if ( $self-> {image}) {
501		$self-> {image}-> remove_notification($_)
502			for @{ $self-> {__watch}-> {notifications} };
503	}
504
505	if ( $self-> {__watch}-> {preview} && ($clear_image // 1)) {
506		$self-> image( undef);
507	}
508
509	delete $self-> {__watch};
510}
511
5121;
513
514=pod
515
516=head1 NAME
517
518Prima::ImageViewer - standard image, icon, and bitmap viewer class.
519
520=head2 SYNOPSIS
521
522	use Prima qw(ImageViewer StdBitmap Application);
523	Prima::ImageViewer-> new(
524		image => Prima::StdBitmap::image(0),
525		zoom  => 2.718,
526	);
527	run Prima;
528
529=for podview <img src="imageviewer.gif">
530
531=for html <p><img src="https://raw.githubusercontent.com/dk/Prima/master/pod/Prima/imageviewer.gif">
532
533=head1 DESCRIPTION
534
535The module contains C<Prima::ImageViewer> class, which provides
536image displaying functionality, including different zoom levels.
537
538C<Prima::ImageViewer> is a descendant of C<Prima::ScrollWidget>
539and inherits its document scrolling behavior and programming interface.
540See L<Prima::ScrollWidget> for details.
541
542=head1 API
543
544=head2 Properties
545
546=over
547
548=item alignment INTEGER
549
550One of the following C<ta::XXX> constants:
551
552	ta::Left
553	ta::Center
554	ta::Right
555
556Selects the horizontal image alignment.
557
558Default value: C<ta::Left>
559
560=item autoZoom BOOLEAN
561
562When set, the image is automatically stretched while keeping aspects to the best available fit,
563given the C<zoomPrecision>. Scrollbars are turned off if C<autoZoom> is set to 1.
564
565=item image OBJECT
566
567Selects the image object to be displayed. OBJECT can be
568an instance of C<Prima::Image>, C<Prima::Icon>, or C<Prima::DeviceBitmap> class.
569
570=item imageFile FILE
571
572Set the image FILE to be loaded and displayed. Is rarely used since does not return
573a loading success flag.
574
575=item stretch BOOLEAN
576
577If set, the image is simply stretched over the visual area,
578without keeping the aspect. Scroll bars, zooming and
579keyboard navigation become disabled.
580
581=item quality BOOLEAN
582
583A boolean flag, selecting if the palette of C<image> is to be
584copied into the widget palette, providing higher visual
585quality on paletted displays. See also L<Prima::Widget/palette>.
586
587Default value: 1
588
589=item valignment INTEGER
590
591One of the following C<ta::XXX> constants:
592
593	ta::Top
594	ta::Middle or ta::Center
595	ta::Bottom
596
597Selects the vertical image alignment.
598
599NB: C<ta::Middle> value is not equal to C<ta::Center>'s, however
600the both constants produce equal effect here.
601
602Default value: C<ta::Bottom>
603
604=item zoom FLOAT
605
606Selects zoom level for image display. The acceptable value range is between
6070.01 and 100. The zoom value is rounded to the closest value divisible by
6081/C<zoomPrecision>. For example, is C<zoomPrecision> is 100, the zoom values
609will be rounded to the precision of hundredth - to fiftieth and twentieth
610fractional values - .02, .04, .05, .06, .08, and 0.1 . When C<zoomPrecision>
611is 1000, the precision is one thousandth, and so on.
612
613Default value: 1
614
615=item zoomPrecision INTEGER
616
617Zoom precision of C<zoom> property. Minimal acceptable value is 10, where zoom
618will be rounded to 0.2, 0.4, 0.5, 0.6, 0.8 and 1.0 .
619
620The reason behind this arithmetics is that when an image of an arbitrary zoom factor
621is requested to be displayed, the image sometimes must be drawn from
622a fraction image pixel - for example, 10x zoomed image shifted 3 pixels left, must be
623displayed so the first image pixel from the left occupies 7 screen pixels, and
624the next ones - 10 screen pixels.  That means, that the correct image display
625routine must ask the system to draw the image at offset -3 screen pixels, where
626the first image pixel column would correspond to that offset.
627
628When the zoom factor is fractional, the picture is getting more complex. For
629example, with zoom factor 12.345, and zero screen offset, the first image pixel
630begins at the 12th screen pixel, the next one - at the 25th ( because of the
631roundoff ), then the 37th etc etc. If the image is 2000x2000 pixels wide, and
632is asked to be drawn so that it appears shifted 499 screen image pixels left,
633it needs to be drawn from the 499/12.345=40.42122th image pixel. Is might seem
634that indeed it would be enough to ask the system to begin drawing from image
635pixel 40, and offset int(0.42122*12.345)=5 screen pixels to the left, however,
636that procedure will not account for the correct fixed point roundoff that
637accumulates as system scales the image. For zoom factor 12.345 this roundoff
638sequence is, as we seen before, (12,25,37,49,62,74,86,99,111,123) for the first
63910 pixels displayed, that occupy (12,13,12,12,13,12,12,13,12,12) screen pixels
640correspondingly.  For the pixels starting at 499, the sequence is
641(506,519,531,543,556,568,580,593,605,617) offsets or
642(13,12,12,13,13,12,12,13,12,12) widths -- note the two subsequent 13s there.
643This sequence begins to repeat itself after 200 iterations
644(12.345*200=2469.000), which means that in order to achieve correct display
645results, the image must be asked to be displayed from as far as image pixel 0
646if image's first pixel on the screen is between 0 and 199 ( or for screen
647pixels 0-2468), then from image pixel 200 for offsets 200-399, ( screen pixels
6482469-4937), and so on.
649
650Since the system internally allocates memory for image scaling, that means that up
651to 2*200*min(window_width,image_width)*bytes_per_pixel unneccessary bytes will
652be allocated for each image drawing call (2 because the calculations are valid
653for both the vertical and horizontal strips), and this can lead to slowdown or
654even request failure when image or window dimensions are large. The proposed
655solution is to roundoff accepted zoom factors, so these offsets are kept small
656- for example, N.25 zoom factors require only max 1/.25=4 extra pixels. When
657C<zoomPrecision> value is 100, zoom factors are rounded to 0.X2, 0.X4, 0.X5,
6580.X6, 0.X8, 0.X0, thus requiring max 50 extra pixels.
659
660NB. If, despite the efforts, the property gets in the way, increase it to
6611000 or even 10000, but note that this may lead to problems.
662
663Default value: 100
664
665=back
666
667=head2 Methods
668
669=over
670
671=item on_paint SELF, CANVAS
672
673The C<Paint> notification handler is mentioned here for the specific case
674of its return value, that is the return value of internal C<put_image> call.
675For those who might be interested in C<put_image> failures, that mostly occur
676when trying to draw an image that is too big, the following code might be
677useful:
678
679    sub on_paint
680    {
681        my ( $self, $canvas) = @_;
682	warn "put_image() error:$@" unless $self-> SUPER::on_paint($canvas);
683    }
684
685=item screen2point X, Y, [ X, Y, ... ]
686
687Performs translation of integer pairs integers as (X,Y)-points from widget coordinates
688to pixel offset in image coordinates. Takes in account zoom level,
689image alignments, and offsets. Returns array of same length as the input.
690
691Useful for determining correspondence, for example, of a mouse event
692to a image point.
693
694The reverse function is C<point2screen>.
695
696=item point2screen   X, Y, [ X, Y, ... ]
697
698Performs translation of integer pairs as (X,Y)-points from image pixel offset
699to widget image coordinates. Takes in account zoom level,
700image alignments, and offsets. Returns array of same length as the input.
701
702Useful for determining a screen location of an image point.
703
704The reverse function is C<screen2point>.
705
706=item watch_load_progress IMAGE
707
708When called, image viewer watches as IMAGE is being loaded ( see L<Prima::Image/load> )
709and displays the progress. As soon as IMAGE begins to load, it replaces the existing C<image>
710property. Example:
711
712    $i = Prima::Image-> new;
713    $viewer-> watch_load_progress( $i);
714    $i-> load('huge.jpg');
715    $viewer-> unwatch_load_progress;
716
717Similar functionality is present in L<Prima::Dialog::ImageDialog>.
718
719=item unwatch_load_progress CLEAR_IMAGE=1
720
721Stops monitoring of image loading progress. If CLEAR_IMAGE is 0, the leftovers of the
722incremental loading stay intact in C<image> propery. Otherwise, C<image> is set to C<undef>.
723
724=item zoom_round ZOOM
725
726Rounds the zoom factor to C<zoomPrecision> precision, returns
727the rounded zoom value. The algorithm is the same as used internally
728in C<zoom> property.
729
730=back
731
732=head1 AUTHOR
733
734Dmitry Karasik, E<lt>dmitry@karasik.eu.orgE<gt>.
735
736=head1 SEE ALSO
737
738L<Prima>, L<Prima::Image>, L<Prima::ScrollWidget>, L<Prima::Dialog::ImageDialog>, F<examples/iv.pl>.
739
740=cut
741