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

Side by Side 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: 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * The base class for simple filters that only modify the image content 6 * The base class for simple filters that only modify the image content
7 * but do not modify the image dimensions. 7 * but do not modify the image dimensions.
8 */ 8 */
9 ImageEditor.Mode.Adjust = function(displayName, filterFunc) { 9 ImageEditor.Mode.Adjust = function(displayName) {
10 ImageEditor.Mode.call(this, displayName); 10 ImageEditor.Mode.call(this, displayName);
11 this.filterFunc_ = filterFunc; 11 this.viewportGeneration_ = 0;
12 } 12 };
13 13
14 ImageEditor.Mode.Adjust.prototype = {__proto__: ImageEditor.Mode.prototype}; 14 ImageEditor.Mode.Adjust.prototype = {__proto__: ImageEditor.Mode.prototype};
15 15
16 /**
17 * ImageEditor.Mode methods overridden.
18 */
19
SeRya 2011/08/10 13:00:20 No new line needed.
Vladislav Kaznacheev 2011/08/10 14:03:25 Done.
20 ImageEditor.Mode.Adjust.prototype.commit = function() {
21 if (!this.filter_) return; // Did not do anything yet.
22
23 // Applying the filter to the entire image takes some time, so we do
24 // it in small increments, providing visual feedback.
25 // TODO: provide modal progress indicator.
26
27 // First hide the preview and show the original image.
28 this.repaint();
29
30 var self = this;
31
32 function repaintStrip(fromRow, toRow) {
33 var imageStrip = new Rect(self.getViewport().getImageBounds());
34 imageStrip.top = fromRow;
35 imageStrip.height = toRow - fromRow;
36
37 var screenStrip = new Rect(self.getViewport().getImageBoundsOnScreen());
38 screenStrip.top = Math.round(self.getViewport().imageToScreenY(fromRow));
39 screenStrip.height = Math.round(self.getViewport().imageToScreenY(toRow)) -
40 screenStrip.top;
41
42 self.getBuffer().repaintScreenRect(screenStrip, imageStrip);
43 }
44
45 ImageUtil.trace.resetTimer('filter');
46
47 var lastUpdatedRow = 0;
48
49 Filter.applyByStrips(
50 this.getContent().getCanvas().getContext('2d'),
51 this.filter_,
52 function (updatedRow, rowCount) {
53 repaintStrip(lastUpdatedRow, updatedRow);
54 lastUpdatedRow = updatedRow;
55 if (updatedRow == rowCount) {
56 ImageUtil.trace.reportTimer('filter');
57 self.getContent().invalidateCaches();
58 self.repaint();
59 }
60 });
61 };
62
16 ImageEditor.Mode.Adjust.prototype.rollback = function() { 63 ImageEditor.Mode.Adjust.prototype.rollback = function() {
17 if (!this.backup_) return; // Did not do anything yet. 64 this.filter_ = null;
18 this.getContent().drawImageData(this.backup_, 0, 0); 65 this.previewImageData_ = null;
19 this.backup_ = null; 66 };
67
68 ImageEditor.Mode.Adjust.prototype.update = function(options) {
69 // We assume filter names are used in the UI directly.
70 // This will have to change with i18n.
71 this.filter_ = this.createFilter(options);
72 this.previewValid_ = false;
20 this.repaint(); 73 this.repaint();
21 }; 74 };
22 75
23 ImageEditor.Mode.Adjust.prototype.update = function(options) { 76 /**
24 if (!this.backup_) { 77 * Clip and scale the source image data for the preview.
25 this.backup_ = this.getContent().copyImageData(); 78 * Use the cached copy if the viewport has not changed.
26 this.scratch_ = this.getContent().copyImageData(); 79 */
27 } 80 ImageEditor.Mode.Adjust.prototype.updatePreviewImage = function() {
28 81 if (!this.previewImageData_ ||
29 ImageUtil.trace.resetTimer('filter'); 82 this.viewportGeneration_ != this.getViewport().getCacheGeneration()) {
30 this.filterFunc_(this.scratch_, this.backup_, options); 83 this.viewportGeneration_ = this.getViewport().getCacheGeneration();
31 ImageUtil.trace.reportTimer('filter'); 84
32 this.getContent().drawImageData(this.scratch_, 0, 0); 85 var imageRect = this.getPreviewRect(this.getViewport().getImageClipped());
33 this.repaint(); 86 var screenRect = this.getPreviewRect(this.getViewport().getScreenClipped());
34 }; 87
35 88 // Copy the visible part of the image at the current screen scale.
36 /** 89 var canvas = this.getContent().createBlankCanvas(
37 * A simple filter that multiplies every component of a pixel by some number. 90 screenRect.width, screenRect.height);
38 */ 91 var context = canvas.getContext('2d');
39 ImageEditor.Mode.Brightness = function() { 92 Rect.drawImage(context, this.getContent().getCanvas(), null, imageRect);
40 ImageEditor.Mode.Adjust.call( 93 this.originalImageData =
41 this, 'Brightness', ImageEditor.Mode.Brightness.filter); 94 context.getImageData(0, 0, screenRect.width, screenRect.height);
42 } 95 this.previewImageData_ =
43 96 context.getImageData(0, 0, screenRect.width, screenRect.height);
44 ImageEditor.Mode.Brightness.prototype = 97 this.previewValid_ = false;
98 }
99
100 if (this.filter_ && !this.previewValid_) {
101 ImageUtil.trace.resetTimer('preview');
102 this.filter_(this.previewImageData_, this.originalImageData, 0, 0);
103 ImageUtil.trace.reportTimer('preview');
104 this.previewValid_ = true;
105 }
106 };
107
108 ImageEditor.Mode.Adjust.prototype.draw = function(context) {
109 this.updatePreviewImage();
110
111 var screenClipped = this.getViewport().getScreenClipped();
112
113 var previewRect = this.getPreviewRect(screenClipped);
114 context.putImageData(
115 this.previewImageData_, previewRect.left, previewRect.top);
116
117 if (previewRect.width < screenClipped.width &&
118 previewRect.height < screenClipped.height) {
119 // Some part of the original image is not covered by the preview,
120 // shade it out.
121 context.globalAlpha = 0.75;
122 context.fillStyle = '#000000';
123 context.strokeStyle = '#000000';
124 Rect.fillBetween(
125 context, previewRect, this.getViewport().getScreenBounds());
126 Rect.outline(context, previewRect);
127 }
128 };
129
130 /**
131 * Own methods
132 */
133
SeRya 2011/08/10 13:00:20 New line
Vladislav Kaznacheev 2011/08/10 14:03:25 Done.
134 ImageEditor.Mode.Adjust.prototype.createFilter = function(options) {
135 return Filter.create(this.displayName.toLowerCase(), options);
136 };
137
138 ImageEditor.Mode.Adjust.prototype.getPreviewRect = function(rect) {
139 if (this.getViewport().getScale() >= 1) {
140 return rect;
141 } else {
142 var bounds = this.getViewport().getImageBounds();
143 var screen = this.getViewport().getScreenClipped();
144
145 screen = screen.inflate(-screen.width / 8, -screen.height / 8);
146
147 return rect.inflate(-rect.width / 2, -rect.height / 2).
148 inflate(Math.min(screen.width, bounds.width) / 2,
149 Math.min(screen.height, bounds.height) / 2);
150 }
151 };
152
153 /**
154 * A base class for color filters that are scale independent (i.e. can
155 * be applied to a scaled image with basicaly the same effect).
156 * Displays a histogram.
157 */
158 ImageEditor.Mode.ColorFilter = function() {
159 ImageEditor.Mode.Adjust.apply(this, arguments);
160 };
161
162 ImageEditor.Mode.ColorFilter.prototype =
45 {__proto__: ImageEditor.Mode.Adjust.prototype}; 163 {__proto__: ImageEditor.Mode.Adjust.prototype};
46 164
47 ImageEditor.Mode.register(ImageEditor.Mode.Brightness); 165 ImageEditor.Mode.ColorFilter.prototype.setUp = function() {
48 166 ImageEditor.Mode.Adjust.prototype.setUp.apply(this, arguments);
49 ImageEditor.Mode.Brightness.UI_RANGE = 100; 167 this.histogram_ =
50 168 new ImageEditor.Mode.Histogram(this.getViewport(), this.getContent());
51 ImageEditor.Mode.Brightness.prototype.createTools = function(toolbar) { 169 };
52 toolbar.addRange( 170
53 'brightness', 171 ImageEditor.Mode.ColorFilter.prototype.draw = function(context) {
54 -ImageEditor.Mode.Brightness.UI_RANGE, 172 ImageEditor.Mode.Adjust.prototype.draw.apply(this, arguments);
55 0, 173 this.histogram_.draw(context);
56 ImageEditor.Mode.Brightness.UI_RANGE); 174 };
57 }; 175
58 176 ImageEditor.Mode.ColorFilter.prototype.getPreviewRect = function(rect) {
59 ImageEditor.Mode.Brightness.filter = function(dst, src, options) { 177 return rect;
60 // Translate from -100..100 range to 1/5..5 178 };
61 var factor = 179
62 Math.pow(5, options.brightness / ImageEditor.Mode.Brightness.UI_RANGE); 180 ImageEditor.Mode.ColorFilter.prototype.createFilter = function(options) {
63 181 var filter =
64 var dstData = dst.data; 182 ImageEditor.Mode.Adjust.prototype.createFilter.apply(this, arguments);
65 var srcData = src.data; 183 this.histogram_.update(filter);
66 var width = src.width; 184 return filter;
67 var height = src.height; 185 };
68 186
69 function scale(value) { 187 ImageEditor.Mode.ColorFilter.prototype.rollback = function() {
70 return value * factor; 188 ImageEditor.Mode.Adjust.prototype.rollback.apply(this, arguments);
71 } 189 this.histogram_.update(null);
72 190 };
73 var values = ImageUtil.precomputeByteFunction(scale, 255); 191
74 192 /**
75 var index = 0; 193 * A histogram container.
76 for (var y = 0; y != height; y++) { 194 */
77 for (var x = 0; x != width; x++ ) { 195 ImageEditor.Mode.Histogram = function(viewport, content) {
78 dstData[index] = values[srcData[index]]; index++; 196 this.viewport_ = viewport;
79 dstData[index] = values[srcData[index]]; index++; 197
80 dstData[index] = values[srcData[index]]; index++; 198 var canvas = content.getCanvas();
81 dstData[index] = 0xFF; index++; 199 var downScale = Math.max(1, Math.sqrt(canvas.width * canvas.height / 10000));
200 var thumbnail = content.copyCanvas(canvas.width / downScale,
201 canvas.height / downScale);
202 var context = thumbnail.getContext('2d');
203
204 this.originalImageData_ =
205 context.getImageData(0, 0, thumbnail.width, thumbnail.height);
206 this.filteredImageData_ =
207 context.getImageData(0, 0, thumbnail.width, thumbnail.height);
208
209 this.update();
210 };
211
212 ImageEditor.Mode.Histogram.prototype.getData = function() { return this.data_ };
213
214 ImageEditor.Mode.Histogram.BUCKET_WIDTH = 8;
215 ImageEditor.Mode.Histogram.BAR_WIDTH = 2;
216 ImageEditor.Mode.Histogram.RIGHT = 5;
217 ImageEditor.Mode.Histogram.TOP = 5;
218
219 ImageEditor.Mode.Histogram.prototype.update = function(filter) {
220 if (filter) {
221 filter(this.filteredImageData_, this.originalImageData_, 0, 0);
222 this.data_ = Filter.getHistogram(this.filteredImageData_);
223 } else {
224 this.data_ = Filter.getHistogram(this.originalImageData_);
225 }
226 };
227
228 ImageEditor.Mode.Histogram.prototype.draw = function(context) {
229 var screen = this.viewport_.getScreenBounds();
230
231 var barCount = 2 + 3 * (256 / ImageEditor.Mode.Histogram.BUCKET_WIDTH);
232 var width = ImageEditor.Mode.Histogram.BAR_WIDTH * barCount;
233 var height = Math.round(width / 2);
234 var rect = new Rect(
235 screen.left + screen.width - ImageEditor.Mode.Histogram.RIGHT - width,
236 ImageEditor.Mode.Histogram.TOP,
237 width,
238 height);
239
240 context.globalAlpha = 1;
241 context.fillStyle = '#E0E0E0';
242 context.strokeStyle = '#000000';
243 context.lineCap = 'square';
244 Rect.fill(context, rect);
245 Rect.outline(context, rect);
246
247 function drawChannel(channel, style, offsetX, offsetY) {
248 context.strokeStyle = style;
249 context.beginPath();
250 for (var i = 0; i != 256; i += ImageEditor.Mode.Histogram.BUCKET_WIDTH) {
251 var barHeight = channel[i];
252 for (var b = 1; b < ImageEditor.Mode.Histogram.BUCKET_WIDTH; b++)
253 barHeight = Math.max(barHeight, channel[i + b]);
254 barHeight = Math.min(barHeight, height);
255 for (var j = 0; j != ImageEditor.Mode.Histogram.BAR_WIDTH; j++) {
256 context.moveTo(offsetX, offsetY);
257 context.lineTo(offsetX, offsetY - barHeight);
258 offsetX++;
259 }
260 offsetX += 2 * ImageEditor.Mode.Histogram.BAR_WIDTH;
82 } 261 }
83 } 262 context.closePath();
84 }; 263 context.stroke();
85 264 }
265
266 var offsetX = rect.left + 0.5 + ImageEditor.Mode.Histogram.BAR_WIDTH;
267 var offsetY = rect.top + rect.height;
268
269 drawChannel(this.data_.r, '#F00000', offsetX, offsetY);
270 offsetX += ImageEditor.Mode.Histogram.BAR_WIDTH;
271 drawChannel(this.data_.g, '#00F000', offsetX, offsetY);
272 offsetX += ImageEditor.Mode.Histogram.BAR_WIDTH;
273 drawChannel(this.data_.b, '#0000F0', offsetX, offsetY);
274 };
275
276 /**
277 * Exposure/contrast filter.
278 */
279 ImageEditor.Mode.Exposure = function() {
280 ImageEditor.Mode.ColorFilter.call(this, 'Exposure');
281 };
282
283 ImageEditor.Mode.Exposure.prototype =
284 {__proto__: ImageEditor.Mode.ColorFilter.prototype};
285
286 ImageEditor.Mode.register(ImageEditor.Mode.Exposure);
287
288 ImageEditor.Mode.Exposure.prototype.createTools = function(toolbar) {
289 toolbar.addRange('brightness', -1, 0, 1, 100);
290 toolbar.addRange('contrast', -1, 0, 1, 100);
291 };
292
293 /**
294 * Autofix.
295 */
296 ImageEditor.Mode.Autofix = function() {
297 ImageEditor.Mode.ColorFilter.call(this, 'Autofix');
298 };
299
300 ImageEditor.Mode.Autofix.prototype =
301 {__proto__: ImageEditor.Mode.ColorFilter.prototype};
302
303 ImageEditor.Mode.register(ImageEditor.Mode.Autofix);
304
305 ImageEditor.Mode.Autofix.prototype.createTools = function(toolbar) {
306 var self = this;
307 toolbar.addButton('Apply', function() {
308 self.update({histogram: self.histogram_.getData()});
309 });
310 };
311
312 /**
313 * Blur filter.
314 */
315 ImageEditor.Mode.Blur = function() {
316 ImageEditor.Mode.Adjust.call(this, 'Blur');
317 };
318
319 ImageEditor.Mode.Blur.prototype =
320 {__proto__: ImageEditor.Mode.Adjust.prototype};
321
322 ImageEditor.Mode.register(ImageEditor.Mode.Blur);
323
324 ImageEditor.Mode.Blur.prototype.createTools = function(toolbar) {
325 toolbar.addRange('strength', 0, 0, 1, 100);
326 toolbar.addRange('radius', 1, 1, 3);
327 };
328
329 /**
330 * Sharpen filter.
331 */
332 ImageEditor.Mode.Sharpen = function() {
333 ImageEditor.Mode.Adjust.call(this, 'Sharpen');
334 };
335
336 ImageEditor.Mode.Sharpen.prototype =
337 {__proto__: ImageEditor.Mode.Adjust.prototype};
338
339 ImageEditor.Mode.register(ImageEditor.Mode.Sharpen);
340
341 ImageEditor.Mode.Sharpen.prototype.createTools = function(toolbar) {
342 toolbar.addRange('strength', 0, 0, 1, 100);
343 toolbar.addRange('radius', 1, 1, 3);
344 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698