Fix some RegularShape bugs related to canvsa size
- remove jitter when using RegularShape size in animation, size should always be an integer - canvas may have been too small for miter line join - canvas was at least one stroke width too large for round line join - reduce canvas size even more for bevel line join Canvas now precisely fits the shape including stroke, The angle of the shape is ignored for the calculation.
This commit is contained in:
@@ -339,6 +339,96 @@ class RegularShape extends ImageStyle {
|
||||
*/
|
||||
unlistenImageChange(listener) {}
|
||||
|
||||
/**
|
||||
* Calculate additional canvas size needed for the miter.
|
||||
* @param {string} lineJoin Line join
|
||||
* @param {number} strokeWidth Stroke width
|
||||
* @param {number} miterLimit Miter limit
|
||||
* @return {number} Additional canvas size needed
|
||||
* @private
|
||||
*/
|
||||
calculateLineJoinSize_(lineJoin, strokeWidth, miterLimit) {
|
||||
if (
|
||||
strokeWidth === 0 ||
|
||||
this.points_ === Infinity ||
|
||||
(lineJoin !== 'bevel' && lineJoin !== 'miter')
|
||||
) {
|
||||
return strokeWidth;
|
||||
}
|
||||
// m | ^
|
||||
// i | |\ .
|
||||
// t >| #\
|
||||
// e | |\ \ .
|
||||
// r \s\
|
||||
// | \t\ . .
|
||||
// \r\ . .
|
||||
// | \o\ . . . . .
|
||||
// e \k\ . . . .
|
||||
// | \e\ . . . . .
|
||||
// d \ \ . . . .
|
||||
// | _ _a_ _\# . . .
|
||||
// r1 / ` . .
|
||||
// | . .
|
||||
// b / . .
|
||||
// | . .
|
||||
// / r2 . .
|
||||
// | . .
|
||||
// / . .
|
||||
// |α . .
|
||||
// / . .
|
||||
// ° center
|
||||
let r1 = this.radius_;
|
||||
let r2 = this.radius2_ === undefined ? r1 : this.radius2_;
|
||||
if (r1 < r2) {
|
||||
const tmp = r1;
|
||||
r1 = r2;
|
||||
r2 = tmp;
|
||||
}
|
||||
const points =
|
||||
this.radius2_ === undefined ? this.points_ : this.points_ * 2;
|
||||
const alpha = (2 * Math.PI) / points;
|
||||
const a = r2 * Math.sin(alpha);
|
||||
const b = Math.sqrt(r2 * r2 - a * a);
|
||||
const d = r1 - b;
|
||||
const e = Math.sqrt(a * a + d * d);
|
||||
const miterRatio = e / a;
|
||||
if (lineJoin === 'miter' && miterRatio <= miterLimit) {
|
||||
return miterRatio * strokeWidth;
|
||||
}
|
||||
// Calculate the distnce from center to the stroke corner where
|
||||
// it was cut short because of the miter limit.
|
||||
// l
|
||||
// ----+---- <= distance from center to here is maxr
|
||||
// /####|k ##\
|
||||
// /#####^#####\
|
||||
// /#### /+\# s #\
|
||||
// /### h/+++\# t #\
|
||||
// /### t/+++++\# r #\
|
||||
// /### a/+++++++\# o #\
|
||||
// /### p/++ fill +\# k #\
|
||||
///#### /+++++^+++++\# e #\
|
||||
//#####/+++++/+\+++++\#####\
|
||||
const k = strokeWidth / 2 / miterRatio;
|
||||
const l = (strokeWidth / 2) * (d / e);
|
||||
const maxr = Math.sqrt((r1 + k) * (r1 + k) + l * l);
|
||||
const bevelAdd = maxr - r1;
|
||||
if (this.radius2_ === undefined || lineJoin === 'bevel') {
|
||||
return bevelAdd * 2;
|
||||
}
|
||||
// If outer miter is over the miter limit the inner miter may reach through the
|
||||
// center and be longer than the bevel, same calculation as above but swap r1 / r2.
|
||||
const aa = r1 * Math.sin(alpha);
|
||||
const bb = Math.sqrt(r1 * r1 - aa * aa);
|
||||
const dd = r2 - bb;
|
||||
const ee = Math.sqrt(aa * aa + dd * dd);
|
||||
const innerMiterRatio = ee / aa;
|
||||
if (innerMiterRatio <= miterLimit) {
|
||||
const innerLength = (innerMiterRatio * strokeWidth) / 2 - r2 - r1;
|
||||
return 2 * Math.max(bevelAdd, innerLength);
|
||||
}
|
||||
return bevelAdd * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {RenderOptions} The render options
|
||||
* @protected
|
||||
@@ -378,8 +468,9 @@ class RegularShape extends ImageStyle {
|
||||
}
|
||||
}
|
||||
|
||||
const add = this.calculateLineJoinSize_(lineJoin, strokeWidth, miterLimit);
|
||||
const maxRadius = Math.max(this.radius_, this.radius2_ || 0);
|
||||
const size = 2 * (maxRadius + strokeWidth) + 1;
|
||||
const size = Math.ceil(2 * maxRadius + add);
|
||||
|
||||
return {
|
||||
strokeStyle: strokeStyle,
|
||||
|
||||
@@ -7,14 +7,14 @@ describe('ol.style.Circle', function () {
|
||||
it('creates a canvas (no fill-style)', function () {
|
||||
const style = new CircleStyle({radius: 10});
|
||||
expect(style.getImage(1)).to.be.an(HTMLCanvasElement);
|
||||
expect(style.getSize()).to.eql([21, 21]);
|
||||
expect(style.getImageSize()).to.eql([21, 21]);
|
||||
expect(style.getSize()).to.eql([20, 20]);
|
||||
expect(style.getImageSize()).to.eql([20, 20]);
|
||||
expect(style.getOrigin()).to.eql([0, 0]);
|
||||
expect(style.getAnchor()).to.eql([10.5, 10.5]);
|
||||
expect(style.getAnchor()).to.eql([10, 10]);
|
||||
// no hit-detection image is created, because no fill style is set
|
||||
expect(style.getImage(1)).to.be(style.getHitDetectionImage());
|
||||
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
|
||||
expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
|
||||
expect(style.getHitDetectionImageSize()).to.eql([20, 20]);
|
||||
});
|
||||
|
||||
it('creates a canvas (transparent fill-style)', function () {
|
||||
@@ -25,14 +25,14 @@ describe('ol.style.Circle', function () {
|
||||
}),
|
||||
});
|
||||
expect(style.getImage(1)).to.be.an(HTMLCanvasElement);
|
||||
expect(style.getSize()).to.eql([21, 21]);
|
||||
expect(style.getImageSize()).to.eql([21, 21]);
|
||||
expect(style.getSize()).to.eql([20, 20]);
|
||||
expect(style.getImageSize()).to.eql([20, 20]);
|
||||
expect(style.getOrigin()).to.eql([0, 0]);
|
||||
expect(style.getAnchor()).to.eql([10.5, 10.5]);
|
||||
expect(style.getAnchor()).to.eql([10, 10]);
|
||||
// hit-detection image is created, because transparent fill style is set
|
||||
expect(style.getImage(1)).to.not.be(style.getHitDetectionImage());
|
||||
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
|
||||
expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
|
||||
expect(style.getHitDetectionImageSize()).to.eql([20, 20]);
|
||||
});
|
||||
|
||||
it('creates a canvas (non-transparent fill-style)', function () {
|
||||
@@ -43,14 +43,14 @@ describe('ol.style.Circle', function () {
|
||||
}),
|
||||
});
|
||||
expect(style.getImage(1)).to.be.an(HTMLCanvasElement);
|
||||
expect(style.getSize()).to.eql([21, 21]);
|
||||
expect(style.getImageSize()).to.eql([21, 21]);
|
||||
expect(style.getSize()).to.eql([20, 20]);
|
||||
expect(style.getImageSize()).to.eql([20, 20]);
|
||||
expect(style.getOrigin()).to.eql([0, 0]);
|
||||
expect(style.getAnchor()).to.eql([10.5, 10.5]);
|
||||
expect(style.getAnchor()).to.eql([10, 10]);
|
||||
// no hit-detection image is created, because non-transparent fill style is set
|
||||
expect(style.getImage(1)).to.be(style.getHitDetectionImage());
|
||||
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
|
||||
expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
|
||||
expect(style.getHitDetectionImageSize()).to.eql([20, 20]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -33,14 +33,14 @@ describe('ol.style.RegularShape', function () {
|
||||
it('creates a canvas (no fill-style)', function () {
|
||||
const style = new RegularShape({radius: 10});
|
||||
expect(style.getImage(1)).to.be.an(HTMLCanvasElement);
|
||||
expect(style.getSize()).to.eql([21, 21]);
|
||||
expect(style.getImageSize()).to.eql([21, 21]);
|
||||
expect(style.getSize()).to.eql([20, 20]);
|
||||
expect(style.getImageSize()).to.eql([20, 20]);
|
||||
expect(style.getOrigin()).to.eql([0, 0]);
|
||||
expect(style.getAnchor()).to.eql([10.5, 10.5]);
|
||||
expect(style.getAnchor()).to.eql([10, 10]);
|
||||
// no hit-detection image is created, because no fill style is set
|
||||
expect(style.getImage(1)).to.be(style.getHitDetectionImage());
|
||||
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
|
||||
expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
|
||||
expect(style.getHitDetectionImageSize()).to.eql([20, 20]);
|
||||
});
|
||||
|
||||
it('creates a canvas (transparent fill-style)', function () {
|
||||
@@ -51,18 +51,18 @@ describe('ol.style.RegularShape', function () {
|
||||
}),
|
||||
});
|
||||
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.getImage(1).width).to.be(20);
|
||||
expect(style.getImage(2).width).to.be(40);
|
||||
expect(style.getPixelRatio(2)).to.be(2);
|
||||
expect(style.getSize()).to.eql([21, 21]);
|
||||
expect(style.getImageSize()).to.eql([21, 21]);
|
||||
expect(style.getSize()).to.eql([20, 20]);
|
||||
expect(style.getImageSize()).to.eql([20, 20]);
|
||||
expect(style.getOrigin()).to.eql([0, 0]);
|
||||
expect(style.getAnchor()).to.eql([10.5, 10.5]);
|
||||
expect(style.getAnchor()).to.eql([10, 10]);
|
||||
// hit-detection image is created, because transparent fill style is set
|
||||
expect(style.getImage(1)).to.not.be(style.getHitDetectionImage());
|
||||
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
|
||||
expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
|
||||
expect(style.getHitDetectionImage().width).to.be(21);
|
||||
expect(style.getHitDetectionImageSize()).to.eql([20, 20]);
|
||||
expect(style.getHitDetectionImage().width).to.be(20);
|
||||
});
|
||||
|
||||
it('creates a canvas (non-transparent fill-style)', function () {
|
||||
@@ -73,14 +73,14 @@ describe('ol.style.RegularShape', function () {
|
||||
}),
|
||||
});
|
||||
expect(style.getImage(1)).to.be.an(HTMLCanvasElement);
|
||||
expect(style.getSize()).to.eql([21, 21]);
|
||||
expect(style.getImageSize()).to.eql([21, 21]);
|
||||
expect(style.getSize()).to.eql([20, 20]);
|
||||
expect(style.getImageSize()).to.eql([20, 20]);
|
||||
expect(style.getOrigin()).to.eql([0, 0]);
|
||||
expect(style.getAnchor()).to.eql([10.5, 10.5]);
|
||||
expect(style.getAnchor()).to.eql([10, 10]);
|
||||
// no hit-detection image is created, because non-transparent fill style is set
|
||||
expect(style.getImage(1)).to.be(style.getHitDetectionImage());
|
||||
expect(style.getHitDetectionImage()).to.be.an(HTMLCanvasElement);
|
||||
expect(style.getHitDetectionImageSize()).to.eql([21, 21]);
|
||||
expect(style.getHitDetectionImageSize()).to.eql([20, 20]);
|
||||
});
|
||||
|
||||
it('sets default displacement [0, 0]', function () {
|
||||
@@ -228,4 +228,67 @@ describe('ol.style.RegularShape', function () {
|
||||
expect(canvas.closePath.callCount).to.be(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#calculateLineJoinSize_', function () {
|
||||
function create({
|
||||
radius = 10,
|
||||
radius2,
|
||||
points = 4,
|
||||
strokeWidth = 10,
|
||||
lineJoin = 'miter',
|
||||
miterLimit = 10,
|
||||
}) {
|
||||
return new RegularShape({
|
||||
radius,
|
||||
radius2,
|
||||
points,
|
||||
stroke: new Stroke({
|
||||
color: 'red',
|
||||
width: strokeWidth,
|
||||
lineJoin,
|
||||
miterLimit,
|
||||
}),
|
||||
});
|
||||
}
|
||||
describe('polygon', function () {
|
||||
it('sets size to diameter', function () {
|
||||
const style = create({strokeWidth: 0});
|
||||
expect(style.getSize()).to.eql([20, 20]);
|
||||
});
|
||||
it('sets size to diameter rounded up', function () {
|
||||
const style = create({radius: 9.9, strokeWidth: 0});
|
||||
expect(style.getSize()).to.eql([20, 20]);
|
||||
});
|
||||
it('sets size to diameter plus miter', function () {
|
||||
const style = create({});
|
||||
expect(style.getSize()).to.eql([35, 35]);
|
||||
});
|
||||
it('sets size to diameter plus miter with miter limit', function () {
|
||||
const style = create({miterLimit: 0});
|
||||
expect(style.getSize()).to.eql([28, 28]);
|
||||
});
|
||||
it('sets size to diameter plus bevel', function () {
|
||||
const style = create({lineJoin: 'bevel'});
|
||||
expect(style.getSize()).to.eql([28, 28]);
|
||||
});
|
||||
it('sets size to diameter plus stroke width with round line join', function () {
|
||||
const style = create({lineJoin: 'round'});
|
||||
expect(style.getSize()).to.eql([30, 30]);
|
||||
});
|
||||
});
|
||||
describe('star', function () {
|
||||
it('sets size to diameter plus miter r1 > r2', function () {
|
||||
const style = create({radius2: 1, miterLimit: 100});
|
||||
expect(style.getSize()).to.eql([152, 152]);
|
||||
});
|
||||
it('sets size to diameter plus miter r1 < r2', function () {
|
||||
const style = create({radius2: 2, points: 7, miterLimit: 100});
|
||||
expect(style.getSize()).to.eql([116, 116]);
|
||||
});
|
||||
it('sets size with spokes through center and outer bevel', function () {
|
||||
const style = create({radius2: 80, points: 9, strokeWidth: 90});
|
||||
expect(style.getSize()).to.eql([213, 213]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user