@visual-z/xeno 0.2.1 → 0.4.0
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/CHANGELOG.md +18 -0
- package/README.md +126 -34
- package/docs/app-server.md +84 -0
- package/docs/install.md +51 -13
- package/docs/providers.md +24 -3
- package/package.json +7 -5
- package/src/apps/app-server.ts +28 -0
- package/src/apps/approve.ts +3 -3
- package/src/apps/cli.ts +25 -10
- package/src/apps/commands.ts +45 -8
- package/src/apps/completions.ts +78 -22
- package/src/apps/control-transport.ts +89 -0
- package/src/apps/image-draft.ts +46 -0
- package/src/apps/model-catalog.ts +2 -1
- package/src/apps/project-trust.ts +2 -42
- package/src/apps/provider-connect.ts +346 -0
- package/src/apps/render.ts +22 -47
- package/src/apps/resources.ts +5 -2
- package/src/apps/shell.ts +333 -1542
- package/src/apps/startup-input.ts +45 -9
- package/src/apps/startup.ts +149 -94
- package/src/apps/ui/app.ts +1553 -0
- package/src/apps/ui/control-model.ts +26 -0
- package/src/apps/ui/extension-host.ts +194 -0
- package/src/apps/ui/image-cache.ts +78 -0
- package/src/apps/ui/palette.ts +88 -0
- package/src/apps/ui/project.ts +127 -0
- package/src/apps/ui/standalone.ts +146 -0
- package/src/apps/ui/status-model.ts +68 -0
- package/src/apps/ui/surface.ts +86 -0
- package/src/apps/ui/theme.ts +86 -0
- package/src/core/acceptance.ts +107 -0
- package/src/core/action-defaults.ts +47 -295
- package/src/core/benchmark.ts +2 -2
- package/src/core/branch-receipt.ts +24 -0
- package/src/core/checkpoints.ts +64 -0
- package/src/core/churn.ts +2 -0
- package/src/core/command-artifacts.ts +94 -0
- package/src/core/command-contract.ts +35 -0
- package/src/core/completion-action.ts +20 -0
- package/src/core/completion-review.ts +37 -21
- package/src/core/context-detail.ts +37 -0
- package/src/core/context-policy.ts +74 -1
- package/src/core/continuation.ts +17 -2
- package/src/core/control-protocol.ts +62 -0
- package/src/core/control-view.ts +42 -0
- package/src/core/derive.ts +29 -3
- package/src/core/draft-images.ts +56 -0
- package/src/core/environment.ts +145 -0
- package/src/core/evaluation-coverage.ts +74 -0
- package/src/core/event-validation.ts +81 -6
- package/src/core/events.ts +99 -3
- package/src/core/evidence-links.ts +7 -0
- package/src/core/evidence.ts +13 -3
- package/src/core/execution.ts +8 -2
- package/src/core/experiment.ts +133 -0
- package/src/core/export.ts +9 -0
- package/src/core/failure-recovery.ts +173 -0
- package/src/core/file-precondition.ts +72 -0
- package/src/core/handoff.ts +65 -4
- package/src/core/harness-diagnostics.ts +206 -0
- package/src/core/history-query.ts +38 -2
- package/src/core/history.ts +38 -1
- package/src/core/integration-health.ts +34 -0
- package/src/core/keymap.ts +8 -20
- package/src/core/model-picker.ts +64 -0
- package/src/core/native-eval.ts +25 -0
- package/src/core/note-guidance.ts +34 -0
- package/src/core/oauth-fields.ts +44 -0
- package/src/core/observation-view.ts +49 -8
- package/src/core/permissions.ts +24 -2
- package/src/core/process-receipt.ts +105 -0
- package/src/core/process-state.ts +37 -0
- package/src/core/program-status.ts +31 -0
- package/src/core/project.ts +70 -19
- package/src/core/prompt-policy.ts +37 -0
- package/src/core/query-eval-contract.ts +14 -0
- package/src/core/read-observation.ts +82 -0
- package/src/core/recovery-corpus.ts +17 -0
- package/src/core/recovery-evaluation.ts +60 -0
- package/src/core/recovery-sources.ts +74 -0
- package/src/core/request-progress.ts +87 -0
- package/src/core/request-state.ts +114 -0
- package/src/core/runtime-target.ts +94 -0
- package/src/core/search-observation.ts +97 -0
- package/src/core/session-tree.ts +147 -0
- package/src/core/settings.ts +33 -10
- package/src/core/skills.ts +44 -0
- package/src/core/sql-query.ts +211 -0
- package/src/core/theme.ts +36 -58
- package/src/core/tool-contract.ts +51 -2
- package/src/core/tool-discovery.ts +87 -0
- package/src/core/tool-failure.ts +60 -0
- package/src/core/tool-input.ts +6 -0
- package/src/core/tool-mode-protocol.ts +25 -0
- package/src/core/transcript.ts +19 -4
- package/src/core/trial-lifecycle.ts +14 -0
- package/src/core/tui-mode.ts +29 -0
- package/src/core/update-check.ts +97 -0
- package/src/core/version.ts +55 -0
- package/src/core/work-state.ts +49 -3
- package/src/core/working-set.ts +91 -0
- package/src/runtime/auth.ts +11 -4
- package/src/runtime/branch-jobs.ts +36 -0
- package/src/runtime/catalog.ts +13 -3
- package/src/runtime/clipboard.ts +15 -4
- package/src/runtime/command-artifacts.ts +64 -0
- package/src/runtime/control-connection.ts +104 -0
- package/src/runtime/environment-tools.ts +60 -0
- package/src/runtime/evidence-notes.ts +4 -2
- package/src/runtime/exec-sessions.ts +206 -61
- package/src/runtime/execution-control.ts +1 -0
- package/src/runtime/extension-ui.ts +22 -30
- package/src/runtime/extensions.ts +95 -16
- package/src/runtime/harness-comparison.ts +27 -7
- package/src/runtime/harness-eval.ts +462 -0
- package/src/runtime/history-cache.ts +57 -0
- package/src/runtime/history-tools.ts +62 -12
- package/src/runtime/integrations.ts +210 -0
- package/src/runtime/loop.ts +1088 -282
- package/src/runtime/model-discovery.ts +208 -0
- package/src/runtime/model.ts +9 -0
- package/src/runtime/native-eval-fixtures.ts +33 -0
- package/src/runtime/native-eval.ts +146 -0
- package/src/runtime/oauth.ts +80 -3
- package/src/runtime/pi-provider.ts +169 -12
- package/src/runtime/program-status.ts +38 -0
- package/src/runtime/provider-connect.ts +109 -0
- package/src/runtime/provider-presets.ts +87 -0
- package/src/runtime/provider-request.ts +13 -2
- package/src/runtime/provider.ts +23 -9
- package/src/runtime/query-eval-verifier.ts +28 -0
- package/src/runtime/recovery-coverage.ts +71 -0
- package/src/runtime/recovery-evidence.ts +76 -0
- package/src/runtime/recovery-fixtures.ts +116 -0
- package/src/runtime/runtime-target.ts +149 -0
- package/src/runtime/session-control.ts +198 -0
- package/src/runtime/tool-broker.ts +137 -0
- package/src/runtime/tool-discovery.ts +57 -0
- package/src/runtime/tool-mode-eval.ts +146 -0
- package/src/runtime/tool-queue.ts +77 -0
- package/src/runtime/tools.ts +628 -183
- package/src/runtime/trial-control.ts +45 -0
- package/src/runtime/update-check.ts +47 -0
- package/src/runtime/window-trial.ts +2 -1
- package/src/runtime/working-notes.ts +33 -3
- package/src/runtime/workspace-lifecycle.ts +97 -0
- package/src/store/acceptance.ts +15 -0
- package/src/store/checkpoint-history.ts +43 -0
- package/src/store/command-artifacts.ts +96 -0
- package/src/store/command.ts +65 -37
- package/src/store/file-checkpoints.ts +104 -0
- package/src/store/file-index.ts +13 -1
- package/src/store/git-checkpoints.ts +67 -0
- package/src/store/observations.ts +61 -20
- package/src/store/process-receipts.ts +34 -0
- package/src/store/query-source.ts +13 -8
- package/src/store/recovery.ts +2 -3
- package/src/store/replay.ts +16 -4
- package/src/store/runtime-target.ts +64 -0
- package/src/store/search.ts +171 -132
- package/src/store/session-archive.ts +4 -0
- package/src/store/source-cancellation.ts +6 -0
- package/src/store/update-cache.ts +36 -0
- package/src/store/workspace-observation.ts +113 -0
- package/src/tui/ansi-spans.ts +126 -0
- package/src/tui/style.ts +19 -16
- package/src/tui/ui/atomic-draft.ts +121 -0
- package/src/tui/ui/canvas.ts +96 -0
- package/src/tui/ui/colors.ts +70 -0
- package/src/tui/ui/composer.ts +200 -0
- package/src/tui/ui/control-panel.ts +23 -0
- package/src/tui/ui/dialog.ts +379 -0
- package/src/tui/ui/draft.ts +23 -0
- package/src/tui/ui/edge.ts +186 -0
- package/src/tui/ui/image-size.ts +40 -0
- package/src/tui/ui/ink.ts +210 -0
- package/src/tui/ui/items.ts +40 -0
- package/src/tui/ui/keys.ts +40 -0
- package/src/tui/ui/layout.ts +74 -0
- package/src/tui/ui/list-model.ts +86 -0
- package/src/tui/ui/list-view.ts +172 -0
- package/src/tui/ui/mascot.ts +67 -0
- package/src/tui/ui/messages.ts +518 -0
- package/src/tui/ui/motion.ts +28 -0
- package/src/tui/ui/paths.ts +10 -0
- package/src/tui/ui/pictures.ts +78 -0
- package/src/tui/ui/screen.ts +421 -0
- package/src/tui/ui/status.ts +279 -0
- package/src/tui/ui/styled.ts +85 -0
- package/src/tui/ui/testing.ts +5 -0
- package/src/tui/ui/tool-card.ts +150 -0
- package/src/apps/extension-ui.ts +0 -330
- package/src/apps/resource-browser.ts +0 -75
- package/src/apps/session-browser.ts +0 -113
- package/src/apps/session-picker.ts +0 -55
- package/src/tui/ansi.ts +0 -67
- package/src/tui/autocomplete.ts +0 -29
- package/src/tui/chat-viewport.ts +0 -47
- package/src/tui/complete.ts +0 -87
- package/src/tui/editor.ts +0 -940
- package/src/tui/host.ts +0 -264
- package/src/tui/input.ts +0 -75
- package/src/tui/keys.ts +0 -378
- package/src/tui/markdown.ts +0 -17
- package/src/tui/render.ts +0 -153
- package/src/tui/select.ts +0 -258
- package/src/tui/settings.ts +0 -44
- package/src/tui/statusline.ts +0 -162
- package/src/tui/system-theme.ts +0 -700
- package/src/tui/terminal.ts +0 -30
- package/src/tui/text.ts +0 -52
- package/src/tui/toolview.ts +0 -386
- package/src/tui/transcript.ts +0 -123
- package/src/tui/tree.ts +0 -471
- package/src/vendor/pi-tui/LICENSE.upstream +0 -21
- package/src/vendor/pi-tui/UPSTREAM.json +0 -50
- package/src/vendor/pi-tui/VENDOR.md +0 -25
- package/src/vendor/pi-tui/alt-screen-search.ts +0 -327
- package/src/vendor/pi-tui/autocomplete.ts +0 -860
- package/src/vendor/pi-tui/colors.ts +0 -367
- package/src/vendor/pi-tui/components/alt-screen-flash.ts +0 -51
- package/src/vendor/pi-tui/components/box.ts +0 -166
- package/src/vendor/pi-tui/components/cancellable-loader.ts +0 -40
- package/src/vendor/pi-tui/components/editor.ts +0 -2472
- package/src/vendor/pi-tui/components/h-stack.ts +0 -44
- package/src/vendor/pi-tui/components/image.ts +0 -127
- package/src/vendor/pi-tui/components/input.ts +0 -494
- package/src/vendor/pi-tui/components/loader.ts +0 -101
- package/src/vendor/pi-tui/components/markdown.ts +0 -1021
- package/src/vendor/pi-tui/components/mouse-region.ts +0 -33
- package/src/vendor/pi-tui/components/scroll-view.ts +0 -224
- package/src/vendor/pi-tui/components/select-list.ts +0 -273
- package/src/vendor/pi-tui/components/settings-list.ts +0 -328
- package/src/vendor/pi-tui/components/spacer.ts +0 -28
- package/src/vendor/pi-tui/components/stack.ts +0 -154
- package/src/vendor/pi-tui/components/text.ts +0 -107
- package/src/vendor/pi-tui/components/truncated-text.ts +0 -65
- package/src/vendor/pi-tui/components/v-stack.ts +0 -33
- package/src/vendor/pi-tui/editor-component.ts +0 -74
- package/src/vendor/pi-tui/fuzzy.ts +0 -138
- package/src/vendor/pi-tui/index.ts +0 -186
- package/src/vendor/pi-tui/keybindings.ts +0 -320
- package/src/vendor/pi-tui/keys.ts +0 -1401
- package/src/vendor/pi-tui/kill-ring.ts +0 -46
- package/src/vendor/pi-tui/latex.ts +0 -1506
- package/src/vendor/pi-tui/layout-node.ts +0 -51
- package/src/vendor/pi-tui/layout.ts +0 -449
- package/src/vendor/pi-tui/native/UPSTREAM.json +0 -23
- package/src/vendor/pi-tui/native/clipboard.h +0 -208
- package/src/vendor/pi-tui/native/darwin/README.md +0 -22
- package/src/vendor/pi-tui/native/darwin/build.sh +0 -69
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +0 -132
- package/src/vendor/pi-tui/native/linux/README.md +0 -23
- package/src/vendor/pi-tui/native/linux/build.sh +0 -65
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +0 -70
- package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +0 -274
- package/src/vendor/pi-tui/native/napi.h +0 -91
- package/src/vendor/pi-tui/native/win32/README.md +0 -31
- package/src/vendor/pi-tui/native/win32/build.mjs +0 -232
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/src/win32-platform.c +0 -241
- package/src/vendor/pi-tui/native-modifiers.ts +0 -13
- package/src/vendor/pi-tui/native-module-path.ts +0 -31
- package/src/vendor/pi-tui/native-platform.ts +0 -65
- package/src/vendor/pi-tui/oklab.ts +0 -233
- package/src/vendor/pi-tui/stdin-buffer.ts +0 -444
- package/src/vendor/pi-tui/terminal-colors.ts +0 -91
- package/src/vendor/pi-tui/terminal-image.ts +0 -731
- package/src/vendor/pi-tui/terminal.ts +0 -554
- package/src/vendor/pi-tui/tui-alt-screen.ts +0 -1750
- package/src/vendor/pi-tui/tui-main-screen.ts +0 -655
- package/src/vendor/pi-tui/tui.ts +0 -1493
- package/src/vendor/pi-tui/undo-stack.ts +0 -28
- package/src/vendor/pi-tui/utils.ts +0 -1385
- package/src/vendor/pi-tui/wheel-scroll.ts +0 -82
- package/src/vendor/pi-tui/word-navigation.ts +0 -117
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
#define PI_CLIPBOARD_WRITE
|
|
2
|
-
#include "../../clipboard.h"
|
|
3
|
-
|
|
4
|
-
#ifndef BI_ALPHABITFIELDS
|
|
5
|
-
#define BI_ALPHABITFIELDS 6
|
|
6
|
-
#endif
|
|
7
|
-
|
|
8
|
-
#define KEY_PRESSED_MASK 0x8000
|
|
9
|
-
#define OPEN_CLIPBOARD_ATTEMPTS 10
|
|
10
|
-
#define OPEN_CLIPBOARD_RETRY_MS 5
|
|
11
|
-
#define BITMAP_FILE_HEADER_SIZE 14
|
|
12
|
-
|
|
13
|
-
static int string_equals(const char* left, const char* right) {
|
|
14
|
-
while (*left && *right && *left == *right) {
|
|
15
|
-
left++;
|
|
16
|
-
right++;
|
|
17
|
-
}
|
|
18
|
-
return *left == 0 && *right == 0;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
static int is_key_pressed(int virtual_key) {
|
|
22
|
-
return ((unsigned short)GetAsyncKeyState(virtual_key) & KEY_PRESSED_MASK) != 0;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
static int is_modifier_name_pressed(const char* name) {
|
|
26
|
-
if (string_equals(name, "shift")) return is_key_pressed(VK_SHIFT) || is_key_pressed(VK_LSHIFT) || is_key_pressed(VK_RSHIFT);
|
|
27
|
-
if (string_equals(name, "control")) return is_key_pressed(VK_CONTROL) || is_key_pressed(VK_LCONTROL) || is_key_pressed(VK_RCONTROL);
|
|
28
|
-
if (string_equals(name, "option") || string_equals(name, "alt")) return is_key_pressed(VK_MENU) || is_key_pressed(VK_LMENU) || is_key_pressed(VK_RMENU);
|
|
29
|
-
if (string_equals(name, "command") || string_equals(name, "super") || string_equals(name, "win")) return is_key_pressed(VK_LWIN) || is_key_pressed(VK_RWIN);
|
|
30
|
-
return 0;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
static napi_value __cdecl enable_virtual_terminal_input(napi_env env, napi_callback_info info) {
|
|
34
|
-
(void)info;
|
|
35
|
-
|
|
36
|
-
HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);
|
|
37
|
-
DWORD mode = 0;
|
|
38
|
-
bool enabled = handle != INVALID_HANDLE_VALUE &&
|
|
39
|
-
GetConsoleMode(handle, &mode) &&
|
|
40
|
-
SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_INPUT);
|
|
41
|
-
|
|
42
|
-
napi_get_boolean_fn napi_get_boolean = (napi_get_boolean_fn)node_symbol("napi_get_boolean");
|
|
43
|
-
napi_value result = 0;
|
|
44
|
-
if (!napi_get_boolean || napi_get_boolean(env, enabled, &result) != 0) {
|
|
45
|
-
return fail(env, "Could not configure console input");
|
|
46
|
-
}
|
|
47
|
-
return result;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
static napi_value __cdecl is_modifier_pressed(napi_env env, napi_callback_info info) {
|
|
51
|
-
napi_get_cb_info_fn napi_get_cb_info = (napi_get_cb_info_fn)node_symbol("napi_get_cb_info");
|
|
52
|
-
napi_get_value_string_utf8_fn napi_get_value_string_utf8 =
|
|
53
|
-
(napi_get_value_string_utf8_fn)node_symbol("napi_get_value_string_utf8");
|
|
54
|
-
napi_get_boolean_fn napi_get_boolean = (napi_get_boolean_fn)node_symbol("napi_get_boolean");
|
|
55
|
-
|
|
56
|
-
bool pressed = false;
|
|
57
|
-
if (napi_get_cb_info && napi_get_value_string_utf8) {
|
|
58
|
-
size_t argc = 1;
|
|
59
|
-
napi_value args[1] = {0};
|
|
60
|
-
if (napi_get_cb_info(env, info, &argc, args, 0, 0) == 0 && argc >= 1 && args[0]) {
|
|
61
|
-
char name[16] = {0};
|
|
62
|
-
size_t copied = 0;
|
|
63
|
-
if (napi_get_value_string_utf8(env, args[0], name, sizeof(name), &copied) == 0) {
|
|
64
|
-
pressed = is_modifier_name_pressed(name);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
napi_value result = 0;
|
|
70
|
-
if (!napi_get_boolean || napi_get_boolean(env, pressed, &result) != 0) {
|
|
71
|
-
return fail(env, "Could not inspect modifier state");
|
|
72
|
-
}
|
|
73
|
-
return result;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
static bool open_clipboard(HWND owner) {
|
|
77
|
-
for (int attempt = 0; attempt < OPEN_CLIPBOARD_ATTEMPTS; attempt++) {
|
|
78
|
-
if (OpenClipboard(owner)) return true;
|
|
79
|
-
Sleep(OPEN_CLIPBOARD_RETRY_MS);
|
|
80
|
-
}
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
static uint16_t read_u16(const uint8_t* data) {
|
|
85
|
-
return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
static uint32_t read_u32(const uint8_t* data) {
|
|
89
|
-
return (uint32_t)data[0] |
|
|
90
|
-
((uint32_t)data[1] << 8) |
|
|
91
|
-
((uint32_t)data[2] << 16) |
|
|
92
|
-
((uint32_t)data[3] << 24);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
static void write_u32(uint8_t* data, uint32_t value) {
|
|
96
|
-
data[0] = (uint8_t)value;
|
|
97
|
-
data[1] = (uint8_t)(value >> 8);
|
|
98
|
-
data[2] = (uint8_t)(value >> 16);
|
|
99
|
-
data[3] = (uint8_t)(value >> 24);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
static void copy_bytes(uint8_t* destination, const uint8_t* source, size_t length) {
|
|
103
|
-
volatile uint8_t* target = destination;
|
|
104
|
-
const volatile uint8_t* input = source;
|
|
105
|
-
for (size_t index = 0; index < length; index++) target[index] = input[index];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
static size_t dib_pixel_offset(const uint8_t* dib, size_t size) {
|
|
109
|
-
if (size < 12) return 0;
|
|
110
|
-
uint32_t header_size = read_u32(dib);
|
|
111
|
-
|
|
112
|
-
if (header_size == 12) {
|
|
113
|
-
uint16_t bits_per_pixel = read_u16(dib + 10);
|
|
114
|
-
uint32_t color_count = bits_per_pixel <= 8 ? 1u << bits_per_pixel : 0;
|
|
115
|
-
size_t offset = header_size + (size_t)color_count * 3;
|
|
116
|
-
return offset <= size ? offset : 0;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (header_size < 40 || header_size > size) return 0;
|
|
120
|
-
uint16_t bits_per_pixel = read_u16(dib + 14);
|
|
121
|
-
uint32_t compression = read_u32(dib + 16);
|
|
122
|
-
uint32_t color_count = read_u32(dib + 32);
|
|
123
|
-
if (!color_count && bits_per_pixel <= 8) color_count = 1u << bits_per_pixel;
|
|
124
|
-
|
|
125
|
-
size_t offset = header_size + (size_t)color_count * 4;
|
|
126
|
-
if (header_size == 40 && compression == BI_BITFIELDS) offset += 3 * sizeof(uint32_t);
|
|
127
|
-
if (header_size == 40 && compression == BI_ALPHABITFIELDS) offset += 4 * sizeof(uint32_t);
|
|
128
|
-
return offset <= size ? offset : 0;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
static void read_clipboard_image(clipboard_job* job) {
|
|
132
|
-
UINT png_format = RegisterClipboardFormatW(L"PNG");
|
|
133
|
-
if (!(png_format && IsClipboardFormatAvailable(png_format)) &&
|
|
134
|
-
!IsClipboardFormatAvailable(CF_DIBV5) && !IsClipboardFormatAvailable(CF_DIB)) {
|
|
135
|
-
job->format = CLIPBOARD_EMPTY;
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
HGLOBAL handle = png_format && IsClipboardFormatAvailable(png_format)
|
|
139
|
-
? (HGLOBAL)GetClipboardData(png_format) : 0;
|
|
140
|
-
bool is_png = handle != 0;
|
|
141
|
-
if (!handle && IsClipboardFormatAvailable(CF_DIBV5)) handle = (HGLOBAL)GetClipboardData(CF_DIBV5);
|
|
142
|
-
if (!handle && IsClipboardFormatAvailable(CF_DIB)) handle = (HGLOBAL)GetClipboardData(CF_DIB);
|
|
143
|
-
|
|
144
|
-
const uint8_t* source = handle ? (const uint8_t*)GlobalLock(handle) : 0;
|
|
145
|
-
size_t size = handle ? GlobalSize(handle) : 0;
|
|
146
|
-
if (!source || !size) {
|
|
147
|
-
if (source) GlobalUnlock(handle);
|
|
148
|
-
job->error = "Clipboard does not contain an image";
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
if (is_png) {
|
|
152
|
-
clipboard_copy(job, source, size, CLIPBOARD_BUFFER);
|
|
153
|
-
} else {
|
|
154
|
-
size_t offset = dib_pixel_offset(source, size);
|
|
155
|
-
uint8_t* bitmap = offset && size <= UINT32_MAX - BITMAP_FILE_HEADER_SIZE
|
|
156
|
-
? clipboard_alloc(size + BITMAP_FILE_HEADER_SIZE) : 0;
|
|
157
|
-
if (bitmap) {
|
|
158
|
-
bitmap[0] = 'B';
|
|
159
|
-
bitmap[1] = 'M';
|
|
160
|
-
write_u32(bitmap + 2, (uint32_t)(size + BITMAP_FILE_HEADER_SIZE));
|
|
161
|
-
write_u32(bitmap + 10, (uint32_t)(BITMAP_FILE_HEADER_SIZE + offset));
|
|
162
|
-
copy_bytes(bitmap + BITMAP_FILE_HEADER_SIZE, source, size);
|
|
163
|
-
job->data = bitmap;
|
|
164
|
-
job->length = size + BITMAP_FILE_HEADER_SIZE;
|
|
165
|
-
job->format = CLIPBOARD_BUFFER;
|
|
166
|
-
} else {
|
|
167
|
-
job->error = "Could not create clipboard image buffer";
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
GlobalUnlock(handle);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
static void clipboard_execute(clipboard_job* job) {
|
|
174
|
-
// A real clipboard owner is required for text writes, including under Node
|
|
175
|
-
// without a console. Create and destroy it on the same worker thread.
|
|
176
|
-
HWND owner = 0;
|
|
177
|
-
if (job->operation == CLIPBOARD_WRITE) {
|
|
178
|
-
owner = CreateWindowExW(0, L"STATIC", 0, 0, 0, 0, 0, 0,
|
|
179
|
-
HWND_MESSAGE, 0, GetModuleHandleA(0), 0);
|
|
180
|
-
if (!owner) {
|
|
181
|
-
job->error = "Could not create clipboard owner window";
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if (!open_clipboard(owner)) {
|
|
186
|
-
if (owner) DestroyWindow(owner);
|
|
187
|
-
job->error = "Could not open clipboard";
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
if (job->operation == CLIPBOARD_WRITE) {
|
|
191
|
-
size_t size = (job->length + 1) * sizeof(uint16_t);
|
|
192
|
-
HGLOBAL handle = GlobalAlloc(GMEM_MOVEABLE, size);
|
|
193
|
-
void* text = handle ? GlobalLock(handle) : 0;
|
|
194
|
-
bool success = false;
|
|
195
|
-
if (text) {
|
|
196
|
-
copy_bytes(text, job->data, size);
|
|
197
|
-
GlobalUnlock(handle);
|
|
198
|
-
success = EmptyClipboard() && SetClipboardData(CF_UNICODETEXT, handle) != 0;
|
|
199
|
-
}
|
|
200
|
-
if (!success) {
|
|
201
|
-
if (handle) GlobalFree(handle);
|
|
202
|
-
job->error = "Could not set clipboard text";
|
|
203
|
-
}
|
|
204
|
-
} else if (job->operation == CLIPBOARD_TEXT) {
|
|
205
|
-
if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) {
|
|
206
|
-
job->format = CLIPBOARD_EMPTY;
|
|
207
|
-
} else {
|
|
208
|
-
HGLOBAL handle = (HGLOBAL)GetClipboardData(CF_UNICODETEXT);
|
|
209
|
-
const uint16_t* text = handle ? GlobalLock(handle) : 0;
|
|
210
|
-
if (text) {
|
|
211
|
-
size_t capacity = GlobalSize(handle) / sizeof(uint16_t);
|
|
212
|
-
size_t length = 0;
|
|
213
|
-
while (length < capacity && text[length]) length++;
|
|
214
|
-
clipboard_copy(job, text, length * sizeof(uint16_t), CLIPBOARD_UTF16);
|
|
215
|
-
GlobalUnlock(handle);
|
|
216
|
-
} else {
|
|
217
|
-
job->error = "Clipboard does not contain text";
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
} else {
|
|
221
|
-
read_clipboard_image(job);
|
|
222
|
-
}
|
|
223
|
-
CloseClipboard();
|
|
224
|
-
if (owner) DestroyWindow(owner);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
BOOL WINAPI _DllMainCRTStartup(HINSTANCE instance, DWORD reason, LPVOID reserved) {
|
|
228
|
-
(void)instance;
|
|
229
|
-
(void)reason;
|
|
230
|
-
(void)reserved;
|
|
231
|
-
return TRUE;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
PI_NAPI_EXPORT napi_value PI_NAPI_CALL napi_register_module_v1(napi_env env, napi_value exports) {
|
|
235
|
-
set_function_export(env, exports, "enableVirtualTerminalInput", enable_virtual_terminal_input);
|
|
236
|
-
set_function_export(env, exports, "isModifierPressed", is_modifier_pressed);
|
|
237
|
-
set_function_export(env, exports, "getText", get_clipboard_text);
|
|
238
|
-
set_function_export(env, exports, "setText", set_clipboard_text);
|
|
239
|
-
set_function_export(env, exports, "getImage", get_clipboard_image);
|
|
240
|
-
return exports;
|
|
241
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { getNativePlatformHelper, type ModifierKey } from "./native-platform.ts";
|
|
2
|
-
|
|
3
|
-
export type { ModifierKey } from "./native-platform.ts";
|
|
4
|
-
|
|
5
|
-
export function isNativeModifierPressed(key: ModifierKey): boolean {
|
|
6
|
-
const helper = getNativePlatformHelper();
|
|
7
|
-
if (!helper?.isModifierPressed) return false;
|
|
8
|
-
try {
|
|
9
|
-
return helper.isModifierPressed(key) === true;
|
|
10
|
-
} catch {
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
|
-
const moduleRequire = createRequire(import.meta.url);
|
|
6
|
-
const TUI_PACKAGE_NAME = "@earendil-works/pi-tui";
|
|
7
|
-
|
|
8
|
-
export interface NativeModuleCandidateOptions {
|
|
9
|
-
moduleUrl?: string;
|
|
10
|
-
execPath?: string;
|
|
11
|
-
resolvePackage?: (specifier: string) => string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function getNativeModuleCandidates(nativePath: string, options: NativeModuleCandidateOptions = {}): string[] {
|
|
15
|
-
const moduleDir = dirname(fileURLToPath(options.moduleUrl ?? import.meta.url));
|
|
16
|
-
const candidates: string[] = [];
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const packageEntry = (options.resolvePackage ?? moduleRequire.resolve)(TUI_PACKAGE_NAME);
|
|
20
|
-
candidates.push(join(dirname(packageEntry), "..", nativePath));
|
|
21
|
-
} catch {
|
|
22
|
-
// Standalone binaries do not have an installed TUI package.
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
candidates.push(
|
|
26
|
-
join(moduleDir, "..", nativePath),
|
|
27
|
-
join(moduleDir, nativePath),
|
|
28
|
-
join(dirname(options.execPath ?? process.execPath), nativePath),
|
|
29
|
-
);
|
|
30
|
-
return Array.from(new Set(candidates));
|
|
31
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { getNativeModuleCandidates } from "./native-module-path.ts";
|
|
4
|
-
|
|
5
|
-
const cjsRequire = createRequire(import.meta.url);
|
|
6
|
-
|
|
7
|
-
export type ModifierKey = "shift" | "command" | "control" | "option";
|
|
8
|
-
|
|
9
|
-
export interface NativeClipboard {
|
|
10
|
-
/** Undefined means unavailable, null means no text; transfer failures reject. */
|
|
11
|
-
getText(): Promise<string | null | undefined>;
|
|
12
|
-
/** Undefined means unavailable, null means no image; transfer failures reject. */
|
|
13
|
-
getImage(): Promise<Uint8Array | null | undefined>;
|
|
14
|
-
/** POSIX paths of file URLs on the clipboard. Undefined means unsupported, null means no files. */
|
|
15
|
-
getFilePaths?(): Promise<string[] | null | undefined>;
|
|
16
|
-
/** Linux uses command-line tools to retain clipboard ownership instead. */
|
|
17
|
-
setText?(text: string): Promise<void>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
type NativePlatformHelper = NativeClipboard & {
|
|
21
|
-
enableVirtualTerminalInput?: () => boolean;
|
|
22
|
-
isModifierPressed?: (name: ModifierKey) => boolean;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// Cache module loading, not display availability: a disconnected display can recover.
|
|
26
|
-
const helpers = new Map<string, NativePlatformHelper | undefined>();
|
|
27
|
-
|
|
28
|
-
function loadNativePlatformHelper(platform: string, suffix = ""): NativePlatformHelper | undefined {
|
|
29
|
-
const arch = process.arch;
|
|
30
|
-
if (arch !== "x64" && arch !== "arm64") return undefined;
|
|
31
|
-
const nativePath = path.join(
|
|
32
|
-
"native",
|
|
33
|
-
platform,
|
|
34
|
-
"prebuilds",
|
|
35
|
-
`${platform}-${arch}`,
|
|
36
|
-
`${platform}-platform${suffix}.node`,
|
|
37
|
-
);
|
|
38
|
-
if (helpers.has(nativePath)) return helpers.get(nativePath);
|
|
39
|
-
|
|
40
|
-
for (const modulePath of getNativeModuleCandidates(nativePath)) {
|
|
41
|
-
try {
|
|
42
|
-
const helper = cjsRequire(modulePath) as Partial<NativePlatformHelper> | null;
|
|
43
|
-
if (typeof helper?.getText === "function" && typeof helper.getImage === "function") {
|
|
44
|
-
helpers.set(nativePath, helper as NativePlatformHelper);
|
|
45
|
-
return helper as NativePlatformHelper;
|
|
46
|
-
}
|
|
47
|
-
} catch {
|
|
48
|
-
// Try the next possible packaging location.
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
helpers.set(nativePath, undefined);
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function getNativePlatformHelper(): NativePlatformHelper | undefined {
|
|
56
|
-
if (process.platform !== "darwin" && process.platform !== "win32") return undefined;
|
|
57
|
-
return loadNativePlatformHelper(process.platform);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Load a clipboard helper without opening the display until a read is requested. */
|
|
61
|
-
export function getNativeClipboard(): NativeClipboard | undefined {
|
|
62
|
-
if (process.platform !== "linux") return getNativePlatformHelper();
|
|
63
|
-
if (!process.env.DISPLAY) return undefined;
|
|
64
|
-
return loadNativePlatformHelper("linux", "-x11");
|
|
65
|
-
}
|
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Oklab and OKHSL <-> sRGB conversion. `colors.ts` builds its OKLCH, OKHSL, and color mixing on it.
|
|
3
|
-
*
|
|
4
|
-
* Oklab and OKHSL are Björn Ottosson's color spaces; OKHSL's saturation is relative to the sRGB gamut at
|
|
5
|
-
* each hue and lightness. This is a port of his reference implementation (https://bottosson.github.io/posts/colorpicker/),
|
|
6
|
-
* Copyright (c) 2021 Björn Ottosson, used under the MIT license:
|
|
7
|
-
*
|
|
8
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
9
|
-
* associated documentation files (the "Software"), to deal in the Software without restriction, including
|
|
10
|
-
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
-
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
|
12
|
-
* following conditions: The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
* copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
|
14
|
-
* KIND, EXPRESS OR IMPLIED.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import type { RgbColor } from "./terminal-colors.ts";
|
|
18
|
-
|
|
19
|
-
type Vector = [number, number, number];
|
|
20
|
-
type Matrix = [Vector, Vector, Vector];
|
|
21
|
-
|
|
22
|
-
const multiply = (m: Matrix, [x, y, z]: Vector): Vector =>
|
|
23
|
-
m.map((row) => row[0] * x + row[1] * y + row[2] * z) as Vector;
|
|
24
|
-
|
|
25
|
-
// ============================================================================
|
|
26
|
-
// OKHSL <-> sRGB
|
|
27
|
-
// ============================================================================
|
|
28
|
-
|
|
29
|
-
const LINEAR_SRGB_TO_LMS: Matrix = [
|
|
30
|
-
[0.4122214694707629, 0.5363325372617349, 0.0514459932675022],
|
|
31
|
-
[0.2119034958178251, 0.6806995506452344, 0.1073969535369405],
|
|
32
|
-
[0.0883024591900564, 0.2817188391361215, 0.6299787016738222],
|
|
33
|
-
];
|
|
34
|
-
const LMS_TO_LAB: Matrix = [
|
|
35
|
-
[0.210454268309314, 0.793617774702305, -0.0040720430116193],
|
|
36
|
-
[1.9779985324311684, -2.42859224204858, 0.450593709617411],
|
|
37
|
-
[0.0259040424655478, 0.7827717124575296, -0.8086757549230774],
|
|
38
|
-
];
|
|
39
|
-
const LAB_TO_LMS: Matrix = [
|
|
40
|
-
[1, 0.3963377773761749, 0.2158037573099136],
|
|
41
|
-
[1, -0.1055613458156586, -0.0638541728258133],
|
|
42
|
-
[1, -0.0894841775298119, -1.2914855480194092],
|
|
43
|
-
];
|
|
44
|
-
const LMS_TO_LINEAR_SRGB: Matrix = [
|
|
45
|
-
[4.0767416360759583, -3.3077115392580629, 0.2309699031821043],
|
|
46
|
-
[-1.2684379732850315, 2.6097573492876882, -0.341319376002657],
|
|
47
|
-
[-0.0041960761386756, -0.7034186179359362, 1.7076146940746117],
|
|
48
|
-
];
|
|
49
|
-
/**
|
|
50
|
-
* Per sRGB channel (red, green, blue): the (a, b) half-plane where that channel clips
|
|
51
|
-
* first, and the polynomial approximating the maximum saturation there.
|
|
52
|
-
*/
|
|
53
|
-
const SATURATION_FIT: [[number, number], number[]][] = [
|
|
54
|
-
[
|
|
55
|
-
[-1.8817031, -0.80936501],
|
|
56
|
-
[1.19086277, 1.76576728, 0.59662641, 0.75515197, 0.56771245],
|
|
57
|
-
],
|
|
58
|
-
[
|
|
59
|
-
[1.8144408, -1.19445267],
|
|
60
|
-
[0.73956515, -0.45954404, 0.08285427, 0.12541073, -0.14503204],
|
|
61
|
-
],
|
|
62
|
-
[
|
|
63
|
-
[0.13110758, 1.81333971],
|
|
64
|
-
[1.35733652, -0.00915799, -1.1513021, -0.50559606, 0.00692167],
|
|
65
|
-
],
|
|
66
|
-
];
|
|
67
|
-
const K1 = 0.206;
|
|
68
|
-
const K2 = 0.03;
|
|
69
|
-
const K3 = (1 + K1) / (1 + K2);
|
|
70
|
-
|
|
71
|
-
/** Oklab lightness to OKHSL lightness. */
|
|
72
|
-
export const oklabToOkhslLightness = (x: number): number =>
|
|
73
|
-
0.5 * (K3 * x - K1 + Math.sqrt((K3 * x - K1) ** 2 + 4 * K2 * K3 * x));
|
|
74
|
-
/** OKHSL lightness to Oklab lightness. */
|
|
75
|
-
const okhslToOklabLightness = (x: number): number => (x * x + K1 * x) / (K3 * (x + K2));
|
|
76
|
-
|
|
77
|
-
/** sRGB transfer function: linear to encoded channel, both 0-1. */
|
|
78
|
-
const linearToSrgb = (value: number): number =>
|
|
79
|
-
value > 0.0031308 ? 1.055 * value ** (1 / 2.4) - 0.055 : 12.92 * value;
|
|
80
|
-
/** Inverse sRGB transfer function: encoded to linear channel, both 0-1. */
|
|
81
|
-
const srgbToLinear = (value: number): number => (value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4);
|
|
82
|
-
|
|
83
|
-
/** Oklab [L, a, b] to linear sRGB [r, g, b] (0-1, may leave the gamut). */
|
|
84
|
-
export function oklabToLinearSrgb(lab: Vector): Vector {
|
|
85
|
-
return multiply(LMS_TO_LINEAR_SRGB, multiply(LAB_TO_LMS, lab).map((value) => value ** 3) as Vector);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Linear sRGB [r, g, b] (0-1) to Oklab [L, a, b]. */
|
|
89
|
-
function linearSrgbToOklab(rgb: Vector): Vector {
|
|
90
|
-
return multiply(LMS_TO_LAB, multiply(LINEAR_SRGB_TO_LMS, rgb).map(Math.cbrt) as Vector);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** sRGB channels (0-255) to Oklab [L, a, b]. */
|
|
94
|
-
export function rgbToOklab({ r, g, b }: RgbColor): Vector {
|
|
95
|
-
return linearSrgbToOklab([r / 255, g / 255, b / 255].map(srgbToLinear) as Vector);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** Linear sRGB [r, g, b] to sRGB channels (0-255, rounded), clipping out-of-gamut channels. */
|
|
99
|
-
export function linearSrgbToRgb(linear: Vector): RgbColor {
|
|
100
|
-
const [r, g, b] = linear.map((value) => Math.round(Math.min(1, Math.max(0, linearToSrgb(value))) * 255));
|
|
101
|
-
return { r, g, b };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** Rate of change of each cube-root LMS component along a chroma direction (a, b). */
|
|
105
|
-
function lmsSlopes(a: number, b: number): Vector {
|
|
106
|
-
return [LAB_TO_LMS[0], LAB_TO_LMS[1], LAB_TO_LMS[2]].map((row) => row[1] * a + row[2] * b) as Vector;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Largest saturation (C/L) inside sRGB for hue (a, b): polynomial fit plus one Halley step. */
|
|
110
|
-
function maxSaturation(a: number, b: number): number {
|
|
111
|
-
const channel = SATURATION_FIT.findIndex(([[x, y]], index) => index === 2 || x * a + y * b > 1);
|
|
112
|
-
const [k0, k1, k2, k3, k4] = SATURATION_FIT[channel][1];
|
|
113
|
-
const weights = LMS_TO_LINEAR_SRGB[channel];
|
|
114
|
-
const saturation = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;
|
|
115
|
-
|
|
116
|
-
const slopes = lmsSlopes(a, b);
|
|
117
|
-
const base = slopes.map((k) => 1 + saturation * k);
|
|
118
|
-
const dot = (values: number[]) => values.reduce((sum, value, index) => sum + weights[index] * value, 0);
|
|
119
|
-
const f = dot(base.map((value) => value ** 3));
|
|
120
|
-
const f1 = dot(base.map((value, index) => 3 * slopes[index] * value ** 2));
|
|
121
|
-
const f2 = dot(base.map((value, index) => 6 * slopes[index] ** 2 * value));
|
|
122
|
-
return saturation - (f * f1) / (f1 * f1 - 0.5 * f * f2);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** Oklab lightness and chroma of the most saturated sRGB color of hue (a, b). */
|
|
126
|
-
function cusp(a: number, b: number): [number, number] {
|
|
127
|
-
const saturation = maxSaturation(a, b);
|
|
128
|
-
const lightness = Math.cbrt(1 / Math.max(...oklabToLinearSrgb([1, saturation * a, saturation * b])));
|
|
129
|
-
return [lightness, lightness * saturation];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** Chroma where the constant-lightness line at `lightness` leaves the sRGB gamut. */
|
|
133
|
-
function maxChroma(a: number, b: number, lightness: number, [cuspL, cuspC]: [number, number]): number {
|
|
134
|
-
if (lightness <= cuspL) return (cuspC * lightness) / cuspL;
|
|
135
|
-
// Upper half: triangle edge, then one Halley step against each channel reaching 1.
|
|
136
|
-
const t = (cuspC * (lightness - 1)) / (cuspL - 1);
|
|
137
|
-
const slopes = lmsSlopes(a, b);
|
|
138
|
-
const lms = slopes.map((k) => lightness + t * k);
|
|
139
|
-
const cubes = lms.map((value) => value ** 3);
|
|
140
|
-
const first = lms.map((value, index) => 3 * slopes[index] * value ** 2);
|
|
141
|
-
const second = lms.map((value, index) => 6 * slopes[index] ** 2 * value);
|
|
142
|
-
const dot = (row: Vector, values: number[]) => row[0] * values[0] + row[1] * values[1] + row[2] * values[2];
|
|
143
|
-
const steps = LMS_TO_LINEAR_SRGB.map((row) => {
|
|
144
|
-
const f = dot(row, cubes) - 1;
|
|
145
|
-
const f1 = dot(row, first);
|
|
146
|
-
const f2 = dot(row, second);
|
|
147
|
-
const u = f1 / (f1 * f1 - 0.5 * f * f2);
|
|
148
|
-
return u >= 0 ? -f * u : Number.MAX_VALUE;
|
|
149
|
-
});
|
|
150
|
-
return t + Math.min(...steps);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** OKHSL's chroma reference points at lightness L and hue (a, b): [c0, cMid, cMax]. */
|
|
154
|
-
function chromaStops(L: number, a: number, b: number): [number, number, number] {
|
|
155
|
-
const peak = cusp(a, b);
|
|
156
|
-
const cMax = maxChroma(a, b, L, peak);
|
|
157
|
-
const k = cMax / Math.min(L * (peak[1] / peak[0]), (1 - L) * (peak[1] / (1 - peak[0])));
|
|
158
|
-
const midS =
|
|
159
|
-
0.11516993 +
|
|
160
|
-
1 /
|
|
161
|
-
(7.4477897 +
|
|
162
|
-
4.1590124 * b +
|
|
163
|
-
a *
|
|
164
|
-
(-2.19557347 +
|
|
165
|
-
1.75198401 * b +
|
|
166
|
-
a * (-2.13704948 - 10.02301043 * b + a * (-4.24894561 + 5.38770819 * b + 4.69891013 * a))));
|
|
167
|
-
const midT =
|
|
168
|
-
0.11239642 +
|
|
169
|
-
1 /
|
|
170
|
-
(1.6132032 -
|
|
171
|
-
0.68124379 * b +
|
|
172
|
-
a *
|
|
173
|
-
(0.40370612 +
|
|
174
|
-
0.90148123 * b +
|
|
175
|
-
a * (-0.27087943 + 0.6122399 * b + a * (0.00299215 - 0.45399568 * b - 0.14661872 * a))));
|
|
176
|
-
const cMid = 0.9 * k * Math.sqrt(Math.sqrt(1 / (1 / (L * midS) ** 4 + 1 / ((1 - L) * midT) ** 4)));
|
|
177
|
-
const c0 = Math.sqrt(1 / (1 / (L * 0.4) ** 2 + 1 / ((1 - L) * 0.8) ** 2));
|
|
178
|
-
return [c0, cMid, cMax];
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Convert OKHSL to sRGB channels (0-255, rounded), clipping out-of-gamut channels.
|
|
183
|
-
* @param hue Hue in degrees.
|
|
184
|
-
* @param saturation Saturation, 0-1.
|
|
185
|
-
* @param lightness Lightness, 0-1.
|
|
186
|
-
*/
|
|
187
|
-
export function okhslToRgb(hue: number, saturation: number, lightness: number): RgbColor {
|
|
188
|
-
const L = okhslToOklabLightness(lightness);
|
|
189
|
-
let lab: Vector = [L, 0, 0];
|
|
190
|
-
if (L > 0 && L < 1 && saturation > 0) {
|
|
191
|
-
const angle = (2 * Math.PI * (((hue % 360) + 360) % 360)) / 360;
|
|
192
|
-
const a = Math.cos(angle);
|
|
193
|
-
const b = Math.sin(angle);
|
|
194
|
-
const [c0, cMid, cMax] = chromaStops(L, a, b);
|
|
195
|
-
// Chroma rises from 0 through cMid at s = 0.8 to cMax at s = 1.
|
|
196
|
-
let chroma: number;
|
|
197
|
-
if (saturation < 0.8) {
|
|
198
|
-
const t = 1.25 * saturation;
|
|
199
|
-
const k1 = 0.8 * c0;
|
|
200
|
-
chroma = (t * k1) / (1 - (1 - k1 / cMid) * t);
|
|
201
|
-
} else {
|
|
202
|
-
const t = 5 * (saturation - 0.8);
|
|
203
|
-
const k1 = (0.2 * cMid ** 2 * 1.25 ** 2) / c0;
|
|
204
|
-
chroma = cMid + (t * k1) / (1 - (1 - k1 / (cMax - cMid)) * t);
|
|
205
|
-
}
|
|
206
|
-
lab = [L, chroma * a, chroma * b];
|
|
207
|
-
}
|
|
208
|
-
return linearSrgbToRgb(oklabToLinearSrgb(lab));
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Convert sRGB channels (0-255) to OKHSL.
|
|
213
|
-
* @returns Hue `h` in degrees (0 for grays), saturation `s` and lightness `l` 0-1.
|
|
214
|
-
*/
|
|
215
|
-
export function rgbToOkhsl(rgb: RgbColor): { h: number; s: number; l: number } {
|
|
216
|
-
const [L, labA, labB] = rgbToOklab(rgb);
|
|
217
|
-
const chroma = Math.hypot(labA, labB);
|
|
218
|
-
const lightness = oklabToOkhslLightness(L);
|
|
219
|
-
if (chroma < 1e-9 || lightness <= 0 || lightness >= 1) return { h: 0, s: 0, l: lightness };
|
|
220
|
-
|
|
221
|
-
const hue = ((Math.atan2(labB, labA) * 180) / Math.PI + 360) % 360;
|
|
222
|
-
const [c0, cMid, cMax] = chromaStops(L, labA / chroma, labB / chroma);
|
|
223
|
-
let saturation: number;
|
|
224
|
-
if (chroma < cMid) {
|
|
225
|
-
const k1 = 0.8 * c0;
|
|
226
|
-
saturation = 0.8 * (chroma / (k1 + (1 - k1 / cMid) * chroma));
|
|
227
|
-
} else {
|
|
228
|
-
const k1 = (0.2 * cMid ** 2 * 1.25 ** 2) / c0;
|
|
229
|
-
const offset = chroma - cMid;
|
|
230
|
-
saturation = 0.8 + 0.2 * (offset / (k1 + (1 - k1 / (cMax - cMid)) * offset));
|
|
231
|
-
}
|
|
232
|
-
return { h: hue, s: Math.min(1, Math.max(0, saturation)), l: lightness };
|
|
233
|
-
}
|