15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)'use strict';
62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Command queue is the only way to modify images.
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Supports undo/redo.
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Command execution is asynchronous (callback-based).
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Document} document Document to create canvases in.
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {HTMLCanvasElement} canvas The canvas with the original image.
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(callback)} saveFunction Function to save the image.
152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @constructor
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function CommandQueue(document, canvas, saveFunction) {
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.document_ = document;
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.undo_ = [];
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.redo_ = [];
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.subscribers_ = [];
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.currentImage_ = canvas;
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // Current image may be null or not-null but with width = height = 0.
252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // Copying an image with zero dimensions causes js errors.
262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (this.currentImage_) {
272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    this.baselineImage_ = document.createElement('canvas');
282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    this.baselineImage_.width = this.currentImage_.width;
292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    this.baselineImage_.height = this.currentImage_.height;
302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if (this.currentImage_.width > 0 && this.currentImage_.height > 0) {
312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      var context = this.baselineImage_.getContext('2d');
322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      context.drawImage(this.currentImage_, 0, 0);
332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  } else {
352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    this.baselineImage_ = null;
362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  this.previousImage_ = document.createElement('canvas');
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  this.previousImageAvailable_ = false;
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  this.saveFunction_ = saveFunction;
422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  this.busy_ = false;
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.UIContext_ = {};
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Attach the UI elements to the command queue.
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Once the UI is attached the results of image manipulations are displayed.
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {ImageView} imageView The ImageView object to display the results.
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {ImageEditor.Prompt} prompt Prompt to use with this CommandQueue.
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(boolean)} lock Function to enable/disable buttons etc.
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)CommandQueue.prototype.attachUI = function(imageView, prompt, lock) {
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.UIContext_ = {
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    imageView: imageView,
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    prompt: prompt,
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    lock: lock
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  };
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Execute the action when the queue is not busy.
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function} callback Callback.
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)CommandQueue.prototype.executeWhenReady = function(callback) {
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.isBusy())
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.subscribers_.push(callback);
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    setTimeout(callback, 0);
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {boolean} True if the command queue is busy.
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)CommandQueue.prototype.isBusy = function() { return this.busy_ };
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Set the queue state to busy. Lock the UI.
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)CommandQueue.prototype.setBusy_ = function() {
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.busy_)
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    throw new Error('CommandQueue already busy');
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.busy_ = true;
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.UIContext_.lock)
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.UIContext_.lock(true);
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ImageUtil.trace.resetTimer('command-busy');
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Set the queue state to not busy. Unlock the UI and execute pending actions.
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)CommandQueue.prototype.clearBusy_ = function() {
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!this.busy_)
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    throw new Error('Inconsistent CommandQueue already not busy');
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.busy_ = false;
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Execute the actions requested while the queue was busy.
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while (this.subscribers_.length)
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.subscribers_.shift()();
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.UIContext_.lock)
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.UIContext_.lock(false);
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ImageUtil.trace.reportTimer('command-busy');
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Commit the image change: save and unlock the UI.
1162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {number=} opt_delay Delay in ms (to avoid disrupting the animation).
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)CommandQueue.prototype.commit_ = function(opt_delay) {
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  setTimeout(this.saveFunction_.bind(null, this.clearBusy_.bind(this)),
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      opt_delay || 0);
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Internal function to execute the command in a given context.
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Command} command The command to execute.
1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {Object} uiContext The UI context.
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function} callback Completion callback.
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)CommandQueue.prototype.doExecute_ = function(command, uiContext, callback) {
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!this.currentImage_)
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    throw new Error('Cannot operate on null image');
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Remember one previous image so that the first undo is as fast as possible.
1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  this.previousImage_.width = this.currentImage_.width;
1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  this.previousImage_.height = this.currentImage_.height;
1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  this.previousImageAvailable_ = true;
1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  var context = this.previousImage_.getContext('2d');
1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  context.drawImage(this.currentImage_, 0, 0);
1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  command.execute(
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.document_,
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.currentImage_,
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      function(result, opt_delay) {
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.currentImage_ = result;
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        callback(opt_delay);
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }.bind(this),
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      uiContext);
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Executes the command.
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Command} command Command to execute.
1572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {boolean=} opt_keep_redo True if redo stack should not be cleared.
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)CommandQueue.prototype.execute = function(command, opt_keep_redo) {
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.setBusy_();
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!opt_keep_redo)
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.redo_ = [];
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.undo_.push(command);
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.doExecute_(command, this.UIContext_, this.commit_.bind(this));
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {boolean} True if Undo is applicable.
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)CommandQueue.prototype.canUndo = function() {
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return this.undo_.length != 0;
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Undo the most recent command.
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)CommandQueue.prototype.undo = function() {
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!this.canUndo())
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    throw new Error('Cannot undo');
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.setBusy_();
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var command = this.undo_.pop();
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.redo_.push(command);
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var self = this;
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  function complete() {
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var delay = command.revertView(
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.currentImage_, self.UIContext_.imageView);
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.commit_(delay);
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (this.previousImageAvailable_) {
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // First undo after an execute call.
1992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    this.currentImage_.width = this.previousImage_.width;
2002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    this.currentImage_.height = this.previousImage_.height;
2012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    var context = this.currentImage_.getContext('2d');
2022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    context.drawImage(this.previousImage_, 0, 0);
2032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    // Free memory.
2052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    this.previousImage_.width = 0;
2062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    this.previousImage_.height = 0;
2072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    this.previousImageAvailable_ = false;
2082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    complete();
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // TODO(kaznacheev) Consider recalculating previousImage_ right here
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // by replaying the commands in the background.
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
2132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    this.currentImage_.width = this.baselineImage_.width;
2142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    this.currentImage_.height = this.baselineImage_.height;
2152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    var context = this.currentImage_.getContext('2d');
2162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    context.drawImage(this.baselineImage_, 0, 0);
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    var replay = function(index) {
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (index < self.undo_.length)
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.doExecute_(self.undo_[index], {}, replay.bind(null, index + 1));
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else {
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        complete();
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
2242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    };
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    replay(0);
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {boolean} True if Redo is applicable.
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)CommandQueue.prototype.canRedo = function() {
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return this.redo_.length != 0;
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Repeat the command that was recently un-done.
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)CommandQueue.prototype.redo = function() {
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!this.canRedo())
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    throw new Error('Cannot redo');
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.execute(this.redo_.pop(), true);
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * Closes internal buffers. Call to ensure, that internal buffers are freed
2492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * as soon as possible.
2502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) */
2512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)CommandQueue.prototype.close = function() {
2522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // Free memory used by the undo buffer.
2532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  this.previousImage_.width = 0;
2542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  this.previousImage_.height = 0;
2552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  this.previousImageAvailable_ = false;
2562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (this.baselineImage_) {
2582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    this.baselineImage_.width = 0;
2592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    this.baselineImage_.height = 0;
2602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
2612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)};
2622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/**
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Command object encapsulates an operation on an image and a way to visualize
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * its result.
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} name Command name.
2682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @constructor
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function Command(name) {
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.name_ = name;
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {string} String representation of the command.
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Command.prototype.toString = function() {
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 'Command ' + this.name_;
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Execute the command and visualize its results.
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * The two actions are combined into one method because sometimes it is nice
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * to be able to show partial results for slower operations.
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Document} document Document on which to execute command.
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {HTMLCanvasElement} srcCanvas Canvas to execute on.
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(HTMLCanvasElement, number)} callback Callback to call on
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *   completion.
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} uiContext Context to work in.
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Command.prototype.execute = function(document, srcCanvas, callback, uiContext) {
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  console.error('Command.prototype.execute not implemented');
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Visualize reversion of the operation.
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {HTMLCanvasElement} canvas Image data to use.
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {ImageView} imageView ImageView to revert.
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {number} Animation duration in ms.
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Command.prototype.revertView = function(canvas, imageView) {
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  imageView.replace(canvas);
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 0;
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Creates canvas to render on.
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Document} document Document to create canvas in.
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {HTMLCanvasElement} srcCanvas to copy optional dimensions from.
3142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {number=} opt_width new canvas width.
3152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {number=} opt_height new canvas height.
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {HTMLCanvasElement} Newly created canvas.
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @private
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Command.prototype.createCanvas_ = function(
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    document, srcCanvas, opt_width, opt_height) {
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var result = document.createElement('canvas');
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  result.width = opt_width || srcCanvas.width;
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  result.height = opt_height || srcCanvas.height;
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return result;
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Rotate command
3302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {number} rotate90 Rotation angle in 90 degree increments (signed).
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @constructor
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @extends {Command}
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Command.Rotate = function(rotate90) {
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Command.call(this, 'rotate(' + rotate90 * 90 + 'deg)');
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.rotate90_ = rotate90;
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Command.Rotate.prototype = { __proto__: Command.prototype };
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @override */
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Command.Rotate.prototype.execute = function(
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    document, srcCanvas, callback, uiContext) {
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var result = this.createCanvas_(
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      document,
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      srcCanvas,
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      (this.rotate90_ & 1) ? srcCanvas.height : srcCanvas.width,
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      (this.rotate90_ & 1) ? srcCanvas.width : srcCanvas.height);
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ImageUtil.drawImageTransformed(
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      result, srcCanvas, 1, 1, this.rotate90_ * Math.PI / 2);
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var delay;
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (uiContext.imageView) {
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    delay = uiContext.imageView.replaceAndAnimate(result, null, this.rotate90_);
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  setTimeout(callback, 0, result, delay);
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @override */
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Command.Rotate.prototype.revertView = function(canvas, imageView) {
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return imageView.replaceAndAnimate(canvas, null, -this.rotate90_);
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Crop command.
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
3674e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) * @param {Rect} imageRect Crop rectangle in image coordinates.
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @constructor
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @extends {Command}
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Command.Crop = function(imageRect) {
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Command.call(this, 'crop' + imageRect.toString());
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.imageRect_ = imageRect;
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Command.Crop.prototype = { __proto__: Command.prototype };
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @override */
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Command.Crop.prototype.execute = function(
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    document, srcCanvas, callback, uiContext) {
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var result = this.createCanvas_(
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      document, srcCanvas, this.imageRect_.width, this.imageRect_.height);
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Rect.drawImage(result.getContext('2d'), srcCanvas, null, this.imageRect_);
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var delay;
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (uiContext.imageView) {
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    delay = uiContext.imageView.replaceAndAnimate(result, this.imageRect_, 0);
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  setTimeout(callback, 0, result, delay);
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @override */
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Command.Crop.prototype.revertView = function(canvas, imageView) {
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return imageView.animateAndReplace(canvas, this.imageRect_);
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Filter command.
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
4002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {string} name Command name.
4012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {function(ImageData,ImageData,number,number)} filter Filter function.
4022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {string} message Message to display when done.
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @constructor
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @extends {Command}
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Command.Filter = function(name, filter, message) {
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Command.call(this, name);
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.filter_ = filter;
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.message_ = message;
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Command.Filter.prototype = { __proto__: Command.prototype };
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)/** @override */
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Command.Filter.prototype.execute = function(
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    document, srcCanvas, callback, uiContext) {
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var result = this.createCanvas_(document, srcCanvas);
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var self = this;
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var previousRow = 0;
4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  function onProgressVisible(updatedRow, rowCount) {
4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (updatedRow == rowCount) {
4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      uiContext.imageView.replace(result);
4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (self.message_)
4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        uiContext.prompt.show(self.message_, 2000);
4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      callback(result);
4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var viewport = uiContext.imageView.viewport_;
4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var imageStrip = new Rect(viewport.getImageBounds());
4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      imageStrip.top = previousRow;
4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      imageStrip.height = updatedRow - previousRow;
4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var screenStrip = new Rect(viewport.getImageBoundsOnScreen());
4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      screenStrip.top = Math.round(viewport.imageToScreenY(previousRow));
4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      screenStrip.height =
4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          Math.round(viewport.imageToScreenY(updatedRow)) - screenStrip.top;
4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      uiContext.imageView.paintDeviceRect(
4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          viewport.screenToDeviceRect(screenStrip), result, imageStrip);
4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      previousRow = updatedRow;
4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  function onProgressInvisible(updatedRow, rowCount) {
4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (updatedRow == rowCount) {
4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      callback(result);
4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  filter.applyByStrips(result, srcCanvas, this.filter_,
4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      uiContext.imageView ? onProgressVisible : onProgressInvisible);
4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
456