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