High resolution icon

This commit is contained in:
Stéphane Brunner
2020-07-15 10:16:46 +02:00
parent f2fe6e5957
commit fd1effa992
6 changed files with 216 additions and 40 deletions

BIN
examples/data/bigdot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

66
examples/data/dot.svg Normal file
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

View File

@@ -1,7 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg">
<g>
<rect width="20" height="20" style="fill:#fff; stroke-width:4px; stroke:#000" />
<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="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>
</svg>

Before

Width:  |  Height:  |  Size: 225 B

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -19,12 +19,19 @@ const london = new Feature({
const madrid = new Feature({
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(
new Style({
image: new Icon({
color: '#8959A8',
crossOrigin: 'anonymous',
// For Internet Explorer 11
imgSize: [20, 20],
src: 'data/square.svg',
}),
@@ -36,7 +43,8 @@ london.setStyle(
image: new Icon({
color: '#4271AE',
crossOrigin: 'anonymous',
src: 'data/dot.png',
src: 'data/bigdot.png',
scale: 0.2,
}),
})
);
@@ -44,15 +52,37 @@ london.setStyle(
madrid.setStyle(
new Style({
image: new Icon({
color: [113, 140, 0],
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({
features: [rome, london, madrid],
features: [rome, london, madrid, paris, berlin],
});
const vectorLayer = new VectorLayer({

View File

@@ -320,6 +320,16 @@ class Icon extends ImageStyle {
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.
*/

View File

@@ -23,9 +23,9 @@ class IconImage extends EventTarget {
/**
* @private
* @type {HTMLImageElement|HTMLCanvasElement}
* @type {Object<number, HTMLImageElement|HTMLCanvasElement>}
*/
this.hitDetectionImage_ = null;
this.hitDetectionImage_ = {};
/**
* @private
@@ -39,9 +39,9 @@ class IconImage extends EventTarget {
/**
* @private
* @type {HTMLCanvasElement}
* @type {Object<number, HTMLCanvasElement>}
*/
this.canvas_ = color ? document.createElement('canvas') : null;
this.canvas_ = {};
/**
* @private
@@ -75,22 +75,18 @@ class IconImage extends EventTarget {
/**
* @private
* @type {boolean|undefined}
*/
this.tainted_;
}
/**
* @private
* @param {CanvasRenderingContext2D=} context A context with the image already drawn into.
* @return {boolean} The image canvas is tainted.
*/
isTainted_(context) {
isTainted_() {
if (this.tainted_ === undefined && this.imageState_ === ImageState.LOADED) {
if (!context) {
context = createCanvasContext2D(1, 1);
context.drawImage(this.image_, 0, 0);
}
const context = createCanvasContext2D(1, 1);
context.drawImage(this.image_, 0, 0);
try {
context.getImageData(0, 0, 1, 1);
this.tainted_ = false;
@@ -125,10 +121,10 @@ class IconImage extends EventTarget {
if (this.size_) {
this.image_.width = this.size_[0];
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.replaceColor_();
this.dispatchChangeEvent_();
}
@@ -137,7 +133,17 @@ class IconImage extends EventTarget {
* @return {HTMLImageElement|HTMLCanvasElement} Image or Canvas element.
*/
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 +158,23 @@ class IconImage extends EventTarget {
* @return {HTMLImageElement|HTMLCanvasElement} Image element.
*/
getHitDetectionImage(pixelRatio) {
if (!this.hitDetectionImage_) {
if (!this.hitDetectionImage_[pixelRatio]) {
if (this.isTainted_()) {
const usedPixelRatio = this.color_ ? pixelRatio : 1;
const width = this.size_[0];
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);
this.hitDetectionImage_ = context.canvas;
this.hitDetectionImage_[pixelRatio] = context.canvas;
} else {
this.hitDetectionImage_ = this.image_;
this.hitDetectionImage_[pixelRatio] = this.image_;
}
}
return this.hitDetectionImage_;
return this.hitDetectionImage_[pixelRatio];
}
/**
@@ -201,20 +212,25 @@ class IconImage extends EventTarget {
}
/**
* @param {number} pixelRatio Pixel ratio.
* @private
*/
replaceColor_() {
if (!this.color_) {
replaceColor_(pixelRatio) {
if (!this.color_ || this.canvas_[pixelRatio]) {
return;
}
this.canvas_.width = this.image_.width;
this.canvas_.height = this.image_.height;
const canvas = document.createElement('canvas');
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);
if (this.isTainted_(ctx)) {
if (this.isTainted_()) {
// If reading from the canvas throws a SecurityError the same effect can be
// achieved with globalCompositeOperation.
// This could be used as the default, but it is not fully supported by all
@@ -226,19 +242,14 @@ class IconImage extends EventTarget {
const c = this.color_;
ctx.globalCompositeOperation = 'multiply';
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.drawImage(this.image_, 0, 0);
return;
}
const imgData = ctx.getImageData(
0,
0,
this.image_.width,
this.image_.height
);
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imgData.data;
const r = this.color_[0] / 255.0;
const g = this.color_[1] / 255.0;