diff --git a/src/style/style_layer/line_style_layer.js b/src/style/style_layer/line_style_layer.js index f3dbfe995a1..981fb6badf9 100644 --- a/src/style/style_layer/line_style_layer.js +++ b/src/style/style_layer/line_style_layer.js @@ -73,7 +73,7 @@ class LineStyleLayer extends StyleLayer { const maxTilePixelSize = 1024; const maxTextureCoverage = geometryTileCoverageAtMaxZoom * maxTilePixelSize; const textureResolution = nextPowerOfTwo(clamp(maxTextureCoverage, 256, maxTextureSize)); - this.gradient = renderColorRamp(expression, 'lineProgress', this.stepInterpolant ? textureResolution : 256); + this.gradient = renderColorRamp(expression, 'lineProgress', this.stepInterpolant ? textureResolution : 256, this.gradient || undefined); } recalculate(parameters: EvaluationParameters, availableImages: Array) { diff --git a/src/util/color_ramp.js b/src/util/color_ramp.js index 5a7cca78cc6..70218b0f034 100644 --- a/src/util/color_ramp.js +++ b/src/util/color_ramp.js @@ -12,20 +12,28 @@ import type {StylePropertyExpression} from '../style-spec/expression/index'; * * @private */ -export default function renderColorRamp(expression: StylePropertyExpression, colorRampEvaluationParameter: string, textureResolution?: number): RGBAImage { +export default function renderColorRamp(expression: StylePropertyExpression, colorRampEvaluationParameter: string, textureResolution?: number, image?: RGBAImage): RGBAImage { const resolution = textureResolution || 256; assert(isPowerOfTwo(resolution)); - const colorRampData = new Uint8Array(resolution * 4); + let outImage; + + if (image && image.width === resolution) { + outImage = image; + } else { + outImage = new RGBAImage({width: resolution, height: 1}); + } + const evaluationGlobals = {}; for (let i = 0, j = 0; i < resolution; i++, j += 4) { evaluationGlobals[colorRampEvaluationParameter] = i / (resolution - 1); const pxColor = expression.evaluate((evaluationGlobals: any)); // the colors are being unpremultiplied because Color uses // premultiplied values, and the Texture class expects unpremultiplied ones - colorRampData[j + 0] = Math.floor(pxColor.r * 255 / pxColor.a); - colorRampData[j + 1] = Math.floor(pxColor.g * 255 / pxColor.a); - colorRampData[j + 2] = Math.floor(pxColor.b * 255 / pxColor.a); - colorRampData[j + 3] = Math.floor(pxColor.a * 255); + outImage.data[j + 0] = Math.floor(pxColor.r * 255 / pxColor.a); + outImage.data[j + 1] = Math.floor(pxColor.g * 255 / pxColor.a); + outImage.data[j + 2] = Math.floor(pxColor.b * 255 / pxColor.a); + outImage.data[j + 3] = Math.floor(pxColor.a * 255); } - return new RGBAImage({width: resolution, height: 1}, colorRampData); + + return outImage; } diff --git a/test/unit/util/color_ramp.test.js b/test/unit/util/color_ramp.test.js index 22687cf3163..9c4e4bc1879 100644 --- a/test/unit/util/color_ramp.test.js +++ b/test/unit/util/color_ramp.test.js @@ -21,7 +21,7 @@ function nearlyEquals(a, b) { return a.every((e, i) => Math.abs(e - b[i]) <= 3); } -test('renderColorRamp', (t) => { +test('renderColorRamp linear', (t) => { const expression = createPropertyExpression([ 'interpolate', @@ -47,3 +47,61 @@ test('renderColorRamp', (t) => { t.end(); }); + +test('renderColorRamp step', (t) => { + + const expression = createPropertyExpression([ + 'step', + ['line-progress'], + 'rgba(0, 0, 255, 0.1)', + 0.1, 'red', + 0.2, 'yellow', + 0.3, 'white', + 0.5, 'black', + 1, 'black' + ], spec, {handleErrors: false}).value; + + const ramp = renderColorRamp(expression, 'lineProgress', 512); + + t.equal(ramp.width, 512); + t.equal(ramp.height, 1); + + t.equal(pixelAt(ramp, 0)[3], 25, 'pixel at 0.0 matches input alpha'); + t.ok(nearlyEquals(pixelAt(ramp, 50), [0, 0, 255, 25]), 'pixel < 0.1 matches input'); + t.ok(nearlyEquals(pixelAt(ramp, 53), [255, 0, 0, 255]), 'pixel > 0.1 & < 0.2 matches input'); + t.ok(nearlyEquals(pixelAt(ramp, 103), [255, 255, 0, 255]), 'pixel > 0.2 & < 0.3 matches input'); + t.ok(nearlyEquals(pixelAt(ramp, 160), [255, 255, 255, 255]), 'pixel > 0.3 & < 0.5 matches input'); + t.ok(nearlyEquals(pixelAt(ramp, 256), [0, 0, 0, 255]), 'pixel > 0.5 matches input'); + + t.end(); +}); + +test('renderColorRamp usePlacement', (t) => { + + const expression = createPropertyExpression([ + 'step', + ['line-progress'], + 'rgba(255, 0, 0, 0.5)', + 0.1, 'black', + 0.2, 'red', + 0.3, 'blue', + 0.5, 'white', + 1, 'white' + ], spec, {handleErrors: false}).value; + + let ramp = renderColorRamp(expression, 'lineProgress', 512); + + t.equal(ramp.width, 512); + t.equal(ramp.height, 1); + + ramp = renderColorRamp(expression, 'lineProgress', 512, ramp); + + t.equal(pixelAt(ramp, 0)[3], 127, 'pixel at 0.0 matches input alpha'); + t.ok(nearlyEquals(pixelAt(ramp, 50), [255, 0, 0, 127]), 'pixel < 0.1 matches input'); + t.ok(nearlyEquals(pixelAt(ramp, 53), [0, 0, 0, 255]), 'pixel > 0.1 & < 0.2 matches input'); + t.ok(nearlyEquals(pixelAt(ramp, 103), [255, 0, 0, 255]), 'pixel > 0.2 & < 0.3 matches input'); + t.ok(nearlyEquals(pixelAt(ramp, 160), [0, 0, 255, 255]), 'pixel > 0.3 & < 0.5 matches input'); + t.ok(nearlyEquals(pixelAt(ramp, 256), [255, 255, 255, 255]), 'pixel > 0.5 matches input'); + + t.end(); +});