1 // Copyright (c) 2012 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.
11 ImageEditor.Mode.Crop = function() {
12 ImageEditor.Mode.call(this, 'crop', 'GALLERY_CROP');
15 ImageEditor.Mode.Crop.prototype = {__proto__: ImageEditor.Mode.prototype};
20 ImageEditor.Mode.Crop.prototype.setUp = function() {
21 ImageEditor.Mode.prototype.setUp.apply(this, arguments);
23 var container = this.getImageView().container_;
24 var doc = container.ownerDocument;
26 this.domOverlay_ = doc.createElement('div');
27 this.domOverlay_.className = 'crop-overlay';
28 container.appendChild(this.domOverlay_);
30 this.shadowTop_ = doc.createElement('div');
31 this.shadowTop_.className = 'shadow';
32 this.domOverlay_.appendChild(this.shadowTop_);
34 this.middleBox_ = doc.createElement('div');
35 this.middleBox_.className = 'middle-box';
36 this.domOverlay_.appendChild(this.middleBox_);
38 this.shadowLeft_ = doc.createElement('div');
39 this.shadowLeft_.className = 'shadow';
40 this.middleBox_.appendChild(this.shadowLeft_);
42 this.cropFrame_ = doc.createElement('div');
43 this.cropFrame_.className = 'crop-frame';
44 this.middleBox_.appendChild(this.cropFrame_);
46 this.shadowRight_ = doc.createElement('div');
47 this.shadowRight_.className = 'shadow';
48 this.middleBox_.appendChild(this.shadowRight_);
50 this.shadowBottom_ = doc.createElement('div');
51 this.shadowBottom_.className = 'shadow';
52 this.domOverlay_.appendChild(this.shadowBottom_);
54 var cropFrame = this.cropFrame_;
55 function addCropFrame(className) {
56 var div = doc.createElement('div');
57 div.className = className;
58 cropFrame.appendChild(div);
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');
70 this.onResizedBound_ = this.onResized_.bind(this);
71 window.addEventListener('resize', this.onResizedBound_);
73 this.createDefaultCrop();
77 * Handles resizing of the window and updates the crop rectangle.
80 ImageEditor.Mode.Crop.prototype.onResized_ = function() {
87 ImageEditor.Mode.Crop.prototype.reset = function() {
88 ImageEditor.Mode.prototype.reset.call(this);
89 this.createDefaultCrop();
95 ImageEditor.Mode.Crop.prototype.positionDOM = function() {
96 var screenClipped = this.viewport_.getScreenClipped();
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));
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';
109 this.shadowLeft_.style.width = screenCrop.left - screenClipped.left + 'px';
111 this.shadowTop_.style.height = screenCrop.top - screenClipped.top + 'px';
113 this.shadowRight_.style.width = screenClipped.left + screenClipped.width -
114 (screenCrop.left + screenCrop.width) + 'px';
116 this.shadowBottom_.style.height = screenClipped.top + screenClipped.height -
117 (screenCrop.top + screenCrop.height) + 'px';
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;
136 ImageEditor.Mode.Crop.MOUSE_GRAB_RADIUS = 6;
141 ImageEditor.Mode.Crop.TOUCH_GRAB_RADIUS = 20;
145 * @return {Command.Crop} // TODO(JSDOC).
147 ImageEditor.Mode.Crop.prototype.getCommand = function() {
148 var cropImageRect = this.cropRect_.getRect();
149 return new Command.Crop(cropImageRect);
155 ImageEditor.Mode.Crop.prototype.createDefaultCrop = function() {
156 var rect = new Rect(this.getViewport().getImageClipped());
158 -Math.round(rect.width / 6), -Math.round(rect.height / 6));
159 this.cropRect_ = new DraggableRect(rect, this.getViewport());
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.
170 ImageEditor.Mode.Crop.prototype.getCursorStyle = function(x, y, mouseDown) {
171 return this.cropRect_.getCursorStyle(x, y, mouseDown);
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.
181 ImageEditor.Mode.Crop.prototype.getDragHandler = function(x, y, touch) {
182 var cropDragHandler = this.cropRect_.getDragHandler(x, y, touch);
183 if (!cropDragHandler) return null;
186 return function(x, y) {
187 cropDragHandler(x, y);
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.
199 ImageEditor.Mode.Crop.prototype.getDoubleTapAction = function(x, y) {
200 return this.cropRect_.getDoubleTapAction(x, y);
204 * A draggable rectangle over the image.
205 * @param {Rect} rect // TODO(JSDOC).
206 * @param {Viewport} viewport // TODO(JSDOC).
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.
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;
218 this.viewport_ = viewport;
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;
226 // Translation table to form CSS-compatible cursor style.
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] = '';
235 // Static members to simplify reflective access to the bounds.
240 DraggableRect.LEFT = 'left';
245 DraggableRect.RIGHT = 'right';
250 DraggableRect.TOP = 'top';
255 DraggableRect.BOTTOM = 'bottom';
260 DraggableRect.NONE = 'none';
264 * @return {number} // TODO(JSDOC).
266 DraggableRect.prototype.getLeft = function() {
267 return this.bounds_[DraggableRect.LEFT];
272 * @return {number} // TODO(JSDOC).
274 DraggableRect.prototype.getRight = function() {
275 return this.bounds_[DraggableRect.RIGHT];
280 * @return {number} // TODO(JSDOC).
282 DraggableRect.prototype.getTop = function() {
283 return this.bounds_[DraggableRect.TOP];
288 * @return {number} // TODO(JSDOC).
290 DraggableRect.prototype.getBottom = function() {
291 return this.bounds_[DraggableRect.BOTTOM];
296 * @return {Rect} // TODO(JSDOC).
298 DraggableRect.prototype.getRect = function() {
299 return new Rect(this.bounds_);
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).
309 DraggableRect.prototype.getDragMode = function(x, y, touch) {
311 xSide: DraggableRect.NONE,
312 ySide: DraggableRect.NONE
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);
320 var circle = new Circle(x, y, R);
322 var xBetween = ImageUtil.between(bounds.left, x, bounds.right);
323 var yBetween = ImageUtil.between(bounds.top, y, bounds.bottom);
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) {
348 result.newcrop = true;
349 result.xSide = DraggableRect.RIGHT;
350 result.ySide = DraggableRect.BOTTOM;
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).
363 DraggableRect.prototype.getCursorStyle = function(x, y, mouseDown) {
366 mode = this.dragMode_;
368 mode = this.getDragMode(
369 this.viewport_.screenToImageX(x), this.viewport_.screenToImageY(y));
371 if (mode.whole) return 'move';
372 if (mode.newcrop) return 'crop';
373 return this.cssSide_[mode.ySide] + this.cssSide_[mode.xSide] + '-resize';
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).
383 DraggableRect.prototype.getDragHandler = function(x, y, touch) {
384 x = this.viewport_.screenToImageX(x);
385 y = this.viewport_.screenToImageY(y);
387 var clipRect = this.viewport_.getImageClipped();
388 if (!clipRect.inside(x, y)) return null;
390 this.dragMode_ = this.getDragMode(x, y, touch);
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;
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;
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;
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;
435 if (this.dragMode_.xSide != DraggableRect.NONE) {
436 mouseBiasX = self.bounds_[this.dragMode_.xSide] - x;
437 resizeFuncX = function(x) {
439 self.bounds_[self.dragMode_.xSide] = x;
440 if (self.bounds_.left > self.bounds_.right) {
441 self.dragMode_.xSide = flipSide(self.dragMode_.xSide);
445 if (this.dragMode_.ySide != DraggableRect.NONE) {
446 mouseBiasY = self.bounds_[this.dragMode_.ySide] - y;
447 resizeFuncY = function(y) {
449 self.bounds_[self.dragMode_.ySide] = y;
450 if (self.bounds_.top > self.bounds_.bottom) {
451 self.dragMode_.ySide = flipSide(self.dragMode_.ySide);
457 function convertX(x) {
458 return ImageUtil.clamp(
460 self.viewport_.screenToImageX(x) + mouseBiasX,
461 clipRect.left + clipRect.width - fixedWidth);
464 function convertY(y) {
465 return ImageUtil.clamp(
467 self.viewport_.screenToImageY(y) + mouseBiasY,
468 clipRect.top + clipRect.height - fixedHeight);
471 return function(x, y) {
472 if (resizeFuncX) resizeFuncX(convertX(x));
473 if (resizeFuncY) resizeFuncY(convertY(y));
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).
484 DraggableRect.prototype.getDoubleTapAction = function(x, y, touch) {
485 x = this.viewport_.screenToImageX(x);
486 y = this.viewport_.screenToImageY(y);
488 var clipRect = this.viewport_.getImageClipped();
489 if (clipRect.inside(x, y))
490 return ImageBuffer.DoubleTapAction.COMMIT;
492 return ImageBuffer.DoubleTapAction.NOTHING;