@pierre/theme 0.0.25 → 0.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +1 -0
- package/dist/pierre-dark-vibrant.mjs +1 -0
- package/dist/pierre-dark.mjs +1 -0
- package/dist/pierre-light-vibrant.mjs +1 -0
- package/dist/pierre-light.mjs +1 -0
- package/package.json +6 -1
- package/.github/workflows/publish.yml +0 -68
- package/.github/workflows/test.yml +0 -80
- package/.vscode/extensions.json +0 -5
- package/.vscode/launch.json +0 -24
- package/.vscode/tasks.json +0 -24
- package/.vscodeignore +0 -10
- package/CONTRIBUTING.md +0 -75
- package/DISPLAY-P3.md +0 -120
- package/src/build.ts +0 -60
- package/src/color-p3.ts +0 -229
- package/src/demo-p3.ts +0 -44
- package/src/package-vsix.ts +0 -22
- package/src/palette.ts +0 -379
- package/src/test.ts +0 -272
- package/src/theme.ts +0 -615
- package/src/zed-theme.ts +0 -606
- package/tsconfig.json +0 -21
- package/zed/LICENSE.md +0 -21
- package/zed/README.md +0 -25
- package/zed/extension.toml +0 -7
- package/zed/themes/pierre.json +0 -991
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
name: Test & Build
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request:
|
|
5
|
-
branches: [main]
|
|
6
|
-
push:
|
|
7
|
-
branches: [main]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
test:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
|
|
13
|
-
steps:
|
|
14
|
-
- name: Checkout code
|
|
15
|
-
uses: actions/checkout@v4
|
|
16
|
-
|
|
17
|
-
- name: Setup Node.js
|
|
18
|
-
uses: actions/setup-node@v4
|
|
19
|
-
with:
|
|
20
|
-
node-version: '20'
|
|
21
|
-
cache: 'npm'
|
|
22
|
-
|
|
23
|
-
- name: Install dependencies
|
|
24
|
-
run: npm ci
|
|
25
|
-
|
|
26
|
-
- name: Run TypeScript compilation check
|
|
27
|
-
run: npx tsc --noEmit
|
|
28
|
-
|
|
29
|
-
- name: Build theme files
|
|
30
|
-
run: npm run build
|
|
31
|
-
|
|
32
|
-
- name: Run tests
|
|
33
|
-
run: npm test
|
|
34
|
-
|
|
35
|
-
- name: Verify theme files exist
|
|
36
|
-
run: |
|
|
37
|
-
if [ ! -f "themes/pierre-light.json" ]; then
|
|
38
|
-
echo "❌ pierre-light.json not generated"
|
|
39
|
-
exit 1
|
|
40
|
-
fi
|
|
41
|
-
if [ ! -f "themes/pierre-dark.json" ]; then
|
|
42
|
-
echo "❌ pierre-dark.json not generated"
|
|
43
|
-
exit 1
|
|
44
|
-
fi
|
|
45
|
-
echo "✅ All theme files generated successfully"
|
|
46
|
-
|
|
47
|
-
- name: Verify ESM wrapper modules exist
|
|
48
|
-
run: |
|
|
49
|
-
for theme in pierre-dark pierre-light pierre-dark-vibrant pierre-light-vibrant; do
|
|
50
|
-
if [ ! -f "dist/${theme}.mjs" ]; then
|
|
51
|
-
echo "❌ dist/${theme}.mjs not generated"
|
|
52
|
-
exit 1
|
|
53
|
-
fi
|
|
54
|
-
done
|
|
55
|
-
if [ ! -f "dist/index.mjs" ]; then
|
|
56
|
-
echo "❌ dist/index.mjs not generated"
|
|
57
|
-
exit 1
|
|
58
|
-
fi
|
|
59
|
-
echo "✅ All ESM wrapper modules generated successfully"
|
|
60
|
-
|
|
61
|
-
- name: Check file sizes
|
|
62
|
-
run: |
|
|
63
|
-
light_size=$(wc -c < themes/pierre-light.json)
|
|
64
|
-
dark_size=$(wc -c < themes/pierre-dark.json)
|
|
65
|
-
|
|
66
|
-
echo "📊 Theme file sizes:"
|
|
67
|
-
echo " - pierre-light.json: $light_size bytes"
|
|
68
|
-
echo " - pierre-dark.json: $dark_size bytes"
|
|
69
|
-
|
|
70
|
-
# Sanity check - themes should be at least 10KB
|
|
71
|
-
if [ $light_size -lt 10000 ]; then
|
|
72
|
-
echo "❌ pierre-light.json seems too small ($light_size bytes)"
|
|
73
|
-
exit 1
|
|
74
|
-
fi
|
|
75
|
-
if [ $dark_size -lt 10000 ]; then
|
|
76
|
-
echo "❌ pierre-dark.json seems too small ($dark_size bytes)"
|
|
77
|
-
exit 1
|
|
78
|
-
fi
|
|
79
|
-
|
|
80
|
-
echo "✅ Theme files are properly sized"
|
package/.vscode/extensions.json
DELETED
package/.vscode/launch.json
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// A launch configuration that launches the extension inside a new window
|
|
2
|
-
// Use IntelliSense to learn about possible attributes.
|
|
3
|
-
// Hover to view descriptions of existing attributes.
|
|
4
|
-
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
|
-
{
|
|
6
|
-
"version": "0.2.0",
|
|
7
|
-
"configurations": [
|
|
8
|
-
{
|
|
9
|
-
"name": "Run Extension",
|
|
10
|
-
"type": "extensionHost",
|
|
11
|
-
"request": "launch",
|
|
12
|
-
"runtimeExecutable": "${execPath}",
|
|
13
|
-
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
|
14
|
-
"preLaunchTask": "npm: build"
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"name": "Run Extension Without Build",
|
|
18
|
-
"type": "extensionHost",
|
|
19
|
-
"request": "launch",
|
|
20
|
-
"runtimeExecutable": "${execPath}",
|
|
21
|
-
"args": ["--extensionDevelopmentPath=${workspaceFolder}"]
|
|
22
|
-
}
|
|
23
|
-
]
|
|
24
|
-
}
|
package/.vscode/tasks.json
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "2.0.0",
|
|
3
|
-
"tasks": [
|
|
4
|
-
{
|
|
5
|
-
"type": "npm",
|
|
6
|
-
"script": "start",
|
|
7
|
-
"group": {
|
|
8
|
-
"kind": "build",
|
|
9
|
-
"isDefault": true
|
|
10
|
-
},
|
|
11
|
-
"problemMatcher": [],
|
|
12
|
-
"label": "npm: start",
|
|
13
|
-
"detail": "nodemon --watch src src/index.js"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"type": "npm",
|
|
17
|
-
"script": "build",
|
|
18
|
-
"group": "build",
|
|
19
|
-
"problemMatcher": [],
|
|
20
|
-
"label": "npm: build",
|
|
21
|
-
"detail": "node src/index.js"
|
|
22
|
-
}
|
|
23
|
-
]
|
|
24
|
-
}
|
package/.vscodeignore
DELETED
package/CONTRIBUTING.md
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# Pierre Theme
|
|
2
|
-
|
|
3
|
-
Light and dark themes for Visual Studio Code, Cursor, Zed, and Shiki. Built for [Diffs.com](https://diffs.com) by [The Pierre Computer Company](https://pierre.computer).
|
|
4
|
-
|
|
5
|
-
## Preview
|
|
6
|
-
|
|
7
|
-

|
|
8
|
-

|
|
9
|
-
|
|
10
|
-
## Install
|
|
11
|
-
|
|
12
|
-
### Visual Studio Code
|
|
13
|
-
|
|
14
|
-
From the menu in Visual Studio Code:
|
|
15
|
-
|
|
16
|
-
- View > Extensions (or hit Command+Shift+X or Control+Shift+X)
|
|
17
|
-
- Search for `Pierre Theme`
|
|
18
|
-
- Click install
|
|
19
|
-
|
|
20
|
-
You can also install or download from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=pierrecomputer.pierre-theme).
|
|
21
|
-
|
|
22
|
-
### Cursor
|
|
23
|
-
|
|
24
|
-
From the menu in Cursor:
|
|
25
|
-
|
|
26
|
-
- View > Extensions (or hit Command+Shift+X or Control+Shift+X)
|
|
27
|
-
- Search for `Pierre Theme`
|
|
28
|
-
- Click install
|
|
29
|
-
|
|
30
|
-
You can also install or download from the [Open VSX registry](https://open-vsx.org/extension/pierrecomputer/pierre-theme).
|
|
31
|
-
|
|
32
|
-
### Zed
|
|
33
|
-
|
|
34
|
-
From the menu in Zed:
|
|
35
|
-
|
|
36
|
-
- Zed > Extensions (or hit Command+Shift+X or Control+Shift+X)
|
|
37
|
-
- Search for `Pierre`
|
|
38
|
-
- Click install
|
|
39
|
-
|
|
40
|
-
## Vibrant themes (Display P3)
|
|
41
|
-
|
|
42
|
-
> [!NOTE]
|
|
43
|
-
> Vibrant themes do not work in VS Code or Cursor at this time as it does not support color formats other than Hex or RGB. You can, however, use these with [Diffs](https://diffs.com) or any [Shiki](https://shiki.style) project to render code.
|
|
44
|
-
|
|
45
|
-
The **Vibrant** theme variants use CSS's `color(display-p3 r g b)` format with enhanced saturation to fully utilize Display P3's wider color gamut. Display P3 can represent ~25% more colors than standard sRGB, and these themes are optimized to take full advantage of that on compatible displays.
|
|
46
|
-
|
|
47
|
-
The conversion algorithm transforms sRGB colors to Display P3 through proper linear color space transformations, then enhances saturation (15-30%) and luminance (5% for vibrant colors) to push colors into the wider P3 gamut that isn't accessible in sRGB.
|
|
48
|
-
|
|
49
|
-
## Override
|
|
50
|
-
|
|
51
|
-
To override this (or any other) theme in your personal config file, please follow the guide in the [color theme](https://code.visualstudio.com/api/extension-guides/color-theme) documentation. This is handy for small tweaks to the theme without having to fork and maintain your own theme.
|
|
52
|
-
|
|
53
|
-
## Contribute
|
|
54
|
-
|
|
55
|
-
1. Clone and open this [repo](https://github.com/pierrecomputer/theme) in your editor
|
|
56
|
-
2. Run `npm install` to install the dependencies.
|
|
57
|
-
3. Press `F5` to open a new window with your extension loaded
|
|
58
|
-
4. Open `Code > Preferences > Color Theme` [`⌘k ⌘t`] and pick the "Pierre…" theme you want to test.
|
|
59
|
-
5. Make changes to the [`/src/theme.ts`](https://github.com/pierrecomputer/theme/blob/main/src/theme.ts) file.
|
|
60
|
-
6. Run `npm run build` to update the theme. You can also run `npm run start` instead to automatically rebuild the theme while making changes and no reloading should be necessary.
|
|
61
|
-
7. Run `npm test` to validate your changes (this runs automatically on PRs).
|
|
62
|
-
8. Once you're happy, commit your changes and open a PR.
|
|
63
|
-
|
|
64
|
-
## Scripts
|
|
65
|
-
|
|
66
|
-
| Script | Description |
|
|
67
|
-
| --- | --- |
|
|
68
|
-
| `npm run build` | Builds the theme `.json` files in `./themes` directory |
|
|
69
|
-
| `npm test` | Runs validation tests on the theme (includes build) |
|
|
70
|
-
| `npm run package` | Compiles the theme `.vsix` file at the project root |
|
|
71
|
-
| `npm start` | Automatically runs build on file change |
|
|
72
|
-
|
|
73
|
-
## Credit
|
|
74
|
-
|
|
75
|
-
This theme was built on top of [GitHub's Visual Studio Code Theme](https://github.com/primer/github-vscode-theme). All credit to them for the technique and build tooling, which we've since iterated on for more specific language tokens.
|
package/DISPLAY-P3.md
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
# Display P3 Color Space Implementation
|
|
2
|
-
|
|
3
|
-
This document covers the Display P3 color space implementation for the Pierre theme, including technical details for future reference.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
The vibrant theme variants (`pierre-light-vibrant.json` and `pierre-dark-vibrant.json`) use CSS `color(display-p3 r g b)` format with saturation enhancement to fully utilize Display P3's wider color gamut. These themes are designed for web projects using [Shiki](https://shiki.style/) syntax highlighting. They won't work in VS Code itself, as VS Code only supports hex/RGB color formats.
|
|
8
|
-
|
|
9
|
-
## Color Conversion Process
|
|
10
|
-
|
|
11
|
-
### 1. Linear Transformation
|
|
12
|
-
|
|
13
|
-
```typescript
|
|
14
|
-
// 1. Parse hex → RGB (0-1 range)
|
|
15
|
-
const [r, g, b] = hexToRgb01(hex)
|
|
16
|
-
|
|
17
|
-
// 2. Linearize sRGB (remove gamma)
|
|
18
|
-
const linear = srgbToLinear(rgb)
|
|
19
|
-
|
|
20
|
-
// 3. Transform to P3 color space (matrix transformation)
|
|
21
|
-
const [rP3, gP3, bP3] = linearSrgbToLinearP3(linearR, linearG, linearB)
|
|
22
|
-
|
|
23
|
-
// 4. Apply P3 gamma
|
|
24
|
-
const displayP3 = linearToP3(linearP3)
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
**Transformation Matrix** (linear sRGB → linear P3):
|
|
28
|
-
```
|
|
29
|
-
R_p3 = 0.82246197 * R_srgb + 0.17753803 * G_srgb
|
|
30
|
-
G_p3 = 0.03319420 * R_srgb + 0.96680580 * G_srgb
|
|
31
|
-
B_p3 = 0.01708263 * R_srgb + 0.07239744 * G_srgb + 0.91051993 * B_srgb
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### 2. Gamut Enhancement
|
|
35
|
-
|
|
36
|
-
After P3 conversion, colors are enhanced to push into the wider gamut:
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
// 1. Convert to HSL for easier manipulation
|
|
40
|
-
const [h, s, l] = rgbToHsl(r, g, b)
|
|
41
|
-
|
|
42
|
-
// 2. Skip neutrals (grays, near-blacks/whites)
|
|
43
|
-
if (s < 0.1 || l < 0.1 || l > 0.9) return [r, g, b]
|
|
44
|
-
|
|
45
|
-
// 3. Boost saturation (15-30% based on original)
|
|
46
|
-
const saturationBoost = 0.15 + (s * 0.15)
|
|
47
|
-
const newS = Math.min(1.0, s + (s * saturationBoost))
|
|
48
|
-
|
|
49
|
-
// 4. Boost luminance for vibrant colors (5%)
|
|
50
|
-
let newL = l
|
|
51
|
-
if (s > 0.5 && l < 0.7) {
|
|
52
|
-
newL = Math.min(0.9, l + (l * 0.05))
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// 5. Convert back to RGB → format as CSS
|
|
56
|
-
return `color(display-p3 ${r} ${g} ${b})`
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
This enhancement pushes colors into P3 gamut regions not accessible in sRGB, making them noticeably more vibrant on compatible displays.
|
|
60
|
-
|
|
61
|
-
## Color Examples
|
|
62
|
-
|
|
63
|
-
| Color | sRGB (Standard) | Display P3 (Vibrant) | Notes |
|
|
64
|
-
|--------|-----------------|----------------------|-------|
|
|
65
|
-
| Blue | `#008cff` | `color(display-p3 0.267653 0.570512 1.000000)` | Maxed blue channel |
|
|
66
|
-
| Red | `#ff2e3f` | `color(display-p3 1.000000 0.250216 0.262337)` | Maxed red channel |
|
|
67
|
-
| Purple | `#c635e4` | `color(display-p3 0.770871 0.230698 0.945253)` | Highly saturated |
|
|
68
|
-
| Green | `#0dbe4e` | `color(display-p3 0.298067 0.776115 0.322484)` | Enhanced saturation |
|
|
69
|
-
| Cyan | `#08c0ef` | `color(display-p3 0.327292 0.790977 0.995660)` | Nearly maxed blue |
|
|
70
|
-
|
|
71
|
-
## Usage with Shiki
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
npm i @pierre/vscode-theme
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
import { createHighlighter } from 'shiki'
|
|
79
|
-
import pierreDarkVibrant from 'pierre-vscode-theme/themes/pierre-dark-vibrant.json'
|
|
80
|
-
|
|
81
|
-
const highlighter = await createHighlighter({
|
|
82
|
-
themes: [pierreDarkVibrant],
|
|
83
|
-
langs: ['typescript', 'javascript']
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
const html = highlighter.codeToHtml(code, {
|
|
87
|
-
lang: 'typescript',
|
|
88
|
-
theme: 'Pierre Dark Vibrant'
|
|
89
|
-
})
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Relevant files
|
|
93
|
-
|
|
94
|
-
- **`src/color-p3.ts`** - Color conversion and enhancement
|
|
95
|
-
- **`src/demo-p3.ts`** - Demo showing conversions (`npx ts-node src/demo-p3.ts`)
|
|
96
|
-
- **`color-comparison.html`** - Visual comparison tool (open in Safari on P3 display)
|
|
97
|
-
|
|
98
|
-
## Testing
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
# View color conversions
|
|
102
|
-
npx ts-node src/demo-p3.ts
|
|
103
|
-
|
|
104
|
-
# Rebuild themes
|
|
105
|
-
npm run build
|
|
106
|
-
|
|
107
|
-
# Run tests
|
|
108
|
-
npm test
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## Why this matters
|
|
112
|
-
|
|
113
|
-
Unlike typical P3 conversions that just transform color space mathematically, this implementation:
|
|
114
|
-
|
|
115
|
-
1. **Actually uses the wider gamut** - Pushes colors beyond sRGB constraints
|
|
116
|
-
2. **Intelligently enhances** - Only boosts saturated colors, preserves neutrals
|
|
117
|
-
3. **Maintains accuracy** - Grays, blacks, whites stay true
|
|
118
|
-
4. **Degrades gracefully** - Automatic fallback for non-P3 browsers
|
|
119
|
-
|
|
120
|
-
This makes the themes truly take advantage of modern display technology on compatible hardware.
|
package/src/build.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// src/build.ts
|
|
2
|
-
import { writeFileSync, mkdirSync } from "node:fs";
|
|
3
|
-
import { light as rolesLight, dark as rolesDark } from "./palette";
|
|
4
|
-
import { makeTheme } from "./theme";
|
|
5
|
-
import { makeZedThemeFamily } from "./zed-theme";
|
|
6
|
-
import { convertRolesToP3 } from "./color-p3";
|
|
7
|
-
|
|
8
|
-
mkdirSync("themes", { recursive: true });
|
|
9
|
-
mkdirSync("zed/themes", { recursive: true });
|
|
10
|
-
|
|
11
|
-
// Convert palettes to Display P3 color space
|
|
12
|
-
const rolesLightP3 = convertRolesToP3(rolesLight);
|
|
13
|
-
const rolesDarkP3 = convertRolesToP3(rolesDark);
|
|
14
|
-
|
|
15
|
-
// ============================================
|
|
16
|
-
// VS Code Themes
|
|
17
|
-
// ============================================
|
|
18
|
-
const vscodeThemes = [
|
|
19
|
-
{ file: "themes/pierre-light.json", theme: makeTheme("Pierre Light", "light", rolesLight) },
|
|
20
|
-
{ file: "themes/pierre-dark.json", theme: makeTheme("Pierre Dark", "dark", rolesDark) },
|
|
21
|
-
{ file: "themes/pierre-light-vibrant.json", theme: makeTheme("Pierre Light Vibrant", "light", rolesLightP3) },
|
|
22
|
-
{ file: "themes/pierre-dark-vibrant.json", theme: makeTheme("Pierre Dark Vibrant", "dark", rolesDarkP3) }
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
for (const {file, theme} of vscodeThemes) {
|
|
26
|
-
writeFileSync(file, JSON.stringify(theme, null, 2), "utf8");
|
|
27
|
-
console.log("Wrote", file);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// ============================================
|
|
31
|
-
// Zed Theme Family
|
|
32
|
-
// ============================================
|
|
33
|
-
const zedTheme = makeZedThemeFamily("Pierre", "pierrecomputer", [
|
|
34
|
-
{ name: "Pierre Light", appearance: "light", roles: rolesLight },
|
|
35
|
-
{ name: "Pierre Dark", appearance: "dark", roles: rolesDark },
|
|
36
|
-
]);
|
|
37
|
-
|
|
38
|
-
writeFileSync("zed/themes/pierre.json", JSON.stringify(zedTheme, null, 2), "utf8");
|
|
39
|
-
console.log("Wrote zed/themes/pierre.json");
|
|
40
|
-
|
|
41
|
-
// ============================================
|
|
42
|
-
// ESM wrapper modules (for npm / Shiki consumers)
|
|
43
|
-
// ============================================
|
|
44
|
-
mkdirSync("dist", { recursive: true });
|
|
45
|
-
|
|
46
|
-
const themeNames: string[] = [];
|
|
47
|
-
|
|
48
|
-
for (const { file, theme } of vscodeThemes) {
|
|
49
|
-
const name = file.replace("themes/", "").replace(".json", "");
|
|
50
|
-
themeNames.push(name);
|
|
51
|
-
const json = JSON.stringify(theme);
|
|
52
|
-
const escaped = json.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
53
|
-
const mjs = `export default Object.freeze(JSON.parse('${escaped}'))\n`;
|
|
54
|
-
writeFileSync(`dist/${name}.mjs`, mjs, "utf8");
|
|
55
|
-
console.log("Wrote", `dist/${name}.mjs`);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const indexMjs = `export const themeNames = ${JSON.stringify(themeNames)}\n`;
|
|
59
|
-
writeFileSync("dist/index.mjs", indexMjs, "utf8");
|
|
60
|
-
console.log("Wrote dist/index.mjs");
|
package/src/color-p3.ts
DELETED
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
// src/color-p3.ts
|
|
2
|
-
// Convert sRGB hex colors to CSS Display P3 color space format
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Convert hex color to RGB values (0-1 range)
|
|
6
|
-
*/
|
|
7
|
-
function hexToRgb01(hex: string): [number, number, number] {
|
|
8
|
-
const cleaned = hex.replace('#', '');
|
|
9
|
-
const expanded = cleaned.length === 3
|
|
10
|
-
? cleaned.split('').map(x => x + x).join('')
|
|
11
|
-
: cleaned;
|
|
12
|
-
|
|
13
|
-
const num = parseInt(expanded, 16);
|
|
14
|
-
const r = ((num >> 16) & 255) / 255;
|
|
15
|
-
const g = ((num >> 8) & 255) / 255;
|
|
16
|
-
const b = (num & 255) / 255;
|
|
17
|
-
|
|
18
|
-
return [r, g, b];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Apply sRGB gamma correction (linearize)
|
|
23
|
-
*/
|
|
24
|
-
function srgbToLinear(c: number): number {
|
|
25
|
-
if (c <= 0.04045) {
|
|
26
|
-
return c / 12.92;
|
|
27
|
-
}
|
|
28
|
-
return Math.pow((c + 0.055) / 1.055, 2.4);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Remove sRGB gamma correction (to display)
|
|
33
|
-
*/
|
|
34
|
-
function linearToSrgb(c: number): number {
|
|
35
|
-
if (c <= 0.0031308) {
|
|
36
|
-
return c * 12.92;
|
|
37
|
-
}
|
|
38
|
-
return 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Apply Display P3 gamma correction (same as sRGB)
|
|
43
|
-
*/
|
|
44
|
-
const linearToP3 = linearToSrgb;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Convert linear sRGB to linear Display P3
|
|
48
|
-
* Using the standard conversion matrix from sRGB to P3
|
|
49
|
-
*/
|
|
50
|
-
function linearSrgbToLinearP3(r: number, g: number, b: number): [number, number, number] {
|
|
51
|
-
// Conversion matrix from linear sRGB to linear Display P3
|
|
52
|
-
// This matrix converts from sRGB primaries to P3 primaries via XYZ
|
|
53
|
-
const rOut = 0.82246197 * r + 0.17753803 * g + 0.00000000 * b;
|
|
54
|
-
const gOut = 0.03319420 * r + 0.96680580 * g + 0.00000000 * b;
|
|
55
|
-
const bOut = 0.01708263 * r + 0.07239744 * g + 0.91051993 * b;
|
|
56
|
-
|
|
57
|
-
return [rOut, gOut, bOut];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Format a number for CSS color function (round to reasonable precision)
|
|
62
|
-
*/
|
|
63
|
-
function formatColorValue(value: number): string {
|
|
64
|
-
// Clamp to 0-1 range
|
|
65
|
-
const clamped = Math.max(0, Math.min(1, value));
|
|
66
|
-
// Round to 6 decimal places for precision without being excessive
|
|
67
|
-
return clamped.toFixed(6);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Convert RGB to HSL color space
|
|
72
|
-
*/
|
|
73
|
-
function rgbToHsl(r: number, g: number, b: number): [number, number, number] {
|
|
74
|
-
const max = Math.max(r, g, b);
|
|
75
|
-
const min = Math.min(r, g, b);
|
|
76
|
-
const delta = max - min;
|
|
77
|
-
|
|
78
|
-
let h = 0;
|
|
79
|
-
let s = 0;
|
|
80
|
-
const l = (max + min) / 2;
|
|
81
|
-
|
|
82
|
-
if (delta !== 0) {
|
|
83
|
-
s = l > 0.5 ? delta / (2 - max - min) : delta / (max + min);
|
|
84
|
-
|
|
85
|
-
switch (max) {
|
|
86
|
-
case r:
|
|
87
|
-
h = ((g - b) / delta + (g < b ? 6 : 0)) / 6;
|
|
88
|
-
break;
|
|
89
|
-
case g:
|
|
90
|
-
h = ((b - r) / delta + 2) / 6;
|
|
91
|
-
break;
|
|
92
|
-
case b:
|
|
93
|
-
h = ((r - g) / delta + 4) / 6;
|
|
94
|
-
break;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return [h, s, l];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Convert HSL to RGB color space
|
|
103
|
-
*/
|
|
104
|
-
function hslToRgb(h: number, s: number, l: number): [number, number, number] {
|
|
105
|
-
let r, g, b;
|
|
106
|
-
|
|
107
|
-
if (s === 0) {
|
|
108
|
-
r = g = b = l;
|
|
109
|
-
} else {
|
|
110
|
-
const hue2rgb = (p: number, q: number, t: number) => {
|
|
111
|
-
if (t < 0) t += 1;
|
|
112
|
-
if (t > 1) t -= 1;
|
|
113
|
-
if (t < 1/6) return p + (q - p) * 6 * t;
|
|
114
|
-
if (t < 1/2) return q;
|
|
115
|
-
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
|
116
|
-
return p;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
120
|
-
const p = 2 * l - q;
|
|
121
|
-
|
|
122
|
-
r = hue2rgb(p, q, h + 1/3);
|
|
123
|
-
g = hue2rgb(p, q, h);
|
|
124
|
-
b = hue2rgb(p, q, h - 1/3);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return [r, g, b];
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Enhance colors to take advantage of P3's wider gamut
|
|
132
|
-
* Increases saturation and vibrancy for colors that can benefit from P3
|
|
133
|
-
*/
|
|
134
|
-
function enhanceForP3Gamut(r: number, g: number, b: number): [number, number, number] {
|
|
135
|
-
// Convert to HSL for easier saturation manipulation
|
|
136
|
-
const [h, s, l] = rgbToHsl(r, g, b);
|
|
137
|
-
|
|
138
|
-
// Only enhance colors that have some saturation and aren't too light or dark
|
|
139
|
-
// (grays and near-blacks/whites don't benefit from P3 enhancement)
|
|
140
|
-
if (s < 0.1 || l < 0.1 || l > 0.9) {
|
|
141
|
-
return [r, g, b];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Calculate enhancement factor based on original saturation
|
|
145
|
-
// More saturated colors get more enhancement (they can handle it)
|
|
146
|
-
const saturationBoost = 0.15 + (s * 0.15); // 15-30% boost depending on saturation
|
|
147
|
-
|
|
148
|
-
// Boost saturation, but not beyond 1.0
|
|
149
|
-
let newS = s + (s * saturationBoost);
|
|
150
|
-
newS = Math.min(1.0, newS);
|
|
151
|
-
|
|
152
|
-
// For highly saturated colors, also slightly increase luminance to make them "pop"
|
|
153
|
-
// but only if they're not already too light
|
|
154
|
-
let newL = l;
|
|
155
|
-
if (s > 0.5 && l < 0.7) {
|
|
156
|
-
newL = l + (l * 0.05); // Slight 5% luminance boost for vibrant colors
|
|
157
|
-
newL = Math.min(0.9, newL);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Convert back to RGB
|
|
161
|
-
return hslToRgb(h, newS, newL);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Convert sRGB hex color to CSS Display P3 color format with enhancement
|
|
166
|
-
* Returns: "color(display-p3 r g b)" or "color(display-p3 r g b / alpha)"
|
|
167
|
-
*/
|
|
168
|
-
export function srgbHexToP3Color(srgbHex: string, enhance: boolean = true): string {
|
|
169
|
-
// Handle alpha channel if present
|
|
170
|
-
const hasAlpha = srgbHex.length === 9 || (srgbHex.startsWith('#') && srgbHex.length === 9);
|
|
171
|
-
let alpha = '';
|
|
172
|
-
let colorHex = srgbHex;
|
|
173
|
-
|
|
174
|
-
if (hasAlpha) {
|
|
175
|
-
const alphaHex = srgbHex.slice(-2);
|
|
176
|
-
const alphaValue = parseInt(alphaHex, 16) / 255;
|
|
177
|
-
alpha = ` / ${formatColorValue(alphaValue)}`;
|
|
178
|
-
colorHex = srgbHex.slice(0, -2);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Convert to linear sRGB
|
|
182
|
-
const [sR, sG, sB] = hexToRgb01(colorHex);
|
|
183
|
-
const linearSR = srgbToLinear(sR);
|
|
184
|
-
const linearSG = srgbToLinear(sG);
|
|
185
|
-
const linearSB = srgbToLinear(sB);
|
|
186
|
-
|
|
187
|
-
// Convert to linear P3
|
|
188
|
-
const [linearPR, linearPG, linearPB] = linearSrgbToLinearP3(linearSR, linearSG, linearSB);
|
|
189
|
-
|
|
190
|
-
// Apply P3 gamma to get display values
|
|
191
|
-
let pR = linearToP3(linearPR);
|
|
192
|
-
let pG = linearToP3(linearPG);
|
|
193
|
-
let pB = linearToP3(linearPB);
|
|
194
|
-
|
|
195
|
-
// Enhance colors to take advantage of P3's wider gamut
|
|
196
|
-
if (enhance) {
|
|
197
|
-
[pR, pG, pB] = enhanceForP3Gamut(pR, pG, pB);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Format as CSS color(display-p3 ...) function
|
|
201
|
-
return `color(display-p3 ${formatColorValue(pR)} ${formatColorValue(pG)} ${formatColorValue(pB)}${alpha})`;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Convert all colors in a Roles object to Display P3
|
|
206
|
-
*/
|
|
207
|
-
export function convertRolesToP3<T>(obj: T): T {
|
|
208
|
-
if (typeof obj === 'string') {
|
|
209
|
-
// If it's a hex color string, convert it
|
|
210
|
-
if ((obj as string).match(/^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/)) {
|
|
211
|
-
return srgbHexToP3Color(obj as string) as any;
|
|
212
|
-
}
|
|
213
|
-
return obj;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (Array.isArray(obj)) {
|
|
217
|
-
return obj.map(item => convertRolesToP3(item)) as any;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (obj !== null && typeof obj === 'object') {
|
|
221
|
-
const result: any = {};
|
|
222
|
-
for (const [key, value] of Object.entries(obj as any)) {
|
|
223
|
-
result[key] = convertRolesToP3(value);
|
|
224
|
-
}
|
|
225
|
-
return result;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return obj;
|
|
229
|
-
}
|
package/src/demo-p3.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// src/demo-p3.ts
|
|
2
|
-
// Demonstration of Display P3 color conversion with enhancement
|
|
3
|
-
|
|
4
|
-
import { srgbHexToP3Color } from "./color-p3";
|
|
5
|
-
|
|
6
|
-
console.log("Display P3 Color Conversion Demo");
|
|
7
|
-
console.log("=".repeat(70));
|
|
8
|
-
console.log("");
|
|
9
|
-
|
|
10
|
-
const testColors = [
|
|
11
|
-
{ name: "Blue", srgb: "#008cff" },
|
|
12
|
-
{ name: "Green", srgb: "#0dbe4e" },
|
|
13
|
-
{ name: "Red", srgb: "#ff2e3f" },
|
|
14
|
-
{ name: "Purple", srgb: "#c635e4" },
|
|
15
|
-
{ name: "Pink", srgb: "#fc2b73" },
|
|
16
|
-
{ name: "Orange", srgb: "#fe8c2c" },
|
|
17
|
-
{ name: "Cyan", srgb: "#08c0ef" },
|
|
18
|
-
{ name: "Teal", srgb: "#00c5d2" }
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
console.log("Color conversions from sRGB to Enhanced Display P3:");
|
|
22
|
-
console.log("(Enhanced to take advantage of P3's wider gamut)");
|
|
23
|
-
console.log("");
|
|
24
|
-
|
|
25
|
-
for (const { name, srgb } of testColors) {
|
|
26
|
-
const p3Basic = srgbHexToP3Color(srgb, false);
|
|
27
|
-
const p3Enhanced = srgbHexToP3Color(srgb, true);
|
|
28
|
-
console.log(`${name.padEnd(10)} ${srgb}`);
|
|
29
|
-
console.log(`${''.padEnd(10)} Basic: ${p3Basic}`);
|
|
30
|
-
console.log(`${''.padEnd(10)} Enhanced: ${p3Enhanced}`);
|
|
31
|
-
console.log("");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
console.log("=".repeat(70));
|
|
35
|
-
console.log("");
|
|
36
|
-
console.log("Enhancement Details:");
|
|
37
|
-
console.log("- Saturation boost: 15-30% depending on original saturation");
|
|
38
|
-
console.log("- Luminance boost: 5% for highly saturated colors");
|
|
39
|
-
console.log("- Grays and near-blacks/whites are left unchanged");
|
|
40
|
-
console.log("");
|
|
41
|
-
console.log("These enhanced colors take full advantage of Display P3's");
|
|
42
|
-
console.log("wider color gamut (~25% more colors than sRGB).");
|
|
43
|
-
console.log("");
|
|
44
|
-
console.log("Browser support: Safari 10+, Chrome 111+, Firefox 113+, Edge 111+");
|