| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214 | 4x
4x
4x
4x
4x
4x
2636x
2636x
2636x
421x
2636x
4x
1184x
4x
2368x
2368x
2368x
4x
1184x
4x
6912x
3024x
3888x
4x
2304x
4x
6912x
4x
2304x
4x
6912x
1728x
5184x
5184x
1971x
3213x
4x
2304x
4x
6912x
4x
2304x
4x
6912x
4x
2304x
4x
7356x
3164x
4192x
4x
2452x
4x
6912x
4x
2304x
4x
4x
4x
4x
4x
4x
4x
4x
4x
4x
2304x
2304x
2304x
2304x
2304x
2304x
| import { labToRGB, lchToRGB, rgbToLAB, rgbToLCH } from "./colorConverters";
import { ColorLAB } from "./colorLAB";
import { ColorLCH } from "./colorLCH";
import { ColorRGBA64 } from "./colorRGBA64";
import { clamp } from "./mathUtils";
// The alpha channel of the input is ignored
export function saturateViaLCH(
input: ColorRGBA64,
saturation: number,
saturationConstant: number = 18
): ColorRGBA64 {
const lch: ColorLCH = rgbToLCH(input);
let sat: number = lch.c + saturation * saturationConstant;
if (sat < 0) {
sat = 0;
}
return lchToRGB(new ColorLCH(lch.l, sat, lch.h));
}
// The alpha channel of the input is ignored
export function desaturateViaLCH(
input: ColorRGBA64,
saturation: number,
saturationConstant: number = 18
): ColorRGBA64 {
return saturateViaLCH(input, -1 * saturation, saturationConstant);
}
// The alpha channel of the input is ignored
export function darkenViaLAB(
input: ColorRGBA64,
amount: number,
darkenConstant: number = 18
): ColorRGBA64 {
const lab: ColorLAB = rgbToLAB(input);
const darkened: number = lab.l - amount * darkenConstant;
return labToRGB(new ColorLAB(darkened, lab.a, lab.b));
}
// The alpha channel of the input is ignored
export function lightenViaLAB(
input: ColorRGBA64,
amount: number,
darkenConstant: number = 18
): ColorRGBA64 {
return darkenViaLAB(input, -1 * amount, darkenConstant);
}
export function blendBurnChannel(bottom: number, top: number): number {
if (top === 0.0) {
// Despite the discontinuity, other sources seem to use 0.0 here instead of 1
return 0.0;
}
return 1.0 - (1.0 - bottom) / top;
}
// The alpha channel of the input is ignored
export function blendBurn(bottom: ColorRGBA64, top: ColorRGBA64): ColorRGBA64 {
return new ColorRGBA64(
blendBurnChannel(bottom.r, top.r),
blendBurnChannel(bottom.g, top.g),
blendBurnChannel(bottom.b, top.b),
1
);
}
export function blendDarkenChannel(bottom: number, top: number): number {
return Math.min(bottom, top);
}
// The alpha channel of the input is ignored
export function blendDarken(bottom: ColorRGBA64, top: ColorRGBA64): ColorRGBA64 {
return new ColorRGBA64(
blendDarkenChannel(bottom.r, top.r),
blendDarkenChannel(bottom.g, top.g),
blendDarkenChannel(bottom.b, top.b),
1
);
}
export function blendDodgeChannel(bottom: number, top: number): number {
if (top >= 1.0) {
return 1.0;
}
const retVal: number = bottom / (1.0 - top);
if (retVal >= 1.0) {
return 1.0;
}
return retVal;
}
// The alpha channel of the input is ignored
export function blendDodge(bottom: ColorRGBA64, top: ColorRGBA64): ColorRGBA64 {
return new ColorRGBA64(
blendDodgeChannel(bottom.r, top.r),
blendDodgeChannel(bottom.g, top.g),
blendDodgeChannel(bottom.b, top.b),
1
);
}
export function blendLightenChannel(bottom: number, top: number): number {
return Math.max(bottom, top);
}
// The alpha channel of the input is ignored
export function blendLighten(bottom: ColorRGBA64, top: ColorRGBA64): ColorRGBA64 {
return new ColorRGBA64(
blendLightenChannel(bottom.r, top.r),
blendLightenChannel(bottom.g, top.g),
blendLightenChannel(bottom.b, top.b),
1
);
}
export function blendMultiplyChannel(bottom: number, top: number): number {
return bottom * top;
}
// The alpha channel of the input is ignored
export function blendMultiply(bottom: ColorRGBA64, top: ColorRGBA64): ColorRGBA64 {
return new ColorRGBA64(
blendMultiplyChannel(bottom.r, top.r),
blendMultiplyChannel(bottom.g, top.g),
blendMultiplyChannel(bottom.b, top.b),
1
);
}
export function blendOverlayChannel(bottom: number, top: number): number {
if (bottom < 0.5) {
return clamp(2.0 * top * bottom, 0, 1);
}
return clamp(1.0 - 2.0 * (1.0 - top) * (1.0 - bottom), 0, 1);
}
// The alpha channel of the input is ignored
export function blendOverlay(bottom: ColorRGBA64, top: ColorRGBA64): ColorRGBA64 {
return new ColorRGBA64(
blendOverlayChannel(bottom.r, top.r),
blendOverlayChannel(bottom.g, top.g),
blendOverlayChannel(bottom.b, top.b),
1
);
}
export function blendScreenChannel(bottom: number, top: number): number {
return 1.0 - (1.0 - top) * (1.0 - bottom);
}
// The alpha channel of the input is ignored
export function blendScreen(bottom: ColorRGBA64, top: ColorRGBA64): ColorRGBA64 {
return new ColorRGBA64(
blendScreenChannel(bottom.r, top.r),
blendScreenChannel(bottom.g, top.g),
blendScreenChannel(bottom.b, top.b),
1
);
}
export enum ColorBlendMode {
Burn,
Darken,
Dodge,
Lighten,
Multiply,
Overlay,
Screen,
}
// The alpha channel of the input is ignored
export function blend(
mode: ColorBlendMode,
bottom: ColorRGBA64,
top: ColorRGBA64
): ColorRGBA64 {
switch (mode) {
case ColorBlendMode.Burn:
return blendBurn(bottom, top);
case ColorBlendMode.Darken:
return blendDarken(bottom, top);
case ColorBlendMode.Dodge:
return blendDodge(bottom, top);
case ColorBlendMode.Lighten:
return blendLighten(bottom, top);
case ColorBlendMode.Multiply:
return blendMultiply(bottom, top);
case ColorBlendMode.Overlay:
return blendOverlay(bottom, top);
case ColorBlendMode.Screen:
return blendScreen(bottom, top);
default:
throw new Error("Unknown blend mode");
}
}
// Alpha channel of bottom is ignored
// The returned color always has an alpha channel of 1
// Different programs (eg: paint.net, photoshop) will give different answers than this occasionally but within +/- 1/255 in each channel. Just depends on the details of how they round off decimals
export function computeAlphaBlend(bottom: ColorRGBA64, top: ColorRGBA64): ColorRGBA64 {
Iif (top.a >= 1) {
return top;
} else Iif (top.a <= 0) {
return new ColorRGBA64(bottom.r, bottom.g, bottom.b, 1);
}
const r: number = top.a * top.r + (1 - top.a) * bottom.r;
const g: number = top.a * top.g + (1 - top.a) * bottom.g;
const b: number = top.a * top.b + (1 - top.a) * bottom.b;
return new ColorRGBA64(r, g, b, 1);
}
|