Adding hex value for colors and brightness to the light support (#9)
PR #9 [PBI #28283] * Methods to allow colors input in hexadecimal value * Adding brightness support * Adding getitem() method to the mock CPX API * Minor formatting changes * Adressing PR's comments about naming and convention * Correcting Python naming convention and re-writing function adapted from an online source
This commit is contained in:
Родитель
bcd5b6dbb1
Коммит
4d457bbfc0
|
@ -2,7 +2,7 @@ from .pixel import Pixel
|
|||
|
||||
class Express:
|
||||
def __init__(self):
|
||||
# Our actual state
|
||||
# State in the Python process
|
||||
self.state = {
|
||||
'pixels': [
|
||||
(0, 0, 0),
|
||||
|
@ -16,6 +16,7 @@ class Express:
|
|||
(0, 0, 0),
|
||||
(0, 0, 0)
|
||||
],
|
||||
'brightness': 1.0,
|
||||
'button_a': False,
|
||||
'button_b': False,
|
||||
}
|
||||
|
|
|
@ -11,20 +11,50 @@ class Pixel:
|
|||
sys.stdout.flush()
|
||||
|
||||
def __setitem__(self, index, val):
|
||||
self._state['pixels'][index] = self.extractPixelValue(val)
|
||||
self._state['pixels'][index] = self.extract_pixel_value(val)
|
||||
|
||||
def extractPixelValue(self, val):
|
||||
def __getitem__(self, index):
|
||||
return self._state['pixels'][index]
|
||||
|
||||
def extract_pixel_value(self, val):
|
||||
# Convert HEX to RGB
|
||||
if type(val) is not tuple:
|
||||
val = self.hex_to_rgb(val)
|
||||
# Check it's a valid tuple
|
||||
if len(val) != 3:
|
||||
raise ValueError('The pixel value should be a tuple with 3 values.')
|
||||
raise ValueError('The pixel value should be a tuple with 3 values between 0 and 255 or an hexadecimal color between #000000 and #FFFFFF.')
|
||||
# Convert to int
|
||||
val = tuple(map(int, val))
|
||||
# Prevent negative values
|
||||
if any(pix < 0 or pix > 255 for pix in val):
|
||||
raise ValueError('The pixel value should be in range 0, 255.')
|
||||
raise ValueError('The pixel value should between 0 and 255 or an hexadecimal color between #000000 and #FFFFFF.')
|
||||
|
||||
return val
|
||||
|
||||
def fill(self, val):
|
||||
for index in range(len(self._state['pixels'])):
|
||||
self._state['pixels'][index] = self.extractPixelValue(val)
|
||||
self._state['pixels'][index] = self.extract_pixel_value(val)
|
||||
|
||||
def hex_to_rgb(self, hexValue):
|
||||
hexValue = hexValue.lstrip('#')
|
||||
if len(hexValue) != 6:
|
||||
raise ValueError('The pixel hexadicimal color value should be in range #000000 and #FFFFFF.')
|
||||
# Convert the string hex to rgb tuple
|
||||
hexToRgbValue = []
|
||||
for i in range(0, len(hexValue), 2):
|
||||
hexColor = hexValue[i:i+2]
|
||||
hexToRgbValue.append(int(hexColor, 16))
|
||||
return tuple(hexToRgbValue)
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
return self._state['brightness']
|
||||
|
||||
@brightness.setter
|
||||
def brightness(self, brightness):
|
||||
if not self.valid_brightness(brightness):
|
||||
raise ValueError('The brightness value should be a number between 0 and 1.')
|
||||
self._state['brightness'] = brightness
|
||||
|
||||
def valid_brightness(self, brightness):
|
||||
return (type(brightness) is float or type(brightness) is int) and (brightness >= 0 and brightness <= 1)
|
||||
|
|
|
@ -3,6 +3,7 @@ import Cpx from "./cpx/Cpx";
|
|||
|
||||
interface IState {
|
||||
pixels: Array<Array<number>>;
|
||||
brightness: number;
|
||||
button_a: any;
|
||||
button_b: any;
|
||||
}
|
||||
|
@ -35,6 +36,7 @@ class Simulator extends React.Component<any, IState> {
|
|||
[0, 0, 0],
|
||||
[0, 0, 0]
|
||||
],
|
||||
brightness: 1.0,
|
||||
button_a: false,
|
||||
button_b: false
|
||||
};
|
||||
|
@ -59,7 +61,7 @@ class Simulator extends React.Component<any, IState> {
|
|||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Cpx pixels={this.state.pixels} onClick={this.sendClickInfo} />
|
||||
<Cpx pixels={this.state.pixels} brightness={this.state.brightness} onClick={this.sendClickInfo} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -78,6 +80,7 @@ class Simulator extends React.Component<any, IState> {
|
|||
[0, 0, 0],
|
||||
[0, 0, 0]
|
||||
],
|
||||
brightness: 1.0,
|
||||
button_a: false,
|
||||
button_b: false
|
||||
});
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import * as React from "react";
|
||||
import CPX_SVG from "./Cpx_svg";
|
||||
import * as SvgStyle from "./Cpx_svg_style";
|
||||
|
@ -7,6 +6,7 @@ import svg from "./Svg_utils";
|
|||
|
||||
interface IProps {
|
||||
pixels: Array<Array<number>>;
|
||||
brightness: number;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
|
@ -14,21 +14,21 @@ interface IProps {
|
|||
/** Functional Component render */
|
||||
const Cpx: React.FC<IProps> = props => {
|
||||
|
||||
let svgElement = window.document.getElementById('svg');
|
||||
let svgElement = window.document.getElementById("cpx_svg");
|
||||
|
||||
if (svgElement)
|
||||
initSvgStyle(svgElement);
|
||||
if (svgElement) {
|
||||
initSvgStyle(svgElement, props.brightness);
|
||||
// Update Neopixels state
|
||||
updateNeopixels(props);
|
||||
}
|
||||
|
||||
// Update LEDs state
|
||||
updateLEDs(props.pixels);
|
||||
|
||||
return (
|
||||
CPX_SVG
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
const initSvgStyle = (svgElement: HTMLElement): void => {
|
||||
const initSvgStyle = (svgElement: HTMLElement, brightness: number): void => {
|
||||
let style: SVGStyleElement = svg.child(svgElement, "style", {}) as SVGStyleElement;
|
||||
style.textContent = SvgStyle.SVG_STYLE;
|
||||
|
||||
|
@ -39,7 +39,7 @@ const initSvgStyle = (svgElement: HTMLElement): void => {
|
|||
svg.child(glow, "feGaussianBlur", { stdDeviation: "5", result: "glow" });
|
||||
let merge = svg.child(glow, "feMerge", {});
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
svg.child(merge, "feMergeNode", { in: "glow" })
|
||||
svg.child(merge, "feMergeNode", { in: "glow" });
|
||||
}
|
||||
|
||||
let neopixelglow = svg.child(defs, "filter", { id: "neopixelglow", x: "-300%", y: "-300%", width: "600%", height: "600%" });
|
||||
|
@ -48,34 +48,58 @@ const initSvgStyle = (svgElement: HTMLElement): void => {
|
|||
svg.child(neopixelmerge, "feMergeNode", { in: "coloredBlur" });
|
||||
svg.child(neopixelmerge, "feMergeNode", { in: "coloredBlur" });
|
||||
svg.child(neopixelmerge, "feMergeNode", { in: "SourceGraphic" });
|
||||
|
||||
// Brightness
|
||||
let neopixelfeComponentTransfer = svg.child(neopixelglow, "feComponentTransfer", {});
|
||||
svg.child(neopixelfeComponentTransfer, "feFuncR", {id:"brightnessFilterR", type: "linear", slope: brightness});
|
||||
svg.child(neopixelfeComponentTransfer, "feFuncG", {id:"brightnessFilterG", type: "linear", slope: brightness});
|
||||
svg.child(neopixelfeComponentTransfer, "feFuncB", {id:"brightnessFilterB", type: "linear", slope: brightness});
|
||||
}
|
||||
|
||||
|
||||
const isLightOn = (pixValue: Array<number>): boolean => {
|
||||
return ! pixValue.every((val) => { return (val == 0) });
|
||||
}
|
||||
|
||||
|
||||
const setLED = (pixValue: Array<number>, led: HTMLElement): void => {
|
||||
if (isLightOn(pixValue)) {
|
||||
led.style.fill = "rgb(" + pixValue.toString() + ")";
|
||||
led.style.filter = `url(#neopixelglow)`;
|
||||
}
|
||||
else {
|
||||
led.style.fill = "#c8c8c8";
|
||||
led.style.filter = `none`;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const updateLEDs = (pixelsState: Array<Array<number>>): void => {
|
||||
for (let i = 0; i < 10 ; i ++) {
|
||||
let led = window.document.getElementById(`LED${i}`);
|
||||
const updateNeopixels = (props: IProps): void => {
|
||||
for (let i = 0; i < props.pixels.length; i ++) {
|
||||
let led = window.document.getElementById(`NEOPIXEL_${i}`);
|
||||
if (led) {
|
||||
setLED(pixelsState[i], led);
|
||||
setNeopixel(led, props.pixels[i], props.brightness);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const setNeopixel = (led: HTMLElement, pixValue: Array<number>, brightness: number): void => {
|
||||
if (isLightOn(pixValue) && brightness > 0) {
|
||||
// Neopixels style (Adapted from : https://github.com/microsoft/pxt-adafruit/blob/master/sim/visuals/board.ts)
|
||||
changeBrightness("brightnessFilterR", brightness);
|
||||
changeBrightness("brightnessFilterG", brightness);
|
||||
changeBrightness("brightnessFilterB", brightness);
|
||||
|
||||
let [hue, sat, lum] = SvgStyle.rgbToHsl([pixValue[0], pixValue[1], pixValue[2]]);
|
||||
let innerLum = Math.max(lum * SvgStyle.INTENSITY_FACTOR, SvgStyle.MIN_INNER_LUM);
|
||||
lum = lum * 90 / 100 + 10; // at least 10% luminosity for the stroke
|
||||
|
||||
led.style.filter = `url(#neopixelglow)`;
|
||||
led.style.fill = `hsl(${hue}, ${sat}%, ${innerLum}%)`;
|
||||
led.style.stroke = `hsl(${hue}, ${sat}%, ${Math.min(lum * 3, SvgStyle.MAX_STROKE_LUM)}%)`
|
||||
led.style.strokeWidth = `1.5`;
|
||||
} else {
|
||||
led.style.fill = SvgStyle.OFF_COLOR;
|
||||
led.style.filter = `none`;
|
||||
led.style.stroke = `none`;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const isLightOn = (pixValue: Array<number>): boolean => {
|
||||
return ! pixValue.every((val) => { return (val == 0) });
|
||||
}
|
||||
|
||||
|
||||
const changeBrightness = (filterID: string, brightness: number): void => {
|
||||
let brightnessFilter: HTMLElement | null = window.document.getElementById(filterID);
|
||||
if (brightnessFilter)
|
||||
brightnessFilter.setAttribute("slope", brightness.toString());
|
||||
}
|
||||
|
||||
|
||||
export default Cpx;
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -4,6 +4,45 @@
|
|||
export const MB_WIDTH = 180.09375;
|
||||
export const MB_HEIGHT = 179.22874;
|
||||
|
||||
export const OFF_COLOR = "#c8c8c8";
|
||||
export const MAX_STROKE_LUM = 75;
|
||||
export const MIN_INNER_LUM = 85;
|
||||
export const INTENSITY_FACTOR = 1.3;
|
||||
|
||||
// Adapted from : https://github.com/microsoft/pxt/blob/master/pxtsim/simlib.ts
|
||||
export function rgbToHsl(rgb: [number, number, number]): [number, number, number] {
|
||||
let [r, g, b] = rgb;
|
||||
let [r$, g$, b$] = [r / 255, g / 255, b / 255];
|
||||
let cMin = Math.min(r$, g$, b$);
|
||||
let cMax = Math.max(r$, g$, b$);
|
||||
let cDelta = cMax - cMin;
|
||||
let h: number = 0, s: number, l: number;
|
||||
let maxAndMin = cMax + cMin;
|
||||
|
||||
// Luminosity
|
||||
l = (maxAndMin / 2) * 100
|
||||
|
||||
if (cDelta === 0) {
|
||||
s = 0; h = 0;
|
||||
} else {
|
||||
// Hue
|
||||
if (cMax === r$)
|
||||
h = 60 * (((g$ - b$) / cDelta) % 6);
|
||||
else if (cMax === g$)
|
||||
h = 60 * (((b$ - r$) / cDelta) + 2);
|
||||
else if (cMax === b$)
|
||||
h = 60 * (((r$ - g$) / cDelta) + 4);
|
||||
|
||||
// Saturation
|
||||
if (l > 50)
|
||||
s = 100 * (cDelta / (2 - maxAndMin));
|
||||
else
|
||||
s = 100 * (cDelta / maxAndMin);
|
||||
}
|
||||
|
||||
return [Math.floor(h), Math.floor(s), Math.floor(l)];
|
||||
}
|
||||
|
||||
export const SVG_STYLE = `
|
||||
svg.sim {
|
||||
box-sizing: border-box;
|
||||
|
|
Загрузка…
Ссылка в новой задаче