1package Gtk3::ImageView; 2 3use warnings; 4use strict; 5no if $] >= 5.018, warnings => 'experimental::smartmatch'; 6use feature 'switch'; 7use Cairo; 8use Glib qw(TRUE FALSE); # To get TRUE and FALSE 9use Gtk3; 10use Gtk3::ImageView::Tool; 11use Gtk3::ImageView::Tool::Dragger; 12use Gtk3::ImageView::Tool::Selector; 13use List::Util qw(min); 14use Scalar::Util qw(blessed); 15use Carp; 16use Readonly; 17Readonly my $HALF => 0.5; 18Readonly my $MAX_ZOOM => 100; 19 20our $VERSION = 9; 21 22use Glib::Object::Subclass Gtk3::DrawingArea::, signals => { 23 'zoom-changed' => { 24 param_types => ['Glib::Float'], # new zoom 25 }, 26 'offset-changed' => { 27 param_types => [ 'Glib::Int', 'Glib::Int' ], # new offset 28 }, 29 'selection-changed' => { 30 param_types => ['Glib::Scalar'], # Gdk::Rectangle of selection area 31 }, 32 'tool-changed' => { 33 param_types => ['Glib::Scalar'], # new Gtk3::ImageView::Tool 34 }, 35 'dnd-start' => { 36 param_types => [ 37 'Glib::Float', # x 38 'Glib::Float', # y 39 'Glib::UInt', # button 40 ], 41 return_type => 'Glib::Boolean', 42 flags => ['run-last'], 43 } 44 }, 45 properties => [ 46 Glib::ParamSpec->object( 47 'pixbuf', # name 48 'pixbuf', # nickname 49 'Gtk3::Gdk::Pixbuf to be shown', # blurb 50 'Gtk3::Gdk::Pixbuf', 51 [qw/readable writable/] # flags 52 ), 53 Glib::ParamSpec->scalar( 54 'offset', # name 55 'Image offset', # nick 56 'Gdk::Rectangle hash of x, y', # blurb 57 [qw/readable writable/] # flags 58 ), 59 Glib::ParamSpec->float( 60 'zoom', # name 61 'zoom', # nick 62 'zoom level', # blurb 63 0.001, # minimum 64 100.0, # maximum 65 1.0, # default_value 66 [qw/readable writable/] # flags 67 ), 68 Glib::ParamSpec->float( 69 'zoom-step', # name 70 'Zoom step', # nick 71 'Zoom coefficient for every scrolling step', # blurb 72 1.0, # minimum 73 10.0, # maximum 74 2.0, # default_value 75 [qw/readable writable/] # flags 76 ), 77 Glib::ParamSpec->float( 78 'resolution-ratio', # name 79 'resolution-ratio', # nick 80 'Ratio of x-resolution/y-resolution', # blurb 81 0.0001, # minimum 82 1000.0, # maximum 83 1.0, # default_value 84 [qw/readable writable/] # flags 85 ), 86 Glib::ParamSpec->scalar( 87 'tool', # name 88 'tool', # nickname 89 'Active Gtk3::ImageView::Tool', # blurb 90 [qw/readable writable/] # flags 91 ), 92 Glib::ParamSpec->scalar( 93 'selection', # name 94 'Selection', # nick 95 'Gdk::Rectangle hash of selected region', # blurb 96 [qw/readable writable/] # flags 97 ), 98 Glib::ParamSpec->boolean( 99 'zoom-to-fit', # name 100 'Zoom to fit', # nickname 101 'Whether the zoom factor is automatically calculated to fit the window' 102 , # blurb 103 TRUE, # default 104 [qw/readable writable/] # flags 105 ), 106 Glib::ParamSpec->float( 107 'zoom-to-fit-limit', # name 108 'Zoom to fit limit', # nickname 109 'When zooming automatically, don\'t zoom more than this', # blurb 110 0.0001, # minimum 111 100.0, # maximum 112 100.0, # default_value 113 [qw/readable writable/] # flags 114 ), 115 Glib::ParamSpec->string( 116 'interpolation', # name 117 'interpolation', # nick 118 'Interpolation method to use, from Cairo::Filter', # blurb 119 'good', # default 120 [qw/readable writable/], # flags 121 ), 122 ]; 123 124sub INIT_INSTANCE { 125 my $self = shift; 126 $self->signal_connect( draw => \&_draw ); 127 $self->signal_connect( 'button-press-event' => \&_button_pressed ); 128 $self->signal_connect( 'button-release-event' => \&_button_released ); 129 $self->signal_connect( 'motion-notify-event' => \&_motion ); 130 $self->signal_connect( 'scroll-event' => \&_scroll ); 131 $self->signal_connect( configure_event => \&_configure_event ); 132 $self->set_app_paintable(TRUE); 133 134 if ( 135 $Glib::Object::Introspection::VERSION < 136 0.043 ## no critic (ProhibitMagicNumbers) 137 ) 138 { 139 $self->add_events( 140 ${ Gtk3::Gdk::EventMask->new(qw/exposure-mask/) } | 141 ${ Gtk3::Gdk::EventMask->new(qw/button-press-mask/) } | 142 ${ Gtk3::Gdk::EventMask->new(qw/button-release-mask/) } | 143 ${ Gtk3::Gdk::EventMask->new(qw/pointer-motion-mask/) } | 144 ${ Gtk3::Gdk::EventMask->new(qw/scroll-mask/) } ); 145 } 146 else { 147 $self->add_events( 148 Glib::Object::Introspection->convert_sv_to_flags( 149 'Gtk3::Gdk::EventMask', 'exposure-mask' ) | 150 Glib::Object::Introspection->convert_sv_to_flags( 151 'Gtk3::Gdk::EventMask', 'button-press-mask' ) | 152 Glib::Object::Introspection->convert_sv_to_flags( 153 'Gtk3::Gdk::EventMask', 'button-release-mask' ) | 154 Glib::Object::Introspection->convert_sv_to_flags( 155 'Gtk3::Gdk::EventMask', 'pointer-motion-mask' ) | 156 Glib::Object::Introspection->convert_sv_to_flags( 157 'Gtk3::Gdk::EventMask', 'scroll-mask' 158 ) 159 ); 160 } 161 $self->set_tool( Gtk3::ImageView::Tool::Dragger->new($self) ); 162 $self->set_redraw_on_allocate(FALSE); 163 return $self; 164} 165 166sub SET_PROPERTY { 167 my ( $self, $pspec, $newval ) = @_; 168 my $name = $pspec->get_name; 169 my $oldval = $self->get($name); 170 my $invalidate = FALSE; 171 if ( ( defined $newval and defined $oldval and $newval ne $oldval ) 172 or ( defined $newval xor defined $oldval ) ) 173 { 174 given ($name) { 175 when ('pixbuf') { 176 $self->{$name} = $newval; 177 $invalidate = TRUE; 178 } 179 when ('zoom') { 180 $self->{$name} = $newval; 181 $self->signal_emit( 'zoom-changed', $newval ); 182 $invalidate = TRUE; 183 } 184 when ('offset') { 185 if ( ( defined $newval xor defined $oldval ) 186 or $oldval->{x} != $newval->{x} 187 or $oldval->{y} != $newval->{y} ) 188 { 189 $self->{$name} = $newval; 190 $self->signal_emit( 'offset-changed', $newval->{x}, 191 $newval->{y} ); 192 $invalidate = TRUE; 193 } 194 } 195 when ('resolution-ratio') { 196 $self->{$name} = $newval; 197 $invalidate = TRUE; 198 } 199 when ('interpolation') { 200 $self->{$name} = $newval; 201 $invalidate = TRUE; 202 } 203 when ('selection') { 204 if ( ( defined $newval xor defined $oldval ) 205 or $oldval->{x} != $newval->{x} 206 or $oldval->{y} != $newval->{y} 207 or $oldval->{width} != $newval->{width} 208 or $oldval->{height} != $newval->{height} ) 209 { 210 $self->{$name} = $newval; 211 $invalidate = TRUE; 212 $self->signal_emit( 'selection-changed', $newval ); 213 } 214 } 215 when ('tool') { 216 $self->{$name} = $newval; 217 if ( defined $self->get_selection ) { 218 $invalidate = TRUE; 219 } 220 $self->signal_emit( 'tool-changed', $newval ); 221 } 222 default { 223 $self->{$name} = $newval; 224 225 # $self->SUPER::SET_PROPERTY( $pspec, $newval ); 226 } 227 } 228 if ($invalidate) { 229 $self->queue_draw(); 230 } 231 } 232 return; 233} 234 235sub set_pixbuf { 236 my ( $self, $pixbuf, $zoom_to_fit ) = @_; 237 $self->set( 'pixbuf', $pixbuf ); 238 $self->set_zoom_to_fit($zoom_to_fit); 239 if ( not $zoom_to_fit ) { 240 $self->set_offset( 0, 0 ); 241 } 242 return; 243} 244 245sub get_pixbuf { 246 my ($self) = @_; 247 return $self->get('pixbuf'); 248} 249 250sub get_pixbuf_size { 251 my ($self) = @_; 252 my $pixbuf = $self->get_pixbuf; 253 if ( defined $pixbuf ) { 254 return { width => $pixbuf->get_width, height => $pixbuf->get_height }; 255 } 256 return; 257} 258 259sub _button_pressed { 260 my ( $self, $event ) = @_; 261 return $self->get_tool->button_pressed($event); 262} 263 264sub _button_released { 265 my ( $self, $event ) = @_; 266 $self->get_tool->button_released($event); 267 return; 268} 269 270sub _motion { 271 my ( $self, $event ) = @_; 272 $self->update_cursor( $event->x, $event->y ); 273 $self->get_tool->motion($event); 274 return; 275} 276 277sub _scroll { 278 my ( $self, $event ) = @_; 279 my ( $center_x, $center_y ) = 280 $self->to_image_coords( $event->x, $event->y ); 281 my $zoom; 282 $self->set_zoom_to_fit(FALSE); 283 if ( $event->direction eq 'up' ) { 284 $zoom = $self->get_zoom * $self->get('zoom-step'); 285 } 286 else { 287 $zoom = $self->get_zoom / $self->get('zoom-step'); 288 } 289 $self->_set_zoom_with_center( $zoom, $center_x, $center_y ); 290 return; 291} 292 293sub _draw { 294 my ( $self, $context ) = @_; 295 my $allocation = $self->get_allocation; 296 my $style = $self->get_style_context; 297 my $pixbuf = $self->get_pixbuf; 298 my $ratio = $self->get_resolution_ratio; 299 my $viewport = $self->get_viewport; 300 $style->add_class('imageview'); 301 302 $style->save; 303 $style->add_class(Gtk3::STYLE_CLASS_BACKGROUND); 304 Gtk3::render_background( $style, $context, $allocation->{x}, 305 $allocation->{y}, $allocation->{width}, $allocation->{height} ); 306 $style->restore; 307 308 if ( defined $pixbuf ) { 309 if ( $pixbuf->get_has_alpha ) { 310 $style->save; 311 312 # '.imageview' affects also area outside of the image. But only 313 # when background-image is specified. background-color seems to 314 # have no effect there. Probably a bug in Gtk? Either way, this is 315 # why need a special class 'transparent' to match the correct area 316 # inside the image where both image and color work. 317 $style->add_class('transparent'); 318 my ( $x1, $y1 ) = $self->to_widget_coords( 0, 0 ); 319 my ( $x2, $y2 ) = 320 $self->to_widget_coords( $pixbuf->get_width, 321 $pixbuf->get_height ); 322 Gtk3::render_background( $style, $context, $x1, $y1, $x2 - $x1, 323 $y2 - $y1 ); 324 $style->restore; 325 } 326 327 my $zoom = $self->get_zoom / $self->get('scale-factor'); 328 $context->scale( $zoom / $ratio, $zoom ); 329 my $offset = $self->get_offset; 330 $context->translate( $offset->{x}, $offset->{y} ); 331 Gtk3::Gdk::cairo_set_source_pixbuf( $context, $pixbuf, 0, 0 ); 332 $context->get_source->set_filter( $self->get_interpolation ); 333 } 334 else { 335 my $bgcol = $style->get( 'normal', 'background-color' ); 336 Gtk3::Gdk::cairo_set_source_rgba( $context, $bgcol ); 337 } 338 $context->paint; 339 340 my $selection = $self->get_selection; 341 if ( defined $pixbuf and defined $selection ) { 342 my ( $x, $y, $w, $h, ) = ( 343 $selection->{x}, $selection->{y}, 344 $selection->{width}, $selection->{height}, 345 ); 346 if ( $w <= 0 or $h <= 0 ) { return TRUE } 347 348 $style->save; 349 $style->add_class(Gtk3::STYLE_CLASS_RUBBERBAND); 350 Gtk3::render_background( $style, $context, $x, $y, $w, $h ); 351 Gtk3::render_frame( $style, $context, $x, $y, $w, $h ); 352 $style->restore; 353 } 354 return TRUE; 355} 356 357sub _configure_event { 358 my ( $self, $event ) = @_; 359 if ( $self->get_zoom_to_fit ) { 360 $self->zoom_to_box( $self->get_pixbuf_size ); 361 } 362 return; 363} 364 365# setting the zoom via the public API disables zoom-to-fit 366 367sub set_zoom { 368 my ( $self, $zoom ) = @_; 369 $self->set_zoom_to_fit(FALSE); 370 $self->_set_zoom_no_center($zoom); 371 return; 372} 373 374sub _set_zoom { 375 my ( $self, $zoom ) = @_; 376 if ( $zoom > $MAX_ZOOM ) { $zoom = $MAX_ZOOM } 377 $self->set( 'zoom', $zoom ); 378 return; 379} 380 381sub get_zoom { 382 my ($self) = @_; 383 return $self->get('zoom'); 384} 385 386# convert x, y in image coords to widget coords 387sub to_widget_coords { 388 my ( $self, $x, $y ) = @_; 389 my $zoom = $self->get_zoom; 390 my $ratio = $self->get_resolution_ratio; 391 my $offset = $self->get_offset; 392 my $factor = $self->get('scale-factor'); 393 return ( $x + $offset->{x} ) * $zoom / $factor / $ratio, 394 ( $y + $offset->{y} ) * $zoom / $factor; 395} 396 397# convert x, y in widget coords to image coords 398sub to_image_coords { 399 my ( $self, $x, $y ) = @_; 400 my $zoom = $self->get_zoom; 401 my $ratio = $self->get_resolution_ratio; 402 my $offset = $self->get_offset; 403 my $factor = $self->get('scale-factor'); 404 return $x * $factor / $zoom * $ratio - $offset->{x}, 405 $y * $factor / $zoom - $offset->{y}; 406} 407 408# convert x, y in widget distance to image distance 409sub to_image_distance { 410 my ( $self, $x, $y ) = @_; 411 my $zoom = $self->get_zoom; 412 my $ratio = $self->get_resolution_ratio; 413 my $factor = $self->get('scale-factor'); 414 return $x * $factor / $zoom * $ratio, $y * $factor / $zoom; 415} 416 417# set zoom with centre in image coordinates 418sub _set_zoom_with_center { 419 my ( $self, $zoom, $center_x, $center_y ) = @_; 420 my $allocation = $self->get_allocation; 421 my $ratio = $self->get_resolution_ratio; 422 my $factor = $self->get('scale-factor'); 423 my $offset_x = 424 $allocation->{width} * $factor / 2 / $zoom * $ratio - $center_x; 425 my $offset_y = $allocation->{height} * $factor / 2 / $zoom - $center_y; 426 $self->_set_zoom($zoom); 427 $self->set_offset( $offset_x, $offset_y ); 428 return; 429} 430 431# sets zoom, centred on the viewport 432sub _set_zoom_no_center { 433 my ( $self, $zoom ) = @_; 434 my $allocation = $self->get_allocation; 435 my ( $center_x, $center_y ) = 436 $self->to_image_coords( $allocation->{width} / 2, 437 $allocation->{height} / 2 ); 438 $self->_set_zoom_with_center( $zoom, $center_x, $center_y ); 439 return; 440} 441 442sub set_zoom_to_fit { 443 my ( $self, $zoom_to_fit, $limit ) = @_; 444 $self->set( 'zoom-to-fit', $zoom_to_fit ); 445 if ( defined $limit ) { 446 $self->set( 'zoom-to-fit-limit', $limit ); 447 } 448 if ( not $zoom_to_fit ) { return } 449 $self->zoom_to_box( $self->get_pixbuf_size ); 450 return; 451} 452 453sub zoom_to_box { 454 my ( $self, $box, $additional_factor ) = @_; 455 if ( not defined $box ) { return } 456 if ( not defined $box->{x} ) { $box->{x} = 0 } 457 if ( not defined $box->{y} ) { $box->{y} = 0 } 458 if ( not defined $additional_factor ) { $additional_factor = 1 } 459 my $allocation = $self->get_allocation; 460 my $ratio = $self->get_resolution_ratio; 461 my $limit = $self->get('zoom-to-fit-limit'); 462 my $sc_factor_w = 463 min( $limit, $allocation->{width} / $box->{width} ) * $ratio; 464 my $sc_factor_h = 465 min( $limit, $allocation->{height} / $box->{height} ); 466 $self->_set_zoom_with_center( 467 min( $sc_factor_w, $sc_factor_h ) * 468 $additional_factor * 469 $self->get('scale-factor'), 470 ( $box->{x} + $box->{width} / 2 ) / $ratio, 471 $box->{y} + $box->{height} / 2 472 ); 473 return; 474} 475 476sub zoom_to_selection { 477 my ( $self, $context_factor ) = @_; 478 $self->zoom_to_box( $self->get_selection, $context_factor ); 479 return; 480} 481 482sub get_zoom_to_fit { 483 my ($self) = @_; 484 return $self->get('zoom-to-fit'); 485} 486 487sub zoom_in { 488 my ($self) = @_; 489 $self->set_zoom_to_fit(FALSE); 490 $self->_set_zoom_no_center( $self->get_zoom * $self->get('zoom-step') ); 491 return; 492} 493 494sub zoom_out { 495 my ($self) = @_; 496 $self->set_zoom_to_fit(FALSE); 497 $self->_set_zoom_no_center( $self->get_zoom / $self->get('zoom-step') ); 498 return; 499} 500 501sub zoom_to_fit { 502 my ($self) = @_; 503 $self->set_zoom_to_fit(TRUE); 504 return; 505} 506 507sub set_fitting { 508 my ( $self, $value ) = @_; 509 $self->set_zoom_to_fit( $value, 1 ); 510 return; 511} 512 513sub _clamp_direction { 514 my ( $offset, $allocation, $pixbuf_size ) = @_; 515 516 # Centre the image if it is smaller than the widget 517 if ( $allocation > $pixbuf_size ) { 518 $offset = ( $allocation - $pixbuf_size ) / 2; 519 } 520 521 # Otherwise don't allow the LH/top edge of the image to be visible 522 elsif ( $offset > 0 ) { 523 $offset = 0; 524 } 525 526 # Otherwise don't allow the RH/bottom edge of the image to be visible 527 elsif ( $offset < $allocation - $pixbuf_size ) { 528 $offset = $allocation - $pixbuf_size; 529 } 530 return $offset; 531} 532 533sub set_offset { 534 my ( $self, $offset_x, $offset_y ) = @_; 535 if ( not defined $self->get_pixbuf ) { return } 536 537 # Convert the widget size to image scale to make the comparisons easier 538 my $allocation = $self->get_allocation; 539 ( $allocation->{width}, $allocation->{height} ) = 540 $self->to_image_distance( $allocation->{width}, $allocation->{height} ); 541 my $pixbuf_size = $self->get_pixbuf_size; 542 543 $offset_x = _clamp_direction( $offset_x, $allocation->{width}, 544 $pixbuf_size->{width} ); 545 $offset_y = _clamp_direction( $offset_y, $allocation->{height}, 546 $pixbuf_size->{height} ); 547 548 $self->set( 'offset', { x => $offset_x, y => $offset_y } ); 549 return; 550} 551 552sub get_offset { 553 my ($self) = @_; 554 return $self->get('offset'); 555} 556 557sub get_viewport { 558 my ($self) = @_; 559 my $allocation = $self->get_allocation; 560 my $pixbuf = $self->get_pixbuf; 561 my ( $x, $y, $w, $h ); 562 if ( defined $pixbuf ) { 563 ( $x, $y, $w, $h ) = ( 564 $self->to_image_coords( 0, 0 ), 565 $self->to_image_distance( 566 $allocation->{width}, $allocation->{height} 567 ) 568 ); 569 } 570 else { 571 ( $x, $y, $w, $h ) = 572 ( 0, 0, $allocation->{width}, $allocation->{height} ); 573 } 574 return { x => $x, y => $y, width => $w, height => $h }; 575} 576 577sub set_tool { 578 my ( $self, $tool ) = @_; 579 if ( not( blessed $tool and $tool->isa('Gtk3::ImageView::Tool') ) ) { 580 581 # TODO remove this fallback, only accept Tool directly 582 given ($tool) { 583 when ('dragger') { 584 $tool = Gtk3::ImageView::Tool::Dragger->new($self); 585 } 586 when ('selector') { 587 $tool = Gtk3::ImageView::Tool::Selector->new($self); 588 } 589 default { 590 croak 'invalid set_tool call'; 591 } 592 } 593 } 594 $self->set( 'tool', $tool ); 595 return; 596} 597 598sub get_tool { 599 my ($self) = @_; 600 return $self->get('tool'); 601} 602 603sub set_selection { 604 my ( $self, $selection ) = @_; 605 my $pixbuf_size = $self->get_pixbuf_size; 606 if ( not defined $pixbuf_size ) { return } 607 if ( $selection->{x} < 0 ) { 608 $selection->{width} += $selection->{x}; 609 $selection->{x} = 0; 610 } 611 if ( $selection->{y} < 0 ) { 612 $selection->{height} += $selection->{y}; 613 $selection->{y} = 0; 614 } 615 if ( $selection->{x} + $selection->{width} > $pixbuf_size->{width} ) { 616 $selection->{width} = $pixbuf_size->{width} - $selection->{x}; 617 } 618 if ( $selection->{y} + $selection->{height} > $pixbuf_size->{height} ) { 619 $selection->{height} = $pixbuf_size->{height} - $selection->{y}; 620 } 621 $self->set( 'selection', $selection ); 622 return; 623} 624 625sub get_selection { 626 my ($self) = @_; 627 return $self->get('selection'); 628} 629 630sub set_resolution_ratio { 631 my ( $self, $ratio ) = @_; 632 $self->set( 'resolution-ratio', $ratio ); 633 if ( $self->get_zoom_to_fit ) { 634 $self->zoom_to_box( $self->get_pixbuf_size ); 635 } 636 return; 637} 638 639sub get_resolution_ratio { 640 my ($self) = @_; 641 return $self->get('resolution-ratio'); 642} 643 644sub update_cursor { 645 my ( $self, $x, $y ) = @_; 646 my $pixbuf_size = $self->get_pixbuf_size; 647 if ( not defined $pixbuf_size ) { return } 648 my $win = $self->get_window; 649 my $cursor = $self->get_tool->cursor_at_point( $x, $y ); 650 if ( defined $cursor ) { 651 $win->set_cursor($cursor); 652 } 653 654 return; 655} 656 657sub set_interpolation { 658 my ( $self, $interpolation ) = @_; 659 $self->set( 'interpolation', $interpolation ); 660 return; 661} 662 663sub get_interpolation { 664 my ($self) = @_; 665 return $self->get('interpolation'); 666} 667 6681; 669 670__END__ 671 672=encoding utf8 673 674=head1 NAME 675 676Gtk3::ImageView - Image viewer widget for Gtk3 677 678=head1 VERSION 679 6809 681 682=head1 SYNOPSIS 683 684 use Gtk3::ImageView; 685 Gtk3->init; 686 687 $window = Gtk3::Window->new(); 688 689 $view = Gtk3::ImageView->new; 690 $view->set_pixbuf($pixbuf, TRUE); 691 $window->add($view); 692 693 $window->show_all; 694 695=head1 DESCRIPTION 696 697The Gtk3::ImageView widget allows the user to zoom, pan and select the specified 698image and provides hooks to allow additional tools, e.g. painter, to be created 699and used. 700 701Gtk3::ImageView is a Gtk3 port of L<Gtk2::ImageView|Gtk2::ImageView>. 702 703To discuss Gtk3::ImageView or gtk3-perl, ask questions and flame/praise the 704authors, join gtk-perl-list@gnome.org at lists.gnome.org. 705 706=for readme stop 707 708=head1 SUBROUTINES/METHODS 709 710=head2 $view->set_pixbuf( $pixbuf, $zoom_to_fit ) 711 712Defines the image to view. The optional zoom_to_fit parameter specifies whether 713to zoom to fit the image or not. 714 715=head2 $view->get_pixbuf 716 717Returns the image currently being viewed. 718 719=head2 $view->get_pixbuf_size 720 721Returns a hash of containing the size of the current image in width and height 722keys. 723 724=head2 $view->set_zoom($zoom) 725 726Specifies the zoom level. 727 728=head2 $view->get_zoom 729 730Returns the current zoom level. 731 732=head2 $view->set_zoom_to_fit($zoom_to_fit, $limit) 733 734Specifies whether to zoom to fit or not. If limit is defined, such automatic zoom will not zoom more than it. If the limit is undefined, the previously set limit is used, initially it's unlimited. 735 736=head2 $view->zoom_to_box( $box, $additional_factor ) 737 738Specifies a box to zoom to, including an additional zoom factor 739 740=head2 $view->zoom_to_selection($context_factor) 741 742Zooms to the current selection, plus an addition zoom factor. Shortcut for 743 744 $view->zoom_to_box( $view->get_selection, $context_factor ); 745 746=head2 $view->get_zoom_to_fit 747 748Returns whether the view is currently zoom to fit or not. 749 750=head2 $view->zoom_in 751 752Increases the current zoom by C<zoom-step> times (defaults to 2). 753 754=head2 $view->zoom_out 755 756Decreases the current zoom by C<zoom-step> times (defaults to 2). 757 758=head2 $view->zoom_to_fit 759 760Shortcut for 761 762 $view->set_zoom_to_fit(TRUE); 763 764=head2 $view->set_fitting( $value ) 765 766Shortcut for 767 768 $view->set_zoom_to_fit($value, 1); 769 770which means that it won't stretch a small image to a large window. Provided 771(despite the ambiguous name) for compatibility with Gtk2::ImageView. 772 773=head2 $view->set_offset( $x, $y ) 774 775Set the current view offset (i.e. pan position). 776 777=head2 $view->set_offset 778 779Returns the current view offset (i.e. pan position). 780 781=head2 $view->get_viewport 782 783Returns a hash containing the position and size of the current viewport. 784 785=head2 $view->set_tool 786 787Set the current tool (i.e. mode) - an object of a subclass of 788C<Gtk3::ImageView::Tool>. 789 790Here are some known subclasses of it: 791 792=over 1 793 794=item * C<Gtk3::ImageView::Tool::Dragger> allows the image to be dragged when zoomed. 795 796=item * C<Gtk3::ImageView::Tool::Selector> allows the user to select a rectangular area of the image. 797 798=item * C<Gtk3::ImageView::Tool::SelectorDragger> selects or drags with different mouse buttons. 799 800=back 801 802Don't rely too much on how Tool currently interacts with ImageView. 803 804=head2 $view->get_tool 805 806Returns the current tool (i.e. mode). 807 808=head2 $view->set_selection($selection) 809 810Set the current selection as a hash of position and size. 811 812=head2 $view->get_selection 813 814Returns the current selection as a hash of position and size. 815 816=head2 $view->set_resolution_ratio($ratio) 817 818Set the ratio of the resolutions in the x and y directions, allowing images 819with non-square pixels to be correctly displayed. 820 821=head2 $view->get_resolution_ratio 822 823Returns the current resolution ratio. 824 825=head2 $view->set_interpolation 826 827The interpolation method to use, from L<C<cairo_filter_t>|https://www.cairographics.org/manual/cairo-cairo-pattern-t.html#cairo-filter-t> type. Possible values are lowercase strings like C<nearest>. To use different interpolation depending on zoom, set it in the C<zoom-changed> signal. 828 829=head2 $view->get_interpolation 830 831Returns the current interpolation method. 832 833=head1 DIAGNOSTICS 834 835=head1 CONFIGURATION AND ENVIRONMENT 836 837=head1 DEPENDENCIES 838 839=head1 INCOMPATIBILITIES 840 841=head2 Porting from L<Gtk2::ImageView|Gtk2::ImageView> 842 843=over 1 844 845=item * C<set_from_pixbuf()> was renamed to C<set_pixbuf()> and now its second argument means C<zoom_to_fit()> instead of C<set_fitting()>. 846 847=item * C<set_fitting(TRUE)> used to be the default, now you need to call it explicitly if you want that behavior. However, once it's called, new calls to C<set_from_pixbuf()> won't reset it, see C<set_zoom_to_fit()> for more details.. 848 849=item * Drag and drop now can be triggered by subscribing to C<dnd-start> signal, and calling C<$view-E<gt>drag_begin_with_coordinates()> from the handler. C<drag_source_set()> won't work. 850 851=item * C<Gtk2::ImageView::ScrollWin> replacement is not yet implemented. 852 853=item * C<set_transp()> is now available through L<CSS|https://developer.gnome.org/gtk3/stable/chap-css-overview.html> instead, e.g. via 854 855 .imageview.transparent { 856 background-image: url('checkers.svg'); 857 } 858 859=item * C<set_interpolation()> now accepts L<C<cairo_filter_t>|https://www.cairographics.org/manual/cairo-cairo-pattern-t.html#cairo-filter-t> instead of L<C<GdkInterpType>|https://developer.gnome.org/gdk-pixbuf/stable/gdk-pixbuf-Scaling.html#GdkInterpType>. 860 861=back 862 863=head1 BUGS AND LIMITATIONS 864 865This should be rewritten on C, and Perl bindings should be provided via Glib Object Introspection. 866 867=head1 AUTHOR 868 869Jeffrey Ratcliffe, E<lt>jffry@posteo.netE<gt> 870 871Alexey Sokolov E<lt>sokolov@google.comE<gt> 872 873=head1 LICENSE AND COPYRIGHT 874 875Copyright (C) 2018--2020 by Jeffrey Ratcliffe 876 877Copyright (C) 2020 by Google LLC 878 879Modelled after the GtkImageView C widget by Björn Lindqvist <bjourne@gmail.com> 880 881This library is free software; you can redistribute it and/or modify 882it under the same terms as Perl itself, either Perl version 5.8.5 or, 883at your option, any later version of Perl 5 you may have available. 884 885=cut 886