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