- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / file_manager / foreground / js / image_editor / image_transform.js
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.
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 };