michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cc = Components.classes; michael@0: const Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: const XHTML_NS = "http://www.w3.org/1999/xhtml"; michael@0: const MAXIMUM_PIXELS = Math.pow(144, 2); michael@0: michael@0: function ColorAnalyzer() { michael@0: // a queue of callbacks for each job we give to the worker michael@0: this.callbacks = []; michael@0: michael@0: this.hiddenWindowDoc = Cc["@mozilla.org/appshell/appShellService;1"]. michael@0: getService(Ci.nsIAppShellService). michael@0: hiddenDOMWindow.document; michael@0: michael@0: this.worker = new ChromeWorker("resource://gre/modules/ColorAnalyzer_worker.js"); michael@0: this.worker.onmessage = this.onWorkerMessage.bind(this); michael@0: this.worker.onerror = this.onWorkerError.bind(this); michael@0: } michael@0: michael@0: ColorAnalyzer.prototype = { michael@0: findRepresentativeColor: function ColorAnalyzer_frc(imageURI, callback) { michael@0: function cleanup() { michael@0: image.removeEventListener("load", loadListener); michael@0: image.removeEventListener("error", errorListener); michael@0: } michael@0: let image = this.hiddenWindowDoc.createElementNS(XHTML_NS, "img"); michael@0: let loadListener = this.onImageLoad.bind(this, image, callback, cleanup); michael@0: let errorListener = this.onImageError.bind(this, image, callback, cleanup); michael@0: image.addEventListener("load", loadListener); michael@0: image.addEventListener("error", errorListener); michael@0: image.src = imageURI.spec; michael@0: }, michael@0: michael@0: onImageLoad: function ColorAnalyzer_onImageLoad(image, callback, cleanup) { michael@0: if (image.naturalWidth * image.naturalHeight > MAXIMUM_PIXELS) { michael@0: // this will probably take too long to process - fail michael@0: callback.onComplete(false); michael@0: } else { michael@0: let canvas = this.hiddenWindowDoc.createElementNS(XHTML_NS, "canvas"); michael@0: canvas.width = image.naturalWidth; michael@0: canvas.height = image.naturalHeight; michael@0: let ctx = canvas.getContext("2d"); michael@0: ctx.drawImage(image, 0, 0); michael@0: this.startJob(ctx.getImageData(0, 0, canvas.width, canvas.height), michael@0: callback); michael@0: } michael@0: cleanup(); michael@0: }, michael@0: michael@0: onImageError: function ColorAnalyzer_onImageError(image, callback, cleanup) { michael@0: Cu.reportError("ColorAnalyzer: image at " + image.src + " didn't load"); michael@0: callback.onComplete(false); michael@0: cleanup(); michael@0: }, michael@0: michael@0: startJob: function ColorAnalyzer_startJob(imageData, callback) { michael@0: this.callbacks.push(callback); michael@0: this.worker.postMessage({ imageData: imageData, maxColors: 1 }); michael@0: }, michael@0: michael@0: onWorkerMessage: function ColorAnalyzer_onWorkerMessage(event) { michael@0: // colors can be empty on failure michael@0: if (event.data.colors.length < 1) { michael@0: this.callbacks.shift().onComplete(false); michael@0: } else { michael@0: this.callbacks.shift().onComplete(true, event.data.colors[0]); michael@0: } michael@0: }, michael@0: michael@0: onWorkerError: function ColorAnalyzer_onWorkerError(error) { michael@0: // this shouldn't happen, but just in case michael@0: error.preventDefault(); michael@0: Cu.reportError("ColorAnalyzer worker: " + error.message); michael@0: this.callbacks.shift().onComplete(false); michael@0: }, michael@0: michael@0: classID: Components.ID("{d056186c-28a0-494e-aacc-9e433772b143}"), michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.mozIColorAnalyzer]) michael@0: }; michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ColorAnalyzer]);