Merge pull request #11037 from mike-000/patch-11

Allow icon and text styles to be scaled in two dimensions
This commit is contained in:
Andreas Hocevar
2020-06-13 10:00:06 +02:00
committed by GitHub
19 changed files with 528 additions and 111 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

+16
View File
@@ -0,0 +1,16 @@
---
layout: example.html
title: Icon Scale
shortdesc: Example of scaling icons and labels.
docs: >
Icons and labels can be scaled in both dimensions if required. A negative value will flip the image
or text around its anchor point (reversed text is <b>not</b> suitable for line placement). A newline
character inserted in label text is interpreted in a <b>vector layer</b>, but will not be shown in
a <b>vector context</b>.
tags: "vector, style, icon, label, scale"
resources:
- https://code.jquery.com/jquery-2.2.3.min.js
- https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css
- https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js
---
<div id="map" class="map"><div id="popup"></div></div>
+133
View File
@@ -0,0 +1,133 @@
import Feature from '../src/ol/Feature.js';
import Map from '../src/ol/Map.js';
import Overlay from '../src/ol/Overlay.js';
import Point from '../src/ol/geom/Point.js';
import TileJSON from '../src/ol/source/TileJSON.js';
import VectorSource from '../src/ol/source/Vector.js';
import View from '../src/ol/View.js';
import {Icon, Style, Text} from '../src/ol/style.js';
import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js';
import {fromLonLat} from '../src/ol/proj.js';
import {getVectorContext} from '../src/ol/render.js';
const rasterLayer = new TileLayer({
source: new TileJSON({
url: 'https://a.tiles.mapbox.com/v3/aj.1x1-degrees.json',
crossOrigin: '',
}),
});
const iconFeature = new Feature({
geometry: new Point(fromLonLat([0, -10])),
name: 'Fish.1',
});
const feature1 = new Feature({
geometry: new Point(fromLonLat([0, -10])),
name: 'Fish.1 Island',
});
const feature2 = new Feature({
geometry: new Point(fromLonLat([-30, 10])),
name: 'Fish.2 Island',
});
const iconStyle = new Style({
image: new Icon({
anchor: [0.5, 0.9],
src: 'data/fish.png',
crossOrigin: '',
scale: [0, 0],
rotation: Math.PI / 4,
}),
text: new Text({
text: 'FISH\nTEXT',
scale: [0, 0],
rotation: Math.PI / 4,
textAlign: 'center',
textBaseline: 'top',
}),
});
let i = 0;
let j = 45;
iconFeature.setStyle(function () {
const x = Math.sin((i * Math.PI) / 180) * 3;
const y = Math.sin((j * Math.PI) / 180) * 4;
iconStyle.getImage().setScale([x, y]);
iconStyle.getText().setScale([x, y]);
return iconStyle;
});
rasterLayer.on('postrender', function (event) {
const vectorContext = getVectorContext(event);
const x = Math.cos((i * Math.PI) / 180) * 3;
const y = Math.cos((j * Math.PI) / 180) * 4;
iconStyle.getImage().setScale([x, y]);
iconStyle.getText().setScale([x, y]);
vectorContext.drawFeature(feature2, iconStyle);
});
const vectorSource = new VectorSource({
features: [iconFeature, feature1, feature2],
});
const vectorLayer = new VectorLayer({
source: vectorSource,
});
const map = new Map({
layers: [rasterLayer, vectorLayer],
target: document.getElementById('map'),
view: new View({
center: fromLonLat([-15, 0]),
zoom: 3,
}),
});
setInterval(function () {
i = (i + 4) % 360;
j = (j + 5) % 360;
vectorSource.changed();
}, 1000);
const element = document.getElementById('popup');
const popup = new Overlay({
element: element,
positioning: 'bottom-center',
stopEvent: false,
offset: [0, -50],
});
map.addOverlay(popup);
// display popup on click
map.on('click', function (evt) {
const feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) {
return feature;
});
$(element).popover('destroy');
if (feature) {
const coordinates = feature.getGeometry().getCoordinates();
popup.setPosition(coordinates);
$(element).popover({
placement: 'top',
html: true,
animation: false,
content: feature.get('name'),
});
$(element).popover('show');
}
});
// change mouse cursor when over marker
map.on('pointermove', function (e) {
if (e.dragging) {
$(element).popover('destroy');
return;
}
const pixel = map.getEventPixel(e.originalEvent);
const hit = map.hasFeatureAtPixel(pixel);
map.getTarget().style.cursor = hit ? 'pointer' : '';
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+117
View File
@@ -0,0 +1,117 @@
import Feature from '../../../src/ol/Feature.js';
import Icon from '../../../src/ol/style/Icon.js';
import Map from '../../../src/ol/Map.js';
import Point from '../../../src/ol/geom/Point.js';
import Style from '../../../src/ol/style/Style.js';
import VectorLayer from '../../../src/ol/layer/Vector.js';
import VectorSource from '../../../src/ol/source/Vector.js';
import View from '../../../src/ol/View.js';
const features = [];
for (let i = 0; i < 2; ++i) {
const x = i * 5;
features.push(
new Feature({
geometry: new Point([x + 2, 2]),
scale: [1.5, 1],
anchor: [1, 0.5],
rotated: i,
}),
new Feature({
geometry: new Point([x + 3, 2]),
scale: [1.5, 1],
anchor: [0.5, 0.5],
rotated: i,
}),
new Feature({
geometry: new Point([x + 4, 2]),
scale: [1.5, 1],
anchor: [0, 0.5],
rotated: i,
}),
new Feature({
geometry: new Point([x + 2, 4]),
scale: [-1, 1],
anchor: [0, 0.5],
rotated: i,
}),
new Feature({
geometry: new Point([x + 3, 4]),
scale: [-1, 1],
anchor: [0.5, 0.5],
rotated: i,
}),
new Feature({
geometry: new Point([x + 4, 4]),
scale: [-1, 1],
anchor: [1, 0.5],
rotated: i,
}),
new Feature({
geometry: new Point([x + 2, 6]),
scale: [1, -1],
anchor: [0.5, 1],
rotated: i,
}),
new Feature({
geometry: new Point([x + 3, 6]),
scale: [1, -1],
anchor: [0.5, 0.5],
rotated: i,
}),
new Feature({
geometry: new Point([x + 4, 6]),
scale: [1, -1],
anchor: [0.5, 0],
rotated: i,
}),
new Feature({
geometry: new Point([x + 2, 8]),
scale: [1, 1.5],
anchor: [0.5, 0],
rotated: i,
}),
new Feature({
geometry: new Point([x + 3, 8]),
scale: [1, 1.5],
anchor: [0.5, 0.5],
rotated: i,
}),
new Feature({
geometry: new Point([x + 4, 8]),
scale: [1, 1.5],
anchor: [0.5, 1],
rotated: i,
})
);
}
const vectorSource = new VectorSource({
features: features,
});
const style = new Style({
image: new Icon({
src: '/data/fish.png',
}),
});
const vectorLayer = new VectorLayer({
source: vectorSource,
style: function (feature) {
style.getImage().setScale(feature.get('scale'));
style.getImage().setAnchor(feature.get('anchor'));
style.getImage().setRotation((feature.get('rotated') * Math.PI) / 4);
return style;
},
});
const map = new Map({
pixelRatio: 1,
layers: [vectorLayer],
target: 'map',
view: new View(),
});
map.getView().fit([0, 0, 11, 11]);
render();
Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

@@ -59,7 +59,7 @@ feature1.setStyle(
vectorSource.addFeature(feature1); vectorSource.addFeature(feature1);
const lineString2 = lineString1.clone(); const lineString2 = lineString1.clone();
lineString2.translate(0, 30); lineString2.translate(0, 20);
const feature2 = new Feature({geometry: lineString2}); const feature2 = new Feature({geometry: lineString2});
feature2.setStyle( feature2.setStyle(
new Style({ new Style({
@@ -123,7 +123,7 @@ feature4.setStyle(
vectorSource.addFeature(feature4); vectorSource.addFeature(feature4);
const lineString5 = lineString4.clone(); const lineString5 = lineString4.clone();
lineString5.translate(0, 30); lineString5.translate(0, 20);
const feature5 = new Feature({geometry: lineString5}); const feature5 = new Feature({geometry: lineString5});
feature5.setStyle( feature5.setStyle(
new Style({ new Style({
@@ -141,7 +141,7 @@ feature5.setStyle(
vectorSource.addFeature(feature5); vectorSource.addFeature(feature5);
const lineString6 = lineString5.clone(); const lineString6 = lineString5.clone();
lineString6.translate(0, 30); lineString6.translate(0, 20);
const feature6 = new Feature({geometry: lineString6}); const feature6 = new Feature({geometry: lineString6});
feature6.setStyle( feature6.setStyle(
new Style({ new Style({
@@ -160,6 +160,28 @@ feature6.setStyle(
); );
vectorSource.addFeature(feature6); vectorSource.addFeature(feature6);
const lineString7 = lineString6.clone();
lineString7.translate(0, 30);
const feature7 = new Feature({geometry: lineString7});
feature7.setStyle(
new Style({
stroke: new Stroke({color: 'blue'}),
text: new Text({
text: 'Reflection',
font: 'normal 400 12px/1 Ubuntu',
scale: [2, -1],
textBaseline: 'bottom',
textAlign: 'right',
placement: 'line',
stroke: new Stroke({
color: '#FFFF00',
width: 1,
}),
}),
})
);
vectorSource.addFeature(feature7);
const map = new Map({ const map = new Map({
pixelRatio: 1, pixelRatio: 1,
layers: [ layers: [
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

+11 -3
View File
@@ -110,15 +110,23 @@ feature.setStyle(
); );
vectorSource.addFeature(feature); vectorSource.addFeature(feature);
// background and padding // two dimensional scale
feature = new Feature({ feature = new Feature({
geometry: new Point([-10, 0]), geometry: new Point([100, 20]),
}); });
feature.setStyle( feature.setStyle(
new Style({ new Style({
text: new Text({ text: new Text({
text: 'hello', text: 'mirror',
font: '12px Ubuntu', font: '12px Ubuntu',
scale: [-1, 2],
rotateWithView: true,
fill: new Fill({
color: 'red',
}),
stroke: new Stroke({
color: '#000',
}),
padding: [1, 2, 3, 5], padding: [1, 2, 3, 5],
backgroundFill: new Fill({ backgroundFill: new Fill({
color: 'rgba(55, 55, 55, 0.25)', color: 'rgba(55, 55, 55, 0.25)',
Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

+3 -3
View File
@@ -915,11 +915,11 @@ function createNameStyleFunction(foundStyle, name) {
imageSize = DEFAULT_IMAGE_STYLE_SIZE; imageSize = DEFAULT_IMAGE_STYLE_SIZE;
} }
if (imageSize.length == 2) { if (imageSize.length == 2) {
const imageScale = imageStyle.getScale(); const imageScale = imageStyle.getScaleArray();
// Offset the label to be centered to the right of the icon, // Offset the label to be centered to the right of the icon,
// if there is one. // if there is one.
textOffset[0] = (imageScale * imageSize[0]) / 2; textOffset[0] = (imageScale[0] * imageSize[0]) / 2;
textOffset[1] = (-imageScale * imageSize[1]) / 2; textOffset[1] = (-imageScale[1] * imageSize[1]) / 2;
textAlign = 'left'; textAlign = 'left';
} }
} }
+21 -6
View File
@@ -63,7 +63,7 @@ import {toString} from '../transform.js';
* @property {boolean} [overflow] * @property {boolean} [overflow]
* @property {import("../style/Fill.js").default} [backgroundFill] * @property {import("../style/Fill.js").default} [backgroundFill]
* @property {import("../style/Stroke.js").default} [backgroundStroke] * @property {import("../style/Stroke.js").default} [backgroundStroke]
* @property {number} [scale] * @property {import("../size.js").Size} [scale]
* @property {Array<number>} [padding] * @property {Array<number>} [padding]
*/ */
@@ -410,7 +410,7 @@ export function rotateAtOffset(context, rotation, offsetX, offsetY) {
* @param {number} h Height. * @param {number} h Height.
* @param {number} x X. * @param {number} x X.
* @param {number} y Y. * @param {number} y Y.
* @param {number} scale Scale. * @param {import("../size.js").Size} scale Scale.
*/ */
export function drawImageOrLabel( export function drawImageOrLabel(
context, context,
@@ -437,10 +437,25 @@ export function drawImageOrLabel(
if (/** @type {*} */ (labelOrImage).contextInstructions) { if (/** @type {*} */ (labelOrImage).contextInstructions) {
// label // label
context.translate(x, y); context.translate(x, y);
context.scale(scale, scale); context.scale(scale[0], scale[1]);
executeLabelInstructions(/** @type {Label} */ (labelOrImage), context); executeLabelInstructions(/** @type {Label} */ (labelOrImage), context);
} else if (scale[0] < 0 || scale[1] < 0) {
// flipped image
context.translate(x, y);
context.scale(scale[0], scale[1]);
context.drawImage(
/** @type {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} */ (labelOrImage),
originX,
originY,
w,
h,
0,
0,
w,
h
);
} else { } else {
// image // if image not flipped translate and scale can be avoided
context.drawImage( context.drawImage(
/** @type {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} */ (labelOrImage), /** @type {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} */ (labelOrImage),
originX, originX,
@@ -449,8 +464,8 @@ export function drawImageOrLabel(
h, h,
x, x,
y, y,
w * scale, w * scale[0],
h * scale h * scale[1]
); );
} }
+32 -19
View File
@@ -194,7 +194,10 @@ class Executor {
const fillState = fillKey ? this.fillStates[fillKey] : null; const fillState = fillKey ? this.fillStates[fillKey] : null;
const textState = this.textStates[textKey]; const textState = this.textStates[textKey];
const pixelRatio = this.pixelRatio; const pixelRatio = this.pixelRatio;
const scale = textState.scale * pixelRatio; const scale = [
textState.scale[0] * pixelRatio,
textState.scale[1] * pixelRatio,
];
const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign]; const align = TEXT_ALIGN[textState.textAlign || defaultTextAlign];
const strokeWidth = const strokeWidth =
strokeKey && strokeState.lineWidth ? strokeState.lineWidth : 0; strokeKey && strokeState.lineWidth ? strokeState.lineWidth : 0;
@@ -207,15 +210,17 @@ class Executor {
const height = lineHeight * numLines; const height = lineHeight * numLines;
const renderWidth = width + strokeWidth; const renderWidth = width + strokeWidth;
const contextInstructions = []; const contextInstructions = [];
// make canvas 2 pixels wider to account for italic text width measurement errors
const w = (renderWidth + 2) * scale[0];
const h = (height + strokeWidth) * scale[1];
/** @type {import("../canvas.js").Label} */ /** @type {import("../canvas.js").Label} */
const label = { const label = {
// make canvas 2 pixels wider to account for italic text width measurement errors width: w < 0 ? Math.floor(w) : Math.ceil(w),
width: Math.ceil((renderWidth + 2) * scale), height: h < 0 ? Math.floor(h) : Math.ceil(h),
height: Math.ceil((height + strokeWidth) * scale),
contextInstructions: contextInstructions, contextInstructions: contextInstructions,
}; };
if (scale != 1) { if (scale[0] != 1 || scale[1] != 1) {
contextInstructions.push('scale', [scale, scale]); contextInstructions.push('scale', scale);
} }
contextInstructions.push('font', textState.font); contextInstructions.push('font', textState.font);
if (strokeKey) { if (strokeKey) {
@@ -317,7 +322,7 @@ class Executor {
* @param {number} originX Origin X. * @param {number} originX Origin X.
* @param {number} originY Origin Y. * @param {number} originY Origin Y.
* @param {number} rotation Rotation. * @param {number} rotation Rotation.
* @param {number} scale Scale. * @param {import("../../size.js").Size} scale Scale.
* @param {boolean} snapToPixel Snap to pixel. * @param {boolean} snapToPixel Snap to pixel.
* @param {number} width Width. * @param {number} width Width.
* @param {Array<number>} padding Padding. * @param {Array<number>} padding Padding.
@@ -347,8 +352,8 @@ class Executor {
strokeInstruction strokeInstruction
) { ) {
const fillStroke = fillInstruction || strokeInstruction; const fillStroke = fillInstruction || strokeInstruction;
anchorX *= scale; anchorX *= scale[0];
anchorY *= scale; anchorY *= scale[1];
x -= anchorX; x -= anchorX;
y -= anchorY; y -= anchorY;
@@ -360,8 +365,8 @@ class Executor {
height + originY > imageOrLabel.height height + originY > imageOrLabel.height
? imageOrLabel.height - originY ? imageOrLabel.height - originY
: height; : height;
const boxW = padding[3] + w * scale + padding[1]; const boxW = padding[3] + w * scale[0] + padding[1];
const boxH = padding[0] + h * scale + padding[2]; const boxH = padding[0] + h * scale[1] + padding[2];
const boxX = x - padding[3]; const boxX = x - padding[3];
const boxY = y - padding[0]; const boxY = y - padding[0];
@@ -415,7 +420,7 @@ class Executor {
); );
const canvas = context.canvas; const canvas = context.canvas;
const strokePadding = strokeInstruction const strokePadding = strokeInstruction
? (strokeInstruction[2] * scale) / 2 ? (strokeInstruction[2] * scale[0]) / 2
: 0; : 0;
const renderBuffer = this.renderBuffer_; const renderBuffer = this.renderBuffer_;
const intersects = const intersects =
@@ -612,7 +617,7 @@ class Executor {
strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0; strokeState && strokeState.lineWidth ? strokeState.lineWidth : 0;
// Remove the 2 pixels we added in createLabel() for the anchor // Remove the 2 pixels we added in createLabel() for the anchor
const width = label.width / pixelRatio - 2 * textState.scale; const width = label.width / pixelRatio - 2 * textState.scale[0];
const anchorX = align * width + 2 * (0.5 - align) * strokeWidth; const anchorX = align * width + 2 * (0.5 - align) * strokeWidth;
const anchorY = const anchorY =
(baseline * label.height) / pixelRatio + (baseline * label.height) / pixelRatio +
@@ -791,7 +796,7 @@ class Executor {
const originY = /** @type {number} */ (instruction[10]); const originY = /** @type {number} */ (instruction[10]);
const rotateWithView = /** @type {boolean} */ (instruction[11]); const rotateWithView = /** @type {boolean} */ (instruction[11]);
let rotation = /** @type {number} */ (instruction[12]); let rotation = /** @type {number} */ (instruction[12]);
const scale = /** @type {number} */ (instruction[13]); const scale = /** @type {import("../../size.js").Size} */ (instruction[13]);
let width = /** @type {number} */ (instruction[14]); let width = /** @type {number} */ (instruction[14]);
if (!image && instruction.length >= 19) { if (!image && instruction.length >= 19) {
@@ -914,11 +919,17 @@ class Executor {
const strokeWidth = /** @type {number} */ (instruction[11]); const strokeWidth = /** @type {number} */ (instruction[11]);
text = /** @type {string} */ (instruction[12]); text = /** @type {string} */ (instruction[12]);
textKey = /** @type {string} */ (instruction[13]); textKey = /** @type {string} */ (instruction[13]);
const pixelRatioScale = /** @type {number} */ (instruction[14]); const pixelRatioScale = [
/** @type {number} */ (instruction[14]),
/** @type {number} */ (instruction[14]),
];
const textState = this.textStates[textKey]; const textState = this.textStates[textKey];
const font = textState.font; const font = textState.font;
const textScale = textState.scale * measurePixelRatio; const textScale = [
textState.scale[0] * measurePixelRatio,
textState.scale[1] * measurePixelRatio,
];
let cachedWidths; let cachedWidths;
if (font in this.widths_) { if (font in this.widths_) {
@@ -930,7 +941,8 @@ class Executor {
const pathLength = lineStringLength(pixelCoordinates, begin, end, 2); const pathLength = lineStringLength(pixelCoordinates, begin, end, 2);
const textLength = const textLength =
textScale * measureAndCacheTextWidth(font, text, cachedWidths); Math.abs(textScale[0]) *
measureAndCacheTextWidth(font, text, cachedWidths);
if (overflow || textLength <= pathLength) { if (overflow || textLength <= pathLength) {
const textAlign = this.textStates[textKey].textAlign; const textAlign = this.textStates[textKey].textAlign;
const startM = (pathLength - textLength) * TEXT_ALIGN[textAlign]; const startM = (pathLength - textLength) * TEXT_ALIGN[textAlign];
@@ -942,7 +954,7 @@ class Executor {
text, text,
startM, startM,
maxAngle, maxAngle,
textScale, Math.abs(textScale[0]),
measureAndCacheTextWidth, measureAndCacheTextWidth,
font, font,
cachedWidths cachedWidths
@@ -958,7 +970,8 @@ class Executor {
anchorX = /** @type {number} */ (part[2]) + strokeWidth; anchorX = /** @type {number} */ (part[2]) + strokeWidth;
anchorY = anchorY =
baseline * label.height + baseline * label.height +
(0.5 - baseline) * 2 * strokeWidth - ((0.5 - baseline) * 2 * strokeWidth * textScale[1]) /
textScale[0] -
offsetY; offsetY;
rendered = rendered =
this.replayImageOrLabel_( this.replayImageOrLabel_(
+4 -4
View File
@@ -82,7 +82,7 @@ class CanvasImageBuilder extends CanvasBuilder {
/** /**
* @private * @private
* @type {number|undefined} * @type {import("../../size.js").Size|undefined}
*/ */
this.scale_ = undefined; this.scale_ = undefined;
@@ -145,7 +145,7 @@ class CanvasImageBuilder extends CanvasBuilder {
this.originY_, this.originY_,
this.rotateWithView_, this.rotateWithView_,
this.rotation_, this.rotation_,
this.scale_ * this.pixelRatio, [this.scale_[0] * this.pixelRatio, this.scale_[1] * this.pixelRatio],
this.width_, this.width_,
]); ]);
this.hitDetectionInstructions.push([ this.hitDetectionInstructions.push([
@@ -202,7 +202,7 @@ class CanvasImageBuilder extends CanvasBuilder {
this.originY_, this.originY_,
this.rotateWithView_, this.rotateWithView_,
this.rotation_, this.rotation_,
this.scale_ * this.pixelRatio, [this.scale_[0] * this.pixelRatio, this.scale_[1] * this.pixelRatio],
this.width_, this.width_,
]); ]);
this.hitDetectionInstructions.push([ this.hitDetectionInstructions.push([
@@ -268,7 +268,7 @@ class CanvasImageBuilder extends CanvasBuilder {
this.originY_ = origin[1]; this.originY_ = origin[1];
this.rotateWithView_ = imageStyle.getRotateWithView(); this.rotateWithView_ = imageStyle.getRotateWithView();
this.rotation_ = imageStyle.getRotation(); this.rotation_ = imageStyle.getRotation();
this.scale_ = imageStyle.getScale(); this.scale_ = imageStyle.getScaleArray();
this.width_ = size[0]; this.width_ = size[0];
} }
} }
+88 -51
View File
@@ -188,9 +188,9 @@ class CanvasImmediateRenderer extends VectorContext {
/** /**
* @private * @private
* @type {number} * @type {import("../../size.js").Size}
*/ */
this.imageScale_ = 0; this.imageScale_ = [0, 0];
/** /**
* @private * @private
@@ -230,9 +230,9 @@ class CanvasImmediateRenderer extends VectorContext {
/** /**
* @private * @private
* @type {number} * @type {import("../../size.js").Size}
*/ */
this.textScale_ = 0; this.textScale_ = [0, 0];
/** /**
* @private * @private
@@ -297,35 +297,51 @@ class CanvasImmediateRenderer extends VectorContext {
for (let i = 0, ii = pixelCoordinates.length; i < ii; i += 2) { for (let i = 0, ii = pixelCoordinates.length; i < ii; i += 2) {
const x = pixelCoordinates[i] - this.imageAnchorX_; const x = pixelCoordinates[i] - this.imageAnchorX_;
const y = pixelCoordinates[i + 1] - this.imageAnchorY_; const y = pixelCoordinates[i + 1] - this.imageAnchorY_;
if (rotation !== 0 || this.imageScale_ != 1) { if (
rotation !== 0 ||
this.imageScale_[0] != 1 ||
this.imageScale_[1] != 1
) {
const centerX = x + this.imageAnchorX_; const centerX = x + this.imageAnchorX_;
const centerY = y + this.imageAnchorY_; const centerY = y + this.imageAnchorY_;
composeTransform( composeTransform(
localTransform, localTransform,
centerX, centerX,
centerY, centerY,
this.imageScale_, 1,
this.imageScale_, 1,
rotation, rotation,
-centerX, -centerX,
-centerY -centerY
); );
context.setTransform.apply(context, localTransform); context.setTransform.apply(context, localTransform);
context.translate(centerX, centerY);
context.scale(this.imageScale_[0], this.imageScale_[1]);
context.drawImage(
this.image_,
this.imageOriginX_,
this.imageOriginY_,
this.imageWidth_,
this.imageHeight_,
-this.imageAnchorX_,
-this.imageAnchorY_,
this.imageWidth_,
this.imageHeight_
);
context.setTransform(1, 0, 0, 1, 0, 0);
} else {
context.drawImage(
this.image_,
this.imageOriginX_,
this.imageOriginY_,
this.imageWidth_,
this.imageHeight_,
x,
y,
this.imageWidth_,
this.imageHeight_
);
} }
context.drawImage(
this.image_,
this.imageOriginX_,
this.imageOriginY_,
this.imageWidth_,
this.imageHeight_,
x,
y,
this.imageWidth_,
this.imageHeight_
);
}
if (rotation !== 0 || this.imageScale_ != 1) {
context.setTransform(1, 0, 0, 1, 0, 0);
} }
if (this.imageOpacity_ != 1) { if (this.imageOpacity_ != 1) {
context.globalAlpha = alpha; context.globalAlpha = alpha;
@@ -366,28 +382,39 @@ class CanvasImmediateRenderer extends VectorContext {
for (; offset < end; offset += stride) { for (; offset < end; offset += stride) {
const x = pixelCoordinates[offset] + this.textOffsetX_; const x = pixelCoordinates[offset] + this.textOffsetX_;
const y = pixelCoordinates[offset + 1] + this.textOffsetY_; const y = pixelCoordinates[offset + 1] + this.textOffsetY_;
if (rotation !== 0 || this.textScale_ != 1) { if (
rotation !== 0 ||
this.textScale_[0] != 1 ||
this.textScale_[1] != 1
) {
const localTransform = composeTransform( const localTransform = composeTransform(
this.tmpLocalTransform_, this.tmpLocalTransform_,
x, x,
y, y,
this.textScale_, 1,
this.textScale_, 1,
rotation, rotation,
-x, -x,
-y -y
); );
context.setTransform.apply(context, localTransform); context.setTransform.apply(context, localTransform);
context.translate(x, y);
context.scale(this.textScale_[0], this.textScale_[1]);
if (this.textStrokeState_) {
context.strokeText(this.text_, 0, 0);
}
if (this.textFillState_) {
context.fillText(this.text_, 0, 0);
}
context.setTransform(1, 0, 0, 1, 0, 0);
} else {
if (this.textStrokeState_) {
context.strokeText(this.text_, x, y);
}
if (this.textFillState_) {
context.fillText(this.text_, x, y);
}
} }
if (this.textStrokeState_) {
context.strokeText(this.text_, x, y);
}
if (this.textFillState_) {
context.fillText(this.text_, x, y);
}
}
if (rotation !== 0 || this.textScale_ != 1) {
context.setTransform(1, 0, 0, 1, 0, 0);
} }
} }
@@ -994,22 +1021,30 @@ class CanvasImmediateRenderer extends VectorContext {
if (!imageStyle) { if (!imageStyle) {
this.image_ = null; this.image_ = null;
} else { } else {
const imageAnchor = imageStyle.getAnchor();
// FIXME pixel ratio
const imageImage = imageStyle.getImage(1);
const imageOrigin = imageStyle.getOrigin();
const imageSize = imageStyle.getSize(); const imageSize = imageStyle.getSize();
this.imageAnchorX_ = imageAnchor[0]; if (!imageSize) {
this.imageAnchorY_ = imageAnchor[1]; this.image_ = null;
this.imageHeight_ = imageSize[1]; } else {
this.image_ = imageImage; const imageAnchor = imageStyle.getAnchor();
this.imageOpacity_ = imageStyle.getOpacity(); // FIXME pixel ratio
this.imageOriginX_ = imageOrigin[0]; const imageImage = imageStyle.getImage(1);
this.imageOriginY_ = imageOrigin[1]; const imageOrigin = imageStyle.getOrigin();
this.imageRotateWithView_ = imageStyle.getRotateWithView(); const imageScale = imageStyle.getScaleArray();
this.imageRotation_ = imageStyle.getRotation(); this.imageAnchorX_ = imageAnchor[0];
this.imageScale_ = imageStyle.getScale() * this.pixelRatio_; this.imageAnchorY_ = imageAnchor[1];
this.imageWidth_ = imageSize[0]; this.imageHeight_ = imageSize[1];
this.image_ = imageImage;
this.imageOpacity_ = imageStyle.getOpacity();
this.imageOriginX_ = imageOrigin[0];
this.imageOriginY_ = imageOrigin[1];
this.imageRotateWithView_ = imageStyle.getRotateWithView();
this.imageRotation_ = imageStyle.getRotation();
this.imageScale_ = [
this.pixelRatio_ * imageScale[0],
this.pixelRatio_ * imageScale[1],
];
this.imageWidth_ = imageSize[0];
}
} }
} }
@@ -1078,7 +1113,7 @@ class CanvasImmediateRenderer extends VectorContext {
const textOffsetY = textStyle.getOffsetY(); const textOffsetY = textStyle.getOffsetY();
const textRotateWithView = textStyle.getRotateWithView(); const textRotateWithView = textStyle.getRotateWithView();
const textRotation = textStyle.getRotation(); const textRotation = textStyle.getRotation();
const textScale = textStyle.getScale(); const textScale = textStyle.getScaleArray();
const textText = textStyle.getText(); const textText = textStyle.getText();
const textTextAlign = textStyle.getTextAlign(); const textTextAlign = textStyle.getTextAlign();
const textTextBaseline = textStyle.getTextBaseline(); const textTextBaseline = textStyle.getTextBaseline();
@@ -1099,8 +1134,10 @@ class CanvasImmediateRenderer extends VectorContext {
this.textRotateWithView_ = this.textRotateWithView_ =
textRotateWithView !== undefined ? textRotateWithView : false; textRotateWithView !== undefined ? textRotateWithView : false;
this.textRotation_ = textRotation !== undefined ? textRotation : 0; this.textRotation_ = textRotation !== undefined ? textRotation : 0;
this.textScale_ = this.textScale_ = [
this.pixelRatio_ * (textScale !== undefined ? textScale : 1); this.pixelRatio_ * textScale[0],
this.pixelRatio_ * textScale[1],
];
} }
} }
} }
+30 -9
View File
@@ -303,6 +303,27 @@ class CanvasTextBuilder extends CanvasBuilder {
this.beginGeometry(geometry, feature); this.beginGeometry(geometry, feature);
// adjust padding for negative scale
let padding = textState.padding;
if (
padding != defaultPadding &&
(textState.scale[0] < 0 || textState.scale[1] < 0)
) {
let p0 = textState.padding[0];
let p1 = textState.padding[1];
let p2 = textState.padding[2];
let p3 = textState.padding[3];
if (textState.scale[0] < 0) {
p1 = -p1;
p3 = -p3;
}
if (textState.scale[1] < 0) {
p0 = -p0;
p2 = -p2;
}
padding = [p0, p1, p2, p3];
}
// The image is unknown at this stage so we pass null; it will be computed at render time. // The image is unknown at this stage so we pass null; it will be computed at render time.
// For clarity, we pass NaN for offsetX, offsetY, width and height, which will be computed at // For clarity, we pass NaN for offsetX, offsetY, width and height, which will be computed at
// render time. // render time.
@@ -321,11 +342,11 @@ class CanvasTextBuilder extends CanvasBuilder {
0, 0,
this.textRotateWithView_, this.textRotateWithView_,
this.textRotation_, this.textRotation_,
1, [1, 1],
NaN, NaN,
textState.padding == defaultPadding padding == defaultPadding
? defaultPadding ? defaultPadding
: textState.padding.map(function (p) { : padding.map(function (p) {
return p * pixelRatio; return p * pixelRatio;
}), }),
!!textState.backgroundFill, !!textState.backgroundFill,
@@ -338,6 +359,7 @@ class CanvasTextBuilder extends CanvasBuilder {
this.textOffsetY_, this.textOffsetY_,
geometryWidths, geometryWidths,
]); ]);
const scale = 1 / pixelRatio;
this.hitDetectionInstructions.push([ this.hitDetectionInstructions.push([
CanvasInstruction.DRAW_IMAGE, CanvasInstruction.DRAW_IMAGE,
begin, begin,
@@ -352,9 +374,9 @@ class CanvasTextBuilder extends CanvasBuilder {
0, 0,
this.textRotateWithView_, this.textRotateWithView_,
this.textRotation_, this.textRotation_,
1 / this.pixelRatio, [scale, scale],
NaN, NaN,
textState.padding, padding,
!!textState.backgroundFill, !!textState.backgroundFill,
!!textState.backgroundStroke, !!textState.backgroundStroke,
this.text_, this.text_,
@@ -431,9 +453,8 @@ class CanvasTextBuilder extends CanvasBuilder {
const offsetY = this.textOffsetY_ * pixelRatio; const offsetY = this.textOffsetY_ * pixelRatio;
const text = this.text_; const text = this.text_;
const textScale = textState.scale;
const strokeWidth = strokeState const strokeWidth = strokeState
? (strokeState.lineWidth * textScale) / 2 ? (strokeState.lineWidth * Math.abs(textState.scale[0])) / 2
: 0; : 0;
this.instructions.push([ this.instructions.push([
@@ -529,7 +550,7 @@ class CanvasTextBuilder extends CanvasBuilder {
textState = this.textState_; textState = this.textState_;
const font = textStyle.getFont() || defaultFont; const font = textStyle.getFont() || defaultFont;
registerFont(font); registerFont(font);
const textScale = textStyle.getScale(); const textScale = textStyle.getScaleArray();
textState.overflow = textStyle.getOverflow(); textState.overflow = textStyle.getOverflow();
textState.font = font; textState.font = font;
textState.maxAngle = textStyle.getMaxAngle(); textState.maxAngle = textStyle.getMaxAngle();
@@ -540,7 +561,7 @@ class CanvasTextBuilder extends CanvasBuilder {
textState.backgroundFill = textStyle.getBackgroundFill(); textState.backgroundFill = textStyle.getBackgroundFill();
textState.backgroundStroke = textStyle.getBackgroundStroke(); textState.backgroundStroke = textStyle.getBackgroundStroke();
textState.padding = textStyle.getPadding() || defaultPadding; textState.padding = textStyle.getPadding() || defaultPadding;
textState.scale = textScale === undefined ? 1 : textScale; textState.scale = textScale === undefined ? [1, 1] : textScale;
const textOffsetX = textStyle.getOffsetX(); const textOffsetX = textStyle.getOffsetX();
const textOffsetY = textStyle.getOffsetY(); const textOffsetY = textStyle.getOffsetY();
+4 -3
View File
@@ -36,7 +36,7 @@ import {getUid} from '../util.js';
* @property {import("./IconOrigin.js").default} [offsetOrigin='top-left'] Origin of the offset: `bottom-left`, `bottom-right`, * @property {import("./IconOrigin.js").default} [offsetOrigin='top-left'] Origin of the offset: `bottom-left`, `bottom-right`,
* `top-left` or `top-right`. * `top-left` or `top-right`.
* @property {number} [opacity=1] Opacity of the icon. * @property {number} [opacity=1] Opacity of the icon.
* @property {number} [scale=1] Scale. * @property {number|import("../size.js").Size} [scale=1] Scale.
* @property {boolean} [rotateWithView=false] Whether to rotate the icon with the view. * @property {boolean} [rotateWithView=false] Whether to rotate the icon with the view.
* @property {number} [rotation=0] Rotation in radians (positive rotation clockwise). * @property {number} [rotation=0] Rotation in radians (positive rotation clockwise).
* @property {import("../size.js").Size} [size] Icon size in pixel. Can be used together with `offset` to define the * @property {import("../size.js").Size} [size] Icon size in pixel. Can be used together with `offset` to define the
@@ -69,7 +69,7 @@ class Icon extends ImageStyle {
const rotation = options.rotation !== undefined ? options.rotation : 0; const rotation = options.rotation !== undefined ? options.rotation : 0;
/** /**
* @type {number} * @type {number|import("../size.js").Size}
*/ */
const scale = options.scale !== undefined ? options.scale : 1; const scale = options.scale !== undefined ? options.scale : 1;
@@ -215,6 +215,7 @@ class Icon extends ImageStyle {
* @api * @api
*/ */
clone() { clone() {
const scale = this.getScale();
return new Icon({ return new Icon({
anchor: this.anchor_.slice(), anchor: this.anchor_.slice(),
anchorOrigin: this.anchorOrigin_, anchorOrigin: this.anchorOrigin_,
@@ -230,7 +231,7 @@ class Icon extends ImageStyle {
offsetOrigin: this.offsetOrigin_, offsetOrigin: this.offsetOrigin_,
size: this.size_ !== null ? this.size_.slice() : undefined, size: this.size_ !== null ? this.size_.slice() : undefined,
opacity: this.getOpacity(), opacity: this.getOpacity(),
scale: this.getScale(), scale: Array.isArray(scale) ? scale.slice() : scale,
rotation: this.getRotation(), rotation: this.getRotation(),
rotateWithView: this.getRotateWithView(), rotateWithView: this.getRotateWithView(),
}); });
+22 -5
View File
@@ -2,13 +2,14 @@
* @module ol/style/Image * @module ol/style/Image
*/ */
import {abstract} from '../util.js'; import {abstract} from '../util.js';
import {toSize} from '../size.js';
/** /**
* @typedef {Object} Options * @typedef {Object} Options
* @property {number} opacity * @property {number} opacity
* @property {boolean} rotateWithView * @property {boolean} rotateWithView
* @property {number} rotation * @property {number} rotation
* @property {number} scale * @property {number|import("../size.js").Size} scale
* @property {Array<number>} displacement * @property {Array<number>} displacement
*/ */
@@ -45,10 +46,16 @@ class ImageStyle {
/** /**
* @private * @private
* @type {number} * @type {number|import("../size.js").Size}
*/ */
this.scale_ = options.scale; this.scale_ = options.scale;
/**
* @private
* @type {import("../size.js").Size}
*/
this.scaleArray_ = toSize(options.scale);
/** /**
* @private * @private
* @type {Array<number>} * @type {Array<number>}
@@ -62,9 +69,10 @@ class ImageStyle {
* @api * @api
*/ */
clone() { clone() {
const scale = this.getScale();
return new ImageStyle({ return new ImageStyle({
opacity: this.getOpacity(), opacity: this.getOpacity(),
scale: this.getScale(), scale: Array.isArray(scale) ? scale.slice() : scale,
rotation: this.getRotation(), rotation: this.getRotation(),
rotateWithView: this.getRotateWithView(), rotateWithView: this.getRotateWithView(),
displacement: this.getDisplacement().slice(), displacement: this.getDisplacement().slice(),
@@ -100,13 +108,21 @@ class ImageStyle {
/** /**
* Get the symbolizer scale. * Get the symbolizer scale.
* @return {number} Scale. * @return {number|import("../size.js").Size} Scale.
* @api * @api
*/ */
getScale() { getScale() {
return this.scale_; return this.scale_;
} }
/**
* Get the symbolizer scale array.
* @return {import("../size.js").Size} Scale array.
*/
getScaleArray() {
return this.scaleArray_;
}
/** /**
* Get the displacement of the shape * Get the displacement of the shape
* @return {Array<number>} Shape's center displacement * @return {Array<number>} Shape's center displacement
@@ -219,11 +235,12 @@ class ImageStyle {
/** /**
* Set the scale. * Set the scale.
* *
* @param {number} scale Scale. * @param {number|import("../size.js").Size} scale Scale.
* @api * @api
*/ */
setScale(scale) { setScale(scale) {
this.scale_ = scale; this.scale_ = scale;
this.scaleArray_ = toSize(scale);
} }
/** /**
+22 -5
View File
@@ -3,6 +3,7 @@
*/ */
import Fill from './Fill.js'; import Fill from './Fill.js';
import TextPlacement from './TextPlacement.js'; import TextPlacement from './TextPlacement.js';
import {toSize} from '../size.js';
/** /**
* The default fill color to use if no fill was set at construction time; a * The default fill color to use if no fill was set at construction time; a
@@ -23,7 +24,7 @@ const DEFAULT_FILL_COLOR = '#333';
* @property {boolean} [overflow=false] For polygon labels or when `placement` is set to `'line'`, allow text to exceed * @property {boolean} [overflow=false] For polygon labels or when `placement` is set to `'line'`, allow text to exceed
* the width of the polygon at the label position or the length of the path that it follows. * the width of the polygon at the label position or the length of the path that it follows.
* @property {import("./TextPlacement.js").default|string} [placement='point'] Text placement. * @property {import("./TextPlacement.js").default|string} [placement='point'] Text placement.
* @property {number} [scale] Scale. * @property {number|import("../size.js").Size} [scale] Scale.
* @property {boolean} [rotateWithView=false] Whether to rotate the text with the view. * @property {boolean} [rotateWithView=false] Whether to rotate the text with the view.
* @property {number} [rotation=0] Rotation in radians (positive rotation clockwise). * @property {number} [rotation=0] Rotation in radians (positive rotation clockwise).
* @property {string} [text] Text content. * @property {string} [text] Text content.
@@ -74,10 +75,16 @@ class Text {
/** /**
* @private * @private
* @type {number|undefined} * @type {number|import("../size.js").Size|undefined}
*/ */
this.scale_ = options.scale; this.scale_ = options.scale;
/**
* @private
* @type {import("../size.js").Size}
*/
this.scaleArray_ = toSize(options.scale !== undefined ? options.scale : 1);
/** /**
* @private * @private
* @type {string|undefined} * @type {string|undefined}
@@ -172,6 +179,7 @@ class Text {
* @api * @api
*/ */
clone() { clone() {
const scale = this.getScale();
return new Text({ return new Text({
font: this.getFont(), font: this.getFont(),
placement: this.getPlacement(), placement: this.getPlacement(),
@@ -179,7 +187,7 @@ class Text {
overflow: this.getOverflow(), overflow: this.getOverflow(),
rotation: this.getRotation(), rotation: this.getRotation(),
rotateWithView: this.getRotateWithView(), rotateWithView: this.getRotateWithView(),
scale: this.getScale(), scale: Array.isArray(scale) ? scale.slice() : scale,
text: this.getText(), text: this.getText(),
textAlign: this.getTextAlign(), textAlign: this.getTextAlign(),
textBaseline: this.getTextBaseline(), textBaseline: this.getTextBaseline(),
@@ -280,13 +288,21 @@ class Text {
/** /**
* Get the text scale. * Get the text scale.
* @return {number|undefined} Scale. * @return {number|import("../size.js").Size|undefined} Scale.
* @api * @api
*/ */
getScale() { getScale() {
return this.scale_; return this.scale_;
} }
/**
* Get the symbolizer scale array.
* @return {import("../size.js").Size} Scale array.
*/
getScaleArray() {
return this.scaleArray_;
}
/** /**
* Get the stroke style for the text. * Get the stroke style for the text.
* @return {import("./Stroke.js").default} Stroke style. * @return {import("./Stroke.js").default} Stroke style.
@@ -443,11 +459,12 @@ class Text {
/** /**
* Set the scale. * Set the scale.
* *
* @param {number|undefined} scale Scale. * @param {number|import("../size.js").Size|undefined} scale Scale.
* @api * @api
*/ */
setScale(scale) { setScale(scale) {
this.scale_ = scale; this.scale_ = scale;
this.scaleArray_ = toSize(scale !== undefined ? scale : 1);
} }
/** /**