Skip to content
This repository has been archived by the owner on Mar 8, 2023. It is now read-only.

Commit

Permalink
HARP-7517: Partial support for rgba[..] and "rgba(..)" expressions in…
Browse files Browse the repository at this point in the history
… theme.

Gives compatibility for themes using 4 channel colors in styling with
attributes expressions like:
- color: rgba[255, 255, 255, 0.5]
- backgroundColor: "rgba(255, 255, 255, 0.5)"

Although those styling attributes will be parsed and read, the alpha
channel component will be silently ignored. Full support for alpha channel
need to be implemented.

TODO:
- support RGBA/ARGB colors internally in engine,
- alpha channel mapping to THREE.js opacity and transparent material
properties,
- support four channel color interpolation,
- more unit tests.

Signed-off-by: Krystian Kostecki <ext-krystian.kostecki@here.com>
  • Loading branch information
kkostecki committed Nov 6, 2019
1 parent daf0c05 commit 16f78f3
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 28 deletions.
7 changes: 5 additions & 2 deletions @here/harp-datasource-protocol/lib/InterpolatedProperty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {
StringEncodedNumeralFormats,
StringEncodedNumeralType,
StringEncodedPixels,
StringEncodedRGB
StringEncodedRGB,
StringEncodedRGBA
} from "./StringEncodedNumeral";

const logger = LoggerManager.instance.create("InterpolatedProperty");
Expand Down Expand Up @@ -109,6 +110,7 @@ export function getPropertyValue<T>(
return matchedFormat.decoder(property)[0] * pixelToMeters;
case StringEncodedNumeralType.Hex:
case StringEncodedNumeralType.RGB:
case StringEncodedNumeralType.RGBA:
case StringEncodedNumeralType.HSL:
const hslValues = matchedFormat.decoder(property);
return tmpColor.setHSL(hslValues[0], hslValues[1], hslValues[2]).getHex();
Expand All @@ -123,6 +125,7 @@ export function getPropertyValue<T>(
return getInterpolatedLength(property, level, pixelToMeters);
case StringEncodedNumeralType.Hex:
case StringEncodedNumeralType.RGB:
case StringEncodedNumeralType.RGBA:
case StringEncodedNumeralType.HSL:
return getInterpolatedColor(property, level);
}
Expand Down Expand Up @@ -275,7 +278,7 @@ function removeDuplicatePropertyValues<T>(p: InterpolatedPropertyDefinition<T>)
}
}

const colorFormats = [StringEncodedHSL, StringEncodedHex, StringEncodedRGB];
const colorFormats = [StringEncodedHSL, StringEncodedHex, StringEncodedRGB, StringEncodedRGBA];
const worldSizeFormats = [StringEncodedMeters, StringEncodedPixels];

function procesStringEnocodedNumeralInterpolatedProperty(
Expand Down
21 changes: 21 additions & 0 deletions @here/harp-datasource-protocol/lib/StringEncodedNumeral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export enum StringEncodedNumeralType {
Pixels,
Hex,
RGB,
RGBA,
HSL
}

Expand Down Expand Up @@ -73,6 +74,25 @@ export const StringEncodedRGB: StringEncodedNumeralFormat = {
return [tmpHSL.h, tmpHSL.s, tmpHSL.l];
}
};
export const StringEncodedRGBA: StringEncodedNumeralFormat = {
type: StringEncodedNumeralType.RGBA,
size: 3,
// tslint:disable-next-line:max-line-length
regExp: /rgba\((?:([0-9]{1,2}|1[0-9]{1,2}|2[0-4][0-9]|25[0-5]), ?)(?:([0-9]{1,2}|1[0-9]{1,2}|2[0-4][0-9]|25[0-5]), ?)(?:([0-9]{1,2}|1[0-9]{1,2}|2[0-4][0-9]|25[0-5]), ?)(?:(0(?:\.[0-9]+)?|1(?:\.0+)?))\)/,
decoder: (encodedValue: string) => {
const channels = StringEncodedRGBA.regExp.exec(encodedValue)!;
// For now we simply ignore alpha channel value.
// TODO: To be resolved with HARP-7517
tmpColor
.setRGB(
parseInt(channels[1], 10) / 255,
parseInt(channels[2], 10) / 255,
parseInt(channels[3], 10) / 255
)
.getHSL(tmpHSL);
return [tmpHSL.h, tmpHSL.s, tmpHSL.l];
}
};
export const StringEncodedHSL: StringEncodedNumeralFormat = {
type: StringEncodedNumeralType.HSL,
size: 3,
Expand All @@ -97,5 +117,6 @@ export const StringEncodedNumeralFormats: StringEncodedNumeralFormat[] = [
StringEncodedPixels,
StringEncodedHex,
StringEncodedRGB,
StringEncodedRGBA,
StringEncodedHSL
];
64 changes: 51 additions & 13 deletions @here/harp-datasource-protocol/lib/operators/ColorOperators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,46 @@
import * as THREE from "three";

import { CallExpr } from "../Expr";

import { ExprEvaluatorContext, OperatorDescriptorMap } from "../ExprEvaluator";

const tmpColor = new THREE.Color();
const operators = {
rgba: {
call: (context: ExprEvaluatorContext, call: CallExpr) => {
const r = context.evaluate(call.args[0]);
const g = context.evaluate(call.args[1]);
const b = context.evaluate(call.args[2]);
const a = context.evaluate(call.args[3]);
if (
typeof r === "number" &&
typeof g === "number" &&
typeof b === "number" &&
typeof a === "number" &&
r >= 0 &&
g >= 0 &&
b >= 0 &&
a >= 0 &&
a <= 1
) {
return rgbaToString(r, g, b, a);
}
throw new Error(`unknown color 'rgba(${r},${g},${b},${a})'`);
}
},
rgb: {
call: (context: ExprEvaluatorContext, call: CallExpr) => {
const r = context.evaluate(call.args[0]);
const g = context.evaluate(call.args[1]);
const b = context.evaluate(call.args[2]);
if (typeof r === "number" && typeof g === "number" && typeof b === "number") {
return (
"#" +
tmpColor
.setRGB(
THREE.Math.clamp(r, 0, 255) / 255,
THREE.Math.clamp(g, 0, 255) / 255,
THREE.Math.clamp(b, 0, 255) / 255
)
.getHexString()
);
if (
typeof r === "number" &&
typeof g === "number" &&
typeof b === "number" &&
r >= 0 &&
g >= 0 &&
b >= 0
) {
return rgbaToString(r, g, b);
}
throw new Error(`unknown color 'rgb(${r},${g},${b})'`);
}
Expand All @@ -45,12 +64,31 @@ const operators = {
s >= 0 &&
l >= 0
) {
return `hsl(${h},${Math.round(s)}%,${Math.round(l)}%)`;
return hslToString(h, s, l);
}
throw new Error(`unknown color 'hsl(${h},${s}%,${l}%)'`);
}
}
};

function rgbaToString(r: number, g: number, b: number, a?: number): string {
// For now we simply ignore alpha component from rgba(...) expressions.
// TODO: To be resolved with HARP-7517.
return (
"#" +
tmpColor
.setRGB(
THREE.Math.clamp(r, 0, 255) / 255,
THREE.Math.clamp(g, 0, 255) / 255,
THREE.Math.clamp(b, 0, 255) / 255
)
.getHexString()
);
}

function hslToString(h: number, s: number, l: number): string {
return `hsl(${h},${Math.round(s)}%,${Math.round(l)}%)`;
}

export const ColorOperators: OperatorDescriptorMap = operators;
export type ColorOperatorNames = keyof typeof operators;
69 changes: 69 additions & 0 deletions @here/harp-datasource-protocol/test/ExprEvaluatorTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,76 @@ describe("ExprEvaluator", function() {
new THREE.Color("rgb(127, 127, 127)").getHexString()
);

assert.throw(() => evaluate(["rgb", -20, 40, 50]), "unknown color 'rgb(-20,40,50)'");
assert.throw(() => evaluate(["rgb", "a", 40, 50]), "unknown color 'rgb(a,40,50)'");
});
});

describe("Operator 'rgba'", function() {
it("call", function() {
assert.strictEqual(
new THREE.Color(evaluate(["rgba", 0, 0, 0, 1.0]) as string).getHexString(),
new THREE.Color("rgb(0, 0, 0)").getHexString()
);

assert.strictEqual(
new THREE.Color(evaluate(["rgba", 255, 0, 0, 1]) as string).getHexString(),
new THREE.Color("rgb(255, 0, 0)").getHexString()
);

assert.strictEqual(
new THREE.Color(evaluate(["rgba", 255, 255, 0, 1]) as string).getHexString(),
new THREE.Color("rgb(255, 255, 0)").getHexString()
);

assert.strictEqual(
new THREE.Color(evaluate(["rgba", 255, 255, 255, 1]) as string).getHexString(),
new THREE.Color("rgb(255, 255, 255)").getHexString()
);

assert.strictEqual(
new THREE.Color(evaluate(["rgba", 127, 127, 127, 1]) as string).getHexString(),
new THREE.Color("rgb(127, 127, 127)").getHexString()
);

assert.strictEqual(
new THREE.Color(evaluate(["rgba", 500, 500, 500, 1]) as string).getHexString(),
new THREE.Color("rgb(500, 500, 500)").getHexString()
);

// Still equal - ignores alpha.
// Some assumptions will change after fully implementing HARP-7517
assert.strictEqual(
evaluate(["rgba", 255, 0, 0, 0.5]) as string,
evaluate(["rgba", 255, 0, 0, 0.6]) as string
);

assert.strictEqual(
new THREE.Color(evaluate(["rgba", 255, 255, 255, 0.5]) as string).getHexString(),
new THREE.Color("rgb(255, 255, 255)").getHexString()
);

// Bad statements.
assert.throw(
() => evaluate(["rgba", -20, 40, 50, 1]),
"unknown color 'rgb(-20,40,50,1)'"
);
assert.throw(
() => evaluate(["rgba", 20, 40, 50, -1]),
"unknown color 'rgb(20,40,50,-1)'"
);
assert.throw(
() => evaluate(["rgba", 20, 40, 50, "a"]),
"unknown color 'rgb(20,40,50,a)'"
);
assert.throw(
() => evaluate(["rgba", 20, 40, 50, 1.1]),
"unknown color 'rgb(20,40,50,1.1)'"
);
assert.throw(
() => evaluate(["rgba", 20, 40, 50, 2.0]),
"unknown color 'rgb(20,40,50,2.0)'"
);
});
});
});
41 changes: 28 additions & 13 deletions @here/harp-mapview/lib/DecodedTileHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,7 @@ export function createMaterial(
// skip reserved property names
return;
}
const m = material as any;
if (m[prop] instanceof THREE.Color) {
m[prop].set(params[prop]);
} else {
m[prop] = params[prop];
}
applyTechniquePropertyToMaterial(material, prop, params[prop]);
});
} else {
applyTechniqueToMaterial(technique, material, options.level, options.skipExtraProps);
Expand Down Expand Up @@ -455,22 +450,42 @@ export function applyTechniqueToMaterial(
}
const prop = propertyName as keyof (typeof technique);
const m = material as any;
let value = technique[prop];
if (typeof m[prop] === "undefined") {
return;
}
let value = technique[prop];
if (level !== undefined && isInterpolatedProperty(value)) {
value = getPropertyValue(value, level);
}
if (m[prop] instanceof THREE.Color) {
m[prop].set(value);
m[prop] = m[prop]; // Trigger setter
} else {
m[prop] = value;
}
applyTechniquePropertyToMaterial(material, prop, value);
});
}

/**
* Apply single and generic technique property to corresponding material parameter.
*
* @note Special handling for material parameters of `THREE.Color` type is provided thus it
* does not provide constructor that would take [[string]] or [[number]] values.
*
* @param material target material
* @param prop material parameter name (or index)
* @param value corresponding technique property value which is applied.
*/
function applyTechniquePropertyToMaterial(
material: THREE.Material,
prop: string | number,
value: any
) {
const m = material as any;
if (m[prop] instanceof THREE.Color) {
m[prop].set(value);
// Trigger setter notifying change
m[prop] = m[prop];
} else {
m[prop] = value;
}
}

function getTextureBuffer(
buffer: ArrayBuffer,
textureDataType: THREE.TextureDataType | undefined
Expand Down

0 comments on commit 16f78f3

Please sign in to comment.