Skip to content

Commit

Permalink
fix(#61): changing temp wheel based on light props
Browse files Browse the repository at this point in the history
  • Loading branch information
Gh61 committed Sep 28, 2023
1 parent 0800758 commit 0ca4a0c
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 29 deletions.
72 changes: 48 additions & 24 deletions src/controls/color-temp-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class HueColorWheelCache {
// version 2 - revised function to distribute kelvin values across the temp wheel
private static readonly version = 2;

public static saveWheel(mode: HueColorTempPickerMode, radius: number, canvas: HTMLCanvasElement) {
const key = this.createKey(mode, radius);
public static saveWheel(mode: HueColorTempPickerMode, radius: number, tempMin: number, tempMax: number, canvas: HTMLCanvasElement) {
const key = this.createKey(mode, radius, tempMin, tempMax);
const dataUrl = canvas.toDataURL(); // we're using dataUrl, because in raw format, the image exceeds localStorage size limit
try {
localStorage.setItem(key, dataUrl);
Expand All @@ -30,8 +30,8 @@ class HueColorWheelCache {
}
}

public static tryGetWheel(mode: HueColorTempPickerMode, radius: number) {
const key = this.createKey(mode, radius);
public static tryGetWheel(mode: HueColorTempPickerMode, radius: number, tempMin: number, tempMax: number) {
const key = this.createKey(mode, radius, tempMin, tempMax);
try {
const dataUrl = localStorage.getItem(key) || null;
if (dataUrl) {
Expand All @@ -50,8 +50,13 @@ class HueColorWheelCache {
};
}

private static createKey(mode: HueColorTempPickerMode, radius: number) {
return `HueColorWheelCache_${mode}${radius}x${radius}v${this.version}`;
private static createKey(mode: HueColorTempPickerMode, radius: number, tempMin: number, tempMax: number) {
let modeString = mode;
if (mode == 'temp') {
modeString += `(${tempMin}-${tempMax})`;
}

return `HueColorWheelCache_${modeString}${radius}x${radius}v${this.version}`;
}
}

Expand Down Expand Up @@ -85,18 +90,35 @@ export class HueColorTempPicker extends LitElement {
@property()
public mode: HueColorTempPickerMode = 'color';

@property()
public tempMin = 2000;
/**
* Will change min and max temp in kelvins.
* Forcing the picker to re-render the temp wheel.
*/
public setTempRange(minKelvin: number, maxKelvin: number): void {
let changed = false;
if (minKelvin != this._tempMin) {
this._tempMin = minKelvin;
changed = true;
}
if (maxKelvin != this._tempMax) {
this._tempMax = maxKelvin;
changed = true;
}

@property()
public tempMax = 6500;
if (changed && this._isRendered && this.mode == 'temp') {
this.drawWheel();
}
}

private onResize(): void {
this._markers.forEach(m => m.refresh());
}

// #region Rendering

private _tempMin = 2000; // default hue min
private _tempMax = 6535; // default hue max
private _isRendered = false;
private _canvas: HTMLDivElement;
private _backgroundLayer: HTMLCanvasElement;
private _interactionLayer: SVGElement;
Expand All @@ -107,6 +129,8 @@ export class HueColorTempPicker extends LitElement {

this.setupLayers();
this.drawWheel();

this._isRendered = true;
}

protected override updated(_changedProperties: PropertyValues<HueColorTempPicker>): void {
Expand Down Expand Up @@ -149,8 +173,7 @@ export class HueColorTempPicker extends LitElement {

const radius = HueColorTempPicker.renderWidthHeight / 2;

let image: ImageData;
const cacheItem = HueColorWheelCache.tryGetWheel(this.mode, radius);
const cacheItem = HueColorWheelCache.tryGetWheel(this.mode, radius, this._tempMin, this._tempMax);
if (cacheItem.success) {
// we have dataUrl, we need to parse them through Image element, then render them to canvas
const img = new Image();
Expand All @@ -160,7 +183,7 @@ export class HueColorTempPicker extends LitElement {
img.src = cacheItem.dataUrl!;

} else {
image = ctx.createImageData(2 * radius, 2 * radius);
const image = ctx.createImageData(2 * radius, 2 * radius);
const data = image.data;

for (let x = -radius; x < radius; x++) {
Expand All @@ -182,8 +205,9 @@ export class HueColorTempPicker extends LitElement {

ctx.putImageData(image, 0, 0);

HueColorWheelCache.saveWheel(this.mode, radius, this._backgroundLayer);
HueColorWheelCache.saveWheel(this.mode, radius, this._tempMin, this._tempMax, this._backgroundLayer);
}
console.log(`Drew ${this.mode} wheel`);
}

//#region Marker methods
Expand Down Expand Up @@ -276,7 +300,7 @@ export class HueColorTempPicker extends LitElement {
const [index, , adjustedY, rowLength] = HueColorTempPicker.computeIndex(x, y, radius);

const n = adjustedY / rowLength;
const kelvin = Math.round(HueColorTempPicker.utils.hueCurveScale(n, this.tempMin, this.tempMax));
const kelvin = Math.round(HueColorTempPicker.utils.hueCurveScale(n, this._tempMin, this._tempMax));

const color = Color.hueTempToRgb(kelvin);

Expand Down Expand Up @@ -305,13 +329,13 @@ export class HueColorTempPicker extends LitElement {
* @param currentCoordinates Actual coordinates on wheel. (May be used for setting the marker close to it.)
*/
public getCoordinatesAndTemp(kelvin: number, radius: number, currentCoordinates?: Point) {
if (kelvin < this.tempMin)
kelvin = this.tempMin;
else if (kelvin > this.tempMax)
kelvin = this.tempMax;
if (kelvin < this._tempMin)
kelvin = this._tempMin;
else if (kelvin > this._tempMax)
kelvin = this._tempMax;

const rowLength = 2 * radius;
const n = HueColorTempPicker.utils.inverseHueCurveScale(kelvin, this.tempMin, this.tempMax);
const n = HueColorTempPicker.utils.inverseHueCurveScale(kelvin, this._tempMin, this._tempMax);
const adjustedY = n * rowLength;
let y = adjustedY - radius;

Expand Down Expand Up @@ -403,20 +427,20 @@ export class HueColorTempPicker extends LitElement {
let low = 0;
let high = 1;
let t = 0.5; // Initial guess for t

// we are using binary search - this function is not used so often, the performance should be enough
while (high - low > epsilon) {
const midValue = this.hueCurveScale(t, min, max);

if (midValue < targetValue) {
low = t;
} else {
high = t;
}

t = (low + high) / 2;
}

return t;
},

Expand Down
14 changes: 11 additions & 3 deletions src/controls/light-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,20 @@ export class HueLightDetail extends IdLitElement {
return;
}

const lightFeatures = this.lightContainer.features;

this._colorMarker.icon = this.lightContainer.getIcon() || IconHelper.getIcon(1);
this._modeSelector.showColor = this.lightContainer.features.color;
this._modeSelector.showTemp = this.lightContainer.features.colorTemp;
this._modeSelector.showColor = lightFeatures.color;
this._modeSelector.showTemp = lightFeatures.colorTemp;
if (lightFeatures.colorTemp &&
lightFeatures.colorTempMinKelvin &&
lightFeatures.colorTempMaxKelvin) {
// set new temp range
this._colorPicker.setTempRange(lightFeatures.colorTempMinKelvin, lightFeatures.colorTempMaxKelvin);
}

// show full-sized brightness picker
if (this.lightContainer.features.isOnlyBrightness()) {
if (lightFeatures.isOnlyBrightness()) {
this._modeSelector.mode = 'brightness';
this.toggleFullSizedBrightness(true);
} else {
Expand Down
33 changes: 33 additions & 0 deletions src/core/light-features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export class LightFeatures implements ILightFeatures {
break;
}
}

if (this.colorTemp) {
this.colorTempMinKelvin = lightEntity.attributes.min_color_temp_kelvin || null;
this.colorTempMaxKelvin = lightEntity.attributes.max_color_temp_kelvin || null;
}
}

public isEmpty(): boolean {
Expand All @@ -53,6 +58,8 @@ export class LightFeatures implements ILightFeatures {

public readonly brightness: boolean = false;
public readonly colorTemp: boolean = false;
public readonly colorTempMinKelvin: number | null = null;
public readonly colorTempMaxKelvin: number | null = null;
public readonly color: boolean = false;
}

Expand Down Expand Up @@ -96,6 +103,32 @@ export class LightFeaturesCombined implements ILightFeatures {
return this._features().some(f => f.colorTemp);
}

public get colorTempMinKelvin() {
let min: number | null = null;

// return the smallest value, if any specified
this._features().forEach(f => {
if (f.colorTempMinKelvin && (min == null || min > f.colorTempMinKelvin)) {
min = f.colorTempMinKelvin;
}
});

return min;
}

public get colorTempMaxKelvin() {
let max: number | null = null;

// return the biggest value, if any specified
this._features().forEach(f => {
if (f.colorTempMaxKelvin && (max == null || max < f.colorTempMaxKelvin)) {
max = f.colorTempMaxKelvin;
}
});

return max;
}

public get color(): boolean {
return this._features().some(f => f.color);
}
Expand Down
10 changes: 10 additions & 0 deletions src/types/types-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,16 @@ export interface ILightFeatures {
*/
get colorTemp(): boolean;

/**
* Gets minimal value of colorTemp in kelvins (only if supported).
*/
get colorTempMinKelvin(): number | null;

/**
* Gets minimal value of colorTemp in kelvins (only if supported).
*/
get colorTempMaxKelvin(): number | null;

/**
* Gets if it's possible to set color of the light.
*/
Expand Down
3 changes: 1 addition & 2 deletions tests/color-temp-picker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ describe('HueTempPicker', () => {

const picker = new HueColorTempPicker();
picker.mode = 'temp';
picker.tempMin = 2020;
picker.tempMax = 6451;
picker.setTempRange(2020, 6451);

const x = 0;
const radius = 950;
Expand Down

0 comments on commit 0ca4a0c

Please sign in to comment.