summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkaznacheev@chromium.org <kaznacheev@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-05 10:40:30 +0000
committerkaznacheev@chromium.org <kaznacheev@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-05 10:40:30 +0000
commit8b1ce2cbd0211c3b2126a6cc5189b864b6afb5b7 (patch)
tree0843b3d19fb934e35c6b03dc380dd5ed0d257e04
parent6278441fe3cad3a15a61da31ca502fc3768b8b43 (diff)
downloadchromium_src-8b1ce2cbd0211c3b2126a6cc5189b864b6afb5b7.zip
chromium_src-8b1ce2cbd0211c3b2126a6cc5189b864b6afb5b7.tar.gz
chromium_src-8b1ce2cbd0211c3b2126a6cc5189b864b6afb5b7.tar.bz2
Improved ChromeOS Gallery
The patch implements most of the desired appearance and behavior for Gallery v1. BUG=chromium-os:19534 TEST= Review URL: http://codereview.chromium.org/8113033 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@104085 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/resources/component_extension_resources.grd7
-rw-r--r--chrome/browser/resources/file_manager/images/gallery/close_x.pngbin0 -> 2942 bytes
-rw-r--r--chrome/browser/resources/file_manager/images/gallery/icon_brightness.pngbin3139 -> 3142 bytes
-rw-r--r--chrome/browser/resources/file_manager/images/gallery/icon_brightness_selected.pngbin3122 -> 3117 bytes
-rw-r--r--chrome/browser/resources/file_manager/images/gallery/icon_cancel_x.pngbin0 -> 3037 bytes
-rw-r--r--chrome/browser/resources/file_manager/images/gallery/icon_contrast.pngbin0 -> 3139 bytes
-rw-r--r--chrome/browser/resources/file_manager/images/gallery/icon_contrast_selected.pngbin0 -> 3122 bytes
-rw-r--r--chrome/browser/resources/file_manager/images/gallery/icon_ok_check.pngbin0 -> 3013 bytes
-rw-r--r--chrome/browser/resources/file_manager/images/gallery/icon_redo.pngbin0 -> 3031 bytes
-rw-r--r--chrome/browser/resources/file_manager/images/gallery/icon_rotate_left.pngbin0 -> 3221 bytes
-rw-r--r--chrome/browser/resources/file_manager/images/gallery/icon_rotate_left_selected.pngbin0 -> 3192 bytes
-rw-r--r--chrome/browser/resources/file_manager/images/gallery/thumb_fade_left.pngbin0 -> 576 bytes
-rw-r--r--chrome/browser/resources/file_manager/images/gallery/thumb_fade_right.pngbin0 -> 585 bytes
-rw-r--r--chrome/browser/resources/file_manager/js/exif_parser.js29
-rw-r--r--chrome/browser/resources/file_manager/js/file_manager.js19
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/commands.js340
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/exif_encoder.js7
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/filter.js24
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/gallery.css418
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/gallery.html2
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/gallery.js604
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/gallery_demo.html22
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/gallery_demo.js50
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/image_adjust.js159
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/image_buffer.js372
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/image_editor.js455
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/image_encoder.js22
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/image_transform.js461
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/image_util.js92
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/image_view.js324
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/viewport.js33
-rw-r--r--chrome/browser/resources/file_manager/js/metadata_dispatcher.js39
-rw-r--r--chrome/browser/resources/file_manager/js/metadata_provider.js7
-rw-r--r--chrome/browser/resources/file_manager/js/mock_chrome.js8
34 files changed, 2143 insertions, 1351 deletions
diff --git a/chrome/browser/resources/component_extension_resources.grd b/chrome/browser/resources/component_extension_resources.grd
index 0f43652..282e5dc 100644
--- a/chrome/browser/resources/component_extension_resources.grd
+++ b/chrome/browser/resources/component_extension_resources.grd
@@ -79,12 +79,19 @@
<include name="IDR_FILE_MANAGER_IMG_GALLERY_CROP_SELECTED" file="file_manager/images/gallery/icon_crop_selected.png" type="BINDATA" />
<include name="IDR_FILE_MANAGER_IMG_GALLERY_BRIGHTNESS" file="file_manager/images/gallery/icon_brightness.png" type="BINDATA" />
<include name="IDR_FILE_MANAGER_IMG_GALLERY_BRIGHTNESS_SELECTED" file="file_manager/images/gallery/icon_brightness_selected.png" type="BINDATA" />
+ <include name="IDR_FILE_MANAGER_IMG_GALLERY_CONTRAST" file="file_manager/images/gallery/icon_contrast.png" type="BINDATA" />
+ <include name="IDR_FILE_MANAGER_IMG_GALLERY_CONTRAST_SELECTED" file="file_manager/images/gallery/icon_contrast_selected.png" type="BINDATA" />
<include name="IDR_FILE_MANAGER_IMG_GALLERY_ROTATE" file="file_manager/images/gallery/icon_rotate.png" type="BINDATA" />
<include name="IDR_FILE_MANAGER_IMG_GALLERY_ROTATE_SELECTED" file="file_manager/images/gallery/icon_rotate_selected.png" type="BINDATA" />
+ <include name="IDR_FILE_MANAGER_IMG_GALLERY_ROTATE_LEFT" file="file_manager/images/gallery/icon_rotate_left.png" type="BINDATA" />
+ <include name="IDR_FILE_MANAGER_IMG_GALLERY_ROTATE_LEFT_SELECTED" file="file_manager/images/gallery/icon_rotate_left_selected.png" type="BINDATA" />
<include name="IDR_FILE_MANAGER_IMG_GALLERY_UNDO" file="file_manager/images/gallery/icon_undo.png" type="BINDATA" />
+ <include name="IDR_FILE_MANAGER_IMG_GALLERY_REDO" file="file_manager/images/gallery/icon_redo.png" type="BINDATA" />
<include name="IDR_FILE_MANAGER_IMG_GALLERY_OK" file="file_manager/images/gallery/icon_ok_check.png" type="BINDATA" />
<include name="IDR_FILE_MANAGER_IMG_GALLERY_CANCEL" file="file_manager/images/gallery/icon_cancel_x.png" type="BINDATA" />
<include name="IDR_FILE_MANAGER_IMG_GALLERY_CLOSE" file="file_manager/images/gallery/close_x.png" type="BINDATA" />
+ <include name="IDR_FILE_MANAGER_IMG_GALLERY_FADE_LEFT" file="file_manager/images/gallery/thumb_fade_left.png" type="BINDATA" />
+ <include name="IDR_FILE_MANAGER_IMG_GALLERY_FADE_RIGHT" file="file_manager/images/gallery/thumb_fade_right.png" type="BINDATA" />
<include name="IDR_FILE_MANAGER_ICON_DETAIL_VIEW" file="file_manager/images/icon-detail-view.png" type="BINDATA" />
<include name="IDR_FILE_MANAGER_ICON_THUMB_VIEW" file="file_manager/images/icon-thumb-view.png" type="BINDATA" />
diff --git a/chrome/browser/resources/file_manager/images/gallery/close_x.png b/chrome/browser/resources/file_manager/images/gallery/close_x.png
index e69de29..d9415ca 100644
--- a/chrome/browser/resources/file_manager/images/gallery/close_x.png
+++ b/chrome/browser/resources/file_manager/images/gallery/close_x.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/images/gallery/icon_brightness.png b/chrome/browser/resources/file_manager/images/gallery/icon_brightness.png
index 3f2a531..fee577b 100644
--- a/chrome/browser/resources/file_manager/images/gallery/icon_brightness.png
+++ b/chrome/browser/resources/file_manager/images/gallery/icon_brightness.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/images/gallery/icon_brightness_selected.png b/chrome/browser/resources/file_manager/images/gallery/icon_brightness_selected.png
index 05f14c7..96796fe 100644
--- a/chrome/browser/resources/file_manager/images/gallery/icon_brightness_selected.png
+++ b/chrome/browser/resources/file_manager/images/gallery/icon_brightness_selected.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/images/gallery/icon_cancel_x.png b/chrome/browser/resources/file_manager/images/gallery/icon_cancel_x.png
index e69de29..c44ad86 100644
--- a/chrome/browser/resources/file_manager/images/gallery/icon_cancel_x.png
+++ b/chrome/browser/resources/file_manager/images/gallery/icon_cancel_x.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/images/gallery/icon_contrast.png b/chrome/browser/resources/file_manager/images/gallery/icon_contrast.png
new file mode 100644
index 0000000..3f2a531
--- /dev/null
+++ b/chrome/browser/resources/file_manager/images/gallery/icon_contrast.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/images/gallery/icon_contrast_selected.png b/chrome/browser/resources/file_manager/images/gallery/icon_contrast_selected.png
new file mode 100644
index 0000000..05f14c7
--- /dev/null
+++ b/chrome/browser/resources/file_manager/images/gallery/icon_contrast_selected.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/images/gallery/icon_ok_check.png b/chrome/browser/resources/file_manager/images/gallery/icon_ok_check.png
index e69de29..30c4bf0 100644
--- a/chrome/browser/resources/file_manager/images/gallery/icon_ok_check.png
+++ b/chrome/browser/resources/file_manager/images/gallery/icon_ok_check.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/images/gallery/icon_redo.png b/chrome/browser/resources/file_manager/images/gallery/icon_redo.png
new file mode 100644
index 0000000..beabd2f
--- /dev/null
+++ b/chrome/browser/resources/file_manager/images/gallery/icon_redo.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/images/gallery/icon_rotate_left.png b/chrome/browser/resources/file_manager/images/gallery/icon_rotate_left.png
new file mode 100644
index 0000000..928adc7
--- /dev/null
+++ b/chrome/browser/resources/file_manager/images/gallery/icon_rotate_left.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/images/gallery/icon_rotate_left_selected.png b/chrome/browser/resources/file_manager/images/gallery/icon_rotate_left_selected.png
new file mode 100644
index 0000000..a96e352
--- /dev/null
+++ b/chrome/browser/resources/file_manager/images/gallery/icon_rotate_left_selected.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/images/gallery/thumb_fade_left.png b/chrome/browser/resources/file_manager/images/gallery/thumb_fade_left.png
new file mode 100644
index 0000000..a261f1d
--- /dev/null
+++ b/chrome/browser/resources/file_manager/images/gallery/thumb_fade_left.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/images/gallery/thumb_fade_right.png b/chrome/browser/resources/file_manager/images/gallery/thumb_fade_right.png
new file mode 100644
index 0000000..c792e19
--- /dev/null
+++ b/chrome/browser/resources/file_manager/images/gallery/thumb_fade_right.png
Binary files differ
diff --git a/chrome/browser/resources/file_manager/js/exif_parser.js b/chrome/browser/resources/file_manager/js/exif_parser.js
index 04c3c39c..be594e4 100644
--- a/chrome/browser/resources/file_manager/js/exif_parser.js
+++ b/chrome/browser/resources/file_manager/js/exif_parser.js
@@ -18,10 +18,13 @@ const EXIF_TAG_JPG_THUMB_OFFSET = 0x0201; // Pointer from TIFF to thumbnail.
const EXIF_TAG_JPG_THUMB_LENGTH = 0x0202; // Length of thumbnail data.
const EXIF_TAG_ORIENTATION = 0x0112;
+const EXIF_TAG_X_DIMENSION = 0xA002;
+const EXIF_TAG_Y_DIMENSION = 0xA003;
function ExifParser(parent) {
MetadataParser.apply(this, [parent]);
this.verbose = false;
+ this.mimeType = 'image/jpeg';
}
ExifParser.parserType = 'exif';
@@ -102,17 +105,14 @@ ExifParser.prototype.parse = function(file, callback, errorCallback) {
return onError('Invalid TIFF tag: ' + tag.toString(16));
var metadata = {
- metadataType: 'exif',
- mimeType: 'image/jpeg',
+ metadataType: ExifParser.parserType,
+ mimeType: self.mimeType,
littleEndian: (order == EXIF_ALIGN_LITTLE),
ifd: {
image: {},
- thumbnail: {},
- exif: {},
- gps: {}
+ thumbnail: {}
}
};
-
var directoryOffset = br.readScalar(4);
// Image directory.
@@ -135,7 +135,19 @@ ExifParser.prototype.parse = function(file, callback, errorCallback) {
self.vlog('Read EXIF directory.');
directoryOffset = metadata.ifd.image[EXIF_TAG_EXIFDATA].value;
br.seek(directoryOffset);
+ metadata.ifd.exif = {};
self.readDirectory(br, metadata.ifd.exif);
+
+ if (EXIF_TAG_X_DIMENSION in metadata.ifd.exif &&
+ EXIF_TAG_Y_DIMENSION in metadata.ifd.exif) {
+ if (metadata.imageTransform && metadata.imageTransform.rotate90) {
+ metadata.width = metadata.ifd.exif[EXIF_TAG_Y_DIMENSION].value;
+ metadata.height = metadata.ifd.exif[EXIF_TAG_X_DIMENSION].value;
+ } else {
+ metadata.width = metadata.ifd.exif[EXIF_TAG_X_DIMENSION].value;
+ metadata.height = metadata.ifd.exif[EXIF_TAG_Y_DIMENSION].value;
+ }
+ }
}
// GPS Directory may also be linked from the image directory.
@@ -143,6 +155,7 @@ ExifParser.prototype.parse = function(file, callback, errorCallback) {
self.vlog('Read GPS directory.');
directoryOffset = metadata.ifd.image[EXIF_TAG_GPSDATA].value;
br.seek(directoryOffset);
+ metadata.ifd.gps = {};
self.readDirectory(br, metadata.ifd.gps);
}
@@ -256,7 +269,9 @@ ExifParser.prototype.readTagValue = function(br, tag) {
case 2: // String
safeRead(1);
- if (tag.componentCount == 1) {
+ if (tag.componentCount == 0) {
+ tag.value = '';
+ } else if (tag.componentCount == 1) {
tag.value = String.fromCharCode(tag.value);
} else {
tag.value = String.fromCharCode.apply(null, tag.value);
diff --git a/chrome/browser/resources/file_manager/js/file_manager.js b/chrome/browser/resources/file_manager/js/file_manager.js
index 69c0d31..b8e23f4 100644
--- a/chrome/browser/resources/file_manager/js/file_manager.js
+++ b/chrome/browser/resources/file_manager/js/file_manager.js
@@ -2000,14 +2000,32 @@ FileManager.prototype = {
galleryFrame.className = 'overlay-pane';
galleryFrame.scrolling = 'no';
+ var selectedUrl;
+ if (urls.length == 1) {
+ // Single item selected. Pass to the Gallery as a selected.
+ selectedUrl = urls[0];
+ // Pass every image in the directory so that it shows up in the ribbon.
+ urls = [];
+ for (var i = 0; i != this.dataModel_.length; i++) {
+ var url = this.dataModel_.item(i).toURL();
+ if (url.match(iconTypes.image))
+ urls.push(url);
+ }
+ } else {
+ // Multiple selection. Pass just those items, select the first entry.
+ selectedUrl = urls[0];
+ }
+
galleryFrame.onload = function() {
galleryFrame.contentWindow.Gallery.open(
self.currentDirEntry_,
urls,
+ selectedUrl,
function () {
// TODO(kaznacheev): keep selection.
self.rescanDirectoryNow_(); // Make sure new files show up.
self.dialogDom_.removeChild(galleryFrame);
+ self.refocus();
},
self.metadataProvider_,
shareActions);
@@ -2015,6 +2033,7 @@ FileManager.prototype = {
galleryFrame.src = 'js/image_editor/gallery.html';
this.dialogDom_.appendChild(galleryFrame);
+ galleryFrame.focus();
};
/**
diff --git a/chrome/browser/resources/file_manager/js/image_editor/commands.js b/chrome/browser/resources/file_manager/js/image_editor/commands.js
new file mode 100644
index 0000000..3a4bfed
--- /dev/null
+++ b/chrome/browser/resources/file_manager/js/image_editor/commands.js
@@ -0,0 +1,340 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * Command queue is the only way to modify images.
+ * Supports undo/redo.
+ * Command execution is asynchronous (callback-based).
+ */
+function CommandQueue(document, canvas) {
+ this.document_ = document;
+ this.undo_ = [];
+ this.redo_ = [];
+ this.subscribers_ = [];
+
+ this.baselineImage_ = canvas;
+ this.currentImage_ = canvas;
+ this.previousImage_ = null;
+
+ this.busy_ = false;
+
+ this.UIContext_ = null;
+}
+
+/**
+ * Attach the UI elements to the command queue.
+ * Once the UI is attached the results of image manipulations are displayed.
+ *
+ * @param {ImageView} imageView The ImageView object to display the results
+ * @param {ImageEditor.Prompt} prompt
+ * @param {function(boolean)} lock Function to enable/disable buttons etc.
+ */
+CommandQueue.prototype.attachUI = function(imageView, prompt, lock) {
+ this.UIContext_ = {
+ imageView: imageView,
+ prompt: prompt,
+ lock: lock
+ };
+};
+
+/**
+ * Detach the UI. Further image modifications will not be displayed.
+ */
+CommandQueue.prototype.detachUI = function() {
+ // Instead of nulling out this.UIContext_ we null out its fields so that
+ // the commands currently being executed see this change.
+ this.UIContext_.imageView = null;
+ this.UIContext_.prompt = null;
+ this.UIContext_.lock = null;
+};
+
+/**
+ * Asynchronous getter. Does not return the image while the queue is busy.
+ */
+CommandQueue.prototype.requestCurrentImage = function(callback) {
+ if (this.isBusy()) {
+ this.subscribers_.push(callback);
+ } else {
+ var self = this;
+ setTimeout(function() { callback(self.currentImage_) }, 0);
+ }
+};
+
+CommandQueue.prototype.isBusy = function() { return this.busy_ };
+
+CommandQueue.prototype.setBusy_ = function(on) {
+ if (this.busy_ == on)
+ throw new Error('Inconsistent CommandQueue lock state');
+
+ this.busy_ = on;
+
+ if (!on) {
+ // Notify the subscribers that requested the image while the queue was busy.
+ while (this.subscribers_.length) {
+ this.subscribers_.pop()(this.currentImage_);
+ }
+ }
+
+ if (this.UIContext_)
+ this.UIContext_.lock(on);
+
+ if (on) {
+ ImageUtil.trace.resetTimer('command-busy');
+ } else {
+ ImageUtil.trace.reportTimer('command-busy');
+ }
+};
+
+CommandQueue.prototype.doExecute_ = function(command, uiContext, callback) {
+ if (!this.currentImage_)
+ throw new Error('Cannot operate on null image');
+
+ // Remember one previous image so that the first undo is as fast as possible.
+ this.previousImage_ = this.currentImage_;
+ var self = this;
+ command.execute(
+ this.document_,
+ this.currentImage_,
+ function(result) {
+ self.currentImage_ = result;
+ callback();
+ },
+ uiContext);
+};
+
+/**
+ * Executes the command.
+ *
+ * @param {Command} command
+ * @param {boolean} opt_keep_redo true if redo stack should not be cleared.
+ */
+CommandQueue.prototype.execute = function(command, opt_keep_redo) {
+ this.setBusy_(true);
+
+ if (!opt_keep_redo)
+ this.redo_ = [];
+
+ this.undo_.push(command);
+
+ this.doExecute_(command, this.UIContext_, this.setBusy_.bind(this, false));
+};
+
+CommandQueue.prototype.canUndo = function() {
+ return this.undo_.length != 0;
+};
+
+CommandQueue.prototype.undo = function() {
+ if (!this.canUndo())
+ throw new Error('Cannot undo');
+
+ this.setBusy_(true);
+
+ var command = this.undo_.pop();
+ this.redo_.push(command);
+
+ var self = this;
+
+ function complete() {
+ if (self.UIContext_.imageView) {
+ command.revertView(self.currentImage_, self.UIContext_.imageView);
+ }
+ self.setBusy_(false);
+ }
+
+ if (this.previousImage_) {
+ // First undo after an execute call.
+ this.currentImage_ = this.previousImage_;
+ this.previousImage_ = null;
+ complete();
+ // TODO(kaznacheev) Consider recalculating previousImage_ right here
+ // by replaying the commands in the background.
+ } else {
+ this.currentImage_ = this.baselineImage_;
+
+ function replay(index) {
+ if (index < self.undo_.length)
+ self.doExecute_(self.undo_[index], {}, replay.bind(null, index + 1));
+ else {
+ complete();
+ }
+ }
+
+ replay(0);
+ }
+};
+
+CommandQueue.prototype.canRedo = function() {
+ return this.redo_.length != 0;
+};
+
+CommandQueue.prototype.redo = function() {
+ if (!this.canRedo())
+ throw new Error('Cannot redo');
+
+ this.execute(this.redo_.pop(), true);
+};
+
+/**
+ * Command object encapsulates an operation on an image and a way to visualize
+ * its result.
+ */
+function Command(name) {
+ this.name_ = name;
+}
+
+Command.prototype.toString = function() {
+ return 'Command ' + this.name_;
+};
+
+/**
+ * Execute the command and visualize its results.
+ *
+ * The two actions are combined into one method because sometimes it is nice
+ * to be able to show partial results for slower operations.
+ *
+ * @param {Document} document
+ * @param {HTMLCanvasElement} srcCanvas
+ * @param {Object} uiContext
+ * @param {function(HTMLCanvasElement)}
+ */
+Command.prototype.execute = function(document, srcCanvas, callback, uiContext) {
+ setTimeout(callback.bind(null, null), 0);
+};
+
+/**
+ * Visualize reversion of the operation.
+ *
+ * @param {HTMLCanvasElement} canvas
+ * @param {ImageView} imageView
+ */
+Command.prototype.revertView = function(canvas, imageView) {
+ imageView.replace(canvas);
+};
+
+Command.prototype.createCanvas_ = function(
+ document, srcCanvas, opt_width, opt_height) {
+ var result = document.createElement('canvas');
+ result.width = opt_width || srcCanvas.width;
+ result.height = opt_height || srcCanvas.height;
+ return result;
+};
+
+
+/**
+ * Rotate command
+ * @param {number} rotate90 Rotation angle in 90 degree increments (signed)
+ */
+Command.Rotate = function(rotate90) {
+ Command.call(this, 'rotate(' + rotate90 * 90 + 'deg)');
+ this.rotate90_ = rotate90;
+};
+
+Command.Rotate.prototype = { __proto__: Command.prototype };
+
+Command.Rotate.prototype.execute = function(
+ document, srcCanvas, callback, uiContext) {
+ var result = this.createCanvas_(
+ document,
+ srcCanvas,
+ (this.rotate90_ & 1) ? srcCanvas.height : srcCanvas.width,
+ (this.rotate90_ & 1) ? srcCanvas.width : srcCanvas.height);
+ ImageUtil.drawImageTransformed(
+ result, srcCanvas, 1, 1, this.rotate90_ * Math.PI / 2);
+ if (uiContext && uiContext.imageView) {
+ uiContext.imageView.replaceAndAnimate(result, null, this.rotate90_);
+ }
+ setTimeout(callback.bind(null, result), 0);
+};
+
+Command.Rotate.prototype.revertView = function(canvas, imageView) {
+ imageView.replaceAndAnimate(canvas, null, -this.rotate90_);
+};
+
+
+/**
+ * Crop command.
+ *
+ * @param {Rect} imageRect Crop rectange in image coordinates.
+ * @param {Rect} screenRect Crop rectange in screen coordinates (for animation).
+ */
+Command.Crop = function(imageRect, screenRect) {
+ Command.call(this, 'crop' + imageRect.toString());
+ this.imageRect_ = imageRect;
+ this.screenRect_ = screenRect;
+};
+
+Command.Crop.prototype = { __proto__: Command.prototype };
+
+Command.Crop.prototype.execute = function(
+ document, srcCanvas, callback, uiContext) {
+ var result = this.createCanvas_(
+ document, srcCanvas, this.imageRect_.width, this.imageRect_.height);
+ Rect.drawImage(result.getContext("2d"), srcCanvas, null, this.imageRect_);
+ if (uiContext && uiContext.imageView) {
+ uiContext.imageView.
+ replaceAndAnimate(result, this.screenRect_, 0);
+ }
+ setTimeout(callback.bind(null, result), 0);
+};
+
+Command.Crop.prototype.revertView = function(canvas, imageView) {
+ imageView.animateAndReplace(canvas, this.screenRect_);
+};
+
+
+/**
+ * Filter command.
+ *
+ * @param {string} name Command name
+ * @param {function(ImageData,ImageData,number,number)} filter Filter function
+ * @param {string} message Message to display when done
+ */
+Command.Filter = function(name, filter, message) {
+ Command.call(this, name);
+ this.filter_ = filter;
+ this.message_ = message;
+};
+
+Command.Filter.prototype = { __proto__: Command.prototype };
+
+Command.Filter.prototype.execute = function(
+ document, srcCanvas, callback, uiContext) {
+ var result = this.createCanvas_(document, srcCanvas);
+
+ var self = this;
+
+ var previousRow = 0;
+
+ function onProgressVisible(updatedRow, rowCount) {
+ if (updatedRow == rowCount) {
+ uiContext.imageView.replace(result);
+ if (self.message_)
+ uiContext.prompt.show(self.message_, 2000);
+ callback(result);
+ } else {
+ var viewport = uiContext.imageView.viewport_;
+
+ var imageStrip = new Rect(viewport.getImageBounds());
+ imageStrip.top = previousRow;
+ imageStrip.height = updatedRow - previousRow;
+
+ var screenStrip = new Rect(viewport.getImageBoundsOnScreen());
+ screenStrip.top = Math.round(viewport.imageToScreenY(previousRow));
+ screenStrip.height =
+ Math.round(viewport.imageToScreenY(updatedRow)) - screenStrip.top;
+
+ uiContext.imageView.paintScreenRect(screenStrip, result, imageStrip);
+ previousRow = updatedRow;
+ }
+ }
+
+ function onProgressInvisible(updatedRow, rowCount) {
+ if (updatedRow == rowCount) {
+ callback(result);
+ }
+ }
+
+ filter.applyByStrips(result, srcCanvas, this.filter_,
+ uiContext.imageView ? onProgressVisible : onProgressInvisible);
+};
diff --git a/chrome/browser/resources/file_manager/js/image_editor/exif_encoder.js b/chrome/browser/resources/file_manager/js/image_editor/exif_encoder.js
index 7a1704d..9e60b4b 100644
--- a/chrome/browser/resources/file_manager/js/image_editor/exif_encoder.js
+++ b/chrome/browser/resources/file_manager/js/image_editor/exif_encoder.js
@@ -66,6 +66,9 @@ ExifEncoder.prototype.setImageData = function(canvas) {
ExifEncoder.findOrCreateTag(exif, EXIF_TAG_X_DIMENSION).value = canvas.width;
ExifEncoder.findOrCreateTag(exif, EXIF_TAG_Y_DIMENSION).value = canvas.height;
+ this.metadata_.width = canvas.width;
+ this.metadata_.height = canvas.height;
+
// Always save in default orientation.
delete this.metadata_.imageTransform;
ExifEncoder.findOrCreateTag(image, EXIF_TAG_ORIENTATION).value = 1;
@@ -486,7 +489,7 @@ ByteWriter.prototype.forward = function(key, width) {
*/
ByteWriter.prototype.resolve = function(key, value) {
if (!(key in this.forwards_))
- throw new Error('Undeclared forward key ' + key);
+ throw new Error('Undeclared forward key ' + key.toString(16));
var forward = this.forwards_[key];
var curPos = this.pos_;
this.pos_ = forward.pos;
@@ -507,6 +510,6 @@ ByteWriter.prototype.resolveOffset = function(key) {
*/
ByteWriter.prototype.checkResolved = function() {
for (var key in this.forwards_) {
- throw new Error('Unresolved forward pointer ' + key);
+ throw new Error ('Unresolved forward pointer ' + key.toString(16));
}
}; \ No newline at end of file
diff --git a/chrome/browser/resources/file_manager/js/image_editor/filter.js b/chrome/browser/resources/file_manager/js/image_editor/filter.js
index 6976367..324fecb 100644
--- a/chrome/browser/resources/file_manager/js/image_editor/filter.js
+++ b/chrome/browser/resources/file_manager/js/image_editor/filter.js
@@ -30,21 +30,23 @@ filter.create = function(name, options) {
*
* To be used with large images to avoid freezing up the UI.
*
- * @param {CanvasRenderingContext2D} context
+ * @param {HTMLCanvasElement} dstCanvas
+ * @param {HTMLCanvasElement} srcCanvas
* @param {function(ImageData,ImageData,number,number)} filterFunc
* @param {function(number,number} progressCallback
* @param {number} maxPixelsPerStrip
*/
filter.applyByStrips = function(
- context, filterFunc, progressCallback, maxPixelsPerStrip) {
- var source = context.getImageData(
- 0, 0, context.canvas.width, context.canvas.height);
+ dstCanvas, srcCanvas, filterFunc, progressCallback, maxPixelsPerStrip) {
+ var dstContext = dstCanvas.getContext('2d');
+ var srcContext = srcCanvas.getContext('2d');
+ var source = srcContext.getImageData(0, 0, srcCanvas.width, srcCanvas.height);
- var stripCount = Math.ceil (source.width * source.height /
+ var stripCount = Math.ceil (srcCanvas.width * srcCanvas.height /
(maxPixelsPerStrip || 1000000)); // 1 Mpix is a reasonable default.
- var strip = context.getImageData(0, 0,
- source.width, Math.ceil (source.height / stripCount));
+ var strip = srcContext.getImageData(0, 0,
+ srcCanvas.width, Math.ceil (srcCanvas.height / stripCount));
var offset = 0;
@@ -59,16 +61,20 @@ filter.applyByStrips = function(
}
filterFunc(strip, source, 0, offset);
- context.putImageData(strip, 0, offset);
+ dstContext.putImageData(strip, 0, offset);
offset += strip.height;
- progressCallback(offset, source.height);
if (offset < source.height) {
setTimeout(filterStrip, 0);
+ } else {
+ ImageUtil.trace.reportTimer('filter-commit');
}
+
+ progressCallback(offset, source.height);
}
+ ImageUtil.trace.resetTimer('filter-commit');
filterStrip();
};
diff --git a/chrome/browser/resources/file_manager/js/image_editor/gallery.css b/chrome/browser/resources/file_manager/js/image_editor/gallery.css
index b71d373..b2af0f0 100644
--- a/chrome/browser/resources/file_manager/js/image_editor/gallery.css
+++ b/chrome/browser/resources/file_manager/js/image_editor/gallery.css
@@ -6,6 +6,9 @@
body {
margin: 0;
+ -webkit-user-select: none;
+ font-family: Open Sans,Droid Sans Fallback,sans-serif;
+ font-size: 84%;
}
.gallery {
@@ -16,62 +19,102 @@ body {
overflow: hidden;
}
+/* Close button */
.gallery > .close {
position: absolute;
- padding: 1px 5px 1px 21px;
- right: 10px;
- top: 10px;
+ right: 5px;
+ top: 5px;
height: 20px;
+ width: 20px;
color: white;
cursor: pointer;
+ opacity: 0.4;
+ z-index: 200;
background-image: url(../../images/gallery/close_x.png);
background-repeat: no-repeat;
- background-position: 5px center;
+ background-position: center center;
}
.gallery > .close:hover {
+ opacity: 0.7;
background-color: rgba(81,81,81,1);
}
.gallery > .image-container {
position: absolute;
- top: 30px;
- bottom: 0px;
+ height: 100%;
width: 100%;
background-color: rgba(0,0,0,1);
}
+/* Image container and canvas elements */
+
+.gallery > .image-container > canvas {
+ position: absolute;
+ pointer-events: none;
+
+ -webkit-transition-property: -webkit-transform, opacity;
+ -webkit-transition-timing-function: ease-in-out;
+ /* -webkit-transition-duration is set in image_view.js*/
+}
+
+.gallery > .image-container > canvas[fade] {
+ opacity: 0;
+}
+
+.gallery > .image-container > canvas[fade='left'] {
+ -webkit-transform: translate(-40px,0);
+}
+
+.gallery > .image-container > canvas[fade='right'] {
+ -webkit-transform: translate(40px,0);
+}
+
+/* Toolbar */
+
.gallery > .toolbar {
position: absolute;
bottom: 0px;
width: 100%;
- height: 60px;
- padding: 3px;
+ height: 55px;
display: -webkit-box;
-webkit-box-orient: horizontal;
-webkit-box-pack: start;
-webkit-box-align: stretch;
- opacity: 1.0;
- -webkit-transition: opacity 0.5s ease-in-out;
background-color: rgba(18,18,18,0.75);
border-top: 1px solid rgba(31,31,31,0.75);
+
+ pointer-events: none;
+ opacity: 0;
+ -webkit-transform: translate(0, 0px);
+
+ -webkit-transition-property: webkit-transform, opacity;
+ -webkit-transition-duration: 220ms;
+ -webkit-transition-timing-function: ease;
}
-.gallery > .toolbar[hidden] {
- opacity: 0.0;
+.gallery[tools] > .toolbar {
+ pointer-events: auto;
+ opacity: 1;
+ -webkit-transform: translate(0, 0);
}
-.gallery .ribbon {
- -webkit-box-flex: 1;
- overflow: hidden;
- height: 100%;
- display: -webkit-box;
- -webkit-box-orient: horizontal;
- -webkit-box-pack: left;
+.gallery[tools][mousedrag] > .toolbar {
+ pointer-events: none;
+ opacity: 0.2;
+}
+
+.gallery[tools][locked] > .toolbar {
+ pointer-events: none;
+}
+
+.gallery[locked] {
+ cursor: wait;
}
-.gallery .ribbon-left,
-.gallery .ribbon-right {
+.gallery .arrow {
+ position: absolute;
+ z-index: 100;
width: 20px;
height: 100%;
cursor: pointer;
@@ -80,25 +123,60 @@ body {
-webkit-box-pack: center;
background-repeat: no-repeat;
background-position: center center;
+ pointer-events: none;
+ opacity: 0;
}
-.gallery .ribbon-left {
- background-image: url(../../images/gallery/arrow_left.png);
+.gallery[tools] .arrow {
+ opacity: 1;
+}
+
+.gallery[tools] .arrow[active] {
+ pointer-events: auto;
}
-.gallery .ribbon-left[disabled] {
+.gallery .arrow.left {
+ left: 0;
background-image: url(../../images/gallery/arrow_left_disabled.png);
}
-.gallery .ribbon-right {
- background-image: url(../../images/gallery/arrow_right.png);
- border-right: 1px solid rgba(87,87,87,0.5);
+.gallery .arrow.left[active] {
+ background-image: url(../../images/gallery/arrow_left.png);
}
-.gallery .ribbon-right[disabled] {
+.gallery .arrow.right {
+ right: 0;
background-image: url(../../images/gallery/arrow_right_disabled.png);
}
+.gallery .arrow.right[active] {
+ background-image: url(../../images/gallery/arrow_right.png);
+}
+
+/* Thumbnails */
+
+.gallery .ribbon-spacer {
+ position: relative;
+ display: -webkit-box;
+ -webkit-box-flex: 1;
+ -webkit-box-orient: horizontal;
+}
+
+.gallery .toolbar .ribbon {
+ overflow: hidden;
+ height: 100%;
+ display: -webkit-box;
+ -webkit-box-orient: horizontal;
+ -webkit-box-pack: left;
+
+ max-width: 100%;
+ -webkit-transition: max-width 0.5s ease-in-out;
+}
+
+.gallery[editing] .toolbar .ribbon {
+ max-width: 0;
+}
+
.gallery .ribbon-image {
display: -webkit-box;
-webkit-box-orient: horizontal;
@@ -110,58 +188,89 @@ body {
height: 47px;
margin: 2px;
border: 2px solid rgba(255,255,255,0); /* transparent white */
+
+ margin-left: 2px;
+ -webkit-transition: margin-left 0.3s ease-in-out;
+}
+
+.gallery .ribbon-image[shifted] {
+ margin-left: -53px;
}
.gallery .ribbon-image[selected] {
border: 2px solid rgba(255,233,168,1);
}
-.gallery .ribbon-image > img {
- max-width: 45px;
- max-height: 45px;
+.gallery .image-wrapper {
+ position: relative;
+ overflow: hidden;
+ width: 45px;
+ height: 45px;
+ border: 1px solid rgba(0,0,0,0); /* transparent black */
}
-.gallery .ribbon-spacer {
- -webkit-box-flex: 1;
- height: 100%;
+.gallery .toolbar .fade {
+ background-repeat: no-repeat;
+ background-position: center center;
+ position: relative;
+ z-index: 10;
+ width: 55px;
+ pointer-events: none;
+ opacity: 0;
}
-.gallery .edit-bar {
- -webkit-box-flex: 0;
- max-width: 60%;
- height: 100%;
- color: white;
- display: -webkit-box;
- -webkit-box-orient: horizontal;
- -webkit-transition: max-width 0.5s ease-in-out;
+.gallery .toolbar .fade[active] {
+ opacity: 1;
}
-.gallery .edit-bar > .edit-main{
+.gallery .toolbar .fade.left {
+ margin-right: -55px; /* Fully overlap the next thumbnail */
+ background-image: url(../../images/gallery/thumb_fade_left.png);
+}
+
+.gallery .toolbar .fade.right {
+ margin-left: -55px; /* Fully overlap the previous thumbnail */
+ background-image: url(../../images/gallery/thumb_fade_right.png);
+}
+
+/* Editor buttons */
+
+.gallery .toolbar .edit-bar {
+ position: absolute;
+ overflow: hidden;
+ pointer-events: none;
+ right: 0;
+ width: 75%;
+ height: 55px;
+ color: white;
display: -webkit-box;
-webkit-box-orient: horizontal;
- opacity: 1.0;
- -webkit-transition: opacity 0.25s ease-in-out 0.25s;
+ -webkit-box-pack: center;
+ -webkit-transition: width 0.5s ease-in-out;
}
-.gallery .edit-bar[hidden] {
- max-width: 0%;
- -webkit-transition: max-width 0.5s ease-in-out;
+.gallery[editing] .toolbar .edit-bar {
+ width: 100%;
}
-.gallery .edit-bar[hidden] > .edit-main{
+.gallery .toolbar .edit-bar > .edit-main{
+ display: -webkit-box;
+ -webkit-box-orient: horizontal;
opacity: 0;
-webkit-transition: opacity 0.25s ease-in-out;
}
-.gallery .edit-bar[hidden] > .edit-main[hidden] {
- display: none;
+.gallery[editing] .toolbar .edit-bar > .edit-main{
+ pointer-events: auto;
+ opacity: 1.0;
}
.gallery > .toolbar .button {
-webkit-box-flex: 0;
- padding: 10px 10px 10px 33px;
+ padding: 0px 10px 0px 35px;
cursor: pointer;
- margin: 10px 5px;
+ margin: 8px 0px 7px 3px;
+ height: 40px;
display: -webkit-box;
-webkit-box-orient: horizontal;
@@ -216,19 +325,44 @@ body {
background-image: url(../../images/gallery/icon_brightness_selected.png);
}
-.gallery > .toolbar .button.rotate {
+.gallery > .toolbar .button.rotate-right {
background-image: url(../../images/gallery/icon_rotate.png);
}
-.gallery > .toolbar .button.rotate[pressed] {
+.gallery > .toolbar .button.rotate-right[pressed] {
background-image: url(../../images/gallery/icon_rotate_selected.png);
}
+.gallery > .toolbar .button.rotate-left {
+ background-image: url(../../images/gallery/icon_rotate_left.png);
+}
+
+.gallery > .toolbar .button.rotate-left[pressed] {
+ background-image: url(../../images/gallery/icon_rotate_left_selected.png);
+}
+
.gallery > .toolbar .button.undo {
background-image: url(../../images/gallery/icon_undo.png);
}
+.gallery > .toolbar .button.redo {
+ position: absolute; /* Exclude from center-packing*/
+ background-image: url(../../images/gallery/icon_redo.png);
+}
+
+.gallery > .toolbar .button[disabled] {
+ pointer-events: none;
+ opacity: 0.5;
+}
+
+.gallery > .toolbar .button[hidden] {
+ display: none;
+}
+
.gallery > .toolbar > .button.edit {
+ position: relative;
+ z-index: 10;
+ margin-left: 25px;
background-image: url(../../images/gallery/icon_edit.png);
}
@@ -237,6 +371,9 @@ body {
}
.gallery > .toolbar > .button.share {
+ position: relative;
+ z-index: 10;
+ margin-right: 8px;
background-image: url(../../images/gallery/icon_share.png);
}
@@ -244,53 +381,180 @@ body {
background-image: url(../../images/gallery/icon_share_selected.png);
}
+/* Secondary toolbar (mode-specific tools) */
+
.gallery .edit-modal {
position: absolute;
- left: 0;
- bottom: 53px;
- height: 30px;
- color: black;
- background-color: white;
+ width: 100%;
+ bottom: 80px;
+ height: 40px;
display: -webkit-box;
-webkit-box-orient: horizontal;
+ -webkit-box-pack: center;
+ pointer-events: none;
}
-.gallery .edit-modal[hidden] {
+.gallery .edit-modal-wrapper[hidden] {
display: none;
}
-.gallery .edit-modal > .button.mode {
- color: black;
- background-color: rgba(240,240,240,1);
- padding: 0 5px 0 30px;
- margin: 2px 2px;
+.gallery .edit-modal-wrapper {
+ color: white;
+ padding-right: 5px;
+ background-color: rgba(0, 0, 0, 0.75);
+ display: -webkit-box;
+ -webkit-box-orient: horizontal;
+ -webkit-box-pack: center;
+ pointer-events: auto;
+}
+
+.gallery .edit-modal .label {
+ padding: 0 10px;
+
+ display: -webkit-box;
+ -webkit-box-orient: horizontal;
+ -webkit-box-align: center;
background-repeat: no-repeat;
- background-position: 5px center;
+ background-position: 20px center;
}
-.gallery .edit-modal > .button.mode:hover {
- background-color: rgba(200,200,200,1);
+.gallery .edit-modal .label.brightness {
+ padding-left: 50px;
+ background-image: url(../../images/gallery/icon_brightness.png);
}
-.gallery .edit-modal .button.ok {
- background-image: url(../../images/gallery/icon_ok_check.png);
+.gallery .edit-modal .label.contrast {
+ margin-left: 15px;
+ padding-left: 50px;
+ background-image: url(../../images/gallery/icon_contrast.png);
}
-.gallery .edit-modal .button.cancel {
- background-image: url(../../images/gallery/icon_cancel_x.png);
+.gallery .edit-modal .range {
+ margin: 9px 10px 9px 0;
}
-.gallery .edit-modal > .label {
- padding: 0 10px;
+/* Crop frame */
+
+.gallery .crop-overlay {
+ position: absolute;
+ pointer-events: none;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+}
+
+.gallery .crop-overlay .shadow {
+ background-color: rgba(0,0,0,0.4);
+}
+
+.gallery .crop-overlay .middle-box {
+ display: -webkit-box;
+ -webkit-box-orient: horizontal;
+ -webkit-box-flex: 1;
+}
+
+.gallery .crop-frame {
+ position: relative;
+ display: -webkit-box;
+ -webkit-box-flex: 1;
+}
+
+.gallery .crop-frame div{
+ position: absolute;
+ background-color: rgba(255, 255, 255, 0.8);
+ -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.75);
+}
+
+.gallery .crop-frame .horizontal {
+ left: 7px;
+ right: 7px;
+ height: 1px;
+}
+
+.gallery .crop-frame .horizontal.top {
+ top: 0;
+}
+
+.gallery .crop-frame .horizontal.bottom {
+ bottom: 0;
+}
+
+.gallery .crop-frame .vertical {
+ top: 7px;
+ bottom: 7px;
+ width: 1px;
+}
+
+.gallery .crop-frame .vertical.left {
+ left: 0;
+}
+
+.gallery .crop-frame .vertical.right {
+ right: 0;
+}
+
+.gallery .crop-frame .corner {
+ border-radius: 6px;
+ width: 13px;
+ height: 13px;
+}
+
+.gallery .crop-frame .corner.left {
+ left: -6px;
+}
+
+.gallery .crop-frame .corner.right {
+ right: -6px;
+}
+
+.gallery .crop-frame .corner.top {
+ top: -6px;
+}
+
+.gallery .crop-frame .corner.bottom {
+ bottom: -6px;
+}
+
+/* Prompt/notification panel */
+
+.gallery .prompt-wrapper {
+ position: absolute;
+ pointer-events: none;
+
+ width: 100%;
+ height: 100%;
display: -webkit-box;
-webkit-box-orient: horizontal;
-webkit-box-align: center;
+ -webkit-box-pack: center;
}
-.gallery .edit-modal > .range {
- margin: 4px 10px 4px 0;
+.gallery .prompt {
+ font-size: 120%;
+ height: 40px;
+ padding: 0 20px;
+ color: white;
+ background-color: rgba(0, 0, 0, 0.8);
+
+ display: -webkit-box;
+ -webkit-box-orient: horizontal;
+ -webkit-box-align: center;
+
+ position: relative;
+ top: 5px;
+ opacity: 0;
+ -webkit-transition: all 180ms ease;
+}
+
+.gallery .prompt[state='fadein'] {
+ top: 0;
+ opacity: 1;
+}
+
+.gallery .prompt[state='fadeout'] {
+ top: 0;
+ opacity: 0;
}
.gallery .share-menu {
diff --git a/chrome/browser/resources/file_manager/js/image_editor/gallery.html b/chrome/browser/resources/file_manager/js/image_editor/gallery.html
index 28e5406..1f57e31 100644
--- a/chrome/browser/resources/file_manager/js/image_editor/gallery.html
+++ b/chrome/browser/resources/file_manager/js/image_editor/gallery.html
@@ -17,6 +17,8 @@
<script type="text/javascript" src="image_util.js"></script>
<script type="text/javascript" src="viewport.js"></script>
<script type="text/javascript" src="image_buffer.js"></script>
+ <script type="text/javascript" src="image_view.js"></script>
+ <script type="text/javascript" src="commands.js"></script>
<script type="text/javascript" src="image_editor.js"></script>
<script type="text/javascript" src="image_transform.js"></script>
<script type="text/javascript" src="image_adjust.js"></script>
diff --git a/chrome/browser/resources/file_manager/js/image_editor/gallery.js b/chrome/browser/resources/file_manager/js/image_editor/gallery.js
index 30af34a..12843df 100644
--- a/chrome/browser/resources/file_manager/js/image_editor/gallery.js
+++ b/chrome/browser/resources/file_manager/js/image_editor/gallery.js
@@ -23,47 +23,65 @@ function Gallery(container, closeCallback, metadataProvider, shareActions) {
this.initDom_(shareActions);
}
-Gallery.open = function(
- parentDirEntry, urls, closeCallback, metadataProvider, shareActions) {
+Gallery.open = function(parentDirEntry, items, selectedItem,
+ closeCallback, metadataProvider, shareActions) {
var container = document.querySelector('.gallery');
container.innerHTML = '';
- var gallery = new Gallery(container, closeCallback, metadataProvider,
- shareActions);
- gallery.load(parentDirEntry, urls);
+ var gallery = new Gallery(
+ container, closeCallback, metadataProvider, shareActions);
+ gallery.load(parentDirEntry, items, selectedItem);
};
// TODO(kaznacheev): localization.
Gallery.displayStrings = {
- close: 'Close',
edit: 'Edit',
share: 'Share',
autofix: 'Auto-fix',
crop: 'Crop',
- exposure: 'Brightness / contrast',
+ exposure: 'Brightness',
brightness: 'Brightness',
contrast: 'Contrast',
- rotate: 'Rotate',
- undo: 'Undo'
+ 'rotate-left': 'Left',
+ 'rotate-right': 'Right',
+ 'enter-when-done': 'Press Enter when done',
+ fixed: 'Fixed',
+ undo: 'Undo',
+ redo: 'Redo'
};
Gallery.editorModes = [
- ImageEditor.Mode.InstantAutofix,
- ImageEditor.Mode.Crop,
- ImageEditor.Mode.Exposure,
- ImageEditor.Mode.InstantRotate
+ new ImageEditor.Mode.InstantAutofix(),
+ new ImageEditor.Mode.Crop(),
+ new ImageEditor.Mode.Exposure(),
+ new ImageEditor.Mode.OneClick('rotate-left', new Command.Rotate(-1)),
+ new ImageEditor.Mode.OneClick('rotate-right', new Command.Rotate(1))
];
-Gallery.FADE_TIMEOUT = 5000;
+Gallery.FADE_TIMEOUT = 3000;
+Gallery.FIRST_FADE_TIMEOUT = 1000;
Gallery.prototype.initDom_ = function(shareActions) {
var doc = this.document_;
- this.container_.addEventListener('keydown', this.onKeyDown_.bind(this));
- this.container_.addEventListener('mousemove', this.onMouseMove_.bind(this));
+
+ // Clean up after the previous instance of Gallery.
+ this.container_.removeAttribute('editing');
+ this.container_.removeAttribute('tools');
+ if (window.galleryKeyDown) {
+ doc.body.removeEventListener('keydown', window.galleryKeyDown);
+ }
+ if (window.galleryMouseMove) {
+ doc.body.removeEventListener('keydown', window.galleryMouseMove);
+ }
+
+ window.galleryKeyDown = this.onKeyDown_.bind(this);
+ doc.body.addEventListener('keydown', window.galleryKeyDown);
+
+ window.galleryMouseMove = this.onMouseMove_.bind(this);
+ doc.body.addEventListener('mousemove', window.galleryMouseMove);
this.closeButton_ = doc.createElement('div');
this.closeButton_.className = 'close';
- this.closeButton_.textContent = Gallery.displayStrings['close'];
this.closeButton_.addEventListener('click', this.onClose_.bind(this));
this.container_.appendChild(this.closeButton_);
@@ -75,11 +93,28 @@ Gallery.prototype.initDom_ = function(shareActions) {
this.toolbar_.className = 'toolbar';
this.container_.appendChild(this.toolbar_);
- this.ribbon_ = new Ribbon(this.toolbar_, this.onSelect_.bind(this));
+ this.ribbonSpacer_ = doc.createElement('div');
+ this.ribbonSpacer_.className = 'ribbon-spacer';
+ this.toolbar_.appendChild(this.ribbonSpacer_);
+
+ this.toolbar_.addEventListener('mouseover',
+ this.enableFading_.bind(this, false));
+ this.toolbar_.addEventListener('mouseout',
+ this.enableFading_.bind(this, true));
+
+ this.arrowLeft_ = this.document_.createElement('div');
+ this.arrowLeft_.className = 'arrow left';
+ this.container_.appendChild(this.arrowLeft_);
+
+ this.arrowRight_ = this.document_.createElement('div');
+ this.arrowRight_.className = 'arrow right';
+ this.container_.appendChild(this.arrowRight_);
+
+ this.ribbon_ = new Ribbon(this.ribbonSpacer_, this.onSelect_.bind(this),
+ this.arrowLeft_, this.arrowRight_);
this.editBar_ = doc.createElement('div');
this.editBar_.className = 'edit-bar';
- this.editBar_.setAttribute('hidden', 'hidden');
this.toolbar_.appendChild(this.editBar_);
this.editButton_ = doc.createElement('div');
@@ -98,13 +133,16 @@ Gallery.prototype.initDom_ = function(shareActions) {
this.editBarMain_ = doc.createElement('div');
this.editBarMain_.className = 'edit-main';
- this.editBarMain_.setAttribute('hidden', 'hidden');
this.editBar_.appendChild(this.editBarMain_);
this.editBarMode_ = doc.createElement('div');
this.editBarMode_.className = 'edit-modal';
- this.editBarMode_.setAttribute('hidden', 'hidden');
- this.editBar_.appendChild(this.editBarMode_);
+ this.container_.appendChild(this.editBarMode_);
+
+ this.editBarModeWrapper_ = doc.createElement('div');
+ ImageUtil.setAttribute(this.editBarModeWrapper_, 'hidden', true);
+ this.editBarModeWrapper_.className = 'edit-modal-wrapper';
+ this.editBarMode_.appendChild(this.editBarModeWrapper_);
this.shareMenu_ = doc.createElement('div');
this.shareMenu_.className = 'share-menu';
@@ -126,88 +164,83 @@ Gallery.prototype.initDom_ = function(shareActions) {
this.editor_ = new ImageEditor(
this.imageContainer_,
this.editBarMain_,
- this.editBarMode_,
+ this.editBarModeWrapper_,
Gallery.editorModes,
Gallery.displayStrings);
+
+ this.imageView_ = this.editor_.getImageView();
+
+ this.editor_.trackWindow(doc.defaultView);
};
-Gallery.prototype.load = function(parentDirEntry, urls) {
- this.editBar_.setAttribute('hidden', 'hidden');
- this.editBarMain_.setAttribute('hidden', 'hidden');
+Gallery.prototype.load = function(parentDirEntry, items, selectedItem) {
+ this.parentDirEntry_ = parentDirEntry;
+
this.editButton_.removeAttribute('pressed');
this.shareButton_.removeAttribute('pressed');
- this.toolbar_.removeAttribute('hidden');
+
this.shareMenu_.setAttribute('hidden', 'hidden');
this.editing_ = false;
this.sharing_ = false;
- if (urls.length == 0)
- return;
+ this.cancelFading_();
+ this.initiateFading_();
- // TODO(kaznacheev): instead of always selecting the 0-th url
- // select the url passed from the FileManager.
- this.ribbon_.load(urls, urls[0], this.metadataProvider_);
- this.parentDirEntry_ = parentDirEntry;
+ var urls = [];
+ var selectedURL;
- this.initiateFading_();
-};
+ // Convert canvas and blob items to blob urls.
+ for (var i = 0; i != items.length; i++) {
+ var item = items[i];
+ var selected = (item == selectedItem);
-Gallery.prototype.saveChanges_ = function(opt_callback) {
- if (!this.editor_.isModified()) {
- if (opt_callback) opt_callback();
- return;
+ if (item.constructor.name == 'HTMLCanvasElement') {
+ item = ImageEncoder.getBlob(item,
+ ImageEncoder.encodeMetadata({mimeType: 'image/jpeg'}, item), 1);
+ }
+ if (item.constructor.name == 'Blob' ||
+ item.constructor.name == 'File') {
+ item = window.webkitURL.createObjectURL(item);
+ }
+ if (typeof item == 'string') {
+ urls.push(item);
+ if (selected) selectedURL = item;
+ } else {
+ console.error('Unsupported image type');
+ }
}
- var currentItem = this.currentItem_;
-
- var metadataEncoder = this.editor_.encodeMetadata();
- var canvas = this.editor_.getBuffer().getContent().detachCanvas();
+ if (urls.length == 0)
+ throw new Error('Cannot open the gallery for 0 items');
- currentItem.overrideContent(canvas, metadataEncoder.getMetadata());
+ selectedURL = selectedURL || urls[0];
- if (currentItem.isFromLocalFile()) {
- var newFile = currentItem.isOriginal();
- var name = currentItem.getCopyName();
+ var self = this;
- var self = this;
+ function initRibbon() {
+ self.currentItem_ =
+ self.ribbon_.load(urls, selectedURL, self.metadataProvider_);
+ // Flash the ribbon briefly to let the user know it is there.
+ self.initiateFading_(Gallery.FIRST_FADE_TIMEOUT);
+ }
- function onSuccess(url) {
- console.log('Saved from gallery', name);
- // Force the metadata provider to reread the metadata from the file.
- self.metadataProvider_.reset(url);
- currentItem.onSaveSuccess(url);
- if (opt_callback) opt_callback();
- }
+ // Initialize the ribbon only after the selected image is fully loaded.
+ this.metadataProvider_.fetch(selectedURL, function (metadata) {
+ self.editor_.openSession(selectedURL, metadata, 0, initRibbon);
+ });
+};
- function onError(error) {
- console.log('Error saving from gallery', name, error);
- currentItem.onSaveError(error);
+Gallery.prototype.saveChanges_ = function(opt_callback) {
+ var item = this.currentItem_;
+ var self = this;
+ this.editor_.closeSession(function(canvas, modified) {
+ if (modified) {
+ item.save(
+ self.parentDirEntry_, self.metadataProvider_, canvas, opt_callback);
+ } else {
if (opt_callback) opt_callback();
}
-
- this.parentDirEntry_.getFile(
- name, {create: newFile, exclusive: newFile}, function(fileEntry) {
- fileEntry.createWriter(function(fileWriter) {
- function writeContent() {
- fileWriter.onwriteend = onSuccess.bind(null, fileEntry.toURL());
- fileWriter.write(ImageEncoder.getBlob(canvas, metadataEncoder));
- }
- fileWriter.onerror = onError;
- if (newFile) {
- writeContent();
- } else {
- fileWriter.onwriteend = writeContent;
- fileWriter.truncate(0);
- }
- },
- onError);
- }, onError);
- } else {
- // This branch is needed only for gallery_demo.js
- currentItem.onSaveSuccess(
- canvas.toDataURL(metadataEncoder.getMetadata().mimeType));
- if (opt_callback) opt_callback();
- }
+ });
};
Gallery.prototype.onActionExecute_ = function(action) {
@@ -226,47 +259,32 @@ Gallery.prototype.onClose_ = function() {
};
Gallery.prototype.onSelect_ = function(item) {
- if (this.currentItem_) {
- this.saveChanges_();
- }
+ if (this.currentItem_ == item)
+ return;
+
+ this.saveChanges_();
+ var slide = item.getIndex() - this.currentItem_.getIndex();
this.currentItem_ = item;
- this.editor_.load(this.currentItem_.getContent(),
- ImageUtil.deepCopy(this.currentItem_.getMetadata()));
+ this.editor_.openSession(
+ this.currentItem_.getContent(), this.currentItem_.getMetadata(), slide);
};
-Gallery.prototype.onEdit_ = function(event) {
- this.toolbar_.removeAttribute('hidden');
-
- var self = this;
+Gallery.prototype.onEdit_ = function() {
if (this.editing_) {
- this.editor_.onModeLeave();
- this.editBar_.setAttribute('hidden', 'hidden');
- this.editButton_.removeAttribute('pressed');
- this.editing_ = false;
+ this.editor_.leaveModeGently();
this.initiateFading_();
- window.setTimeout(function() {
- // Hide the toolbar, so it will not overlap with buttons.
- self.editBarMain_.setAttribute('hidden', 'hidden');
- self.ribbon_.redraw();
- }, 500);
} else {
this.cancelFading_();
- // Show the toolbar.
- this.editBarMain_.removeAttribute('hidden');
- // Use setTimeout, so computed style will be recomputed.
- window.setTimeout(function() {
- self.editBar_.removeAttribute('hidden');
- self.editButton_.setAttribute('pressed', 'pressed');
- self.editing_ = true;
- self.ribbon_.redraw();
- }, 0);
}
+ this.editing_ = !this.editing_;
+ ImageUtil.setAttribute(this.container_, 'editing', this.editing_);
+ ImageUtil.setAttribute(this.editButton_, 'pressed', this.editing_);
};
Gallery.prototype.onShare_ = function(event) {
- this.toolbar_.removeAttribute('hidden');
+ ImageUtil.setAttribute(this.container_, 'tools', true);
if (this.sharing_) {
this.shareMenu_.setAttribute('hidden', 'hidden');
@@ -277,198 +295,364 @@ Gallery.prototype.onShare_ = function(event) {
};
Gallery.prototype.onKeyDown_ = function(event) {
- if (this.editing_ || this.sharing_)
+ if (this.sharing_)
+ return;
+
+ if (this.editing_ && this.editor_.onKeyDown(event))
return;
+
switch (event.keyIdentifier) {
+ case 'U+001B': // Escape
+ if (this.editing_) {
+ this.onEdit_();
+ } else {
+ this.onClose_();
+ }
+ break;
+
+ case 'U+0045': // 'e'
+ if (!this.editing_)
+ this.onEdit_();
+ break;
+
case 'Home':
- this.ribbon_.scrollToFirst();
+ this.ribbon_.selectFirst();
break;
case 'Left':
- this.ribbon_.scrollLeft();
+ this.ribbon_.selectPrevious();
break;
case 'Right':
- this.ribbon_.scrollRight();
+ this.ribbon_.selectNext();
break;
case 'End':
- this.ribbon_.scrollToLast();
+ this.ribbon_.selectLast();
break;
}
};
Gallery.prototype.onMouseMove_ = function(e) {
this.cancelFading_();
- this.toolbar_.removeAttribute('hidden');
this.initiateFading_();
};
Gallery.prototype.onFadeTimeout_ = function() {
this.fadeTimeoutId_ = null;
if (this.editing_ || this.sharing_) return;
- this.toolbar_.setAttribute('hidden', 'hidden');
+ this.container_.removeAttribute('tools');
};
-Gallery.prototype.initiateFading_ = function() {
+Gallery.prototype.enableFading_ = function(on) {
+ this.fadingEnabled_ = on;
+ if (this.fadingEnabled_)
+ this.initiateFading_();
+ else
+ this.cancelFading_();
+};
+
+Gallery.prototype.initiateFading_ = function(opt_timeout) {
+ if (!this.fadingEnabled_)
+ return;
+
if (this.editing_ || this.sharing_ || this.fadeTimeoutId_) {
return;
}
this.fadeTimeoutId_ = window.setTimeout(
- this.onFadeTimeoutBound_, Gallery.FADE_TIMEOUT);
+ this.onFadeTimeoutBound_, opt_timeout || Gallery.FADE_TIMEOUT);
};
Gallery.prototype.cancelFading_ = function() {
+ ImageUtil.setAttribute(this.container_, 'tools', true);
+
if (this.fadeTimeoutId_) {
window.clearTimeout(this.fadeTimeoutId_);
this.fadeTimeoutId_ = null;
}
};
-function Ribbon(parentNode, onSelect) {
- this.container_ = parentNode;
- this.document_ = parentNode.ownerDocument;
+function Ribbon(container, onSelect, arrowLeft, arrowRight) {
+ this.container_ = container;
+ this.document_ = container.ownerDocument;
+
+ this.arrowLeft_ = arrowLeft;
+ this.arrowLeft_.addEventListener('click', this.selectPrevious.bind(this));
- this.left_ = this.document_.createElement('div');
- this.left_.className = 'ribbon-left';
- this.left_.addEventListener('click', this.scrollLeft.bind(this));
- this.container_.appendChild(this.left_);
+ this.arrowRight_ = arrowRight;
+ this.arrowRight_.addEventListener('click', this.selectNext.bind(this));
+
+ this.fadeLeft_ = this.document_.createElement('div');
+ this.fadeLeft_.className = 'fade left';
+ this.container_.appendChild(this.fadeLeft_);
this.bar_ = this.document_.createElement('div');
this.bar_.className = 'ribbon';
this.container_.appendChild(this.bar_);
- this.right_ = this.document_.createElement('div');
- this.right_.className = 'ribbon-right';
- this.right_.addEventListener('click', this.scrollRight.bind(this));
- this.container_.appendChild(this.right_);
-
- this.spacer_ = this.document_.createElement('div');
- this.spacer_.className = 'ribbon-spacer';
+ this.fadeRight_ = this.document_.createElement('div');
+ this.fadeRight_.className = 'fade right';
+ this.container_.appendChild(this.fadeRight_);
this.onSelect_ = onSelect;
this.items_ = [];
- this.selectedItem_ = null;
- this.firstIndex_ = 0;
+ this.selectedIndex_ = -1;
+ this.firstVisibleIndex_ = 0;
+ this.lastVisibleIndex_ = 0;
}
Ribbon.prototype.clear = function() {
this.bar_.textContent = '';
this.items_ = [];
- this.selectedItem_ = null;
- this.firstIndex_ = 0;
+ this.selectedIndex_ = -1;
+ this.firstVisibleIndex_ = 0;
+ this.lastVisibleIndex_ = 0;
};
-Ribbon.prototype.add = function(url, selected, metadataProvider) {
+Ribbon.prototype.add = function(url, metadataProvider) {
var index = this.items_.length;
- var selectClosure = this.select.bind(this);
- var item =
- new Ribbon.Item(this.document_, url, selectClosure);
+ var selectClosure = this.select.bind(this, index);
+ var item = new Ribbon.Item(index, url, this.document_, selectClosure);
this.items_.push(item);
+ var self = this;
metadataProvider.fetch(url, function(metadata) {
item.setMetadata(metadata);
- if (selected) selectClosure(item);
+ if (item.isSelected()) {
+ self.onSelect_(item);
+ }
});
+ return item;
};
-Ribbon.prototype.load = function(urls, selectedUrl, metadataProvider) {
+Ribbon.prototype.load = function(urls, selectedURL, metadataProvider) {
this.clear();
+ var selectedIndex = -1;
for (var index = 0; index < urls.length; ++index) {
- this.add(urls[index], urls[index] == selectedUrl, metadataProvider);
+ var item = this.add(urls[index], metadataProvider);
+ if (urls[index] == selectedURL)
+ selectedIndex = index;
}
-
+ this.select(selectedIndex);
window.setTimeout(this.redraw.bind(this), 0);
+ return this.items_[this.selectedIndex_];
};
-Ribbon.prototype.select = function(item) {
- if (this.selectedItem_) {
- this.selectedItem_.select(false);
+Ribbon.prototype.select = function(index) {
+ if (index == this.selectedIndex_)
+ return; // Do not reselect.
+
+ if (this.selectedIndex_ != -1) {
+ this.items_[this.selectedIndex_].select(false);
}
- this.selectedItem_ = item;
+ this.selectedIndex_ = index;
+
+ if (this.selectedIndex_ < this.firstVisibleIndex_) {
+ if (this.selectedIndex_ == this.firstVisibleIndex_ - 1) {
+ this.scrollLeft();
+ } else {
+ this.redraw();
+ }
+ }
+ if (this.selectedIndex_ > this.lastVisibleIndex_) {
+ if (this.selectedIndex_ == this.lastVisibleIndex_ + 1) {
+ this.scrollRight();
+ } else {
+ this.redraw();
+ }
+ }
- if (this.selectedItem_) {
- this.selectedItem_.select(true);
- if (this.onSelect_)
- this.onSelect_(this.selectedItem_);
+ if (this.selectedIndex_ != -1) {
+ var selectedItem = this.items_[this.selectedIndex_];
+ selectedItem.select(true);
+ if (selectedItem.getMetadata()) {
+ this.onSelect_(selectedItem);
+ } // otherwise onSelect is called from the metadata callback.
}
+
+ ImageUtil.setAttribute(this.arrowLeft_, 'active', this.selectedIndex_ > 0);
+ ImageUtil.setAttribute(this.arrowRight_, 'active',
+ this.selectedIndex_ + 1 < this.items_.length);
+
+ ImageUtil.setAttribute(this.fadeLeft_, 'active', this.firstVisibleIndex_ > 0);
+ ImageUtil.setAttribute(this.fadeRight_, 'active',
+ this.lastVisibleIndex_ + 1 < this.items_.length);
};
Ribbon.prototype.redraw = function() {
this.bar_.textContent = '';
- // TODO(dgozman): get rid of these constants.
- var itemWidth = 49;
- var width = this.bar_.clientWidth - 40;
+ // The thumbnails are square.
+ var itemWidth = this.bar_.parentNode.clientHeight;
+ var width = this.bar_.parentNode.clientWidth;
+
+ var fullItems = Math.floor(width / itemWidth);
+
+ this.bar_.style.width = fullItems * itemWidth + 'px';
+
+ this.firstVisibleIndex_ =
+ Math.min(this.firstVisibleIndex_, this.selectedIndex_);
+
+ this.lastVisibleIndex_ =
+ Math.min(this.items_.length, this.firstVisibleIndex_ + fullItems) - 1;
+
+ this.lastVisibleIndex_ =
+ Math.max(this.lastVisibleIndex_, this.selectedIndex_);
+
+ this.firstVisibleIndex_ =
+ Math.max(0, this.lastVisibleIndex_ - fullItems + 1);
- var fit = Math.round(Math.floor(width / itemWidth));
- var lastIndex = Math.min(this.items_.length, this.firstIndex_ + fit);
- this.firstIndex_ = Math.max(0, lastIndex - fit);
- for (var index = this.firstIndex_; index < lastIndex; ++index) {
+ fullItems = this.lastVisibleIndex_ - this.firstVisibleIndex_ + 1;
+
+ for (var index = this.firstVisibleIndex_;
+ index <= this.lastVisibleIndex_;
+ ++index) {
this.bar_.appendChild(this.items_[index].getBox());
}
- this.bar_.appendChild(this.spacer_);
};
+// TODO(kaznacheev) The animation logic below does not really work for fast
+// repetitive scrolling.
+
Ribbon.prototype.scrollLeft = function() {
- if (this.firstIndex_ > 0) {
- this.firstIndex_--;
- this.redraw();
- }
+ console.log('scrollLeft');
+ this.firstVisibleIndex_--;
+ var fadeIn = this.items_[this.firstVisibleIndex_].getBox();
+ ImageUtil.setAttribute(fadeIn, 'shifted', true);
+ this.bar_.insertBefore(fadeIn, this.bar_.firstElementChild);
+ setTimeout(
+ function() { ImageUtil.setAttribute(fadeIn, 'shifted', false) },
+ 0);
+
+ var pushOut = this.bar_.lastElementChild;
+ pushOut.parentNode.removeChild(pushOut);
+
+ this.lastVisibleIndex_--;
};
Ribbon.prototype.scrollRight = function() {
- if (this.firstIndex_ < this.items_.length - 1) {
- this.firstIndex_++;
- this.redraw();
- }
+ console.log('scrollRight');
+
+ var fadeOut = this.items_[this.firstVisibleIndex_].getBox();
+ ImageUtil.setAttribute(fadeOut, 'shifted', true);
+ setTimeout(function() {
+ fadeOut.parentNode.removeChild(fadeOut);
+ ImageUtil.setAttribute(fadeOut, 'shifted', false);
+ }, 500);
+
+ this.firstVisibleIndex_++;
+ this.lastVisibleIndex_++;
+
+ var pushIn = this.items_[this.lastVisibleIndex_].getBox();
+ this.bar_.appendChild(pushIn);
};
-Ribbon.prototype.scrollToFirst = function() {
- if (this.firstIndex_ > 0) {
- this.firstIndex_ = 0;
- this.redraw();
- }
+Ribbon.prototype.selectPrevious = function() {
+ if (this.selectedIndex_ > 0)
+ this.select(this.selectedIndex_ - 1);
};
-Ribbon.prototype.scrollToLast = function() {
- if (this.firstIndex_ < this.items_.length - 1) {
- this.firstIndex_ = this.items_.length - 1;
- this.redraw();
- }
+Ribbon.prototype.selectNext = function() {
+ if (this.selectedIndex_ < this.items_.length - 1)
+ this.select(this.selectedIndex_ + 1);
};
+Ribbon.prototype.selectFirst = function() {
+ this.select(0);
+};
-Ribbon.Item = function(document, url, selectClosure) {
- this.url_ = url;
+Ribbon.prototype.selectLast = function() {
+ this.select(this.items_.length - 1);
+};
- this.img_ = document.createElement('img');
+
+Ribbon.Item = function(index, url, document, selectClosure) {
+ this.index_ = index;
+ this.url_ = url;
this.box_ = document.createElement('div');
this.box_.className = 'ribbon-image';
- this.box_.addEventListener('click', selectClosure.bind(null, this));
- this.box_.appendChild(this.img_);
+ this.box_.addEventListener('click', selectClosure);
+
+ this.wrapper_ = document.createElement('div');
+ this.wrapper_.className = 'image-wrapper';
+ this.box_.appendChild(this.wrapper_);
+
+ this.img_ = document.createElement('img');
+ this.wrapper_.appendChild(this.img_);
this.original_ = true;
};
+Ribbon.Item.prototype.getIndex = function () { return this.index_ };
+
Ribbon.Item.prototype.getBox = function () { return this.box_ };
Ribbon.Item.prototype.isOriginal = function () { return this.original_ };
Ribbon.Item.prototype.getUrl = function () { return this.url_ };
+Ribbon.Item.prototype.isSelected = function() {
+ return this.box_.hasAttribute('selected');
+};
+
Ribbon.Item.prototype.select = function(on) {
- if (on)
- this.box_.setAttribute('selected', 'selected');
- else
- this.box_.removeAttribute('selected');
+ ImageUtil.setAttribute(this.box_, 'selected', on);
};
-// TODO: Localize?
-Ribbon.Item.COPY_SIGNATURE = '_Edited_';
+Ribbon.Item.prototype.save = function(
+ dirEntry, metadataProvider, canvas, opt_callback) {
+ var metadataEncoder =
+ ImageEncoder.encodeMetadata(this.getMetadata(), canvas, 1);
+
+ this.overrideContent(canvas, metadataEncoder.getMetadata());
+
+ var self = this;
+
+ if (!dirEntry) { // Happens only in gallery_demo.js
+ self.onSaveSuccess(
+ window.webkitURL.createObjectURL(
+ ImageEncoder.getBlob(canvas, metadataEncoder)));
+ if (opt_callback) opt_callback();
+ return;
+ }
+
+ var newFile = this.isOriginal();
+ var name = this.getCopyName();
-Ribbon.Item.prototype.isFromLocalFile = function () {
- return this.url_.indexOf('filesystem:') == 0;
+ function onSuccess(url) {
+ console.log('Saved from gallery', name);
+ // Force the metadata provider to reread the metadata from the file.
+ metadataProvider.reset(url);
+ self.onSaveSuccess(url);
+ if (opt_callback) opt_callback();
+ }
+
+ function onError(error) {
+ console.log('Error saving from gallery', name, error);
+ self.onSaveError(error);
+ if (opt_callback) opt_callback();
+ }
+
+ dirEntry.getFile(
+ name, {create: newFile, exclusive: newFile}, function(fileEntry) {
+ fileEntry.createWriter(function(fileWriter) {
+ function writeContent() {
+ fileWriter.onwriteend = onSuccess.bind(null, fileEntry.toURL());
+ fileWriter.write(ImageEncoder.getBlob(canvas, metadataEncoder));
+ }
+ fileWriter.onerror = onError;
+ if (newFile) {
+ writeContent();
+ } else {
+ fileWriter.onwriteend = writeContent;
+ fileWriter.truncate(0);
+ }
+ },
+ onError);
+ }, onError);
};
+// TODO: Localize?
+Ribbon.Item.COPY_SIGNATURE = '_Edited_';
+
Ribbon.Item.prototype.getCopyName = function () {
// When saving a modified image we never overwrite the original file (the one
// that existed prior to opening the Gallery. Instead we save to a file named
@@ -553,11 +737,45 @@ Ribbon.Item.prototype.setMetadata = function(metadata) {
metadata.thumbnailTransform :
metadata.imageTransform;
- this.box_.style.webkitTransform = transform ?
+ this.wrapper_.style.webkitTransform = transform ?
('scaleX(' + transform.scaleX + ') ' +
'scaleY(' + transform.scaleY + ') ' +
'rotate(' + transform.rotate90 * 90 + 'deg)') :
'';
+ function percent(ratio) { return Math.round(ratio * 100) + '%' }
+
+ function resizeToFill(img, aspect) {
+ if ((aspect > 1)) {
+ img.style.height = percent(1);
+ img.style.width = percent(aspect);
+ img.style.marginLeft = percent((1 - aspect) / 2);
+ } else {
+ aspect = 1 / aspect;
+ img.style.width = percent(1);
+ img.style.height = percent(aspect);
+ img.style.marginTop = percent((1 - aspect) / 2);
+ }
+ }
+
+ if (metadata.width && metadata.height) {
+ var aspect = metadata.width / metadata.height;
+ if (transform && transform.rotate90) {
+ aspect = 1 / aspect;
+ }
+ resizeToFill(this.img_, aspect);
+ } else {
+ // No metadata available, loading the thumbnail first, then adjust the size.
+ this.img_.maxWidth = '100%';
+ this.img_.maxHeight = '100%';
+
+ var img = this.img_;
+ this.img_.onload = function() {
+ img.maxWidth = 'none';
+ img.maxHeight = 'none';
+ resizeToFill(img, img.width / img.height);
+ }
+ }
+
this.img_.setAttribute('src', metadata.thumbnailURL || this.url_);
};
diff --git a/chrome/browser/resources/file_manager/js/image_editor/gallery_demo.html b/chrome/browser/resources/file_manager/js/image_editor/gallery_demo.html
index ced0553..d4c357dc 100644
--- a/chrome/browser/resources/file_manager/js/image_editor/gallery_demo.html
+++ b/chrome/browser/resources/file_manager/js/image_editor/gallery_demo.html
@@ -5,10 +5,12 @@
-->
<html>
<head>
+ <script type="text/javascript" src="../metadata_provider.js"></script>
<script type="text/javascript" src="gallery_demo.js"></script>
<style type="text/css">
body {
+ -webkit-user-select: none;
margin: 0
}
@@ -18,8 +20,14 @@
border: none;
}
+ .gallery-frame.chromebook {
+ width: 1280px;
+ height: 700px;
+ }
+
.debug-buttons {
position: absolute;
+ color: white;
left: 1px;
top: 1px;
}
@@ -30,6 +38,11 @@
top: 28px;
text-align: left;
color: white;
+ opacity: 0.3;
+ }
+
+ .debug-output:hover {
+ opacity: 1;
}
</style>
@@ -37,12 +50,15 @@
<body>
<iframe class="gallery-frame"
scrolling="no"
- src="gallery.html">
+ src="gallery.html"
+ onload="loadGallery()"/>
</iframe>
<div class="debug-buttons">
- <button onclick="loadTestGrid()">Test grid</button>
- <input type="file" multiple onchange="openFiles(this.files)"/>
+ <input type="checkbox" onchange="toggleSize(this);"/>
+ <span>Chromebook size</span>
+ <button onclick="loadGallery()">Test grid</button>
+ <input type="file" multiple onchange="loadGallery(this.files)"/>
</div>
<div class="debug-output"></div>
diff --git a/chrome/browser/resources/file_manager/js/image_editor/gallery_demo.js b/chrome/browser/resources/file_manager/js/image_editor/gallery_demo.js
index 2788023..ab004d9 100644
--- a/chrome/browser/resources/file_manager/js/image_editor/gallery_demo.js
+++ b/chrome/browser/resources/file_manager/js/image_editor/gallery_demo.js
@@ -2,40 +2,38 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-var mockDirEntry = {
- getFile: function(name, options, onSuccess, onError) {
- onError('File write not supported');
- }
-};
+var metadataProvider = new MetadataProvider('../metadata_dispatcher.js');
+
+function toggleSize(check) {
+ var iframe = document.querySelector('.gallery-frame');
+ iframe.classList.toggle('chromebook');
+}
-var mockMetadataProvider = {
- fetch: function(url, callback) {
- setTimeout(function(){ callback({mimeType: 'image/jpeg'}) }, 0);
- },
+var mockActions = [
+ {
+ title: 'Send',
+ iconUrl: 'http://google.com/favicon.ico',
+ execute: function() { alert('Sending is not supported') }
+ }];
- reset: function() {}
-};
+function loadGallery(items) {
+ if (!items) items = [createTestGrid()];
-function initGallery(urls) {
- var contentWindow = document.querySelector('.gallery-frame').contentWindow;
+ var iframe = document.querySelector('.gallery-frame');
+ var contentWindow = iframe.contentWindow;
contentWindow.ImageUtil.trace.bindToDOM(
document.querySelector('.debug-output'));
contentWindow.Gallery.open(
- mockDirEntry, urls, function(){}, mockMetadataProvider);
-}
-
-function openFiles(files) {
- var urls = [];
- for (var i = 0; i != files.length; i++) {
- urls.push(window.webkitURL.createObjectURL(files[i]));
- }
- initGallery(urls);
-}
-
-function loadTestGrid() {
- initGallery([createTestGrid().toDataURL('image/jpeg')]);
+ null, // No local file access
+ items,
+ items[0],
+ function() {}, // Do nothing on Close
+ metadataProvider,
+ mockActions);
+
+ iframe.focus();
}
function createTestGrid() {
diff --git a/chrome/browser/resources/file_manager/js/image_editor/image_adjust.js b/chrome/browser/resources/file_manager/js/image_editor/image_adjust.js
index cf6b47d..716cd96 100644
--- a/chrome/browser/resources/file_manager/js/image_editor/image_adjust.js
+++ b/chrome/browser/resources/file_manager/js/image_editor/image_adjust.js
@@ -9,6 +9,7 @@
*/
ImageEditor.Mode.Adjust = function(arglist) {
ImageEditor.Mode.apply(this, arguments);
+ this.implicitCommit = true;
this.viewportGeneration_ = 0;
};
@@ -18,47 +19,18 @@ ImageEditor.Mode.Adjust.prototype = {__proto__: ImageEditor.Mode.prototype};
* ImageEditor.Mode methods overridden.
*/
-ImageEditor.Mode.Adjust.prototype.commit = function() {
- if (!this.filter_) return; // Did not do anything yet.
+ImageEditor.Mode.Adjust.prototype.getCommand = function() {
+ if (!this.filter_) return null;
- // Applying the filter to the entire image takes some time, so we do
- // it in small increments, providing visual feedback.
- // TODO: provide modal progress indicator.
-
- // First hide the preview and show the original image.
- this.repaint();
-
- var self = this;
-
- function repaintStrip(fromRow, toRow) {
- var imageStrip = new Rect(self.getViewport().getImageBounds());
- imageStrip.top = fromRow;
- imageStrip.height = toRow - fromRow;
-
- var screenStrip = new Rect(self.getViewport().getImageBoundsOnScreen());
- screenStrip.top = Math.round(self.getViewport().imageToScreenY(fromRow));
- screenStrip.height = Math.round(self.getViewport().imageToScreenY(toRow)) -
- screenStrip.top;
+ return new Command.Filter(this.name, this.filter_, this.message_);
+};
- self.getBuffer().repaintScreenRect(screenStrip, imageStrip);
+ImageEditor.Mode.Adjust.prototype.cleanUpUI = function() {
+ ImageEditor.Mode.prototype.cleanUpUI.apply(this, arguments);
+ if (this.canvas_) {
+ this.canvas_.parentNode.removeChild(this.canvas_);
+ this.canvas_ = null;
}
-
- ImageUtil.trace.resetTimer('filter');
-
- var lastUpdatedRow = 0;
-
- filter.applyByStrips(
- this.getContent().getCanvas().getContext('2d'),
- this.filter_,
- function (updatedRow, rowCount) {
- repaintStrip(lastUpdatedRow, updatedRow);
- lastUpdatedRow = updatedRow;
- if (updatedRow == rowCount) {
- ImageUtil.trace.reportTimer('filter');
- self.getContent().invalidateCaches();
- self.repaint();
- }
- });
};
ImageEditor.Mode.Adjust.prototype.cleanUpCaches = function() {
@@ -67,15 +39,21 @@ ImageEditor.Mode.Adjust.prototype.cleanUpCaches = function() {
};
ImageEditor.Mode.Adjust.prototype.update = function(options) {
+ ImageEditor.Mode.prototype.update.apply(this, arguments);
+
// We assume filter names are used in the UI directly.
// This will have to change with i18n.
this.filter_ = this.createFilter(options);
- this.previewValid_ = false;
- this.repaint();
+ this.updatePreviewImage();
+ ImageUtil.trace.resetTimer('preview');
+ this.filter_(this.previewImageData_, this.originalImageData, 0, 0);
+ ImageUtil.trace.reportTimer('preview');
+ this.canvas_.getContext('2d').putImageData(
+ this.previewImageData_, 0, 0);
};
/**
- * Clip and scale the source image data for the preview.
+ * Copy the source image data for the preview.
* Use the cached copy if the viewport has not changed.
*/
ImageEditor.Mode.Adjust.prototype.updatePreviewImage = function() {
@@ -83,48 +61,23 @@ ImageEditor.Mode.Adjust.prototype.updatePreviewImage = function() {
this.viewportGeneration_ != this.getViewport().getCacheGeneration()) {
this.viewportGeneration_ = this.getViewport().getCacheGeneration();
- var imageRect = this.getPreviewRect(this.getViewport().getImageClipped());
- var screenRect = this.getPreviewRect(this.getViewport().getScreenClipped());
-
- // Copy the visible part of the image at the current screen scale.
- var canvas = this.getContent().createBlankCanvas(
- screenRect.width, screenRect.height);
- var context = canvas.getContext('2d');
- Rect.drawImage(context, this.getContent().getCanvas(), null, imageRect);
- this.originalImageData =
- context.getImageData(0, 0, screenRect.width, screenRect.height);
- this.previewImageData_ =
- context.getImageData(0, 0, screenRect.width, screenRect.height);
- this.previewValid_ = false;
- }
+ if (!this.canvas_) {
+ var container = this.getImageView().container_;
+ this.canvas_ = container.ownerDocument.createElement('canvas');
+ container.appendChild(this.canvas_);
+ }
- if (this.filter_ && !this.previewValid_) {
- ImageUtil.trace.resetTimer('preview');
- this.filter_(this.previewImageData_, this.originalImageData, 0, 0);
- ImageUtil.trace.reportTimer('preview');
- this.previewValid_ = true;
- }
-};
+ var screenClipped = this.getViewport().getScreenClipped();
-ImageEditor.Mode.Adjust.prototype.draw = function(context) {
- this.updatePreviewImage();
+ this.canvas_.style.left = screenClipped.left + 'px';
+ this.canvas_.style.top = screenClipped.top + 'px';
+ if (this.canvas_.width != screenClipped.width)
+ this.canvas_.width = screenClipped.width;
+ if (this.canvas_.height != screenClipped.height)
+ this.canvas_.height = screenClipped.height;
- var screenClipped = this.getViewport().getScreenClipped();
-
- var previewRect = this.getPreviewRect(screenClipped);
- context.putImageData(
- this.previewImageData_, previewRect.left, previewRect.top);
-
- if (previewRect.width < screenClipped.width &&
- previewRect.height < screenClipped.height) {
- // Some part of the original image is not covered by the preview,
- // shade it out.
- context.globalAlpha = 0.75;
- context.fillStyle = '#000000';
- context.strokeStyle = '#000000';
- Rect.fillBetween(
- context, previewRect, this.getViewport().getScreenBounds());
- Rect.outline(context, previewRect);
+ this.originalImageData = this.getImageView().copyScreenImageData();
+ this.previewImageData_ = this.getImageView().copyScreenImageData();
}
};
@@ -136,21 +89,6 @@ ImageEditor.Mode.Adjust.prototype.createFilter = function(options) {
return filter.create(this.name, options);
};
-ImageEditor.Mode.Adjust.prototype.getPreviewRect = function(rect) {
- if (this.getViewport().getScale() >= 1) {
- return rect;
- } else {
- var bounds = this.getViewport().getImageBounds();
- var screen = this.getViewport().getScreenClipped();
-
- screen = screen.inflate(-screen.width / 8, -screen.height / 8);
-
- return rect.inflate(-rect.width / 2, -rect.height / 2).
- inflate(Math.min(screen.width, bounds.width) / 2,
- Math.min(screen.height, bounds.height) / 2);
- }
-};
-
/**
* A base class for color filters that are scale independent (i.e. can
* be applied to a scaled image with basicaly the same effect).
@@ -166,12 +104,8 @@ ImageEditor.Mode.ColorFilter.prototype =
ImageEditor.Mode.ColorFilter.prototype.setUp = function() {
ImageEditor.Mode.Adjust.prototype.setUp.apply(this, arguments);
- this.histogram_ =
- new ImageEditor.Mode.Histogram(this.getViewport(), this.getContent());
-};
-
-ImageEditor.Mode.ColorFilter.prototype.getPreviewRect = function(rect) {
- return rect;
+ this.histogram_ = new ImageEditor.Mode.Histogram(
+ this.getViewport(), this.getImageView().getCanvas());
};
ImageEditor.Mode.ColorFilter.prototype.createFilter = function(options) {
@@ -190,14 +124,16 @@ ImageEditor.Mode.ColorFilter.prototype.cleanUpUI = function() {
* A histogram container.
* @constructor
*/
-ImageEditor.Mode.Histogram = function(viewport, content) {
+ImageEditor.Mode.Histogram = function(viewport, canvas) {
this.viewport_ = viewport;
- var canvas = content.getCanvas();
var downScale = Math.max(1, Math.sqrt(canvas.width * canvas.height / 10000));
- var thumbnail = content.copyCanvas(canvas.width / downScale,
- canvas.height / downScale);
+
+ var thumbnail = canvas.ownerDocument.createElement('canvas');
+ thumbnail.width = canvas.width / downScale;
+ thumbnail.height = canvas.height / downScale;
var context = thumbnail.getContext('2d');
+ Rect.drawImage(context, canvas);
this.originalImageData_ =
context.getImageData(0, 0, thumbnail.width, thumbnail.height);
@@ -282,8 +218,6 @@ ImageEditor.Mode.Exposure = function() {
ImageEditor.Mode.Exposure.prototype =
{__proto__: ImageEditor.Mode.ColorFilter.prototype};
-ImageEditor.Mode.register(ImageEditor.Mode.Exposure);
-
ImageEditor.Mode.Exposure.prototype.createTools = function(toolbar) {
toolbar.addRange('brightness', -1, 0, 1, 100);
toolbar.addRange('contrast', -1, 0, 1, 100);
@@ -295,13 +229,12 @@ ImageEditor.Mode.Exposure.prototype.createTools = function(toolbar) {
*/
ImageEditor.Mode.Autofix = function() {
ImageEditor.Mode.ColorFilter.call(this, 'autofix');
+ this.message_ = 'fixed';
};
ImageEditor.Mode.Autofix.prototype =
{__proto__: ImageEditor.Mode.ColorFilter.prototype};
-ImageEditor.Mode.register(ImageEditor.Mode.Autofix);
-
ImageEditor.Mode.Autofix.prototype.createTools = function(toolbar) {
var self = this;
toolbar.addButton('Apply', this.apply.bind(this));
@@ -317,12 +250,14 @@ ImageEditor.Mode.Autofix.prototype.apply = function() {
*/
ImageEditor.Mode.InstantAutofix = function() {
ImageEditor.Mode.Autofix.apply(this, arguments);
+ this.instant = true;
};
ImageEditor.Mode.InstantAutofix.prototype =
{__proto__: ImageEditor.Mode.Autofix.prototype};
-ImageEditor.Mode.InstantAutofix.prototype.oneClick = function() {
+ImageEditor.Mode.InstantAutofix.prototype.setUp = function() {
+ ImageEditor.Mode.Autofix.prototype.setUp.apply(this, arguments);
this.apply();
};
@@ -337,8 +272,6 @@ ImageEditor.Mode.Blur = function() {
ImageEditor.Mode.Blur.prototype =
{__proto__: ImageEditor.Mode.Adjust.prototype};
-// TODO(dgozman): register Mode.Blur in v2.
-
ImageEditor.Mode.Blur.prototype.createTools = function(toolbar) {
toolbar.addRange('strength', 0, 0, 1, 100);
toolbar.addRange('radius', 1, 1, 3);
@@ -355,8 +288,6 @@ ImageEditor.Mode.Sharpen = function() {
ImageEditor.Mode.Sharpen.prototype =
{__proto__: ImageEditor.Mode.Adjust.prototype};
-// TODO(dgozman): register Mode.Sharpen in v2.
-
ImageEditor.Mode.Sharpen.prototype.createTools = function(toolbar) {
toolbar.addRange('strength', 0, 0, 1, 100);
toolbar.addRange('radius', 1, 1, 3);
diff --git a/chrome/browser/resources/file_manager/js/image_editor/image_buffer.js b/chrome/browser/resources/file_manager/js/image_editor/image_buffer.js
index 46fd65f..46c1407 100644
--- a/chrome/browser/resources/file_manager/js/image_editor/image_buffer.js
+++ b/chrome/browser/resources/file_manager/js/image_editor/image_buffer.js
@@ -3,81 +3,16 @@
// found in the LICENSE file.
/**
- * The ImageBuffer object holds an offscreen canvas object and
- * draws its content on the screen canvas applying scale and offset.
- * Supports pluggable overlays that modify the image appearance and behavior.
+ * A stack of overlays that display itself and handle mouse events.
+ * TODO(kaznacheev) Consider disbanding this class and moving
+ * the functionality to individual objects that display anything or handle
+ * mouse events.
* @constructor
*/
-function ImageBuffer(screenCanvas) {
- this.screenCanvas_ = screenCanvas;
-
- this.viewport_ = new Viewport(this.repaint.bind(this));
- this.viewport_.setScreenSize(screenCanvas.width, screenCanvas.height);
-
- this.content_ = new ImageBuffer.Content(
- this.viewport_, screenCanvas.ownerDocument);
-
+function ImageBuffer() {
this.overlays_ = [];
- this.addOverlay(new ImageBuffer.Margin(this.viewport_));
- this.addOverlay(this.content_);
- // TODO(dgozman): consider adding overview in v2.
}
-ImageBuffer.prototype.getViewport = function() { return this.viewport_ };
-
-ImageBuffer.prototype.getContent = function() { return this.content_ };
-
-/**
- * Loads the new content.
- * A string parameter is treated as an image url.
- * @param {String|HTMLImageElement|HTMLCanvasElement} source
- * @param {{scaleX: number, scaleY: number, rotate90: number}} opt_transform
- */
-ImageBuffer.prototype.load = function(source, opt_transform) {
- if (typeof source == 'string') {
- var self = this;
- var image = new Image();
- image.onload = function(e) { self.load(e.target, opt_transform) };
- image.src = source;
- } else {
- this.content_.load(source, opt_transform);
- this.repaint();
- }
-};
-
-ImageBuffer.prototype.resizeScreen = function(width, height, keepFitting) {
- this.screenCanvas_.width = width;
- this.screenCanvas_.height = height;
-
- var wasFitting =
- this.viewport_.getScale() == this.viewport_.getFittingScale();
-
- this.viewport_.setScreenSize(width, height);
-
- var minScale = this.viewport_.getFittingScale();
- if ((wasFitting && keepFitting) || this.viewport_.getScale() < minScale) {
- this.viewport_.setScale(minScale, true);
- }
- this.repaint();
-};
-
-/**
- * Paints the content on the screen canvas taking the current scale and offset
- * into account.
- */
-ImageBuffer.prototype.repaint = function (opt_fromOverlay) {
- this.viewport_.update();
- this.drawOverlays(this.screenCanvas_.getContext("2d"), opt_fromOverlay);
-};
-
-ImageBuffer.prototype.repaintScreenRect = function (screenRect, imageRect) {
- Rect.drawImage(
- this.screenCanvas_.getContext('2d'),
- this.getContent().getCanvas(),
- screenRect || this.getViewport().imageToScreenRect(screenRect),
- imageRect || this.getViewport().screenToImageRect(screenRect));
-};
-
/**
* @param {ImageBuffer.Overlay} overlay
*/
@@ -106,18 +41,10 @@ ImageBuffer.prototype.removeOverlay = function (overlay) {
/**
* Draws overlays in the ascending Z-order.
- * Skips overlays below opt_startFrom.
*/
-ImageBuffer.prototype.drawOverlays = function (context, opt_fromOverlay) {
- var skip = true;
+ImageBuffer.prototype.draw = function () {
for (var i = 0; i != this.overlays_.length; i++) {
- var overlay = this.overlays_[i];
- if (!opt_fromOverlay || opt_fromOverlay == overlay) skip = false;
- if (skip) continue;
-
- context.save();
- overlay.draw(context);
- context.restore();
+ this.overlays_[i].draw();
}
};
@@ -170,287 +97,4 @@ ImageBuffer.Overlay.prototype.getCursorStyle = function() { return null };
ImageBuffer.Overlay.prototype.onClick = function() { return false };
-ImageBuffer.Overlay.prototype.getDragHandler = function() { return null };
-
-
-/**
- * The margin overlay draws the image outline and paints the margins.
- */
-ImageBuffer.Margin = function(viewport) {
- this.viewport_ = viewport;
-};
-
-ImageBuffer.Margin.prototype = {__proto__: ImageBuffer.Overlay.prototype};
-
-// Draw below everything including the content.
-ImageBuffer.Margin.prototype.getZIndex = function() { return -2 };
-
-ImageBuffer.Margin.prototype.draw = function(context) {
- context.save();
- context.fillStyle = '#000000';
- context.strokeStyle = '#000000';
- Rect.fillBetween(context,
- this.viewport_.getImageBoundsOnScreen(),
- this.viewport_.getScreenBounds());
-
- Rect.outline(context, this.viewport_.getImageBoundsOnScreen());
- context.restore();
-};
-
-/**
- * The overlay containing the image.
- */
-ImageBuffer.Content = function(viewport, document) {
- this.viewport_ = viewport;
- this.document_ = document;
-
- this.generation_ = 0;
-
- this.setCanvas(this.createBlankCanvas(0, 0));
-};
-
-ImageBuffer.Content.prototype = {__proto__: ImageBuffer.Overlay.prototype};
-
-// Draw below overlays with the default zIndex.
-ImageBuffer.Content.prototype.getZIndex = function() { return -1 };
-
-ImageBuffer.Content.prototype.draw = function(context) {
- Rect.drawImage(
- context, this.canvas_, this.viewport_.getImageBoundsOnScreen());
-};
-
-ImageBuffer.Content.prototype.getCursorStyle = function (x, y, mouseDown) {
- // Indicate that the image is draggable.
- if (this.viewport_.isClipped() &&
- this.viewport_.getScreenClipped().inside(x, y))
- return 'move';
-
- return null;
-};
-
-ImageBuffer.Content.prototype.getDragHandler = function (x, y) {
- var cursor = this.getCursorStyle(x, y);
- if (cursor == 'move') {
- // Return the handler that drags the entire image.
- return this.viewport_.createOffsetSetter(x, y);
- }
-
- return null;
-};
-
-ImageBuffer.Content.prototype.getCacheGeneration = function() {
- return this.generation_;
-};
-
-ImageBuffer.Content.prototype.invalidateCaches = function() {
- this.generation_++;
-};
-
-ImageBuffer.Content.prototype.getCanvas = function() { return this.canvas_ };
-
-ImageBuffer.Content.prototype.detachCanvas = function() {
- var canvas = this.canvas_;
- this.setCanvas(this.createBlankCanvas(0, 0));
- return canvas;
-};
-
-/**
- * Replaces the off-screen canvas.
- * To be used when the editor modifies the image dimensions.
- * If the logical width/height are supplied they override the canvas dimensions
- * and the canvas contents is scaled when displayed.
- * @param {HTMLCanvasElement} canvas
- * @param {number} opt_width Logical width (=canvas.width by default)
- * @param {number} opt_height Logical height (=canvas.height by default)
- */
-ImageBuffer.Content.prototype.setCanvas = function(
- canvas, opt_width, opt_height) {
- this.canvas_ = canvas;
- this.viewport_.setImageSize(opt_width || canvas.width,
- opt_height || canvas.height);
-
- this.invalidateCaches();
-};
-
-/**
- * @return {HTMLCanvasElement} A new blank canvas of the required size.
- */
-ImageBuffer.Content.prototype.createBlankCanvas = function (width, height) {
- var canvas = this.document_.createElement('canvas');
- canvas.width = width;
- canvas.height = height;
- return canvas;
-};
-
-/**
- * @param {number} opt_width Width of the copy, original width by default.
- * @param {number} opt_height Height of the copy, original height by default.
- * @return {HTMLCanvasElement} A new canvas with a copy of the content.
- */
-ImageBuffer.Content.prototype.copyCanvas = function (opt_width, opt_height) {
- var canvas = this.createBlankCanvas(opt_width || this.canvas_.width,
- opt_height || this.canvas_.height);
- Rect.drawImage(canvas.getContext('2d'), this.canvas_);
- return canvas;
-};
-
-/**
- * @return {ImageData} A new ImageData object with a copy of the content.
- */
-ImageBuffer.Content.prototype.copyImageData = function (opt_width, opt_height) {
- return this.canvas_.getContext("2d").getImageData(
- 0, 0, opt_width || this.canvas_.width, opt_height || this.canvas_.height);
-};
-
-/**
- * @param {HTMLImageElement|HTMLCanvasElement} image
- * @param {{scaleX: number, scaleY: number, rotate90: number}} opt_transform
- */
-ImageBuffer.Content.prototype.load = function(image, opt_transform) {
- opt_transform = opt_transform || { scaleX: 1, scaleY: 1, rotate90: 0};
-
- if (opt_transform.rotate90 & 1) { // Rotated +/-90deg, swap the dimensions.
- this.canvas_.width = image.height;
- this.canvas_.height = image.width;
- } else {
- this.canvas_.width = image.width;
- this.canvas_.height = image.height;
- }
-
- this.clear();
- ImageUtil.drawImageTransformed(
- this.canvas_,
- image,
- opt_transform.scaleX,
- opt_transform.scaleY,
- opt_transform.rotate90 * Math.PI / 2);
- this.invalidateCaches();
-
- this.viewport_.setImageSize(this.canvas_.width, this.canvas_.height);
- this.viewport_.fitImage();
-};
-
-ImageBuffer.Content.prototype.clear = function() {
- var context = this.canvas_.getContext("2d");
- context.globalAlpha = 1;
- context.fillStyle = '#FFFFFF';
- Rect.fill(context, new Rect(this.canvas_));
-};
-
-/**
- * @param {ImageData} imageData
- */
-ImageBuffer.Content.prototype.drawImageData = function (imageData, x, y) {
- this.canvas_.getContext("2d").putImageData(imageData, x, y);
- this.invalidateCaches();
-};
-
-/**
- * The overview overlay draws the image thumbnail in the bottom right corner.
- * Indicates the currently visible part. Supports panning by dragging.
- */
-ImageBuffer.Overview = function(viewport, content) {
- this.viewport_ = viewport;
- this.content_ = content;
- this.contentGeneration_ = 0;
-};
-
-ImageBuffer.Overview.prototype = {__proto__: ImageBuffer.Overlay.prototype};
-
-// Draw above everything.
-ImageBuffer.Overview.prototype.getZIndex = function() { return 100 };
-
-ImageBuffer.Overview.MAX_SIZE = 150;
-ImageBuffer.Overview.RIGHT = 7;
-ImageBuffer.Overview.BOTTOM = 50;
-
-ImageBuffer.Overview.prototype.update = function() {
- var imageBounds = this.viewport_.getImageBounds();
-
- if (this.contentGeneration_ != this.content_.getCacheGeneration()) {
- this.contentGeneration_ = this.content_.getCacheGeneration();
-
- var aspect = imageBounds.width / imageBounds.height;
-
- this.canvas_ = this.content_.copyCanvas(
- ImageBuffer.Overview.MAX_SIZE * Math.min(aspect, 1),
- ImageBuffer.Overview.MAX_SIZE / Math.max(aspect, 1));
- }
-
- this.bounds_ = null;
- this.clipped_ = null;
-
- if (this.viewport_.isClipped()) {
- var screenBounds = this.viewport_.getScreenBounds();
-
- this.bounds_ = new Rect(
- screenBounds.width - ImageBuffer.Overview.RIGHT - this.canvas_.width,
- screenBounds.height - ImageBuffer.Overview.BOTTOM - this.canvas_.height,
- this.canvas_.width,
- this.canvas_.height);
-
- this.scale_ = this.bounds_.width / imageBounds.width;
-
- this.clipped_ = this.viewport_.getImageClipped().
- scale(this.scale_).
- shift(this.bounds_.left, this.bounds_.top);
- }
-};
-
-ImageBuffer.Overview.prototype.draw = function(context) {
- this.update();
-
- if (!this.clipped_) return;
-
- // Draw the thumbnail.
- Rect.drawImage(context, this.canvas_, this.bounds_);
-
- // Draw the shadow over the off-screen part of the thumbnail.
- context.globalAlpha = 0.3;
- context.fillStyle = '#000000';
- Rect.fillBetween(context, this.clipped_, this.bounds_);
-
- // Outline the on-screen part of the thumbnail.
- context.strokeStyle = '#FFFFFF';
- Rect.outline(context, this.clipped_);
-
- context.globalAlpha = 1;
- // Draw the thumbnail border.
- context.strokeStyle = '#000000';
- Rect.outline(context, this.bounds_);
-};
-
-ImageBuffer.Overview.prototype.getCursorStyle = function(x, y) {
- if (!this.bounds_ || !this.bounds_.inside(x, y)) return null;
-
- // Indicate that the on-screen part is draggable.
- if (this.clipped_ && this.clipped_.inside(x, y)) return 'move';
-
- // Indicate that the rest of the thumbnail is clickable.
- return 'crosshair';
-};
-
-ImageBuffer.Overview.prototype.onClick = function(x, y) {
- if (this.getCursorStyle(x, y) != 'crosshair') return false;
- this.viewport_.setCenter(
- (x - this.bounds_.left) / this.scale_,
- (y - this.bounds_.top) / this.scale_);
- this.viewport_.repaint();
- return true;
-};
-
-ImageBuffer.Overview.prototype.getDragHandler = function(x, y) {
- var cursor = this.getCursorStyle(x, y);
-
- if (cursor == 'move') {
- var self = this;
- function scale() { return -self.scale_;}
- function hit(x, y) { return self.bounds_ && self.bounds_.inside(x, y); }
- return this.viewport_.createOffsetSetter(x, y, scale, hit);
- } else if (cursor == 'crosshair') {
- // Force non-draggable behavior.
- return function() {};
- } else {
- return null;
- }
-};
+ImageBuffer.Overlay.prototype.getDragHandler = function() { return null }; \ No newline at end of file
diff --git a/chrome/browser/resources/file_manager/js/image_editor/image_editor.js b/chrome/browser/resources/file_manager/js/image_editor/image_editor.js
index 8e89b37..8304e1c 100644
--- a/chrome/browser/resources/file_manager/js/image_editor/image_editor.js
+++ b/chrome/browser/resources/file_manager/js/image_editor/image_editor.js
@@ -8,35 +8,30 @@
* @param {HTMLElement} container
* @param {HTMLElement} mainToolbarContainer
* @param {HTMLElement} modeToolbarContainer
- * @param {Array.<ImageEditor.Mode>} tools
+ * @param {Array.<ImageEditor.Mode>} modes
* @param {Object} displayStrings
*/
function ImageEditor(
container, mainToolbarContainer, modeToolbarContainer,
- tools, displayStrings) {
+ modes, displayStrings) {
this.container_ = container;
- this.tools_ = tools || ImageEditor.Mode.constructors;
this.displayStrings_ = displayStrings;
this.container_.innerHTML = '';
var document = this.container_.ownerDocument;
- this.canvasWrapper_ = document.createElement('div');
- this.canvasWrapper_.className = 'canvas-wrapper';
- container.appendChild(this.canvasWrapper_);
+ this.viewport_ = new Viewport();
+ this.viewport_.sizeByFrame(this.container_);
- var canvas = document.createElement('canvas');
- this.canvasWrapper_.appendChild(canvas);
- canvas.width = this.canvasWrapper_.clientWidth;
- canvas.height = this.canvasWrapper_.clientHeight;
+ this.buffer_ = new ImageBuffer();
+ this.viewport_.addRepaintCallback(this.buffer_.draw.bind(this.buffer_));
- this.buffer_ = new ImageBuffer(canvas);
- this.modified_ = false;
+ this.imageView_ = new ImageView(this.container_, this.viewport_);
+ this.buffer_.addOverlay(this.imageView_);
- // TODO(dgozman): consider adding a ScaleControl in v2.
-
- this.panControl_ = new ImageEditor.MouseControl(canvas, this.getBuffer());
+ this.panControl_ = new ImageEditor.MouseControl(
+ this.container_, this.getBuffer());
this.mainToolbar_ = new ImageEditor.Toolbar(
mainToolbarContainer, displayStrings);
@@ -44,95 +39,132 @@ function ImageEditor(
this.modeToolbar_ = new ImageEditor.Toolbar(
modeToolbarContainer, displayStrings, this.onOptionsChange.bind(this));
- this.createToolButtons();
+ this.prompt_ = new ImageEditor.Prompt(
+ this.container_, this.getDisplayString.bind(this));
+
+ this.createToolButtons(modes);
+
+ this.commandQueue_ = null;
}
-/**
- * Create an ImageEditor instance bound to a current web page, load the content.
- *
- * Use this method when image_editor.html is loaded into an iframe.
- *
- * @param {function(Blob)} saveCallback
- * @param {function()} closeCallback
- * @param {HTMLCanvasElement|HTMLImageElement|String} source
- * @param {Object} opt_metadata
- * @return {ImageEditor}
- */
-ImageEditor.open = function(saveCallback, closeCallback, source, opt_metadata) {
- var container = document.getElementsByClassName('image-editor')[0];
- var toolbar = document.getElementsByClassName('toolbar-container')[0];
- var editor = new ImageEditor(container, toolbar, saveCallback, closeCallback);
- if (ImageEditor.resizeListener) {
+ImageEditor.prototype.trackWindow = function(window) {
+ if (window.resizeListener) {
// Make sure we do not leak the previous instance.
- window.removeEventListener('resize', ImageEditor.resizeListener, false);
+ window.removeEventListener('resize', window.resizeListener, false);
}
- ImageEditor.resizeListener = editor.resizeFrame.bind(editor);
- window.addEventListener('resize', ImageEditor.resizeListener, false);
- editor.load(source, opt_metadata);
- return editor;
+ window.resizeListener = this.resizeFrame.bind(this);
+ window.addEventListener('resize', window.resizeListener, false);
};
-/**
- * Loads a new image and its metadata.
- *
- * Takes into account the image orientation encoded in metadata.
- *
- * @param {HTMLCanvasElement|HTMLImageElement|String} source
- * @param {Object} opt_metadata
- */
-ImageEditor.prototype.load = function(source, opt_metadata) {
- this.onModeLeave();
- this.originalSource_ = source;
- this.originalMetadata_ = opt_metadata || {};
- this.getBuffer().load(
- this.originalSource_, this.originalMetadata_.imageTransform);
- this.modified_ = false;
+ImageEditor.prototype.isLocked = function() {
+ return !this.commandQueue_ || this.commandQueue_.isBusy();
+};
+
+ImageEditor.prototype.lockUI = function(on) {
+ ImageUtil.setAttribute(this.container_.parentNode, 'locked', on);
+ this.container_.style.cursor = on ? 'wait' : 'default';
+ return true;
};
-ImageEditor.prototype.reload = function() {
- this.load(this.originalSource_, this.originalMetadata_);
+ImageEditor.prototype.openSession = function(
+ source, metadata, slide, opt_callback) {
+ var self = this;
+ this.imageView_.load(source, metadata, slide, function() {
+ self.commandQueue_ = new CommandQueue(
+ self.container_.ownerDocument, self.imageView_.getCanvas());
+ self.commandQueue_.attachUI(
+ self.getImageView(), self.getPrompt(), self.lockUI.bind(self));
+ self.updateUndoRedo();
+ if (opt_callback) opt_callback();
+ });
};
/**
- * Create a metadata encoder object that holds metadata corresponding to
- * the current image.
- *
- * @param {number} quality
+ * @param {function(HTMLCanvasElement,boolean) opt_callback Passes the current
+ * image and the modified flag.
*/
-ImageEditor.prototype.encodeMetadata = function(quality) {
- return ImageEncoder.encodeMetadata(this.originalMetadata_,
- this.getBuffer().getContent().getCanvas(), quality || 1);
+ImageEditor.prototype.closeSession = function(opt_callback) {
+ if (this.imageView_.isLoading()) {
+ this.imageView_.cancelLoad();
+ return;
+ }
+
+ if (!this.commandQueue_) return;
+
+ this.leaveModeGently();
+
+ this.commandQueue_.detachUI();
+ var detachedQueue = this.commandQueue_;
+ this.commandQueue_ = null;
+
+ if (opt_callback) {
+ // The detached command queue can still be busy. Let it finish.
+ detachedQueue.requestCurrentImage(function(canvas) {
+ opt_callback(canvas, detachedQueue.canUndo());
+ });
+ }
+};
+
+ImageEditor.prototype.undo = function() {
+ if (this.commandQueue_.isBusy()) return;
+ this.getPrompt().hide();
+ this.leaveMode(false);
+ this.commandQueue_.undo();
+ this.updateUndoRedo();
};
-ImageEditor.prototype.isModified = function() { return this.modified_ };
+ImageEditor.prototype.redo = function() {
+ if (this.commandQueue_.isBusy()) return;
+ this.getPrompt().hide();
+ this.leaveMode(false);
+ this.commandQueue_.redo();
+ this.updateUndoRedo();
+};
+
+ImageEditor.prototype.updateUndoRedo = function() {
+ var canUndo = this.commandQueue_ && this.commandQueue_.canUndo();
+ var canRedo = this.commandQueue_ && this.commandQueue_.canRedo();
+ ImageUtil.setAttribute(this.undoButton_, 'disabled', !canUndo);
+ ImageUtil.setAttribute(this.redoButton_, 'hidden', !canRedo);
+};
+
+ImageEditor.prototype.getCanvas = function() {
+ return this.getImageView().getCanvas();
+};
/**
* Window resize handler.
*/
ImageEditor.prototype.resizeFrame = function() {
- this.getBuffer().resizeScreen(
- this.canvasWrapper_.clientWidth, this.canvasWrapper_.clientHeight, true);
+ this.getViewport().sizeByFrameAndFit(this.container_);
+ this.getViewport().repaint();
};
/**
* @return {ImageBuffer}
*/
-ImageEditor.prototype.getBuffer = function () {
- return this.buffer_;
-};
+ImageEditor.prototype.getBuffer = function () { return this.buffer_ };
/**
- * Destroys the UI and calls the close callback.
+ * @return {ImageView}
*/
-ImageEditor.prototype.close = function() {
- this.container_.innerHTML = '';
- this.closeCallback_();
-};
+ImageEditor.prototype.getImageView = function () { return this.imageView_ };
+
+/**
+ * @return {Viewport}
+ */
+ImageEditor.prototype.getViewport = function () { return this.viewport_ };
+
+/**
+ * @return {ImageEditor.Prompt}
+ */
+ImageEditor.prototype.getPrompt = function () { return this.prompt_ };
ImageEditor.prototype.onOptionsChange = function(options) {
ImageUtil.trace.resetTimer('update');
- if (this.currentMode_)
+ if (this.currentMode_) {
this.currentMode_.update(options);
+ }
ImageUtil.trace.reportTimer('update');
};
@@ -153,30 +185,20 @@ ImageEditor.Mode = function(name, displayName) {
ImageEditor.Mode.prototype = {__proto__: ImageBuffer.Overlay.prototype };
-ImageEditor.Mode.prototype.getBuffer = function() {
- return this.buffer_;
-};
-
-ImageEditor.Mode.prototype.repaint = function(opt_fromOverlay) {
- return this.buffer_.repaint(opt_fromOverlay);
-};
-
-ImageEditor.Mode.prototype.getViewport = function() {
- return this.viewport_;
-};
+ImageEditor.Mode.prototype.getViewport = function() { return this.viewport_ };
-ImageEditor.Mode.prototype.getContent = function() {
- return this.content_;
-};
+ImageEditor.Mode.prototype.getImageView = function() { return this.imageView_ };
/**
* Called before entering the mode.
*/
-ImageEditor.Mode.prototype.setUp = function(buffer) {
- this.buffer_ = buffer;
- this.viewport_ = buffer.getViewport();
- this.content_ = buffer.getContent();
- this.buffer_.addOverlay(this);
+ImageEditor.Mode.prototype.setUp = function(editor) {
+ this.editor_ = editor;
+ this.viewport_ = editor.getViewport();
+ this.imageView_ = editor.getImageView();
+ this.editor_.getBuffer().addOverlay(this);
+
+ this.updated_ = false;
};
/**
@@ -188,7 +210,7 @@ ImageEditor.Mode.prototype.createTools = function(toolbar) {};
* Called before exiting the mode.
*/
ImageEditor.Mode.prototype.cleanUpUI = function() {
- this.buffer_.removeOverlay(this);
+ this.editor_.getBuffer().removeOverlay(this);
};
/**
@@ -199,93 +221,140 @@ ImageEditor.Mode.prototype.cleanUpCaches = function() {};
/**
* Called when any of the controls changed its value.
*/
-ImageEditor.Mode.prototype.update = function(options) {};
+ImageEditor.Mode.prototype.update = function(options) {
+ this.markUpdated();
+};
-/**
- * The user clicked 'OK'. Finalize the change.
- */
-ImageEditor.Mode.prototype.commit = function() {};
+ImageEditor.Mode.prototype.markUpdated = function() {
+ this.editor_.getPrompt().hide();
+ this.updated_ = true;
+};
/**
- * The user clicker 'Reset' or 'Cancel'. Undo the change.
+ * One-click editor tool, requires no interaction, just executes the command.
+ * @param {string} name
+ * @param {Command} command
+ * @constructor
*/
-ImageEditor.Mode.prototype.rollback = function() {};
-
+ImageEditor.Mode.OneClick = function(name, command) {
+ ImageEditor.Mode.call(this, name);
+ this.instant = true;
+ this.command_ = command;
+};
-ImageEditor.Mode.constructors = [];
+ImageEditor.Mode.OneClick.prototype = {__proto__: ImageEditor.Mode.prototype};
-ImageEditor.Mode.register = function(constructor) {
- ImageEditor.Mode.constructors.push(constructor);
+ImageEditor.Mode.OneClick.prototype.getCommand = function() {
+ return this.command_;
};
-ImageEditor.prototype.createToolButtons = function() {
+
+ImageEditor.prototype.createToolButtons = function(modes) {
this.mainToolbar_.clear();
- for (var i = 0; i != this.tools_.length; i++) {
- var mode = new this.tools_[i];
+ for (var i = 0; i != modes.length; i++) {
+ var mode = modes[i];
this.mainToolbar_.addButton(this.getDisplayString(mode.name),
- this.onModeEnter.bind(this, mode), mode.name);
+ this.enterMode.bind(this, mode), mode.name);
}
- this.mainToolbar_.addButton(this.getDisplayString('undo'),
- this.reload.bind(this), 'undo');
+ this.undoButton_ = this.mainToolbar_.addButton(this.getDisplayString('undo'),
+ this.undo.bind(this), 'undo');
+ this.redoButton_ = this.mainToolbar_.addButton(this.getDisplayString('redo'),
+ this.redo.bind(this), 'redo');
};
+ImageEditor.prototype.isModal = function() { return !!this.currentMode_ };
+
/**
* The user clicked on the mode button.
*/
-ImageEditor.prototype.onModeEnter = function(mode, event) {
- var previousMode = this.currentMode_;
- this.onModeLeave(false);
+ImageEditor.prototype.enterMode = function(mode, event) {
+ if (this.commandQueue_.isBusy()) return;
+
+ if (this.currentMode_ == mode) {
+ // Currently active editor tool clicked, commit if modified.
+ this.leaveMode(this.currentMode_.updated_);
+ return;
+ }
- if (previousMode == mode) return;
+ this.leaveModeGently();
this.currentTool_ = event.target;
- this.currentTool_.setAttribute('pressed', 'pressed');
+
+ ImageUtil.setAttribute(this.currentTool_, 'pressed', true);
this.currentMode_ = mode;
- this.currentMode_.setUp(this.getBuffer());
+ this.currentMode_.setUp(this);
- if (this.currentMode_.oneClick) {
- this.currentMode_.oneClick();
- this.onModeLeave(true);
+ if (this.currentMode_.instant) { // Instant tool.
+ this.leaveMode(true);
return;
}
this.modeToolbar_.clear();
this.currentMode_.createTools(this.modeToolbar_);
- this.modeToolbar_.addButton(this.getDisplayString('OK'),
- this.onModeLeave.bind(this, true), 'mode', 'ok'),
- this.modeToolbar_.addButton(this.getDisplayString('Cancel'),
- this.onModeLeave.bind(this, false), 'mode', 'cancel');
-
- this.modeToolbar_.show(this.currentTool_);
-
- this.getBuffer().repaint();
+ this.modeToolbar_.show(true);
+ this.getPrompt().show('enter-when-done');
};
/**
* The user clicked on 'OK' or 'Cancel' or on a different mode button.
*/
-ImageEditor.prototype.onModeLeave = function(save) {
+ImageEditor.prototype.leaveMode = function(commit) {
if (!this.currentMode_) return;
- this.modeToolbar_.hide();
+ if (!this.currentMode_.instant) {
+ this.getPrompt().hide();
+ }
+
+ this.modeToolbar_.show(false);
this.currentMode_.cleanUpUI();
- if (save) {
- this.currentMode_.commit();
- this.modified_ = true;
- } else {
- this.currentMode_.rollback();
+ if (commit) {
+ var self = this;
+ this.commandQueue_.execute(this.currentMode_.getCommand());
+ this.updateUndoRedo();
}
this.currentMode_.cleanUpCaches();
this.currentMode_ = null;
- this.currentTool_.removeAttribute('pressed');
+ ImageUtil.setAttribute(this.currentTool_, 'pressed', false);
this.currentTool_ = null;
+};
- this.getBuffer().repaint();
-
+ImageEditor.prototype.leaveModeGently = function() {
+ this.leaveMode(this.currentMode_ &&
+ this.currentMode_.updated_ &&
+ this.currentMode_.implicitCommit);
+};
+
+ImageEditor.prototype.onKeyDown = function(event) {
+ switch(event.keyIdentifier) {
+ case 'U+001B': // Escape
+ case 'Enter':
+ if (this.isModal()) {
+ this.leaveMode(event.keyIdentifier == 'Enter');
+ return true;
+ }
+ break;
+
+ case 'U+005A': // 'z'
+ if (event.ctrlKey) {
+ if (event.shiftKey) {
+ if (this.commandQueue_.canRedo()) {
+ this.redo();
+ return true;
+ }
+ } else {
+ if (this.commandQueue_.canUndo()) {
+ this.undo();
+ return true;
+ }
+ }
+ }
+ break;
+ }
+ return false;
};
/**
@@ -440,12 +509,12 @@ ImageEditor.ScaleControl.prototype.on1to1Button = function () {
* A helper object for panning the ImageBuffer.
* @constructor
*/
-ImageEditor.MouseControl = function(canvas, buffer) {
- this.canvas_ = canvas;
+ImageEditor.MouseControl = function(container, buffer) {
+ this.container_ = container;
this.buffer_ = buffer;
- canvas.addEventListener('mousedown', this.onMouseDown.bind(this), false);
- canvas.addEventListener('mouseup', this.onMouseUp.bind(this), false);
- canvas.addEventListener('mousemove', this.onMouseMove.bind(this), false);
+ container.addEventListener('mousedown', this.onMouseDown.bind(this), false);
+ container.addEventListener('mouseup', this.onMouseUp.bind(this), false);
+ container.addEventListener('mousemove', this.onMouseMove.bind(this), false);
};
ImageEditor.MouseControl.getPosition = function(e) {
@@ -461,8 +530,7 @@ ImageEditor.MouseControl.prototype.onMouseDown = function(e) {
this.dragHandler_ = this.buffer_.getDragHandler(position.x, position.y);
this.dragHappened_ = false;
- this.canvas_.style.cursor =
- this.buffer_.getCursorStyle(position.x, position.y, !!this.dragHandler_);
+ this.updateCursor_(position);
e.preventDefault();
};
@@ -474,21 +542,39 @@ ImageEditor.MouseControl.prototype.onMouseUp = function(e) {
}
this.dragHandler_ = null;
this.dragHappened_ = false;
+ this.lockMouse_(false);
e.preventDefault();
};
ImageEditor.MouseControl.prototype.onMouseMove = function(e) {
var position = ImageEditor.MouseControl.getPosition(e);
- this.canvas_.style.cursor =
- this.buffer_.getCursorStyle(position.x, position.y, !!this.dragHandler_);
+ if (this.dragHandler_ && !e.which) {
+ // mouseup must have happened while the mouse was outside our window.
+ this.dragHandler_ = null;
+ this.lockMouse_(false);
+ }
+
+ this.updateCursor_(position);
if (this.dragHandler_) {
this.dragHandler_(position.x, position.y);
this.dragHappened_ = true;
+ this.lockMouse_(true);
}
e.preventDefault();
};
+ImageEditor.MouseControl.prototype.lockMouse_ = function(on) {
+ ImageUtil.setAttribute(this.container_.parentNode, 'mousedrag', on);
+};
+
+ImageEditor.MouseControl.prototype.updateCursor_ = function(position) {
+ this.container_.style.cursor =
+ this.container_.parentNode.hasAttribute('locked') ?
+ '' :
+ this.buffer_.getCursorStyle(position.x, position.y, !!this.dragHandler_);
+};
+
/**
* A toolbar for the ImageEditor.
* @constructor
@@ -523,11 +609,10 @@ ImageEditor.Toolbar.prototype.addLabel = function(text) {
};
ImageEditor.Toolbar.prototype.addButton = function(
- text, handler, opt_class1, opt_class2) {
+ text, handler, opt_class1) {
var button = this.create_('div');
button.classList.add('button');
if (opt_class1) button.classList.add(opt_class1);
- if (opt_class2) button.classList.add(opt_class2);
button.textContent = this.getDisplayString(text);
button.addEventListener('click', handler, false);
return this.add(button);
@@ -586,7 +671,7 @@ ImageEditor.Toolbar.prototype.addRange = function(
var label = this.create_('div');
label.textContent = this.getDisplayString(name);
- label.className = 'label';
+ label.className = 'label ' + name;
this.add(label);
this.add(range);
if (opt_showNumeric) this.add(numeric);
@@ -609,19 +694,71 @@ ImageEditor.Toolbar.prototype.reset = function() {
}
};
-ImageEditor.Toolbar.prototype.show = function(parentButton) {
- this.wrapper_.removeAttribute('hidden');
+ImageEditor.Toolbar.prototype.show = function(on) {
+ if (!this.wrapper_.firstChild)
+ return; // Do not show empty toolbar;
+
+ ImageUtil.setAttribute(this.wrapper_, 'hidden', !on);
+};
+
+/** A prompt panel for the editor.
+ *
+ * @param {HTMLElement} container
+ */
+ImageEditor.Prompt = function(container, localizeFunction) {
+ this.container_ = container.parentNode;
+ this.localizeFunction_ = localizeFunction;
+};
- this.wrapper_.style.left = '0';
+ImageEditor.Prompt.prototype.reset = function() {
+ this.cancelTimer();
+ if (this.wrapper_) {
+ this.container_.removeChild(this.wrapper_);
+ this.wrapper_ = null;
+ this.prompt_ = null;
+ }
+};
- var parentRect = parentButton.getBoundingClientRect();
- var wrapperRect = this.wrapper_.getBoundingClientRect();
+ImageEditor.Prompt.prototype.cancelTimer = function() {
+ if (this.timer_) {
+ clearTimeout(this.timer_);
+ this.timer_ = null;
+ }
+};
- // Align the horizontal center of the toolbar with the center of the parent.
- this.wrapper_.style.left =
- (parentRect.left + (parentRect.width - wrapperRect.width) / 2) + 'px';
+ImageEditor.Prompt.prototype.setTimer = function(callback, timeout) {
+ this.cancelTimer();
+ var self = this;
+ this.timer_ = setTimeout(function() {
+ self.timer_ = null;
+ callback();
+ }, timeout);
};
-ImageEditor.Toolbar.prototype.hide = function() {
- this.wrapper_.setAttribute('hidden', 'hidden');
+ImageEditor.Prompt.prototype.show = function(text, timeout) {
+ this.reset();
+
+ var document = this.container_.ownerDocument;
+ this.wrapper_ = document.createElement('div');
+ this.wrapper_.className = 'prompt-wrapper';
+ this.container_.appendChild(this.wrapper_);
+
+ this.prompt_ = document.createElement('div');
+ this.prompt_.className = 'prompt';
+ this.wrapper_.appendChild(this.prompt_);
+
+ this.prompt_.textContent = this.localizeFunction_(text);
+
+ setTimeout(
+ this.prompt_.setAttribute.bind(this.prompt_, 'state', 'fadein'), 0);
+
+ if (timeout)
+ this.setTimer(this.hide.bind(this), timeout);
};
+
+ImageEditor.Prompt.prototype.hide = function() {
+ if (!this.prompt_) return;
+ this.prompt_.setAttribute('state', 'fadeout');
+ // Allow some time for the animation to play out.
+ this.setTimer(this.reset.bind(this), 500);
+}; \ No newline at end of file
diff --git a/chrome/browser/resources/file_manager/js/image_editor/image_encoder.js b/chrome/browser/resources/file_manager/js/image_editor/image_encoder.js
index 5a43509..319fbff 100644
--- a/chrome/browser/resources/file_manager/js/image_editor/image_encoder.js
+++ b/chrome/browser/resources/file_manager/js/image_editor/image_encoder.js
@@ -24,8 +24,9 @@ ImageEncoder.registerMetadataEncoder = function(constructor, mimeType) {
* @return {ImageEncoder.MetadataEncoder}
*/
ImageEncoder.createMetadataEncoder = function(metadata) {
- var constructor = ImageEncoder.metadataEncoders[metadata.mimeType];
- return constructor ? new constructor(ImageUtil.deepCopy(metadata)) : null;
+ var constructor = ImageEncoder.metadataEncoders[metadata.mimeType] ||
+ ImageEncoder.MetadataEncoder;
+ return new constructor(metadata);
};
@@ -41,7 +42,7 @@ ImageEncoder.encodeMetadata = function(metadata, canvas, quality) {
var encoder = ImageEncoder.createMetadataEncoder(metadata);
if (encoder) {
encoder.setImageData(canvas);
- ImageEncoder.encodeThumbnail(canvas, encoder, quality);
+ ImageEncoder.encodeThumbnail(canvas, encoder, quality || 1);
}
return encoder;
};
@@ -55,10 +56,11 @@ ImageEncoder.encodeMetadata = function(metadata, canvas, quality) {
* @return {Blob}
*/
ImageEncoder.getBlob = function(canvas, metadataEncoder, quality) {
+ var mimeType = metadataEncoder.getMetadata().mimeType;
var blobBuilder = new WebKitBlobBuilder();
- ImageEncoder.buildBlob(blobBuilder, canvas,
- metadataEncoder, metadataEncoder.getMetadata().mimeType, quality);
- return blobBuilder.getBlob();
+ ImageEncoder.buildBlob(
+ blobBuilder, canvas, metadataEncoder, mimeType, quality);
+ return blobBuilder.getBlob(mimeType);
};
/**
@@ -228,7 +230,9 @@ ImageEncoder.MetadataEncoder.prototype.setImageData = function(canvas) {};
* @param {String} dataUrl Data url containing the thumbnail.
*/
ImageEncoder.MetadataEncoder.prototype.
- setThumbnailData = function(canvas, dataUrl) {};
+ setThumbnailData = function(canvas, dataUrl) {
+ this.metadata_.thumbnailURL = dataUrl;
+};
/**
* Return a range where the metadata is (or should be) located.
@@ -243,4 +247,6 @@ ImageEncoder.MetadataEncoder.prototype.
* The return type is optimized for passing to Blob.append.
* @return {ArrayBuffer}
*/
-ImageEncoder.MetadataEncoder.prototype.encode = function() { return null }; \ No newline at end of file
+ImageEncoder.MetadataEncoder.prototype.encode = function() {
+ return new Uint8Array(0).buffer;
+};
diff --git a/chrome/browser/resources/file_manager/js/image_editor/image_transform.js b/chrome/browser/resources/file_manager/js/image_editor/image_transform.js
index 3ae0db5..039114a 100644
--- a/chrome/browser/resources/file_manager/js/image_editor/image_transform.js
+++ b/chrome/browser/resources/file_manager/js/image_editor/image_transform.js
@@ -3,366 +3,105 @@
// found in the LICENSE file.
/**
- * Resize mode.
- */
-
-ImageEditor.Mode.Resize = function() {
- ImageEditor.Mode.call(this, 'resize');
-};
-
-ImageEditor.Mode.Resize.prototype = {__proto__: ImageEditor.Mode.prototype};
-
-// TODO(dgozman): register Mode.Resize in v2.
-
-ImageEditor.Mode.Resize.prototype.createTools = function(toolbar) {
- var canvas = this.getContent().getCanvas();
- this.widthRange_ =
- toolbar.addRange('width', 0, canvas.width, canvas.width * 2);
- this.heightRange_ =
- toolbar.addRange('height', 0, canvas.height, canvas.height * 2);
-};
-
-ImageEditor.Mode.Resize.prototype.commit = function() {
- ImageUtil.trace.resetTimer('transform');
- var newCanvas = this.getContent().copyCanvas(
- this.widthRange_.getValue(), this.heightRange_.getValue());
- ImageUtil.trace.reportTimer('transform');
- this.getContent().setCanvas(newCanvas);
- this.getViewport().fitImage();
-};
-
-/**
- * Rotate mode.
+ * Crop mode.
+ * @constructor
*/
-
-ImageEditor.Mode.Rotate = function() {
- ImageEditor.Mode.call(this, 'rotate');
-};
-
-ImageEditor.Mode.Rotate.prototype = {__proto__: ImageEditor.Mode.prototype};
-
-ImageEditor.Mode.register(ImageEditor.Mode.Rotate);
-
-ImageEditor.Mode.Rotate.prototype.cleanUpCaches = function() {
- this.backup_ = null;
- this.transform_ = null;
-};
-
-ImageEditor.Mode.Rotate.prototype.commit = function() {};
-
-ImageEditor.Mode.Rotate.prototype.rollback = function() {
- if (this.backup_) {
- this.getContent().setCanvas(this.backup_);
- }
-};
-
-ImageEditor.Mode.Rotate.prototype.createTools = function(toolbar) {
- toolbar.addButton("Left", this.modifyTransform.bind(this, 1, 1, 3));
- toolbar.addButton("Right", this.modifyTransform.bind(this, 1, 1, 1));
- toolbar.addButton("Flip V", this.modifyTransform.bind(this, 1, -1, 0));
- toolbar.addButton("Flip H", this.modifyTransform.bind(this, -1, 1, 0));
-
- var srcCanvas = this.getContent().getCanvas();
-
- var width = srcCanvas.width;
- var height = srcCanvas.height;
- var maxTg = Math.min(width / height, height / width);
- var maxTilt = Math.floor(Math.atan(maxTg) * 180 / Math.PI);
- this.tiltRange_ =
- toolbar.addRange('angle', -maxTilt, 0, maxTilt, 10);
-
- this.tiltRange_.
- addEventListener('mousedown', this.onTiltStart.bind(this), false);
- this.tiltRange_.
- addEventListener('mouseup', this.onTiltStop.bind(this), false);
-};
-
-ImageEditor.Mode.Rotate.prototype.getOriginal = function() {
- if (!this.backup_) {
- this.backup_ = this.getContent().getCanvas();
- }
- return this.backup_;
-};
-
-ImageEditor.Mode.Rotate.prototype.getTransform = function() {
- if (!this.transform_) {
- this.transform_ = new ImageEditor.Mode.Rotate.Transform();
- }
- return this.transform_;
-};
-
-ImageEditor.Mode.Rotate.prototype.onTiltStart = function() {
- this.tiltDrag_ = true;
-
- var original = this.getOriginal();
-
- // Downscale the original image to the overview thumbnail size.
- var downScale = ImageBuffer.Overview.MAX_SIZE /
- Math.max(original.width, original.height);
-
- this.preScaledOriginal_ = this.getContent().createBlankCanvas(
- original.width * downScale, original.height * downScale);
- Rect.drawImage(this.preScaledOriginal_.getContext('2d'), original);
-
- // Translate the current offset into the original image coordinate space.
- var viewport = this.getViewport();
- var originalOffset = this.getTransform().transformOffsetToBaseline(
- viewport.getOffsetX(), viewport.getOffsetY());
-
- // Find the part of the original image that is sufficient to pre-render
- // the rotation results.
- var screenClipped = viewport.getScreenClipped();
- var diagonal = viewport.screenToImageSize(
- Math.sqrt(screenClipped.width * screenClipped.width +
- screenClipped.height * screenClipped.height));
-
- var originalBounds = new Rect(original);
-
- var originalPreclipped = new Rect(
- originalBounds.width / 2 - originalOffset.x - diagonal / 2,
- originalBounds.height / 2 - originalOffset.y - diagonal / 2,
- diagonal,
- diagonal).clamp(originalBounds);
-
- // We assume that the scale is not changing during the mouse drag.
- var scale = viewport.getScale();
- this.preClippedOriginal_ = this.getContent().createBlankCanvas(
- originalPreclipped.width * scale, originalPreclipped.height * scale);
-
- Rect.drawImage(this.preClippedOriginal_.getContext('2d'), original, null,
- originalPreclipped);
-
- this.repaint();
-};
-
-ImageEditor.Mode.Rotate.prototype.onTiltStop = function() {
- this.tiltDrag_ = false;
- if (this.preScaledOriginal_) {
- this.preScaledOriginal_ = false;
- this.preClippedOriginal_ = false;
- this.applyTransform();
- } else {
- this.repaint();
- }
-};
-
-ImageEditor.Mode.Rotate.prototype.draw = function(context) {
- if (!this.tiltDrag_) return;
-
- var screenClipped = this.getViewport().getScreenClipped();
-
- if (this.preClippedOriginal_) {
- ImageUtil.trace.resetTimer('preview');
- var transformed = this.getContent().createBlankCanvas(
- screenClipped.width, screenClipped.height);
- this.getTransform().apply(transformed, this.preClippedOriginal_);
- Rect.drawImage(context, transformed, screenClipped);
- ImageUtil.trace.reportTimer('preview');
- }
-
- const STEP = 50;
- context.save();
- context.globalAlpha = 0.4;
- context.strokeStyle = "#C0C0C0";
-
- context.beginPath();
- var top = screenClipped.top + 0.5;
- var left = screenClipped.left + 0.5;
- for(var x = Math.ceil(screenClipped.left / STEP) * STEP;
- x < screenClipped.left + screenClipped.width;
- x += STEP) {
- context.moveTo(x + 0.5, top);
- context.lineTo(x + 0.5, top + screenClipped.height);
- }
- for(var y = Math.ceil(screenClipped.top / STEP) * STEP;
- y < screenClipped.top + screenClipped.height;
- y += STEP) {
- context.moveTo(left, y + 0.5);
- context.lineTo(left + screenClipped.width, y + 0.5);
- }
- context.closePath();
- context.stroke();
-
- context.restore();
-};
-
-ImageEditor.Mode.Rotate.prototype.modifyTransform =
- function(scaleX, scaleY, turn90) {
-
- var transform = this.getTransform();
- var viewport = this.getViewport();
-
- var baselineOffset = transform.transformOffsetToBaseline(
- viewport.getOffsetX(), viewport.getOffsetY());
-
- transform.modify(scaleX, scaleY, turn90, this.tiltRange_.getValue());
-
- var newOffset = transform.transformOffsetFromBaseline(
- baselineOffset.x, baselineOffset.y);
-
- // Ignoring offset clipping makes rotation behave more naturally.
- viewport.setOffset(newOffset.x, newOffset.y, true /*ignore clipping*/);
-
- if (scaleX * scaleY < 0) {
- this.tiltRange_.setValue(transform.tilt);
- }
-
- this.applyTransform();
+ImageEditor.Mode.Crop = function() {
+ ImageEditor.Mode.call(this, 'crop');
};
-ImageEditor.Mode.Rotate.prototype.applyTransform = function() {
- var srcCanvas = this.getOriginal();
-
- var newSize = this.transform_.getTiltedRectSize(
- srcCanvas.width, srcCanvas.height);
-
- var scale = 1;
-
- if (this.preScaledOriginal_) {
- scale = this.preScaledOriginal_.width / srcCanvas.width;
- srcCanvas = this.preScaledOriginal_;
- }
-
- var dstCanvas = this.getContent().createBlankCanvas(
- newSize.width * scale, newSize.height * scale);
- ImageUtil.trace.resetTimer('transform');
- this.transform_.apply(dstCanvas, srcCanvas);
- ImageUtil.trace.reportTimer('transform');
- this.getContent().setCanvas(dstCanvas, newSize.width, newSize.height);
-
- this.repaint();
-};
+ImageEditor.Mode.Crop.prototype = {__proto__: ImageEditor.Mode.prototype};
-ImageEditor.Mode.Rotate.prototype.update = function(values) {
- this.modifyTransform(1, 1, 0);
-};
+ImageEditor.Mode.Crop.prototype.setUp = function() {
+ ImageEditor.Mode.prototype.setUp.apply(this, arguments);
-ImageEditor.Mode.Rotate.Transform = function() {
- this.scaleX = 1;
- this.scaleY = 1;
- this.turn90 = 0;
- this.tilt = 0;
-};
+ this.createDefaultCrop();
-ImageEditor.Mode.Rotate.Transform.prototype.modify =
- function(scaleX, scaleY, turn90, tilt) {
- this.scaleX *= scaleX;
- this.scaleY *= scaleY;
- this.turn90 += turn90;
- this.tilt = (scaleX * scaleY > 0) ? tilt : -tilt;
-};
+ var container = this.getImageView().container_;
+ var doc = container.ownerDocument;
-const DEG_IN_RADIAN = 180 / Math.PI;
+ this.domOverlay_ = doc.createElement('div');
+ this.domOverlay_.className = 'crop-overlay';
+ container.appendChild(this.domOverlay_);
-ImageEditor.Mode.Rotate.Transform.prototype.getAngle = function() {
- return (this.turn90 * 90 + this.tilt) / DEG_IN_RADIAN;
-};
+ this.shadowTop_ = doc.createElement('div');
+ this.shadowTop_.className = 'shadow';
+ this.domOverlay_.appendChild(this.shadowTop_);
-ImageEditor.Mode.Rotate.Transform.prototype.transformOffsetFromBaseline =
- function(x, y) {
- var angle = this.getAngle();
- var sin = Math.sin(angle);
- var cos = Math.cos(angle);
+ this.middleBox_ = doc.createElement('div');
+ this.middleBox_.className = 'middle-box';
+ this.domOverlay_.appendChild(this.middleBox_);
- x *= this.scaleX;
- y *= this.scaleY;
+ this.shadowLeft_ = doc.createElement('div');
+ this.shadowLeft_.className = 'shadow';
+ this.middleBox_.appendChild(this.shadowLeft_);
- return {
- x: (x * cos - y * sin),
- y: (x * sin + y * cos)
- };
-};
+ this.cropFrame_ = doc.createElement('div');
+ this.cropFrame_.className = 'crop-frame';
+ this.middleBox_.appendChild(this.cropFrame_);
-ImageEditor.Mode.Rotate.Transform.prototype.transformOffsetToBaseline =
- function(x, y) {
- var angle = -this.getAngle();
- var sin = Math.sin(angle);
- var cos = Math.cos(angle);
+ this.shadowRight_ = doc.createElement('div');
+ this.shadowRight_.className = 'shadow';
+ this.middleBox_.appendChild(this.shadowRight_);
- return {
- x: (x * cos - y * sin) / this.scaleX,
- y: (x * sin + y * cos) / this.scaleY
- };
-};
+ this.shadowBottom_ = doc.createElement('div');
+ this.shadowBottom_.className = 'shadow';
+ this.domOverlay_.appendChild(this.shadowBottom_);
-ImageEditor.Mode.Rotate.Transform.prototype.getTiltedRectSize =
- function(width, height) {
- if (this.turn90 & 1) {
- var temp = width;
- width = height;
- height = temp;
+ var cropFrame = this.cropFrame_;
+ function addCropFrame(className) {
+ var div = doc.createElement('div');
+ div.className = className;
+ cropFrame.appendChild(div);
}
- var angle = Math.abs(this.tilt) / DEG_IN_RADIAN;
+ addCropFrame('left top corner');
+ addCropFrame('top horizontal');
+ addCropFrame('right top corner');
+ addCropFrame('left vertical');
+ addCropFrame('right vertical');
+ addCropFrame('left bottom corner');
+ addCropFrame('bottom horizontal');
+ addCropFrame('right bottom corner');
- var sin = Math.sin(angle);
- var cos = Math.cos(angle);
- var denom = cos * cos - sin * sin;
-
- return {
- width: Math.floor((width * cos - height * sin) / denom),
- height: Math.floor((height * cos - width * sin) / denom)
- }
+ this.positionDOM();
};
-ImageEditor.Mode.Rotate.Transform.prototype.apply = function(
- dstCanvas, srcCanvas) {
- ImageUtil.drawImageTransformed(
- dstCanvas, srcCanvas, this.scaleX, this.scaleY, this.getAngle());
-};
+ImageEditor.Mode.Crop.prototype.positionDOM = function() {
+ var screenClipped = this.viewport_.getScreenClipped();
-/**
- * Instant rotate.
- * @constructor
- */
-ImageEditor.Mode.InstantRotate = function() {
- ImageEditor.Mode.Rotate.apply(this, arguments);
-};
+ this.domOverlay_.style.left = screenClipped.left + 'px';
+ this.domOverlay_.style.top = screenClipped.top + 'px';
+ this.domOverlay_.style.width = screenClipped.width + 'px';
+ this.domOverlay_.style.height = screenClipped.height + 'px';
-ImageEditor.Mode.InstantRotate.prototype =
- {__proto__: ImageEditor.Mode.Rotate.prototype};
+ this.shadowLeft_.style.width =
+ this.viewport_.imageToScreenX(this.cropRect_.getLeft())
+ - screenClipped.left + 'px';
-ImageEditor.Mode.InstantRotate.prototype.oneClick = function() {
- this.tiltRange_ = {
- getValue: function() { return 0 },
- setValue: function() {}
- };
- this.modifyTransform(1, 1, 1);
- this.getBuffer().getViewport().fitImage();
-};
+ this.shadowTop_.style.height =
+ this.viewport_.imageToScreenY(this.cropRect_.getTop())
+ - screenClipped.top + 'px';
-/**
- * Crop mode.
- */
+ this.shadowRight_.style.width = screenClipped.left + screenClipped.width -
+ this.viewport_.imageToScreenX(this.cropRect_.getRight()) + 'px';
-ImageEditor.Mode.Crop = function() {
- ImageEditor.Mode.call(this, 'crop');
+ this.shadowBottom_.style.height = screenClipped.top + screenClipped.height -
+ this.viewport_.imageToScreenY(this.cropRect_.getBottom()) + 'px';
};
-ImageEditor.Mode.Crop.prototype = {__proto__: ImageEditor.Mode.prototype};
-
-ImageEditor.Mode.register(ImageEditor.Mode.Crop);
-
-ImageEditor.Mode.Crop.prototype.createTools = function() {
- this.createDefaultCrop();
+ImageEditor.Mode.Crop.prototype.cleanUpUI = function() {
+ ImageEditor.Mode.prototype.cleanUpUI.apply(this, arguments);
+ this.domOverlay_.parentNode.removeChild(this.domOverlay_);
+ this.domOverlay_ = null;
};
-ImageEditor.Mode.Crop.GRAB_RADIUS = 5;
+ImageEditor.Mode.Crop.GRAB_RADIUS = 6;
-ImageEditor.Mode.Crop.prototype.commit = function() {
+ImageEditor.Mode.Crop.prototype.getCommand = function() {
var cropImageRect = this.cropRect_.getRect();
-
- var newCanvas = this.getContent().
- createBlankCanvas(cropImageRect.width, cropImageRect.height);
-
- var newContext = newCanvas.getContext("2d");
- ImageUtil.trace.resetTimer('transform');
- Rect.drawImage(newContext, this.getContent().getCanvas(),
- new Rect(newCanvas), cropImageRect);
- ImageUtil.trace.reportTimer('transform');
-
- this.getContent().setCanvas(newCanvas);
- this.getViewport().fitImage();
+ var cropScreenRect = this.viewport_.imageToScreenRect(cropImageRect);
+ return new Command.Crop(cropImageRect, cropScreenRect);
};
ImageEditor.Mode.Crop.prototype.createDefaultCrop = function() {
@@ -373,49 +112,6 @@ ImageEditor.Mode.Crop.prototype.createDefaultCrop = function() {
rect, this.getViewport(), ImageEditor.Mode.Crop.GRAB_RADIUS);
};
-ImageEditor.Mode.Crop.prototype.draw = function(context) {
- var R = ImageEditor.Mode.Crop.GRAB_RADIUS;
-
- var inner = this.getViewport().imageToScreenRect(this.cropRect_.getRect());
- var outer = this.getViewport().getScreenClipped();
-
- var inner_bottom = inner.top + inner.height;
- var inner_right = inner.left + inner.width;
-
- context.globalAlpha = 0.25;
- context.fillStyle = '#000000';
- Rect.fillBetween(context, inner, outer);
-
- context.fillStyle = '#FFFFFF';
- context.beginPath();
- context.moveTo(inner.left, inner.top);
- context.arc(inner.left, inner.top, R, 0, Math.PI * 2);
- context.moveTo(inner.left, inner_bottom);
- context.arc(inner.left, inner_bottom, R, 0, Math.PI * 2);
- context.moveTo(inner_right, inner.top);
- context.arc(inner_right, inner.top, R, 0, Math.PI * 2);
- context.moveTo(inner_right, inner_bottom);
- context.arc(inner_right, inner_bottom, R, 0, Math.PI * 2);
- context.closePath();
- context.fill();
-
- context.globalAlpha = 0.5;
- context.strokeStyle = '#FFFFFF';
-
- context.beginPath();
- context.closePath();
- for (var i = 0; i <= 3; i++) {
- var y = inner.top - 0.5 + Math.round((inner.height + 1) * i / 3);
- context.moveTo(inner.left, y);
- context.lineTo(inner.left + inner.width, y);
-
- var x = inner.left - 0.5 + Math.round((inner.width + 1) * i / 3);
- context.moveTo(x, inner.top);
- context.lineTo(x, inner.top + inner.height);
- }
- context.stroke();
-};
-
ImageEditor.Mode.Crop.prototype.getCursorStyle = function(x, y, mouseDown) {
return this.cropRect_.getCursorStyle(x, y, mouseDown);
};
@@ -427,7 +123,8 @@ ImageEditor.Mode.Crop.prototype.getDragHandler = function(x, y) {
var self = this;
return function(x, y) {
cropDragHandler(x, y);
- self.repaint();
+ self.markUpdated();
+ self.positionDOM();
};
};
@@ -470,6 +167,22 @@ DraggableRect.TOP = 'top';
DraggableRect.BOTTOM = 'bottom';
DraggableRect.NONE = 'none';
+DraggableRect.prototype.getLeft = function () {
+ return this.bounds_[DraggableRect.LEFT];
+};
+
+DraggableRect.prototype.getRight = function() {
+ return this.bounds_[DraggableRect.RIGHT];
+};
+
+DraggableRect.prototype.getTop = function () {
+ return this.bounds_[DraggableRect.TOP];
+};
+
+DraggableRect.prototype.getBottom = function() {
+ return this.bounds_[DraggableRect.BOTTOM];
+};
+
DraggableRect.prototype.getRect = function() { return new Rect(this.bounds_) };
DraggableRect.prototype.getDragMode = function(x, y) {
diff --git a/chrome/browser/resources/file_manager/js/image_editor/image_util.js b/chrome/browser/resources/file_manager/js/image_editor/image_util.js
index 4274f28..b37bbd0 100644
--- a/chrome/browser/resources/file_manager/js/image_editor/image_util.js
+++ b/chrome/browser/resources/file_manager/js/image_editor/image_util.js
@@ -182,6 +182,10 @@ Rect.prototype.clamp = function(bounds) {
return rect;
};
+Rect.prototype.toString = function() {
+ return '(' + this.left + ',' + this.top + '):' +
+ '(' + (this.left + this.width) + ',' + (this.top + this.height) + ')';
+};
/*
* Useful shortcuts for drawing (static functions).
*/
@@ -275,7 +279,7 @@ ImageUtil.drawImageTransformed = function(dst, src, scaleX, scaleY, angle) {
ImageUtil.deepCopy = function(obj) {
- if (typeof obj != 'object')
+ if (obj == null || typeof obj != 'object')
return obj; // Copy built-in types as is.
var res;
@@ -294,3 +298,89 @@ ImageUtil.deepCopy = function(obj) {
}
return res;
};
+
+ImageUtil.setAttribute = function(element, attribute, on) {
+ if (on)
+ element.setAttribute(attribute, attribute);
+ else
+ element.removeAttribute(attribute);
+};
+
+/**
+ * Load image into a canvas taking the transform into account.
+ *
+ * The source image is copied to the canvas stripe-by-stripe to avoid
+ * freezing up the UI.
+ *
+ * @param {HTMLCanvasElement} canvas
+ * @param {string|HTMLImageElement|HTMLCanvasElement} source
+ * @param {{scaleX: number, scaleY: number, rotate90: number}} transform
+ * @param {number} delay
+ * @param {function} callback
+ * @return {function()} Function to call to cancel the load.
+ */
+ImageUtil.loadImageAsync = function(
+ canvas, source, transform, delay, callback) {
+ var image;
+ var timeout = setTimeout(resolveURL, delay);
+
+ function resolveURL() {
+ timeout = null;
+ if (typeof source == 'string') {
+ image = new Image();
+ image.onload = function(e) { image = null; loadImage(e.target); };
+ image.src = source;
+ } else {
+ loadImage(source);
+ }
+ }
+
+ function loadImage(image) {
+ transform = transform || { scaleX: 1, scaleY: 1, rotate90: 0};
+
+ if (transform.rotate90 & 1) { // Rotated +/-90deg, swap the dimensions.
+ canvas.width = image.height;
+ canvas.height = image.width;
+ } else {
+ canvas.width = image.width;
+ canvas.height = image.height;
+ }
+
+ ImageUtil.trace.resetTimer('load-draw');
+
+ var context = canvas.getContext('2d');
+ context.save();
+ context.translate(context.canvas.width / 2, context.canvas.height / 2);
+ context.rotate(transform.rotate90 * Math.PI/2);
+ context.scale(transform.scaleX, transform.scaleY);
+
+ var stripCount = Math.ceil (image.width * image.height / ( 1 << 20));
+ var to = 0;
+ var step = Math.max(16, Math.ceil(image.height / stripCount)) & 0xFFFFF0;
+
+ function copyStrip() {
+ var from = to;
+ to = Math.min (from + step, image.height);
+
+ context.drawImage(image,
+ 0, from, image.width, to - from,
+ - image.width/2, from - image.height/2, image.width, to - from);
+
+ if (to == image.height) {
+ context.restore();
+ timeout = null;
+ ImageUtil.trace.reportTimer('load-draw');
+ callback();
+ } else {
+ timeout = setTimeout(copyStrip, 0);
+ }
+ }
+
+ copyStrip();
+ }
+
+ return function () {
+ if (image) image.onload = function(){};
+ if (timeout) clearTimeout(timeout);
+ };
+};
diff --git a/chrome/browser/resources/file_manager/js/image_editor/image_view.js b/chrome/browser/resources/file_manager/js/image_editor/image_view.js
new file mode 100644
index 0000000..0509cbe
--- /dev/null
+++ b/chrome/browser/resources/file_manager/js/image_editor/image_view.js
@@ -0,0 +1,324 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * The overlay displaying the image.
+ */
+function ImageView(container, viewport) {
+ this.container_ = container;
+ this.viewport_ = viewport;
+ this.document_ = container.ownerDocument;
+ this.contentGeneration_ = 0;
+ this.displayedContentGeneration_ = 0;
+ this.displayedViewportGeneration_ = 0;
+}
+
+ImageView.ANIMATION_DURATION = 180;
+ImageView.ANIMATION_WAIT_INTERVAL = ImageView.ANIMATION_DURATION + 100;
+ImageView.FAST_SCROLL_INTERVAL = 300;
+
+ImageView.prototype = {__proto__: ImageBuffer.Overlay.prototype};
+
+// Draw below overlays with the default zIndex.
+ImageView.prototype.getZIndex = function() { return -1 };
+
+ImageView.prototype.draw = function() {
+ var forceRepaint = false;
+
+ var screenClipped = this.viewport_.getScreenClipped();
+
+ if (this.displayedViewportGeneration_ !=
+ this.viewport_.getCacheGeneration()) {
+ this.displayedViewportGeneration_ = this.viewport_.getCacheGeneration();
+
+ if (this.screenCanvas_.width != screenClipped.width)
+ this.screenCanvas_.width = screenClipped.width;
+
+ if (this.screenCanvas_.height != screenClipped.height)
+ this.screenCanvas_.height = screenClipped.height;
+
+ this.screenCanvas_.style.left = screenClipped.left + 'px';
+ this.screenCanvas_.style.top = screenClipped.top + 'px';
+
+ forceRepaint = true;
+ }
+
+ if (forceRepaint ||
+ this.displayedContentGeneration_ != this.contentGeneration_) {
+ this.displayedContentGeneration_ = this.contentGeneration_;
+
+ ImageUtil.trace.resetTimer('paint');
+ this.paintScreenRect(
+ screenClipped, this.contentCanvas_, this.viewport_.getImageClipped());
+ ImageUtil.trace.reportTimer('paint');
+ }
+};
+
+ImageView.prototype.getCursorStyle = function (x, y, mouseDown) {
+ // Indicate that the image is draggable.
+ if (this.viewport_.isClipped() &&
+ this.viewport_.getScreenClipped().inside(x, y))
+ return 'move';
+
+ return null;
+};
+
+ImageView.prototype.getDragHandler = function (x, y) {
+ var cursor = this.getCursorStyle(x, y);
+ if (cursor == 'move') {
+ // Return the handler that drags the entire image.
+ return this.viewport_.createOffsetSetter(x, y);
+ }
+
+ return null;
+};
+
+ImageView.prototype.getCacheGeneration = function() {
+ return this.contentGeneration_;
+};
+
+ImageView.prototype.invalidateCaches = function() {
+ this.contentGeneration_++;
+};
+
+ImageView.prototype.getCanvas = function() { return this.contentCanvas_ };
+
+ImageView.prototype.paintScreenRect = function (screenRect, canvas, imageRect) {
+ // Map screen canvas (0,0) to (screenClipped.left, screenClipped.top)
+ var screenClipped = this.viewport_.getScreenClipped();
+ screenRect = screenRect.shift(-screenClipped.left, -screenClipped.top);
+
+ // The source canvas may have different physical size than the image size
+ // set at the viewport. Adjust imageRect accordingly.
+ var bounds = this.viewport_.getImageBounds();
+ var scaleX = canvas.width / bounds.width;
+ var scaleY = canvas.height / bounds.height;
+ imageRect = new Rect(imageRect.left * scaleX, imageRect.top * scaleY,
+ imageRect.width * scaleX, imageRect.height * scaleY);
+ Rect.drawImage(
+ this.screenCanvas_.getContext("2d"), canvas, screenRect, imageRect);
+};
+
+/**
+ * @return {ImageData} A new ImageData object with a copy of the content.
+ */
+ImageView.prototype.copyScreenImageData = function () {
+ return this.screenCanvas_.getContext("2d").getImageData(
+ 0, 0, this.screenCanvas_.width, this.screenCanvas_.height);
+};
+
+ImageView.prototype.isLoading = function() {
+ return this.cancelThumbnailLoad_ || this.cancelImageLoad_;
+};
+
+ImageView.prototype.cancelLoad = function() {
+ if (this.cancelThumbnailLoad_) {
+ this.cancelThumbnailLoad_();
+ this.cancelThumbnailLoad_ = null;
+ }
+ if (this.cancelImageLoad_) {
+ this.cancelImageLoad_();
+ this.cancelImageLoad_ = null;
+ }
+};
+
+/**
+ * Load and display a new image.
+ *
+ * Loads the thumbnail first, then replaces it with the main image.
+ * Takes into account the image orientation encoded in the metadata.
+ *
+ * @param {string|HTMLCanvasElement|HTMLImageElement} source
+ * @param {Object} metadata
+ * @param {Object} slide Slide-in animation direction.
+ * @param {function} opt_callback
+ */
+ImageView.prototype.load = function(
+ source, metadata, slide, opt_callback) {
+
+ metadata = metadata|| {};
+
+ this.cancelLoad();
+
+ ImageUtil.trace.resetTimer('load');
+ var canvas = this.container_.ownerDocument.createElement('canvas');
+
+ var self = this;
+
+ if (metadata.thumbnailURL) {
+ this.cancelImageLoad_ = ImageUtil.loadImageAsync(
+ canvas,
+ metadata.thumbnailURL,
+ metadata.thumbnailTransform,
+ 0, /* no delay */
+ displayThumbnail);
+ } else {
+ loadMainImage(0);
+ }
+
+ function displayThumbnail() {
+ self.cancelThumbnailLoad_ = null;
+
+ // The thumbnail may have different aspect ratio than the main image.
+ // Force the main image proportions to avoid flicker.
+ var time = Date.now();
+
+ var mainImageLoadDelay = ImageView.ANIMATION_DURATION;
+
+ // Do not do slide-in animation when scrolling very fast.
+ if (self.lastLoadTime_ &&
+ (time - self.lastLoadTime_) < ImageView.FAST_SCROLL_INTERVAL) {
+ slide = 0;
+ }
+ self.lastLoadTime_ = time;
+
+ self.replace(canvas, slide, metadata.width, metadata.height);
+ if (!slide) mainImageLoadDelay = 0;
+ slide = 0;
+ loadMainImage(mainImageLoadDelay);
+ }
+
+ function loadMainImage(delay) {
+ self.cancelImageLoad_ = ImageUtil.loadImageAsync(
+ canvas, source, metadata.imageTransform, delay, displayMainImage);
+ }
+
+ function displayMainImage() {
+ self.cancelImageLoad_ = null;
+ self.replace(canvas, slide);
+ ImageUtil.trace.reportTimer('load');
+ if (opt_callback) opt_callback();
+ }
+};
+
+ImageView.prototype.replaceContent_ = function(
+ canvas, opt_reuseScreenCanvas, opt_width, opt_height) {
+ if (!opt_reuseScreenCanvas || !this.screenCanvas_) {
+ this.screenCanvas_ = this.document_.createElement('canvas');
+ this.screenCanvas_.style.webkitTransitionDuration =
+ ImageView.ANIMATION_DURATION + 'ms';
+ }
+
+ this.contentCanvas_ = canvas;
+ this.invalidateCaches();
+ this.viewport_.setImageSize(
+ opt_width || this.contentCanvas_.width,
+ opt_height || this.contentCanvas_.height);
+ this.viewport_.fitImage();
+ this.viewport_.update();
+ this.draw();
+
+ if (opt_reuseScreenCanvas && !this.screenCanvas_.parentNode) {
+ this.container_.appendChild(this.screenCanvas_);
+ }
+};
+
+/**
+ * Replace the displayed image, possibly with slide-in animation.
+ *
+ * @param {HTMLCanvasElement} canvas
+ * @param {number} opt_slide Slide-in animation direction.
+ * <0 for right-to-left, > 0 for left-to-right, 0 for no animation.
+ */
+ImageView.prototype.replace = function(
+ canvas, opt_slide, opt_width, opt_height) {
+ var oldScreenCanvas = this.screenCanvas_;
+
+ this.replaceContent_(canvas, !opt_slide, opt_width, opt_height);
+ if (!opt_slide) return;
+
+ var newScreenCanvas = this.screenCanvas_;
+
+ function numToSlideAttr(num) {
+ return num < 0 ? 'left' : num > 0 ? 'right' : 'center';
+ }
+
+ newScreenCanvas.setAttribute('fade', numToSlideAttr(opt_slide));
+ this.container_.appendChild(newScreenCanvas);
+
+ setTimeout(function() {
+ newScreenCanvas.removeAttribute('fade');
+ if (oldScreenCanvas) {
+ oldScreenCanvas.setAttribute('fade', numToSlideAttr(-opt_slide));
+ setTimeout(function() {
+ oldScreenCanvas.parentNode.removeChild(oldScreenCanvas);
+ }, ImageView.ANIMATION_WAIT_INTERVAL);
+ }
+ }, 0);
+};
+
+ImageView.makeTransform = function(rect1, rect2, scale, rotate90) {
+ var shiftX = (rect1.left + rect1.width / 2) - (rect2.left + rect2.width / 2);
+ var shiftY = (rect1.top + rect1.height / 2) - (rect2.top + rect2.height / 2);
+
+ return 'rotate(' + (rotate90 || 0) * 90 + 'deg) ' +
+ 'translate(' + shiftX + 'px,' + shiftY + 'px)' +
+ 'scaleX(' + scale + ') ' +
+ 'scaleY(' + scale + ')';
+};
+
+/**
+ * Hide the old image instantly, animate the new image to visualize
+ * cropping and/or rotation.
+ */
+ImageView.prototype.replaceAndAnimate = function(canvas, cropRect, rotate90) {
+ cropRect = cropRect || this.viewport_.getScreenClipped();
+ var oldScale = this.viewport_.getScale();
+
+ var oldScreenCanvas = this.screenCanvas_;
+ this.replaceContent_(canvas);
+ var newScreenCanvas = this.screenCanvas_;
+
+ // Display the new canvas, initially transformed.
+
+ // Transform instantly.
+ var duration = newScreenCanvas.style.webkitTransitionDuration;
+ newScreenCanvas.style.webkitTransitionDuration = '0ms';
+
+ newScreenCanvas.style.webkitTransform = ImageView.makeTransform(
+ cropRect,
+ this.viewport_.getScreenClipped(),
+ oldScale / this.viewport_.getScale(),
+ -rotate90);
+
+ oldScreenCanvas.parentNode.appendChild(newScreenCanvas);
+ oldScreenCanvas.parentNode.removeChild(oldScreenCanvas);
+
+ // Let the layout fire.
+ setTimeout(function() {
+ // Animated back to non-transformed state.
+ newScreenCanvas.style.webkitTransitionDuration = duration;
+ newScreenCanvas.style.webkitTransform = '';
+ }, 0);
+};
+
+/**
+ * Shrink the given current image to the given crop rectangle while fading in
+ * the new image.
+ */
+ImageView.prototype.animateAndReplace = function(canvas, cropRect) {
+ var fullRect = this.viewport_.getScreenClipped();
+ var oldScale = this.viewport_.getScale();
+
+ var oldScreenCanvas = this.screenCanvas_;
+ this.replaceContent_(canvas);
+ var newCanvas = this.screenCanvas_;
+
+ newCanvas.setAttribute('fade', 'center');
+ oldScreenCanvas.parentNode.insertBefore(newCanvas, oldScreenCanvas);
+
+ // Animate to the transformed state.
+
+ oldScreenCanvas.style.webkitTransform = ImageView.makeTransform(
+ cropRect,
+ fullRect,
+ this.viewport_.getScale() / oldScale,
+ 0);
+
+ setTimeout(function() { newCanvas.removeAttribute('fade') }, 0);
+
+ setTimeout(function() {
+ oldScreenCanvas.parentNode.removeChild(oldScreenCanvas);
+ }, ImageView.ANIMATION_WAIT_INTERVAL);
+};
diff --git a/chrome/browser/resources/file_manager/js/image_editor/viewport.js b/chrome/browser/resources/file_manager/js/image_editor/viewport.js
index d7282db..ff780ff 100644
--- a/chrome/browser/resources/file_manager/js/image_editor/viewport.js
+++ b/chrome/browser/resources/file_manager/js/image_editor/viewport.js
@@ -5,9 +5,7 @@
/**
* Viewport class controls the way the image is displayed (scale, offset etc).
*/
-function Viewport(repaintCallback) {
- this.repaintCallback_ = repaintCallback;
-
+function Viewport() {
this.imageBounds_ = new Rect();
this.screenBounds_ = new Rect();
@@ -18,7 +16,7 @@ function Viewport(repaintCallback) {
this.generation_ = 0;
this.scaleControl_ = null;
-
+ this.repaintCallbacks_ = [];
this.update();
}
@@ -43,6 +41,19 @@ Viewport.prototype.setScreenSize = function(width, height) {
this.invalidateCaches();
};
+Viewport.prototype.sizeByFrame = function(frame) {
+ this.setScreenSize(frame.clientWidth, frame.clientHeight);
+};
+
+Viewport.prototype.sizeByFrameAndFit = function(frame) {
+ var wasFitting = this.getScale() == this.getFittingScale();
+ this.sizeByFrame(frame);
+ var minScale = this.getFittingScale();
+ if (wasFitting || (this.getScale() < minScale)) {
+ this.setScale(minScale, true);
+ }
+};
+
Viewport.prototype.getScale = function() { return this.scale_ };
Viewport.prototype.setScale = function(scale, notify) {
@@ -55,7 +66,7 @@ Viewport.prototype.setScale = function(scale, notify) {
Viewport.prototype.getFittingScale = function() {
var scaleX = this.screenBounds_.width / this.imageBounds_.width;
var scaleY = this.screenBounds_.height / this.imageBounds_.height;
- return Math.min(scaleX, scaleY) * 0.85;
+ return Math.min(scaleX, scaleY);
};
Viewport.prototype.fitImage = function() {
@@ -199,8 +210,8 @@ Viewport.prototype.imageToScreenRect = function(rect) {
return new Rect(
this.imageToScreenX(rect.left),
this.imageToScreenY(rect.top),
- this.imageToScreenSize(rect.width),
- this.imageToScreenSize(rect.height));
+ Math.round(this.imageToScreenSize(rect.width)),
+ Math.round(this.imageToScreenSize(rect.height)));
};
/**
@@ -277,6 +288,12 @@ Viewport.prototype.update = function() {
}
};
+Viewport.prototype.addRepaintCallback = function (callback) {
+ this.repaintCallbacks_.push(callback);
+};
+
Viewport.prototype.repaint = function () {
- if (this.repaintCallback_) this.repaintCallback_();
+ this.update();
+ for (var i = 0; i != this.repaintCallbacks_.length; i++)
+ this.repaintCallbacks_[i]();
};
diff --git a/chrome/browser/resources/file_manager/js/metadata_dispatcher.js b/chrome/browser/resources/file_manager/js/metadata_dispatcher.js
index 65ac1bc..151d58c 100644
--- a/chrome/browser/resources/file_manager/js/metadata_dispatcher.js
+++ b/chrome/browser/resources/file_manager/js/metadata_dispatcher.js
@@ -21,7 +21,7 @@ function MetadataDispatcher() {
importScripts('mpeg_parser.js');
importScripts('id3_parser.js');
- var patterns = [];
+ var patterns = ['blob:']; // We use blob urls in gallery_demo.js
for (var i = 0; i < MetadataDispatcher.parserClasses_.length; i++) {
var parserClass = MetadataDispatcher.parserClasses_[i];
@@ -160,6 +160,43 @@ MetadataDispatcher.prototype.processOneFile = function(fileURL, callback) {
}
];
+ if (fileURL.indexOf('blob:') == 0) {
+ // Blob urls require different steps:
+ steps =
+ [ // Read the blob into an array buffer and get the content type
+ function readBlob() {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', fileURL, true);
+ xhr.responseType = 'arraybuffer';
+ xhr.onload = function(e) {
+ if (xhr.status == 200) {
+ nextStep(xhr.getResponseHeader('Content-Type'), xhr.response);
+ } else {
+ onError('HTTP ' + xhr.status);
+ }
+ };
+ xhr.send();
+ },
+
+ // Step two, find the parser matching the content type.
+ function detectFormat(mimeType, arrayBuffer) {
+ for (var i = 0; i != self.parserInstances_.length; i++) {
+ var parser = self.parserInstances_[i];
+ if (parser.mimeType && mimeType.match(parser.mimeType)) {
+ var blobBuilder = new WebKitBlobBuilder();
+ blobBuilder.append(arrayBuffer);
+ nextStep(blobBuilder.getBlob(), parser);
+ return;
+ }
+ }
+ callback({mimeType: mimeType}); // Unrecognized mime type.
+ },
+
+ // Reuse the last step from the standard sequence.
+ steps[steps.length - 1]
+ ];
+ }
+
nextStep();
};
diff --git a/chrome/browser/resources/file_manager/js/metadata_provider.js b/chrome/browser/resources/file_manager/js/metadata_provider.js
index 1ec8325..8ae303d 100644
--- a/chrome/browser/resources/file_manager/js/metadata_provider.js
+++ b/chrome/browser/resources/file_manager/js/metadata_provider.js
@@ -2,13 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-function MetadataProvider() {
+/**
+ * @param {string} opt_workerPath path to the worker source JS file.
+ */
+function MetadataProvider(opt_workerPath) {
this.cache_ = {};
// Pass all URLs to the metadata reader until we have a correct filter.
this.urlFilter = /.*/;
- this.dispatcher_ = new Worker('js/metadata_dispatcher.js');
+ this.dispatcher_ = new Worker(opt_workerPath || 'js/metadata_dispatcher.js');
this.dispatcher_.onmessage = this.onMessage_.bind(this);
this.dispatcher_.postMessage({verb: 'init'});
// Initialization is not complete until the Worker sends back the
diff --git a/chrome/browser/resources/file_manager/js/mock_chrome.js b/chrome/browser/resources/file_manager/js/mock_chrome.js
index 395bbb7..2afd04d 100644
--- a/chrome/browser/resources/file_manager/js/mock_chrome.js
+++ b/chrome/browser/resources/file_manager/js/mock_chrome.js
@@ -89,8 +89,10 @@ chrome.fileBrowserPrivate = {
regexp: /\.(zip)$/i,
iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAYAAAAmL5yKAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sEEBcOAw9XftIAAADFSURBVCjPrZKxCsIwEIa/FHFwsvYxROjSQXAoqLiIL+xgBtvZ91A6uOnQc2hT0zRqkR4c3P25+/PfJTCwLU6wEpgBWkDXuInDPSwF5r7mJIeNQFTnIiCeONpVdYlLoK9wEUhNg8+B9FDVaZcgCKAovjTXfvPJFwGZtKW60pt8bOGBzfLouemnFY/MAs8wDeEI4NzaybewBu4AysKVgrK0gfe5iB9vjdAUqQ/S1Y/R3IX9Zc1zxc7zxe2/0Iskt7AsG0hhx14W8XV43FgV4gAAAABJRU5ErkJggg=='
},
- { taskId: 'gallery',
- title: 'Gallery',
+ {
+ internal: true,
+ taskId: 'mock|gallery',
+ title: 'View and edit',
regexp: /\.(jpe?g|gif|png|cr2?|tiff)$/i,
iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAYAAAAmL5yKAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sEEBcOAw9XftIAAADFSURBVCjPrZKxCsIwEIa/FHFwsvYxROjSQXAoqLiIL+xgBtvZ91A6uOnQc2hT0zRqkR4c3P25+/PfJTCwLU6wEpgBWkDXuInDPSwF5r7mJIeNQFTnIiCeONpVdYlLoK9wEUhNg8+B9FDVaZcgCKAovjTXfvPJFwGZtKW60pt8bOGBzfLouemnFY/MAs8wDeEI4NzaybewBu4AysKVgrK0gfe5iB9vjdAUqQ/S1Y/R3IX9Zc1zxc7zxe2/0Iskt7AsG0hhx14W8XV43FgV4gAAAABJRU5ErkJggg=='
}
@@ -231,7 +233,7 @@ chrome.fileBrowserPrivate = {
UNMOUNT_ARCHIVE: 'Close archive',
FORMAT_DEVICE: 'Format device',
- GALLERY: 'Gallery',
+ GALLERY: 'View and edit',
CONFIRM_OVERWRITE_FILE: 'A file named "$1" already exists. Do you want to replace it?',
FILE_ALREADY_EXISTS: 'The file named "$1" already exists. Please choose a different name.',