@remotion/effects 4.0.507 → 4.0.509
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/color-correction-shader-utils.d.ts +4 -0
- package/dist/color-correction.d.ts +27 -0
- package/dist/esm/color-correction.mjs +560 -0
- package/dist/esm/exposure.mjs +372 -0
- package/dist/esm/levels.mjs +347 -0
- package/dist/esm/shadows-highlights.mjs +386 -0
- package/dist/esm/vibrance.mjs +363 -0
- package/dist/esm/white-balance.mjs +384 -0
- package/dist/exposure.d.ts +7 -0
- package/dist/levels.d.ts +11 -0
- package/dist/shadows-highlights.d.ts +9 -0
- package/dist/vibrance.d.ts +7 -0
- package/dist/white-balance.d.ts +9 -0
- package/package.json +51 -3
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const COLOR_SPACE_GLSL = "\nvec3 srgbToLinear(vec3 color) {\n\tvec3 lower = color / 12.92;\n\tvec3 upper = pow((color + 0.055) / 1.055, vec3(2.4));\n\treturn mix(lower, upper, step(vec3(0.04045), color));\n}\n\nvec3 linearToSrgb(vec3 color) {\n\tvec3 nonNegative = max(color, vec3(0.0));\n\tvec3 lower = nonNegative * 12.92;\n\tvec3 upper = 1.055 * pow(nonNegative, vec3(1.0 / 2.4)) - 0.055;\n\treturn mix(lower, upper, step(vec3(0.0031308), nonNegative));\n}\n";
|
|
2
|
+
export declare const WHITE_BALANCE_GLSL = "\nvec3 applyWhiteBalanceLinear(vec3 linear, float temperature, float tint) {\n\tvec3 temperatureOffset = vec3(0.3, 0.0, -0.3) * temperature;\n\tvec3 tintOffset = vec3(0.15, -0.3, 0.15) * tint;\n\tvec3 gains = exp2(temperatureOffset + tintOffset);\n\tfloat luminanceGain = dot(gains, vec3(0.2126, 0.7152, 0.0722));\n\treturn linear * gains / luminanceGain;\n}\n";
|
|
3
|
+
export declare const SHADOWS_HIGHLIGHTS_GLSL = "\nfloat getShadowsHighlightsStops(\n\tvec3 color,\n\tfloat shadows,\n\tfloat highlights\n) {\n\tfloat luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));\n\tfloat shadowWeight = 1.0 - smoothstep(0.0, 0.6, luminance);\n\tfloat highlightWeight = smoothstep(0.4, 1.0, luminance);\n\tshadowWeight *= shadowWeight;\n\thighlightWeight *= highlightWeight;\n\treturn shadows * shadowWeight + highlights * highlightWeight;\n}\n";
|
|
4
|
+
export declare const VIBRANCE_GLSL = "\nvec3 applyVibrance(vec3 color, float amount) {\n\tfloat maximum = max(max(color.r, color.g), color.b);\n\tfloat minimum = min(min(color.r, color.g), color.b);\n\tfloat lightness = (maximum + minimum) * 0.5;\n\tfloat chroma = maximum - minimum;\n\tfloat saturationDenominator = 1.0 - abs(2.0 * lightness - 1.0);\n\tfloat saturation = saturationDenominator <= 0.0\n\t\t? 0.0\n\t\t: chroma / saturationDenominator;\n\n\tif (saturation <= 0.000001) {\n\t\treturn color;\n\t}\n\n\tfloat targetSaturation = amount >= 0.0\n\t\t? saturation + amount * (1.0 - saturation)\n\t\t: saturation * (1.0 + amount);\n\treturn clamp(\n\t\tvec3(lightness) +\n\t\t\t(color - vec3(lightness)) * (targetSaturation / saturation),\n\t\t0.0,\n\t\t1.0\n\t);\n}\n";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type ColorCorrectionParams = {
|
|
2
|
+
/** Exposure adjustment in stops, from `-5` to `5`. Defaults to `0`. */
|
|
3
|
+
readonly exposure?: number;
|
|
4
|
+
/** Contrast multiplier. Defaults to `1`. */
|
|
5
|
+
readonly contrast?: number;
|
|
6
|
+
/** Contrast pivot from `0` to `1`. Defaults to `0.5`. */
|
|
7
|
+
readonly pivot?: number;
|
|
8
|
+
/** Shadow adjustment from `-1` to `1`. Defaults to `0`. */
|
|
9
|
+
readonly shadows?: number;
|
|
10
|
+
/** Highlight adjustment from `-1` to `1`. Defaults to `0`. */
|
|
11
|
+
readonly highlights?: number;
|
|
12
|
+
/** White-region adjustment from `-1` to `1`. Defaults to `0`. */
|
|
13
|
+
readonly whites?: number;
|
|
14
|
+
/** Black-region adjustment from `-1` to `1`. Defaults to `0`. */
|
|
15
|
+
readonly blacks?: number;
|
|
16
|
+
/** Blue-to-amber temperature adjustment from `-1` to `1`. Defaults to `0`. */
|
|
17
|
+
readonly temperature?: number;
|
|
18
|
+
/** Green-to-magenta tint adjustment from `-1` to `1`. Defaults to `0`. */
|
|
19
|
+
readonly tint?: number;
|
|
20
|
+
/** Saturation multiplier. Defaults to `1`. */
|
|
21
|
+
readonly saturation?: number;
|
|
22
|
+
/** Vibrance adjustment from `-1` to `1`. Defaults to `0`. */
|
|
23
|
+
readonly vibrance?: number;
|
|
24
|
+
};
|
|
25
|
+
export declare const colorCorrection: (params?: (ColorCorrectionParams & {
|
|
26
|
+
readonly disabled?: boolean | undefined;
|
|
27
|
+
}) | undefined) => import("remotion").EffectDescriptor<unknown>;
|
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
// src/color-correction.ts
|
|
2
|
+
import { Internals } from "remotion";
|
|
3
|
+
|
|
4
|
+
// src/color-correction-shader-utils.ts
|
|
5
|
+
var COLOR_SPACE_GLSL = `
|
|
6
|
+
vec3 srgbToLinear(vec3 color) {
|
|
7
|
+
vec3 lower = color / 12.92;
|
|
8
|
+
vec3 upper = pow((color + 0.055) / 1.055, vec3(2.4));
|
|
9
|
+
return mix(lower, upper, step(vec3(0.04045), color));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
vec3 linearToSrgb(vec3 color) {
|
|
13
|
+
vec3 nonNegative = max(color, vec3(0.0));
|
|
14
|
+
vec3 lower = nonNegative * 12.92;
|
|
15
|
+
vec3 upper = 1.055 * pow(nonNegative, vec3(1.0 / 2.4)) - 0.055;
|
|
16
|
+
return mix(lower, upper, step(vec3(0.0031308), nonNegative));
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
var WHITE_BALANCE_GLSL = `
|
|
20
|
+
vec3 applyWhiteBalanceLinear(vec3 linear, float temperature, float tint) {
|
|
21
|
+
vec3 temperatureOffset = vec3(0.3, 0.0, -0.3) * temperature;
|
|
22
|
+
vec3 tintOffset = vec3(0.15, -0.3, 0.15) * tint;
|
|
23
|
+
vec3 gains = exp2(temperatureOffset + tintOffset);
|
|
24
|
+
float luminanceGain = dot(gains, vec3(0.2126, 0.7152, 0.0722));
|
|
25
|
+
return linear * gains / luminanceGain;
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
var SHADOWS_HIGHLIGHTS_GLSL = `
|
|
29
|
+
float getShadowsHighlightsStops(
|
|
30
|
+
vec3 color,
|
|
31
|
+
float shadows,
|
|
32
|
+
float highlights
|
|
33
|
+
) {
|
|
34
|
+
float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));
|
|
35
|
+
float shadowWeight = 1.0 - smoothstep(0.0, 0.6, luminance);
|
|
36
|
+
float highlightWeight = smoothstep(0.4, 1.0, luminance);
|
|
37
|
+
shadowWeight *= shadowWeight;
|
|
38
|
+
highlightWeight *= highlightWeight;
|
|
39
|
+
return shadows * shadowWeight + highlights * highlightWeight;
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
var VIBRANCE_GLSL = `
|
|
43
|
+
vec3 applyVibrance(vec3 color, float amount) {
|
|
44
|
+
float maximum = max(max(color.r, color.g), color.b);
|
|
45
|
+
float minimum = min(min(color.r, color.g), color.b);
|
|
46
|
+
float lightness = (maximum + minimum) * 0.5;
|
|
47
|
+
float chroma = maximum - minimum;
|
|
48
|
+
float saturationDenominator = 1.0 - abs(2.0 * lightness - 1.0);
|
|
49
|
+
float saturation = saturationDenominator <= 0.0
|
|
50
|
+
? 0.0
|
|
51
|
+
: chroma / saturationDenominator;
|
|
52
|
+
|
|
53
|
+
if (saturation <= 0.000001) {
|
|
54
|
+
return color;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
float targetSaturation = amount >= 0.0
|
|
58
|
+
? saturation + amount * (1.0 - saturation)
|
|
59
|
+
: saturation * (1.0 + amount);
|
|
60
|
+
return clamp(
|
|
61
|
+
vec3(lightness) +
|
|
62
|
+
(color - vec3(lightness)) * (targetSaturation / saturation),
|
|
63
|
+
0.0,
|
|
64
|
+
1.0
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
// src/validate-effect-param.ts
|
|
70
|
+
var assertEffectParamsObject = (params, effectLabel) => {
|
|
71
|
+
if (params === null || typeof params !== "object") {
|
|
72
|
+
throw new TypeError(`${effectLabel} effect requires a parameters object, but got ${JSON.stringify(params)}`);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var assertRequiredFiniteNumber = (value, name) => {
|
|
76
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
77
|
+
throw new TypeError(`"${name}" must be a finite number, but got ${JSON.stringify(value)}`);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var assertRequiredColor = (value, name) => {
|
|
81
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
82
|
+
throw new TypeError(`"${name}" must be a non-empty string, but got ${JSON.stringify(value)}`);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var assertOptionalColor = (value, name) => {
|
|
86
|
+
if (value === undefined) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
assertRequiredColor(value, name);
|
|
90
|
+
};
|
|
91
|
+
var assertOptionalBoolean = (value, name) => {
|
|
92
|
+
if (value === undefined) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (typeof value !== "boolean") {
|
|
96
|
+
throw new TypeError(`"${name}" must be a boolean, but got ${JSON.stringify(value)}`);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// src/color-utils.ts
|
|
101
|
+
var DEFAULT_AMOUNT = 1;
|
|
102
|
+
var DEFAULT_BRIGHTNESS_AMOUNT = 0;
|
|
103
|
+
var DEFAULT_HUE_DEGREES = 0;
|
|
104
|
+
var colorAmountSchema = {
|
|
105
|
+
type: "number",
|
|
106
|
+
min: 0,
|
|
107
|
+
max: 1,
|
|
108
|
+
step: 0.01,
|
|
109
|
+
default: DEFAULT_AMOUNT,
|
|
110
|
+
description: "Amount",
|
|
111
|
+
hiddenFromList: false
|
|
112
|
+
};
|
|
113
|
+
var colorMultiplierSchema = {
|
|
114
|
+
type: "number",
|
|
115
|
+
min: 0,
|
|
116
|
+
step: 0.01,
|
|
117
|
+
default: DEFAULT_AMOUNT,
|
|
118
|
+
description: "Amount",
|
|
119
|
+
hiddenFromList: false
|
|
120
|
+
};
|
|
121
|
+
var brightnessAmountSchema = {
|
|
122
|
+
type: "number",
|
|
123
|
+
min: -1,
|
|
124
|
+
max: 1,
|
|
125
|
+
step: 0.01,
|
|
126
|
+
default: DEFAULT_BRIGHTNESS_AMOUNT,
|
|
127
|
+
description: "Amount",
|
|
128
|
+
hiddenFromList: false
|
|
129
|
+
};
|
|
130
|
+
var hueDegreesSchema = {
|
|
131
|
+
type: "rotation-degrees",
|
|
132
|
+
step: 1,
|
|
133
|
+
default: DEFAULT_HUE_DEGREES,
|
|
134
|
+
description: "Degrees"
|
|
135
|
+
};
|
|
136
|
+
var assertOptionalFiniteNumber = (value, name) => {
|
|
137
|
+
if (value === undefined) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
assertRequiredFiniteNumber(value, name);
|
|
141
|
+
};
|
|
142
|
+
var validateUnitInterval = (value, name) => {
|
|
143
|
+
if (value < 0) {
|
|
144
|
+
throw new TypeError(`"${name}" must be >= 0, but got ${JSON.stringify(value)}`);
|
|
145
|
+
}
|
|
146
|
+
if (value > 1) {
|
|
147
|
+
throw new TypeError(`"${name}" must be <= 1, but got ${JSON.stringify(value)}`);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
var validateNonNegative = (value, name) => {
|
|
151
|
+
if (value < 0) {
|
|
152
|
+
throw new TypeError(`"${name}" must be >= 0, but got ${JSON.stringify(value)}`);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
var validateSignedUnitInterval = (value, name) => {
|
|
156
|
+
if (value < -1) {
|
|
157
|
+
throw new TypeError(`"${name}" must be >= -1, but got ${JSON.stringify(value)}`);
|
|
158
|
+
}
|
|
159
|
+
if (value > 1) {
|
|
160
|
+
throw new TypeError(`"${name}" must be <= 1, but got ${JSON.stringify(value)}`);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
var clampColorChannel = (value) => {
|
|
164
|
+
return Math.max(0, Math.min(255, value));
|
|
165
|
+
};
|
|
166
|
+
var parseColorRgba = (ctx, color) => {
|
|
167
|
+
ctx.clearRect(0, 0, 1, 1);
|
|
168
|
+
ctx.fillStyle = color;
|
|
169
|
+
ctx.fillRect(0, 0, 1, 1);
|
|
170
|
+
const { data } = ctx.getImageData(0, 0, 1, 1);
|
|
171
|
+
return [data[0], data[1], data[2], data[3]];
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// src/color-correction.ts
|
|
175
|
+
var { createEffect, createWebGL2ContextError } = Internals;
|
|
176
|
+
var DEFAULT_EXPOSURE = 0;
|
|
177
|
+
var DEFAULT_CONTRAST = 1;
|
|
178
|
+
var DEFAULT_PIVOT = 0.5;
|
|
179
|
+
var DEFAULT_SHADOWS = 0;
|
|
180
|
+
var DEFAULT_HIGHLIGHTS = 0;
|
|
181
|
+
var DEFAULT_WHITES = 0;
|
|
182
|
+
var DEFAULT_BLACKS = 0;
|
|
183
|
+
var DEFAULT_TEMPERATURE = 0;
|
|
184
|
+
var DEFAULT_TINT = 0;
|
|
185
|
+
var DEFAULT_SATURATION = 1;
|
|
186
|
+
var DEFAULT_VIBRANCE = 0;
|
|
187
|
+
var MIN_EXPOSURE = -5;
|
|
188
|
+
var MAX_EXPOSURE = 5;
|
|
189
|
+
var signedAdjustmentSchema = (description) => ({
|
|
190
|
+
type: "number",
|
|
191
|
+
min: -1,
|
|
192
|
+
max: 1,
|
|
193
|
+
step: 0.01,
|
|
194
|
+
default: 0,
|
|
195
|
+
description,
|
|
196
|
+
hiddenFromList: false
|
|
197
|
+
});
|
|
198
|
+
var colorCorrectionSchema = {
|
|
199
|
+
exposure: {
|
|
200
|
+
type: "number",
|
|
201
|
+
min: MIN_EXPOSURE,
|
|
202
|
+
max: MAX_EXPOSURE,
|
|
203
|
+
step: 0.1,
|
|
204
|
+
default: DEFAULT_EXPOSURE,
|
|
205
|
+
description: "Exposure",
|
|
206
|
+
hiddenFromList: false
|
|
207
|
+
},
|
|
208
|
+
contrast: {
|
|
209
|
+
type: "number",
|
|
210
|
+
min: 0,
|
|
211
|
+
max: 3,
|
|
212
|
+
step: 0.01,
|
|
213
|
+
default: DEFAULT_CONTRAST,
|
|
214
|
+
description: "Contrast",
|
|
215
|
+
hiddenFromList: false
|
|
216
|
+
},
|
|
217
|
+
pivot: {
|
|
218
|
+
type: "number",
|
|
219
|
+
min: 0,
|
|
220
|
+
max: 1,
|
|
221
|
+
step: 0.01,
|
|
222
|
+
default: DEFAULT_PIVOT,
|
|
223
|
+
description: "Contrast pivot",
|
|
224
|
+
hiddenFromList: false
|
|
225
|
+
},
|
|
226
|
+
shadows: signedAdjustmentSchema("Shadows"),
|
|
227
|
+
highlights: signedAdjustmentSchema("Highlights"),
|
|
228
|
+
whites: signedAdjustmentSchema("Whites"),
|
|
229
|
+
blacks: signedAdjustmentSchema("Blacks"),
|
|
230
|
+
temperature: signedAdjustmentSchema("Temperature"),
|
|
231
|
+
tint: signedAdjustmentSchema("Tint"),
|
|
232
|
+
saturation: {
|
|
233
|
+
type: "number",
|
|
234
|
+
min: 0,
|
|
235
|
+
max: 3,
|
|
236
|
+
step: 0.01,
|
|
237
|
+
default: DEFAULT_SATURATION,
|
|
238
|
+
description: "Saturation",
|
|
239
|
+
hiddenFromList: false
|
|
240
|
+
},
|
|
241
|
+
vibrance: signedAdjustmentSchema("Vibrance")
|
|
242
|
+
};
|
|
243
|
+
var resolve = (params) => ({
|
|
244
|
+
exposure: params.exposure ?? DEFAULT_EXPOSURE,
|
|
245
|
+
contrast: params.contrast ?? DEFAULT_CONTRAST,
|
|
246
|
+
pivot: params.pivot ?? DEFAULT_PIVOT,
|
|
247
|
+
shadows: params.shadows ?? DEFAULT_SHADOWS,
|
|
248
|
+
highlights: params.highlights ?? DEFAULT_HIGHLIGHTS,
|
|
249
|
+
whites: params.whites ?? DEFAULT_WHITES,
|
|
250
|
+
blacks: params.blacks ?? DEFAULT_BLACKS,
|
|
251
|
+
temperature: params.temperature ?? DEFAULT_TEMPERATURE,
|
|
252
|
+
tint: params.tint ?? DEFAULT_TINT,
|
|
253
|
+
saturation: params.saturation ?? DEFAULT_SATURATION,
|
|
254
|
+
vibrance: params.vibrance ?? DEFAULT_VIBRANCE
|
|
255
|
+
});
|
|
256
|
+
var validateColorCorrectionParams = (params) => {
|
|
257
|
+
assertEffectParamsObject(params, "Color correction");
|
|
258
|
+
assertOptionalFiniteNumber(params.exposure, "exposure");
|
|
259
|
+
assertOptionalFiniteNumber(params.contrast, "contrast");
|
|
260
|
+
assertOptionalFiniteNumber(params.pivot, "pivot");
|
|
261
|
+
assertOptionalFiniteNumber(params.shadows, "shadows");
|
|
262
|
+
assertOptionalFiniteNumber(params.highlights, "highlights");
|
|
263
|
+
assertOptionalFiniteNumber(params.whites, "whites");
|
|
264
|
+
assertOptionalFiniteNumber(params.blacks, "blacks");
|
|
265
|
+
assertOptionalFiniteNumber(params.temperature, "temperature");
|
|
266
|
+
assertOptionalFiniteNumber(params.tint, "tint");
|
|
267
|
+
assertOptionalFiniteNumber(params.saturation, "saturation");
|
|
268
|
+
assertOptionalFiniteNumber(params.vibrance, "vibrance");
|
|
269
|
+
const resolved = resolve(params);
|
|
270
|
+
if (resolved.exposure < MIN_EXPOSURE) {
|
|
271
|
+
throw new TypeError(`"exposure" must be >= ${MIN_EXPOSURE}, but got ${JSON.stringify(resolved.exposure)}`);
|
|
272
|
+
}
|
|
273
|
+
if (resolved.exposure > MAX_EXPOSURE) {
|
|
274
|
+
throw new TypeError(`"exposure" must be <= ${MAX_EXPOSURE}, but got ${JSON.stringify(resolved.exposure)}`);
|
|
275
|
+
}
|
|
276
|
+
validateNonNegative(resolved.contrast, "contrast");
|
|
277
|
+
validateUnitInterval(resolved.pivot, "pivot");
|
|
278
|
+
validateSignedUnitInterval(resolved.shadows, "shadows");
|
|
279
|
+
validateSignedUnitInterval(resolved.highlights, "highlights");
|
|
280
|
+
validateSignedUnitInterval(resolved.whites, "whites");
|
|
281
|
+
validateSignedUnitInterval(resolved.blacks, "blacks");
|
|
282
|
+
validateSignedUnitInterval(resolved.temperature, "temperature");
|
|
283
|
+
validateSignedUnitInterval(resolved.tint, "tint");
|
|
284
|
+
validateNonNegative(resolved.saturation, "saturation");
|
|
285
|
+
validateSignedUnitInterval(resolved.vibrance, "vibrance");
|
|
286
|
+
};
|
|
287
|
+
var VERTEX_SHADER = `#version 300 es
|
|
288
|
+
in vec2 aPos;
|
|
289
|
+
in vec2 aUv;
|
|
290
|
+
out vec2 vUv;
|
|
291
|
+
|
|
292
|
+
void main() {
|
|
293
|
+
vUv = aUv;
|
|
294
|
+
gl_Position = vec4(aPos, 0.0, 1.0);
|
|
295
|
+
}
|
|
296
|
+
`;
|
|
297
|
+
var FRAGMENT_SHADER = `#version 300 es
|
|
298
|
+
precision highp float;
|
|
299
|
+
|
|
300
|
+
in vec2 vUv;
|
|
301
|
+
out vec4 fragColor;
|
|
302
|
+
|
|
303
|
+
uniform sampler2D uSource;
|
|
304
|
+
uniform float uExposure;
|
|
305
|
+
uniform float uContrast;
|
|
306
|
+
uniform float uPivot;
|
|
307
|
+
uniform float uShadows;
|
|
308
|
+
uniform float uHighlights;
|
|
309
|
+
uniform float uWhites;
|
|
310
|
+
uniform float uBlacks;
|
|
311
|
+
uniform float uTemperature;
|
|
312
|
+
uniform float uTint;
|
|
313
|
+
uniform float uSaturation;
|
|
314
|
+
uniform float uVibrance;
|
|
315
|
+
|
|
316
|
+
${COLOR_SPACE_GLSL}
|
|
317
|
+
${WHITE_BALANCE_GLSL}
|
|
318
|
+
${SHADOWS_HIGHLIGHTS_GLSL}
|
|
319
|
+
${VIBRANCE_GLSL}
|
|
320
|
+
|
|
321
|
+
vec3 applyEndpointTones(vec3 color, float blacks, float whites) {
|
|
322
|
+
float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));
|
|
323
|
+
float blackWeight = 1.0 - smoothstep(0.0, 0.35, luminance);
|
|
324
|
+
float whiteWeight = smoothstep(0.65, 1.0, luminance);
|
|
325
|
+
blackWeight *= blackWeight;
|
|
326
|
+
whiteWeight *= whiteWeight;
|
|
327
|
+
|
|
328
|
+
// Moving tones toward their named endpoint uses the full adjustment.
|
|
329
|
+
// Moving them away is damped to avoid flattening the image too aggressively.
|
|
330
|
+
if (blacks >= 0.0) {
|
|
331
|
+
color += (vec3(1.0) - color) * blacks * blackWeight * 0.2;
|
|
332
|
+
} else {
|
|
333
|
+
color *= 1.0 + blacks * blackWeight;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (whites >= 0.0) {
|
|
337
|
+
color += (vec3(1.0) - color) * whites * whiteWeight;
|
|
338
|
+
} else {
|
|
339
|
+
color *= 1.0 + whites * whiteWeight * 0.2;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return clamp(color, 0.0, 1.0);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
void main() {
|
|
346
|
+
vec4 sourceColor = texture(uSource, vUv);
|
|
347
|
+
float alpha = sourceColor.a;
|
|
348
|
+
|
|
349
|
+
if (alpha <= 0.0) {
|
|
350
|
+
fragColor = vec4(0.0);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (
|
|
355
|
+
uExposure == 0.0 &&
|
|
356
|
+
uContrast == 1.0 &&
|
|
357
|
+
uPivot == 0.5 &&
|
|
358
|
+
uShadows == 0.0 &&
|
|
359
|
+
uHighlights == 0.0 &&
|
|
360
|
+
uWhites == 0.0 &&
|
|
361
|
+
uBlacks == 0.0 &&
|
|
362
|
+
uTemperature == 0.0 &&
|
|
363
|
+
uTint == 0.0 &&
|
|
364
|
+
uSaturation == 1.0 &&
|
|
365
|
+
uVibrance == 0.0
|
|
366
|
+
) {
|
|
367
|
+
fragColor = sourceColor;
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
vec3 color = sourceColor.rgb / alpha;
|
|
372
|
+
vec3 linear = srgbToLinear(color) * exp2(uExposure);
|
|
373
|
+
linear = applyWhiteBalanceLinear(linear, uTemperature, uTint);
|
|
374
|
+
color = linearToSrgb(linear);
|
|
375
|
+
|
|
376
|
+
float tonalStops = getShadowsHighlightsStops(
|
|
377
|
+
color,
|
|
378
|
+
uShadows,
|
|
379
|
+
uHighlights
|
|
380
|
+
);
|
|
381
|
+
linear = srgbToLinear(color) * exp2(tonalStops);
|
|
382
|
+
color = linearToSrgb(linear);
|
|
383
|
+
color = applyEndpointTones(color, uBlacks, uWhites);
|
|
384
|
+
color = clamp((color - vec3(uPivot)) * uContrast + vec3(uPivot), 0.0, 1.0);
|
|
385
|
+
|
|
386
|
+
float luminance = dot(color, vec3(0.213, 0.715, 0.072));
|
|
387
|
+
color = clamp(
|
|
388
|
+
vec3(luminance) + (color - vec3(luminance)) * uSaturation,
|
|
389
|
+
0.0,
|
|
390
|
+
1.0
|
|
391
|
+
);
|
|
392
|
+
color = applyVibrance(color, uVibrance);
|
|
393
|
+
|
|
394
|
+
fragColor = vec4(color * alpha, alpha);
|
|
395
|
+
}
|
|
396
|
+
`;
|
|
397
|
+
var compileShader = (gl, type, source) => {
|
|
398
|
+
const shader = gl.createShader(type);
|
|
399
|
+
if (!shader) {
|
|
400
|
+
throw new Error("Failed to create color correction shader");
|
|
401
|
+
}
|
|
402
|
+
gl.shaderSource(shader, source);
|
|
403
|
+
gl.compileShader(shader);
|
|
404
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
405
|
+
const log = gl.getShaderInfoLog(shader);
|
|
406
|
+
gl.deleteShader(shader);
|
|
407
|
+
throw new Error(`Color correction shader compile failed: ${log ?? "(no log)"}`);
|
|
408
|
+
}
|
|
409
|
+
return shader;
|
|
410
|
+
};
|
|
411
|
+
var createProgram = (gl) => {
|
|
412
|
+
const vertexShader = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER);
|
|
413
|
+
const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER);
|
|
414
|
+
const program = gl.createProgram();
|
|
415
|
+
if (!program) {
|
|
416
|
+
throw new Error("Failed to create color correction shader program");
|
|
417
|
+
}
|
|
418
|
+
gl.attachShader(program, vertexShader);
|
|
419
|
+
gl.attachShader(program, fragmentShader);
|
|
420
|
+
gl.linkProgram(program);
|
|
421
|
+
gl.deleteShader(vertexShader);
|
|
422
|
+
gl.deleteShader(fragmentShader);
|
|
423
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
424
|
+
const log = gl.getProgramInfoLog(program);
|
|
425
|
+
gl.deleteProgram(program);
|
|
426
|
+
throw new Error(`Color correction shader link failed: ${log ?? "(no log)"}`);
|
|
427
|
+
}
|
|
428
|
+
return program;
|
|
429
|
+
};
|
|
430
|
+
var createTexture = (gl) => {
|
|
431
|
+
const texture = gl.createTexture();
|
|
432
|
+
if (!texture) {
|
|
433
|
+
throw new Error("Failed to create color correction texture");
|
|
434
|
+
}
|
|
435
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
436
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
437
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
438
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
439
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
440
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
441
|
+
return texture;
|
|
442
|
+
};
|
|
443
|
+
var setupColorCorrection = (target) => {
|
|
444
|
+
const gl = target.getContext("webgl2", {
|
|
445
|
+
premultipliedAlpha: true,
|
|
446
|
+
alpha: true,
|
|
447
|
+
preserveDrawingBuffer: true
|
|
448
|
+
});
|
|
449
|
+
if (!gl) {
|
|
450
|
+
throw createWebGL2ContextError("color correction effect");
|
|
451
|
+
}
|
|
452
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
|
|
453
|
+
const program = createProgram(gl);
|
|
454
|
+
const vao = gl.createVertexArray();
|
|
455
|
+
if (!vao) {
|
|
456
|
+
throw new Error("Failed to create color correction vertex array");
|
|
457
|
+
}
|
|
458
|
+
const vbo = gl.createBuffer();
|
|
459
|
+
if (!vbo) {
|
|
460
|
+
throw new Error("Failed to create color correction vertex buffer");
|
|
461
|
+
}
|
|
462
|
+
gl.bindVertexArray(vao);
|
|
463
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
|
464
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 0, 0, 1, -1, 1, 0, -1, 1, 0, 1, 1, 1, 1, 1]), gl.STATIC_DRAW);
|
|
465
|
+
const aPos = gl.getAttribLocation(program, "aPos");
|
|
466
|
+
const aUv = gl.getAttribLocation(program, "aUv");
|
|
467
|
+
gl.enableVertexAttribArray(aPos);
|
|
468
|
+
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 16, 0);
|
|
469
|
+
gl.enableVertexAttribArray(aUv);
|
|
470
|
+
gl.vertexAttribPointer(aUv, 2, gl.FLOAT, false, 16, 8);
|
|
471
|
+
gl.bindVertexArray(null);
|
|
472
|
+
return {
|
|
473
|
+
gl,
|
|
474
|
+
program,
|
|
475
|
+
vao,
|
|
476
|
+
vbo,
|
|
477
|
+
textureSource: createTexture(gl),
|
|
478
|
+
uniforms: {
|
|
479
|
+
uSource: gl.getUniformLocation(program, "uSource"),
|
|
480
|
+
uExposure: gl.getUniformLocation(program, "uExposure"),
|
|
481
|
+
uContrast: gl.getUniformLocation(program, "uContrast"),
|
|
482
|
+
uPivot: gl.getUniformLocation(program, "uPivot"),
|
|
483
|
+
uShadows: gl.getUniformLocation(program, "uShadows"),
|
|
484
|
+
uHighlights: gl.getUniformLocation(program, "uHighlights"),
|
|
485
|
+
uWhites: gl.getUniformLocation(program, "uWhites"),
|
|
486
|
+
uBlacks: gl.getUniformLocation(program, "uBlacks"),
|
|
487
|
+
uTemperature: gl.getUniformLocation(program, "uTemperature"),
|
|
488
|
+
uTint: gl.getUniformLocation(program, "uTint"),
|
|
489
|
+
uSaturation: gl.getUniformLocation(program, "uSaturation"),
|
|
490
|
+
uVibrance: gl.getUniformLocation(program, "uVibrance")
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
};
|
|
494
|
+
var colorCorrection = createEffect({
|
|
495
|
+
type: "remotion/color-correction",
|
|
496
|
+
label: "colorCorrection()",
|
|
497
|
+
documentationLink: "https://www.remotion.dev/docs/effects/color-correction",
|
|
498
|
+
backend: "webgl2",
|
|
499
|
+
calculateKey: (params) => {
|
|
500
|
+
const r = resolve(params);
|
|
501
|
+
return `color-correction-${r.exposure}-${r.contrast}-${r.pivot}-${r.shadows}-${r.highlights}-${r.whites}-${r.blacks}-${r.temperature}-${r.tint}-${r.saturation}-${r.vibrance}`;
|
|
502
|
+
},
|
|
503
|
+
setup: setupColorCorrection,
|
|
504
|
+
apply: ({ source, width, height, params, state, flipSourceY }) => {
|
|
505
|
+
const r = resolve(params);
|
|
506
|
+
const { gl, program, textureSource, uniforms, vao } = state;
|
|
507
|
+
gl.viewport(0, 0, width, height);
|
|
508
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
509
|
+
gl.clearColor(0, 0, 0, 0);
|
|
510
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
511
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
512
|
+
gl.bindTexture(gl.TEXTURE_2D, textureSource);
|
|
513
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipSourceY);
|
|
514
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
|
|
515
|
+
gl.useProgram(program);
|
|
516
|
+
if (uniforms.uSource)
|
|
517
|
+
gl.uniform1i(uniforms.uSource, 0);
|
|
518
|
+
if (uniforms.uExposure)
|
|
519
|
+
gl.uniform1f(uniforms.uExposure, r.exposure);
|
|
520
|
+
if (uniforms.uContrast)
|
|
521
|
+
gl.uniform1f(uniforms.uContrast, r.contrast);
|
|
522
|
+
if (uniforms.uPivot)
|
|
523
|
+
gl.uniform1f(uniforms.uPivot, r.pivot);
|
|
524
|
+
if (uniforms.uShadows)
|
|
525
|
+
gl.uniform1f(uniforms.uShadows, r.shadows);
|
|
526
|
+
if (uniforms.uHighlights) {
|
|
527
|
+
gl.uniform1f(uniforms.uHighlights, r.highlights);
|
|
528
|
+
}
|
|
529
|
+
if (uniforms.uWhites)
|
|
530
|
+
gl.uniform1f(uniforms.uWhites, r.whites);
|
|
531
|
+
if (uniforms.uBlacks)
|
|
532
|
+
gl.uniform1f(uniforms.uBlacks, r.blacks);
|
|
533
|
+
if (uniforms.uTemperature) {
|
|
534
|
+
gl.uniform1f(uniforms.uTemperature, r.temperature);
|
|
535
|
+
}
|
|
536
|
+
if (uniforms.uTint)
|
|
537
|
+
gl.uniform1f(uniforms.uTint, r.tint);
|
|
538
|
+
if (uniforms.uSaturation) {
|
|
539
|
+
gl.uniform1f(uniforms.uSaturation, r.saturation);
|
|
540
|
+
}
|
|
541
|
+
if (uniforms.uVibrance)
|
|
542
|
+
gl.uniform1f(uniforms.uVibrance, r.vibrance);
|
|
543
|
+
gl.bindVertexArray(vao);
|
|
544
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
545
|
+
gl.bindVertexArray(null);
|
|
546
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
547
|
+
gl.useProgram(null);
|
|
548
|
+
},
|
|
549
|
+
cleanup: ({ gl, program, vao, vbo, textureSource }) => {
|
|
550
|
+
gl.deleteTexture(textureSource);
|
|
551
|
+
gl.deleteBuffer(vbo);
|
|
552
|
+
gl.deleteProgram(program);
|
|
553
|
+
gl.deleteVertexArray(vao);
|
|
554
|
+
},
|
|
555
|
+
schema: colorCorrectionSchema,
|
|
556
|
+
validateParams: validateColorCorrectionParams
|
|
557
|
+
});
|
|
558
|
+
export {
|
|
559
|
+
colorCorrection
|
|
560
|
+
};
|