Skip to content

Commit

Permalink
feat(Style): add icon.color and icon.opacity
Browse files Browse the repository at this point in the history
  • Loading branch information
ftoromanoff committed May 4, 2023
1 parent 0bf309b commit 4fd5dc4
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 11 deletions.
67 changes: 56 additions & 11 deletions src/Core/Style.js
Expand Up @@ -2,6 +2,8 @@ import { FEATURE_TYPES } from 'Core/Feature';
import Cache from 'Core/Scheduler/Cache';
import Fetcher from 'Provider/Fetcher';
import * as mapbox from '@mapbox/mapbox-gl-style-spec';
import { Color } from 'three';
import { deltaE } from 'Renderer/Color';

import itowns_stroke_single_before from './StyleChunk/itowns_stroke_single_before.css';

Expand Down Expand Up @@ -77,13 +79,37 @@ function readVectorProperty(property, options) {
}
}

function getImage(source, key) {
function getImage(source, value) {
const target = document.createElement('img');

if (typeof source == 'string') {
target.src = source;
} else if (source && source[key]) {
const sprite = source[key];
if (value) {
const color = new Color(value);
Fetcher.texture(source, { crossOrigin: 'anonymous' })
.then((texture) => {
const img = texture.image;
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
ctx.drawImage(img, 0, 0);
const imgd = ctx.getImageData(0, 0, img.naturalWidth, img.naturalHeight);
const pix = imgd.data;

const colorToChange = new Color('white');
for (var i = 0, n = pix.length; i < n; i += 4) {
const d = deltaE(pix.slice(i, i + 3), colorToChange) / 100;
pix[i] = (pix[i] * d + color.r * 255 * (1 - d));
pix[i + 1] = (pix[i + 1] * d + color.g * 255 * (1 - d));
pix[i + 2] = (pix[i + 2] * d + color.b * 255 * (1 - d));
}
ctx.putImageData(imgd, 0, 0);
target.src = canvas.toDataURL('image/png');
});
} else {
target.src = source;
}
} else if (source && source[value]) {
const sprite = source[value];
canvas.width = sprite.width;
canvas.height = sprite.height;
canvas.getContext('2d').drawImage(source.img, sprite.x, sprite.y, sprite.width, sprite.height, 0, 0, sprite.width, sprite.height);
Expand Down Expand Up @@ -261,13 +287,18 @@ function defineStyleProperty(style, category, name, value, defaultValue) {
* Default is `0`.
*
* @property {Object} icon - Defines the appearance of icons attached to label.
* @property {String} icon.source - The url of the icons' image file.
* @property {String} icon.key - The key of the icons' image in a vector tile data set.
* @property {string} icon.source - The url of the icons' image file.
* @property {string} icon.key - The key of the icons' image in a vector tile data set.
* @property {string} [icon.anchor='center'] - The anchor of the icon compared to the label position.
* Can be `left`, `bottom`, `right`, `center`, `top-left`, `top-right`, `bottom-left`
* or `bottom-right`.
* @property {number} icon.size - If the icon's image is passed with `icon.source` or
* `icon.key`, it size when displayed on screen is multiplied by `icon.size`. Default is `1`.
* @property {string|function} icon.color - The color of the icon. Can be any [valid
* color string](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value).
* It will change the color of the white pixels of the icon source image.
* @property {number|function} icon.opacity - The opacity of the icon. Can be between
* `0.0` and `1.0`. Default is `1.0`.
*
* @example
* const style = new itowns.Style({
Expand Down Expand Up @@ -371,6 +402,8 @@ class Style {
defineStyleProperty(this, 'icon', 'key', params.icon.key);
defineStyleProperty(this, 'icon', 'anchor', params.icon.anchor, 'center');
defineStyleProperty(this, 'icon', 'size', params.icon.size, 1);
defineStyleProperty(this, 'icon', 'color', params.icon.color);
defineStyleProperty(this, 'icon', 'opacity', params.icon.opacity, 1.0);
}

/**
Expand Down Expand Up @@ -456,8 +489,14 @@ class Style {
this.text.opacity = properties['label-opacity'];
this.text.size = properties['label-size'];

if (properties.icon) {
this.icon.source = properties.icon;
const icon = {
...(properties.icon !== undefined && { source: properties.icon }),
...(properties['icon-scale'] !== undefined && { size: properties['icon-scale'] }),
...(properties['icon-opacity'] !== undefined && { opacity: properties['icon-opacity'] }),
...(properties['icon-color'] !== undefined && { color: properties['icon-color'] }),
};
if (Object.keys(icon).length) {
this.icon = icon;
}
} else {
this.stroke.color = properties.stroke;
Expand Down Expand Up @@ -558,6 +597,9 @@ class Style {
if (key) {
this.icon.key = key;
this.icon.size = readVectorProperty(layer.layout['icon-size']) || 1;
const { color, opacity } = rgba2rgb(readVectorProperty(layer.paint['icon-color'], { type: 'color' }));
this.icon.color = color;
this.icon.opacity = readVectorProperty(layer.paint['icon-opacity']) || (opacity !== undefined && opacity);
}
}
return this;
Expand Down Expand Up @@ -599,16 +641,17 @@ class Style {
const image = this.icon.source;
const size = this.icon.size;
const key = this.icon.key;
const color = this.icon.color;

let icon = cacheStyle.get(image || key, size);
let icon = cacheStyle.get(image || key, size, color);

if (!icon) {
if (key && sprites) {
icon = getImage(sprites, key);
} else {
icon = getImage(image);
icon = getImage(image, color);
}
cacheStyle.set(icon, image || key, size);
cacheStyle.set(icon, image || key, size, color);
}

const addIcon = () => {
Expand All @@ -618,6 +661,8 @@ class Style {

cIcon.width = icon.width * this.icon.size;
cIcon.height = icon.height * this.icon.size;
cIcon.style.color = this.icon.color;
cIcon.style.opacity = this.icon.opacity;
cIcon.style.position = 'absolute';
cIcon.style.top = '0';
cIcon.style.left = '0';
Expand Down
69 changes: 69 additions & 0 deletions src/Renderer/Color.js
@@ -0,0 +1,69 @@
export function lab2rgb(lab) {
var y = (lab[0] + 16) / 116;
var x = lab[1] / 500 + y;
var z = y - lab[2] / 200;
var r; var g; var
b;

x = 0.95047 * ((x * x * x > 0.008856) ? x * x * x : (x - 16 / 116) / 7.787);
y = 1.00000 * ((y * y * y > 0.008856) ? y * y * y : (y - 16 / 116) / 7.787);
z = 1.08883 * ((z * z * z > 0.008856) ? z * z * z : (z - 16 / 116) / 7.787);

r = x * 3.2406 + y * -1.5372 + z * -0.4986;
g = x * -0.9689 + y * 1.8758 + z * 0.0415;
b = x * 0.0557 + y * -0.2040 + z * 1.0570;

r = (r > 0.0031308) ? (1.055 * r ** (1 / 2.4) - 0.055) : 12.92 * r;
g = (g > 0.0031308) ? (1.055 * g ** (1 / 2.4) - 0.055) : 12.92 * g;
b = (b > 0.0031308) ? (1.055 * b ** (1 / 2.4) - 0.055) : 12.92 * b;

return [Math.max(0, Math.min(1, r)) * 255,
Math.max(0, Math.min(1, g)) * 255,
Math.max(0, Math.min(1, b)) * 255];
}


export function rgb2lab(rgb) {
var r = rgb.r || rgb[0] / 255;
var g = rgb.g || rgb[1] / 255;
var b = rgb.b || rgb[2] / 255;
var x; var y; var
z;

r = (r > 0.04045) ? ((r + 0.055) / 1.055) ** 2.4 : r / 12.92;
g = (g > 0.04045) ? ((g + 0.055) / 1.055) ** 2.4 : g / 12.92;
b = (b > 0.04045) ? ((b + 0.055) / 1.055) ** 2.4 : b / 12.92;

x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000;
z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;

x = (x > 0.008856) ? x ** (1 / 3) : (7.787 * x) + 16 / 116;
y = (y > 0.008856) ? y ** (1 / 3) : (7.787 * y) + 16 / 116;
z = (z > 0.008856) ? z ** (1 / 3) : (7.787 * z) + 16 / 116;

return [(116 * y) - 16, 500 * (x - y), 200 * (y - z)];
}

// calculate the perceptual distance between colors in CIELAB
// https://github.com/THEjoezack/ColorMine/blob/master/ColorMine/ColorSpaces/Comparisons/Cie94Comparison.cs
export function deltaE(rgbA, rgbB) {
const labA = rgb2lab(rgbA);
const labB = rgb2lab(rgbB);
var deltaL = labA[0] - labB[0];
var deltaA = labA[1] - labB[1];
var deltaB = labA[2] - labB[2];
var c1 = Math.sqrt(labA[1] * labA[1] + labA[2] * labA[2]);
var c2 = Math.sqrt(labB[1] * labB[1] + labB[2] * labB[2]);
var deltaC = c1 - c2;
var deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC;
deltaH = deltaH < 0 ? 0 : Math.sqrt(deltaH);
var sc = 1.0 + 0.045 * c1;
var sh = 1.0 + 0.015 * c1;
var deltaLKlsl = deltaL / (1.0);
var deltaCkcsc = deltaC / (sc);
var deltaHkhsh = deltaH / (sh);
var i = deltaLKlsl * deltaLKlsl + deltaCkcsc * deltaCkcsc + deltaHkhsh * deltaHkhsh;
return i < 0 ? 0 : Math.sqrt(i);
}

0 comments on commit 4fd5dc4

Please sign in to comment.