Skip to content
This repository has been archived by the owner on Jun 3, 2022. It is now read-only.

Commit

Permalink
Adds new ColorUtils class to handle color alpha conversions. (#79)
Browse files Browse the repository at this point in the history
* Adds new ColorUtils class to handle color alpha conversions.

* Fixes comment.

* Fixes alignment.

* Adds test for ColorUtils. Also fixes ColorSwatchControl test error.
  • Loading branch information
chriscox authored Mar 15, 2017
1 parent 5c7de69 commit aaf59ee
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 39 deletions.
23 changes: 6 additions & 17 deletions src/core/variables/ColorVariable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
* under the License.
*/

import * as TinyColor from "tinycolor2";

import { ColorUtils, RgbaColor } from "../../lib/ColorUtils";
import { ConstraintType, ControlType, DataType } from "../../lib/Constants";
import { ISerializableData } from "../../lib/LocalStorage";
import { IVariableCallback, IVariableListParams, Variable } from "./Variable";
Expand Down Expand Up @@ -70,16 +69,6 @@ export class ColorVariable extends Variable implements IColorVariableParams {
ConstraintType.LIST : ConstraintType.NONE;
}

/**
* Updates the selected value.
* @override
* @param {any} value The selected value.
*/
updateValue(value: any): void {
let hexColorValue = TinyColor(value).toHexString();
super.updateValue(hexColorValue);
}

/**
* Clones the variable.
* @return {ColorVariable} Returns the cloned variable.
Expand Down Expand Up @@ -109,9 +98,9 @@ export class ColorVariable extends Variable implements IColorVariableParams {
*/
serialize(): ISerializableData {
let data = super.serialize();
data.selectedValue = TinyColor(this.selectedValue).toRgb();
data.selectedValue = ColorUtils.toRgba(this.selectedValue);
data.limitedToValues = this.limitedToValues.map((value: any) => {
return TinyColor(value).toRgb();
return ColorUtils.toRgba(value);
});
return data;
}
Expand All @@ -123,9 +112,9 @@ export class ColorVariable extends Variable implements IColorVariableParams {
* @return {ColorVariable} A new initialized ColorVariable.
*/
static deserialize(data: ISerializableData): Variable {
let selectedValue = TinyColor(data.selectedValue).toHexString();
let limitedToValues = data.limitedToValues.map((color: string) => {
return TinyColor(color).toHexString();
let selectedValue = ColorUtils.toRgbaString(data.selectedValue);
let limitedToValues = data.limitedToValues.map((color: RgbaColor) => {
return ColorUtils.toRgbaString(color);
});
let variable = new ColorVariable(data.key, selectedValue, limitedToValues);
variable.title = data.title;
Expand Down
11 changes: 0 additions & 11 deletions src/core/variables/Variable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,17 +165,6 @@ export class Variable implements IVariableParams {
}

set selectedValue(value: any) {
this.updateValue(value);
}

/**
* Updates the selected value, saves, and executes callbacks.
*
* Subclasses should override this method if any transformations are
* required on the selected value.
* @param {any} value The selected value.
*/
updateValue(value: any): void {
this._selectedValue = value;
this.save();
if (this._initialized) {
Expand Down
85 changes: 85 additions & 0 deletions src/lib/ColorUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/** @license
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

import * as TinyColor from "tinycolor2";

/**
* A representation of RGBA color.
* @interface
*/
export interface RgbaColor {
// All values [0-255].
r: number;
g: number;
b: number;
a: number;
}

/**
* A class that provides utilities to interact with TinyColor library.
* @class
*/
export class ColorUtils {

/**
* Converts a color string or RgbaColor to an RGBA string.
* @param {any} color The color string or RgbaColor to convert.
* @return {string} Returns the RGBA string.
*/
static toRgbaString(color: any): string {
if (typeof color !== "string") {
// Convert alpha back from int[0-255] to [0-1] float.
(color as RgbaColor).a /= 255;
}
return TinyColor(color).toRgbString();
}

/**
* Converts a color string to its RgbaColor equivalent. Any alpha values
* will be normalized to int[0-255].
* @param {string} color The color string to convert.
* @return {RgbaColor} Returns the RgbaColor.
*/
static toRgba(color: string): RgbaColor {
let rgba = TinyColor(color).toRgb();
// Convert alpha from float to int[0-255].
rgba.a = Math.round(rgba.a * 255);
return rgba;
}

/**
* Returns whether to color strings are equal.
* @param {string} color1 The first color to test if equal.
* @param {string} color2 The second color to test if equal.
* @return {boolean} Returns true if equal. Otherwise false.
*/
static areEqual(color1: string, color2: string): boolean {
return TinyColor(color1).toRgbString() === TinyColor(color2).toRgbString();
}

/**
* Determine a readable color from a base color and list of possible colors.
* @param {any} baseColor The base color of which to test for readable color.
* @param {any[]} colorList A list of possible colors.
* @return {string} Returns the most readable color.
*/
static mostReadable(baseColor: any, colorList: any[]): string {
colorList.map((color) => {
return TinyColor(color);
});
return TinyColor.mostReadable(TinyColor(baseColor), colorList).toRgbString();
}
}
38 changes: 38 additions & 0 deletions src/lib/__tests__/ColorUtils_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as chai from "chai";

import { ColorUtils } from "../ColorUtils";

const expect = chai.expect;

describe("ColorUtils", () => {

const redRgbString: string = "rgb(255, 0, 0)";
const alphaRgbaString: string = "rgba(120, 13, 99, 0.6)";

it("should create proper rgba string from color strings", () => {
expect(ColorUtils.toRgbaString("red")).to.equal(redRgbString);
expect(ColorUtils.toRgbaString("FF0000")).to.equal(redRgbString);
expect(ColorUtils.toRgbaString(alphaRgbaString)).to.equal(alphaRgbaString);
});

it("should create proper rgba string from RgbaColor object", () => {
let redRgbaColor = ColorUtils.toRgba("red");
expect(ColorUtils.toRgbaString(redRgbaColor)).to.equal(redRgbString);

let alpgaRgbaColor = ColorUtils.toRgba(alphaRgbaString);
expect(ColorUtils.toRgbaString(alpgaRgbaColor)).to.equal(alphaRgbaString);

let blueRgbaColor = ColorUtils.toRgba("blue");
expect(ColorUtils.toRgbaString(blueRgbaColor)).to.not.equal(redRgbString);
});

it("should properly compare color equality", () => {
expect(ColorUtils.areEqual(redRgbString, "red")).to.be.true;
expect(ColorUtils.areEqual(redRgbString, "blue")).to.not.be.true;
});

it("should properly choose most readable color", () => {
let readableColor = ColorUtils.mostReadable("black", ["white", "black"]);
expect(readableColor).to.equal(ColorUtils.toRgbaString("white"));
});
});
3 changes: 2 additions & 1 deletion src/ui/__tests__/ColorSwatchControl_test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as chai from "chai";

import { remixer } from "../../core/Remixer";
import { ColorSwatchControl } from "../controls/ColorSwatchControl";
import { ColorUtils } from "../../lib/ColorUtils";
import { CSS } from "../../lib/Constants";
import { Variable } from "../../core/variables/Variable";

Expand Down Expand Up @@ -42,7 +43,7 @@ describe("ColorSwatchControl", () => {

for (let i = 0; i < list.children.length; i++) {
let element = list.children[i] as HTMLElement;
expect(element.dataset["value"]).to.equal(limitedToValues[i]);
expect(element.dataset["value"]).to.equal(ColorUtils.toRgbaString(limitedToValues[i]));
}
});
});
18 changes: 8 additions & 10 deletions src/ui/controls/ColorSwatchControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
*/

import * as React from "react";
import * as TinyColor from "tinycolor2";

import { ColorUtils } from "../../lib/ColorUtils";
import { CSS } from "../../lib/Constants";
import { IColorControlProps } from "./controlProps";

Expand Down Expand Up @@ -48,22 +48,20 @@ export class ColorSwatchControl extends React.Component<IColorControlProps, void
limitedToValues,
selectedValue,
} = this.props.variable;

return (
<div className={`${CSS.RMX_COLOR_SWATCH} ${CSS.MDL_LIST_ITEM} ${CSS.MDL_TWO_LINE}`}>
<span className={CSS.MDL_PRIMARY}>
<span>{title}
<span className={CSS.RMX_SELECTED_VALUE}>
{TinyColor(selectedValue).toString()}
{ColorUtils.toRgbaString(selectedValue)}
</span>
</span>
<span className={CSS.MDL_SECONDARY}>
{limitedToValues.map((value: string) => (
<ColorSwatch color={value} key={value}
isSelected={
TinyColor(selectedValue).toRgbString() ===
TinyColor(value).toRgbString()
}
<ColorSwatch
color={ColorUtils.toRgbaString(value)}
key={value}
isSelected={ColorUtils.areEqual(selectedValue, value)}
onClick={this.onClick}
/>
))}
Expand Down Expand Up @@ -94,10 +92,10 @@ function ColorSwatch(props: IColorSwatchProps) {
isSelected,
onClick,
} = props;

// Determine a readable color to prevent a white checkmark on a light
// color swatch.
let readableCheckColors = [TinyColor("white"), TinyColor("gray")];
let checkColor = TinyColor.mostReadable(TinyColor(color), readableCheckColors);
let checkColor = ColorUtils.mostReadable(color, ["white", "gray"]);
return (
<div
className={CSS.RMX_COLOR_SWATCH_ITEM}
Expand Down

0 comments on commit aaf59ee

Please sign in to comment.