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

Side by Side Diff: ui/file_manager/gallery/js/image_editor/commands.js

Issue 1608143002: support animated GIF (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: fix nits Created 4 years, 11 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
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 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 * Command queue is the only way to modify images. 6 * Command queue is the only way to modify images.
7 * Supports undo/redo. 7 * Supports undo/redo.
8 * Command execution is asynchronous (callback-based). 8 * Command execution is asynchronous (callback-based).
9 * 9 *
10 * @param {!Document} document Document to create canvases in. 10 * @param {!Document} document Document to create canvases in.
11 * @param {!HTMLCanvasElement} canvas The canvas with the original image. 11 * @param {!HTMLCanvasElement|!HTMLImageElement} image The canvas with the
12 * original image.
12 * @param {function(function())} saveFunction Function to save the image. 13 * @param {function(function())} saveFunction Function to save the image.
13 * @constructor 14 * @constructor
14 * @struct 15 * @struct
15 */ 16 */
16 function CommandQueue(document, canvas, saveFunction) { 17 function CommandQueue(document, image, saveFunction) {
17 this.document_ = document; 18 this.document_ = document;
18 this.undo_ = []; 19 this.undo_ = [];
19 this.redo_ = []; 20 this.redo_ = [];
20 this.subscribers_ = []; 21 this.subscribers_ = [];
21 this.currentImage_ = canvas; 22 /**
23 * @type {HTMLCanvasElement|HTMLImageElement}
24 * @private
25 */
22 26
yawano 2016/01/21 05:14:38 nit: remove blank line from line 26. And insert a
ryoh 2016/01/21 07:28:53 Done.
23 // Current image may be null or not-null but with width = height = 0. 27 this.currentImage_ = image;
24 // Copying an image with zero dimensions causes js errors.
25 if (this.currentImage_) {
26 this.baselineImage_ = document.createElement('canvas');
27 this.baselineImage_.width = this.currentImage_.width;
28 this.baselineImage_.height = this.currentImage_.height;
29 if (this.currentImage_.width > 0 && this.currentImage_.height > 0) {
30 var context = this.baselineImage_.getContext('2d');
31 context.drawImage(this.currentImage_, 0, 0);
32 }
33 } else {
34 this.baselineImage_ = null;
35 }
36 28
37 this.previousImage_ = document.createElement('canvas'); 29 /**
38 this.previousImageAvailable_ = false; 30 * @type {HTMLCanvasElement|HTMLImageElement}
31 * @private
32 */
33 this.baselineImage_ = image;
34
35 /**
36 * @type {HTMLCanvasElement|HTMLImageElement}
37 * @private
38 */
39 this.previousImage_ = null;
39 40
40 this.saveFunction_ = saveFunction; 41 this.saveFunction_ = saveFunction;
41 this.busy_ = false; 42 this.busy_ = false;
42 this.UIContext_ = {}; 43 this.UIContext_ = {};
43 } 44 }
44 45
45 /** 46 /**
46 * Attach the UI elements to the command queue. 47 * Attach the UI elements to the command queue.
47 * Once the UI is attached the results of image manipulations are displayed. 48 * Once the UI is attached the results of image manipulations are displayed.
48 * 49 *
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after
132 * 133 *
133 * @param {!Command} command The command to execute. 134 * @param {!Command} command The command to execute.
134 * @param {!Object} uiContext The UI context. 135 * @param {!Object} uiContext The UI context.
135 * @param {function(number=)} callback Completion callback. 136 * @param {function(number=)} callback Completion callback.
136 * @private 137 * @private
137 */ 138 */
138 CommandQueue.prototype.doExecute_ = function(command, uiContext, callback) { 139 CommandQueue.prototype.doExecute_ = function(command, uiContext, callback) {
139 if (!this.currentImage_) 140 if (!this.currentImage_)
140 throw new Error('Cannot operate on null image'); 141 throw new Error('Cannot operate on null image');
141 142
142 // Remember one previous image so that the first undo is as fast as possible.
143 this.previousImage_.width = this.currentImage_.width;
144 this.previousImage_.height = this.currentImage_.height;
145 this.previousImageAvailable_ = true;
146 var context = this.previousImage_.getContext('2d');
147 context.drawImage(this.currentImage_, 0, 0);
148
149 command.execute( 143 command.execute(
150 this.document_, 144 this.document_,
151 this.currentImage_, 145 this.currentImage_,
152 /** 146 /**
153 * @type {function(HTMLCanvasElement, number=)} 147 * @type {function(HTMLCanvasElement, number=)}
154 */ 148 */
155 (function(result, opt_delay) { 149 (function(result, opt_delay) {
150 this.previousImage_ = this.currentImage_;
156 this.currentImage_ = result; 151 this.currentImage_ = result;
157 callback(opt_delay); 152 callback(opt_delay);
158 }.bind(this)), 153 }.bind(this)),
159 uiContext); 154 uiContext);
160 }; 155 };
161 156
162 /** 157 /**
163 * Executes the command. 158 * Executes the command.
164 * 159 *
165 * @param {!Command} command Command to execute. 160 * @param {!Command} command Command to execute.
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
197 this.redo_.push(command); 192 this.redo_.push(command);
198 193
199 var self = this; 194 var self = this;
200 195
201 function complete() { 196 function complete() {
202 var delay = command.revertView( 197 var delay = command.revertView(
203 self.currentImage_, self.UIContext_.imageView); 198 self.currentImage_, self.UIContext_.imageView);
204 self.commit_(false /* Do not show undo action */, delay); 199 self.commit_(false /* Do not show undo action */, delay);
205 } 200 }
206 201
207 if (this.previousImageAvailable_) { 202 if (this.previousImage_) {
208 // First undo after an execute call. 203 // First undo after an execute call.
209 this.currentImage_.width = this.previousImage_.width; 204 this.currentImage_ = this.previousImage_;
210 this.currentImage_.height = this.previousImage_.height; 205 this.previousImage_ = null;
211 var context = this.currentImage_.getContext('2d');
212 context.drawImage(this.previousImage_, 0, 0);
213
214 // Free memory.
215 this.previousImage_.width = 0;
216 this.previousImage_.height = 0;
217 this.previousImageAvailable_ = false;
218 206
219 complete(); 207 complete();
220 // TODO(kaznacheev) Consider recalculating previousImage_ right here 208 // TODO(kaznacheev) Consider recalculating previousImage_ right here
221 // by replaying the commands in the background. 209 // by replaying the commands in the background.
222 } else { 210 } else {
223 this.currentImage_.width = this.baselineImage_.width; 211 this.currentImage_ = this.baselineImage_;
224 this.currentImage_.height = this.baselineImage_.height;
225 var context = this.currentImage_.getContext('2d');
226 context.drawImage(this.baselineImage_, 0, 0);
227 212
228 var replay = function(index) { 213 var replay = function(index) {
229 if (index < self.undo_.length) 214 if (index < self.undo_.length)
230 self.doExecute_(self.undo_[index], {}, replay.bind(null, index + 1)); 215 self.doExecute_(self.undo_[index], {}, replay.bind(null, index + 1));
231 else { 216 else {
232 complete(); 217 complete();
233 } 218 }
234 }; 219 };
235 220
236 replay(0); 221 replay(0);
(...skipping 16 matching lines...) Expand all
253 238
254 this.execute(this.redo_.pop(), true); 239 this.execute(this.redo_.pop(), true);
255 }; 240 };
256 241
257 /** 242 /**
258 * Closes internal buffers. Call to ensure, that internal buffers are freed 243 * Closes internal buffers. Call to ensure, that internal buffers are freed
259 * as soon as possible. 244 * as soon as possible.
260 */ 245 */
261 CommandQueue.prototype.close = function() { 246 CommandQueue.prototype.close = function() {
262 // Free memory used by the undo buffer. 247 // Free memory used by the undo buffer.
263 this.previousImage_.width = 0; 248 this.currentImage_ = null;
264 this.previousImage_.height = 0; 249 this.previousImage_ = null;
265 this.previousImageAvailable_ = false; 250 this.baselineImage_ = null;
266
267 if (this.baselineImage_) {
268 this.baselineImage_.width = 0;
269 this.baselineImage_.height = 0;
270 }
271 }; 251 };
272 252
273 /** 253 /**
274 * Command object encapsulates an operation on an image and a way to visualize 254 * Command object encapsulates an operation on an image and a way to visualize
275 * its result. 255 * its result.
276 * 256 *
277 * @param {string} name Command name. 257 * @param {string} name Command name.
278 * @constructor 258 * @constructor
279 * @struct 259 * @struct
280 */ 260 */
281 function Command(name) { 261 function Command(name) {
282 this.name_ = name; 262 this.name_ = name;
283 } 263 }
284 264
285 /** 265 /**
286 * @return {string} String representation of the command. 266 * @return {string} String representation of the command.
287 */ 267 */
288 Command.prototype.toString = function() { 268 Command.prototype.toString = function() {
289 return 'Command ' + this.name_; 269 return 'Command ' + this.name_;
290 }; 270 };
291 271
292 /** 272 /**
293 * Execute the command and visualize its results. 273 * Execute the command and visualize its results.
294 * 274 *
295 * The two actions are combined into one method because sometimes it is nice 275 * The two actions are combined into one method because sometimes it is nice
296 * to be able to show partial results for slower operations. 276 * to be able to show partial results for slower operations.
297 * 277 *
298 * @param {!Document} document Document on which to execute command. 278 * @param {!Document} document Document on which to execute command.
299 * @param {!HTMLCanvasElement} srcCanvas Canvas to execute on. 279 * @param {!HTMLCanvasElement|!HTMLImageElement} srcImage Image to execute on.
280 * Do NOT modify this object.
300 * @param {function(HTMLCanvasElement, number=)} callback Callback to call on 281 * @param {function(HTMLCanvasElement, number=)} callback Callback to call on
301 * completion. 282 * completion.
302 * @param {!Object} uiContext Context to work in. 283 * @param {!Object} uiContext Context to work in.
303 */ 284 */
304 Command.prototype.execute = function(document, srcCanvas, callback, uiContext) { 285 Command.prototype.execute = function(document, srcImage, callback, uiContext) {
305 console.error('Command.prototype.execute not implemented'); 286 console.error('Command.prototype.execute not implemented');
306 }; 287 };
307 288
308 /** 289 /**
309 * Visualize reversion of the operation. 290 * Visualize reversion of the operation.
310 * 291 *
311 * @param {!HTMLCanvasElement} canvas Image data to use. 292 * @param {!HTMLCanvasElement|!HTMLCanvasElement} image previous image.
312 * @param {!ImageView} imageView ImageView to revert. 293 * @param {!ImageView} imageView ImageView to revert.
313 * @return {number} Animation duration in ms. 294 * @return {number} Animation duration in ms.
314 */ 295 */
315 Command.prototype.revertView = function(canvas, imageView) { 296 Command.prototype.revertView = function(image, imageView) {
316 imageView.replace(canvas); 297 imageView.replace(image);
317 return 0; 298 return 0;
318 }; 299 };
319 300
320 /** 301 /**
321 * Creates canvas to render on. 302 * Creates canvas to render on.
322 * 303 *
323 * @param {!Document} document Document to create canvas in. 304 * @param {!Document} document Document to create canvas in.
324 * @param {!HTMLCanvasElement} srcCanvas to copy optional dimensions from. 305 * @param {!HTMLCanvasElement|!HTMLImageElement} srcImage to copy optional
306 * dimensions from.
325 * @param {number=} opt_width new canvas width. 307 * @param {number=} opt_width new canvas width.
326 * @param {number=} opt_height new canvas height. 308 * @param {number=} opt_height new canvas height.
327 * @return {!HTMLCanvasElement} Newly created canvas. 309 * @return {!HTMLCanvasElement} Newly created canvas.
328 * @private 310 * @private
329 */ 311 */
330 Command.prototype.createCanvas_ = function( 312 Command.prototype.createCanvas_ = function(
331 document, srcCanvas, opt_width, opt_height) { 313 document, srcImage, opt_width, opt_height) {
332 var result = assertInstanceof(document.createElement('canvas'), 314 var result = assertInstanceof(document.createElement('canvas'),
333 HTMLCanvasElement); 315 HTMLCanvasElement);
334 result.width = opt_width || srcCanvas.width; 316 result.width = opt_width || srcImage.width;
335 result.height = opt_height || srcCanvas.height; 317 result.height = opt_height || srcImage.height;
336 return result; 318 return result;
337 }; 319 };
338 320
339 321
340 /** 322 /**
341 * Rotate command 323 * Rotate command
342 * @param {number} rotate90 Rotation angle in 90 degree increments (signed). 324 * @param {number} rotate90 Rotation angle in 90 degree increments (signed).
343 * @constructor 325 * @constructor
344 * @extends {Command} 326 * @extends {Command}
345 * @struct 327 * @struct
346 */ 328 */
347 Command.Rotate = function(rotate90) { 329 Command.Rotate = function(rotate90) {
348 Command.call(this, 'rotate(' + rotate90 * 90 + 'deg)'); 330 Command.call(this, 'rotate(' + rotate90 * 90 + 'deg)');
349 this.rotate90_ = rotate90; 331 this.rotate90_ = rotate90;
350 }; 332 };
351 333
352 Command.Rotate.prototype = { __proto__: Command.prototype }; 334 Command.Rotate.prototype = { __proto__: Command.prototype };
353 335
354 /** @override */ 336 /** @override */
355 Command.Rotate.prototype.execute = function( 337 Command.Rotate.prototype.execute = function(
356 document, srcCanvas, callback, uiContext) { 338 document, srcImage, callback, uiContext) {
357 var result = this.createCanvas_( 339 var result = this.createCanvas_(
358 document, 340 document,
359 srcCanvas, 341 srcImage,
360 (this.rotate90_ & 1) ? srcCanvas.height : srcCanvas.width, 342 (this.rotate90_ & 1) ? srcImage.height : srcImage.width,
361 (this.rotate90_ & 1) ? srcCanvas.width : srcCanvas.height); 343 (this.rotate90_ & 1) ? srcImage.width : srcImage.height);
362 ImageUtil.drawImageTransformed( 344 ImageUtil.drawImageTransformed(
363 result, srcCanvas, 1, 1, this.rotate90_ * Math.PI / 2); 345 result, srcImage, 1, 1, this.rotate90_ * Math.PI / 2);
364 var delay; 346 var delay;
365 if (uiContext.imageView) { 347 if (uiContext.imageView) {
366 delay = uiContext.imageView.replaceAndAnimate(result, null, this.rotate90_); 348 delay = uiContext.imageView.replaceAndAnimate(result, null, this.rotate90_);
367 } 349 }
368 setTimeout(callback, 0, result, delay); 350 setTimeout(callback, 0, result, delay);
369 }; 351 };
370 352
371 /** @override */ 353 /** @override */
372 Command.Rotate.prototype.revertView = function(canvas, imageView) { 354 Command.Rotate.prototype.revertView = function(image, imageView) {
373 return imageView.replaceAndAnimate(canvas, null, -this.rotate90_); 355 return imageView.replaceAndAnimate(image, null, -this.rotate90_);
374 }; 356 };
375 357
376 358
377 /** 359 /**
378 * Crop command. 360 * Crop command.
379 * 361 *
380 * @param {!ImageRect} imageRect Crop rectangle in image coordinates. 362 * @param {!ImageRect} imageRect Crop rectangle in image coordinates.
381 * @constructor 363 * @constructor
382 * @extends {Command} 364 * @extends {Command}
383 * @struct 365 * @struct
(...skipping 13 matching lines...) Expand all
397 var ctx = assertInstanceof(result.getContext('2d'), CanvasRenderingContext2D); 379 var ctx = assertInstanceof(result.getContext('2d'), CanvasRenderingContext2D);
398 ImageRect.drawImage(ctx, srcCanvas, null, this.imageRect_); 380 ImageRect.drawImage(ctx, srcCanvas, null, this.imageRect_);
399 var delay; 381 var delay;
400 if (uiContext.imageView) { 382 if (uiContext.imageView) {
401 delay = uiContext.imageView.replaceAndAnimate(result, this.imageRect_, 0); 383 delay = uiContext.imageView.replaceAndAnimate(result, this.imageRect_, 0);
402 } 384 }
403 setTimeout(callback, 0, result, delay); 385 setTimeout(callback, 0, result, delay);
404 }; 386 };
405 387
406 /** @override */ 388 /** @override */
407 Command.Crop.prototype.revertView = function(canvas, imageView) { 389 Command.Crop.prototype.revertView = function(image, imageView) {
408 return imageView.animateAndReplace(canvas, this.imageRect_); 390 return imageView.animateAndReplace(image, this.imageRect_);
409 }; 391 };
410 392
411 393
412 /** 394 /**
413 * Filter command. 395 * Filter command.
414 * 396 *
415 * @param {string} name Command name. 397 * @param {string} name Command name.
416 * @param {function(!ImageData,!ImageData,number,number)} filter Filter 398 * @param {function(!ImageData,!ImageData,number,number)} filter Filter
417 * function. 399 * function.
418 * @param {?string} message Message to display when done. 400 * @param {?string} message Message to display when done.
419 * @constructor 401 * @constructor
420 * @extends {Command} 402 * @extends {Command}
421 * @struct 403 * @struct
422 */ 404 */
423 Command.Filter = function(name, filter, message) { 405 Command.Filter = function(name, filter, message) {
424 Command.call(this, name); 406 Command.call(this, name);
425 this.filter_ = filter; 407 this.filter_ = filter;
426 this.message_ = message; 408 this.message_ = message;
427 }; 409 };
428 410
429 Command.Filter.prototype = { __proto__: Command.prototype }; 411 Command.Filter.prototype = { __proto__: Command.prototype };
430 412
431 /** @override */ 413 /** @override */
432 Command.Filter.prototype.execute = function( 414 Command.Filter.prototype.execute = function(
433 document, srcCanvas, callback, uiContext) { 415 document, srcImage, callback, uiContext) {
434 var result = this.createCanvas_(document, srcCanvas); 416 var result = this.createCanvas_(document, srcImage);
435 var self = this; 417 var self = this;
436 var previousRow = 0; 418 var previousRow = 0;
437 419
438 function onProgressVisible(updatedRow, rowCount) { 420 function onProgressVisible(updatedRow, rowCount) {
439 if (updatedRow == rowCount) { 421 if (updatedRow == rowCount) {
440 uiContext.imageView.replace(result); 422 uiContext.imageView.replace(result);
441 if (self.message_) 423 if (self.message_)
442 uiContext.prompt.show(self.message_, 2000); 424 uiContext.prompt.show(self.message_, 2000);
443 callback(result); 425 callback(result);
444 } else { 426 } else {
(...skipping 13 matching lines...) Expand all
458 previousRow = updatedRow; 440 previousRow = updatedRow;
459 } 441 }
460 } 442 }
461 443
462 function onProgressInvisible(updatedRow, rowCount) { 444 function onProgressInvisible(updatedRow, rowCount) {
463 if (updatedRow == rowCount) { 445 if (updatedRow == rowCount) {
464 callback(result); 446 callback(result);
465 } 447 }
466 } 448 }
467 449
468 filter.applyByStrips(result, srcCanvas, this.filter_, 450 filter.applyByStrips(result, srcImage, this.filter_,
469 uiContext.imageView ? onProgressVisible : onProgressInvisible); 451 uiContext.imageView ? onProgressVisible : onProgressInvisible);
470 }; 452 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698