diff --git a/examples/icon-negative.html b/examples/icon-negative.html
new file mode 100644
index 0000000000..9fd760e860
--- /dev/null
+++ b/examples/icon-negative.html
@@ -0,0 +1,9 @@
+---
+layout: example.html
+title: Icon Pixel Operations
+shortdesc: Canvas pixel operations on a point icon.
+docs: >
+ Example using an icon to symbolize a point. Click on the icon to select it, and it will be rendered using its negative image.
+tags: "vector, style, icon, marker, canvas, select"
+---
+
diff --git a/examples/icon-negative.js b/examples/icon-negative.js
new file mode 100644
index 0000000000..86f6839889
--- /dev/null
+++ b/examples/icon-negative.js
@@ -0,0 +1,71 @@
+goog.require('ol.Feature');
+goog.require('ol.Map');
+goog.require('ol.View');
+goog.require('ol.geom.Point');
+goog.require('ol.interaction.Select');
+goog.require('ol.layer.Tile');
+goog.require('ol.layer.Vector');
+goog.require('ol.source.Stamen');
+goog.require('ol.source.Vector');
+goog.require('ol.style.Icon');
+goog.require('ol.style.Style');
+
+
+function createStyle(src, img) {
+ return new ol.style.Style({
+ image: new ol.style.Icon(/** @type {olx.style.IconOptions} */ ({
+ anchor: [0.5, 0.96],
+ src: src,
+ img: img,
+ imgSize: img ? [img.width, img.height] : undefined
+ }))
+ });
+}
+
+var iconFeature = new ol.Feature(new ol.geom.Point([0, 0]));
+iconFeature.set('style', createStyle('data/icon.png', undefined));
+
+var map = new ol.Map({
+ layers: [
+ new ol.layer.Tile({
+ source: new ol.source.Stamen({ layer: 'watercolor' })
+ }),
+ new ol.layer.Vector({
+ style: function(feature) { return feature.get('style'); },
+ source: new ol.source.Vector({ features: [iconFeature] })
+ })
+ ],
+ target: document.getElementById('map'),
+ view: new ol.View({
+ center: [0, 0],
+ zoom: 3
+ })
+});
+
+var selectStyle = {};
+var select = new ol.interaction.Select({
+ style: function(feature, resolution) {
+ var image = feature.get('style').getImage().getImage();
+ if (!selectStyle[image.src]) {
+ var canvas = document.createElement('canvas');
+ var context = canvas.getContext('2d');
+ canvas.width = image.width;
+ canvas.height = image.height;
+ context.drawImage(image, 0, 0, image.width, image.height);
+ var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
+ var data = imageData.data;
+ for (var i = 0, ii = data.length; i < ii; i = i + (i % 4 == 2 ? 2 : 1)) {
+ data[i] = 255 - data[i];
+ }
+ context.putImageData(imageData, 0, 0);
+ selectStyle[image.src] = createStyle(undefined, canvas);
+ }
+ return selectStyle[image.src];
+ }
+});
+map.addInteraction(select);
+
+map.on('pointermove', function(evt) {
+ map.getTargetElement().style.cursor =
+ map.hasFeatureAtPixel(evt.pixel) ? 'pointer' : '';
+});
diff --git a/externs/olx.js b/externs/olx.js
index 71deb6a622..5ae51dc0f7 100644
--- a/externs/olx.js
+++ b/externs/olx.js
@@ -6005,7 +6005,7 @@ olx.style.FillOptions.prototype.color;
* anchorXUnits: (ol.style.IconAnchorUnits|undefined),
* anchorYUnits: (ol.style.IconAnchorUnits|undefined),
* crossOrigin: (null|string|undefined),
- * img: (Image|undefined),
+ * img: (Image|HTMLCanvasElement|undefined),
* offset: (Array.|undefined),
* offsetOrigin: (ol.style.IconOrigin|undefined),
* opacity: (number|undefined),
@@ -6074,7 +6074,7 @@ olx.style.IconOptions.prototype.crossOrigin;
* Image object for the icon. If the `src` option is not provided then the
* provided image must already be loaded. And in that case, it is required
* to provide the size of the image, with the `imgSize` option.
- * @type {Image|undefined}
+ * @type {Image|HTMLCanvasElement|undefined}
* @api
*/
olx.style.IconOptions.prototype.img;
diff --git a/src/ol/style/iconstyle.js b/src/ol/style/iconstyle.js
index 8417b08a9d..1f841ee774 100644
--- a/src/ol/style/iconstyle.js
+++ b/src/ol/style/iconstyle.js
@@ -90,7 +90,7 @@ ol.style.Icon = function(opt_options) {
options.crossOrigin !== undefined ? options.crossOrigin : null;
/**
- * @type {Image}
+ * @type {Image|HTMLCanvasElement}
*/
var image = options.img !== undefined ? options.img : null;
@@ -114,7 +114,7 @@ ol.style.Icon = function(opt_options) {
'imgSize must be set when image is provided');
if ((src === undefined || src.length === 0) && image) {
- src = image.src;
+ src = image.src || goog.getUid(image).toString();
}
goog.asserts.assert(src !== undefined && src.length > 0,
'must provide a defined and non-empty src or image');
@@ -244,7 +244,7 @@ ol.style.Icon.prototype.getAnchor = function() {
/**
* Get the image icon.
* @param {number} pixelRatio Pixel ratio.
- * @return {Image} Image element.
+ * @return {Image|HTMLCanvasElement} Image or Canvas element.
* @api
*/
ol.style.Icon.prototype.getImage = function(pixelRatio) {
@@ -368,7 +368,7 @@ ol.style.Icon.prototype.unlistenImageChange = function(listener, thisArg) {
/**
* @constructor
- * @param {Image} image Image.
+ * @param {Image|HTMLCanvasElement} image Image.
* @param {string|undefined} src Src.
* @param {ol.Size} size Size.
* @param {?string} crossOrigin Cross origin.
@@ -388,7 +388,7 @@ ol.style.IconImage_ = function(image, src, size, crossOrigin, imageState) {
/**
* @private
- * @type {Image}
+ * @type {Image|HTMLCanvasElement}
*/
this.image_ = !image ? new Image() : image;
@@ -434,7 +434,7 @@ goog.inherits(ol.style.IconImage_, goog.events.EventTarget);
/**
- * @param {Image} image Image.
+ * @param {Image|HTMLCanvasElement} image Image.
* @param {string} src Src.
* @param {ol.Size} size Size.
* @param {?string} crossOrigin Cross origin.
@@ -499,7 +499,7 @@ ol.style.IconImage_.prototype.handleImageLoad_ = function() {
/**
* @param {number} pixelRatio Pixel ratio.
- * @return {Image} Image element.
+ * @return {Image|HTMLCanvasElement} Image or Canvas element.
*/
ol.style.IconImage_.prototype.getImage = function(pixelRatio) {
return this.image_;
diff --git a/test/spec/ol/style/iconstyle.test.js b/test/spec/ol/style/iconstyle.test.js
index ff99a50fb0..6efb6c57a8 100644
--- a/test/spec/ol/style/iconstyle.test.js
+++ b/test/spec/ol/style/iconstyle.test.js
@@ -9,6 +9,20 @@ goog.require('ol.style.IconOrigin');
describe('ol.style.Icon', function() {
var size = [36, 48];
+ describe('constructor', function() {
+
+ it('caches canvas images with a uid as src', function() {
+ var canvas = document.createElement('canvas');
+ new ol.style.Icon({
+ img: canvas,
+ imgSize: size
+ });
+ expect(ol.style.IconImage_.get(
+ canvas, goog.getUid(canvas), size, '').getImage()).to.eql(canvas);
+ });
+
+ });
+
describe('#getAnchor', function() {
var fractionAnchor = [0.25, 0.25];