Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix HCL/LAB color spaces #5843

Merged
merged 2 commits into from
Dec 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 14 additions & 30 deletions src/style-spec/function/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ function createFunction(parameters, propertySpec) {
}
}

if (parameters.colorSpace && parameters.colorSpace !== 'rgb' && !colorSpaces[parameters.colorSpace]) {
throw new Error(`Unknown color space: ${parameters.colorSpace}`);
}

let innerFun;
let hashedStops;
let categoricalKeyType;
Expand All @@ -62,31 +66,6 @@ function createFunction(parameters, propertySpec) {
throw new Error(`Unknown function type "${type}"`);
}

let outputFunction;

// If we're interpolating colors in a color system other than RGBA,
// first translate all stop values to that color system, then interpolate
// arrays as usual. The `outputFunction` option lets us then translate
// the result of that interpolation back into RGBA.
if (parameters.colorSpace && parameters.colorSpace !== 'rgb') {
if (colorSpaces[parameters.colorSpace]) {
const colorspace = colorSpaces[parameters.colorSpace];
// Avoid mutating the parameters value
parameters = JSON.parse(JSON.stringify(parameters));
for (let s = 0; s < parameters.stops.length; s++) {
parameters.stops[s] = [
parameters.stops[s][0],
colorspace.forward(parameters.stops[s][1])
];
}
outputFunction = colorspace.reverse;
} else {
throw new Error(`Unknown color space: ${parameters.colorSpace}`);
}
} else {
outputFunction = identityFunction;
}

if (zoomAndFeatureDependent) {
const featureFunctions = {};
const zoomStops = [];
Expand Down Expand Up @@ -116,10 +95,10 @@ function createFunction(parameters, propertySpec) {
interpolationFactor: Interpolate.interpolationFactor.bind(undefined, {name: 'linear'}),
zoomStops: featureFunctionStops.map(s => s[0]),
evaluate({zoom}, properties) {
return outputFunction(evaluateExponentialFunction({
return evaluateExponentialFunction({
stops: featureFunctionStops,
base: parameters.base
}, propertySpec, zoom).evaluate(zoom, properties));
}, propertySpec, zoom).evaluate(zoom, properties);
}
};
} else if (zoomDependent) {
Expand All @@ -129,7 +108,7 @@ function createFunction(parameters, propertySpec) {
Interpolate.interpolationFactor.bind(undefined, {name: 'exponential', base: parameters.base !== undefined ? parameters.base : 1}) :
() => 0,
zoomStops: parameters.stops.map(s => s[0]),
evaluate: ({zoom}) => outputFunction(innerFun(parameters, propertySpec, zoom, hashedStops, categoricalKeyType))
evaluate: ({zoom}) => innerFun(parameters, propertySpec, zoom, hashedStops, categoricalKeyType)
};
} else {
return {
Expand All @@ -139,7 +118,7 @@ function createFunction(parameters, propertySpec) {
if (value === undefined) {
return coalesce(parameters.default, propertySpec.default);
}
return outputFunction(innerFun(parameters, propertySpec, value, hashedStops, categoricalKeyType));
return innerFun(parameters, propertySpec, value, hashedStops, categoricalKeyType);
}
};
}
Expand Down Expand Up @@ -187,7 +166,12 @@ function evaluateExponentialFunction(parameters, propertySpec, input) {

const outputLower = parameters.stops[index][1];
const outputUpper = parameters.stops[index + 1][1];
const interp = interpolate[propertySpec.type] || identityFunction;
let interp = interpolate[propertySpec.type] || identityFunction;

if (parameters.colorSpace && parameters.colorSpace !== 'rgb') {
const colorspace = colorSpaces[parameters.colorSpace];
interp = (a, b) => colorspace.reverse(colorspace.interpolate(colorspace.forward(a), colorspace.forward(b), t));
}

if (typeof outputLower.evaluate === 'function') {
return {
Expand Down
30 changes: 28 additions & 2 deletions src/style-spec/util/color_spaces.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @flow

const Color = require('./color');
const interpolateNumber = require('./interpolate').number;

type LABColor = {
l: number,
Expand Down Expand Up @@ -77,6 +78,15 @@ function labToRgb(labColor: LABColor): Color {
);
}

function interpolateLab(from: LABColor, to: LABColor, t: number) {
return {
l: interpolateNumber(from.l, to.l, t),
a: interpolateNumber(from.a, to.a, t),
b: interpolateNumber(from.b, to.b, t),
alpha: interpolateNumber(from.alpha, to.alpha, t)
};
}

// HCL
function rgbToHcl(rgbColor: Color): HCLColor {
const {l, a, b} = rgbToLab(rgbColor);
Expand All @@ -101,13 +111,29 @@ function hclToRgb(hclColor: HCLColor): Color {
});
}

function interpolateHue(a: number, b: number, t: number) {
const d = b - a;
return a + t * (d > 180 || d < -180 ? d - 360 * Math.round(d / 360) : d);
}

function interpolateHcl(from: HCLColor, to: HCLColor, t: number) {
return {
h: interpolateHue(from.h, to.h, t),
c: interpolateNumber(from.c, to.c, t),
l: interpolateNumber(from.l, to.l, t),
alpha: interpolateNumber(from.alpha, to.alpha, t)
};
}

module.exports = {
lab: {
forward: rgbToLab,
reverse: labToRgb
reverse: labToRgb,
interpolate: interpolateLab
},
hcl: {
forward: rgbToHcl,
reverse: hclToRgb
reverse: hclToRgb,
interpolate: interpolateHcl
}
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"version": 8,
"metadata": {
"test": {
"width": 64,
"height": 64
}
},
"zoom": 5,
"sources": {},
"layers": [
{
"id": "background",
"type": "background",
"paint": {
"background-color": {
"stops": [
[
0,
"rgb(118,0,118)"
],
[
10,
"rgb(255,155,0)"
]
],
"colorSpace": "hcl"
}
}
}
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"version": 8,
"metadata": {
"test": {
"width": 64,
"height": 64
}
},
"zoom": 5,
"sources": {},
"layers": [
{
"id": "background",
"type": "background",
"paint": {
"background-color": {
"stops": [
[
0,
"rgb(118,0,118)"
],
[
10,
"rgb(255,155,0)"
]
],
"colorSpace": "lab"
}
}
}
]
}
30 changes: 14 additions & 16 deletions test/unit/style-spec/function.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,40 +184,38 @@ test('exponential function', (t) => {
t.end();
});

t.test('lab colorspace', {skip: true}, (t) => {
t.test('lab colorspace', (t) => {
const f = createFunction({
type: 'exponential',
colorSpace: 'lab',
stops: [[1, [0, 0, 0, 1]], [10, [0, 1, 1, 1]]]
stops: [[1, 'rgba(0,0,0,1)'], [10, 'rgba(0,255,255,1)']]
}, {
type: 'color'
}).evaluate;

t.deepEqual(f({zoom: 0}), new Color(0, 0, 0, 1));
t.deepEqual(f({zoom: 5}).map((n) => {
return parseFloat(n.toFixed(3));
}), new Color(0, 0.444, 0.444, 1));
t.equalWithPrecision(f({zoom: 5}).r, 0, 1e-6);
t.equalWithPrecision(f({zoom: 5}).g, 0.444, 1e-3);
t.equalWithPrecision(f({zoom: 5}).b, 0.444, 1e-3);

t.end();
});

t.test('rgb colorspace', {skip: true}, (t) => {
t.test('rgb colorspace', (t) => {
const f = createFunction({
type: 'exponential',
colorSpace: 'rgb',
stops: [[0, [0, 0, 0, 1]], [10, [1, 1, 1, 1]]]
stops: [[0, 'rgba(0,0,0,1)'], [10, 'rgba(255,255,255,1)']]
}, {
type: 'color'
}).evaluate;

t.deepEqual(f({zoom: 5}).map((n) => {
return parseFloat(n.toFixed(3));
}), new Color(0.5, 0.5, 0.5, 1));
t.deepEqual(f({zoom: 5}), new Color(0.5, 0.5, 0.5, 1));

t.end();
});

t.test('unknown color spaces', {skip: true}, (t) => {
t.test('unknown color spaces', (t) => {
t.throws(() => {
createFunction({
type: 'exponential',
Expand All @@ -231,7 +229,7 @@ test('exponential function', (t) => {
t.end();
});

t.test('interpolation mutation avoidance', {skip: true}, (t) => {
t.test('interpolation mutation avoidance', (t) => {
const params = {
type: 'exponential',
colorSpace: 'lab',
Expand Down Expand Up @@ -289,7 +287,7 @@ test('exponential function', (t) => {
t.end();
});

t.test('property type mismatch, function default', {skip: true}, (t) => {
t.test('property type mismatch, function default', (t) => {
const f = createFunction({
property: 'foo',
type: 'exponential',
Expand All @@ -304,7 +302,7 @@ test('exponential function', (t) => {
t.end();
});

t.test('property type mismatch, spec default', {skip: true}, (t) => {
t.test('property type mismatch, spec default', (t) => {
const f = createFunction({
property: 'foo',
type: 'exponential',
Expand Down Expand Up @@ -818,7 +816,7 @@ test('identity function', (t) => {
t.end();
});

t.test('number function default', {skip: true}, (t) => {
t.test('number function default', (t) => {
const f = createFunction({
property: 'foo',
type: 'identity',
Expand All @@ -832,7 +830,7 @@ test('identity function', (t) => {
t.end();
});

t.test('number spec default', {skip: true}, (t) => {
t.test('number spec default', (t) => {
const f = createFunction({
property: 'foo',
type: 'identity'
Expand Down