Skip to content

Commit

Permalink
Parse CMYK strings, update specs
Browse files Browse the repository at this point in the history
  • Loading branch information
omgovich authored and Vlad Shilov committed Jul 18, 2021
1 parent 5faa747 commit b580aa1
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 64 deletions.
12 changes: 8 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 2.2.0

- New plugin: CMYK color space ❤️ [@EricRovell](https://github.com/EricRovell)

### 2.1.0

- Add new `hue` and `rotate` methods
Expand Down Expand Up @@ -51,8 +55,8 @@

### 1.2.1

- Fix: Do not treat 7-digit hex as a valid color ❤️ @subzey
- Parser update: Turn NaN input values into valid numbers ❤️ @subzey
- Fix: Do not treat 7-digit hex as a valid color ❤️ [@subzey](https://github.com/subzey)
- Parser update: Turn NaN input values into valid numbers ❤️ [@subzey](https://github.com/subzey)

### 1.2.0

Expand All @@ -72,7 +76,7 @@

### 0.10.2

- Sort named colors dictionary for better compression ❤️ @subzey
- Sort named colors dictionary for better compression ❤️ [@subzey](https://github.com/subzey)

### 0.10.1

Expand Down Expand Up @@ -109,7 +113,7 @@

### 0.6.2

- 20% speed improvement ❤️ @jeetiss
- 20% speed improvement ❤️ [@jeetiss](https://github.com/jeetiss)

### 0.6.1

Expand Down
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,25 @@ colord("#555aaa").toCmyk(); // { c: 50, m: 47, y: 0, k: 33, a: 1 }

</details>

<details>
<summary><b><code>.toCmykString()</code></b> (<b>cmyk</b> plugin)</summary>

Converts a color to color space.

Converts a color to [CMYK](https://en.wikipedia.org/wiki/CMYK_color_model) color space and expresses it through the [functional notation](https://www.w3.org/TR/css-color-4/#device-cmyk)

```js
import { colord, extend } from "colord";
import cmykPlugin from "colord/plugins/cmyk";

extend([cmykPlugin]);

colord("#99ffff").toCmykString(); // "device-cmyk(40% 0% 0% 0%)"
colord("#00336680").toCmykString(); // "device-cmyk(100% 50% 0% 60% / 0.5)"
```

</details>

<details>
<summary><b><code>.toHwb()</code></b> (<b>hwb</b> plugin)</summary>

Expand Down Expand Up @@ -655,9 +674,9 @@ colord("#e60000").isReadable("#ffff47", { level: "AAA", size: "large" }); // tru
</details>

<details>
<summary><b><code>cmyk</code> (CMYK color space)</b> <i>1.38 KB</i></summary>
<summary><b><code>cmyk</code> (CMYK color space)</b> <i>0.6 KB</i></summary>

Adds support of [CMYK](https://www.sttmedia.com/colormodel-cmyk) color model. The conversion logic is ported from [CSS Color Module Level 4 Specification](https://www.w3.org/TR/css-color-4/#color-conversion-code).
Adds support of [CMYK](https://www.sttmedia.com/colormodel-cmyk) color model.

```js
import { colord, extend } from "colord";
Expand All @@ -666,7 +685,9 @@ import cmykPlugin from "colord/plugins/cmyk";
extend([cmykPlugin]);

colord("#ffffff").toCmyk(); // { c: 0, m: 0, y: 0, k: 0, a: 1 }
colord("#999966").toCmykString(); // "device-cmyk(0% 0% 33% 40%)"
colord({ c: 0, m: 0, y: 0, k: 100, a: 1 }).toHex(); // "#000000"
colord("device-cmyk(0% 61% 72% 0% / 50%)").toHex(); // "#ff634780"
```

</details>
Expand Down Expand Up @@ -833,4 +854,4 @@ const bar: RgbColor = { r: 0, g: 0, v: 0 }; // ERROR
- [x] [LAB](https://www.w3.org/TR/css-color-4/#resolving-lab-lch-values) color space (via plugin)
- [x] [LCH](https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/) color space (via plugin)
- [x] Mix colors (via plugin)
- [ ] CMYK color space (via plugin)
- [x] CMYK color space (via plugin)
12 changes: 11 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "colord",
"version": "2.1.0",
"version": "2.2.0",
"description": "👑 A tiny yet powerful tool for high-performance color manipulations and conversions",
"keywords": [
"color",
Expand All @@ -18,6 +18,7 @@
"css",
"color-names",
"a11y",
"cmyk",
"mix"
],
"repository": "omgovich/colord",
Expand All @@ -37,6 +38,11 @@
"require": "./plugins/a11y.js",
"default": "./plugins/a11y.mjs"
},
"./plugins/cmyk": {
"import": "./plugins/cmyk.mjs",
"require": "./plugins/cmyk.js",
"default": "./plugins/cmyk.mjs"
},
"./plugins/hwb": {
"import": "./plugins/hwb.mjs",
"require": "./plugins/hwb.js",
Expand Down Expand Up @@ -140,6 +146,10 @@
"path": "dist/plugins/a11y.mjs",
"limit": "0.5 KB"
},
{
"path": "dist/plugins/cmyk.mjs",
"limit": "1 KB"
},
{
"path": "dist/plugins/hwb.mjs",
"limit": "1 KB"
Expand Down
92 changes: 39 additions & 53 deletions src/colorModels/cmyk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,84 +5,70 @@ import { clamp, isPresent, round } from "../helpers";
/**
* Clamps the CMYK color object values.
*/
export function clampCmyka({ c, m, y, k, a = 1 }: CmykaColor): CmykaColor {
return {
c: clamp(c, 0, 100),
m: clamp(m, 0, 100),
y: clamp(y, 0, 100),
k: clamp(k, 0, 100),
a: clamp(a),
};
}
export const clampCmyka = (cmyka: CmykaColor): CmykaColor => ({
c: clamp(cmyka.c, 0, 100),
m: clamp(cmyka.m, 0, 100),
y: clamp(cmyka.y, 0, 100),
k: clamp(cmyka.k, 0, 100),
a: clamp(cmyka.a),
});

/**
* Rounds the CMYK color object values.
*/
export function roundCmyka({ c, m, y, k, a = 1 }: CmykaColor): CmykaColor {
return {
c: round(c, 2),
m: round(m, 2),
y: round(y, 2),
k: round(k, 2),
a: round(a, ALPHA_PRECISION),
};
}
export const roundCmyka = (cmyka: CmykaColor): CmykaColor => ({
c: round(cmyka.c, 2),
m: round(cmyka.m, 2),
y: round(cmyka.y, 2),
k: round(cmyka.k, 2),
a: round(cmyka.a, ALPHA_PRECISION),
});

/**
* Transforms the CMYK color object to RGB.
* https://www.rapidtables.com/convert/color/cmyk-to-rgb.html
*/
export function cmykaToRgba(color: CmykaColor): RgbaColor {
const c = color.c / 100;
const m = color.m / 100;
const y = color.y / 100;
const k = color.k / 100;
const a = color?.a ?? 1;
export function cmykaToRgba(cmyka: CmykaColor): RgbaColor {
return {
r: round(255 * (1 - c) * (1 - k)),
g: round(255 * (1 - m) * (1 - k)),
b: round(255 * (1 - y) * (1 - k)),
a: a ? round(a, ALPHA_PRECISION) : 1,
r: round(255 * (1 - cmyka.c / 100) * (1 - cmyka.k / 100)),
g: round(255 * (1 - cmyka.m / 100) * (1 - cmyka.k / 100)),
b: round(255 * (1 - cmyka.y / 100) * (1 - cmyka.k / 100)),
a: cmyka.a,
};
}

/**
* Convert RGB Color Model object to CMYK.
* https://www.rapidtables.com/convert/color/rgb-to-cmyk.html
*/
export function rgbaToCmyka(color: RgbaColor): CmykaColor {
const [r, g, b, a] = [color.r / 255, color.g / 255, color.b / 255, color.a ?? 1];
const k = 1 - Math.max(r, g, b);
const [c, m, y] = [(1 - r - k) / (1 - k), (1 - g - k) / (1 - k), (1 - b - k) / (1 - k)];
export function rgbaToCmyka(rgba: RgbaColor): CmykaColor {
const k = 1 - Math.max(rgba.r / 255, rgba.g / 255, rgba.b / 255);
const c = (1 - rgba.r / 255 - k) / (1 - k);
const m = (1 - rgba.g / 255 - k) / (1 - k);
const y = (1 - rgba.b / 255 - k) / (1 - k);

return {
c: Number.isNaN(c) ? 0 : round(c * 100),
m: Number.isNaN(m) ? 0 : round(m * 100),
y: Number.isNaN(y) ? 0 : round(y * 100),
c: isNaN(c) ? 0 : round(c * 100),
m: isNaN(m) ? 0 : round(m * 100),
y: isNaN(y) ? 0 : round(y * 100),
k: round(k * 100),
a: round(a, 2),
a: rgba.a,
};
}

/**
* Parses the CMYK color object into RGB.
*/
export function parseCmyka({ c, m, y, k, a = 1 }: InputObject): RgbaColor | null {
if (isPresent(c) && isPresent(m) && isPresent(y) && isPresent(k)) {
const cmyk = clampCmyka({
c: Number(c),
m: Number(m),
y: Number(y),
k: Number(k),
a: Number(a),
});
return cmykaToRgba(cmyk);
}
return null;
}
if (!isPresent(c) || !isPresent(m) || !isPresent(y) || !isPresent(k)) return null;

const cmyk = clampCmyka({
c: Number(c),
m: Number(m),
y: Number(y),
k: Number(k),
a: Number(a),
});

export function cmykToCmykaString(rgb: RgbaColor): string {
const { c, m, y, k, a } = roundCmyka(rgbaToCmyka(rgb));
return a < 1
? `device-cmyk(${c}% ${m}% ${y}% ${k}% / ${a})`
: `device-cmyk(${c}% ${m}% ${y}% ${k}%)`;
return cmykaToRgba(cmyk);
}
32 changes: 32 additions & 0 deletions src/colorModels/cmykString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { RgbaColor } from "../types";
import { clampCmyka, cmykaToRgba, rgbaToCmyka, roundCmyka } from "./cmyk";

const cmykMatcher = /^device-cmyk\(\s*([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i;

/**
* Parses a valid CMYK CSS color function/string
* https://www.w3.org/TR/css-color-4/#device-cmyk
*/
export const parseCmykaString = (input: string): RgbaColor | null => {
const match = cmykMatcher.exec(input);

if (!match) return null;

const cmyka = clampCmyka({
c: Number(match[1]) * (match[2] ? 1 : 100),
m: Number(match[3]) * (match[4] ? 1 : 100),
y: Number(match[5]) * (match[6] ? 1 : 100),
k: Number(match[7]) * (match[8] ? 1 : 100),
a: match[9] === undefined ? 1 : Number(match[9]) / (match[10] ? 100 : 1),
});

return cmykaToRgba(cmyka);
};

export function rgbaToCmykaString(rgb: RgbaColor): string {
const { c, m, y, k, a } = roundCmyka(rgbaToCmyka(rgb));

return a < 1
? `device-cmyk(${c}% ${m}% ${y}% ${k}% / ${a})`
: `device-cmyk(${c}% ${m}% ${y}% ${k}%)`;
}
5 changes: 3 additions & 2 deletions src/plugins/cmyk.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CmykaColor } from "../types";
import { Plugin } from "../extend";
import { parseCmyka, roundCmyka, rgbaToCmyka } from "../colorModels/cmyk";
import { cmykToCmykaString } from "../colorModels/cmyk";
import { parseCmykaString, rgbaToCmykaString } from "../colorModels/cmykString";

declare module "../colord" {
interface Colord {
Expand Down Expand Up @@ -30,10 +30,11 @@ const cmykPlugin: Plugin = (ColordClass, parsers): void => {
};

ColordClass.prototype.toCmykString = function () {
return cmykToCmykaString(this.rgba);
return rgbaToCmykaString(this.rgba);
};

parsers.object.push([parseCmyka, "cmyk"]);
parsers.string.push([parseCmykaString, "cmyk"]);
};

export default cmykPlugin;
8 changes: 7 additions & 1 deletion tests/plugins.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ describe("cmyk", () => {
expect(colord({ c: 0, m: 0, y: 0, k: 0, a: 1 }).toHex()).toBe("#ffffff");
});

it("Parses CMYK color string", () => {
expect(colord("device-cmyk(0% 0% 0% 100%)").toHex()).toBe("#000000");
expect(colord("device-cmyk(0% 61% 72% 0% / 50%)").toHex()).toBe("#ff634780");
expect(colord("device-cmyk(0 0.61 0.72 0 / 0.5)").toHex()).toBe("#ff634780");
});

it("Converts a color to CMYK object", () => {
// https://htmlcolors.com/color-converter
expect(colord("#000000").toCmyk()).toMatchObject({ c: 0, m: 0, y: 0, k: 100, a: 1 });
Expand All @@ -74,7 +80,7 @@ describe("cmyk", () => {
});

it("Converts a color to CMYK string", () => {
https://en.wikipedia.org/wiki/CMYK_color_model
// https://en.wikipedia.org/wiki/CMYK_color_model
expect(colord("#999966").toCmykString()).toBe("device-cmyk(0% 0% 33% 40%)");
expect(colord("#99ffff").toCmykString()).toBe("device-cmyk(40% 0% 0% 0%)");
expect(colord("#00336680").toCmykString()).toBe("device-cmyk(100% 50% 0% 60% / 0.5)");
Expand Down

0 comments on commit b580aa1

Please sign in to comment.