@crabcrop/mcp 0.0.0-stage → 0.1.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/README.md +62 -2
- package/dist/index.js +416 -0
- package/node_modules/@crabcrop/url/README.md +81 -0
- package/node_modules/@crabcrop/url/dist/explain.d.ts +42 -0
- package/node_modules/@crabcrop/url/dist/explain.js +654 -0
- package/node_modules/@crabcrop/url/dist/index.d.ts +84 -0
- package/node_modules/@crabcrop/url/dist/index.js +182 -0
- package/node_modules/@crabcrop/url/dist/operations.d.ts +178 -0
- package/node_modules/@crabcrop/url/dist/operations.js +491 -0
- package/node_modules/@crabcrop/url/package.json +31 -0
- package/package.json +48 -4
- package/server.json +1 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute the signature segment for a remainder: the lowercase
|
|
3
|
+
* hex HMAC-SHA256 of `remainder` under `secret`. The remainder
|
|
4
|
+
* is everything after the signature segment in the URL,
|
|
5
|
+
* exactly as it will appear.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sign(secret: string, remainder: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* The purge signature for a target path (the request
|
|
10
|
+
* remainder without its signature): HMAC-SHA256 of the
|
|
11
|
+
* path under a subkey derived from the secret (the raw
|
|
12
|
+
* HMAC of the secret over the label `crabcrop/purge-key`),
|
|
13
|
+
* so an image signature can never be replayed as a purge
|
|
14
|
+
* signature. This is the signature segment for
|
|
15
|
+
* `POST /purge/<signature>/<path>`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function purgeSignature(secret: string, target: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* The purge-group signature for a cache group: a purge
|
|
20
|
+
* signature over the literal string `purge-group/<group>`,
|
|
21
|
+
* which appears nowhere in the request URL. The helper
|
|
22
|
+
* exists so nobody has to know the signed string. This is
|
|
23
|
+
* the signature segment for
|
|
24
|
+
* `POST /purge-group/<signature>/<group>`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function purgeGroupSignature(secret: string, group: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* The upload signature for an object name: HMAC-SHA256 of
|
|
29
|
+
* the name under a subkey derived from the secret (the raw
|
|
30
|
+
* HMAC of the secret over the label `crabcrop/upload-key`),
|
|
31
|
+
* so neither an image signature nor a purge signature can
|
|
32
|
+
* ever be replayed as an upload signature. This is the
|
|
33
|
+
* signature segment for `PUT /upload/<signature>/<name>`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function uploadSignature(secret: string, name: string): string;
|
|
36
|
+
export interface BuildOptions {
|
|
37
|
+
/** Unix-seconds expiry, emitted as a leading `exp:` segment. */
|
|
38
|
+
expires?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Key ID to stamp on the signature segment (`<key-id>:<tag>`),
|
|
41
|
+
* for servers running a secret rotation: they verify a keyed
|
|
42
|
+
* segment against the named key only. Requires a non-null
|
|
43
|
+
* secret (an `unsafe` URL has no key to name).
|
|
44
|
+
*/
|
|
45
|
+
keyId?: string;
|
|
46
|
+
}
|
|
47
|
+
/** Whether `id` is a well-formed key ID (1-32 of `[A-Za-z0-9_-]`). */
|
|
48
|
+
export declare function isValidKeyId(id: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The signature segment for a keyed signature: `<key-id>:`
|
|
51
|
+
* followed by the hex tag `sign` computes.
|
|
52
|
+
*/
|
|
53
|
+
export declare function signWithKeyId(secret: string, keyId: string, remainder: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* Assemble a signed (or `unsafe`) crabcrop URL. `operations`
|
|
56
|
+
* is the operation string verbatim (several `/`-separated
|
|
57
|
+
* segments and `preset:` references included). A null secret
|
|
58
|
+
* produces the literal `unsafe` signature segment, for servers
|
|
59
|
+
* running with unsafe or presets-only configurations.
|
|
60
|
+
*/
|
|
61
|
+
export declare function buildURL(base: string, secret: string | null, operations: string, source: string, options?: BuildOptions): string;
|
|
62
|
+
/**
|
|
63
|
+
* Build an `srcset` attribute value: one `{width}x0` URL per
|
|
64
|
+
* width, entries formatted `{url} {width}w` and joined by
|
|
65
|
+
* `, `. An expiry, when given, applies to every entry.
|
|
66
|
+
*/
|
|
67
|
+
export declare function buildSrcSet(base: string, secret: string | null, source: string, widths: number[], options?: BuildOptions): string;
|
|
68
|
+
export { explainUrl } from "./explain.ts";
|
|
69
|
+
export { FILTER_DESCRIPTIONS } from "./explain.ts";
|
|
70
|
+
export type { ExplainedSegment, Explanation, SegmentKind, SignatureKind } from "./explain.ts";
|
|
71
|
+
export { filter, joinOps, ops } from "./operations.ts";
|
|
72
|
+
export type { Anchor, BlendMode, CropAlign, CurveChannel, Dimension, ImageFormat, MaskShape, } from "./operations.ts";
|
|
73
|
+
/**
|
|
74
|
+
* Verify a URL's signature locally. Returns true or false, or
|
|
75
|
+
* null when the URL is unsigned or malformed.
|
|
76
|
+
*
|
|
77
|
+
* With a single secret string, the key ID (when the URL
|
|
78
|
+
* carries one) is ignored: the question is whether this
|
|
79
|
+
* secret made the tag. With a keyring (a map of key ID to
|
|
80
|
+
* secret), the server's rotation semantics apply: a keyed
|
|
81
|
+
* URL consults exactly its named key (an unknown ID is
|
|
82
|
+
* false), and an unkeyed URL consults every key in the map.
|
|
83
|
+
*/
|
|
84
|
+
export declare function verifyUrlSignature(secret: string | Record<string, string>, input: string): boolean | null;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build and sign crabcrop URLs, per `spec/url-signing.md` in
|
|
3
|
+
* the crabcrop repository. Standard library only.
|
|
4
|
+
*/
|
|
5
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
6
|
+
/**
|
|
7
|
+
* Compute the signature segment for a remainder: the lowercase
|
|
8
|
+
* hex HMAC-SHA256 of `remainder` under `secret`. The remainder
|
|
9
|
+
* is everything after the signature segment in the URL,
|
|
10
|
+
* exactly as it will appear.
|
|
11
|
+
*/
|
|
12
|
+
export function sign(secret, remainder) {
|
|
13
|
+
return createHmac("sha256", secret).update(remainder, "utf8").digest("hex");
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The purge signature for a target path (the request
|
|
17
|
+
* remainder without its signature): HMAC-SHA256 of the
|
|
18
|
+
* path under a subkey derived from the secret (the raw
|
|
19
|
+
* HMAC of the secret over the label `crabcrop/purge-key`),
|
|
20
|
+
* so an image signature can never be replayed as a purge
|
|
21
|
+
* signature. This is the signature segment for
|
|
22
|
+
* `POST /purge/<signature>/<path>`.
|
|
23
|
+
*/
|
|
24
|
+
export function purgeSignature(secret, target) {
|
|
25
|
+
const subkey = Buffer.from(sign(secret, "crabcrop/purge-key"), "hex");
|
|
26
|
+
return createHmac("sha256", subkey).update(target, "utf8").digest("hex");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The purge-group signature for a cache group: a purge
|
|
30
|
+
* signature over the literal string `purge-group/<group>`,
|
|
31
|
+
* which appears nowhere in the request URL. The helper
|
|
32
|
+
* exists so nobody has to know the signed string. This is
|
|
33
|
+
* the signature segment for
|
|
34
|
+
* `POST /purge-group/<signature>/<group>`.
|
|
35
|
+
*/
|
|
36
|
+
export function purgeGroupSignature(secret, group) {
|
|
37
|
+
return purgeSignature(secret, `purge-group/${group}`);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The upload signature for an object name: HMAC-SHA256 of
|
|
41
|
+
* the name under a subkey derived from the secret (the raw
|
|
42
|
+
* HMAC of the secret over the label `crabcrop/upload-key`),
|
|
43
|
+
* so neither an image signature nor a purge signature can
|
|
44
|
+
* ever be replayed as an upload signature. This is the
|
|
45
|
+
* signature segment for `PUT /upload/<signature>/<name>`.
|
|
46
|
+
*/
|
|
47
|
+
export function uploadSignature(secret, name) {
|
|
48
|
+
const subkey = Buffer.from(sign(secret, "crabcrop/upload-key"), "hex");
|
|
49
|
+
return createHmac("sha256", subkey).update(name, "utf8").digest("hex");
|
|
50
|
+
}
|
|
51
|
+
/** Whether `id` is a well-formed key ID (1-32 of `[A-Za-z0-9_-]`). */
|
|
52
|
+
export function isValidKeyId(id) {
|
|
53
|
+
return /^[A-Za-z0-9_-]{1,32}$/.test(id);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The signature segment for a keyed signature: `<key-id>:`
|
|
57
|
+
* followed by the hex tag `sign` computes.
|
|
58
|
+
*/
|
|
59
|
+
export function signWithKeyId(secret, keyId, remainder) {
|
|
60
|
+
if (!isValidKeyId(keyId)) {
|
|
61
|
+
throw new Error(`invalid key ID ${JSON.stringify(keyId)}: 1 to 32 characters of [A-Za-z0-9_-]`);
|
|
62
|
+
}
|
|
63
|
+
return `${keyId}:${sign(secret, remainder)}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Fail loudly on inputs that can never produce a verifiable URL
|
|
67
|
+
* (audit P-4, P-5): the remainder travels as a raw URL path, so
|
|
68
|
+
* `?`/`#` would truncate it and whitespace, control, and
|
|
69
|
+
* non-ASCII characters cannot be sent as-is; an expiry must be
|
|
70
|
+
* a non-negative integer the server's u64 parser accepts.
|
|
71
|
+
*/
|
|
72
|
+
function assertUrlText(text, what) {
|
|
73
|
+
for (const c of text) {
|
|
74
|
+
const code = c.codePointAt(0);
|
|
75
|
+
if (c === "?" || c === "#" || code <= 0x20 || code === 0x7f || code > 0x7e) {
|
|
76
|
+
throw new Error(`${what} contains a character that cannot appear in a crabcrop URL: ${JSON.stringify(c)}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function assertExpires(expires) {
|
|
81
|
+
if (!Number.isSafeInteger(expires) || expires < 0) {
|
|
82
|
+
throw new Error(`expires must be a non-negative integer number of unix seconds, got ${expires}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A srcset width becomes a `{width}x0` size segment, so it
|
|
87
|
+
* must satisfy the grammar's dimension rules: a positive
|
|
88
|
+
* integer within the server cap. Anything else mints a
|
|
89
|
+
* URL the server rejects (or worse, `-300` reads as a
|
|
90
|
+
* flip), so it throws here, at build time.
|
|
91
|
+
*/
|
|
92
|
+
function assertWidth(width) {
|
|
93
|
+
if (!Number.isSafeInteger(width) || width < 1 || width > 16383) {
|
|
94
|
+
throw new Error(`srcset width must be a positive integer no larger than 16383, got ${width}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Assemble a signed (or `unsafe`) crabcrop URL. `operations`
|
|
99
|
+
* is the operation string verbatim (several `/`-separated
|
|
100
|
+
* segments and `preset:` references included). A null secret
|
|
101
|
+
* produces the literal `unsafe` signature segment, for servers
|
|
102
|
+
* running with unsafe or presets-only configurations.
|
|
103
|
+
*/
|
|
104
|
+
export function buildURL(base, secret, operations, source, options = {}) {
|
|
105
|
+
if (options.expires !== undefined) {
|
|
106
|
+
assertExpires(options.expires);
|
|
107
|
+
}
|
|
108
|
+
assertUrlText(operations, "operations");
|
|
109
|
+
assertUrlText(source, "source");
|
|
110
|
+
const trimmedBase = base.replace(/\/+$/, "");
|
|
111
|
+
let remainder = "";
|
|
112
|
+
if (options.expires !== undefined) {
|
|
113
|
+
remainder += `exp:${options.expires}/`;
|
|
114
|
+
}
|
|
115
|
+
if (operations) {
|
|
116
|
+
remainder += `${operations}/`;
|
|
117
|
+
}
|
|
118
|
+
remainder += source;
|
|
119
|
+
let signature;
|
|
120
|
+
if (secret === null) {
|
|
121
|
+
if (options.keyId !== undefined) {
|
|
122
|
+
throw new Error("keyId requires a secret: an unsafe URL has no key to name");
|
|
123
|
+
}
|
|
124
|
+
signature = "unsafe";
|
|
125
|
+
}
|
|
126
|
+
else if (options.keyId !== undefined) {
|
|
127
|
+
signature = signWithKeyId(secret, options.keyId, remainder);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
signature = sign(secret, remainder);
|
|
131
|
+
}
|
|
132
|
+
return `${trimmedBase}/${signature}/${remainder}`;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Build an `srcset` attribute value: one `{width}x0` URL per
|
|
136
|
+
* width, entries formatted `{url} {width}w` and joined by
|
|
137
|
+
* `, `. An expiry, when given, applies to every entry.
|
|
138
|
+
*/
|
|
139
|
+
export function buildSrcSet(base, secret, source, widths, options = {}) {
|
|
140
|
+
for (const w of widths) {
|
|
141
|
+
assertWidth(w);
|
|
142
|
+
}
|
|
143
|
+
return widths
|
|
144
|
+
.map((w) => `${buildURL(base, secret, `${w}x0`, source, options)} ${w}w`)
|
|
145
|
+
.join(", ");
|
|
146
|
+
}
|
|
147
|
+
export { explainUrl } from "./explain.js";
|
|
148
|
+
export { FILTER_DESCRIPTIONS } from "./explain.js";
|
|
149
|
+
export { filter, joinOps, ops } from "./operations.js";
|
|
150
|
+
import { explainUrl } from "./explain.js";
|
|
151
|
+
/**
|
|
152
|
+
* Verify a URL's signature locally. Returns true or false, or
|
|
153
|
+
* null when the URL is unsigned or malformed.
|
|
154
|
+
*
|
|
155
|
+
* With a single secret string, the key ID (when the URL
|
|
156
|
+
* carries one) is ignored: the question is whether this
|
|
157
|
+
* secret made the tag. With a keyring (a map of key ID to
|
|
158
|
+
* secret), the server's rotation semantics apply: a keyed
|
|
159
|
+
* URL consults exactly its named key (an unknown ID is
|
|
160
|
+
* false), and an unkeyed URL consults every key in the map.
|
|
161
|
+
*/
|
|
162
|
+
export function verifyUrlSignature(secret, input) {
|
|
163
|
+
const { signature, remainder } = explainUrl(input);
|
|
164
|
+
if (!signature || signature.kind !== "hmac") {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
// Constant-time, like the Rust and Python verifiers: a
|
|
168
|
+
// plain === leaks how much of the tag was right.
|
|
169
|
+
const given = Buffer.from(signature.value, "utf8");
|
|
170
|
+
const matches = (candidate) => {
|
|
171
|
+
const expected = Buffer.from(sign(candidate, remainder), "utf8");
|
|
172
|
+
return expected.length === given.length && timingSafeEqual(expected, given);
|
|
173
|
+
};
|
|
174
|
+
if (typeof secret === "string") {
|
|
175
|
+
return matches(secret);
|
|
176
|
+
}
|
|
177
|
+
if (signature.keyId !== undefined) {
|
|
178
|
+
const candidate = secret[signature.keyId];
|
|
179
|
+
return candidate === undefined ? false : matches(candidate);
|
|
180
|
+
}
|
|
181
|
+
return Object.values(secret).some(matches);
|
|
182
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed builders for crabcrop operation strings (AP-24).
|
|
3
|
+
*
|
|
4
|
+
* `buildURL` takes operations verbatim, by design: the string
|
|
5
|
+
* is the spec. This module is the optional typed surface over
|
|
6
|
+
* it: each call validates its arguments against the server's
|
|
7
|
+
* grammar (the same bounds the explainer enforces) and returns
|
|
8
|
+
* the operation segment in its canonical spelling, so a typed
|
|
9
|
+
* pipeline and the equivalent hand-written string produce
|
|
10
|
+
* byte-identical URLs. Validation throws at build time, like
|
|
11
|
+
* `buildSrcSet` does for widths: a value the server would 400
|
|
12
|
+
* never gets signed.
|
|
13
|
+
*/
|
|
14
|
+
/** Output formats the `format(...)` filter accepts. */
|
|
15
|
+
export type ImageFormat = "jpeg" | "jpg" | "png" | "webp" | "jxl" | "avif" | "gif" | "mp4" | "webm";
|
|
16
|
+
/** Crop alignments; `center` is the bare `crop:WxH` spelling. */
|
|
17
|
+
export type CropAlign = "center" | "smart" | "face" | "top" | "bottom" | "left" | "right" | "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
|
18
|
+
/** Text-overlay anchor points. */
|
|
19
|
+
export type Anchor = "top-left" | "top-center" | "top-right" | "center-left" | "center" | "center-right" | "bottom-left" | "bottom-center" | "bottom-right";
|
|
20
|
+
/** Overlay blend modes. */
|
|
21
|
+
export type BlendMode = "normal" | "multiply" | "screen" | "overlay";
|
|
22
|
+
/** Mask shapes for the `mask(...)` filter. */
|
|
23
|
+
export type MaskShape = "circle" | "ellipse";
|
|
24
|
+
/** Channels the `curve(...)` filter remaps. */
|
|
25
|
+
export type CurveChannel = "rgb" | "r" | "g" | "b";
|
|
26
|
+
/**
|
|
27
|
+
* One side of a size: a pixel count (1..16383), 0 for
|
|
28
|
+
* proportional, a negative count to flip that axis, or
|
|
29
|
+
* `"orig"` for the source's own dimension.
|
|
30
|
+
*/
|
|
31
|
+
export type Dimension = number | "orig";
|
|
32
|
+
/** Operation segments, one typed call per segment kind. */
|
|
33
|
+
export declare const ops: {
|
|
34
|
+
/** `300x200`; each side a Dimension (0 proportional, negative flips). */
|
|
35
|
+
size(width: Dimension, height: Dimension): string;
|
|
36
|
+
/**
|
|
37
|
+
* `fit-in:300x200`; no flips (the server rejects them here).
|
|
38
|
+
* With `adaptive`, `fit-in:300x200:adaptive`: the box axes
|
|
39
|
+
* swap when the source orientation disagrees with the box's.
|
|
40
|
+
*/
|
|
41
|
+
fitIn(width: Dimension, height: Dimension, adaptive?: boolean): string;
|
|
42
|
+
/** `crop:300x200` or `crop:300x200:smart`; an exact pixel box. */
|
|
43
|
+
crop(width: number, height: number, align?: CropAlign): string;
|
|
44
|
+
/** `crop-rect:10x20:110x220` (left x top : right x bottom). */
|
|
45
|
+
cropRect(left: number, top: number, right: number, bottom: number): string;
|
|
46
|
+
/** `focal:10x20:110x220` (left x top : right x bottom); steers the next crop. */
|
|
47
|
+
focal(left: number, top: number, right: number, bottom: number): string;
|
|
48
|
+
/** `pad:300x200`, optionally `:blur` or a RRGGBB fill color. */
|
|
49
|
+
pad(width: number, height: number, fill?: "blur" | string): string;
|
|
50
|
+
/** `preset:thumb`; expands server-side. */
|
|
51
|
+
preset(name: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* `maxage:60`; this response's Cache-Control lifetime in
|
|
54
|
+
* seconds (0 to 31536000), overriding the default year for
|
|
55
|
+
* this request only. Header-only: it never enters the
|
|
56
|
+
* cache key, so spellings that differ only here share one
|
|
57
|
+
* stored image.
|
|
58
|
+
*/
|
|
59
|
+
maxage(seconds: number): string;
|
|
60
|
+
/**
|
|
61
|
+
* `debug:focal`; the smart-crop debug view. Place it
|
|
62
|
+
* before the crop it annotates: the response is a
|
|
63
|
+
* diagnostic image of the focal decision (focal point,
|
|
64
|
+
* focal rectangle, crop window drawn on the crop's
|
|
65
|
+
* canvas), never the transform result, and it is never
|
|
66
|
+
* cached (served with Cache-Control: no-store).
|
|
67
|
+
*/
|
|
68
|
+
debugFocal(): string;
|
|
69
|
+
/** Join filter calls into one `filters:` segment. */
|
|
70
|
+
filters(...calls: string[]): string;
|
|
71
|
+
};
|
|
72
|
+
/** Join operation segments into the operations string `buildURL` takes. */
|
|
73
|
+
export declare function joinOps(...segments: string[]): string;
|
|
74
|
+
/** Filter calls for `ops.filters(...)`; each returns its canonical spelling. */
|
|
75
|
+
export declare const filter: {
|
|
76
|
+
grayscale(): string;
|
|
77
|
+
invert(): string;
|
|
78
|
+
noUpscale(): string;
|
|
79
|
+
equalize(): string;
|
|
80
|
+
removeBg(): string;
|
|
81
|
+
keepProfile(): string;
|
|
82
|
+
sepia(): string;
|
|
83
|
+
blurFaces(): string;
|
|
84
|
+
pixelateFaces(): string;
|
|
85
|
+
/** Gaussian blur by sigma (positive). */
|
|
86
|
+
blur(sigma: number): string;
|
|
87
|
+
/** Unsharp-mask strength (positive). */
|
|
88
|
+
sharpen(amount: number): string;
|
|
89
|
+
/** Add to every channel: -255..255. */
|
|
90
|
+
brighten(amount: number): string;
|
|
91
|
+
/** Percent contrast: -100..100 (0 unchanged, -100 flat). */
|
|
92
|
+
contrast(percent: number): string;
|
|
93
|
+
/** Rotate the hue by integer degrees. */
|
|
94
|
+
hueRotate(degrees: number): string;
|
|
95
|
+
/** Saturation factor: 0..255 (0 grayscale, 1 identity). */
|
|
96
|
+
saturation(factor: number): string;
|
|
97
|
+
/** Per-channel offsets, each -255..255. */
|
|
98
|
+
rgb(r: number, g: number, b: number): string;
|
|
99
|
+
/**
|
|
100
|
+
* Tone curve: remap the channel through 2 to 16 [x, y]
|
|
101
|
+
* control points, each coordinate 0..255 with x strictly
|
|
102
|
+
* increasing. Between points the server interpolates
|
|
103
|
+
* linearly; inputs outside the point range clamp to the
|
|
104
|
+
* nearest end value.
|
|
105
|
+
*/
|
|
106
|
+
curve(channel: CurveChannel, points: [number, number][]): string;
|
|
107
|
+
/**
|
|
108
|
+
* Convolution: apply a caller-supplied kernel, 9 values
|
|
109
|
+
* (3x3) or 25 (5x5) in row-major order, each a plain
|
|
110
|
+
* decimal from -1024 to 1024. With normalize set the
|
|
111
|
+
* server scales the kernel by the sum of its entries
|
|
112
|
+
* first (a zero sum applies the kernel as written). The
|
|
113
|
+
* edges replicate. `normalize` is a boolean or the 0/1
|
|
114
|
+
* flag itself.
|
|
115
|
+
*/
|
|
116
|
+
convolution(values: number[], normalize?: boolean | 0 | 1): string;
|
|
117
|
+
/**
|
|
118
|
+
* Film grain: one noise value per pixel, added equally
|
|
119
|
+
* to all three channels (alpha untouched), the value
|
|
120
|
+
* uniform in plus or minus the given percent of full
|
|
121
|
+
* scale. The field is deterministic, seeded from the
|
|
122
|
+
* image content and the amount, so the same request
|
|
123
|
+
* always renders the same bytes; 0 is a no-op.
|
|
124
|
+
*/
|
|
125
|
+
noise(amount: number): string;
|
|
126
|
+
/**
|
|
127
|
+
* Rotate by an angle in degrees (decimals allowed, no
|
|
128
|
+
* exponent spelling); multiples of 90 are exact quarter
|
|
129
|
+
* turns on the server.
|
|
130
|
+
*/
|
|
131
|
+
rotate(angle: number): string;
|
|
132
|
+
/** Trim uniform borders; tolerance 0..255, default 10. */
|
|
133
|
+
trim(tolerance?: number): string;
|
|
134
|
+
/** Encoding quality 1..100, or `"auto"` for perceptual targeting. */
|
|
135
|
+
quality(value: number | "auto"): string;
|
|
136
|
+
format(format: ImageFormat): string;
|
|
137
|
+
/** The background color other operations pad with (RRGGBB). */
|
|
138
|
+
fill(color: string): string;
|
|
139
|
+
/** How much a face crop expands around the face: 0..3. */
|
|
140
|
+
facePad(amount: number): string;
|
|
141
|
+
/** AI upscale by the given factor. */
|
|
142
|
+
upscale(factor: 2 | 4): string;
|
|
143
|
+
/** Device pixel ratio the sizes are multiplied by. */
|
|
144
|
+
dpr(factor: 1 | 1.5 | 2 | 3): string;
|
|
145
|
+
/** Cap the output bytes, lowering quality to fit: at least 1. */
|
|
146
|
+
maxBytes(bytes: number): string;
|
|
147
|
+
/** Round the corners: one radius for all, or [tl, tr, br, bl]. */
|
|
148
|
+
radius(value: number | [number, number, number, number]): string;
|
|
149
|
+
/** Paint an opaque border inside the frame (RRGGBB). */
|
|
150
|
+
border(width: number, color: string): string;
|
|
151
|
+
mask(shape: MaskShape): string;
|
|
152
|
+
duotone(shadow: string, highlight: string): string;
|
|
153
|
+
monochrome(color: string): string;
|
|
154
|
+
/** Mosaic block size in pixels: 1..16383. */
|
|
155
|
+
pixelate(blockSize: number): string;
|
|
156
|
+
flip(axis: "h" | "v"): string;
|
|
157
|
+
/** Overlay a watermark image at (x, y), opacity 0..100. */
|
|
158
|
+
watermark(url: string, x: number, y: number, alpha: number): string;
|
|
159
|
+
/**
|
|
160
|
+
* Composite another image over the result at (x, y) with
|
|
161
|
+
* opacity 0..100 and a blend mode; `widthPct` optionally
|
|
162
|
+
* scales it to that percent of the base width (1..400).
|
|
163
|
+
*/
|
|
164
|
+
overlay(url: string, x: number, y: number, alpha: number, blend: BlendMode, widthPct?: number): string;
|
|
165
|
+
/**
|
|
166
|
+
* Draw a text overlay. Color is RRGGBB or RRGGBBAA; the
|
|
167
|
+
* canonical spelling is always eight lowercase hex digits.
|
|
168
|
+
*/
|
|
169
|
+
text(content: string, size: number, color: string, anchor: Anchor): string;
|
|
170
|
+
/** Blur every detected object of the named class. */
|
|
171
|
+
blurClass(name: string): string;
|
|
172
|
+
/** Pixelate every detected object of the named class. */
|
|
173
|
+
pixelateClass(name: string): string;
|
|
174
|
+
/** Blur the rectangle (x, y, w, h) in pixels. */
|
|
175
|
+
blurRegion(x: number, y: number, w: number, h: number): string;
|
|
176
|
+
/** Pixelate the rectangle (x, y, w, h) in pixels. */
|
|
177
|
+
pixelateRegion(x: number, y: number, w: number, h: number): string;
|
|
178
|
+
};
|