Home | History | Annotate | Download | only in image_editor
      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.
      4 
      5 'use strict';
      6 
      7 /**
      8  * Crop mode.
      9  * @constructor
     10  */
     11 ImageEditor.Mode.Crop = function() {
     12   ImageEditor.Mode.call(this, 'crop', 'GALLERY_CROP');
     13 };
     14 
     15 ImageEditor.Mode.Crop.prototype = {__proto__: ImageEditor.Mode.prototype};
     16 
     17 /**
     18  * TODO(JSDOC).
     19  */
     20 ImageEditor.Mode.Crop.prototype.setUp = function() {
     21   ImageEditor.Mode.prototype.setUp.apply(this, arguments);
     22 
     23   var container = this.getImageView().container_;
     24   var doc = container.ownerDocument;
     25 
     26   this.domOverlay_ = doc.createElement('div');
     27   this.domOverlay_.className = 'crop-overlay';
     28   container.appendChild(this.domOverlay_);
     29 
     30   this.shadowTop_ = doc.createElement('div');
     31   this.shadowTop_.className = 'shadow';
     32   this.domOverlay_.appendChild(this.shadowTop_);
     33 
     34   this.middleBox_ = doc.createElement('div');
     35   this.middleBox_.className = 'middle-box';
     36   this.domOverlay_.appendChild(this.middleBox_);
     37 
     38   this.shadowLeft_ = doc.createElement('div');
     39   this.shadowLeft_.className = 'shadow';
     40   this.middleBox_.appendChild(this.shadowLeft_);
     41 
     42   this.cropFrame_ = doc.createElement('div');
     43   this.cropFrame_.className = 'crop-frame';
     44   this.middleBox_.appendChild(this.cropFrame_);
     45 
     46   this.shadowRight_ = doc.createElement('div');
     47   this.shadowRight_.className = 'shadow';
     48   this.middleBox_.appendChild(this.shadowRight_);
     49 
     50   this.shadowBottom_ = doc.createElement('div');
     51   this.shadowBottom_.className = 'shadow';
     52   this.domOverlay_.appendChild(this.shadowBottom_);
     53 
     54   var cropFrame = this.cropFrame_;
     55   function addCropFrame(className) {
     56     var div = doc.createElement('div');
     57     div.className = className;
     58     cropFrame.appendChild(div);
     59   }
     60 
     61   addCropFrame('left top corner');
     62   addCropFrame('top horizontal');
     63   addCropFrame('right top corner');
     64   addCropFrame('left vertical');
     65   addCropFrame('right vertical');
     66   addCropFrame('left bottom corner');
     67   addCropFrame('bottom horizontal');
     68   addCropFrame('right bottom corner');
     69 
     70   this.onResizedBound_ = this.onResized_.bind(this);
     71   window.addEventListener('resize', this.onResizedBound_);
     72 
     73   this.createDefaultCrop();
     74 };
     75 
     76 /**
     77  * Handles resizing of the window and updates the crop rectangle.
     78  * @private
     79  */
     80 ImageEditor.Mode.Crop.prototype.onResized_ = function() {
     81   this.positionDOM();
     82 };
     83 
     84 /**
     85  * TODO(JSDOC).
     86  */
     87 ImageEditor.Mode.Crop.prototype.reset = function() {
     88   ImageEditor.Mode.prototype.reset.call(this);
     89   this.createDefaultCrop();
     90 };
     91 
     92 /**
     93  * TODO(JSDOC).
     94  */
     95 ImageEditor.Mode.Crop.prototype.positionDOM = function() {
     96   var screenClipped = this.viewport_.getScreenClipped();
     97 
     98   var screenCrop = this.viewport_.imageToScreenRect(this.cropRect_.getRect());
     99   var delta = ImageEditor.Mode.Crop.MOUSE_GRAB_RADIUS;
    100   this.editor_.hideOverlappingTools(
    101       screenCrop.inflate(delta, delta),
    102       screenCrop.inflate(-delta, -delta));
    103 
    104   this.domOverlay_.style.left = screenClipped.left + 'px';
    105   this.domOverlay_.style.top = screenClipped.top + 'px';
    106   this.domOverlay_.style.width = screenClipped.width + 'px';
    107   this.domOverlay_.style.height = screenClipped.height + 'px';
    108 
    109   this.shadowLeft_.style.width = screenCrop.left - screenClipped.left + 'px';
    110 
    111   this.shadowTop_.style.height = screenCrop.top - screenClipped.top + 'px';
    112 
    113   this.shadowRight_.style.width = screenClipped.left + screenClipped.width -
    114       (screenCrop.left + screenCrop.width) + 'px';
    115 
    116   this.shadowBottom_.style.height = screenClipped.top + screenClipped.height -
    117       (screenCrop.top + screenCrop.height) + 'px';
    118 };
    119 
    120 /**
    121  * TODO(JSDOC).
    122  */
    123 ImageEditor.Mode.Crop.prototype.cleanUpUI = function() {
    124   ImageEditor.Mode.prototype.cleanUpUI.apply(this, arguments);
    125   this.domOverlay_.parentNode.removeChild(this.domOverlay_);
    126   this.domOverlay_ = null;
    127   this.editor_.hideOverlappingTools();
    128   window.removeEventListener(this.onResizedBound_);
    129   this.onResizedBound_ = null;
    130 };
    131 
    132 /**
    133  * @const
    134  * @type {number}
    135  */
    136 ImageEditor.Mode.Crop.MOUSE_GRAB_RADIUS = 6;
    137 /**
    138  * @const
    139  * @type {number}
    140  */
    141 ImageEditor.Mode.Crop.TOUCH_GRAB_RADIUS = 20;
    142 
    143 /**
    144  * TODO(JSDOC).
    145  * @return {Command.Crop}  // TODO(JSDOC).
    146  */
    147 ImageEditor.Mode.Crop.prototype.getCommand = function() {
    148   var cropImageRect = this.cropRect_.getRect();
    149   return new Command.Crop(cropImageRect);
    150 };
    151 
    152 /**
    153  * TODO(JSDOC).
    154  */
    155 ImageEditor.Mode.Crop.prototype.createDefaultCrop = function() {
    156   var rect = new Rect(this.getViewport().getImageClipped());
    157   rect = rect.inflate(
    158       -Math.round(rect.width / 6), -Math.round(rect.height / 6));
    159   this.cropRect_ = new DraggableRect(rect, this.getViewport());
    160   this.positionDOM();
    161 };
    162 
    163 /**
    164  * TODO(JSDOC).
    165  * @param {number} x X coordinate for cursor.
    166  * @param {number} y Y coordinate for cursor.
    167  * @param {boolean} mouseDown If mouse button is down.
    168  * @return {string} A value for style.cursor CSS property.
    169  */
    170 ImageEditor.Mode.Crop.prototype.getCursorStyle = function(x, y, mouseDown) {
    171   return this.cropRect_.getCursorStyle(x, y, mouseDown);
    172 };
    173 
    174 /**
    175  * TODO(JSDOC).
    176  * @param {number} x Event X coordinate.
    177  * @param {number} y Event Y coordinate.
    178  * @param {boolean} touch True if it's a touch event, false if mouse.
    179  * @return {function(number,number)} A function to be called on mouse drag.
    180  */
    181 ImageEditor.Mode.Crop.prototype.getDragHandler = function(x, y, touch) {
    182   var cropDragHandler = this.cropRect_.getDragHandler(x, y, touch);
    183   if (!cropDragHandler) return null;
    184 
    185   var self = this;
    186   return function(x, y) {
    187     cropDragHandler(x, y);
    188     self.markUpdated();
    189     self.positionDOM();
    190   };
    191 };
    192 
    193 /**
    194  * TODO(JSDOC).
    195  * @param {number} x X coordinate of the event.
    196  * @param {number} y Y coordinate of the event.
    197  * @return {ImageBuffer.DoubleTapAction} Action to perform as result.
    198  */
    199 ImageEditor.Mode.Crop.prototype.getDoubleTapAction = function(x, y) {
    200   return this.cropRect_.getDoubleTapAction(x, y);
    201 };
    202 
    203 /*
    204  * A draggable rectangle over the image.
    205  * @param {Rect} rect  // TODO(JSDOC).
    206  * @param {Viewport} viewport  // TODO(JSDOC).
    207  * @constructor
    208  */
    209 function DraggableRect(rect, viewport) {
    210   // The bounds are not held in a regular rectangle (with width/height).
    211   // left/top/right/bottom held instead for convenience.
    212   this.bounds_ = {};
    213   this.bounds_[DraggableRect.LEFT] = rect.left;
    214   this.bounds_[DraggableRect.RIGHT] = rect.left + rect.width;
    215   this.bounds_[DraggableRect.TOP] = rect.top;
    216   this.bounds_[DraggableRect.BOTTOM] = rect.top + rect.height;
    217 
    218   this.viewport_ = viewport;
    219 
    220   this.oppositeSide_ = {};
    221   this.oppositeSide_[DraggableRect.LEFT] = DraggableRect.RIGHT;
    222   this.oppositeSide_[DraggableRect.RIGHT] = DraggableRect.LEFT;
    223   this.oppositeSide_[DraggableRect.TOP] = DraggableRect.BOTTOM;
    224   this.oppositeSide_[DraggableRect.BOTTOM] = DraggableRect.TOP;
    225 
    226   // Translation table to form CSS-compatible cursor style.
    227   this.cssSide_ = {};
    228   this.cssSide_[DraggableRect.LEFT] = 'w';
    229   this.cssSide_[DraggableRect.TOP] = 'n';
    230   this.cssSide_[DraggableRect.RIGHT] = 'e';
    231   this.cssSide_[DraggableRect.BOTTOM] = 's';
    232   this.cssSide_[DraggableRect.NONE] = '';
    233 }
    234 
    235 // Static members to simplify reflective access to the bounds.
    236 /**
    237  * @const
    238  * @type {string}
    239  */
    240 DraggableRect.LEFT = 'left';
    241 /**
    242  * @const
    243  * @type {string}
    244  */
    245 DraggableRect.RIGHT = 'right';
    246 /**
    247  * @const
    248  * @type {string}
    249  */
    250 DraggableRect.TOP = 'top';
    251 /**
    252  * @const
    253  * @type {string}
    254  */
    255 DraggableRect.BOTTOM = 'bottom';
    256 /**
    257  * @const
    258  * @type {string}
    259  */
    260 DraggableRect.NONE = 'none';
    261 
    262 /**
    263  * TODO(JSDOC)
    264  * @return {number}  // TODO(JSDOC).
    265  */
    266 DraggableRect.prototype.getLeft = function() {
    267   return this.bounds_[DraggableRect.LEFT];
    268 };
    269 
    270 /**
    271  * TODO(JSDOC)
    272  * @return {number}  // TODO(JSDOC).
    273  */
    274 DraggableRect.prototype.getRight = function() {
    275   return this.bounds_[DraggableRect.RIGHT];
    276 };
    277 
    278 /**
    279  * TODO(JSDOC)
    280  * @return {number}  // TODO(JSDOC).
    281  */
    282 DraggableRect.prototype.getTop = function() {
    283   return this.bounds_[DraggableRect.TOP];
    284 };
    285 
    286 /**
    287  * TODO(JSDOC)
    288  * @return {number}  // TODO(JSDOC).
    289  */
    290 DraggableRect.prototype.getBottom = function() {
    291   return this.bounds_[DraggableRect.BOTTOM];
    292 };
    293 
    294 /**
    295  * TODO(JSDOC)
    296  * @return {Rect}  // TODO(JSDOC).
    297  */
    298 DraggableRect.prototype.getRect = function() {
    299   return new Rect(this.bounds_);
    300 };
    301 
    302 /**
    303  * TODO(JSDOC)
    304  * @param {number} x X coordinate for cursor.
    305  * @param {number} y Y coordinate for cursor.
    306  * @param {boolean} touch  // TODO(JSDOC).
    307  * @return {Object}  // TODO(JSDOC).
    308  */
    309 DraggableRect.prototype.getDragMode = function(x, y, touch) {
    310   var result = {
    311     xSide: DraggableRect.NONE,
    312     ySide: DraggableRect.NONE
    313   };
    314 
    315   var bounds = this.bounds_;
    316   var R = this.viewport_.screenToImageSize(
    317       touch ? ImageEditor.Mode.Crop.TOUCH_GRAB_RADIUS :
    318               ImageEditor.Mode.Crop.MOUSE_GRAB_RADIUS);
    319 
    320   var circle = new Circle(x, y, R);
    321 
    322   var xBetween = ImageUtil.between(bounds.left, x, bounds.right);
    323   var yBetween = ImageUtil.between(bounds.top, y, bounds.bottom);
    324 
    325   if (circle.inside(bounds.left, bounds.top)) {
    326     result.xSide = DraggableRect.LEFT;
    327     result.ySide = DraggableRect.TOP;
    328   } else if (circle.inside(bounds.left, bounds.bottom)) {
    329     result.xSide = DraggableRect.LEFT;
    330     result.ySide = DraggableRect.BOTTOM;
    331   } else if (circle.inside(bounds.right, bounds.top)) {
    332     result.xSide = DraggableRect.RIGHT;
    333     result.ySide = DraggableRect.TOP;
    334   } else if (circle.inside(bounds.right, bounds.bottom)) {
    335     result.xSide = DraggableRect.RIGHT;
    336     result.ySide = DraggableRect.BOTTOM;
    337   } else if (yBetween && Math.abs(x - bounds.left) <= R) {
    338     result.xSide = DraggableRect.LEFT;
    339   } else if (yBetween && Math.abs(x - bounds.right) <= R) {
    340     result.xSide = DraggableRect.RIGHT;
    341   } else if (xBetween && Math.abs(y - bounds.top) <= R) {
    342     result.ySide = DraggableRect.TOP;
    343   } else if (xBetween && Math.abs(y - bounds.bottom) <= R) {
    344     result.ySide = DraggableRect.BOTTOM;
    345   } else if (xBetween && yBetween) {
    346     result.whole = true;
    347   } else {
    348     result.newcrop = true;
    349     result.xSide = DraggableRect.RIGHT;
    350     result.ySide = DraggableRect.BOTTOM;
    351   }
    352 
    353   return result;
    354 };
    355 
    356 /**
    357  * TODO(JSDOC)
    358  * @param {number} x X coordinate for cursor.
    359  * @param {number} y Y coordinate for cursor.
    360  * @param {boolean} mouseDown  If mouse button is down.
    361  * @return {string}  // TODO(JSDOC).
    362  */
    363 DraggableRect.prototype.getCursorStyle = function(x, y, mouseDown) {
    364   var mode;
    365   if (mouseDown) {
    366     mode = this.dragMode_;
    367   } else {
    368     mode = this.getDragMode(
    369         this.viewport_.screenToImageX(x), this.viewport_.screenToImageY(y));
    370   }
    371   if (mode.whole) return 'move';
    372   if (mode.newcrop) return 'crop';
    373   return this.cssSide_[mode.ySide] + this.cssSide_[mode.xSide] + '-resize';
    374 };
    375 
    376 /**
    377  * TODO(JSDOC)
    378  * @param {number} x X coordinate for cursor.
    379  * @param {number} y Y coordinate for cursor.
    380  * @param {boolean} touch  // TODO(JSDOC).
    381  * @return {function(number,number)}  // TODO(JSDOC).
    382  */
    383 DraggableRect.prototype.getDragHandler = function(x, y, touch) {
    384   x = this.viewport_.screenToImageX(x);
    385   y = this.viewport_.screenToImageY(y);
    386 
    387   var clipRect = this.viewport_.getImageClipped();
    388   if (!clipRect.inside(x, y)) return null;
    389 
    390   this.dragMode_ = this.getDragMode(x, y, touch);
    391 
    392   var self = this;
    393 
    394   var mouseBiasX;
    395   var mouseBiasY;
    396 
    397   var fixedWidth = 0;
    398   var fixedHeight = 0;
    399 
    400   var resizeFuncX;
    401   var resizeFuncY;
    402 
    403   if (this.dragMode_.whole) {
    404     mouseBiasX = this.bounds_.left - x;
    405     fixedWidth = this.bounds_.right - this.bounds_.left;
    406     resizeFuncX = function(x) {
    407       self.bounds_.left = x;
    408       self.bounds_.right = self.bounds_.left + fixedWidth;
    409     };
    410     mouseBiasY = this.bounds_.top - y;
    411     fixedHeight = this.bounds_.bottom - this.bounds_.top;
    412     resizeFuncY = function(y) {
    413       self.bounds_.top = y;
    414       self.bounds_.bottom = self.bounds_.top + fixedHeight;
    415     };
    416   } else {
    417     var checkNewCrop = function() {
    418       if (self.dragMode_.newcrop) {
    419         self.dragMode_.newcrop = false;
    420         self.bounds_.left = self.bounds_.right = x;
    421         self.bounds_.top = self.bounds_.bottom = y;
    422         mouseBiasX = 0;
    423         mouseBiasY = 0;
    424       }
    425     };
    426 
    427     var flipSide = function(side) {
    428       var opposite = self.oppositeSide_[side];
    429       var temp = self.bounds_[side];
    430       self.bounds_[side] = self.bounds_[opposite];
    431       self.bounds_[opposite] = temp;
    432       return opposite;
    433     };
    434 
    435     if (this.dragMode_.xSide != DraggableRect.NONE) {
    436       mouseBiasX = self.bounds_[this.dragMode_.xSide] - x;
    437       resizeFuncX = function(x) {
    438         checkNewCrop();
    439         self.bounds_[self.dragMode_.xSide] = x;
    440         if (self.bounds_.left > self.bounds_.right) {
    441           self.dragMode_.xSide = flipSide(self.dragMode_.xSide);
    442         }
    443       };
    444     }
    445     if (this.dragMode_.ySide != DraggableRect.NONE) {
    446       mouseBiasY = self.bounds_[this.dragMode_.ySide] - y;
    447       resizeFuncY = function(y) {
    448         checkNewCrop();
    449         self.bounds_[self.dragMode_.ySide] = y;
    450         if (self.bounds_.top > self.bounds_.bottom) {
    451           self.dragMode_.ySide = flipSide(self.dragMode_.ySide);
    452         }
    453       };
    454     }
    455   }
    456 
    457   function convertX(x) {
    458     return ImageUtil.clamp(
    459         clipRect.left,
    460         self.viewport_.screenToImageX(x) + mouseBiasX,
    461         clipRect.left + clipRect.width - fixedWidth);
    462   }
    463 
    464   function convertY(y) {
    465     return ImageUtil.clamp(
    466         clipRect.top,
    467         self.viewport_.screenToImageY(y) + mouseBiasY,
    468         clipRect.top + clipRect.height - fixedHeight);
    469   }
    470 
    471   return function(x, y) {
    472     if (resizeFuncX) resizeFuncX(convertX(x));
    473     if (resizeFuncY) resizeFuncY(convertY(y));
    474   };
    475 };
    476 
    477 /**
    478  * TODO(JSDOC)
    479  * @param {number} x X coordinate for cursor.
    480  * @param {number} y Y coordinate for cursor.
    481  * @param {boolean} touch  // TODO(JSDOC).
    482  * @return {ImageBuffer.DoubleTapAction}  // TODO(JSDOC).
    483  */
    484 DraggableRect.prototype.getDoubleTapAction = function(x, y, touch) {
    485   x = this.viewport_.screenToImageX(x);
    486   y = this.viewport_.screenToImageY(y);
    487 
    488   var clipRect = this.viewport_.getImageClipped();
    489   if (clipRect.inside(x, y))
    490     return ImageBuffer.DoubleTapAction.COMMIT;
    491   else
    492     return ImageBuffer.DoubleTapAction.NOTHING;
    493 };
    494