1f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved.
2f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)// found in the LICENSE file.
4f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
5f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)'use strict';
6f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
7f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
8f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Viewport class controls the way the image is displayed (scale, offset etc).
9f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @constructor
10f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
11f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)function Viewport() {
12f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.imageBounds_ = new Rect();
13f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.screenBounds_ = new Rect();
14f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
15f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.scale_ = 1;
16f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.offsetX_ = 0;
17f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.offsetY_ = 0;
18f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
19f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.generation_ = 0;
20f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
21f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.scaleControl_ = null;
22f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.repaintCallbacks_ = [];
23f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.update();
24f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)}
25f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
26f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/*
27f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Viewport modification.
28f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
29f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
30f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
31f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {Object} scaleControl The UI object responsible for scaling.
32f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
33f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.setScaleControl = function(scaleControl) {
34f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.scaleControl_ = scaleControl;
35f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
36f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
37f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
38f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} width Image width.
39f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} height Image height.
40f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
41f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.setImageSize = function(width, height) {
42f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.imageBounds_ = new Rect(width, height);
43f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (this.scaleControl_) this.scaleControl_.displayImageSize(width, height);
44f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.invalidateCaches();
45f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
46f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
47f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
48f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} width Screen width.
49f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} height Screen height.
50f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
51f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.setScreenSize = function(width, height) {
52f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.screenBounds_ = new Rect(width, height);
53f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (this.scaleControl_)
54f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    this.scaleControl_.setMinScale(this.getFittingScale());
55f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.invalidateCaches();
56f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
57f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
58f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
59f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Set the size by an HTML element.
60f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) *
61f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {HTMLElement} frame The element acting as the "screen".
62f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
63f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.sizeByFrame = function(frame) {
64f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.setScreenSize(frame.clientWidth, frame.clientHeight);
65f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
66f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
67f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
68f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Set the size and scale to fit an HTML element.
69f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) *
70f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {HTMLElement} frame The element acting as the "screen".
71f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
72f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.sizeByFrameAndFit = function(frame) {
73f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  var wasFitting = this.getScale() == this.getFittingScale();
74f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.sizeByFrame(frame);
75f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  var minScale = this.getFittingScale();
76f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (wasFitting || (this.getScale() < minScale)) {
77f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    this.setScale(minScale, true);
78f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  }
79f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
80f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
81f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
82f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} Scale.
83f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
84f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.getScale = function() { return this.scale_ };
85f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
86f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
87f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} scale The new scale.
88f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {boolean} notify True if the change should be reflected in the UI.
89f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
90f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.setScale = function(scale, notify) {
91f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (this.scale_ == scale) return;
92f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.scale_ = scale;
93f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (notify && this.scaleControl_) this.scaleControl_.displayScale(scale);
94f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.invalidateCaches();
95f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
96f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
97f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
98f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} Best scale to fit the current image into the current screen.
99f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
100f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.getFittingScale = function() {
101f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  var scaleX = this.screenBounds_.width / this.imageBounds_.width;
102f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  var scaleY = this.screenBounds_.height / this.imageBounds_.height;
103f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  // Scales > (1 / this.getDevicePixelRatio()) do not look good. Also they are
104f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  // not really useful as we do not have any pixel-level operations.
105f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return Math.min(1 / Viewport.getDevicePixelRatio(), scaleX, scaleY);
106f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
107f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
108f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
109f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Set the scale to fit the image into the screen.
110f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
111f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.fitImage = function() {
112f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  var scale = this.getFittingScale();
113f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (this.scaleControl_) this.scaleControl_.setMinScale(scale);
114f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.setScale(scale, true);
115f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
116f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
117f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
118f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} X-offset of the viewport.
119f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
120f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.getOffsetX = function() { return this.offsetX_ };
121f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
122f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
123f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} Y-offset of the viewport.
124f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
125f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.getOffsetY = function() { return this.offsetY_ };
126f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
127f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
128f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Set the image offset in the viewport.
129f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} x X-offset.
130f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} y Y-offset.
131f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {boolean} ignoreClipping True if no clipping should be applied.
132f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
133f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.setOffset = function(x, y, ignoreClipping) {
134f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (!ignoreClipping) {
135f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    x = this.clampOffsetX_(x);
136f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    y = this.clampOffsetY_(y);
137f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  }
138f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (this.offsetX_ == x && this.offsetY_ == y) return;
139f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.offsetX_ = x;
140f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.offsetY_ = y;
141f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.invalidateCaches();
142f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
143f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
144f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
145f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Return a closure that can be called to pan the image.
146f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Useful for implementing non-trivial variants of panning (overview etc).
147f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} originalX The x coordinate on the screen canvas that
148f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) *                 corresponds to zero change to offsetX.
149f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} originalY The y coordinate on the screen canvas that
150f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) *                 corresponds to zero change to offsetY.
151f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {function():number} scaleFunc returns the image to screen scale.
152f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {function(number,number):boolean} hitFunc returns true if (x,y) is
153f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) *                                                  in the valid region.
154f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {function} The closure to pan the image.
155f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
156f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.createOffsetSetter = function(
157f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    originalX, originalY, scaleFunc, hitFunc) {
158f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  var originalOffsetX = this.offsetX_;
159f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  var originalOffsetY = this.offsetY_;
160f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (!hitFunc) hitFunc = function() { return true };
161f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (!scaleFunc) scaleFunc = this.getScale.bind(this);
162f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
163f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  var self = this;
164f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return function(x, y) {
165f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    if (hitFunc(x, y)) {
166f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      var scale = scaleFunc();
167f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      self.setOffset(
168f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)          originalOffsetX + (x - originalX) / scale,
169f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)          originalOffsetY + (y - originalY) / scale);
170f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      self.repaint();
171f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    }
172f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  };
173f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
174f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
175f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/*
176f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Access to the current viewport state.
177f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
178f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
179f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
180f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {Rect} The image bounds in image coordinates.
181f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
182f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.getImageBounds = function() { return this.imageBounds_ };
183f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
184f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
185f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)* @return {Rect} The screen bounds in screen coordinates.
186f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)*/
187f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.getScreenBounds = function() { return this.screenBounds_ };
188f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
189f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
190f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {Rect} The visible part of the image, in image coordinates.
191f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
192f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.getImageClipped = function() { return this.imageClipped_ };
193f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
194f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
195f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {Rect} The visible part of the image, in screen coordinates.
196f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
197f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.getScreenClipped = function() { return this.screenClipped_ };
198f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
199f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
200f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * A counter that is incremented with each viewport state change.
201f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Clients that cache anything that depends on the viewport state should keep
202f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * track of this counter.
203f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} counter.
204f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
205f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.getCacheGeneration = function() { return this.generation_ };
206f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
207f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
208f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Called on event view port state change (even if repaint has not been called).
209f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
210f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.invalidateCaches = function() { this.generation_++ };
211f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
212f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
213f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {Rect} The image bounds in screen coordinates.
214f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
215f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.getImageBoundsOnScreen = function() {
216f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return this.imageOnScreen_;
217f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
218f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
219f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/*
220f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Conversion between the screen and image coordinate spaces.
221f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
222f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
223f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
224f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} size Size in screen coordinates.
225f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} Size in image coordinates.
226f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
227f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.screenToImageSize = function(size) {
228f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return size / this.getScale();
229f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
230f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
231f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
232f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} x X in screen coordinates.
233f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} X in image coordinates.
234f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
235f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.screenToImageX = function(x) {
236f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return Math.round((x - this.imageOnScreen_.left) / this.getScale());
237f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
238f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
239f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
240f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} y Y in screen coordinates.
241f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} Y in image coordinates.
242f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
243f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.screenToImageY = function(y) {
244f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return Math.round((y - this.imageOnScreen_.top) / this.getScale());
245f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
246f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
247f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
248f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {Rect} rect Rectangle in screen coordinates.
249f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {Rect} Rectangle in image coordinates.
250f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
251f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.screenToImageRect = function(rect) {
252f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return new Rect(
253f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      this.screenToImageX(rect.left),
254f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      this.screenToImageY(rect.top),
255f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      this.screenToImageSize(rect.width),
256f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      this.screenToImageSize(rect.height));
257f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
258f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
259f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
260f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} size Size in image coordinates.
261f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} Size in screen coordinates.
262f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
263f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.imageToScreenSize = function(size) {
264f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return size * this.getScale();
265f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
266f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
267f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
268f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} x X in image coordinates.
269f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} X in screen coordinates.
270f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
271f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.imageToScreenX = function(x) {
272f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return Math.round(this.imageOnScreen_.left + x * this.getScale());
273f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
274f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
275f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
276f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} y Y in image coordinates.
277f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} Y in screen coordinates.
278f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
279f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.imageToScreenY = function(y) {
280f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return Math.round(this.imageOnScreen_.top + y * this.getScale());
281f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
282f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
283f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
284f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {Rect} rect Rectangle in image coordinates.
285f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {Rect} Rectangle in screen coordinates.
286f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
287f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.imageToScreenRect = function(rect) {
288f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return new Rect(
289f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      this.imageToScreenX(rect.left),
290f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      this.imageToScreenY(rect.top),
291f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      Math.round(this.imageToScreenSize(rect.width)),
292f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      Math.round(this.imageToScreenSize(rect.height)));
293f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
294f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
295f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
296f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} The number of physical pixels in one CSS pixel.
297f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
298f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.getDevicePixelRatio = function() { return window.devicePixelRatio };
299f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
300f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
301f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Convert a rectangle from screen coordinates to 'device' coordinates.
302f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) *
303f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * This conversion enlarges the original rectangle devicePixelRatio times
304f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * with the screen center as a fixed point.
305f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) *
306f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {Rect} rect Rectangle in screen coordinates.
307f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {Rect} Rectangle in device coordinates.
308f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
309f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.screenToDeviceRect = function(rect) {
310f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  var ratio = Viewport.getDevicePixelRatio();
311f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  var screenCenterX = Math.round(
312f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      this.screenBounds_.left + this.screenBounds_.width / 2);
313f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  var screenCenterY = Math.round(
314f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      this.screenBounds_.top + this.screenBounds_.height / 2);
315f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return new Rect(screenCenterX + (rect.left - screenCenterX) * ratio,
316f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                  screenCenterY + (rect.top - screenCenterY) * ratio,
317f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                  rect.width * ratio,
318f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)                  rect.height * ratio);
319f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
320f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
321f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
322f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {Rect} The visible part of the image, in device coordinates.
323f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
324f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.getDeviceClipped = function() {
325f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return this.screenToDeviceRect(this.getScreenClipped());
326f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
327f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
328f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
329f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {boolean} True if some part of the image is clipped by the screen.
330f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
331f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.isClipped = function() {
332f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return this.getMarginX_() < 0 || this.getMarginY_() < 0;
333f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
334f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
335f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
336f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} Horizontal margin.
337f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) *   Negative if the image is clipped horizontally.
338f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @private
339f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
340f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.getMarginX_ = function() {
341f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return Math.round(
342f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    (this.screenBounds_.width - this.imageBounds_.width * this.scale_) / 2);
343f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
344f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
345f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
346f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} Vertical margin.
347f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) *   Negative if the image is clipped vertically.
348f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @private
349f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
350f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.getMarginY_ = function() {
351f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return Math.round(
352f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    (this.screenBounds_.height - this.imageBounds_.height * this.scale_) / 2);
353f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
354f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
355f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
356f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} x X-offset.
357f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} X-offset clamped to the valid range.
358f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @private
359f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
360f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.clampOffsetX_ = function(x) {
361f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  var limit = Math.round(Math.max(0, -this.getMarginX_() / this.getScale()));
362f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return ImageUtil.clamp(-limit, x, limit);
363f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
364f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
365f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
366f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {number} y Y-offset.
367f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @return {number} Y-offset clamped to the valid range.
368f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @private
369f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
370f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.clampOffsetY_ = function(y) {
371f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  var limit = Math.round(Math.max(0, -this.getMarginY_() / this.getScale()));
372f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  return ImageUtil.clamp(-limit, y, limit);
373f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
374f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
375f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
376f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Recalculate the viewport parameters.
377f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
378f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.update = function() {
379f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  var scale = this.getScale();
380f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
381f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  // Image bounds in screen coordinates.
382f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.imageOnScreen_ = new Rect(
383f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      this.getMarginX_(),
384f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      this.getMarginY_(),
385f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      Math.round(this.imageBounds_.width * scale),
386f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      Math.round(this.imageBounds_.height * scale));
387f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
388f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  // A visible part of the image in image coordinates.
389f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.imageClipped_ = new Rect(this.imageBounds_);
390f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
391f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  // A visible part of the image in screen coordinates.
392f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.screenClipped_ = new Rect(this.screenBounds_);
393f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
394f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  // Adjust for the offset.
395f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (this.imageOnScreen_.left < 0) {
396f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    this.imageOnScreen_.left +=
397f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        Math.round(this.clampOffsetX_(this.offsetX_) * scale);
398f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    this.imageClipped_.left = Math.round(-this.imageOnScreen_.left / scale);
399f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    this.imageClipped_.width = Math.round(this.screenBounds_.width / scale);
400f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  } else {
401f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    this.screenClipped_.left = this.imageOnScreen_.left;
402f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    this.screenClipped_.width = this.imageOnScreen_.width;
403f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  }
404f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
405f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (this.imageOnScreen_.top < 0) {
406f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    this.imageOnScreen_.top +=
407f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        Math.round(this.clampOffsetY_(this.offsetY_) * scale);
408f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    this.imageClipped_.top = Math.round(-this.imageOnScreen_.top / scale);
409f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    this.imageClipped_.height = Math.round(this.screenBounds_.height / scale);
410f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  } else {
411f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    this.screenClipped_.top = this.imageOnScreen_.top;
412f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    this.screenClipped_.height = this.imageOnScreen_.height;
413f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  }
414f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
415f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
416f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
417f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * @param {function} callback Repaint callback.
418f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
419f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.addRepaintCallback = function(callback) {
420f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.repaintCallbacks_.push(callback);
421f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
422f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
423f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)/**
424f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) * Repaint all clients.
425f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) */
426f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)Viewport.prototype.repaint = function() {
427f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  this.update();
428f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  for (var i = 0; i != this.repaintCallbacks_.length; i++)
429f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    this.repaintCallbacks_[i]();
430f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)};
431