var inherit = require('../inherit');
var registerFeature = require('../registry').registerFeature;
var quadFeature = require('../quadFeature');
var util = require('../util');
/**
* Create a new instance of class quadFeature.
*
* @class
* @alias geo.canvas.quadFeature
* @param {geo.quadFeature.spec} arg Options object.
* @extends geo.quadFeature
* @returns {geo.canvas.quadFeature}
*/
var canvas_quadFeature = function (arg) {
'use strict';
if (!(this instanceof canvas_quadFeature)) {
return new canvas_quadFeature(arg);
}
quadFeature.call(this, arg);
var object = require('./object');
object.call(this);
var $ = require('jquery');
var m_this = this,
s_exit = this._exit,
s_init = this._init,
s_update = this._update,
m_quads;
/**
* Build this feature.
*/
this._build = function () {
if (!m_this.position()) {
return;
}
m_quads = m_this._generateQuads();
if (m_quads.imgQuads) {
m_quads.imgQuads.sort(function (a, b) {
return a.pos[2] - b.pos[2];
});
}
m_this.buildTime().modified();
};
/**
* When any quad may have changed, ask for a animation frame callback so we
* can update the quad on the next animation cycle.
*
* This is called when a video qaud may have changed play state.
* @param {object} quad The quad record that triggered this.
* @param {jQuery.Event} [evt] The event that triggered this.
*/
this._checkQuadUpdate = function (quad, evt) {
m_this.layer().map().scheduleAnimationFrame(m_this._checkIfChanged);
};
/**
* Check if any video quads are changing or need rerendering. If any are
* changing (because they are seeking), defer rendering and check again. If
* any need rendering, schedule it.
*/
this._checkIfChanged = function () {
if (!m_quads || !m_quads.vidQuads || !m_quads.vidQuads.length) {
return;
}
var render = false, changing = false;
$.each(m_quads.vidQuads, function (idx, quad) {
if (quad.video && quad.video.HAVE_CURRENT_DATA !== undefined) {
if (!quad.video.seeking && quad.video.readyState >= quad.video.HAVE_CURRENT_DATA) {
render = true;
}
if (!quad.video.paused || quad.video.seeking) {
changing = true;
}
}
});
if (render && m_this.renderer()) {
m_this.renderer()._render();
}
if (changing) {
m_this.layer().map().scheduleAnimationFrame(m_this._checkIfChanged);
}
};
/**
* Render all of the color quads.
*
* @param {CanvasRenderingContext2D} context2d The rendering context.
* @param {geo.map} map The current renderer's parent map.
*/
this._renderColorQuads = function (context2d, map) {
if (!m_quads.clrQuads || !m_quads.clrQuads.length) {
return;
}
var oldAlpha = context2d.globalAlpha;
var opacity = oldAlpha;
$.each(m_quads.clrQuads, function (idx, quad) {
var p0 = map.gcsToDisplay({x: quad.pos[0], y: quad.pos[1]}, null),
p1 = map.gcsToDisplay({x: quad.pos[3], y: quad.pos[4]}, null),
p2 = map.gcsToDisplay({x: quad.pos[6], y: quad.pos[7]}, null),
p3 = map.gcsToDisplay({x: quad.pos[9], y: quad.pos[10]}, null);
if (quad.opacity !== opacity) {
opacity = quad.opacity;
context2d.globalAlpha = opacity;
}
context2d.fillStyle = util.convertColorToHex(quad.color, true);
context2d.beginPath();
context2d.moveTo(p0.x, p0.y);
context2d.lineTo(p1.x, p1.y);
context2d.lineTo(p3.x, p3.y);
context2d.lineTo(p2.x, p2.y);
context2d.closePath();
context2d.fill();
});
if (opacity !== oldAlpha) {
context2d.globalAlpha = oldAlpha;
}
};
/**
* Render all of the image and video quads.
*
* @param {CanvasRenderingContext2D} context2d The rendering context.
* @param {geo.map} map The current renderer's parent map.
*/
this._renderImageAndVideoQuads = function (context2d, map) {
if ((!m_quads.imgQuads || !m_quads.imgQuads.length) &&
(!m_quads.vidQuads || !m_quads.vidQuads.length)) {
return;
}
var oldAlpha = context2d.globalAlpha;
var opacity = oldAlpha;
var nearestPixel = m_this.nearestPixel();
if (nearestPixel !== undefined) {
if (nearestPixel !== true && util.isNonNullFinite(nearestPixel)) {
const curZoom = m_this.layer().map().zoom();
nearestPixel = curZoom >= nearestPixel;
}
context2d.imageSmoothingEnabled = !nearestPixel;
}
$.each([m_quads.imgQuads, m_quads.vidQuads], function (listidx, quadlist) {
if (!quadlist) {
return;
}
$.each(quadlist, function (idx, quad) {
var src, w, h;
if (quad.image) {
src = quad.image;
w = src.width;
h = src.height;
} else if (quad.video) {
src = quad.video;
w = src.videoWidth;
h = src.videoHeight;
if (src.seeking) {
return;
}
}
if (!src || !w || !h || quad.opacity <= 0) {
return;
}
// Canvas transform is affine, so quad has to be a parallelogram
// Also, canvas has no way to render z.
var p0 = map.gcsToDisplay({x: quad.pos[0], y: quad.pos[1]}, null),
p2 = map.gcsToDisplay({x: quad.pos[6], y: quad.pos[7]}, null),
p3 = map.gcsToDisplay({x: quad.pos[9], y: quad.pos[10]}, null);
const cw = Math.min(w, quad.crop ? quad.crop.x || w : w);
const ch = Math.min(h, quad.crop ? quad.crop.y || h : h);
context2d.setTransform((p3.x - p2.x) / cw, (p3.y - p2.y) / cw,
(p0.x - p2.x) / ch, (p0.y - p2.y) / ch,
p2.x, p2.y);
if (quad.opacity !== opacity) {
opacity = quad.opacity;
context2d.globalAlpha = opacity;
}
if (!quad.crop) {
context2d.drawImage(src, 0, 0);
} else {
const cropx0 = Math.max(0, quad.crop.left || 0),
cropy0 = Math.max(0, quad.crop.top || 0),
cropx1 = Math.min(w, quad.crop.right || w),
cropy1 = Math.min(h, quad.crop.bottom || h);
if (w && h && cw > 0 && ch > 0 && cropx1 > cropx0 && cropy1 > cropy0) {
context2d.drawImage(
src,
cropx0, cropy0, cropx1 - cropx0, cropy1 - cropy0,
0, 0, cw, ch);
}
}
});
});
if (opacity !== oldAlpha) {
context2d.globalAlpha = oldAlpha;
}
context2d.setTransform(1, 0, 0, 1, 0, 0);
context2d.imageSmoothingEnabled = true;
};
/**
* If this returns true, the render will be skipped and tried again on the
* next animation frame.
*
* @returns {boolean} Truthy to delay rendering.
*/
this._delayRender = function () {
var delay = false;
if (m_quads && m_quads.vidQuads && m_quads.vidQuads.length) {
$.each(m_quads.vidQuads, function (idx, quad) {
if (quad.video && quad.video.HAVE_CURRENT_DATA !== undefined) {
delay |= (quad.video.seeking && quad.delayRenderWhenSeeking);
}
});
}
return delay;
};
/**
* Render all of the quads.
*
* @param {CanvasRenderingContext2D} context The rendering context.
* @param {geo.map} map The current renderer's parent map.
*/
this._renderOnCanvas = function (context, map) {
if (m_quads) {
m_this._renderImageAndVideoQuads(context, map);
m_this._renderColorQuads(context, map);
}
};
/**
* Update.
*/
this._update = function () {
s_update.call(m_this);
if (m_this.buildTime().timestamp() <= m_this.dataTime().timestamp() ||
m_this.updateTime().timestamp() < m_this.timestamp()) {
m_this._build();
}
m_this.updateTime().modified();
m_this.layer().map().scheduleAnimationFrame(m_this._checkIfChanged);
};
/**
* Initialize.
*/
this._init = function () {
s_init.call(m_this, arg);
};
/**
* Destroy.
*/
this._exit = function () {
s_exit.call(m_this);
};
m_this._init(arg);
return this;
};
inherit(canvas_quadFeature, quadFeature);
// Now register it
var capabilities = {};
capabilities[quadFeature.capabilities.color] = true;
capabilities[quadFeature.capabilities.image] = true;
capabilities[quadFeature.capabilities.imageCrop] = true;
capabilities[quadFeature.capabilities.imageFixedScale] = true;
capabilities[quadFeature.capabilities.imageFull] = false;
capabilities[quadFeature.capabilities.canvas] = true;
capabilities[quadFeature.capabilities.video] = true;
registerFeature('canvas', 'quad', canvas_quadFeature, capabilities);
module.exports = canvas_quadFeature;