diff --git a/docs/components/expression-metadata.js b/docs/components/expression-metadata.js index 15c8f55741d..d96033e5a2f 100644 --- a/docs/components/expression-metadata.js +++ b/docs/components/expression-metadata.js @@ -103,6 +103,24 @@ const types = { 'stop_input_n: number, stop_output_n: OutputType, ...' ] }], + 'interpolate-hcl': [{ + type: 'Color', + parameters: [ + 'interpolation: ["linear"] | ["exponential", base] | ["cubic-bezier", x1, y1, x2, y2 ]', + 'input: number', + 'stop_input_1: number, stop_output_1: Color', + 'stop_input_n: number, stop_output_n: Color, ...' + ] + }], + 'interpolate-lab': [{ + type: 'Color', + parameters: [ + 'interpolation: ["linear"] | ["exponential", base] | ["cubic-bezier", x1, y1, x2, y2 ]', + 'input: number', + 'stop_input_1: number, stop_output_1: Color', + 'stop_input_n: number, stop_output_n: Color, ...' + ] + }], length: [{ type: 'number', parameters: ['string | array | value'] diff --git a/src/style-spec/expression/definitions/index.js b/src/style-spec/expression/definitions/index.js index 2b50821baa0..504cfe5da62 100644 --- a/src/style-spec/expression/definitions/index.js +++ b/src/style-spec/expression/definitions/index.js @@ -49,6 +49,8 @@ const expressions: ExpressionRegistry = { 'collator': CollatorExpression, 'format': FormatExpression, 'interpolate': Interpolate, + 'interpolate-hcl': Interpolate, + 'interpolate-lab': Interpolate, 'length': Length, 'let': Let, 'literal': Literal, diff --git a/src/style-spec/expression/definitions/interpolate.js b/src/style-spec/expression/definitions/interpolate.js index 85e1460c9b8..bda11a2ca86 100644 --- a/src/style-spec/expression/definitions/interpolate.js +++ b/src/style-spec/expression/definitions/interpolate.js @@ -3,8 +3,9 @@ import UnitBezier from '@mapbox/unitbezier'; import * as interpolate from '../../util/interpolate'; -import { toString, NumberType } from '../types'; +import { toString, NumberType, ColorType } from '../types'; import { findStopLessThanOrEqualTo } from '../stops'; +import { hcl, lab } from '../../util/color_spaces'; import type { Stops } from '../stops'; import type { Expression } from '../expression'; @@ -21,13 +22,15 @@ export type InterpolationType = class Interpolate implements Expression { type: Type; + operator: 'interpolate' | 'interpolate-hcl' | 'interpolate-lab'; interpolation: InterpolationType; input: Expression; labels: Array; outputs: Array; - constructor(type: Type, interpolation: InterpolationType, input: Expression, stops: Stops) { + constructor(type: Type, operator: 'interpolate' | 'interpolate-hcl' | 'interpolate-lab', interpolation: InterpolationType, input: Expression, stops: Stops) { this.type = type; + this.operator = operator; this.interpolation = interpolation; this.input = input; @@ -54,7 +57,7 @@ class Interpolate implements Expression { } static parse(args: Array, context: ParsingContext) { - let [ , interpolation, input, ...rest] = args; + let [operator, interpolation, input, ...rest] = args; if (!Array.isArray(interpolation) || interpolation.length === 0) { return context.error(`Expected an interpolation type expression.`, 1); @@ -101,7 +104,9 @@ class Interpolate implements Expression { const stops: Stops = []; let outputType: Type = (null: any); - if (context.expectedType && context.expectedType.kind !== 'value') { + if (operator === 'interpolate-hcl' || operator === 'interpolate-lab') { + outputType = ColorType; + } else if (context.expectedType && context.expectedType.kind !== 'value') { outputType = context.expectedType; } @@ -137,7 +142,7 @@ class Interpolate implements Expression { return context.error(`Type ${toString(outputType)} is not interpolatable.`); } - return new Interpolate(outputType, interpolation, input, stops); + return new Interpolate(outputType, (operator: any), interpolation, input, stops); } evaluate(ctx: EvaluationContext) { @@ -166,7 +171,13 @@ class Interpolate implements Expression { const outputLower = outputs[index].evaluate(ctx); const outputUpper = outputs[index + 1].evaluate(ctx); - return (interpolate[this.type.kind.toLowerCase()]: any)(outputLower, outputUpper, t); // eslint-disable-line import/namespace + if (this.operator === 'interpolate') { + return (interpolate[this.type.kind.toLowerCase()]: any)(outputLower, outputUpper, t); // eslint-disable-line import/namespace + } else if (this.operator === 'interpolate-hcl') { + return hcl.reverse(hcl.interpolate(hcl.forward(outputLower), hcl.forward(outputUpper), t)); + } else { + return lab.reverse(lab.interpolate(lab.forward(outputLower), lab.forward(outputUpper), t)); + } } eachChild(fn: (Expression) => void) { @@ -194,7 +205,7 @@ class Interpolate implements Expression { interpolation = ["cubic-bezier" ].concat(this.interpolation.controlPoints); } - const serialized = ["interpolate", interpolation, this.input.serialize()]; + const serialized = [this.operator, interpolation, this.input.serialize()]; for (let i = 0; i < this.labels.length; i++) { serialized.push( diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index d4502100472..6a0724d08a1 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -2405,6 +2405,24 @@ } } }, + "interpolate-hcl": { + "doc": "Produces continuous, smooth results by interpolating between pairs of input and output values (\"stops\"). Works like `interpolate`, but the output type must be `color`, and the interpolation is performed in the Hue-Chroma-Luminance color space.", + "group": "Ramps, scales, curves", + "sdk-support": { + "basic functionality": { + "js": "0.49.0" + } + } + }, + "interpolate-lab": { + "doc": "Produces continuous, smooth results by interpolating between pairs of input and output values (\"stops\"). Works like `interpolate`, but the output type must be `color`, and the interpolation is performed in the CIELAB color space.", + "group": "Ramps, scales, curves", + "sdk-support": { + "basic functionality": { + "js": "0.49.0" + } + } + }, "ln2": { "doc": "Returns mathematical constant ln(2).", "group": "Math", diff --git a/test/integration/expression-tests/interpolate-hcl/linear/test.json b/test/integration/expression-tests/interpolate-hcl/linear/test.json new file mode 100644 index 00000000000..53392e33ba9 --- /dev/null +++ b/test/integration/expression-tests/interpolate-hcl/linear/test.json @@ -0,0 +1,40 @@ +{ + "expression": [ + "interpolate-hcl", + ["linear"], + ["get", "x"], + 1, + "red", + 11, + ["get", "color"] + ], + "inputs": [ + [{}, {"properties": {"x": 0, "color": "blue"}}], + [{}, {"properties": {"x": 5, "color": "blue"}}], + [{}, {"properties": {"x": 11, "color": "blue"}}], + [{}, {"properties": {"x": 11, "color": "oops blue"}}] + ], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": false, + "isZoomConstant": true, + "type": "color" + }, + "outputs": [ + [1, 0, 0, 1], + [0.870599, -0.0798922, 0.394264, 1], + [0, 0, 1, 1], + {"error": "Could not parse color from value 'oops blue'"} + ], + "serialized": [ + "interpolate-hcl", + ["linear"], + ["number", ["get", "x"]], + 1, + ["rgba", 255, 0, 0, 1], + 11, + ["to-color", ["get", "color"]] + ] + } +} diff --git a/test/integration/expression-tests/interpolate-lab/linear/test.json b/test/integration/expression-tests/interpolate-lab/linear/test.json new file mode 100644 index 00000000000..8710e00ef15 --- /dev/null +++ b/test/integration/expression-tests/interpolate-lab/linear/test.json @@ -0,0 +1,40 @@ +{ + "expression": [ + "interpolate-lab", + ["linear"], + ["get", "x"], + 1, + "red", + 11, + ["get", "color"] + ], + "inputs": [ + [{}, {"properties": {"x": 0, "color": "blue"}}], + [{}, {"properties": {"x": 5, "color": "blue"}}], + [{}, {"properties": {"x": 11, "color": "blue"}}], + [{}, {"properties": {"x": 11, "color": "oops blue"}}] + ], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": false, + "isZoomConstant": true, + "type": "color" + }, + "outputs": [ + [1, 0, 0, 1], + [0.599999, 7.22057e-8, 0.4, 1], + [0, 0, 1, 1], + {"error": "Could not parse color from value 'oops blue'"} + ], + "serialized": [ + "interpolate-lab", + ["linear"], + ["number", ["get", "x"]], + 1, + ["rgba", 255, 0, 0, 1], + 11, + ["to-color", ["get", "color"]] + ] + } +}