@radialapps/memories-vips-editor 0.1.2 → 0.1.5
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 +23 -15
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/math.d.ts +25 -0
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +38 -4
- package/dist/math.js.map +1 -1
- package/dist/pipeline.d.ts +9 -0
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +35 -23
- package/dist/pipeline.js.map +1 -1
- package/dist/shaders.d.ts +2 -3
- package/dist/shaders.d.ts.map +1 -1
- package/dist/shaders.js +9 -12
- package/dist/shaders.js.map +1 -1
- package/dist/smallPreview.d.ts +31 -0
- package/dist/smallPreview.d.ts.map +1 -0
- package/dist/smallPreview.js +118 -0
- package/dist/smallPreview.js.map +1 -0
- package/dist/transforms/crop.d.ts.map +1 -1
- package/dist/transforms/crop.js +7 -21
- package/dist/transforms/crop.js.map +1 -1
- package/dist/transforms/highlights.d.ts +17 -0
- package/dist/transforms/highlights.d.ts.map +1 -0
- package/dist/transforms/highlights.js +49 -0
- package/dist/transforms/highlights.js.map +1 -0
- package/dist/transforms/index.d.ts.map +1 -1
- package/dist/transforms/index.js +8 -2
- package/dist/transforms/index.js.map +1 -1
- package/dist/transforms/sepia.d.ts.map +1 -1
- package/dist/transforms/sepia.js +5 -4
- package/dist/transforms/sepia.js.map +1 -1
- package/dist/transforms/shadows.d.ts +17 -0
- package/dist/transforms/shadows.d.ts.map +1 -0
- package/dist/transforms/shadows.js +49 -0
- package/dist/transforms/shadows.js.map +1 -0
- package/dist/transforms/sharpen.js +1 -1
- package/dist/transforms/state.d.ts +1 -1
- package/dist/transforms/state.d.ts.map +1 -1
- package/dist/transforms/state.js +9 -9
- package/dist/transforms/state.js.map +1 -1
- package/dist/transforms/transform.d.ts +2 -2
- package/dist/transforms/transform.d.ts.map +1 -1
- package/dist/transforms/transform.js +4 -1
- package/dist/transforms/transform.js.map +1 -1
- package/dist/transforms/warmth.d.ts +17 -0
- package/dist/transforms/warmth.d.ts.map +1 -0
- package/dist/transforms/warmth.js +46 -0
- package/dist/transforms/warmth.js.map +1 -0
- package/dist/types.d.ts +5 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +4 -3
- package/src/math.ts +43 -4
- package/src/pipeline.ts +46 -22
- package/src/shaders.ts +9 -13
- package/src/smallPreview.ts +120 -0
- package/src/transforms/crop.ts +8 -17
- package/src/transforms/highlights.ts +67 -0
- package/src/transforms/index.ts +10 -4
- package/src/transforms/sepia.ts +5 -4
- package/src/transforms/shadows.ts +67 -0
- package/src/transforms/sharpen.ts +1 -1
- package/src/transforms/state.ts +7 -10
- package/src/transforms/transform.ts +8 -5
- package/src/transforms/warmth.ts +64 -0
- package/src/types.ts +9 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// shadows.ts — lift/crush the dark end, weighted by darkness.
|
|
2
|
+
//
|
|
3
|
+
// weight w = (1 - clamp(avg,0,1))^2 (1 in blacks, 0 in whites, quadratic
|
|
4
|
+
// falloff), then rgb += (amount * 0.5) * w. The 0.5 keeps the gray curve
|
|
5
|
+
// monotonic over the whole slider range (a full-strength lift would reverse
|
|
6
|
+
// deep shadows past mid-gray). Same formula in src/transforms/shadows.h
|
|
7
|
+
// and math.ts applyShadows, so the canvas preview is WYSIWYG.
|
|
8
|
+
import { clampf } from '../math.js';
|
|
9
|
+
import {
|
|
10
|
+
isNumInRange,
|
|
11
|
+
setFloatUniform,
|
|
12
|
+
type ParamControl,
|
|
13
|
+
type RenderContext,
|
|
14
|
+
type Target,
|
|
15
|
+
type Transform,
|
|
16
|
+
} from './transform.js';
|
|
17
|
+
|
|
18
|
+
export interface ShadowsEdit {
|
|
19
|
+
edit: 'shadows';
|
|
20
|
+
/** -1 (crush shadows) .. 1 (lift shadows). Default 0. */
|
|
21
|
+
amount?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type Full = Required<ShadowsEdit>;
|
|
25
|
+
|
|
26
|
+
const FRAG = `
|
|
27
|
+
precision mediump float;
|
|
28
|
+
uniform sampler2D texture;
|
|
29
|
+
uniform float amount;
|
|
30
|
+
varying vec2 texCoord;
|
|
31
|
+
void main() {
|
|
32
|
+
vec4 color = texture2D(texture, texCoord);
|
|
33
|
+
float avg = (color.r + color.g + color.b) / 3.0;
|
|
34
|
+
float l = clamp(avg, 0.0, 1.0);
|
|
35
|
+
float w = (1.0 - l) * (1.0 - l);
|
|
36
|
+
color.rgb += (amount * 0.5) * w;
|
|
37
|
+
gl_FragColor = color;
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
export class ShadowsTransform implements Transform<'shadows', ShadowsEdit> {
|
|
42
|
+
public readonly edit = 'shadows' as const;
|
|
43
|
+
public controls: ParamControl[] = [
|
|
44
|
+
{ key: 'amount', label: 'amount', min: -1, max: 1, step: 0.01, def: 0 },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
public defaults(): Full {
|
|
48
|
+
return { edit: 'shadows', amount: 0 };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public validate(params: ShadowsEdit): string[] {
|
|
52
|
+
if (params.amount !== undefined && !isNumInRange(params.amount, -1, 1))
|
|
53
|
+
return ['amount must be a number in [-1,1]'];
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public normalize(params: ShadowsEdit): Full {
|
|
58
|
+
return { edit: 'shadows', amount: clampf(-1, params.amount ?? 0, 1) };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public apply(ctx: RenderContext, from: Target, to: Target, params: Full): Target {
|
|
62
|
+
ctx.pass(FRAG, from, to, (gl, p) => {
|
|
63
|
+
setFloatUniform(gl, p, 'amount', params.amount);
|
|
64
|
+
});
|
|
65
|
+
return to;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -28,7 +28,7 @@ varying vec2 texCoord;
|
|
|
28
28
|
void main() {
|
|
29
29
|
vec4 blurred = texture2D(blurredTexture, texCoord);
|
|
30
30
|
vec4 original = texture2D(originalTexture, texCoord);
|
|
31
|
-
gl_FragColor = mix(blurred, original, 1.0 + strength);
|
|
31
|
+
gl_FragColor = vec4(mix(blurred.rgb, original.rgb, 1.0 + strength), original.a);
|
|
32
32
|
}
|
|
33
33
|
`;
|
|
34
34
|
|
package/src/transforms/state.ts
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
// transforms/state.ts — {version, edits} state validation/serialization.
|
|
2
|
-
import type
|
|
2
|
+
import { EDIT_STATE_VERSION, type EditState } from '../types.js';
|
|
3
3
|
import { normalizeEdit, validateEdit } from './index.js';
|
|
4
4
|
|
|
5
5
|
export function validateState(state: EditState): string[] {
|
|
6
|
-
const errs: string[] = [];
|
|
7
6
|
if (typeof state !== 'object' || state === null) return ['state must be an object'];
|
|
8
|
-
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
(state as EditState).edits.forEach((edit, i) => {
|
|
7
|
+
const errs: string[] = [];
|
|
8
|
+
if (state.version !== EDIT_STATE_VERSION) errs.push(`version must be ${EDIT_STATE_VERSION}`);
|
|
9
|
+
if (!Array.isArray(state.edits)) return [...errs, 'edits must be an array'];
|
|
10
|
+
state.edits.forEach((edit, i) => {
|
|
14
11
|
for (const e of validateEdit(edit)) errs.push(`edits[${i}]: ${e}`);
|
|
15
12
|
});
|
|
16
13
|
return errs;
|
|
17
14
|
}
|
|
18
15
|
|
|
19
16
|
export function stateToJSON(state: EditState): string {
|
|
20
|
-
return JSON.stringify({ version:
|
|
17
|
+
return JSON.stringify({ version: EDIT_STATE_VERSION, edits: state.edits.map(normalizeEdit) });
|
|
21
18
|
}
|
|
22
19
|
|
|
23
20
|
/** Parse + normalize; throws on invalid shape. */
|
|
@@ -25,5 +22,5 @@ export function stateFromJSON(json: string): EditState {
|
|
|
25
22
|
const raw = JSON.parse(json) as EditState;
|
|
26
23
|
const errs = validateState(raw);
|
|
27
24
|
if (errs.length > 0) throw new Error(`invalid edit state: ${errs.join('; ')}`);
|
|
28
|
-
return { version:
|
|
25
|
+
return { version: EDIT_STATE_VERSION, edits: raw.edits.map(normalizeEdit) };
|
|
29
26
|
}
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// A transform is one named pixel edit (brightnessContrast, sepia, ...).
|
|
4
4
|
// The pipeline (pipeline.ts) stays generic: it looks the transform up by
|
|
5
5
|
// `edit.edit` in the registry (transforms/index.ts), normalizes params
|
|
6
|
-
// through it, and calls apply(). The VIPS side mirrors this shape with
|
|
7
|
-
// `
|
|
6
|
+
// through it, and calls apply(). The VIPS side mirrors this shape with a
|
|
7
|
+
// `Transform` base class plus one header per transform (src/transforms/).
|
|
8
8
|
import type { EditName } from '../types.js';
|
|
9
9
|
|
|
10
10
|
export interface Target {
|
|
@@ -92,10 +92,13 @@ export function setFloatUniform(
|
|
|
92
92
|
if (u) gl.uniform1f(u, value);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
/**
|
|
95
|
+
/** Unknown runtime names (including Object.prototype keys) yield undefined. */
|
|
96
96
|
export function lookup(
|
|
97
97
|
registry: Record<EditName, Transform<string, object>>,
|
|
98
|
-
name:
|
|
98
|
+
name: unknown,
|
|
99
99
|
): Transform<string, object> | undefined {
|
|
100
|
-
|
|
100
|
+
if (typeof name !== 'string' || !Object.prototype.hasOwnProperty.call(registry, name)) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
return registry[name as EditName];
|
|
101
104
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// warmth.ts — white-balance-style temperature shift (red/blue gains).
|
|
2
|
+
//
|
|
3
|
+
// r *= 1 + 0.3*amount, b *= 1 - 0.3*amount, g unchanged: +1 is warm
|
|
4
|
+
// (amber), -1 is cool (blue). Blacks stay black (multiplicative, like a
|
|
5
|
+
// white-balance gain). Same factors in src/transforms/warmth.h and
|
|
6
|
+
// math.ts warmthGains, so the canvas preview is WYSIWYG.
|
|
7
|
+
import { clampf } from '../math.js';
|
|
8
|
+
import {
|
|
9
|
+
isNumInRange,
|
|
10
|
+
setFloatUniform,
|
|
11
|
+
type ParamControl,
|
|
12
|
+
type RenderContext,
|
|
13
|
+
type Target,
|
|
14
|
+
type Transform,
|
|
15
|
+
} from './transform.js';
|
|
16
|
+
|
|
17
|
+
export interface WarmthEdit {
|
|
18
|
+
edit: 'warmth';
|
|
19
|
+
/** -1 (cool) .. 1 (warm). Default 0. */
|
|
20
|
+
amount?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type Full = Required<WarmthEdit>;
|
|
24
|
+
|
|
25
|
+
const FRAG = `
|
|
26
|
+
precision mediump float;
|
|
27
|
+
uniform sampler2D texture;
|
|
28
|
+
uniform float amount;
|
|
29
|
+
varying vec2 texCoord;
|
|
30
|
+
void main() {
|
|
31
|
+
vec4 color = texture2D(texture, texCoord);
|
|
32
|
+
color.r *= 1.0 + amount * 0.3;
|
|
33
|
+
color.b *= 1.0 - amount * 0.3;
|
|
34
|
+
gl_FragColor = color;
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
export class WarmthTransform implements Transform<'warmth', WarmthEdit> {
|
|
39
|
+
public readonly edit = 'warmth' as const;
|
|
40
|
+
public controls: ParamControl[] = [
|
|
41
|
+
{ key: 'amount', label: 'amount', min: -1, max: 1, step: 0.01, def: 0 },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
public defaults(): Full {
|
|
45
|
+
return { edit: 'warmth', amount: 0 };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public validate(params: WarmthEdit): string[] {
|
|
49
|
+
if (params.amount !== undefined && !isNumInRange(params.amount, -1, 1))
|
|
50
|
+
return ['amount must be a number in [-1,1]'];
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public normalize(params: WarmthEdit): Full {
|
|
55
|
+
return { edit: 'warmth', amount: clampf(-1, params.amount ?? 0, 1) };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public apply(ctx: RenderContext, from: Target, to: Target, params: Full): Target {
|
|
59
|
+
ctx.pass(FRAG, from, to, (gl, p) => {
|
|
60
|
+
setFloatUniform(gl, p, 'amount', params.amount);
|
|
61
|
+
});
|
|
62
|
+
return to;
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -10,13 +10,16 @@ import type { BrightnessContrastEdit } from './transforms/brightnessContrast.js'
|
|
|
10
10
|
import type { CropEdit } from './transforms/crop.js';
|
|
11
11
|
import type { ExposureEdit } from './transforms/exposure.js';
|
|
12
12
|
import type { GrayscaleEdit } from './transforms/grayscale.js';
|
|
13
|
+
import type { HighlightsEdit } from './transforms/highlights.js';
|
|
13
14
|
import type { HueSaturationEdit } from './transforms/hueSaturation.js';
|
|
14
15
|
import type { InvertEdit } from './transforms/invert.js';
|
|
15
16
|
import type { RotateEdit } from './transforms/rotate.js';
|
|
16
17
|
import type { SepiaEdit } from './transforms/sepia.js';
|
|
18
|
+
import type { ShadowsEdit } from './transforms/shadows.js';
|
|
17
19
|
import type { SharpenEdit } from './transforms/sharpen.js';
|
|
18
20
|
import type { VibranceEdit } from './transforms/vibrance.js';
|
|
19
21
|
import type { VignetteEdit } from './transforms/vignette.js';
|
|
22
|
+
import type { WarmthEdit } from './transforms/warmth.js';
|
|
20
23
|
|
|
21
24
|
export type EditName =
|
|
22
25
|
| 'brightnessContrast'
|
|
@@ -27,6 +30,9 @@ export type EditName =
|
|
|
27
30
|
| 'blur'
|
|
28
31
|
| 'sharpen'
|
|
29
32
|
| 'vignette'
|
|
33
|
+
| 'shadows'
|
|
34
|
+
| 'highlights'
|
|
35
|
+
| 'warmth'
|
|
30
36
|
| 'invert'
|
|
31
37
|
| 'grayscale'
|
|
32
38
|
| 'crop'
|
|
@@ -41,6 +47,9 @@ export type Edit =
|
|
|
41
47
|
| BlurEdit
|
|
42
48
|
| SharpenEdit
|
|
43
49
|
| VignetteEdit
|
|
50
|
+
| ShadowsEdit
|
|
51
|
+
| HighlightsEdit
|
|
52
|
+
| WarmthEdit
|
|
44
53
|
| InvertEdit
|
|
45
54
|
| GrayscaleEdit
|
|
46
55
|
| CropEdit
|