Add ability for NetLogLogger to gather data from more than just NetLog
[chromium-blink-merge.git] / ui / file_manager / gallery / js / image_editor / viewport.js
blob1250c179d614bde6c6e555e5eb43a301377fa6f7
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6 * Viewport class controls the way the image is displayed (scale, offset etc).
7 * @constructor
8 * @struct
9 */
10 function Viewport() {
11 /**
12 * Size of the full resolution image.
13 * @type {!ImageRect}
14 * @private
16 this.imageBounds_ = new ImageRect(0, 0, 0, 0);
18 /**
19 * Size of the application window.
20 * @type {!ImageRect}
21 * @private
23 this.screenBounds_ = new ImageRect(0, 0, 0, 0);
25 /**
26 * Bounds of the image element on screen without zoom and offset.
27 * @type {ImageRect}
28 * @private
30 this.imageElementBoundsOnScreen_ = null;
32 /**
33 * Bounds of the image with zoom and offset.
34 * @type {ImageRect}
35 * @private
37 this.imageBoundsOnScreen_ = null;
39 /**
40 * Image bounds that is clipped with the screen bounds.
41 * @type {ImageRect}
42 * @private
44 this.imageBoundsOnScreenClipped_ = null;
46 /**
47 * Scale from the full resolution image to the screen displayed image. This is
48 * not zoom operated by users.
49 * @type {number}
50 * @private
52 this.scale_ = 1;
54 /**
55 * Zoom ratio specified by user operations.
56 * @type {number}
57 * @private
59 this.zoom_ = 1;
61 /**
62 * Offset specified by user operations.
63 * @type {number}
64 * @private
66 this.offsetX_ = 0;
68 /**
69 * Offset specified by user operations.
70 * @type {number}
71 * @private
73 this.offsetY_ = 0;
75 /**
76 * Integer Rotation value.
77 * The rotation angle is this.rotation_ * 90.
78 * @type {number}
79 * @private
81 this.rotation_ = 0;
83 /**
84 * Generation of the screen size image cache.
85 * This is incremented every time when the size of image cache is changed.
86 * @type {number}
87 * @private
89 this.generation_ = 0;
91 this.update_();
94 /**
95 * Zoom ratios.
97 * @type {Array.<number>}
98 * @const
100 Viewport.ZOOM_RATIOS = [1, 1.5, 2, 3];
103 * Sets image size.
104 * @param {number} width Image width.
105 * @param {number} height Image height.
107 Viewport.prototype.setImageSize = function(width, height) {
108 this.imageBounds_ = ImageRect.createFromWidthAndHeight(width, height);
109 this.update_();
113 * Sets screen size.
114 * @param {number} width Screen width.
115 * @param {number} height Screen height.
117 Viewport.prototype.setScreenSize = function(width, height) {
118 this.screenBounds_ = ImageRect.createFromWidthAndHeight(width, height);
119 this.update_();
123 * Sets zoom value directly.
124 * @param {number} zoom New zoom value.
126 Viewport.prototype.setZoom = function(zoom) {
127 var zoomMin = Viewport.ZOOM_RATIOS[0];
128 var zoomMax = Viewport.ZOOM_RATIOS[Viewport.ZOOM_RATIOS.length - 1];
129 var adjustedZoom = Math.max(zoomMin, Math.min(zoom, zoomMax));
130 this.zoom_ = adjustedZoom;
131 this.update_();
135 * Returns the value of zoom.
136 * @return {number} Zoom value.
138 Viewport.prototype.getZoom = function() {
139 return this.zoom_;
143 * Sets the nearest larger value of ZOOM_RATIOS.
145 Viewport.prototype.zoomIn = function() {
146 var zoom = Viewport.ZOOM_RATIOS[0];
147 for (var i = 0; i < Viewport.ZOOM_RATIOS.length; i++) {
148 zoom = Viewport.ZOOM_RATIOS[i];
149 if (zoom > this.zoom_)
150 break;
152 this.setZoom(zoom);
156 * Sets the nearest smaller value of ZOOM_RATIOS.
158 Viewport.prototype.zoomOut = function() {
159 var zoom = Viewport.ZOOM_RATIOS[Viewport.ZOOM_RATIOS.length - 1];
160 for (var i = Viewport.ZOOM_RATIOS.length - 1; i >= 0; i--) {
161 zoom = Viewport.ZOOM_RATIOS[i];
162 if (zoom < this.zoom_)
163 break;
165 this.setZoom(zoom);
169 * Obtains whether the picture is zoomed or not.
170 * @return {boolean}
172 Viewport.prototype.isZoomed = function() {
173 return this.zoom_ !== 1;
177 * Sets the rotation value.
178 * @param {number} rotation New rotation value.
180 Viewport.prototype.setRotation = function(rotation) {
181 this.rotation_ = rotation;
182 this.update_();
187 * Obtains the rotation value.
188 * @return {number} Current rotation value.
190 Viewport.prototype.getRotation = function() {
191 return this.rotation_;
195 * Obtains the scale for the specified image size.
197 * @param {number} width Width of the full resolution image.
198 * @param {number} height Height of the full resolution image.
199 * @return {number} The ratio of the full resotion image size and the calculated
200 * displayed image size.
201 * @private
203 Viewport.prototype.getFittingScaleForImageSize_ = function(width, height) {
204 var scaleX = this.screenBounds_.width / width;
205 var scaleY = this.screenBounds_.height / height;
206 return Math.min(scaleX, scaleY, 1);
210 * Returns offset X.
211 * @return {number} X-offset of the viewport.
213 Viewport.prototype.getOffsetX = function() { return this.offsetX_; };
216 * Returns offset Y.
217 * @return {number} Y-offset of the viewport.
219 Viewport.prototype.getOffsetY = function() { return this.offsetY_; };
222 * Set the image offset in the viewport.
223 * @param {number} x X-offset.
224 * @param {number} y Y-offset.
226 Viewport.prototype.setOffset = function(x, y) {
227 if (this.offsetX_ == x && this.offsetY_ == y)
228 return;
229 this.offsetX_ = x;
230 this.offsetY_ = y;
231 this.update_();
235 * Returns image bounds.
236 * @return {!ImageRect} The image bounds in image coordinates.
238 Viewport.prototype.getImageBounds = function() { return this.imageBounds_; };
241 * Returns screen bounds.
242 * @return {!ImageRect} The screen bounds in screen coordinates.
244 Viewport.prototype.getScreenBounds = function() { return this.screenBounds_; };
247 * Returns device bounds.
248 * @return {!ImageRect} The size of screen cache canvas.
250 Viewport.prototype.getDeviceBounds = function() {
251 var size = this.getImageElementBoundsOnScreen();
252 return ImageRect.createFromWidthAndHeight(
253 size.width * window.devicePixelRatio,
254 size.height * window.devicePixelRatio);
258 * A counter that is incremented with each viewport state change.
259 * Clients that cache anything that depends on the viewport state should keep
260 * track of this counter.
261 * @return {number} counter.
263 Viewport.prototype.getCacheGeneration = function() { return this.generation_; };
266 * Returns image bounds in screen coordinates.
267 * @return {!ImageRect} The image bounds in screen coordinates.
269 Viewport.prototype.getImageBoundsOnScreen = function() {
270 assert(this.imageBoundsOnScreen_);
271 return this.imageBoundsOnScreen_;
275 * The image bounds in screen coordinates.
276 * This returns the bounds of element before applying zoom and offset.
277 * @return {!ImageRect}
279 Viewport.prototype.getImageElementBoundsOnScreen = function() {
280 assert(this.imageElementBoundsOnScreen_);
281 return this.imageElementBoundsOnScreen_;
285 * The image bounds on screen, which is clipped with the screen size.
286 * @return {!ImageRect}
288 Viewport.prototype.getImageBoundsOnScreenClipped = function() {
289 assert(this.imageBoundsOnScreenClipped_);
290 return this.imageBoundsOnScreenClipped_;
294 * Returns size in image coordinates.
295 * @param {number} size Size in screen coordinates.
296 * @return {number} Size in image coordinates.
298 Viewport.prototype.screenToImageSize = function(size) {
299 return size / this.scale_;
303 * Returns X in image coordinates.
304 * @param {number} x X in screen coordinates.
305 * @return {number} X in image coordinates.
307 Viewport.prototype.screenToImageX = function(x) {
308 return Math.round((x - this.imageBoundsOnScreen_.left) / this.scale_);
312 * Returns Y in image coordinates.
313 * @param {number} y Y in screen coordinates.
314 * @return {number} Y in image coordinates.
316 Viewport.prototype.screenToImageY = function(y) {
317 return Math.round((y - this.imageBoundsOnScreen_.top) / this.scale_);
321 * Returns a rectangle in image coordinates.
322 * @param {!ImageRect} rect Rectangle in screen coordinates.
323 * @return {!ImageRect} Rectangle in image coordinates.
325 Viewport.prototype.screenToImageRect = function(rect) {
326 return new ImageRect(
327 this.screenToImageX(rect.left),
328 this.screenToImageY(rect.top),
329 this.screenToImageSize(rect.width),
330 this.screenToImageSize(rect.height));
334 * Returns size in screen coordinates.
335 * @param {number} size Size in image coordinates.
336 * @return {number} Size in screen coordinates.
338 Viewport.prototype.imageToScreenSize = function(size) {
339 return size * this.scale_;
343 * Returns X in screen coordinates.
344 * @param {number} x X in image coordinates.
345 * @return {number} X in screen coordinates.
347 Viewport.prototype.imageToScreenX = function(x) {
348 return Math.round(this.imageBoundsOnScreen_.left + x * this.scale_);
352 * Returns Y in screen coordinates.
353 * @param {number} y Y in image coordinates.
354 * @return {number} Y in screen coordinates.
356 Viewport.prototype.imageToScreenY = function(y) {
357 return Math.round(this.imageBoundsOnScreen_.top + y * this.scale_);
361 * Returns a rectangle in screen coordinates.
362 * @param {!ImageRect} rect Rectangle in image coordinates.
363 * @return {!ImageRect} Rectangle in screen coordinates.
365 Viewport.prototype.imageToScreenRect = function(rect) {
366 return new ImageRect(
367 this.imageToScreenX(rect.left),
368 this.imageToScreenY(rect.top),
369 Math.round(this.imageToScreenSize(rect.width)),
370 Math.round(this.imageToScreenSize(rect.height)));
374 * Returns a rectangle with given geometry.
375 * @param {number} width Width of the rectangle.
376 * @param {number} height Height of the rectangle.
377 * @param {number} offsetX X-offset of center position of the rectangle.
378 * @param {number} offsetY Y-offset of center position of the rectangle.
379 * @return {!ImageRect} Rectangle with given geometry.
380 * @private
382 Viewport.prototype.getCenteredRect_ = function(
383 width, height, offsetX, offsetY) {
384 return new ImageRect(
385 ~~((this.screenBounds_.width - width) / 2) + offsetX,
386 ~~((this.screenBounds_.height - height) / 2) + offsetY,
387 width,
388 height);
392 * Resets zoom and offset.
394 Viewport.prototype.resetView = function() {
395 this.zoom_ = 1;
396 this.offsetX_ = 0;
397 this.offsetY_ = 0;
398 this.rotation_ = 0;
399 this.update_();
403 * Recalculate the viewport parameters.
404 * @private
406 Viewport.prototype.update_ = function() {
407 // Update scale.
408 this.scale_ = this.getFittingScaleForImageSize_(
409 this.imageBounds_.width, this.imageBounds_.height);
411 // Limit offset values.
412 var zoomedWidht;
413 var zoomedHeight;
414 if (this.rotation_ % 2 == 0) {
415 zoomedWidht = ~~(this.imageBounds_.width * this.scale_ * this.zoom_);
416 zoomedHeight = ~~(this.imageBounds_.height * this.scale_ * this.zoom_);
417 } else {
418 var scale = this.getFittingScaleForImageSize_(
419 this.imageBounds_.height, this.imageBounds_.width);
420 zoomedWidht = ~~(this.imageBounds_.height * scale * this.zoom_);
421 zoomedHeight = ~~(this.imageBounds_.width * scale * this.zoom_);
423 var dx = Math.max(zoomedWidht - this.screenBounds_.width, 0) / 2;
424 var dy = Math.max(zoomedHeight - this.screenBounds_.height, 0) / 2;
425 this.offsetX_ = ImageUtil.clamp(-dx, this.offsetX_, dx);
426 this.offsetY_ = ImageUtil.clamp(-dy, this.offsetY_, dy);
428 // Image bounds on screen.
429 this.imageBoundsOnScreen_ = this.getCenteredRect_(
430 zoomedWidht, zoomedHeight, this.offsetX_, this.offsetY_);
432 // Image bounds of element (that is not applied zoom and offset) on screen.
433 var oldBounds = this.imageElementBoundsOnScreen_;
434 this.imageElementBoundsOnScreen_ = this.getCenteredRect_(
435 ~~(this.imageBounds_.width * this.scale_),
436 ~~(this.imageBounds_.height * this.scale_),
439 if (!oldBounds ||
440 this.imageElementBoundsOnScreen_.width != oldBounds.width ||
441 this.imageElementBoundsOnScreen_.height != oldBounds.height) {
442 this.generation_++;
445 // Image bounds on screen clipped with the screen bounds.
446 var left = Math.max(this.imageBoundsOnScreen_.left, 0);
447 var top = Math.max(this.imageBoundsOnScreen_.top, 0);
448 var right = Math.min(
449 this.imageBoundsOnScreen_.right, this.screenBounds_.width);
450 var bottom = Math.min(
451 this.imageBoundsOnScreen_.bottom, this.screenBounds_.height);
452 this.imageBoundsOnScreenClipped_ = new ImageRect(
453 left, top, right - left, bottom - top);
457 * Clones the viewport.
458 * @return {!Viewport} New instance.
460 Viewport.prototype.clone = function() {
461 var viewport = new Viewport();
462 viewport.imageBounds_ = ImageRect.createFromBounds(this.imageBounds_);
463 viewport.screenBounds_ = ImageRect.createFromBounds(this.screenBounds_);
464 viewport.scale_ = this.scale_;
465 viewport.zoom_ = this.zoom_;
466 viewport.offsetX_ = this.offsetX_;
467 viewport.offsetY_ = this.offsetY_;
468 viewport.rotation_ = this.rotation_;
469 viewport.generation_ = this.generation_;
470 viewport.update_();
471 return viewport;
475 * Obtains CSS transformation for the screen image.
476 * @return {string} Transformation description.
478 Viewport.prototype.getTransformation = function() {
479 var rotationScaleAdjustment;
480 if (this.rotation_ % 2) {
481 rotationScaleAdjustment = this.getFittingScaleForImageSize_(
482 this.imageBounds_.height, this.imageBounds_.width) / this.scale_;
483 } else {
484 rotationScaleAdjustment = 1;
486 return [
487 'translate(' + this.offsetX_ + 'px, ' + this.offsetY_ + 'px) ',
488 'rotate(' + (this.rotation_ * 90) + 'deg)',
489 'scale(' + (this.zoom_ * rotationScaleAdjustment) + ')'
490 ].join(' ');
494 * Obtains shift CSS transformation for the screen image.
495 * @param {number} dx Amount of shift.
496 * @return {string} Transformation description.
498 Viewport.prototype.getShiftTransformation = function(dx) {
499 return 'translateX(' + dx + 'px) ' + this.getTransformation();
503 * Obtains CSS transformation that makes the rotated image fit the original
504 * image. The new rotated image that the transformation is applied to looks the
505 * same with original image.
507 * @param {boolean} orientation Orientation of the rotation from the original
508 * image to the rotated image. True is for clockwise and false is for
509 * counterclockwise.
510 * @return {string} Transformation description.
512 Viewport.prototype.getInverseTransformForRotatedImage = function(orientation) {
513 var previousImageWidth = this.imageBounds_.height;
514 var previousImageHeight = this.imageBounds_.width;
515 var oldScale = this.getFittingScaleForImageSize_(
516 previousImageWidth, previousImageHeight);
517 var scaleRatio = oldScale / this.scale_;
518 var degree = orientation ? '-90deg' : '90deg';
519 return [
520 'scale(' + scaleRatio + ')',
521 'rotate(' + degree + ')',
522 this.getTransformation()
523 ].join(' ');
527 * Obtains CSS transformation that makes the cropped image fit the original
528 * image. The new cropped image that the transformation is applied to fits to
529 * the cropped rectangle in the original image.
531 * @param {number} imageWidth Width of the original image.
532 * @param {number} imageHeight Height of the original image.
533 * @param {!ImageRect} imageCropRect Crop rectangle in the image's coordinate
534 * system.
535 * @return {string} Transformation description.
537 Viewport.prototype.getInverseTransformForCroppedImage =
538 function(imageWidth, imageHeight, imageCropRect) {
539 var wholeScale = this.getFittingScaleForImageSize_(
540 imageWidth, imageHeight);
541 var croppedScale = this.getFittingScaleForImageSize_(
542 imageCropRect.width, imageCropRect.height);
543 var dx =
544 (imageCropRect.left + imageCropRect.width / 2 - imageWidth / 2) *
545 wholeScale;
546 var dy =
547 (imageCropRect.top + imageCropRect.height / 2 - imageHeight / 2) *
548 wholeScale;
549 return [
550 'translate(' + dx + 'px,' + dy + 'px)',
551 'scale(' + wholeScale / croppedScale + ')',
552 this.getTransformation()
553 ].join(' ');
557 * Obtains CSS transformation that makes the image fit to the screen rectangle.
559 * @param {!ImageRect} screenRect Screen rectangle.
560 * @return {string} Transformation description.
562 Viewport.prototype.getScreenRectTransformForImage = function(screenRect) {
563 var imageBounds = this.getImageElementBoundsOnScreen();
564 var scaleX = screenRect.width / imageBounds.width;
565 var scaleY = screenRect.height / imageBounds.height;
566 var screenWidth = this.screenBounds_.width;
567 var screenHeight = this.screenBounds_.height;
568 var dx = screenRect.left + screenRect.width / 2 - screenWidth / 2;
569 var dy = screenRect.top + screenRect.height / 2 - screenHeight / 2;
570 return [
571 'translate(' + dx + 'px,' + dy + 'px)',
572 'scale(' + scaleX + ',' + scaleY + ')',
573 this.getTransformation()
574 ].join(' ');