Merge pull request #11277 from sbrunner/hires-regularshape-master

Have high resolution regular shape
This commit is contained in:
Andreas Hocevar
2020-07-26 12:39:44 +02:00
committed by GitHub
12 changed files with 368 additions and 110 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

+66
View File
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="20"
height="20"
version="1.1"
id="svg6"
sodipodi:docname="dot.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1533"
inkscape:window-height="845"
id="namedview8"
showgrid="false"
inkscape:zoom="11.8"
inkscape:cx="-35.042373"
inkscape:cy="11.5"
inkscape:window-x="67"
inkscape:window-y="102"
inkscape:window-maximized="1"
inkscape:current-layer="g4"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<g
id="g4"
transform="translate(1.5,-1.5)">
<circle
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path822"
cx="8.5"
cy="11.5"
r="8.5"
inkscape:export-xdpi="400"
inkscape:export-ydpi="400" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+63 -4
View File
@@ -1,7 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg"> xmlns:dc="http://purl.org/dc/elements/1.1/"
<g> xmlns:cc="http://creativecommons.org/ns#"
<rect width="20" height="20" style="fill:#fff; stroke-width:4px; stroke:#000" /> xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="20"
height="20"
version="1.1"
id="svg6"
sodipodi:docname="square.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1533"
inkscape:window-height="845"
id="namedview8"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="11.8"
inkscape:cx="11.5"
inkscape:cy="11.5"
inkscape:window-x="67"
inkscape:window-y="102"
inkscape:window-maximized="1"
inkscape:current-layer="svg6" />
<g
id="g4"
transform="matrix(0.85,0,0,0.85,1.5,1.5)"
style="fill:#fefefe;fill-opacity:1;stroke:#000000;stroke-width:3.52941179;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
<rect
width="20"
height="20"
style="fill:#fefefe;fill-opacity:1;stroke:#000000;stroke-width:3.52941179;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect2"
x="0"
y="0" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 225 B

After

Width:  |  Height:  |  Size: 1.9 KiB

+1 -1
View File
@@ -3,7 +3,7 @@ layout: example.html
title: Icon Colors title: Icon Colors
shortdesc: Example assigning a custom color to an icon shortdesc: Example assigning a custom color to an icon
docs: > docs: >
Example assigning a custom color to an icon. The features in this examples are all using the same image with the different colors coming from the javascript file. Example assigning a custom color to an icon. The icon styles in this example use images with a white fill. For some features, custom colors set using the `color` property.
Note that icon files need to obey the same origin policy or send proper CORS headers for this to work. When relying on CORS headers, the `ol/style/Icon` must be configured with `crossOrigin: 'anonymous'`. Note that icon files need to obey the same origin policy or send proper CORS headers for this to work. When relying on CORS headers, the `ol/style/Icon` must be configured with `crossOrigin: 'anonymous'`.
tags: "vector, style, icon, marker" tags: "vector, style, icon, marker"
resources: resources:
+35 -5
View File
@@ -19,12 +19,19 @@ const london = new Feature({
const madrid = new Feature({ const madrid = new Feature({
geometry: new Point(fromLonLat([-3.683333, 40.4])), geometry: new Point(fromLonLat([-3.683333, 40.4])),
}); });
const paris = new Feature({
geometry: new Point(fromLonLat([2.353, 48.8566])),
});
const berlin = new Feature({
geometry: new Point(fromLonLat([13.3884, 52.5169])),
});
rome.setStyle( rome.setStyle(
new Style({ new Style({
image: new Icon({ image: new Icon({
color: '#8959A8', color: '#BADA55',
crossOrigin: 'anonymous', crossOrigin: 'anonymous',
// For Internet Explorer 11
imgSize: [20, 20], imgSize: [20, 20],
src: 'data/square.svg', src: 'data/square.svg',
}), }),
@@ -36,7 +43,8 @@ london.setStyle(
image: new Icon({ image: new Icon({
color: '#4271AE', color: '#4271AE',
crossOrigin: 'anonymous', crossOrigin: 'anonymous',
src: 'data/dot.png', src: 'data/bigdot.png',
scale: 0.2,
}), }),
}) })
); );
@@ -44,15 +52,37 @@ london.setStyle(
madrid.setStyle( madrid.setStyle(
new Style({ new Style({
image: new Icon({ image: new Icon({
color: [113, 140, 0],
crossOrigin: 'anonymous', crossOrigin: 'anonymous',
src: 'data/dot.png', src: 'data/bigdot.png',
scale: 0.2,
}), }),
}) })
); );
paris.setStyle(
new Style({
image: new Icon({
color: '#8959A8',
crossOrigin: 'anonymous',
// For Internet Explorer 11
imgSize: [20, 20],
src: 'data/dot.svg',
}),
})
);
berlin.setStyle(
new Style({
image: new Icon({
crossOrigin: 'anonymous',
// For Internet Explorer 11
imgSize: [20, 20],
src: 'data/dot.svg',
}),
})
);
const vectorSource = new VectorSource({ const vectorSource = new VectorSource({
features: [rome, london, madrid], features: [rome, london, madrid, paris, berlin],
}); });
const vectorLayer = new VectorLayer({ const vectorLayer = new VectorLayer({
+42 -22
View File
@@ -32,6 +32,12 @@ class CanvasImageBuilder extends CanvasBuilder {
*/ */
this.image_ = null; this.image_ = null;
/**
* @private
* @type {number|undefined}
*/
this.imagePixelRatio_ = undefined;
/** /**
* @private * @private
* @type {number|undefined} * @type {number|undefined}
@@ -136,17 +142,20 @@ class CanvasImageBuilder extends CanvasBuilder {
myEnd, myEnd,
this.image_, this.image_,
// Remaining arguments to DRAW_IMAGE are in alphabetical order // Remaining arguments to DRAW_IMAGE are in alphabetical order
this.anchorX_, this.anchorX_ * this.imagePixelRatio_,
this.anchorY_, this.anchorY_ * this.imagePixelRatio_,
this.declutterGroups_, this.declutterGroups_,
this.height_, Math.ceil(this.height_ * this.imagePixelRatio_),
this.opacity_, this.opacity_,
this.originX_, this.originX_,
this.originY_, this.originY_,
this.rotateWithView_, this.rotateWithView_,
this.rotation_, this.rotation_,
[this.scale_[0] * this.pixelRatio, this.scale_[1] * this.pixelRatio], [
this.width_, (this.scale_[0] * this.pixelRatio) / this.imagePixelRatio_,
(this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_,
],
Math.ceil(this.width_ * this.imagePixelRatio_),
]); ]);
this.hitDetectionInstructions.push([ this.hitDetectionInstructions.push([
CanvasInstruction.DRAW_IMAGE, CanvasInstruction.DRAW_IMAGE,
@@ -154,17 +163,20 @@ class CanvasImageBuilder extends CanvasBuilder {
myEnd, myEnd,
this.hitDetectionImage_, this.hitDetectionImage_,
// Remaining arguments to DRAW_IMAGE are in alphabetical order // Remaining arguments to DRAW_IMAGE are in alphabetical order
this.anchorX_, this.anchorX_ * this.imagePixelRatio_,
this.anchorY_, this.anchorY_ * this.imagePixelRatio_,
this.declutterGroups_, this.declutterGroups_,
this.height_, Math.ceil(this.height_ * this.imagePixelRatio_),
this.opacity_, this.opacity_,
this.originX_, this.originX_,
this.originY_, this.originY_,
this.rotateWithView_, this.rotateWithView_,
this.rotation_, this.rotation_,
this.scale_, [
this.width_, (this.scale_[0] * this.pixelRatio) / this.imagePixelRatio_,
(this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_,
],
Math.ceil(this.width_ * this.imagePixelRatio_),
]); ]);
this.endGeometry(feature); this.endGeometry(feature);
} }
@@ -193,17 +205,20 @@ class CanvasImageBuilder extends CanvasBuilder {
myEnd, myEnd,
this.image_, this.image_,
// Remaining arguments to DRAW_IMAGE are in alphabetical order // Remaining arguments to DRAW_IMAGE are in alphabetical order
this.anchorX_, this.anchorX_ * this.imagePixelRatio_,
this.anchorY_, this.anchorY_ * this.imagePixelRatio_,
this.declutterGroups_, this.declutterGroups_,
this.height_, Math.ceil(this.height_ * this.imagePixelRatio_),
this.opacity_, this.opacity_,
this.originX_, this.originX_,
this.originY_, this.originY_,
this.rotateWithView_, this.rotateWithView_,
this.rotation_, this.rotation_,
[this.scale_[0] * this.pixelRatio, this.scale_[1] * this.pixelRatio], [
this.width_, (this.scale_[0] * this.pixelRatio) / this.imagePixelRatio_,
(this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_,
],
Math.ceil(this.width_ * this.imagePixelRatio_),
]); ]);
this.hitDetectionInstructions.push([ this.hitDetectionInstructions.push([
CanvasInstruction.DRAW_IMAGE, CanvasInstruction.DRAW_IMAGE,
@@ -211,17 +226,20 @@ class CanvasImageBuilder extends CanvasBuilder {
myEnd, myEnd,
this.hitDetectionImage_, this.hitDetectionImage_,
// Remaining arguments to DRAW_IMAGE are in alphabetical order // Remaining arguments to DRAW_IMAGE are in alphabetical order
this.anchorX_, this.anchorX_ * this.imagePixelRatio_,
this.anchorY_, this.anchorY_ * this.imagePixelRatio_,
this.declutterGroups_, this.declutterGroups_,
this.height_, Math.ceil(this.height_ * this.imagePixelRatio_),
this.opacity_, this.opacity_,
this.originX_, this.originX_,
this.originY_, this.originY_,
this.rotateWithView_, this.rotateWithView_,
this.rotation_, this.rotation_,
this.scale_, [
this.width_, (this.scale_[0] * this.pixelRatio) / this.imagePixelRatio_,
(this.scale_[1] * this.pixelRatio) / this.imagePixelRatio_,
],
Math.ceil(this.width_ * this.imagePixelRatio_),
]); ]);
this.endGeometry(feature); this.endGeometry(feature);
} }
@@ -236,6 +254,7 @@ class CanvasImageBuilder extends CanvasBuilder {
this.anchorY_ = undefined; this.anchorY_ = undefined;
this.hitDetectionImage_ = null; this.hitDetectionImage_ = null;
this.image_ = null; this.image_ = null;
this.imagePixelRatio_ = undefined;
this.height_ = undefined; this.height_ = undefined;
this.scale_ = undefined; this.scale_ = undefined;
this.opacity_ = undefined; this.opacity_ = undefined;
@@ -254,9 +273,10 @@ class CanvasImageBuilder extends CanvasBuilder {
setImageStyle(imageStyle, declutterGroups) { setImageStyle(imageStyle, declutterGroups) {
const anchor = imageStyle.getAnchor(); const anchor = imageStyle.getAnchor();
const size = imageStyle.getSize(); const size = imageStyle.getSize();
const hitDetectionImage = imageStyle.getHitDetectionImage(1); const hitDetectionImage = imageStyle.getHitDetectionImage(this.pixelRatio);
const image = imageStyle.getImage(1); const image = imageStyle.getImage(this.pixelRatio);
const origin = imageStyle.getOrigin(); const origin = imageStyle.getOrigin();
this.imagePixelRatio_ = imageStyle.getPixelRatio(this.pixelRatio);
this.anchorX_ = anchor[0]; this.anchorX_ = anchor[0];
this.anchorY_ = anchor[1]; this.anchorY_ = anchor[1];
this.declutterGroups_ = declutterGroups; this.declutterGroups_ = declutterGroups;
+10
View File
@@ -320,6 +320,16 @@ class Icon extends ImageStyle {
return this.iconImage_.getImage(pixelRatio); return this.iconImage_.getImage(pixelRatio);
} }
/**
* Get the pixel ratio.
* @param {number} pixelRatio Pixel ratio.
* @return {number} The pixel ration of the image.
* @api
*/
getPixelRatio(pixelRatio) {
return this.iconImage_.getPixelRatio(pixelRatio);
}
/** /**
* @return {import("../size.js").Size} Image size. * @return {import("../size.js").Size} Image size.
*/ */
+51 -32
View File
@@ -9,6 +9,11 @@ import {createCanvasContext2D} from '../dom.js';
import {shared as iconImageCache} from './IconImageCache.js'; import {shared as iconImageCache} from './IconImageCache.js';
import {listenImage} from '../Image.js'; import {listenImage} from '../Image.js';
/**
* @type {CanvasRenderingContext2D}
*/
let taintedTestContext = null;
class IconImage extends EventTarget { class IconImage extends EventTarget {
/** /**
* @param {HTMLImageElement|HTMLCanvasElement} image Image. * @param {HTMLImageElement|HTMLCanvasElement} image Image.
@@ -23,9 +28,9 @@ class IconImage extends EventTarget {
/** /**
* @private * @private
* @type {HTMLImageElement|HTMLCanvasElement} * @type {Object<number, HTMLImageElement|HTMLCanvasElement>}
*/ */
this.hitDetectionImage_ = null; this.hitDetectionImage_ = {};
/** /**
* @private * @private
@@ -39,9 +44,9 @@ class IconImage extends EventTarget {
/** /**
* @private * @private
* @type {HTMLCanvasElement} * @type {Object<number, HTMLCanvasElement>}
*/ */
this.canvas_ = color ? document.createElement('canvas') : null; this.canvas_ = {};
/** /**
* @private * @private
@@ -75,26 +80,25 @@ class IconImage extends EventTarget {
/** /**
* @private * @private
* @type {boolean|undefined}
*/ */
this.tainted_; this.tainted_;
} }
/** /**
* @private * @private
* @param {CanvasRenderingContext2D=} context A context with the image already drawn into.
* @return {boolean} The image canvas is tainted. * @return {boolean} The image canvas is tainted.
*/ */
isTainted_(context) { isTainted_() {
if (this.tainted_ === undefined && this.imageState_ === ImageState.LOADED) { if (this.tainted_ === undefined && this.imageState_ === ImageState.LOADED) {
if (!context) { if (!taintedTestContext) {
context = createCanvasContext2D(1, 1); taintedTestContext = createCanvasContext2D(1, 1);
context.drawImage(this.image_, 0, 0);
} }
taintedTestContext.drawImage(this.image_, 0, 0);
try { try {
context.getImageData(0, 0, 1, 1); taintedTestContext.getImageData(0, 0, 1, 1);
this.tainted_ = false; this.tainted_ = false;
} catch (e) { } catch (e) {
taintedTestContext = null;
this.tainted_ = true; this.tainted_ = true;
} }
} }
@@ -125,10 +129,10 @@ class IconImage extends EventTarget {
if (this.size_) { if (this.size_) {
this.image_.width = this.size_[0]; this.image_.width = this.size_[0];
this.image_.height = this.size_[1]; this.image_.height = this.size_[1];
} else {
this.size_ = [this.image_.width, this.image_.height];
} }
this.size_ = [this.image_.width, this.image_.height];
this.unlistenImage_(); this.unlistenImage_();
this.replaceColor_();
this.dispatchChangeEvent_(); this.dispatchChangeEvent_();
} }
@@ -137,7 +141,17 @@ class IconImage extends EventTarget {
* @return {HTMLImageElement|HTMLCanvasElement} Image or Canvas element. * @return {HTMLImageElement|HTMLCanvasElement} Image or Canvas element.
*/ */
getImage(pixelRatio) { getImage(pixelRatio) {
return this.canvas_ ? this.canvas_ : this.image_; 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;
} }
/** /**
@@ -152,18 +166,23 @@ class IconImage extends EventTarget {
* @return {HTMLImageElement|HTMLCanvasElement} Image element. * @return {HTMLImageElement|HTMLCanvasElement} Image element.
*/ */
getHitDetectionImage(pixelRatio) { getHitDetectionImage(pixelRatio) {
if (!this.hitDetectionImage_) { if (!this.hitDetectionImage_[pixelRatio]) {
if (this.isTainted_()) { if (this.isTainted_()) {
const usedPixelRatio = this.color_ ? pixelRatio : 1;
const width = this.size_[0]; const width = this.size_[0];
const height = this.size_[1]; const height = this.size_[1];
const context = createCanvasContext2D(width, height); const context = createCanvasContext2D(
Math.ceil(width * usedPixelRatio),
Math.ceil(height * usedPixelRatio)
);
context.scale(usedPixelRatio, usedPixelRatio);
context.fillRect(0, 0, width, height); context.fillRect(0, 0, width, height);
this.hitDetectionImage_ = context.canvas; this.hitDetectionImage_[pixelRatio] = context.canvas;
} else { } else {
this.hitDetectionImage_ = this.image_; this.hitDetectionImage_[pixelRatio] = this.image_;
} }
} }
return this.hitDetectionImage_; return this.hitDetectionImage_[pixelRatio];
} }
/** /**
@@ -201,20 +220,25 @@ class IconImage extends EventTarget {
} }
/** /**
* @param {number} pixelRatio Pixel ratio.
* @private * @private
*/ */
replaceColor_() { replaceColor_(pixelRatio) {
if (!this.color_) { if (!this.color_ || this.canvas_[pixelRatio]) {
return; return;
} }
this.canvas_.width = this.image_.width; const canvas = document.createElement('canvas');
this.canvas_.height = this.image_.height; this.canvas_[pixelRatio] = canvas;
const ctx = this.canvas_.getContext('2d'); canvas.width = Math.ceil(this.image_.width * pixelRatio);
canvas.height = Math.ceil(this.image_.height * pixelRatio);
const ctx = canvas.getContext('2d');
ctx.scale(pixelRatio, pixelRatio);
ctx.drawImage(this.image_, 0, 0); ctx.drawImage(this.image_, 0, 0);
if (this.isTainted_(ctx)) { if (this.isTainted_()) {
// If reading from the canvas throws a SecurityError the same effect can be // If reading from the canvas throws a SecurityError the same effect can be
// achieved with globalCompositeOperation. // achieved with globalCompositeOperation.
// This could be used as the default, but it is not fully supported by all // This could be used as the default, but it is not fully supported by all
@@ -226,19 +250,14 @@ class IconImage extends EventTarget {
const c = this.color_; const c = this.color_;
ctx.globalCompositeOperation = 'multiply'; ctx.globalCompositeOperation = 'multiply';
ctx.fillStyle = 'rgb(' + c[0] + ',' + c[1] + ',' + c[2] + ')'; ctx.fillStyle = 'rgb(' + c[0] + ',' + c[1] + ',' + c[2] + ')';
ctx.fillRect(0, 0, this.image_.width, this.image_.height); ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'destination-in'; ctx.globalCompositeOperation = 'destination-in';
ctx.drawImage(this.image_, 0, 0); ctx.drawImage(this.image_, 0, 0);
return; return;
} }
const imgData = ctx.getImageData( const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
0,
0,
this.image_.width,
this.image_.height
);
const data = imgData.data; const data = imgData.data;
const r = this.color_[0] / 255.0; const r = this.color_[0] / 255.0;
const g = this.color_[1] / 255.0; const g = this.color_[1] / 255.0;
+8
View File
@@ -161,6 +161,14 @@ class ImageStyle {
return abstract(); return abstract();
} }
/*
* Get the image pixel ratio.
* @param {number} pixelRatio Pixel ratio.
* */
getPixelRatio(pixelRatio) {
return 1;
}
/** /**
* @abstract * @abstract
* @return {import("../ImageState.js").default} Image state. * @return {import("../ImageState.js").default} Image state.
+69 -28
View File
@@ -73,15 +73,15 @@ class RegularShape extends ImageStyle {
/** /**
* @private * @private
* @type {HTMLCanvasElement} * @type {Object<number, HTMLCanvasElement>}
*/ */
this.canvas_ = null; this.canvas_ = {};
/** /**
* @private * @private
* @type {HTMLCanvasElement} * @type {Object<number, HTMLCanvasElement>}
*/ */
this.hitDetectionCanvas_ = null; this.hitDetectionCanvas_ = {};
/** /**
* @private * @private
@@ -205,20 +205,45 @@ class RegularShape extends ImageStyle {
/** /**
* @param {number} pixelRatio Pixel ratio. * @param {number} pixelRatio Pixel ratio.
* @return {HTMLImageElement|HTMLCanvasElement} Image element. * @return {HTMLCanvasElement} Image element.
*/ */
getHitDetectionImage(pixelRatio) { getHitDetectionImage(pixelRatio) {
return this.hitDetectionCanvas_; if (!this.hitDetectionCanvas_[pixelRatio || 1]) {
const renderOptions = this.createRenderOptions();
this.createHitDetectionCanvas_(renderOptions, pixelRatio || 1);
}
return this.hitDetectionCanvas_[pixelRatio || 1];
} }
/** /**
* Get the image icon. * Get the image icon.
* @param {number} pixelRatio Pixel ratio. * @param {number} pixelRatio Pixel ratio.
* @return {HTMLImageElement|HTMLCanvasElement} Image or Canvas element. * @return {HTMLCanvasElement} Image or Canvas element.
* @api * @api
*/ */
getImage(pixelRatio) { getImage(pixelRatio) {
return this.canvas_; if (!this.canvas_[pixelRatio || 1]) {
const renderOptions = this.createRenderOptions();
const context = createCanvasContext2D(
renderOptions.size * pixelRatio || 1,
renderOptions.size * pixelRatio || 1
);
this.draw_(renderOptions, context, 0, 0, pixelRatio || 1);
this.canvas_[pixelRatio || 1] = context.canvas;
}
return this.canvas_[pixelRatio || 1];
}
/*
* Get the image pixel ratio.
* @param {number} pixelRatio Pixel ratio.
* */
getPixelRatio(pixelRatio) {
return pixelRatio;
} }
/** /**
@@ -312,9 +337,10 @@ class RegularShape extends ImageStyle {
unlistenImageChange(listener) {} unlistenImageChange(listener) {}
/** /**
* @returns {RenderOptions} The render options
* @protected * @protected
*/ */
render() { createRenderOptions() {
let lineCap = defaultLineCap; let lineCap = defaultLineCap;
let lineJoin = defaultLineJoin; let lineJoin = defaultLineJoin;
let miterLimit = 0; let miterLimit = 0;
@@ -349,9 +375,9 @@ class RegularShape extends ImageStyle {
} }
} }
let size = 2 * (this.radius_ + strokeWidth) + 1; const size = 2 * (this.radius_ + strokeWidth) + 1;
const renderOptions = { return {
strokeStyle: strokeStyle, strokeStyle: strokeStyle,
strokeWidth: strokeWidth, strokeWidth: strokeWidth,
size: size, size: size,
@@ -361,18 +387,30 @@ class RegularShape extends ImageStyle {
lineJoin: lineJoin, lineJoin: lineJoin,
miterLimit: miterLimit, miterLimit: miterLimit,
}; };
}
const context = createCanvasContext2D(size, size); /**
this.canvas_ = context.canvas; * @protected
*/
render() {
const renderOptions = this.createRenderOptions();
const context = createCanvasContext2D(
renderOptions.size,
renderOptions.size
);
this.draw_(renderOptions, context, 0, 0, 1);
this.canvas_[1] = context.canvas;
// canvas.width and height are rounded to the closest integer // canvas.width and height are rounded to the closest integer
size = this.canvas_.width; const size = context.canvas.width;
const imageSize = size; const imageSize = size;
const displacement = this.getDisplacement(); const displacement = this.getDisplacement();
this.draw_(renderOptions, context, 0, 0); this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
this.createHitDetectionCanvas_(renderOptions, 1);
this.createHitDetectionCanvas_(renderOptions);
this.anchor_ = [size / 2 - displacement[0], size / 2 + displacement[1]]; this.anchor_ = [size / 2 - displacement[0], size / 2 + displacement[1]];
this.size_ = [size, size]; this.size_ = [size, size];
@@ -385,11 +423,13 @@ class RegularShape extends ImageStyle {
* @param {CanvasRenderingContext2D} context The rendering context. * @param {CanvasRenderingContext2D} context The rendering context.
* @param {number} x The origin for the symbol (x). * @param {number} x The origin for the symbol (x).
* @param {number} y The origin for the symbol (y). * @param {number} y The origin for the symbol (y).
* @param {number} pixelRatio The pixel ratio.
*/ */
draw_(renderOptions, context, x, y) { draw_(renderOptions, context, x, y, pixelRatio) {
let i, angle0, radiusC; let i, angle0, radiusC;
// reset transform // reset transform
context.setTransform(1, 0, 0, 1, 0, 0); context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
// then move to (x, y) // then move to (x, y)
context.translate(x, y); context.translate(x, y);
@@ -448,10 +488,10 @@ class RegularShape extends ImageStyle {
/** /**
* @private * @private
* @param {RenderOptions} renderOptions Render options. * @param {RenderOptions} renderOptions Render options.
* @param {number} pixelRatio The pixel ratio.
*/ */
createHitDetectionCanvas_(renderOptions) { createHitDetectionCanvas_(renderOptions, pixelRatio) {
this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size]; this.hitDetectionCanvas_[pixelRatio] = this.getImage(pixelRatio);
this.hitDetectionCanvas_ = this.canvas_;
if (this.fill_) { if (this.fill_) {
let color = this.fill_.getColor(); let color = this.fill_.getColor();
@@ -469,12 +509,12 @@ class RegularShape extends ImageStyle {
// if a transparent fill style is set, create an extra hit-detection image // if a transparent fill style is set, create an extra hit-detection image
// with a default fill style // with a default fill style
const context = createCanvasContext2D( const context = createCanvasContext2D(
renderOptions.size, renderOptions.size * pixelRatio,
renderOptions.size renderOptions.size * pixelRatio
); );
this.hitDetectionCanvas_ = context.canvas; this.hitDetectionCanvas_[pixelRatio] = context.canvas;
this.drawHitDetectionCanvas_(renderOptions, context, 0, 0); this.drawHitDetectionCanvas_(renderOptions, context, 0, 0, pixelRatio);
} }
} }
} }
@@ -485,10 +525,11 @@ class RegularShape extends ImageStyle {
* @param {CanvasRenderingContext2D} context The context. * @param {CanvasRenderingContext2D} context The context.
* @param {number} x The origin for the symbol (x). * @param {number} x The origin for the symbol (x).
* @param {number} y The origin for the symbol (y). * @param {number} y The origin for the symbol (y).
* @param {number} pixelRatio The pixel ratio.
*/ */
drawHitDetectionCanvas_(renderOptions, context, x, y) { drawHitDetectionCanvas_(renderOptions, context, x, y, pixelRatio) {
// reset transform // reset transform
context.setTransform(1, 0, 0, 1, 0, 0); context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
// then move to (x, y) // then move to (x, y)
context.translate(x, y); context.translate(x, y);
+9 -9
View File
@@ -6,14 +6,14 @@ describe('ol.style.Circle', function () {
describe('#constructor', function () { describe('#constructor', function () {
it('creates a canvas (no fill-style)', function () { it('creates a canvas (no fill-style)', function () {
const style = new CircleStyle({radius: 10}); const style = new CircleStyle({radius: 10});
expect(style.getImage()).to.be.an(HTMLCanvasElement); expect(style.getImage(1)).to.be.an(HTMLCanvasElement);
expect(style.getSize()).to.eql([21, 21]); expect(style.getSize()).to.eql([21, 21]);
expect(style.getImageSize()).to.eql([21, 21]); expect(style.getImageSize()).to.eql([21, 21]);
expect(style.getOrigin()).to.eql([0, 0]); expect(style.getOrigin()).to.eql([0, 0]);
expect(style.getAnchor()).to.eql([10.5, 10.5]); expect(style.getAnchor()).to.eql([10.5, 10.5]);
// no hit-detection image is created, because no fill style is set // no hit-detection image is created, because no fill style is set
expect(style.getImage()).to.be(style.getHitDetectionImage()); expect(style.getImage(1)).to.be(style.getHitDetectionImage(1));
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImage(1)).to.be.an(HTMLCanvasElement);
expect(style.getHitDetectionImageSize()).to.eql([21, 21]); expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
}); });
@@ -24,14 +24,14 @@ describe('ol.style.Circle', function () {
color: 'transparent', color: 'transparent',
}), }),
}); });
expect(style.getImage()).to.be.an(HTMLCanvasElement); expect(style.getImage(1)).to.be.an(HTMLCanvasElement);
expect(style.getSize()).to.eql([21, 21]); expect(style.getSize()).to.eql([21, 21]);
expect(style.getImageSize()).to.eql([21, 21]); expect(style.getImageSize()).to.eql([21, 21]);
expect(style.getOrigin()).to.eql([0, 0]); expect(style.getOrigin()).to.eql([0, 0]);
expect(style.getAnchor()).to.eql([10.5, 10.5]); expect(style.getAnchor()).to.eql([10.5, 10.5]);
// hit-detection image is created, because transparent fill style is set // hit-detection image is created, because transparent fill style is set
expect(style.getImage()).to.not.be(style.getHitDetectionImage()); expect(style.getImage(1)).to.not.be(style.getHitDetectionImage(1));
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImage(1)).to.be.an(HTMLCanvasElement);
expect(style.getHitDetectionImageSize()).to.eql([21, 21]); expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
}); });
@@ -42,14 +42,14 @@ describe('ol.style.Circle', function () {
color: '#FFFF00', color: '#FFFF00',
}), }),
}); });
expect(style.getImage()).to.be.an(HTMLCanvasElement); expect(style.getImage(1)).to.be.an(HTMLCanvasElement);
expect(style.getSize()).to.eql([21, 21]); expect(style.getSize()).to.eql([21, 21]);
expect(style.getImageSize()).to.eql([21, 21]); expect(style.getImageSize()).to.eql([21, 21]);
expect(style.getOrigin()).to.eql([0, 0]); expect(style.getOrigin()).to.eql([0, 0]);
expect(style.getAnchor()).to.eql([10.5, 10.5]); expect(style.getAnchor()).to.eql([10.5, 10.5]);
// no hit-detection image is created, because non-transparent fill style is set // no hit-detection image is created, because non-transparent fill style is set
expect(style.getImage()).to.be(style.getHitDetectionImage()); expect(style.getImage(1)).to.be(style.getHitDetectionImage(1));
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImage(1)).to.be.an(HTMLCanvasElement);
expect(style.getHitDetectionImageSize()).to.eql([21, 21]); expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
}); });
}); });
+14 -9
View File
@@ -32,14 +32,14 @@ describe('ol.style.RegularShape', function () {
it('creates a canvas (no fill-style)', function () { it('creates a canvas (no fill-style)', function () {
const style = new RegularShape({radius: 10}); const style = new RegularShape({radius: 10});
expect(style.getImage()).to.be.an(HTMLCanvasElement); expect(style.getImage(1)).to.be.an(HTMLCanvasElement);
expect(style.getSize()).to.eql([21, 21]); expect(style.getSize()).to.eql([21, 21]);
expect(style.getImageSize()).to.eql([21, 21]); expect(style.getImageSize()).to.eql([21, 21]);
expect(style.getOrigin()).to.eql([0, 0]); expect(style.getOrigin()).to.eql([0, 0]);
expect(style.getAnchor()).to.eql([10.5, 10.5]); expect(style.getAnchor()).to.eql([10.5, 10.5]);
// no hit-detection image is created, because no fill style is set // no hit-detection image is created, because no fill style is set
expect(style.getImage()).to.be(style.getHitDetectionImage()); expect(style.getImage(1)).to.be(style.getHitDetectionImage(1));
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImage(1)).to.be.an(HTMLCanvasElement);
expect(style.getHitDetectionImageSize()).to.eql([21, 21]); expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
}); });
@@ -50,15 +50,20 @@ describe('ol.style.RegularShape', function () {
color: 'transparent', color: 'transparent',
}), }),
}); });
expect(style.getImage()).to.be.an(HTMLCanvasElement); expect(style.getImage(1)).to.be.an(HTMLCanvasElement);
expect(style.getImage(1).width).to.be(21);
expect(style.getImage(2).width).to.be(42);
expect(style.getPixelRatio(2)).to.be(2);
expect(style.getSize()).to.eql([21, 21]); expect(style.getSize()).to.eql([21, 21]);
expect(style.getImageSize()).to.eql([21, 21]); expect(style.getImageSize()).to.eql([21, 21]);
expect(style.getOrigin()).to.eql([0, 0]); expect(style.getOrigin()).to.eql([0, 0]);
expect(style.getAnchor()).to.eql([10.5, 10.5]); expect(style.getAnchor()).to.eql([10.5, 10.5]);
// hit-detection image is created, because transparent fill style is set // hit-detection image is created, because transparent fill style is set
expect(style.getImage()).to.not.be(style.getHitDetectionImage()); expect(style.getImage(1)).to.not.be(style.getHitDetectionImage(1));
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImage(1)).to.be.an(HTMLCanvasElement);
expect(style.getHitDetectionImageSize()).to.eql([21, 21]); expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
expect(style.getHitDetectionImage(1).width).to.be(21);
expect(style.getHitDetectionImage(2).width).to.be(42);
}); });
it('creates a canvas (non-transparent fill-style)', function () { it('creates a canvas (non-transparent fill-style)', function () {
@@ -68,14 +73,14 @@ describe('ol.style.RegularShape', function () {
color: '#FFFF00', color: '#FFFF00',
}), }),
}); });
expect(style.getImage()).to.be.an(HTMLCanvasElement); expect(style.getImage(1)).to.be.an(HTMLCanvasElement);
expect(style.getSize()).to.eql([21, 21]); expect(style.getSize()).to.eql([21, 21]);
expect(style.getImageSize()).to.eql([21, 21]); expect(style.getImageSize()).to.eql([21, 21]);
expect(style.getOrigin()).to.eql([0, 0]); expect(style.getOrigin()).to.eql([0, 0]);
expect(style.getAnchor()).to.eql([10.5, 10.5]); expect(style.getAnchor()).to.eql([10.5, 10.5]);
// no hit-detection image is created, because non-transparent fill style is set // no hit-detection image is created, because non-transparent fill style is set
expect(style.getImage()).to.be(style.getHitDetectionImage()); expect(style.getImage(1)).to.be(style.getHitDetectionImage(1));
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement); expect(style.getHitDetectionImage(1)).to.be.an(HTMLCanvasElement);
expect(style.getHitDetectionImageSize()).to.eql([21, 21]); expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
}); });