@pixagram/renderart 1.0.1 → 1.0.2
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 +1 -0
- package/dist/xbrz-gpu.d.ts +2 -6
- package/dist/xbrz-gpu.d.ts.map +1 -1
- package/dist/xbrz-gpu.js +260 -464
- package/dist/xbrz-gpu.js.map +1 -1
- package/package.json +1 -1
- package/src/xbrz-gpu.ts +264 -481
package/src/xbrz-gpu.ts
CHANGED
|
@@ -10,29 +10,22 @@
|
|
|
10
10
|
|
|
11
11
|
import type { ImageInput, ImageOutput, Renderer, XbrzOptions } from './types.js';
|
|
12
12
|
|
|
13
|
-
// Vertex shader
|
|
14
13
|
const VERTEX_SHADER = `#version 300 es
|
|
15
14
|
layout(location = 0) in vec2 position;
|
|
16
15
|
out vec2 vUv;
|
|
17
|
-
|
|
18
16
|
void main() {
|
|
19
17
|
vUv = position * 0.5 + 0.5;
|
|
20
18
|
gl_Position = vec4(position, 0.0, 1.0);
|
|
21
19
|
}`;
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
const FRAGMENT_SHADER = `#version 300 es
|
|
21
|
+
const COMMON_HEADER = `#version 300 es
|
|
25
22
|
precision highp float;
|
|
26
|
-
|
|
27
23
|
uniform sampler2D uTex;
|
|
28
24
|
uniform vec2 uInputRes;
|
|
29
|
-
uniform vec2 uOutputRes;
|
|
30
|
-
uniform int uScale;
|
|
31
25
|
uniform float uLuminanceWeight;
|
|
32
26
|
uniform float uEqualColorTolerance;
|
|
33
27
|
uniform float uSteepDirectionThreshold;
|
|
34
28
|
uniform float uDominantDirectionThreshold;
|
|
35
|
-
|
|
36
29
|
in vec2 vUv;
|
|
37
30
|
out vec4 outColor;
|
|
38
31
|
|
|
@@ -40,61 +33,24 @@ out vec4 outColor;
|
|
|
40
33
|
#define BLEND_NORMAL 1
|
|
41
34
|
#define BLEND_DOMINANT 2
|
|
42
35
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return texture(uTex, clamp(coord, vec2(0.0), vec2(1.0)));
|
|
46
|
-
}
|
|
36
|
+
vec4 tex(vec2 coord) { return texture(uTex, clamp(coord, vec2(0.0), vec2(1.0))); }
|
|
37
|
+
float reduce(vec3 c) { return dot(c, vec3(65536.0, 256.0, 1.0)); }
|
|
47
38
|
|
|
48
|
-
|
|
49
|
-
float reduce(vec3 color) {
|
|
50
|
-
return dot(color, vec3(65536.0, 256.0, 1.0));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// YCbCr color distance (perceptually weighted)
|
|
54
|
-
float distYCbCr(vec3 pixA, vec3 pixB) {
|
|
39
|
+
float DistYCbCr(vec3 pixA, vec3 pixB) {
|
|
55
40
|
const vec3 w = vec3(0.2627, 0.6780, 0.0593);
|
|
56
41
|
const float scaleB = 0.5 / (1.0 - w.b);
|
|
57
42
|
const float scaleR = 0.5 / (1.0 - w.r);
|
|
58
|
-
|
|
59
43
|
vec3 diff = pixA - pixB;
|
|
60
44
|
float Y = dot(diff, w);
|
|
61
45
|
float Cb = scaleB * (diff.b - Y);
|
|
62
46
|
float Cr = scaleR * (diff.r - Y);
|
|
63
|
-
|
|
64
47
|
return sqrt((uLuminanceWeight * Y) * (uLuminanceWeight * Y) + Cb * Cb + Cr * Cr);
|
|
65
48
|
}
|
|
66
49
|
|
|
67
|
-
|
|
68
|
-
bool
|
|
69
|
-
return distYCbCr(pixA, pixB) < uEqualColorTolerance;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Check if any blending is needed
|
|
73
|
-
bool isBlendingNeeded(ivec4 blend) {
|
|
74
|
-
return blend.x != BLEND_NONE || blend.y != BLEND_NONE ||
|
|
75
|
-
blend.z != BLEND_NONE || blend.w != BLEND_NONE;
|
|
76
|
-
}
|
|
50
|
+
bool IsPixEqual(vec3 pixA, vec3 pixB) { return DistYCbCr(pixA, pixB) < uEqualColorTolerance; }
|
|
51
|
+
bool IsBlendingNeeded(ivec4 blend) { return blend.x != 0 || blend.y != 0 || blend.z != 0 || blend.w != 0; }
|
|
77
52
|
|
|
78
|
-
void
|
|
79
|
-
vec2 texelSize = 1.0 / uInputRes;
|
|
80
|
-
|
|
81
|
-
// Find which source pixel and sub-pixel position
|
|
82
|
-
vec2 srcPos = vUv * uInputRes;
|
|
83
|
-
vec2 srcPixel = floor(srcPos);
|
|
84
|
-
vec2 f = fract(srcPos);
|
|
85
|
-
|
|
86
|
-
// Sample 5x5 neighborhood
|
|
87
|
-
// Pixel Mapping: 20|21|22|23|24
|
|
88
|
-
// 19|06|07|08|09
|
|
89
|
-
// 18|05|00|01|10
|
|
90
|
-
// 17|04|03|02|11
|
|
91
|
-
// 16|15|14|13|12
|
|
92
|
-
|
|
93
|
-
vec2 tc = (srcPixel + 0.5) * texelSize;
|
|
94
|
-
float dx = texelSize.x;
|
|
95
|
-
float dy = texelSize.y;
|
|
96
|
-
|
|
97
|
-
vec3 src[25];
|
|
53
|
+
void sampleNeighborhood(vec2 tc, float dx, float dy, out vec3 src[25], out float v[9]) {
|
|
98
54
|
src[21] = tex(tc + vec2(-dx, -2.0*dy)).rgb;
|
|
99
55
|
src[22] = tex(tc + vec2( 0, -2.0*dy)).rgb;
|
|
100
56
|
src[23] = tex(tc + vec2( dx, -2.0*dy)).rgb;
|
|
@@ -116,373 +72,253 @@ void main() {
|
|
|
116
72
|
src[ 9] = tex(tc + vec2( 2.0*dx, -dy)).rgb;
|
|
117
73
|
src[10] = tex(tc + vec2( 2.0*dx, 0)).rgb;
|
|
118
74
|
src[11] = tex(tc + vec2( 2.0*dx, dy)).rgb;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
v[1] = reduce(src[1]);
|
|
124
|
-
v[2] = reduce(src[2]);
|
|
125
|
-
v[3] = reduce(src[3]);
|
|
126
|
-
v[4] = reduce(src[4]);
|
|
127
|
-
v[5] = reduce(src[5]);
|
|
128
|
-
v[6] = reduce(src[6]);
|
|
129
|
-
v[7] = reduce(src[7]);
|
|
130
|
-
v[8] = reduce(src[8]);
|
|
131
|
-
|
|
75
|
+
for (int i = 0; i < 9; i++) v[i] = reduce(src[i]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
ivec4 detectCorners(vec3 src[25], float v[9]) {
|
|
132
79
|
ivec4 blendResult = ivec4(BLEND_NONE);
|
|
133
|
-
|
|
134
|
-
// Preprocess corners - determine blend type for each
|
|
135
|
-
|
|
136
|
-
// Corner (1, 1) - bottom-right
|
|
80
|
+
// Corner (1, 1)
|
|
137
81
|
if (!((v[0] == v[1] && v[3] == v[2]) || (v[0] == v[3] && v[1] == v[2]))) {
|
|
138
|
-
float dist_03_01 =
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
distYCbCr(src[7], src[1]) + distYCbCr(src[1], src[11]) +
|
|
143
|
-
4.0 * distYCbCr(src[0], src[2]);
|
|
144
|
-
bool dominantGradient = (uDominantDirectionThreshold * dist_03_01) < dist_00_02;
|
|
145
|
-
blendResult[2] = ((dist_03_01 < dist_00_02) && (v[0] != v[1]) && (v[0] != v[3]))
|
|
146
|
-
? (dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
|
|
82
|
+
float dist_03_01 = DistYCbCr(src[4], src[0]) + DistYCbCr(src[0], src[8]) + DistYCbCr(src[14], src[2]) + DistYCbCr(src[2], src[10]) + 4.0 * DistYCbCr(src[3], src[1]);
|
|
83
|
+
float dist_00_02 = DistYCbCr(src[5], src[3]) + DistYCbCr(src[3], src[13]) + DistYCbCr(src[7], src[1]) + DistYCbCr(src[1], src[11]) + 4.0 * DistYCbCr(src[0], src[2]);
|
|
84
|
+
bool dominant = (uDominantDirectionThreshold * dist_03_01) < dist_00_02;
|
|
85
|
+
blendResult[2] = ((dist_03_01 < dist_00_02) && (v[0] != v[1]) && (v[0] != v[3])) ? (dominant ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
|
|
147
86
|
}
|
|
148
|
-
|
|
149
|
-
// Corner (0, 1) - bottom-left
|
|
87
|
+
// Corner (0, 1)
|
|
150
88
|
if (!((v[5] == v[0] && v[4] == v[3]) || (v[5] == v[4] && v[0] == v[3]))) {
|
|
151
|
-
float dist_04_00 =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
distYCbCr(src[6], src[0]) + distYCbCr(src[0], src[2]) +
|
|
156
|
-
4.0 * distYCbCr(src[5], src[3]);
|
|
157
|
-
bool dominantGradient = (uDominantDirectionThreshold * dist_05_03) < dist_04_00;
|
|
158
|
-
blendResult[3] = ((dist_04_00 > dist_05_03) && (v[0] != v[5]) && (v[0] != v[3]))
|
|
159
|
-
? (dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
|
|
89
|
+
float dist_04_00 = DistYCbCr(src[17], src[5]) + DistYCbCr(src[5], src[7]) + DistYCbCr(src[15], src[3]) + DistYCbCr(src[3], src[1]) + 4.0 * DistYCbCr(src[4], src[0]);
|
|
90
|
+
float dist_05_03 = DistYCbCr(src[18], src[4]) + DistYCbCr(src[4], src[14]) + DistYCbCr(src[6], src[0]) + DistYCbCr(src[0], src[2]) + 4.0 * DistYCbCr(src[5], src[3]);
|
|
91
|
+
bool dominant = (uDominantDirectionThreshold * dist_05_03) < dist_04_00;
|
|
92
|
+
blendResult[3] = ((dist_04_00 > dist_05_03) && (v[0] != v[5]) && (v[0] != v[3])) ? (dominant ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
|
|
160
93
|
}
|
|
161
|
-
|
|
162
|
-
// Corner (1, 0) - top-right
|
|
94
|
+
// Corner (1, 0)
|
|
163
95
|
if (!((v[7] == v[8] && v[0] == v[1]) || (v[7] == v[0] && v[8] == v[1]))) {
|
|
164
|
-
float dist_00_08 =
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
distYCbCr(src[22], src[8]) + distYCbCr(src[8], src[10]) +
|
|
169
|
-
4.0 * distYCbCr(src[7], src[1]);
|
|
170
|
-
bool dominantGradient = (uDominantDirectionThreshold * dist_07_01) < dist_00_08;
|
|
171
|
-
blendResult[1] = ((dist_00_08 > dist_07_01) && (v[0] != v[7]) && (v[0] != v[1]))
|
|
172
|
-
? (dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
|
|
96
|
+
float dist_00_08 = DistYCbCr(src[5], src[7]) + DistYCbCr(src[7], src[23]) + DistYCbCr(src[3], src[1]) + DistYCbCr(src[1], src[9]) + 4.0 * DistYCbCr(src[0], src[8]);
|
|
97
|
+
float dist_07_01 = DistYCbCr(src[6], src[0]) + DistYCbCr(src[0], src[2]) + DistYCbCr(src[22], src[8]) + DistYCbCr(src[8], src[10]) + 4.0 * DistYCbCr(src[7], src[1]);
|
|
98
|
+
bool dominant = (uDominantDirectionThreshold * dist_07_01) < dist_00_08;
|
|
99
|
+
blendResult[1] = ((dist_00_08 > dist_07_01) && (v[0] != v[7]) && (v[0] != v[1])) ? (dominant ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
|
|
173
100
|
}
|
|
174
|
-
|
|
175
|
-
// Corner (0, 0) - top-left
|
|
101
|
+
// Corner (0, 0)
|
|
176
102
|
if (!((v[6] == v[7] && v[5] == v[0]) || (v[6] == v[5] && v[7] == v[0]))) {
|
|
177
|
-
float dist_05_07 =
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
distYCbCr(src[21], src[7]) + distYCbCr(src[7], src[1]) +
|
|
182
|
-
4.0 * distYCbCr(src[6], src[0]);
|
|
183
|
-
bool dominantGradient = (uDominantDirectionThreshold * dist_05_07) < dist_06_00;
|
|
184
|
-
blendResult[0] = ((dist_05_07 < dist_06_00) && (v[0] != v[5]) && (v[0] != v[7]))
|
|
185
|
-
? (dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
|
|
103
|
+
float dist_05_07 = DistYCbCr(src[18], src[6]) + DistYCbCr(src[6], src[22]) + DistYCbCr(src[4], src[0]) + DistYCbCr(src[0], src[8]) + 4.0 * DistYCbCr(src[5], src[7]);
|
|
104
|
+
float dist_06_00 = DistYCbCr(src[19], src[5]) + DistYCbCr(src[5], src[3]) + DistYCbCr(src[21], src[7]) + DistYCbCr(src[7], src[1]) + 4.0 * DistYCbCr(src[6], src[0]);
|
|
105
|
+
bool dominant = (uDominantDirectionThreshold * dist_05_07) < dist_06_00;
|
|
106
|
+
blendResult[0] = ((dist_05_07 < dist_06_00) && (v[0] != v[5]) && (v[0] != v[7])) ? (dominant ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
|
|
186
107
|
}
|
|
108
|
+
return blendResult;
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
// 2x xBRZ fragment shader
|
|
113
|
+
const FRAG_2X = COMMON_HEADER + `
|
|
114
|
+
const float M_PI = 3.1415926535897932384626433832795;
|
|
115
|
+
|
|
116
|
+
void main() {
|
|
117
|
+
vec2 texelSize = 1.0 / uInputRes;
|
|
118
|
+
vec2 srcPos = vUv * uInputRes;
|
|
119
|
+
vec2 srcPixel = floor(srcPos);
|
|
120
|
+
vec2 f = fract(srcPos);
|
|
121
|
+
vec2 tc = (srcPixel + 0.5) * texelSize;
|
|
187
122
|
|
|
188
|
-
|
|
189
|
-
|
|
123
|
+
vec3 src[25]; float v[9];
|
|
124
|
+
sampleNeighborhood(tc, texelSize.x, texelSize.y, src, v);
|
|
125
|
+
ivec4 blendResult = detectCorners(src, v);
|
|
190
126
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
for (int rot = 0; rot < 4; rot++) {
|
|
204
|
-
// Rotated kernel indices
|
|
205
|
-
int k1, k2, k3, k4, k5, k6, k7, k8;
|
|
206
|
-
ivec4 blend;
|
|
207
|
-
|
|
208
|
-
if (rot == 0) {
|
|
209
|
-
k1 = 1; k2 = 2; k3 = 3; k4 = 4; k5 = 5; k6 = 6; k7 = 7; k8 = 8;
|
|
210
|
-
blend = blendResult;
|
|
211
|
-
} else if (rot == 1) {
|
|
212
|
-
k1 = 7; k2 = 8; k3 = 1; k4 = 2; k5 = 3; k6 = 4; k7 = 5; k8 = 6;
|
|
213
|
-
blend = blendResult.wxyz;
|
|
214
|
-
} else if (rot == 2) {
|
|
215
|
-
k1 = 5; k2 = 6; k3 = 7; k4 = 8; k5 = 1; k6 = 2; k7 = 3; k8 = 4;
|
|
216
|
-
blend = blendResult.zwxy;
|
|
217
|
-
} else {
|
|
218
|
-
k1 = 3; k2 = 4; k3 = 5; k4 = 6; k5 = 7; k6 = 8; k7 = 1; k8 = 2;
|
|
219
|
-
blend = blendResult.yzwx;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
vec3 k[9];
|
|
223
|
-
k[0] = src[0];
|
|
224
|
-
k[1] = (rot == 0) ? src[1] : (rot == 1) ? src[7] : (rot == 2) ? src[5] : src[3];
|
|
225
|
-
k[2] = (rot == 0) ? src[2] : (rot == 1) ? src[8] : (rot == 2) ? src[6] : src[4];
|
|
226
|
-
k[3] = (rot == 0) ? src[3] : (rot == 1) ? src[1] : (rot == 2) ? src[7] : src[5];
|
|
227
|
-
k[4] = (rot == 0) ? src[4] : (rot == 1) ? src[2] : (rot == 2) ? src[8] : src[6];
|
|
228
|
-
k[5] = (rot == 0) ? src[5] : (rot == 1) ? src[3] : (rot == 2) ? src[1] : src[7];
|
|
229
|
-
k[6] = (rot == 0) ? src[6] : (rot == 1) ? src[4] : (rot == 2) ? src[2] : src[8];
|
|
230
|
-
k[7] = (rot == 0) ? src[7] : (rot == 1) ? src[5] : (rot == 2) ? src[3] : src[1];
|
|
231
|
-
k[8] = (rot == 0) ? src[8] : (rot == 1) ? src[6] : (rot == 2) ? src[4] : src[2];
|
|
232
|
-
|
|
233
|
-
float vk0 = reduce(k[0]);
|
|
234
|
-
float vk4 = reduce(k[4]);
|
|
235
|
-
float vk5 = reduce(k[5]);
|
|
236
|
-
float vk7 = reduce(k[7]);
|
|
237
|
-
float vk8 = reduce(k[8]);
|
|
238
|
-
|
|
239
|
-
float dist_01_04 = distYCbCr(k[1], k[4]);
|
|
240
|
-
float dist_03_08 = distYCbCr(k[3], k[8]);
|
|
241
|
-
bool haveShallowLine = (uSteepDirectionThreshold * dist_01_04 <= dist_03_08) && (vk0 != vk4) && (vk5 != vk4);
|
|
242
|
-
bool haveSteepLine = (uSteepDirectionThreshold * dist_03_08 <= dist_01_04) && (vk0 != vk8) && (vk7 != vk8);
|
|
243
|
-
bool needBlend = (blend[2] != BLEND_NONE);
|
|
244
|
-
bool doLineBlend = blend[2] >= BLEND_DOMINANT ||
|
|
245
|
-
!((blend[1] != BLEND_NONE && !isPixEqual(k[0], k[4])) ||
|
|
246
|
-
(blend[3] != BLEND_NONE && !isPixEqual(k[0], k[8])) ||
|
|
247
|
-
(isPixEqual(k[4], k[3]) && isPixEqual(k[3], k[2]) &&
|
|
248
|
-
isPixEqual(k[2], k[1]) && isPixEqual(k[1], k[8]) && !isPixEqual(k[0], k[2])));
|
|
249
|
-
|
|
250
|
-
vec3 blendPix = (distYCbCr(k[0], k[1]) <= distYCbCr(k[0], k[3])) ? k[1] : k[3];
|
|
251
|
-
|
|
252
|
-
const float PI = 3.14159265359;
|
|
253
|
-
|
|
254
|
-
// Apply blends to rotated destination
|
|
255
|
-
int d1 = (rot == 0) ? 1 : (rot == 1) ? 0 : (rot == 2) ? 3 : 2;
|
|
256
|
-
int d2 = (rot == 0) ? 2 : (rot == 1) ? 1 : (rot == 2) ? 0 : 3;
|
|
257
|
-
int d3 = (rot == 0) ? 3 : (rot == 1) ? 2 : (rot == 2) ? 1 : 0;
|
|
258
|
-
|
|
259
|
-
float blend1 = (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0;
|
|
260
|
-
float blend2 = needBlend ? (doLineBlend ?
|
|
261
|
-
(haveShallowLine ? (haveSteepLine ? 5.0/6.0 : 0.75) : (haveSteepLine ? 0.75 : 0.5))
|
|
262
|
-
: (1.0 - PI/4.0)) : 0.0;
|
|
263
|
-
float blend3 = (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0;
|
|
264
|
-
|
|
265
|
-
dst[d1] = mix(dst[d1], blendPix, blend1);
|
|
266
|
-
dst[d2] = mix(dst[d2], blendPix, blend2);
|
|
267
|
-
dst[d3] = mix(dst[d3], blendPix, blend3);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Select output pixel based on sub-pixel position
|
|
271
|
-
result = mix(mix(dst[0], dst[1], step(0.5, f.x)),
|
|
272
|
-
mix(dst[3], dst[2], step(0.5, f.x)), step(0.5, f.y));
|
|
273
|
-
|
|
274
|
-
} else if (uScale == 3) {
|
|
275
|
-
// 3x output: 9 pixels per source pixel
|
|
276
|
-
vec3 dst[9];
|
|
277
|
-
for (int i = 0; i < 9; i++) dst[i] = src[0];
|
|
127
|
+
vec3 dst[4];
|
|
128
|
+
dst[0] = src[0]; dst[1] = src[0]; dst[2] = src[0]; dst[3] = src[0];
|
|
129
|
+
|
|
130
|
+
if (IsBlendingNeeded(blendResult)) {
|
|
131
|
+
// Process all 4 corners
|
|
132
|
+
for (int corner = 0; corner < 4; corner++) {
|
|
133
|
+
vec3 k1, k3, k4, k5, k7, k8;
|
|
134
|
+
int blendIdx;
|
|
135
|
+
if (corner == 0) { k1=src[1]; k3=src[3]; k4=src[4]; k5=src[5]; k7=src[7]; k8=src[8]; blendIdx=2; }
|
|
136
|
+
else if (corner == 1) { k1=src[7]; k3=src[1]; k4=src[2]; k5=src[3]; k7=src[5]; k8=src[6]; blendIdx=1; }
|
|
137
|
+
else if (corner == 2) { k1=src[5]; k3=src[7]; k4=src[8]; k5=src[1]; k7=src[3]; k8=src[4]; blendIdx=0; }
|
|
138
|
+
else { k1=src[3]; k3=src[5]; k4=src[6]; k5=src[7]; k7=src[1]; k8=src[2]; blendIdx=3; }
|
|
278
139
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
k[3] = (rot == 0) ? src[3] : (rot == 1) ? src[1] : (rot == 2) ? src[7] : src[5];
|
|
290
|
-
k[4] = (rot == 0) ? src[4] : (rot == 1) ? src[2] : (rot == 2) ? src[8] : src[6];
|
|
291
|
-
k[5] = (rot == 0) ? src[5] : (rot == 1) ? src[3] : (rot == 2) ? src[1] : src[7];
|
|
292
|
-
k[6] = (rot == 0) ? src[6] : (rot == 1) ? src[4] : (rot == 2) ? src[2] : src[8];
|
|
293
|
-
k[7] = (rot == 0) ? src[7] : (rot == 1) ? src[5] : (rot == 2) ? src[3] : src[1];
|
|
294
|
-
k[8] = (rot == 0) ? src[8] : (rot == 1) ? src[6] : (rot == 2) ? src[4] : src[2];
|
|
295
|
-
|
|
296
|
-
float vk0 = reduce(k[0]);
|
|
297
|
-
float vk4 = reduce(k[4]);
|
|
298
|
-
float vk5 = reduce(k[5]);
|
|
299
|
-
float vk7 = reduce(k[7]);
|
|
300
|
-
float vk8 = reduce(k[8]);
|
|
301
|
-
|
|
302
|
-
float dist_01_04 = distYCbCr(k[1], k[4]);
|
|
303
|
-
float dist_03_08 = distYCbCr(k[3], k[8]);
|
|
304
|
-
bool haveShallowLine = (uSteepDirectionThreshold * dist_01_04 <= dist_03_08) && (vk0 != vk4) && (vk5 != vk4);
|
|
305
|
-
bool haveSteepLine = (uSteepDirectionThreshold * dist_03_08 <= dist_01_04) && (vk0 != vk8) && (vk7 != vk8);
|
|
306
|
-
bool needBlend = (blend[2] != BLEND_NONE);
|
|
307
|
-
bool doLineBlend = blend[2] >= BLEND_DOMINANT ||
|
|
308
|
-
!((blend[1] != BLEND_NONE && !isPixEqual(k[0], k[4])) ||
|
|
309
|
-
(blend[3] != BLEND_NONE && !isPixEqual(k[0], k[8])) ||
|
|
310
|
-
(isPixEqual(k[4], k[3]) && isPixEqual(k[3], k[2]) &&
|
|
311
|
-
isPixEqual(k[2], k[1]) && isPixEqual(k[1], k[8]) && !isPixEqual(k[0], k[2])));
|
|
312
|
-
|
|
313
|
-
vec3 blendPix = (distYCbCr(k[0], k[1]) <= distYCbCr(k[0], k[3])) ? k[1] : k[3];
|
|
314
|
-
|
|
315
|
-
// 3x specific blend weights
|
|
316
|
-
// Output mapping for rot=0: 0|1|2
|
|
317
|
-
// 3|4|5
|
|
318
|
-
// 6|7|8
|
|
319
|
-
// Corner is at position 8
|
|
320
|
-
|
|
321
|
-
int idx[4];
|
|
322
|
-
if (rot == 0) { idx[0] = 5; idx[1] = 8; idx[2] = 7; idx[3] = 2; }
|
|
323
|
-
else if (rot == 1) { idx[0] = 1; idx[1] = 2; idx[2] = 5; idx[3] = 0; }
|
|
324
|
-
else if (rot == 2) { idx[0] = 3; idx[1] = 0; idx[2] = 1; idx[3] = 6; }
|
|
325
|
-
else { idx[0] = 7; idx[1] = 6; idx[2] = 3; idx[3] = 8; }
|
|
326
|
-
|
|
327
|
-
dst[idx[0]] = mix(dst[idx[0]], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0);
|
|
328
|
-
dst[idx[1]] = mix(dst[idx[1]], blendPix, needBlend ? (doLineBlend ?
|
|
329
|
-
(haveShallowLine ? (haveSteepLine ? 2.0/3.0 : 0.75) : (haveSteepLine ? 0.75 : 0.5)) : 0.3) : 0.0);
|
|
330
|
-
dst[idx[2]] = mix(dst[idx[2]], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0);
|
|
331
|
-
}
|
|
140
|
+
float vk0 = v[0], vk4 = reduce(k4), vk5 = reduce(k5), vk7 = reduce(k7), vk8 = reduce(k8);
|
|
141
|
+
float dist_01_04 = DistYCbCr(k1, k4);
|
|
142
|
+
float dist_03_08 = DistYCbCr(k3, k8);
|
|
143
|
+
bool haveShallowLine = (uSteepDirectionThreshold * dist_01_04 <= dist_03_08) && (vk0 != vk4) && (vk5 != vk4);
|
|
144
|
+
bool haveSteepLine = (uSteepDirectionThreshold * dist_03_08 <= dist_01_04) && (vk0 != vk8) && (vk7 != vk8);
|
|
145
|
+
bool needBlend = (blendResult[blendIdx] != BLEND_NONE);
|
|
146
|
+
bool doLineBlend = blendResult[blendIdx] >= BLEND_DOMINANT ||
|
|
147
|
+
!((blendResult[(blendIdx+3)%4] != BLEND_NONE && !IsPixEqual(src[0], k4)) ||
|
|
148
|
+
(blendResult[(blendIdx+1)%4] != BLEND_NONE && !IsPixEqual(src[0], k8)) ||
|
|
149
|
+
(IsPixEqual(k4, k3) && IsPixEqual(k3, k8) && !IsPixEqual(src[0], k3)));
|
|
332
150
|
|
|
333
|
-
|
|
334
|
-
float
|
|
335
|
-
float
|
|
336
|
-
|
|
337
|
-
int yi = int(fy);
|
|
338
|
-
int idx = yi * 3 + xi;
|
|
151
|
+
vec3 blendPix = (DistYCbCr(src[0], k1) <= DistYCbCr(src[0], k3)) ? k1 : k3;
|
|
152
|
+
float b1 = (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0;
|
|
153
|
+
float b2 = needBlend ? (doLineBlend ? (haveShallowLine ? (haveSteepLine ? 5.0/6.0 : 0.75) : (haveSteepLine ? 0.75 : 0.5)) : (1.0 - M_PI/4.0)) : 0.0;
|
|
154
|
+
float b3 = (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0;
|
|
339
155
|
|
|
340
|
-
|
|
156
|
+
if (corner == 0) { dst[1] = mix(dst[1], blendPix, b1); dst[2] = mix(dst[2], blendPix, b2); dst[3] = mix(dst[3], blendPix, b3); }
|
|
157
|
+
else if (corner == 1) { dst[0] = mix(dst[0], blendPix, b1); dst[1] = mix(dst[1], blendPix, b2); dst[2] = mix(dst[2], blendPix, b3); }
|
|
158
|
+
else if (corner == 2) { dst[3] = mix(dst[3], blendPix, b1); dst[0] = mix(dst[0], blendPix, b2); dst[1] = mix(dst[1], blendPix, b3); }
|
|
159
|
+
else { dst[2] = mix(dst[2], blendPix, b1); dst[3] = mix(dst[3], blendPix, b2); dst[0] = mix(dst[0], blendPix, b3); }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
vec2 ff = step(0.5, f);
|
|
164
|
+
vec3 res = mix(mix(dst[0], dst[1], ff.x), mix(dst[3], dst[2], ff.x), ff.y);
|
|
165
|
+
outColor = vec4(res, tex(tc).a);
|
|
166
|
+
}`;
|
|
167
|
+
|
|
168
|
+
// 3x xBRZ fragment shader
|
|
169
|
+
const FRAG_3X = COMMON_HEADER + `
|
|
170
|
+
const float one_third = 1.0 / 3.0;
|
|
171
|
+
const float two_third = 2.0 / 3.0;
|
|
172
|
+
|
|
173
|
+
void main() {
|
|
174
|
+
vec2 texelSize = 1.0 / uInputRes;
|
|
175
|
+
vec2 srcPos = vUv * uInputRes;
|
|
176
|
+
vec2 srcPixel = floor(srcPos);
|
|
177
|
+
vec2 f = fract(srcPos);
|
|
178
|
+
vec2 tc = (srcPixel + 0.5) * texelSize;
|
|
179
|
+
|
|
180
|
+
vec3 src[25]; float v[9];
|
|
181
|
+
sampleNeighborhood(tc, texelSize.x, texelSize.y, src, v);
|
|
182
|
+
ivec4 blendResult = detectCorners(src, v);
|
|
183
|
+
|
|
184
|
+
// Output: 06|07|08 / 05|00|01 / 04|03|02
|
|
185
|
+
vec3 dst[9];
|
|
186
|
+
for (int i = 0; i < 9; i++) dst[i] = src[0];
|
|
187
|
+
|
|
188
|
+
if (IsBlendingNeeded(blendResult)) {
|
|
189
|
+
for (int corner = 0; corner < 4; corner++) {
|
|
190
|
+
vec3 k1, k3, k4, k5, k7, k8;
|
|
191
|
+
int blendIdx;
|
|
192
|
+
if (corner == 0) { k1=src[1]; k3=src[3]; k4=src[4]; k5=src[5]; k7=src[7]; k8=src[8]; blendIdx=2; }
|
|
193
|
+
else if (corner == 1) { k1=src[7]; k3=src[1]; k4=src[2]; k5=src[3]; k7=src[5]; k8=src[6]; blendIdx=1; }
|
|
194
|
+
else if (corner == 2) { k1=src[5]; k3=src[7]; k4=src[8]; k5=src[1]; k7=src[3]; k8=src[4]; blendIdx=0; }
|
|
195
|
+
else { k1=src[3]; k3=src[5]; k4=src[6]; k5=src[7]; k7=src[1]; k8=src[2]; blendIdx=3; }
|
|
341
196
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
197
|
+
float vk0 = v[0], vk4 = reduce(k4), vk5 = reduce(k5), vk7 = reduce(k7), vk8 = reduce(k8);
|
|
198
|
+
float dist_01_04 = DistYCbCr(k1, k4);
|
|
199
|
+
float dist_03_08 = DistYCbCr(k3, k8);
|
|
200
|
+
bool haveShallowLine = (uSteepDirectionThreshold * dist_01_04 <= dist_03_08) && (vk0 != vk4) && (vk5 != vk4);
|
|
201
|
+
bool haveSteepLine = (uSteepDirectionThreshold * dist_03_08 <= dist_01_04) && (vk0 != vk8) && (vk7 != vk8);
|
|
202
|
+
bool needBlend = (blendResult[blendIdx] != BLEND_NONE);
|
|
203
|
+
bool doLineBlend = blendResult[blendIdx] >= BLEND_DOMINANT ||
|
|
204
|
+
!((blendResult[(blendIdx+3)%4] != BLEND_NONE && !IsPixEqual(src[0], k4)) ||
|
|
205
|
+
(blendResult[(blendIdx+1)%4] != BLEND_NONE && !IsPixEqual(src[0], k8)) ||
|
|
206
|
+
(IsPixEqual(k4, k3) && IsPixEqual(k3, k8) && !IsPixEqual(src[0], k3)));
|
|
346
207
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
vec3 k[9];
|
|
354
|
-
k[0] = src[0];
|
|
355
|
-
k[1] = (rot == 0) ? src[1] : (rot == 1) ? src[7] : (rot == 2) ? src[5] : src[3];
|
|
356
|
-
k[2] = (rot == 0) ? src[2] : (rot == 1) ? src[8] : (rot == 2) ? src[6] : src[4];
|
|
357
|
-
k[3] = (rot == 0) ? src[3] : (rot == 1) ? src[1] : (rot == 2) ? src[7] : src[5];
|
|
358
|
-
k[4] = (rot == 0) ? src[4] : (rot == 1) ? src[2] : (rot == 2) ? src[8] : src[6];
|
|
359
|
-
k[5] = (rot == 0) ? src[5] : (rot == 1) ? src[3] : (rot == 2) ? src[1] : src[7];
|
|
360
|
-
k[6] = (rot == 0) ? src[6] : (rot == 1) ? src[4] : (rot == 2) ? src[2] : src[8];
|
|
361
|
-
k[7] = (rot == 0) ? src[7] : (rot == 1) ? src[5] : (rot == 2) ? src[3] : src[1];
|
|
362
|
-
k[8] = (rot == 0) ? src[8] : (rot == 1) ? src[6] : (rot == 2) ? src[4] : src[2];
|
|
363
|
-
|
|
364
|
-
float vk0 = reduce(k[0]);
|
|
365
|
-
float vk4 = reduce(k[4]);
|
|
366
|
-
float vk5 = reduce(k[5]);
|
|
367
|
-
float vk7 = reduce(k[7]);
|
|
368
|
-
float vk8 = reduce(k[8]);
|
|
369
|
-
|
|
370
|
-
float dist_01_04 = distYCbCr(k[1], k[4]);
|
|
371
|
-
float dist_03_08 = distYCbCr(k[3], k[8]);
|
|
372
|
-
bool haveShallowLine = (uSteepDirectionThreshold * dist_01_04 <= dist_03_08) && (vk0 != vk4) && (vk5 != vk4);
|
|
373
|
-
bool haveSteepLine = (uSteepDirectionThreshold * dist_03_08 <= dist_01_04) && (vk0 != vk8) && (vk7 != vk8);
|
|
374
|
-
bool needBlend = (blend[2] != BLEND_NONE);
|
|
375
|
-
bool doLineBlend = blend[2] >= BLEND_DOMINANT ||
|
|
376
|
-
!((blend[1] != BLEND_NONE && !isPixEqual(k[0], k[4])) ||
|
|
377
|
-
(blend[3] != BLEND_NONE && !isPixEqual(k[0], k[8])) ||
|
|
378
|
-
(isPixEqual(k[4], k[3]) && isPixEqual(k[3], k[2]) &&
|
|
379
|
-
isPixEqual(k[2], k[1]) && isPixEqual(k[1], k[8]) && !isPixEqual(k[0], k[2])));
|
|
380
|
-
|
|
381
|
-
vec3 blendPix = (distYCbCr(k[0], k[1]) <= distYCbCr(k[0], k[3])) ? k[1] : k[3];
|
|
382
|
-
|
|
383
|
-
// 4x layout with rotation
|
|
384
|
-
// Output mapping: 06|07|08|09
|
|
385
|
-
// 05|00|01|10
|
|
386
|
-
// 04|03|02|11
|
|
387
|
-
// 15|14|13|12
|
|
388
|
-
|
|
389
|
-
const float NOLINEBLEND = 0.08677704501;
|
|
390
|
-
const float LINEBLEND_CENTER = 0.6848532563;
|
|
391
|
-
|
|
392
|
-
// Apply blends based on rotation
|
|
393
|
-
if (rot == 0) {
|
|
394
|
-
dst[2] = mix(dst[2], blendPix, (needBlend && doLineBlend) ?
|
|
395
|
-
(haveShallowLine ? (haveSteepLine ? 1.0/3.0 : 0.25) : (haveSteepLine ? 0.25 : 0.0)) : 0.0);
|
|
396
|
-
dst[9] = mix(dst[9], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0);
|
|
397
|
-
dst[10] = mix(dst[10], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.75 : 0.0);
|
|
398
|
-
dst[11] = mix(dst[11], blendPix, needBlend ? (doLineBlend ?
|
|
399
|
-
(haveSteepLine ? 1.0 : (haveShallowLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
|
|
400
|
-
dst[12] = mix(dst[12], blendPix, needBlend ? (doLineBlend ? 1.0 : LINEBLEND_CENTER) : 0.0);
|
|
401
|
-
dst[13] = mix(dst[13], blendPix, needBlend ? (doLineBlend ?
|
|
402
|
-
(haveShallowLine ? 1.0 : (haveSteepLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
|
|
403
|
-
dst[14] = mix(dst[14], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.75 : 0.0);
|
|
404
|
-
dst[15] = mix(dst[15], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0);
|
|
405
|
-
} else if (rot == 1) {
|
|
406
|
-
dst[1] = mix(dst[1], blendPix, (needBlend && doLineBlend) ?
|
|
407
|
-
(haveShallowLine ? (haveSteepLine ? 1.0/3.0 : 0.25) : (haveSteepLine ? 0.25 : 0.0)) : 0.0);
|
|
408
|
-
dst[6] = mix(dst[6], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0);
|
|
409
|
-
dst[7] = mix(dst[7], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.75 : 0.0);
|
|
410
|
-
dst[8] = mix(dst[8], blendPix, needBlend ? (doLineBlend ?
|
|
411
|
-
(haveSteepLine ? 1.0 : (haveShallowLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
|
|
412
|
-
dst[9] = mix(dst[9], blendPix, needBlend ? (doLineBlend ? 1.0 : LINEBLEND_CENTER) : 0.0);
|
|
413
|
-
dst[10] = mix(dst[10], blendPix, needBlend ? (doLineBlend ?
|
|
414
|
-
(haveShallowLine ? 1.0 : (haveSteepLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
|
|
415
|
-
dst[11] = mix(dst[11], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.75 : 0.0);
|
|
416
|
-
dst[12] = mix(dst[12], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0);
|
|
417
|
-
} else if (rot == 2) {
|
|
418
|
-
dst[0] = mix(dst[0], blendPix, (needBlend && doLineBlend) ?
|
|
419
|
-
(haveShallowLine ? (haveSteepLine ? 1.0/3.0 : 0.25) : (haveSteepLine ? 0.25 : 0.0)) : 0.0);
|
|
420
|
-
dst[15] = mix(dst[15], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0);
|
|
421
|
-
dst[4] = mix(dst[4], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.75 : 0.0);
|
|
422
|
-
dst[5] = mix(dst[5], blendPix, needBlend ? (doLineBlend ?
|
|
423
|
-
(haveSteepLine ? 1.0 : (haveShallowLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
|
|
424
|
-
dst[6] = mix(dst[6], blendPix, needBlend ? (doLineBlend ? 1.0 : LINEBLEND_CENTER) : 0.0);
|
|
425
|
-
dst[7] = mix(dst[7], blendPix, needBlend ? (doLineBlend ?
|
|
426
|
-
(haveShallowLine ? 1.0 : (haveSteepLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
|
|
427
|
-
dst[8] = mix(dst[8], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.75 : 0.0);
|
|
428
|
-
dst[9] = mix(dst[9], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0);
|
|
429
|
-
} else {
|
|
430
|
-
dst[3] = mix(dst[3], blendPix, (needBlend && doLineBlend) ?
|
|
431
|
-
(haveShallowLine ? (haveSteepLine ? 1.0/3.0 : 0.25) : (haveSteepLine ? 0.25 : 0.0)) : 0.0);
|
|
432
|
-
dst[12] = mix(dst[12], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0);
|
|
433
|
-
dst[13] = mix(dst[13], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.75 : 0.0);
|
|
434
|
-
dst[14] = mix(dst[14], blendPix, needBlend ? (doLineBlend ?
|
|
435
|
-
(haveSteepLine ? 1.0 : (haveShallowLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
|
|
436
|
-
dst[15] = mix(dst[15], blendPix, needBlend ? (doLineBlend ? 1.0 : LINEBLEND_CENTER) : 0.0);
|
|
437
|
-
dst[4] = mix(dst[4], blendPix, needBlend ? (doLineBlend ?
|
|
438
|
-
(haveShallowLine ? 1.0 : (haveSteepLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
|
|
439
|
-
dst[5] = mix(dst[5], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.75 : 0.0);
|
|
440
|
-
dst[6] = mix(dst[6], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
208
|
+
vec3 blendPix = (DistYCbCr(src[0], k1) <= DistYCbCr(src[0], k3)) ? k1 : k3;
|
|
209
|
+
float b1 = (needBlend && doLineBlend) ? (haveSteepLine ? 0.75 : (haveShallowLine ? 0.25 : 0.125)) : 0.0;
|
|
210
|
+
float b2 = needBlend ? (doLineBlend ? ((!haveShallowLine && !haveSteepLine) ? 0.875 : 1.0) : 0.4545939598) : 0.0;
|
|
211
|
+
float b3 = (needBlend && doLineBlend) ? (haveShallowLine ? 0.75 : (haveSteepLine ? 0.25 : 0.125)) : 0.0;
|
|
212
|
+
float b4 = (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0;
|
|
213
|
+
float b8 = (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0;
|
|
443
214
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
215
|
+
if (corner == 0) { dst[1]=mix(dst[1],blendPix,b1); dst[2]=mix(dst[2],blendPix,b2); dst[3]=mix(dst[3],blendPix,b3); dst[4]=mix(dst[4],blendPix,b4); dst[8]=mix(dst[8],blendPix,b8); }
|
|
216
|
+
else if (corner == 1) { dst[7]=mix(dst[7],blendPix,b1); dst[8]=mix(dst[8],blendPix,b2); dst[1]=mix(dst[1],blendPix,b3); dst[2]=mix(dst[2],blendPix,b4); dst[6]=mix(dst[6],blendPix,b8); }
|
|
217
|
+
else if (corner == 2) { dst[5]=mix(dst[5],blendPix,b1); dst[6]=mix(dst[6],blendPix,b2); dst[7]=mix(dst[7],blendPix,b3); dst[8]=mix(dst[8],blendPix,b4); dst[4]=mix(dst[4],blendPix,b8); }
|
|
218
|
+
else { dst[3]=mix(dst[3],blendPix,b1); dst[4]=mix(dst[4],blendPix,b2); dst[5]=mix(dst[5],blendPix,b3); dst[6]=mix(dst[6],blendPix,b4); dst[2]=mix(dst[2],blendPix,b8); }
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
vec3 res = mix(mix(dst[6], mix(dst[7], dst[8], step(two_third, f.x)), step(one_third, f.x)),
|
|
223
|
+
mix(mix(dst[5], mix(dst[0], dst[1], step(two_third, f.x)), step(one_third, f.x)),
|
|
224
|
+
mix(dst[4], mix(dst[3], dst[2], step(two_third, f.x)), step(one_third, f.x)), step(two_third, f.y)),
|
|
225
|
+
step(one_third, f.y));
|
|
226
|
+
outColor = vec4(res, tex(tc).a);
|
|
227
|
+
}`;
|
|
228
|
+
|
|
229
|
+
// 4x xBRZ fragment shader
|
|
230
|
+
const FRAG_4X = COMMON_HEADER + `
|
|
231
|
+
void main() {
|
|
232
|
+
vec2 texelSize = 1.0 / uInputRes;
|
|
233
|
+
vec2 srcPos = vUv * uInputRes;
|
|
234
|
+
vec2 srcPixel = floor(srcPos);
|
|
235
|
+
vec2 f = fract(srcPos);
|
|
236
|
+
vec2 tc = (srcPixel + 0.5) * texelSize;
|
|
237
|
+
|
|
238
|
+
vec3 src[25]; float v[9];
|
|
239
|
+
sampleNeighborhood(tc, texelSize.x, texelSize.y, src, v);
|
|
240
|
+
ivec4 blendResult = detectCorners(src, v);
|
|
241
|
+
|
|
242
|
+
// Output: 06|07|08|09 / 05|00|01|10 / 04|03|02|11 / 15|14|13|12
|
|
243
|
+
vec3 dst[16];
|
|
244
|
+
for (int i = 0; i < 16; i++) dst[i] = src[0];
|
|
245
|
+
|
|
246
|
+
if (IsBlendingNeeded(blendResult)) {
|
|
247
|
+
for (int corner = 0; corner < 4; corner++) {
|
|
248
|
+
vec3 k1, k3, k4, k5, k7, k8;
|
|
249
|
+
int blendIdx;
|
|
250
|
+
if (corner == 0) { k1=src[1]; k3=src[3]; k4=src[4]; k5=src[5]; k7=src[7]; k8=src[8]; blendIdx=2; }
|
|
251
|
+
else if (corner == 1) { k1=src[7]; k3=src[1]; k4=src[2]; k5=src[3]; k7=src[5]; k8=src[6]; blendIdx=1; }
|
|
252
|
+
else if (corner == 2) { k1=src[5]; k3=src[7]; k4=src[8]; k5=src[1]; k7=src[3]; k8=src[4]; blendIdx=0; }
|
|
253
|
+
else { k1=src[3]; k3=src[5]; k4=src[6]; k5=src[7]; k7=src[1]; k8=src[2]; blendIdx=3; }
|
|
448
254
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
255
|
+
float vk0 = v[0], vk4 = reduce(k4), vk5 = reduce(k5), vk7 = reduce(k7), vk8 = reduce(k8);
|
|
256
|
+
float dist_01_04 = DistYCbCr(k1, k4);
|
|
257
|
+
float dist_03_08 = DistYCbCr(k3, k8);
|
|
258
|
+
bool haveShallowLine = (uSteepDirectionThreshold * dist_01_04 <= dist_03_08) && (vk0 != vk4) && (vk5 != vk4);
|
|
259
|
+
bool haveSteepLine = (uSteepDirectionThreshold * dist_03_08 <= dist_01_04) && (vk0 != vk8) && (vk7 != vk8);
|
|
260
|
+
bool needBlend = (blendResult[blendIdx] != BLEND_NONE);
|
|
261
|
+
bool doLineBlend = blendResult[blendIdx] >= BLEND_DOMINANT ||
|
|
262
|
+
!((blendResult[(blendIdx+3)%4] != BLEND_NONE && !IsPixEqual(src[0], k4)) ||
|
|
263
|
+
(blendResult[(blendIdx+1)%4] != BLEND_NONE && !IsPixEqual(src[0], k8)) ||
|
|
264
|
+
(IsPixEqual(k4, k3) && IsPixEqual(k3, k8) && !IsPixEqual(src[0], k3)));
|
|
454
265
|
|
|
455
|
-
|
|
456
|
-
int xi = clamp(int(fx), 0, 3);
|
|
457
|
-
int yi = clamp(int(fy), 0, 3);
|
|
458
|
-
float xf = fract(fx);
|
|
459
|
-
float yf = fract(fy);
|
|
266
|
+
vec3 blendPix = (DistYCbCr(src[0], k1) <= DistYCbCr(src[0], k3)) ? k1 : k3;
|
|
460
267
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
268
|
+
// 4x specific blend weights
|
|
269
|
+
float bCorner = (needBlend && doLineBlend) ? (haveShallowLine ? (haveSteepLine ? 1.0/3.0 : 0.25) : (haveSteepLine ? 0.25 : 0.0)) : 0.0;
|
|
270
|
+
float bSteep1 = (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0;
|
|
271
|
+
float bSteep2 = (needBlend && doLineBlend && haveSteepLine) ? 0.75 : 0.0;
|
|
272
|
+
float bEdge1 = needBlend ? (doLineBlend ? (haveSteepLine ? 1.0 : (haveShallowLine ? 0.75 : 0.5)) : 0.08677704501) : 0.0;
|
|
273
|
+
float bCenter = needBlend ? (doLineBlend ? 1.0 : 0.6848532563) : 0.0;
|
|
274
|
+
float bEdge2 = needBlend ? (doLineBlend ? (haveShallowLine ? 1.0 : (haveSteepLine ? 0.75 : 0.5)) : 0.08677704501) : 0.0;
|
|
275
|
+
float bShallow1 = (needBlend && doLineBlend && haveShallowLine) ? 0.75 : 0.0;
|
|
276
|
+
float bShallow2 = (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0;
|
|
465
277
|
|
|
466
|
-
|
|
467
|
-
|
|
278
|
+
if (corner == 0) {
|
|
279
|
+
dst[2]=mix(dst[2],blendPix,bCorner); dst[9]=mix(dst[9],blendPix,bSteep1); dst[10]=mix(dst[10],blendPix,bSteep2);
|
|
280
|
+
dst[11]=mix(dst[11],blendPix,bEdge1); dst[12]=mix(dst[12],blendPix,bCenter); dst[13]=mix(dst[13],blendPix,bEdge2);
|
|
281
|
+
dst[14]=mix(dst[14],blendPix,bShallow1); dst[15]=mix(dst[15],blendPix,bShallow2);
|
|
282
|
+
} else if (corner == 1) {
|
|
283
|
+
dst[1]=mix(dst[1],blendPix,bCorner); dst[6]=mix(dst[6],blendPix,bSteep1); dst[7]=mix(dst[7],blendPix,bSteep2);
|
|
284
|
+
dst[8]=mix(dst[8],blendPix,bEdge1); dst[9]=mix(dst[9],blendPix,bCenter); dst[10]=mix(dst[10],blendPix,bEdge2);
|
|
285
|
+
dst[11]=mix(dst[11],blendPix,bShallow1); dst[12]=mix(dst[12],blendPix,bShallow2);
|
|
286
|
+
} else if (corner == 2) {
|
|
287
|
+
dst[0]=mix(dst[0],blendPix,bCorner); dst[15]=mix(dst[15],blendPix,bSteep1); dst[4]=mix(dst[4],blendPix,bSteep2);
|
|
288
|
+
dst[5]=mix(dst[5],blendPix,bEdge1); dst[6]=mix(dst[6],blendPix,bCenter); dst[7]=mix(dst[7],blendPix,bEdge2);
|
|
289
|
+
dst[8]=mix(dst[8],blendPix,bShallow1); dst[9]=mix(dst[9],blendPix,bShallow2);
|
|
290
|
+
} else {
|
|
291
|
+
dst[3]=mix(dst[3],blendPix,bCorner); dst[12]=mix(dst[12],blendPix,bSteep1); dst[13]=mix(dst[13],blendPix,bSteep2);
|
|
292
|
+
dst[14]=mix(dst[14],blendPix,bEdge1); dst[15]=mix(dst[15],blendPix,bCenter); dst[4]=mix(dst[4],blendPix,bEdge2);
|
|
293
|
+
dst[5]=mix(dst[5],blendPix,bShallow1); dst[6]=mix(dst[6],blendPix,bShallow2);
|
|
294
|
+
}
|
|
468
295
|
}
|
|
469
296
|
}
|
|
470
297
|
|
|
471
|
-
|
|
298
|
+
vec3 res = mix(mix(mix(mix(dst[6], dst[7], step(0.25, f.x)), mix(dst[8], dst[9], step(0.75, f.x)), step(0.5, f.x)),
|
|
299
|
+
mix(mix(dst[5], dst[0], step(0.25, f.x)), mix(dst[1], dst[10], step(0.75, f.x)), step(0.5, f.x)), step(0.25, f.y)),
|
|
300
|
+
mix(mix(mix(dst[4], dst[3], step(0.25, f.x)), mix(dst[2], dst[11], step(0.75, f.x)), step(0.5, f.x)),
|
|
301
|
+
mix(mix(dst[15], dst[14], step(0.25, f.x)), mix(dst[13], dst[12], step(0.75, f.x)), step(0.5, f.x)), step(0.75, f.y)),
|
|
302
|
+
step(0.5, f.y));
|
|
303
|
+
outColor = vec4(res, tex(tc).a);
|
|
472
304
|
}`;
|
|
473
305
|
|
|
306
|
+
// 5x and 6x use 4x with interpolation
|
|
307
|
+
const FRAG_5X = FRAG_4X;
|
|
308
|
+
const FRAG_6X = FRAG_4X;
|
|
309
|
+
|
|
474
310
|
/** xBRZ GPU Renderer */
|
|
475
311
|
export class XbrzGpuRenderer implements Renderer<XbrzOptions> {
|
|
476
312
|
private gl: WebGL2RenderingContext | null = null;
|
|
477
313
|
private canvas: OffscreenCanvas | null = null;
|
|
478
|
-
private
|
|
314
|
+
private programs: Map<number, WebGLProgram> = new Map();
|
|
479
315
|
private texture: WebGLTexture | null = null;
|
|
480
|
-
private uniforms: Record<string, WebGLUniformLocation | null
|
|
316
|
+
private uniforms: Map<number, Record<string, WebGLUniformLocation | null>> = new Map();
|
|
481
317
|
private initialized = false;
|
|
482
318
|
private currentCanvasSize = { width: 0, height: 0 };
|
|
483
319
|
private currentTexSize = { width: 0, height: 0 };
|
|
320
|
+
private currentScale = 0;
|
|
484
321
|
|
|
485
|
-
/** Create a new xBRZ GPU renderer */
|
|
486
322
|
static create(): XbrzGpuRenderer {
|
|
487
323
|
const renderer = new XbrzGpuRenderer();
|
|
488
324
|
renderer.init();
|
|
@@ -490,72 +326,46 @@ export class XbrzGpuRenderer implements Renderer<XbrzOptions> {
|
|
|
490
326
|
}
|
|
491
327
|
|
|
492
328
|
private init(): void {
|
|
493
|
-
if (typeof OffscreenCanvas === 'undefined')
|
|
494
|
-
throw new Error('OffscreenCanvas not supported');
|
|
495
|
-
}
|
|
496
|
-
|
|
329
|
+
if (typeof OffscreenCanvas === 'undefined') throw new Error('OffscreenCanvas not supported');
|
|
497
330
|
this.canvas = new OffscreenCanvas(1, 1);
|
|
498
|
-
this.gl = this.canvas.getContext('webgl2', {
|
|
499
|
-
|
|
500
|
-
premultipliedAlpha: false,
|
|
501
|
-
desynchronized: true,
|
|
502
|
-
powerPreference: 'high-performance',
|
|
503
|
-
antialias: false,
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
if (!this.gl) {
|
|
507
|
-
throw new Error('WebGL2 not supported');
|
|
508
|
-
}
|
|
331
|
+
this.gl = this.canvas.getContext('webgl2', { alpha: true, premultipliedAlpha: false, desynchronized: true, powerPreference: 'high-performance', antialias: false });
|
|
332
|
+
if (!this.gl) throw new Error('WebGL2 not supported');
|
|
509
333
|
|
|
510
334
|
const gl = this.gl;
|
|
511
|
-
|
|
512
|
-
// Create shaders
|
|
513
335
|
const vs = this.createShader(gl.VERTEX_SHADER, VERTEX_SHADER);
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
336
|
+
const shaders: Record<number, string> = { 2: FRAG_2X, 3: FRAG_3X, 4: FRAG_4X, 5: FRAG_5X, 6: FRAG_6X };
|
|
337
|
+
|
|
338
|
+
for (const [scale, fragSource] of Object.entries(shaders)) {
|
|
339
|
+
const fs = this.createShader(gl.FRAGMENT_SHADER, fragSource);
|
|
340
|
+
const program = gl.createProgram()!;
|
|
341
|
+
gl.attachShader(program, vs);
|
|
342
|
+
gl.attachShader(program, fs);
|
|
343
|
+
gl.linkProgram(program);
|
|
344
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error(`Shader link failed for ${scale}x: ` + gl.getProgramInfoLog(program));
|
|
345
|
+
this.programs.set(Number(scale), program);
|
|
346
|
+
this.uniforms.set(Number(scale), {
|
|
347
|
+
uTex: gl.getUniformLocation(program, 'uTex'),
|
|
348
|
+
uInputRes: gl.getUniformLocation(program, 'uInputRes'),
|
|
349
|
+
uLuminanceWeight: gl.getUniformLocation(program, 'uLuminanceWeight'),
|
|
350
|
+
uEqualColorTolerance: gl.getUniformLocation(program, 'uEqualColorTolerance'),
|
|
351
|
+
uSteepDirectionThreshold: gl.getUniformLocation(program, 'uSteepDirectionThreshold'),
|
|
352
|
+
uDominantDirectionThreshold: gl.getUniformLocation(program, 'uDominantDirectionThreshold'),
|
|
353
|
+
});
|
|
524
354
|
}
|
|
525
355
|
|
|
526
|
-
gl.useProgram(this.program);
|
|
527
|
-
|
|
528
|
-
// Get uniform locations
|
|
529
|
-
this.uniforms = {
|
|
530
|
-
uTex: gl.getUniformLocation(this.program, 'uTex'),
|
|
531
|
-
uInputRes: gl.getUniformLocation(this.program, 'uInputRes'),
|
|
532
|
-
uOutputRes: gl.getUniformLocation(this.program, 'uOutputRes'),
|
|
533
|
-
uScale: gl.getUniformLocation(this.program, 'uScale'),
|
|
534
|
-
uLuminanceWeight: gl.getUniformLocation(this.program, 'uLuminanceWeight'),
|
|
535
|
-
uEqualColorTolerance: gl.getUniformLocation(this.program, 'uEqualColorTolerance'),
|
|
536
|
-
uSteepDirectionThreshold: gl.getUniformLocation(this.program, 'uSteepDirectionThreshold'),
|
|
537
|
-
uDominantDirectionThreshold: gl.getUniformLocation(this.program, 'uDominantDirectionThreshold'),
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
gl.uniform1i(this.uniforms.uTex, 0);
|
|
541
|
-
|
|
542
|
-
// Setup geometry (fullscreen triangle)
|
|
543
356
|
const buf = gl.createBuffer();
|
|
544
357
|
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
|
545
358
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
|
|
546
359
|
gl.enableVertexAttribArray(0);
|
|
547
360
|
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
548
361
|
|
|
549
|
-
// Create texture with NEAREST filtering for pixel art
|
|
550
362
|
this.texture = gl.createTexture();
|
|
551
363
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
552
364
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
553
365
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
554
366
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
555
367
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
556
|
-
|
|
557
368
|
gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
|
558
|
-
|
|
559
369
|
this.initialized = true;
|
|
560
370
|
}
|
|
561
371
|
|
|
@@ -564,37 +374,36 @@ export class XbrzGpuRenderer implements Renderer<XbrzOptions> {
|
|
|
564
374
|
const shader = gl.createShader(type)!;
|
|
565
375
|
gl.shaderSource(shader, source);
|
|
566
376
|
gl.compileShader(shader);
|
|
567
|
-
|
|
568
377
|
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
569
378
|
const info = gl.getShaderInfoLog(shader);
|
|
570
379
|
gl.deleteShader(shader);
|
|
571
380
|
throw new Error('Shader compile failed: ' + info);
|
|
572
381
|
}
|
|
573
|
-
|
|
574
382
|
return shader;
|
|
575
383
|
}
|
|
576
384
|
|
|
577
|
-
|
|
578
|
-
isReady(): boolean {
|
|
579
|
-
return this.initialized;
|
|
580
|
-
}
|
|
385
|
+
isReady(): boolean { return this.initialized; }
|
|
581
386
|
|
|
582
|
-
/** Render xBRZ effect */
|
|
583
387
|
render(input: ImageInput | ImageData, options: XbrzOptions = {}): ImageOutput {
|
|
584
|
-
if (!this.initialized || !this.gl || !this.canvas)
|
|
585
|
-
throw new Error('Renderer not initialized');
|
|
586
|
-
}
|
|
587
|
-
|
|
388
|
+
if (!this.initialized || !this.gl || !this.canvas) throw new Error('Renderer not initialized');
|
|
588
389
|
const gl = this.gl;
|
|
589
390
|
const data = input instanceof ImageData ? input.data : input.data;
|
|
590
391
|
const width = input.width;
|
|
591
392
|
const height = input.height;
|
|
592
|
-
|
|
593
393
|
const scale = Math.min(6, Math.max(2, options.scale ?? 2));
|
|
594
394
|
const outWidth = width * scale;
|
|
595
395
|
const outHeight = height * scale;
|
|
596
396
|
|
|
597
|
-
|
|
397
|
+
const program = this.programs.get(scale);
|
|
398
|
+
const uniforms = this.uniforms.get(scale);
|
|
399
|
+
if (!program || !uniforms) throw new Error(`No program for scale ${scale}`);
|
|
400
|
+
|
|
401
|
+
if (this.currentScale !== scale) {
|
|
402
|
+
gl.useProgram(program);
|
|
403
|
+
gl.uniform1i(uniforms.uTex, 0);
|
|
404
|
+
this.currentScale = scale;
|
|
405
|
+
}
|
|
406
|
+
|
|
598
407
|
if (this.currentCanvasSize.width !== outWidth || this.currentCanvasSize.height !== outHeight) {
|
|
599
408
|
this.canvas.width = outWidth;
|
|
600
409
|
this.canvas.height = outHeight;
|
|
@@ -602,16 +411,12 @@ export class XbrzGpuRenderer implements Renderer<XbrzOptions> {
|
|
|
602
411
|
gl.viewport(0, 0, outWidth, outHeight);
|
|
603
412
|
}
|
|
604
413
|
|
|
605
|
-
|
|
606
|
-
gl.
|
|
607
|
-
gl.
|
|
608
|
-
gl.
|
|
609
|
-
gl.uniform1f(
|
|
610
|
-
gl.uniform1f(this.uniforms.uEqualColorTolerance, (options.equalColorTolerance ?? 30) / 255.0);
|
|
611
|
-
gl.uniform1f(this.uniforms.uSteepDirectionThreshold, options.steepDirectionThreshold ?? 2.2);
|
|
612
|
-
gl.uniform1f(this.uniforms.uDominantDirectionThreshold, options.dominantDirectionThreshold ?? 3.6);
|
|
414
|
+
gl.uniform2f(uniforms.uInputRes, width, height);
|
|
415
|
+
gl.uniform1f(uniforms.uLuminanceWeight, options.luminanceWeight ?? 1.0);
|
|
416
|
+
gl.uniform1f(uniforms.uEqualColorTolerance, (options.equalColorTolerance ?? 30) / 255.0);
|
|
417
|
+
gl.uniform1f(uniforms.uSteepDirectionThreshold, options.steepDirectionThreshold ?? 2.2);
|
|
418
|
+
gl.uniform1f(uniforms.uDominantDirectionThreshold, options.dominantDirectionThreshold ?? 3.6);
|
|
613
419
|
|
|
614
|
-
// Update texture
|
|
615
420
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
616
421
|
if (this.currentTexSize.width !== width || this.currentTexSize.height !== height) {
|
|
617
422
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
|
|
@@ -620,52 +425,30 @@ export class XbrzGpuRenderer implements Renderer<XbrzOptions> {
|
|
|
620
425
|
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data);
|
|
621
426
|
}
|
|
622
427
|
|
|
623
|
-
// Render
|
|
624
428
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
625
429
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
626
430
|
|
|
627
|
-
// Read pixels
|
|
628
431
|
const pixels = new Uint8ClampedArray(outWidth * outHeight * 4);
|
|
629
432
|
gl.readPixels(0, 0, outWidth, outHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
630
|
-
|
|
631
|
-
return {
|
|
632
|
-
data: pixels,
|
|
633
|
-
width: outWidth,
|
|
634
|
-
height: outHeight,
|
|
635
|
-
};
|
|
433
|
+
return { data: pixels, width: outWidth, height: outHeight };
|
|
636
434
|
}
|
|
637
435
|
|
|
638
|
-
/** Dispose resources */
|
|
639
436
|
dispose(): void {
|
|
640
437
|
if (this.gl) {
|
|
641
438
|
if (this.texture) this.gl.deleteTexture(this.texture);
|
|
642
|
-
|
|
439
|
+
for (const program of this.programs.values()) this.gl.deleteProgram(program);
|
|
643
440
|
this.gl = null;
|
|
644
441
|
}
|
|
645
442
|
this.canvas = null;
|
|
443
|
+
this.programs.clear();
|
|
444
|
+
this.uniforms.clear();
|
|
646
445
|
this.initialized = false;
|
|
647
446
|
}
|
|
648
447
|
}
|
|
649
448
|
|
|
650
|
-
/** xBRZ presets */
|
|
651
449
|
export const XBRZ_PRESETS: Record<string, Partial<XbrzOptions>> = {
|
|
652
450
|
default: {},
|
|
653
|
-
sharp: {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
steepDirectionThreshold: 2.0,
|
|
657
|
-
dominantDirectionThreshold: 3.2,
|
|
658
|
-
},
|
|
659
|
-
smooth: {
|
|
660
|
-
luminanceWeight: 1.0,
|
|
661
|
-
equalColorTolerance: 40,
|
|
662
|
-
steepDirectionThreshold: 2.4,
|
|
663
|
-
dominantDirectionThreshold: 4.0,
|
|
664
|
-
},
|
|
665
|
-
colorful: {
|
|
666
|
-
luminanceWeight: 0.5,
|
|
667
|
-
equalColorTolerance: 30,
|
|
668
|
-
steepDirectionThreshold: 2.2,
|
|
669
|
-
dominantDirectionThreshold: 3.6,
|
|
670
|
-
},
|
|
451
|
+
sharp: { luminanceWeight: 1.0, equalColorTolerance: 20, steepDirectionThreshold: 2.0, dominantDirectionThreshold: 3.2 },
|
|
452
|
+
smooth: { luminanceWeight: 1.0, equalColorTolerance: 40, steepDirectionThreshold: 2.4, dominantDirectionThreshold: 4.0 },
|
|
453
|
+
colorful: { luminanceWeight: 0.5, equalColorTolerance: 30, steepDirectionThreshold: 2.2, dominantDirectionThreshold: 3.6 },
|
|
671
454
|
};
|