package.style.IconImage.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ol Show documentation
Show all versions of ol Show documentation
OpenLayers mapping library
The newest version!
/**
* @module ol/style/IconImage
*/
import EventTarget from '../events/Target.js';
import EventType from '../events/EventType.js';
import ImageState from '../ImageState.js';
import {asString} from '../color.js';
import {createCanvasContext2D} from '../dom.js';
import {decodeFallback} from '../Image.js';
import {shared as iconImageCache} from './IconImageCache.js';
/**
* @type {CanvasRenderingContext2D}
*/
let taintedTestContext = null;
class IconImage extends EventTarget {
/**
* @param {HTMLImageElement|HTMLCanvasElement|ImageBitmap|null} image Image.
* @param {string|undefined} src Src.
* @param {?string} crossOrigin Cross origin.
* @param {import("../ImageState.js").default|undefined} imageState Image state.
* @param {import("../color.js").Color|string|null} color Color.
*/
constructor(image, src, crossOrigin, imageState, color) {
super();
/**
* @private
* @type {HTMLImageElement|HTMLCanvasElement|ImageBitmap}
*/
this.hitDetectionImage_ = null;
/**
* @private
* @type {HTMLImageElement|HTMLCanvasElement|ImageBitmap|null}
*/
this.image_ = image;
/**
* @private
* @type {string|null}
*/
this.crossOrigin_ = crossOrigin;
/**
* @private
* @type {Object}
*/
this.canvas_ = {};
/**
* @private
* @type {import("../color.js").Color|string|null}
*/
this.color_ = color;
/**
* @private
* @type {import("../ImageState.js").default}
*/
this.imageState_ = imageState === undefined ? ImageState.IDLE : imageState;
/**
* @private
* @type {import("../size.js").Size|null}
*/
this.size_ =
image && image.width && image.height ? [image.width, image.height] : null;
/**
* @private
* @type {string|undefined}
*/
this.src_ = src;
/**
* @private
*/
this.tainted_;
/**
* @private
* @type {Promise|null}
*/
this.ready_ = null;
}
/**
* @private
*/
initializeImage_() {
this.image_ = new Image();
if (this.crossOrigin_ !== null) {
this.image_.crossOrigin = this.crossOrigin_;
}
}
/**
* @private
* @return {boolean} The image canvas is tainted.
*/
isTainted_() {
if (this.tainted_ === undefined && this.imageState_ === ImageState.LOADED) {
if (!taintedTestContext) {
taintedTestContext = createCanvasContext2D(1, 1, undefined, {
willReadFrequently: true,
});
}
taintedTestContext.drawImage(this.image_, 0, 0);
try {
taintedTestContext.getImageData(0, 0, 1, 1);
this.tainted_ = false;
} catch (e) {
taintedTestContext = null;
this.tainted_ = true;
}
}
return this.tainted_ === true;
}
/**
* @private
*/
dispatchChangeEvent_() {
this.dispatchEvent(EventType.CHANGE);
}
/**
* @private
*/
handleImageError_() {
this.imageState_ = ImageState.ERROR;
this.dispatchChangeEvent_();
}
/**
* @private
*/
handleImageLoad_() {
this.imageState_ = ImageState.LOADED;
this.size_ = [this.image_.width, this.image_.height];
this.dispatchChangeEvent_();
}
/**
* @param {number} pixelRatio Pixel ratio.
* @return {HTMLImageElement|HTMLCanvasElement|ImageBitmap} Image or Canvas element or image bitmap.
*/
getImage(pixelRatio) {
if (!this.image_) {
this.initializeImage_();
}
this.replaceColor_(pixelRatio);
return this.canvas_[pixelRatio] ? this.canvas_[pixelRatio] : this.image_;
}
/**
* @param {number} pixelRatio Pixel ratio.
* @return {number} Image or Canvas element.
*/
getPixelRatio(pixelRatio) {
this.replaceColor_(pixelRatio);
return this.canvas_[pixelRatio] ? pixelRatio : 1;
}
/**
* @return {import("../ImageState.js").default} Image state.
*/
getImageState() {
return this.imageState_;
}
/**
* @return {HTMLImageElement|HTMLCanvasElement|ImageBitmap} Image element.
*/
getHitDetectionImage() {
if (!this.image_) {
this.initializeImage_();
}
if (!this.hitDetectionImage_) {
if (this.isTainted_()) {
const width = this.size_[0];
const height = this.size_[1];
const context = createCanvasContext2D(width, height);
context.fillRect(0, 0, width, height);
this.hitDetectionImage_ = context.canvas;
} else {
this.hitDetectionImage_ = this.image_;
}
}
return this.hitDetectionImage_;
}
/**
* Get the size of the icon (in pixels).
* @return {import("../size.js").Size} Image size.
*/
getSize() {
return this.size_;
}
/**
* @return {string|undefined} Image src.
*/
getSrc() {
return this.src_;
}
/**
* Load not yet loaded URI.
*/
load() {
if (this.imageState_ !== ImageState.IDLE) {
return;
}
if (!this.image_) {
this.initializeImage_();
}
this.imageState_ = ImageState.LOADING;
try {
if (this.src_ !== undefined) {
/** @type {HTMLImageElement} */ (this.image_).src = this.src_;
}
} catch (e) {
this.handleImageError_();
}
if (this.image_ instanceof HTMLImageElement) {
decodeFallback(this.image_, this.src_)
.then((image) => {
this.image_ = image;
this.handleImageLoad_();
})
.catch(this.handleImageError_.bind(this));
}
}
/**
* @param {number} pixelRatio Pixel ratio.
* @private
*/
replaceColor_(pixelRatio) {
if (
!this.color_ ||
this.canvas_[pixelRatio] ||
this.imageState_ !== ImageState.LOADED
) {
return;
}
const image = this.image_;
const ctx = createCanvasContext2D(
Math.ceil(image.width * pixelRatio),
Math.ceil(image.height * pixelRatio),
);
const canvas = ctx.canvas;
ctx.scale(pixelRatio, pixelRatio);
ctx.drawImage(image, 0, 0);
ctx.globalCompositeOperation = 'multiply';
ctx.fillStyle = asString(this.color_);
ctx.fillRect(0, 0, canvas.width / pixelRatio, canvas.height / pixelRatio);
ctx.globalCompositeOperation = 'destination-in';
ctx.drawImage(image, 0, 0);
this.canvas_[pixelRatio] = canvas;
}
/**
* @return {Promise} Promise that resolves when the image is loaded.
*/
ready() {
if (!this.ready_) {
this.ready_ = new Promise((resolve) => {
if (
this.imageState_ === ImageState.LOADED ||
this.imageState_ === ImageState.ERROR
) {
resolve();
} else {
const onChange = () => {
if (
this.imageState_ === ImageState.LOADED ||
this.imageState_ === ImageState.ERROR
) {
this.removeEventListener(EventType.CHANGE, onChange);
resolve();
}
};
this.addEventListener(EventType.CHANGE, onChange);
}
});
}
return this.ready_;
}
}
/**
* @param {HTMLImageElement|HTMLCanvasElement|ImageBitmap|null} image Image.
* @param {string|undefined} cacheKey Src.
* @param {?string} crossOrigin Cross origin.
* @param {import("../ImageState.js").default|undefined} imageState Image state.
* @param {import("../color.js").Color|string|null} color Color.
* @param {boolean} [pattern] Also cache a `repeat` pattern with the icon image.
* @return {IconImage} Icon image.
*/
export function get(image, cacheKey, crossOrigin, imageState, color, pattern) {
let iconImage =
cacheKey === undefined
? undefined
: iconImageCache.get(cacheKey, crossOrigin, color);
if (!iconImage) {
iconImage = new IconImage(
image,
image && 'src' in image ? image.src || undefined : cacheKey,
crossOrigin,
imageState,
color,
);
iconImageCache.set(cacheKey, crossOrigin, color, iconImage, pattern);
}
if (
pattern &&
iconImage &&
!iconImageCache.getPattern(cacheKey, crossOrigin, color)
) {
iconImageCache.set(cacheKey, crossOrigin, color, iconImage, pattern);
}
return iconImage;
}
export default IconImage;