Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(65)

Unified Diff: chrome/browser/resources/file_manager/js/image_editor/image_adjust.js

Issue 7552035: Adding simple filters to ChromeOS Image Editor. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Addressed comments Created 9 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/resources/file_manager/js/image_editor/image_adjust.js
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 c47283f903424acba0c4cc03783d32bc2a36e6a4..cf53f40d07537394ef05e4ba7f1dc5de7d50b8c8 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
@@ -5,81 +5,347 @@
/**
* The base class for simple filters that only modify the image content
* but do not modify the image dimensions.
+ * @constructor
*/
-ImageEditor.Mode.Adjust = function(displayName, filterFunc) {
+ImageEditor.Mode.Adjust = function(displayName) {
ImageEditor.Mode.call(this, displayName);
- this.filterFunc_ = filterFunc;
-}
+ this.viewportGeneration_ = 0;
+};
ImageEditor.Mode.Adjust.prototype = {__proto__: ImageEditor.Mode.prototype};
-ImageEditor.Mode.Adjust.prototype.rollback = function() {
- if (!this.backup_) return; // Did not do anything yet.
- this.getContent().drawImageData(this.backup_, 0, 0);
- this.backup_ = null;
+/*
+ * ImageEditor.Mode methods overridden.
+ */
+
+ImageEditor.Mode.Adjust.prototype.commit = function() {
+ if (!this.filter_) return; // Did not do anything yet.
+
+ // 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();
-};
-ImageEditor.Mode.Adjust.prototype.update = function(options) {
- if (!this.backup_) {
- this.backup_ = this.getContent().copyImageData();
- this.scratch_ = this.getContent().copyImageData();
+ 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;
+
+ self.getBuffer().repaintScreenRect(screenStrip, imageStrip);
}
ImageUtil.trace.resetTimer('filter');
- this.filterFunc_(this.scratch_, this.backup_, options);
- ImageUtil.trace.reportTimer('filter');
- this.getContent().drawImageData(this.scratch_, 0, 0);
+
+ 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.rollback = function() {
+ this.filter_ = null;
+ this.previewImageData_ = null;
+};
+
+ImageEditor.Mode.Adjust.prototype.update = function(options) {
+ // 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();
};
/**
- * A simple filter that multiplies every component of a pixel by some number.
+ * Clip and scale the source image data for the preview.
+ * Use the cached copy if the viewport has not changed.
+ */
+ImageEditor.Mode.Adjust.prototype.updatePreviewImage = function() {
+ if (!this.previewImageData_ ||
+ 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.filter_ && !this.previewValid_) {
+ ImageUtil.trace.resetTimer('preview');
+ this.filter_(this.previewImageData_, this.originalImageData, 0, 0);
+ ImageUtil.trace.reportTimer('preview');
+ this.previewValid_ = true;
+ }
+};
+
+ImageEditor.Mode.Adjust.prototype.draw = function(context) {
+ this.updatePreviewImage();
+
+ 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);
+ }
+};
+
+/*
+ * Own methods
+ */
+
+ImageEditor.Mode.Adjust.prototype.createFilter = function(options) {
+ return filter.create(this.displayName.toLowerCase(), 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).
+ * Displays a histogram.
+ * @constructor
*/
-ImageEditor.Mode.Brightness = function() {
- ImageEditor.Mode.Adjust.call(
- this, 'Brightness', ImageEditor.Mode.Brightness.filter);
-}
+ImageEditor.Mode.ColorFilter = function() {
+ ImageEditor.Mode.Adjust.apply(this, arguments);
+};
-ImageEditor.Mode.Brightness.prototype =
+ImageEditor.Mode.ColorFilter.prototype =
{__proto__: ImageEditor.Mode.Adjust.prototype};
-ImageEditor.Mode.register(ImageEditor.Mode.Brightness);
+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.Brightness.UI_RANGE = 100;
+ImageEditor.Mode.ColorFilter.prototype.draw = function(context) {
+ ImageEditor.Mode.Adjust.prototype.draw.apply(this, arguments);
+ this.histogram_.draw(context);
+};
-ImageEditor.Mode.Brightness.prototype.createTools = function(toolbar) {
- toolbar.addRange(
- 'brightness',
- -ImageEditor.Mode.Brightness.UI_RANGE,
- 0,
- ImageEditor.Mode.Brightness.UI_RANGE);
+ImageEditor.Mode.ColorFilter.prototype.getPreviewRect = function(rect) {
+ return rect;
};
-ImageEditor.Mode.Brightness.filter = function(dst, src, options) {
- // Translate from -100..100 range to 1/5..5
- var factor =
- Math.pow(5, options.brightness / ImageEditor.Mode.Brightness.UI_RANGE);
+ImageEditor.Mode.ColorFilter.prototype.createFilter = function(options) {
+ var filterFunc =
+ ImageEditor.Mode.Adjust.prototype.createFilter.apply(this, arguments);
+ this.histogram_.update(filterFunc);
+ return filterFunc;
+};
- var dstData = dst.data;
- var srcData = src.data;
- var width = src.width;
- var height = src.height;
+ImageEditor.Mode.ColorFilter.prototype.rollback = function() {
+ ImageEditor.Mode.Adjust.prototype.rollback.apply(this, arguments);
+ this.histogram_.update(null);
+};
- function scale(value) {
- return value * factor;
+/**
+ * A histogram container.
+ * @constructor
+ */
+ImageEditor.Mode.Histogram = function(viewport, content) {
+ 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 context = thumbnail.getContext('2d');
+
+ this.originalImageData_ =
+ context.getImageData(0, 0, thumbnail.width, thumbnail.height);
+ this.filteredImageData_ =
+ context.getImageData(0, 0, thumbnail.width, thumbnail.height);
+
+ this.update();
+};
+
+ImageEditor.Mode.Histogram.prototype.getData = function() { return this.data_ };
+
+ImageEditor.Mode.Histogram.BUCKET_WIDTH = 8;
+ImageEditor.Mode.Histogram.BAR_WIDTH = 2;
+ImageEditor.Mode.Histogram.RIGHT = 5;
+ImageEditor.Mode.Histogram.TOP = 5;
+
+ImageEditor.Mode.Histogram.prototype.update = function(filterFunc) {
+ if (filterFunc) {
+ filterFunc(this.filteredImageData_, this.originalImageData_, 0, 0);
+ this.data_ = filter.getHistogram(this.filteredImageData_);
+ } else {
+ this.data_ = filter.getHistogram(this.originalImageData_);
}
+};
+
+ImageEditor.Mode.Histogram.prototype.draw = function(context) {
+ var screen = this.viewport_.getScreenBounds();
+
+ var barCount = 2 + 3 * (256 / ImageEditor.Mode.Histogram.BUCKET_WIDTH);
+ var width = ImageEditor.Mode.Histogram.BAR_WIDTH * barCount;
+ var height = Math.round(width / 2);
+ var rect = new Rect(
+ screen.left + screen.width - ImageEditor.Mode.Histogram.RIGHT - width,
+ ImageEditor.Mode.Histogram.TOP,
+ width,
+ height);
- var values = ImageUtil.precomputeByteFunction(scale, 255);
+ context.globalAlpha = 1;
+ context.fillStyle = '#E0E0E0';
+ context.strokeStyle = '#000000';
+ context.lineCap = 'square';
+ Rect.fill(context, rect);
+ Rect.outline(context, rect);
- var index = 0;
- for (var y = 0; y != height; y++) {
- for (var x = 0; x != width; x++ ) {
- dstData[index] = values[srcData[index]]; index++;
- dstData[index] = values[srcData[index]]; index++;
- dstData[index] = values[srcData[index]]; index++;
- dstData[index] = 0xFF; index++;
+ function drawChannel(channel, style, offsetX, offsetY) {
+ context.strokeStyle = style;
+ context.beginPath();
+ for (var i = 0; i != 256; i += ImageEditor.Mode.Histogram.BUCKET_WIDTH) {
+ var barHeight = channel[i];
+ for (var b = 1; b < ImageEditor.Mode.Histogram.BUCKET_WIDTH; b++)
+ barHeight = Math.max(barHeight, channel[i + b]);
+ barHeight = Math.min(barHeight, height);
+ for (var j = 0; j != ImageEditor.Mode.Histogram.BAR_WIDTH; j++) {
+ context.moveTo(offsetX, offsetY);
+ context.lineTo(offsetX, offsetY - barHeight);
+ offsetX++;
+ }
+ offsetX += 2 * ImageEditor.Mode.Histogram.BAR_WIDTH;
}
+ context.closePath();
+ context.stroke();
}
+
+ var offsetX = rect.left + 0.5 + ImageEditor.Mode.Histogram.BAR_WIDTH;
+ var offsetY = rect.top + rect.height;
+
+ drawChannel(this.data_.r, '#F00000', offsetX, offsetY);
+ offsetX += ImageEditor.Mode.Histogram.BAR_WIDTH;
+ drawChannel(this.data_.g, '#00F000', offsetX, offsetY);
+ offsetX += ImageEditor.Mode.Histogram.BAR_WIDTH;
+ drawChannel(this.data_.b, '#0000F0', offsetX, offsetY);
+};
+
+/**
+ * Exposure/contrast filter.
+ * @constructor
+ */
+ImageEditor.Mode.Exposure = function() {
+ ImageEditor.Mode.ColorFilter.call(this, 'Exposure');
};
+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);
+};
+
+/**
+ * Autofix.
+ * @constructor
+ */
+ImageEditor.Mode.Autofix = function() {
+ ImageEditor.Mode.ColorFilter.call(this, 'Autofix');
+};
+
+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', function() {
+ self.update({histogram: self.histogram_.getData()});
+ });
+};
+
+/**
+ * Blur filter.
+ * @constructor
+ */
+ImageEditor.Mode.Blur = function() {
+ ImageEditor.Mode.Adjust.call(this, 'Blur');
+};
+
+ImageEditor.Mode.Blur.prototype =
+ {__proto__: ImageEditor.Mode.Adjust.prototype};
+
+ImageEditor.Mode.register(ImageEditor.Mode.Blur);
+
+ImageEditor.Mode.Blur.prototype.createTools = function(toolbar) {
+ toolbar.addRange('strength', 0, 0, 1, 100);
+ toolbar.addRange('radius', 1, 1, 3);
+};
+
+/**
+ * Sharpen filter.
+ * @constructor
+ */
+ImageEditor.Mode.Sharpen = function() {
+ ImageEditor.Mode.Adjust.call(this, 'Sharpen');
+};
+
+ImageEditor.Mode.Sharpen.prototype =
+ {__proto__: ImageEditor.Mode.Adjust.prototype};
+
+ImageEditor.Mode.register(ImageEditor.Mode.Sharpen);
+
+ImageEditor.Mode.Sharpen.prototype.createTools = function(toolbar) {
+ toolbar.addRange('strength', 0, 0, 1, 100);
+ toolbar.addRange('radius', 1, 1, 3);
+};

Powered by Google App Engine
This is Rietveld 408576698