gimpdisplayshell-scale.c 42.3 KB
Newer Older
1
/* GIMP - The GNU Image Manipulation Program
Elliot Lee's avatar
Elliot Lee committed
2 3
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
4
 * This program is free software: you can redistribute it and/or modify
Elliot Lee's avatar
Elliot Lee committed
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation; either version 3 of the License, or
Elliot Lee's avatar
Elliot Lee committed
7 8 9 10 11 12 13 14
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
15
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
Elliot Lee's avatar
Elliot Lee committed
16
 */
Sven Neumann's avatar
Sven Neumann committed
17 18 19

#include "config.h"

20 21
#include <math.h>

22
#include <gegl.h>
23
#include <gtk/gtk.h>
Sven Neumann's avatar
Sven Neumann committed
24

25
#include "libgimpbase/gimpbase.h"
26
#include "libgimpmath/gimpmath.h"
27
#include "libgimpwidgets/gimpwidgets.h"
28

29
#include "display-types.h"
Sven Neumann's avatar
Sven Neumann committed
30

31
#include "config/gimpguiconfig.h"
32 33

#include "core/gimp.h"
34 35
#include "core/gimpimage.h"

36
#include "gimpdisplay.h"
Michael Natterer's avatar
Michael Natterer committed
37
#include "gimpdisplayshell.h"
38
#include "gimpdisplayshell-expose.h"
39
#include "gimpdisplayshell-rotate.h"
40 41
#include "gimpdisplayshell-scale.h"
#include "gimpdisplayshell-scroll.h"
42
#include "gimpdisplayshell-transform.h"
43
#include "gimpimagewindow.h"
44

45

Martin Nordholts's avatar
Martin Nordholts committed
46 47 48
#define SCALE_TIMEOUT             2
#define SCALE_EPSILON             0.0001
#define ALMOST_CENTERED_THRESHOLD 2
49

50 51 52
#define SCALE_EQUALS(a,b) (fabs ((a) - (b)) < SCALE_EPSILON)


53
/*  local function prototypes  */
Elliot Lee's avatar
Elliot Lee committed
54

55 56 57 58
static void      gimp_display_shell_scale_get_screen_resolution
                                                         (GimpDisplayShell *shell,
                                                          gdouble          *xres,
                                                          gdouble          *yres);
59 60 61 62 63
static void      gimp_display_shell_scale_get_image_size_for_scale
                                                         (GimpDisplayShell *shell,
                                                          gdouble           scale,
                                                          gint             *w,
                                                          gint             *h);
64 65 66 67 68
static void      gimp_display_shell_calculate_scale_x_and_y
                                                         (GimpDisplayShell *shell,
                                                          gdouble           scale,
                                                          gdouble          *scale_x,
                                                          gdouble          *scale_y);
69

70
static void      gimp_display_shell_scale_to             (GimpDisplayShell *shell,
Sven Neumann's avatar
Sven Neumann committed
71
                                                          gdouble           scale,
72 73
                                                          gdouble           viewport_x,
                                                          gdouble           viewport_y);
74 75
static void      gimp_display_shell_scale_fit_or_fill    (GimpDisplayShell *shell,
                                                          gboolean          fill);
76 77

static gboolean  gimp_display_shell_scale_image_starts_to_fit
Sven Neumann's avatar
Sven Neumann committed
78 79 80 81 82
                                                         (GimpDisplayShell *shell,
                                                          gdouble           new_scale,
                                                          gdouble           current_scale,
                                                          gboolean         *vertically,
                                                          gboolean         *horizontally);
83
static gboolean  gimp_display_shell_scale_viewport_coord_almost_centered
Sven Neumann's avatar
Sven Neumann committed
84 85 86 87 88 89
                                                         (GimpDisplayShell *shell,
                                                          gint              x,
                                                          gint              y,
                                                          gboolean         *horizontally,
                                                          gboolean         *vertically);

90 91 92 93 94 95
static void      gimp_display_shell_scale_get_image_center_viewport
                                                         (GimpDisplayShell *shell,
                                                          gint             *image_center_x,
                                                          gint             *image_center_y);


Sven Neumann's avatar
Sven Neumann committed
96 97 98
static void      gimp_display_shell_scale_get_zoom_focus (GimpDisplayShell *shell,
                                                          gdouble           new_scale,
                                                          gdouble           current_scale,
99 100
                                                          gdouble          *x,
                                                          gdouble          *y,
101
                                                          GimpZoomFocus     zoom_focus);
Sven Neumann's avatar
Sven Neumann committed
102

Elliot Lee's avatar
Elliot Lee committed
103

104
/*  public functions  */
Elliot Lee's avatar
Elliot Lee committed
105

106 107 108 109
/**
 * gimp_display_shell_scale_revert:
 * @shell:     the #GimpDisplayShell
 *
110 111
 * Reverts the display to the previously used scale. If no previous
 * scale exist, then the call does nothing.
112 113 114 115 116 117 118 119 120 121 122 123
 *
 * Return value: %TRUE if the scale was reverted, otherwise %FALSE.
 **/
gboolean
gimp_display_shell_scale_revert (GimpDisplayShell *shell)
{
  g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);

  /* don't bother if no scale has been set */
  if (shell->last_scale < SCALE_EPSILON)
    return FALSE;

124 125
  shell->last_scale_time = 0;

126 127 128 129 130 131 132 133 134 135 136
  gimp_display_shell_scale_by_values (shell,
                                      shell->last_scale,
                                      shell->last_offset_x,
                                      shell->last_offset_y,
                                      FALSE);   /* don't resize the window */

  return TRUE;
}

/**
 * gimp_display_shell_scale_can_revert:
137
 * @shell: the #GimpDisplayShell
138 139 140 141 142 143 144 145 146 147 148
 *
 * Return value: %TRUE if a previous display scale exists, otherwise %FALSE.
 **/
gboolean
gimp_display_shell_scale_can_revert (GimpDisplayShell *shell)
{
  g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);

  return (shell->last_scale > SCALE_EPSILON);
}

149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
/**
 * gimp_display_shell_scale_save_revert_values:
 * @shell:
 *
 * Handle the updating of the Revert Zoom variables.
 **/
void
gimp_display_shell_scale_save_revert_values (GimpDisplayShell *shell)
{
  guint now;

  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

  now = time (NULL);

  if (now - shell->last_scale_time >= SCALE_TIMEOUT)
    {
      shell->last_scale    = gimp_zoom_model_get_factor (shell->zoom);
      shell->last_offset_x = shell->offset_x;
      shell->last_offset_y = shell->offset_y;
    }

  shell->last_scale_time = now;
}

174 175 176 177 178 179 180 181 182
/**
 * gimp_display_shell_scale_set_dot_for_dot:
 * @shell:        the #GimpDisplayShell
 * @dot_for_dot:  whether "Dot for Dot" should be enabled
 *
 * If @dot_for_dot is set to %TRUE then the "Dot for Dot" mode (where image and
 * screen pixels are of the same size) is activated. Dually, the mode is
 * disabled if @dot_for_dot is %FALSE.
 **/
Michael Natterer's avatar
Michael Natterer committed
183 184 185 186 187 188
void
gimp_display_shell_scale_set_dot_for_dot (GimpDisplayShell *shell,
                                          gboolean          dot_for_dot)
{
  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

189
  if (dot_for_dot != shell->dot_for_dot)
Michael Natterer's avatar
Michael Natterer committed
190
    {
191 192 193 194 195 196 197
      GimpDisplayConfig *config = shell->display->config;
      gboolean           resize_window;

      /* Resize windows only in multi-window mode */
      resize_window = (config->resize_windows_on_zoom &&
                       ! GIMP_GUI_CONFIG (config)->single_window_mode);

198
      /* freeze the active tool */
199
      gimp_display_shell_pause (shell);
200

201
      shell->dot_for_dot = dot_for_dot;
Michael Natterer's avatar
Michael Natterer committed
202

203
      gimp_display_shell_scale_update (shell);
204

205
      gimp_display_shell_scale_resize (shell, resize_window, FALSE);
206 207

      /* re-enable the active tool */
208
      gimp_display_shell_resume (shell);
Michael Natterer's avatar
Michael Natterer committed
209 210 211
    }
}

212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
/**
 * gimp_display_shell_scale_get_image_size:
 * @shell:
 * @w:
 * @h:
 *
 * Gets the size of the rendered image after it has been scaled.
 *
 **/
void
gimp_display_shell_scale_get_image_size (GimpDisplayShell *shell,
                                         gint             *w,
                                         gint             *h)
{
  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

  gimp_display_shell_scale_get_image_size_for_scale (shell,
                                                     gimp_zoom_model_get_factor (shell->zoom),
                                                     w, h);
}

233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
/**
 * gimp_display_shell_scale_get_image_bounds:
 * @shell:
 * @x:
 * @y:
 * @w:
 * @h:
 *
 * Gets the screen-space boudning box of the image, after it has
 * been transformed (i.e., scaled, rotated, and scrolled).
 **/
void
gimp_display_shell_scale_get_image_bounds (GimpDisplayShell *shell,
                                           gint             *x,
                                           gint             *y,
                                           gint             *w,
                                           gint             *h)
{
  GimpImage *image;
  gdouble    x1, y1;
  gdouble    x2, y2;

  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

  image = gimp_display_get_image (shell->display);

  gimp_display_shell_transform_bounds (shell,
                                       0, 0,
                                       gimp_image_get_width (image),
                                       gimp_image_get_height (image),
                                       &x1, &y1,
                                       &x2, &y2);

  x1 = ceil (x1);
  y1 = ceil (y1);
  x2 = floor (x2);
  y2 = floor (y2);

  if (x) *x = x1 + shell->offset_x;
  if (y) *y = y1 + shell->offset_y;
  if (w) *w = x2 - x1;
  if (h) *h = y2 - y1;
}

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
/**
 * gimp_display_shell_scale_image_is_within_viewport:
 * @shell:
 *
 * Returns: %TRUE if the (scaled) image is smaller than and within the
 *          viewport.
 **/
gboolean
gimp_display_shell_scale_image_is_within_viewport (GimpDisplayShell *shell,
                                                   gboolean         *horizontally,
                                                   gboolean         *vertically)
{
  gint     sw, sh;
  gboolean horizontally_dummy, vertically_dummy;

  g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);

  if (! horizontally) horizontally = &horizontally_dummy;
  if (! vertically)   vertically   = &vertically_dummy;

  gimp_display_shell_scale_get_image_size (shell, &sw, &sh);

  *horizontally = sw              <= shell->disp_width       &&
                  shell->offset_x <= 0                       &&
                  shell->offset_x >= sw - shell->disp_width;

  *vertically   = sh              <= shell->disp_height      &&
                  shell->offset_y <= 0                       &&
                  shell->offset_y >= sh - shell->disp_height;

  return *vertically && *horizontally;
}

310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
/* We used to calculate the scale factor in the SCALEFACTOR_X() and
 * SCALEFACTOR_Y() macros. But since these are rather frequently
 * called and the values rarely change, we now store them in the
 * shell and call this function whenever they need to be recalculated.
 */
void
gimp_display_shell_scale_update (GimpDisplayShell *shell)
{
  GimpImage *image;

  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

  image = gimp_display_get_image (shell->display);

  if (image)
    {
      gimp_display_shell_calculate_scale_x_and_y (shell,
                                                  gimp_zoom_model_get_factor (shell->zoom),
                                                  &shell->scale_x,
                                                  &shell->scale_y);
    }
  else
    {
      shell->scale_x = 1.0;
      shell->scale_y = 1.0;
    }
}

338 339 340 341
/**
 * gimp_display_shell_scale:
 * @shell:     the #GimpDisplayShell
 * @zoom_type: whether to zoom in, our or to a specific scale
342
 * @scale:     ignored unless @zoom_type == %GIMP_ZOOM_TO
343
 *
344 345 346
 * This function figures out the context of the zoom and behaves
 * appropriatley thereafter.
 *
347
 **/
348
void
349 350
gimp_display_shell_scale (GimpDisplayShell *shell,
                          GimpZoomType      zoom_type,
351 352
                          gdouble           new_scale,
                          GimpZoomFocus     zoom_focus)
353
{
354
  gdouble current_scale;
355

356
  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
357
  g_return_if_fail (shell->canvas != NULL);
358

359 360 361
  current_scale = gimp_zoom_model_get_factor (shell->zoom);

  if (zoom_type != GIMP_ZOOM_TO)
362
    new_scale = gimp_zoom_model_zoom_step (zoom_type, current_scale);
363

364
  if (! SCALE_EQUALS (new_scale, current_scale))
365
    {
366 367 368 369 370 371 372 373
      GimpDisplayConfig *config = shell->display->config;
      gboolean           resize_window;

      /* Resize windows only in multi-window mode */
      resize_window = (config->resize_windows_on_zoom &&
                       ! GIMP_GUI_CONFIG (config)->single_window_mode);

      if (resize_window)
374 375 376 377
        {
          /* If the window is resized on zoom, simply do the zoom and
           * get things rolling
           */
378
          gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, new_scale);
379

380
          gimp_display_shell_scale_resize (shell, TRUE, FALSE);
381 382 383
        }
      else
        {
384 385 386 387 388 389
          gboolean starts_fitting_horiz;
          gboolean starts_fitting_vert;
          gboolean zoom_focus_almost_centered_horiz;
          gboolean zoom_focus_almost_centered_vert;
          gboolean image_center_almost_centered_horiz;
          gboolean image_center_almost_centered_vert;
390
          gdouble  x, y;
391 392
          gint     image_center_x;
          gint     image_center_y;
393 394

          gimp_display_shell_scale_get_zoom_focus (shell,
395
                                                   new_scale,
396 397
                                                   current_scale,
                                                   &x,
398 399
                                                   &y,
                                                   zoom_focus);
400 401 402
          gimp_display_shell_scale_get_image_center_viewport (shell,
                                                              &image_center_x,
                                                              &image_center_y);
403

404
          gimp_display_shell_scale_to (shell, new_scale, x, y);
405

Martin Nordholts's avatar
Martin Nordholts committed
406 407 408 409

          /* If an image axis started to fit due to zooming out or if
           * the focus point is as good as in the center, center on
           * that axis
410
           */
Martin Nordholts's avatar
Martin Nordholts committed
411
          gimp_display_shell_scale_image_starts_to_fit (shell,
412
                                                        new_scale,
Martin Nordholts's avatar
Martin Nordholts committed
413
                                                        current_scale,
414 415 416
                                                        &starts_fitting_horiz,
                                                        &starts_fitting_vert);

Martin Nordholts's avatar
Martin Nordholts committed
417 418 419
          gimp_display_shell_scale_viewport_coord_almost_centered (shell,
                                                                   x,
                                                                   y,
420 421 422 423 424 425 426
                                                                   &zoom_focus_almost_centered_horiz,
                                                                   &zoom_focus_almost_centered_vert);
          gimp_display_shell_scale_viewport_coord_almost_centered (shell,
                                                                   image_center_x,
                                                                   image_center_y,
                                                                   &image_center_almost_centered_horiz,
                                                                   &image_center_almost_centered_vert);
427

428
          gimp_display_shell_scroll_center_image (shell,
429 430 431 432 433 434
                                                  starts_fitting_horiz ||
                                                  (zoom_focus_almost_centered_horiz &&
                                                   image_center_almost_centered_horiz),
                                                  starts_fitting_vert ||
                                                  (zoom_focus_almost_centered_vert &&
                                                   image_center_almost_centered_vert));
435
        }
436
    }
437 438
}

439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
/**
 * gimp_display_shell_scale_to_rectangle:
 * @shell:         the #GimpDisplayShell
 * @zoom_type:     whether to zoom in or out
 * @x:             retangle's x in image coordinates
 * @y:             retangle's y in image coordinates
 * @width:         retangle's width in image coordinates
 * @height:        retangle's height in image coordinates
 * @resize_window: whether the display window should be resized
 *
 * Scales and scrolls to a specific image rectangle
 **/
void
gimp_display_shell_scale_to_rectangle (GimpDisplayShell *shell,
                                       GimpZoomType      zoom_type,
                                       gdouble           x,
                                       gdouble           y,
                                       gdouble           width,
                                       gdouble           height,
                                       gboolean          resize_window)
{
460 461 462 463 464
  gdouble current_scale;
  gdouble new_scale;
  gdouble factor   = 1.0;
  gint    offset_x = 0;
  gint    offset_y = 0;
465 466 467

  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

468 469 470 471 472 473 474 475 476 477 478
  gimp_display_shell_transform_bounds (shell,
                                       x, y,
                                       x + width, y + height,
                                       &x, &y,
                                       &width, &height);

  /* Convert scrolled (x1, y1, x2, y2) to unscrolled (x, y, width, height). */
  width  -= x;
  height -= y;
  x      += shell->offset_x;
  y      += shell->offset_y;
479

480 481 482 483 484 485 486 487
  width  = MAX (1.0, width);
  height = MAX (1.0, height);

  current_scale = gimp_zoom_model_get_factor (shell->zoom);

  switch (zoom_type)
    {
    case GIMP_ZOOM_IN:
488 489
      factor = MIN ((shell->disp_width  / width),
                    (shell->disp_height / height));
490 491 492
      break;

    case GIMP_ZOOM_OUT:
493 494
      factor = MAX ((width  / shell->disp_width),
                    (height / shell->disp_height));
495 496 497 498 499 500 501 502 503
      break;

    default:
      g_return_if_reached ();
      break;
    }

  new_scale = current_scale * factor;

504
  switch (zoom_type)
505
    {
506 507 508 509 510 511 512 513 514 515
    case GIMP_ZOOM_IN:
      /*  move the center of the rectangle to the center of the
       *  viewport:
       *
       *  new_offset = center of rectangle in new scale screen coords
       *               including offset
       *               -
       *               center of viewport in screen coords without
       *               offset
       */
516 517
      offset_x = RINT (factor * (x + width  / 2.0) - (shell->disp_width  / 2));
      offset_y = RINT (factor * (y + height / 2.0) - (shell->disp_height / 2));
518 519 520 521 522 523 524 525 526 527 528 529
      break;

    case GIMP_ZOOM_OUT:
      /*  move the center of the viewport to the center of the
       *  rectangle:
       *
       *  new_offset = center of viewport in new scale screen coords
       *               including offset
       *               -
       *               center of rectangle in screen coords without
       *               offset
       */
530 531
      offset_x = RINT (factor * (shell->offset_x + shell->disp_width  / 2) -
                       ((x + width  / 2.0) - shell->offset_x));
532

533
      offset_y = RINT (factor * (shell->offset_y + shell->disp_height / 2) -
534
                       ((y + height / 2.0) - shell->offset_y));
535
      break;
536

537 538 539 540 541 542 543 544
    default:
      break;
    }

  if (new_scale != current_scale   ||
      offset_x  != shell->offset_x ||
      offset_y  != shell->offset_y)
    {
545 546 547 548 549 550 551
      gimp_display_shell_scale_by_values (shell,
                                          new_scale,
                                          offset_x, offset_y,
                                          resize_window);
    }
}

552 553
/**
 * gimp_display_shell_scale_fit_in:
554
 * @shell: the #GimpDisplayShell
555
 *
556 557
 * Sets the scale such that the entire image precisely fits in the
 * display area.
558
 **/
559
void
560
gimp_display_shell_scale_fit_in (GimpDisplayShell *shell)
561 562 563
{
  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

564 565 566
  gimp_display_shell_scale_fit_or_fill (shell,
                                        /* fill = */ FALSE);
 }
567

568
/**
569
 * gimp_display_shell_scale_fill:
570
 * @shell: the #GimpDisplayShell
571
 *
572 573
 * Sets the scale such that the entire display area is precisely
 * filled by the image.
574
 **/
575
void
576
gimp_display_shell_scale_fill (GimpDisplayShell *shell)
577 578 579
{
  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

580 581
  gimp_display_shell_scale_fit_or_fill (shell,
                                        /* fill = */ TRUE);
582 583
}

584 585
/**
 * gimp_display_shell_scale_by_values:
586 587 588 589 590
 * @shell:         the #GimpDisplayShell
 * @scale:         the new scale
 * @offset_x:      the new X offset
 * @offset_y:      the new Y offset
 * @resize_window: whether the display window should be resized
591 592 593
 *
 * Directly sets the image scale and image offsets used by the display. If
 * @resize_window is %TRUE then the display window is resized to better
594
 * accommodate the image, see gimp_display_shell_shrink_wrap().
595
 **/
596 597
void
gimp_display_shell_scale_by_values (GimpDisplayShell *shell,
598
                                    gdouble           scale,
599 600 601 602 603 604
                                    gint              offset_x,
                                    gint              offset_y,
                                    gboolean          resize_window)
{
  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

605 606 607
  /*  Abort early if the values are all setup already. We don't
   *  want to inadvertently resize the window (bug #164281).
   */
608 609
  if (SCALE_EQUALS (gimp_zoom_model_get_factor (shell->zoom), scale) &&
      shell->offset_x == offset_x &&
610 611
      shell->offset_y == offset_y)
    return;
612

613
  gimp_display_shell_scale_save_revert_values (shell);
614

615
  /* freeze the active tool */
616
  gimp_display_shell_pause (shell);
617

618 619
  gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, scale);

620 621
  shell->offset_x = offset_x;
  shell->offset_y = offset_y;
622

623 624
  gimp_display_shell_rotate_update_transform (shell);

625
  gimp_display_shell_scale_resize (shell, resize_window, FALSE);
626 627

  /* re-enable the active tool */
628
  gimp_display_shell_resume (shell);
629
}
630

631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657
void
gimp_display_shell_scale_drag (GimpDisplayShell *shell,
                               gdouble           delta_x,
                               gdouble           delta_y)
{
  gdouble scale;

  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

  scale = gimp_zoom_model_get_factor (shell->zoom);

  if (delta_y > 0)
    {
      gimp_display_shell_scale (shell,
                                GIMP_ZOOM_TO,
                                scale * 1.1,
                                GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS);
    }
  else if (delta_y < 0)
    {
      gimp_display_shell_scale (shell,
                                GIMP_ZOOM_TO,
                                scale * 0.9,
                                GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS);
    }
}

658 659
/**
 * gimp_display_shell_scale_shrink_wrap:
660
 * @shell: the #GimpDisplayShell
661 662
 *
 * Convenience function with the same functionality as
663
 * gimp_display_shell_scale_resize(@shell, TRUE, grow_only).
664
 **/
665
void
666 667
gimp_display_shell_scale_shrink_wrap (GimpDisplayShell *shell,
                                      gboolean          grow_only)
668 669
{
  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
670

671
  gimp_display_shell_scale_resize (shell, TRUE, grow_only);
672 673
}

674 675 676 677
/**
 * gimp_display_shell_scale_resize:
 * @shell:          the #GimpDisplayShell
 * @resize_window:  whether the display window should be resized
678
 * @grow_only:      whether shrinking of the window is allowed or not
679 680 681
 *
 * Function commonly called after a change in display scale to make the changes
 * visible to the user. If @resize_window is %TRUE then the display window is
682
 * resized to accommodate the display image as per
683 684
 * gimp_display_shell_shrink_wrap().
 **/
Elliot Lee's avatar
Elliot Lee committed
685
void
Michael Natterer's avatar
Michael Natterer committed
686 687
gimp_display_shell_scale_resize (GimpDisplayShell *shell,
                                 gboolean          resize_window,
688
                                 gboolean          grow_only)
Elliot Lee's avatar
Elliot Lee committed
689
{
Michael Natterer's avatar
Michael Natterer committed
690
  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
691 692

  /* freeze the active tool */
693
  gimp_display_shell_pause (shell);
694 695

  if (resize_window)
696
    {
697
      GimpImageWindow *window = gimp_display_shell_get_window (shell);
698

699
      if (window && gimp_image_window_get_active_shell (window) == shell)
700 701 702 703
        {
          gimp_image_window_shrink_wrap (window, grow_only);
        }
    }
704

Martin Nordholts's avatar
Martin Nordholts committed
705
  gimp_display_shell_scroll_clamp_and_update (shell);
706
  gimp_display_shell_scaled (shell);
707

708
  gimp_display_shell_expose_full (shell);
709 710

  /* re-enable the active tool */
711
  gimp_display_shell_resume (shell);
712 713
}

714 715 716 717 718 719
void
gimp_display_shell_set_initial_scale (GimpDisplayShell *shell,
                                      gdouble           scale,
                                      gint             *display_width,
                                      gint             *display_height)
{
720
  GimpImage *image;
721 722 723 724 725 726 727 728 729 730
  GdkScreen *screen;
  gint       image_width;
  gint       image_height;
  gint       shell_width;
  gint       shell_height;
  gint       screen_width;
  gint       screen_height;

  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

731 732
  image = gimp_display_get_image (shell->display);

733 734
  screen = gtk_widget_get_screen (GTK_WIDGET (shell));

735 736
  image_width  = gimp_image_get_width  (image);
  image_height = gimp_image_get_height (image);
737 738 739 740

  screen_width  = gdk_screen_get_width (screen)  * 0.75;
  screen_height = gdk_screen_get_height (screen) * 0.75;

741 742 743
  /* We need to zoom before we use SCALE[XY] */
  gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, scale);

744 745 746 747 748 749 750 751
  shell_width  = SCALEX (shell, image_width);
  shell_height = SCALEY (shell, image_height);

  if (shell->display->config->initial_zoom_to_fit)
    {
      /*  Limit to the size of the screen...  */
      if (shell_width > screen_width || shell_height > screen_height)
        {
752
          gdouble new_scale;
753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793
          gdouble current = gimp_zoom_model_get_factor (shell->zoom);

          new_scale = current * MIN (((gdouble) screen_height) / shell_height,
                                     ((gdouble) screen_width)  / shell_width);

          new_scale = gimp_zoom_model_zoom_step (GIMP_ZOOM_OUT, new_scale);

          /*  Since zooming out might skip a zoom step we zoom in
           *  again and test if we are small enough.
           */
          gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO,
                                gimp_zoom_model_zoom_step (GIMP_ZOOM_IN,
                                                           new_scale));

          if (SCALEX (shell, image_width) > screen_width ||
              SCALEY (shell, image_height) > screen_height)
            gimp_zoom_model_zoom (shell->zoom, GIMP_ZOOM_TO, new_scale);

          shell_width  = SCALEX (shell, image_width);
          shell_height = SCALEY (shell, image_height);
        }
    }
  else
    {
      /*  Set up size like above, but do not zoom to fit. Useful when
       *  working on large images.
       */
      if (shell_width > screen_width)
        shell_width = screen_width;

      if (shell_height > screen_height)
        shell_height = screen_height;
    }

  if (display_width)
    *display_width = shell_width;

  if (display_height)
    *display_height = shell_height;
}

Ell's avatar
Ell committed
794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809
/**
 * gimp_display_shell_get_rotated_scale:
 * @shell:   the #GimpDisplayShell
 * @scale_x: horizontal scale output
 * @scale_y: vertical scale output
 *
 * Returns the screen space horizontal and vertical scaling
 * factors, taking rotation into account.
 **/
void
gimp_display_shell_get_rotated_scale (GimpDisplayShell *shell,
                                      gdouble          *scale_x,
                                      gdouble          *scale_y)
{
  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

810
  if (shell->rotate_angle == 0.0 || shell->scale_x == shell->scale_y)
Ell's avatar
Ell committed
811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828
    {
      if (scale_x) *scale_x = shell->scale_x;
      if (scale_y) *scale_y = shell->scale_y;
    }
  else
    {
      gdouble a     = G_PI * shell->rotate_angle / 180.0;
      gdouble cos_a = cos (a);
      gdouble sin_a = sin (a);

      if (scale_x) *scale_x = 1.0 / sqrt (SQR (cos_a / shell->scale_x) +
                                          SQR (sin_a / shell->scale_y));

      if (scale_y) *scale_y = 1.0 / sqrt (SQR (cos_a / shell->scale_y) +
                                          SQR (sin_a / shell->scale_x));
    }
}

829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850
/**
 * gimp_display_shell_push_zoom_focus_pointer_pos:
 * @shell:
 * @x:
 * @y:
 *
 * When the zoom focus mechanism asks for the pointer the next time,
 * use @x and @y.
 **/
void
gimp_display_shell_push_zoom_focus_pointer_pos (GimpDisplayShell *shell,
                                                gint              x,
                                                gint              y)
{
  GdkPoint *point = g_slice_new (GdkPoint);
  point->x = x;
  point->y = y;

  g_queue_push_head (shell->zoom_focus_pointer_queue,
                     point);
}

851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875

/*  private functions   */

static void
gimp_display_shell_scale_get_screen_resolution (GimpDisplayShell *shell,
                                                gdouble          *xres,
                                                gdouble          *yres)
{
  gdouble x, y;

  if (shell->dot_for_dot)
    {
      gimp_image_get_resolution (gimp_display_get_image (shell->display),
                                 &x, &y);
    }
  else
    {
      x = shell->monitor_xres;
      y = shell->monitor_yres;
    }

  if (xres) *xres = x;
  if (yres) *yres = y;
}

876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899
/**
 * gimp_display_shell_scale_get_image_size_for_scale:
 * @shell:
 * @scale:
 * @w:
 * @h:
 *
 **/
static void
gimp_display_shell_scale_get_image_size_for_scale (GimpDisplayShell *shell,
                                                   gdouble           scale,
                                                   gint             *w,
                                                   gint             *h)
{
  GimpImage *image = gimp_display_get_image (shell->display);
  gdouble    scale_x;
  gdouble    scale_y;

  gimp_display_shell_calculate_scale_x_and_y (shell, scale, &scale_x, &scale_y);

  if (w) *w = scale_x * gimp_image_get_width  (image);
  if (h) *h = scale_y * gimp_image_get_height (image);
}

900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927
/**
 * gimp_display_shell_calculate_scale_x_and_y:
 * @shell:
 * @scale:
 * @scale_x:
 * @scale_y:
 *
 **/
static void
gimp_display_shell_calculate_scale_x_and_y (GimpDisplayShell *shell,
                                            gdouble           scale,
                                            gdouble          *scale_x,
                                            gdouble          *scale_y)
{
  GimpImage *image = gimp_display_get_image (shell->display);
  gdouble    xres;
  gdouble    yres;
  gdouble    screen_xres;
  gdouble    screen_yres;

  gimp_image_get_resolution (image, &xres, &yres);
  gimp_display_shell_scale_get_screen_resolution (shell,
                                                  &screen_xres, &screen_yres);

  if (scale_x) *scale_x = scale * screen_xres / xres;
  if (scale_y) *scale_y = scale * screen_yres / yres;
}

928
/**
929 930 931 932 933
 * gimp_display_shell_scale_to:
 * @shell:
 * @scale:
 * @viewport_x:
 * @viewport_y:
934
 *
935 936
 * Zooms. The display offsets are adjusted so that the point specified
 * by @x and @y doesn't change it's position on screen.
937
 **/
938 939 940
static void
gimp_display_shell_scale_to (GimpDisplayShell *shell,
                             gdouble           scale,
941 942
                             gdouble           viewport_x,
                             gdouble           viewport_y)
943
{
944 945
  gdouble image_x, image_y;
  gdouble new_viewport_x, new_viewport_y;
946 947 948

  g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));

949 950
  if (! shell->display)
    return;
951

952 953 954
  /* freeze the active tool */
  gimp_display_shell_pause (shell);

955 956 957
  gimp_display_shell_untransform_xy_f (shell,
                                       viewport_x,
                                       viewport_y,
958 959
                                       &image_x,
                                       &image_y);
960

961
  /* Note that we never come here if we need to resize_windows_on_zoom
962 963 964
   */
  gimp_display_shell_scale_by_values (shell,
                                      scale,
965 966
                                      shell->offset_x,
                                      shell->offset_y,
967
                                      FALSE);
968 969 970 971 972 973 974 975 976 977

  gimp_display_shell_transform_xy_f (shell,
                                     image_x,
                                     image_y,
                                     &new_viewport_x,
                                     &new_viewport_y);

  gimp_display_shell_scroll (shell,
                             new_viewport_x - viewport_x,
                             new_viewport_y - viewport_y);
978 979 980

  /* re-enable the active tool */
  gimp_display_shell_resume (shell);
981 982
}

983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035
/**
 * gimp_display_shell_scale_fit_or_fill:
 * @shell: the #GimpDisplayShell
 * @fill:  whether to scale the image to fill the viewport,
 *         or fit inside the viewport
 *
 * A common implementation for gimp_display_shell_scale_{fit_in,fill}().
 **/
static void
gimp_display_shell_scale_fit_or_fill (GimpDisplayShell *shell,
                                      gboolean          fill)
{
  GimpImage *image;
  gdouble    image_x;
  gdouble    image_y;
  gdouble    image_width;
  gdouble    image_height;
  gdouble    current_scale;
  gdouble    zoom_factor;

  image = gimp_display_get_image (shell->display);

  gimp_display_shell_transform_bounds (shell,
                                       0, 0,
                                       gimp_image_get_width  (image),
                                       gimp_image_get_height (image),
                                       &image_x, &image_y,
                                       &image_width, &image_height);

  image_width  -= image_x;
  image_height -= image_y;

  current_scale = gimp_zoom_model_get_factor (shell->zoom);

  if (fill)
    {
      zoom_factor = MAX (shell->disp_width  / image_width,
                         shell->disp_height / image_height);
    }
  else
    {
      zoom_factor = MIN (shell->disp_width  / image_width,
                         shell->disp_height / image_height);
    }

  gimp_display_shell_scale (shell,
                            GIMP_ZOOM_TO,
                            zoom_factor * current_scale,
                            GIMP_ZOOM_FOCUS_BEST_GUESS);

  gimp_display_shell_scroll_center_image (shell, TRUE, TRUE);
}

1036 1037 1038 1039 1040 1041
static gboolean
gimp_display_shell_scale_image_starts_to_fit (GimpDisplayShell *shell,
                                              gdouble           new_scale,
                                              gdouble           current_scale,
                                              gboolean         *vertically,
                                              gboolean         *horizontally)
1042
{
1043 1044
  gboolean vertically_dummy;
  gboolean horizontally_dummy;
1045

1046 1047
  if (! vertically)   vertically   = &vertically_dummy;
  if (! horizontally) horizontally = &horizontally_dummy;
1048

1049 1050 1051 1052 1053
  /* The image can only start to fit if we zoom out */
  if (new_scale > current_scale)
    {
      *vertically   = FALSE;
      *horizontally = FALSE;
1054 1055 1056
    }
  else
    {
1057 1058 1059 1060 1061
      gint current_scale_width;
      gint current_scale_height;
      gint new_scale_width;
      gint new_scale_height;

1062 1063 1064 1065
      gimp_display_shell_scale_get_image_size_for_scale (shell,
                                                         current_scale,
                                                         &current_scale_width,
                                                         &current_scale_height);
1066

1067 1068 1069 1070
      gimp_display_shell_scale_get_image_size_for_scale (shell,
                                                         new_scale,
                                                         &new_scale_width,
                                                         &new_scale_height);
1071 1072 1073 1074 1075

      *vertically   = (current_scale_width  >  shell->disp_width &&
                       new_scale_width      <= shell->disp_width);
      *horizontally = (current_scale_height >  shell->disp_height &&
                       new_scale_height     <= shell->disp_height);
1076
    }
1077

1078 1079
  return *vertically && *horizontally;
}
1080

1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092
static gboolean
gimp_display_shell_scale_image_stops_to_fit (GimpDisplayShell *shell,
                                             gdouble           new_scale,
                                             gdouble           current_scale,
                                             gboolean         *vertically,
                                             gboolean         *horizontally)
{
  return gimp_display_shell_scale_image_starts_to_fit (shell,
                                                       current_scale,
                                                       new_scale,
                                                       vertically,
                                                       horizontally);
1093 1094
}

1095 1096 1097 1098 1099 1100 1101 1102 1103
/**
 * gimp_display_shell_scale_viewport_coord_almost_centered:
 * @shell:
 * @x:
 * @y:
 * @horizontally:
 * @vertically:
 *
 **/
1104
static gboolean
1105 1106 1107 1108 1109
gimp_display_shell_scale_viewport_coord_almost_centered (GimpDisplayShell *shell,
                                                         gint              x,
                                                         gint              y,
                                                         gboolean         *horizontally,
                                                         gboolean         *vertically)
1110
{
1111 1112 1113 1114 1115 1116 1117
  gboolean local_horizontally;
  gboolean local_vertically;
  gint     center_x = shell->disp_width  / 2;
  gint     center_y = shell->disp_height / 2;

  local_horizontally = (x > center_x - ALMOST_CENTERED_THRESHOLD &&
                        x < center_x + ALMOST_CENTERED_THRESHOLD);
1118

1119 1120
  local_vertically   = (y > center_y - ALMOST_CENTERED_THRESHOLD &&
                        y < center_y + ALMOST_CENTERED_THRESHOLD);
1121

1122 1123 1124 1125
  if (horizontally) *horizontally = local_horizontally;
  if (vertically)   *vertically   = local_vertically;

  return local_horizontally && local_vertically;
1126
}
1127

1128 1129 1130 1131 1132 1133 1134
static void
gimp_display_shell_scale_get_image_center_viewport (GimpDisplayShell *shell,
                                                    gint             *image_center_x,
                                                    gint             *image_center_y)
{
  gint sw, sh;

1135
  gimp_display_shell_scale_get_image_size (shell, &sw, &sh);
1136 1137 1138 1139 1140

  if (image_center_x) *image_center_x = -shell->offset_x + sw / 2;
  if (image_center_y) *image_center_y = -shell->offset_y + sh / 2;
}

1141 1142 1143
/**
 * gimp_display_shell_scale_get_zoom_focus:
 * @shell:
1144
 * @new_scale:
1145 1146 1147
 * @x:
 * @y:
 *
1148 1149
 * Calculates the viewport coordinate to focus on when zooming
 * independently for each axis.
1150 1151 1152
 **/
static void
gimp_display_shell_scale_get_zoom_focus (GimpDisplayShell *shell,
1153
                                         gdouble           new_scale,
1154
                                         gdouble           current_scale,
1155 1156
                                         gdouble          *x,
                                         gdouble          *y,
1157
                                         GimpZoomFocus     zoom_focus)
1158
{
1159 1160 1161 1162 1163 1164
  GtkWidget *window = GTK_WIDGET (gimp_display_shell_get_window (shell));
  GdkEvent  *event;
  gint       image_center_x;
  gint       image_center_y;
  gint       other_x;
  gint       other_y;
1165 1166

  /* Calculate stops-to-fit focus point */
1167 1168 1169
  gimp_display_shell_scale_get_image_center_viewport (shell,
                                                      &image_center_x,
                                                      &image_center_y);
1170

1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187
  /* Calculate other focus point, default is the canvas center */
  other_x = shell->disp_width  / 2;
  other_y = shell->disp_height / 2;

  /*  Center on the mouse position instead of the display center if
   *  one of the following conditions are fulfilled and pointer is
   *  within the canvas:
   *
   *   (1) there's no current event (the action was triggered by an
   *       input controller)
   *   (2) the event originates from the canvas (a scroll event)
   *   (3) the event originates from the window (a key press event)
   *
   *  Basically the only situation where we don't want to center on
   *  mouse position is if the action is being called from a menu.
   */
  event = gtk_get_current_event ();
1188

1189 1190 1191
  if (! event ||
      gtk_get_event_widget (event) == shell->canvas ||
      gtk_get_event_widget (event) == window)
1192
    {
1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218
      GdkPoint *point = g_queue_pop_head (shell->zoom_focus_pointer_queue);
      gint      canvas_pointer_x;
      gint      canvas_pointer_y;

      if (point)
        {
          canvas_pointer_x = point->x;
          canvas_pointer_y = point->y;

          g_slice_free (GdkPoint, point);
        }
      else
        {
          gtk_widget_get_pointer (shell->canvas,
                                  &canvas_pointer_x,
                                  &canvas_pointer_y);
        }

      if (canvas_pointer_x >= 0 &&
          canvas_pointer_y >= 0 &&
          canvas_pointer_x <  shell->disp_width &&
          canvas_pointer_y <  shell->disp_height)
        {
          other_x = canvas_pointer_x;
          other_y = canvas_pointer_y;
        }
1219
    }
1220 1221 1222

  /* Decide which one to use for each axis */
  if (zoom_focus == GIMP_ZOOM_FOCUS_RETAIN_CENTERING_ELSE_BEST_GUESS)
1223
    {
1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235
      if (gimp_display_shell_scale_viewport_coord_almost_centered (shell,
                                                                   image_center_x,
                                                                   image_center_y,
                                                                   NULL,
                                                                   NULL))
        {
          zoom_focus = GIMP_ZOOM_FOCUS_IMAGE_CENTER;
        }
      else
        {
          zoom_focus = GIMP_ZOOM_FOCUS_BEST_GUESS;
        }
1236 1237
    }

1238
  switch (zoom_focus)
1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254
    {
    case GIMP_ZOOM_FOCUS_POINTER:
      *x = other_x;
      *y = other_y;
      break;

    case GIMP_ZOOM_FOCUS_IMAGE_CENTER:
      *x = image_center_x;
      *y = image_center_y;
      break;

    case GIMP_ZOOM_FOCUS_BEST_GUESS:
    default:
      {
        gboolean within_horizontally, within_vertically;
        gboolean stops_horizontally, stops_vertically;
1255

1256 1257 1258
        gimp_display_shell_scale_image_is_within_viewport (shell,
                                                           &within_horizontally,
                                                           &within_vertically);
1259

1260 1261 1262 1263 1264
        gimp_display_shell_scale_image_stops_to_fit (shell,
                                                     new_scale,
                                                     current_scale,
                                                     &stops_horizontally,
                                                     &stops_vertically);
1265

1266 1267 1268 1269 1270
        *x = within_horizontally && ! stops_horizontally ? image_center_x : other_x;
        *y = within_vertically   && ! stops_vertically   ? image_center_y : other_y;
      }
      break;
    }
1271
}