@johnfmorton/some-shade 0.1.0-beta.9 → 0.1.1-beta
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/CHANGELOG.md +14 -0
- package/README.md +0 -17
- package/dist/index.d.ts +21 -3
- package/dist/some-shade.cjs.js +137 -104
- package/dist/some-shade.es.js +407 -288
- package/dist/some-shade.umd.js +138 -105
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1-beta
|
|
4
|
+
|
|
5
|
+
- Fix halftone-duotone and halftone-cmyk shaders to sample at grid cell center — dots are now clean, uniform circles instead of splotchy per-pixel noise
|
|
6
|
+
|
|
7
|
+
## 0.1.0-beta.11
|
|
8
|
+
|
|
9
|
+
- Add grid angle option to dot grid effect (`angle` attribute)
|
|
10
|
+
- Add loading blur option (`loading-blur` attribute) — source image displays blurred until the WebGL effect resolves
|
|
11
|
+
- Add `replayTransition()` public method for programmatically replaying the loading transition
|
|
12
|
+
|
|
13
|
+
## 0.1.0-beta.10
|
|
14
|
+
|
|
15
|
+
- **BREAKING:** Remove pixel sort effect (`pixel-sort`, `threshold`, `sort-direction`, `sort-span` attributes removed)
|
|
16
|
+
|
|
3
17
|
## 0.1.0-beta.9
|
|
4
18
|
|
|
5
19
|
- Fade transition when processed snapshot replaces the source image on scroll
|
package/README.md
CHANGED
|
@@ -59,20 +59,6 @@ Halftone effect using a single custom color.
|
|
|
59
59
|
></some-shade-image>
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
### Pixel Sort
|
|
63
|
-
|
|
64
|
-
Sorts pixels by luminance along a configurable direction.
|
|
65
|
-
|
|
66
|
-
```html
|
|
67
|
-
<some-shade-image
|
|
68
|
-
src="photo.jpg"
|
|
69
|
-
effect="pixel-sort"
|
|
70
|
-
threshold="0.5"
|
|
71
|
-
sort-direction="0"
|
|
72
|
-
sort-span="64"
|
|
73
|
-
></some-shade-image>
|
|
74
|
-
```
|
|
75
|
-
|
|
76
62
|
### Dot Grid
|
|
77
63
|
|
|
78
64
|
Renders the image as a grid of dots with a customizable background.
|
|
@@ -103,9 +89,6 @@ Renders the image as a grid of dots with a customizable background.
|
|
|
103
89
|
| `angle-k` | number | `45` | halftone-cmyk |
|
|
104
90
|
| `duotone-color` | string (hex) | `"#0099cc"` | halftone-duotone |
|
|
105
91
|
| `angle` | number | `0` | halftone-duotone |
|
|
106
|
-
| `threshold` | number | `0.5` | pixel-sort |
|
|
107
|
-
| `sort-direction` | number | `0` | pixel-sort |
|
|
108
|
-
| `sort-span` | number | `64` | pixel-sort |
|
|
109
92
|
| `dot-offset-x` | number | `0.5` | dot-grid |
|
|
110
93
|
| `dot-offset-y` | number | `0.5` | dot-grid |
|
|
111
94
|
| `bg-color` | string (hex) | `"#ffffff"` | dot-grid |
|
package/dist/index.d.ts
CHANGED
|
@@ -26,21 +26,34 @@ export declare class SomeShadeImage extends LitElement {
|
|
|
26
26
|
angleM: number;
|
|
27
27
|
angleY: number;
|
|
28
28
|
angleK: number;
|
|
29
|
+
showC: number;
|
|
30
|
+
showM: number;
|
|
31
|
+
showY: number;
|
|
32
|
+
showK: number;
|
|
29
33
|
duotoneColor: string;
|
|
30
34
|
angle: number;
|
|
31
|
-
threshold: number;
|
|
32
|
-
sortDirection: number;
|
|
33
|
-
sortSpan: number;
|
|
34
35
|
dotOffsetX: number;
|
|
35
36
|
dotOffsetY: number;
|
|
36
37
|
bgColor: string;
|
|
38
|
+
angleWarm: number;
|
|
39
|
+
angleCool: number;
|
|
40
|
+
showWarm: number;
|
|
41
|
+
showCool: number;
|
|
42
|
+
warmColor: string;
|
|
43
|
+
coolColor: string;
|
|
44
|
+
gateWeave: number;
|
|
45
|
+
loadingBlur: number;
|
|
37
46
|
private _webglAvailable;
|
|
38
47
|
private _snapshotUrl;
|
|
39
48
|
private _snapshotLoaded;
|
|
40
49
|
private _image;
|
|
41
50
|
private _observer;
|
|
51
|
+
private _resizeObserver;
|
|
52
|
+
private _lastClientWidth;
|
|
42
53
|
private _visible;
|
|
43
54
|
private _needsRender;
|
|
55
|
+
private _gateWeaveRaf;
|
|
56
|
+
private _gateWeaveTime;
|
|
44
57
|
render(): TemplateResult<1>;
|
|
45
58
|
connectedCallback(): void;
|
|
46
59
|
updated(changed: PropertyValues): void;
|
|
@@ -49,8 +62,13 @@ export declare class SomeShadeImage extends LitElement {
|
|
|
49
62
|
private _scheduleRender;
|
|
50
63
|
private _renderEffect;
|
|
51
64
|
private _onSnapshotLoad;
|
|
65
|
+
/** Hide the rendered snapshot momentarily, then fade it back in.
|
|
66
|
+
* Useful for previewing the loading-blur transition. */
|
|
67
|
+
replayTransition(delay?: number): void;
|
|
52
68
|
private _revokeSnapshot;
|
|
53
69
|
private _getUniformValues;
|
|
70
|
+
private _updateGateWeave;
|
|
71
|
+
private _stopGateWeave;
|
|
54
72
|
private _parseHexColor;
|
|
55
73
|
}
|
|
56
74
|
|
package/dist/some-shade.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const _=require("lit"),l=require("lit/decorators.js");function L(e){return e.getContext("webgl",{alpha:!0,premultipliedAlpha:!1,preserveDrawingBuffer:!0})}function E(e,o,r){const a=e.createShader(o);if(!a)throw new Error("Failed to create shader");if(e.shaderSource(a,r),e.compileShader(a),!e.getShaderParameter(a,e.COMPILE_STATUS)){const t=e.getShaderInfoLog(a);throw e.deleteShader(a),new Error(`Shader compile error: ${t}`)}return a}function k(e,o,r){const a=E(e,e.VERTEX_SHADER,o),t=E(e,e.FRAGMENT_SHADER,r),n=e.createProgram();if(!n)throw new Error("Failed to create program");if(e.attachShader(n,a),e.attachShader(n,t),e.linkProgram(n),!e.getProgramParameter(n,e.LINK_STATUS)){const c=e.getProgramInfoLog(n);throw e.deleteProgram(n),new Error(`Program link error: ${c}`)}e.deleteShader(a),e.deleteShader(t);const d=new Map,h=e.getProgramParameter(n,e.ACTIVE_ATTRIBUTES);for(let c=0;c<h;c++){const f=e.getActiveAttrib(n,c);f&&d.set(f.name,e.getAttribLocation(n,f.name))}const s=new Map,g=e.getProgramParameter(n,e.ACTIVE_UNIFORMS);for(let c=0;c<g;c++){const f=e.getActiveUniform(n,c);if(f){const m=e.getUniformLocation(n,f.name);m&&s.set(f.name,m)}}return{program:n,attribLocations:d,uniformLocations:s}}function D(e,o,r){for(const[a,t]of Object.entries(r)){const n=o.uniformLocations.get(a);if(n){if(typeof t=="number")e.uniform1f(n,t);else if(Array.isArray(t))switch(t.length){case 2:e.uniform2fv(n,t);break;case 3:e.uniform3fv(n,t);break;case 4:e.uniform4fv(n,t);break}}}}function z(e,o){const r=e.createTexture();if(!r)throw new Error("Failed to create texture");return e.bindTexture(e.TEXTURE_2D,r),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,o),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),{texture:r,width:o.naturalWidth,height:o.naturalHeight}}function K(e,o){const r=new Float32Array([-1,-1,0,1,1,-1,1,1,-1,1,0,0,1,1,1,0]),a=e.createBuffer();if(!a)throw new Error("Failed to create buffer");e.bindBuffer(e.ARRAY_BUFFER,a),e.bufferData(e.ARRAY_BUFFER,r,e.STATIC_DRAW);const t=4*Float32Array.BYTES_PER_ELEMENT,n=o.attribLocations.get("a_position");n!==void 0&&n!==-1&&(e.enableVertexAttribArray(n),e.vertexAttribPointer(n,2,e.FLOAT,!1,t,0));const d=o.attribLocations.get("a_texCoord");return d!==void 0&&d!==-1&&(e.enableVertexAttribArray(d),e.vertexAttribPointer(d,2,e.FLOAT,!1,t,2*Float32Array.BYTES_PER_ELEMENT)),a}function P(e){e.drawArrays(e.TRIANGLE_STRIP,0,4)}const M=`precision mediump float;
|
|
2
2
|
|
|
3
3
|
varying vec2 v_texCoord;
|
|
4
4
|
|
|
@@ -10,6 +10,25 @@ uniform float u_angleC;
|
|
|
10
10
|
uniform float u_angleM;
|
|
11
11
|
uniform float u_angleY;
|
|
12
12
|
uniform float u_angleK;
|
|
13
|
+
uniform float u_showC;
|
|
14
|
+
uniform float u_showM;
|
|
15
|
+
uniform float u_showY;
|
|
16
|
+
uniform float u_showK;
|
|
17
|
+
|
|
18
|
+
// Returns the texture-space UV of the cell center for a given rotation angle
|
|
19
|
+
vec2 cellCenterUV(vec2 uv, float angle) {
|
|
20
|
+
float rad = radians(angle);
|
|
21
|
+
float s = sin(rad);
|
|
22
|
+
float c = cos(rad);
|
|
23
|
+
mat2 rot = mat2(c, -s, s, c);
|
|
24
|
+
mat2 invRot = mat2(c, s, -s, c);
|
|
25
|
+
|
|
26
|
+
vec2 rotUV = rot * uv;
|
|
27
|
+
vec2 cell = floor(rotUV / u_gridSize);
|
|
28
|
+
vec2 cellCenter = (cell + 0.5) * u_gridSize;
|
|
29
|
+
|
|
30
|
+
return (invRot * cellCenter) / u_resolution;
|
|
31
|
+
}
|
|
13
32
|
|
|
14
33
|
float halftone(vec2 uv, float angle, float channelValue, float gridSize, float dotRadius) {
|
|
15
34
|
float rad = radians(angle);
|
|
@@ -28,31 +47,40 @@ float halftone(vec2 uv, float angle, float channelValue, float gridSize, float d
|
|
|
28
47
|
|
|
29
48
|
void main() {
|
|
30
49
|
vec2 uv = v_texCoord * u_resolution;
|
|
31
|
-
vec4 color = texture2D(u_image, v_texCoord);
|
|
32
50
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
51
|
+
// Sample at each channel's cell center (each channel has a different grid rotation)
|
|
52
|
+
vec3 cRgb = texture2D(u_image, cellCenterUV(uv, u_angleC)).rgb;
|
|
53
|
+
vec3 mRgb = texture2D(u_image, cellCenterUV(uv, u_angleM)).rgb;
|
|
54
|
+
vec3 yRgb = texture2D(u_image, cellCenterUV(uv, u_angleY)).rgb;
|
|
55
|
+
vec3 kRgb = texture2D(u_image, cellCenterUV(uv, u_angleK)).rgb;
|
|
56
|
+
|
|
57
|
+
// RGB to CMYK for each channel's sample
|
|
58
|
+
float cK = 1.0 - max(max(cRgb.r, cRgb.g), cRgb.b);
|
|
59
|
+
float cInvK = 1.0 - cK;
|
|
60
|
+
float cy = cInvK > 0.001 ? (cInvK - cRgb.r) / cInvK : 0.0;
|
|
36
61
|
|
|
37
|
-
|
|
38
|
-
float
|
|
39
|
-
float
|
|
40
|
-
|
|
41
|
-
float
|
|
42
|
-
float
|
|
62
|
+
float mK = 1.0 - max(max(mRgb.r, mRgb.g), mRgb.b);
|
|
63
|
+
float mInvK = 1.0 - mK;
|
|
64
|
+
float ma = mInvK > 0.001 ? (mInvK - mRgb.g) / mInvK : 0.0;
|
|
65
|
+
|
|
66
|
+
float yK = 1.0 - max(max(yRgb.r, yRgb.g), yRgb.b);
|
|
67
|
+
float yInvK = 1.0 - yK;
|
|
68
|
+
float ye = yInvK > 0.001 ? (yInvK - yRgb.b) / yInvK : 0.0;
|
|
69
|
+
|
|
70
|
+
float k = 1.0 - max(max(kRgb.r, kRgb.g), kRgb.b);
|
|
43
71
|
|
|
44
72
|
// Compute halftone dots for each channel
|
|
45
|
-
float cDot = halftone(uv, u_angleC, cy, u_gridSize, u_dotRadius);
|
|
46
|
-
float mDot = halftone(uv, u_angleM, ma, u_gridSize, u_dotRadius);
|
|
47
|
-
float yDot = halftone(uv, u_angleY, ye, u_gridSize, u_dotRadius);
|
|
48
|
-
float kDot = halftone(uv, u_angleK, k, u_gridSize, u_dotRadius);
|
|
73
|
+
float cDot = halftone(uv, u_angleC, cy, u_gridSize, u_dotRadius) * u_showC;
|
|
74
|
+
float mDot = halftone(uv, u_angleM, ma, u_gridSize, u_dotRadius) * u_showM;
|
|
75
|
+
float yDot = halftone(uv, u_angleY, ye, u_gridSize, u_dotRadius) * u_showY;
|
|
76
|
+
float kDot = halftone(uv, u_angleK, k, u_gridSize, u_dotRadius) * u_showK;
|
|
49
77
|
|
|
50
78
|
// Subtractive color mixing on white paper
|
|
51
79
|
float outR = (1.0 - cDot) * (1.0 - kDot);
|
|
52
80
|
float outG = (1.0 - mDot) * (1.0 - kDot);
|
|
53
81
|
float outB = (1.0 - yDot) * (1.0 - kDot);
|
|
54
82
|
|
|
55
|
-
gl_FragColor = vec4(outR, outG, outB,
|
|
83
|
+
gl_FragColor = vec4(outR, outG, outB, texture2D(u_image, v_texCoord).a);
|
|
56
84
|
}
|
|
57
85
|
`,y=`attribute vec2 a_position;
|
|
58
86
|
attribute vec2 a_texCoord;
|
|
@@ -62,7 +90,7 @@ void main() {
|
|
|
62
90
|
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
63
91
|
v_texCoord = a_texCoord;
|
|
64
92
|
}
|
|
65
|
-
`,
|
|
93
|
+
`,I={name:"halftone-cmyk",fragmentShader:M,vertexShader:y,uniforms:[{name:"u_dotRadius",type:"float",default:4,attribute:"dot-radius"},{name:"u_gridSize",type:"float",default:8,attribute:"grid-size"},{name:"u_angleC",type:"float",default:15,attribute:"angle-c"},{name:"u_angleM",type:"float",default:75,attribute:"angle-m"},{name:"u_angleY",type:"float",default:0,attribute:"angle-y"},{name:"u_angleK",type:"float",default:45,attribute:"angle-k"},{name:"u_showC",type:"float",default:1,attribute:"show-c"},{name:"u_showM",type:"float",default:1,attribute:"show-m"},{name:"u_showY",type:"float",default:1,attribute:"show-y"},{name:"u_showK",type:"float",default:1,attribute:"show-k"}]},O=`precision mediump float;
|
|
66
94
|
|
|
67
95
|
varying vec2 v_texCoord;
|
|
68
96
|
|
|
@@ -75,17 +103,24 @@ uniform float u_angle;
|
|
|
75
103
|
|
|
76
104
|
void main() {
|
|
77
105
|
vec2 uv = v_texCoord * u_resolution;
|
|
78
|
-
vec4 color = texture2D(u_image, v_texCoord);
|
|
79
|
-
|
|
80
|
-
// Convert to luminance
|
|
81
|
-
float luma = dot(color.rgb, vec3(0.299, 0.587, 0.114));
|
|
82
|
-
float darkness = 1.0 - luma;
|
|
83
106
|
|
|
84
107
|
// Rotate grid
|
|
85
108
|
float rad = radians(u_angle);
|
|
86
109
|
float s = sin(rad);
|
|
87
110
|
float c = cos(rad);
|
|
88
|
-
|
|
111
|
+
mat2 rot = mat2(c, -s, s, c);
|
|
112
|
+
mat2 invRot = mat2(c, s, -s, c);
|
|
113
|
+
vec2 rotUV = rot * uv;
|
|
114
|
+
|
|
115
|
+
// Find cell center in rotated space, un-rotate to texture space for sampling
|
|
116
|
+
vec2 cell = floor(rotUV / u_gridSize);
|
|
117
|
+
vec2 cellCenter = (cell + 0.5) * u_gridSize;
|
|
118
|
+
vec2 sampleUV = (invRot * cellCenter) / u_resolution;
|
|
119
|
+
vec4 color = texture2D(u_image, sampleUV);
|
|
120
|
+
|
|
121
|
+
// Convert to luminance
|
|
122
|
+
float luma = dot(color.rgb, vec3(0.299, 0.587, 0.114));
|
|
123
|
+
float darkness = 1.0 - luma;
|
|
89
124
|
|
|
90
125
|
vec2 grid = fract(rotUV / u_gridSize) - 0.5;
|
|
91
126
|
float dist = length(grid) * u_gridSize;
|
|
@@ -97,76 +132,7 @@ void main() {
|
|
|
97
132
|
|
|
98
133
|
gl_FragColor = vec4(result, color.a);
|
|
99
134
|
}
|
|
100
|
-
`,
|
|
101
|
-
|
|
102
|
-
varying vec2 v_texCoord;
|
|
103
|
-
|
|
104
|
-
uniform sampler2D u_image;
|
|
105
|
-
uniform vec2 u_resolution;
|
|
106
|
-
uniform float u_threshold;
|
|
107
|
-
uniform float u_direction;
|
|
108
|
-
uniform float u_span;
|
|
109
|
-
|
|
110
|
-
float brightness(vec3 c) {
|
|
111
|
-
return dot(c, vec3(0.299, 0.587, 0.114));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
void main() {
|
|
115
|
-
vec2 uv = v_texCoord;
|
|
116
|
-
vec4 color = texture2D(u_image, uv);
|
|
117
|
-
float bri = brightness(color.rgb);
|
|
118
|
-
|
|
119
|
-
// Boundary pixel — pass through
|
|
120
|
-
if (bri < u_threshold) {
|
|
121
|
-
gl_FragColor = color;
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
float rad = radians(u_direction);
|
|
126
|
-
vec2 dir = vec2(cos(rad), sin(rad));
|
|
127
|
-
vec2 step = dir / u_resolution;
|
|
128
|
-
|
|
129
|
-
int spanLen = int(u_span);
|
|
130
|
-
|
|
131
|
-
// Walk backward to find span start
|
|
132
|
-
int backCount = 0;
|
|
133
|
-
for (int i = 1; i < 256; i++) {
|
|
134
|
-
if (i >= spanLen) break;
|
|
135
|
-
vec2 sampleUV = uv - step * float(i);
|
|
136
|
-
if (sampleUV.x < 0.0 || sampleUV.x > 1.0 || sampleUV.y < 0.0 || sampleUV.y > 1.0) break;
|
|
137
|
-
float b = brightness(texture2D(u_image, sampleUV).rgb);
|
|
138
|
-
if (b < u_threshold) break;
|
|
139
|
-
backCount++;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Walk forward to find span end
|
|
143
|
-
int fwdCount = 0;
|
|
144
|
-
for (int i = 1; i < 256; i++) {
|
|
145
|
-
if (i >= spanLen) break;
|
|
146
|
-
vec2 sampleUV = uv + step * float(i);
|
|
147
|
-
if (sampleUV.x < 0.0 || sampleUV.x > 1.0 || sampleUV.y < 0.0 || sampleUV.y > 1.0) break;
|
|
148
|
-
float b = brightness(texture2D(u_image, sampleUV).rgb);
|
|
149
|
-
if (b < u_threshold) break;
|
|
150
|
-
fwdCount++;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
int totalSpan = backCount + 1 + fwdCount;
|
|
154
|
-
vec2 spanStartUV = uv - step * float(backCount);
|
|
155
|
-
|
|
156
|
-
// Count pixels in span that are darker than current (= rank)
|
|
157
|
-
int rank = 0;
|
|
158
|
-
for (int i = 0; i < 256; i++) {
|
|
159
|
-
if (i >= totalSpan) break;
|
|
160
|
-
vec2 sampleUV = spanStartUV + step * float(i);
|
|
161
|
-
float b = brightness(texture2D(u_image, sampleUV).rgb);
|
|
162
|
-
if (b < bri) rank++;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Resample at sorted position
|
|
166
|
-
vec2 sortedUV = spanStartUV + step * float(rank);
|
|
167
|
-
gl_FragColor = texture2D(u_image, sortedUV);
|
|
168
|
-
}
|
|
169
|
-
`,N={name:"pixel-sort",fragmentShader:I,vertexShader:y,uniforms:[{name:"u_threshold",type:"float",default:.5,attribute:"threshold"},{name:"u_direction",type:"float",default:0,attribute:"sort-direction"},{name:"u_span",type:"float",default:64,attribute:"sort-span"}]},B=`precision mediump float;
|
|
135
|
+
`,V={name:"halftone-duotone",fragmentShader:O,vertexShader:y,uniforms:[{name:"u_dotRadius",type:"float",default:4,attribute:"dot-radius"},{name:"u_gridSize",type:"float",default:8,attribute:"grid-size"},{name:"u_duotoneColor",type:"vec3",default:[0,.6,.8],attribute:"duotone-color"},{name:"u_angle",type:"float",default:0,attribute:"angle"}]},F=`precision mediump float;
|
|
170
136
|
|
|
171
137
|
varying vec2 v_texCoord;
|
|
172
138
|
|
|
@@ -176,31 +142,42 @@ uniform float u_dotRadius;
|
|
|
176
142
|
uniform float u_gridSize;
|
|
177
143
|
uniform vec2 u_dotOffset;
|
|
178
144
|
uniform vec3 u_bgColor;
|
|
145
|
+
uniform float u_angle;
|
|
179
146
|
|
|
180
147
|
void main() {
|
|
181
148
|
vec2 uv = v_texCoord * u_resolution;
|
|
182
149
|
|
|
183
|
-
//
|
|
184
|
-
|
|
150
|
+
// Rotate grid
|
|
151
|
+
float rad = radians(u_angle);
|
|
152
|
+
float s = sin(rad);
|
|
153
|
+
float c = cos(rad);
|
|
154
|
+
mat2 rot = mat2(c, -s, s, c);
|
|
155
|
+
mat2 invRot = mat2(c, s, -s, c);
|
|
156
|
+
vec2 rotUV = rot * uv;
|
|
157
|
+
|
|
158
|
+
// Which grid cell this fragment belongs to (in rotated space)
|
|
159
|
+
vec2 cell = floor(rotUV / u_gridSize);
|
|
185
160
|
|
|
186
|
-
// Cell origin in
|
|
161
|
+
// Cell origin in rotated space
|
|
187
162
|
vec2 cellOrigin = cell * u_gridSize;
|
|
188
163
|
|
|
189
164
|
// Dot center within the cell, shifted by u_dotOffset (0–1 range)
|
|
190
165
|
vec2 dotCenter = cellOrigin + u_dotOffset * u_gridSize;
|
|
191
166
|
|
|
192
167
|
// 4×4 multi-sample average color across the cell
|
|
168
|
+
// Sample points are computed in rotated space then un-rotated for texture lookup
|
|
193
169
|
vec3 avg = vec3(0.0);
|
|
194
170
|
for (int y = 0; y < 4; y++) {
|
|
195
171
|
for (int x = 0; x < 4; x++) {
|
|
196
|
-
vec2
|
|
172
|
+
vec2 rotSample = cellOrigin + (vec2(float(x), float(y)) + 0.5) * (u_gridSize / 4.0);
|
|
173
|
+
vec2 sampleUV = (invRot * rotSample) / u_resolution;
|
|
197
174
|
avg += texture2D(u_image, sampleUV).rgb;
|
|
198
175
|
}
|
|
199
176
|
}
|
|
200
177
|
avg /= 16.0;
|
|
201
178
|
|
|
202
|
-
// Wrapped (toroidal) distance from fragment to dot center
|
|
203
|
-
vec2 d = (
|
|
179
|
+
// Wrapped (toroidal) distance from fragment to dot center (in rotated space)
|
|
180
|
+
vec2 d = (rotUV - dotCenter) / u_gridSize;
|
|
204
181
|
d = fract(d + 0.5) - 0.5; // wrap to [-0.5, 0.5]
|
|
205
182
|
float dist = length(d) * u_gridSize;
|
|
206
183
|
|
|
@@ -211,14 +188,70 @@ void main() {
|
|
|
211
188
|
|
|
212
189
|
gl_FragColor = vec4(result, 1.0);
|
|
213
190
|
}
|
|
214
|
-
`,
|
|
215
|
-
|
|
216
|
-
|
|
191
|
+
`,N={name:"dot-grid",fragmentShader:F,vertexShader:y,uniforms:[{name:"u_dotRadius",type:"float",default:4,attribute:"dot-radius"},{name:"u_gridSize",type:"float",default:8,attribute:"grid-size"},{name:"u_dotOffset",type:"vec2",default:[.5,.5],attribute:"dot-offset"},{name:"u_bgColor",type:"vec3",default:[1,1,1],attribute:"bg-color"},{name:"u_angle",type:"float",default:0,attribute:"angle"}]},Y=`precision mediump float;
|
|
192
|
+
|
|
193
|
+
varying vec2 v_texCoord;
|
|
194
|
+
|
|
195
|
+
uniform sampler2D u_image;
|
|
196
|
+
uniform vec2 u_resolution;
|
|
197
|
+
uniform float u_dotRadius;
|
|
198
|
+
uniform float u_gridSize;
|
|
199
|
+
uniform float u_angleWarm;
|
|
200
|
+
uniform float u_angleCool;
|
|
201
|
+
uniform float u_angleK;
|
|
202
|
+
uniform float u_showWarm;
|
|
203
|
+
uniform float u_showCool;
|
|
204
|
+
uniform float u_showK;
|
|
205
|
+
uniform vec3 u_warmColor;
|
|
206
|
+
uniform vec3 u_coolColor;
|
|
207
|
+
|
|
208
|
+
float halftone(vec2 uv, float angle, float channelValue, float gridSize, float dotRadius) {
|
|
209
|
+
float rad = radians(angle);
|
|
210
|
+
float s = sin(rad);
|
|
211
|
+
float c = cos(rad);
|
|
212
|
+
mat2 rot = mat2(c, -s, s, c);
|
|
213
|
+
|
|
214
|
+
vec2 rotUV = rot * uv;
|
|
215
|
+
vec2 grid = fract(rotUV / gridSize) - 0.5;
|
|
216
|
+
|
|
217
|
+
float dist = length(grid) * gridSize;
|
|
218
|
+
float radius = dotRadius * sqrt(channelValue);
|
|
219
|
+
|
|
220
|
+
return smoothstep(radius + 0.5, radius - 0.5, dist);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
void main() {
|
|
224
|
+
vec2 uv = v_texCoord * u_resolution;
|
|
225
|
+
vec4 color = texture2D(u_image, v_texCoord);
|
|
226
|
+
|
|
227
|
+
// Two-strip film capture: red-orange and blue-green filter responses
|
|
228
|
+
float warmSep = dot(color.rgb, vec3(0.7, 0.3, 0.0));
|
|
229
|
+
float coolSep = dot(color.rgb, vec3(0.0, 0.5, 0.5));
|
|
230
|
+
|
|
231
|
+
// Black channel: derived from overall darkness
|
|
232
|
+
float k = 1.0 - max(warmSep, coolSep);
|
|
233
|
+
|
|
234
|
+
// Halftone dots for each channel
|
|
235
|
+
float warmDot = halftone(uv, u_angleWarm, warmSep, u_gridSize, u_dotRadius) * u_showWarm;
|
|
236
|
+
float coolDot = halftone(uv, u_angleCool, coolSep, u_gridSize, u_dotRadius) * u_showCool;
|
|
237
|
+
float kDot = halftone(uv, u_angleK, k, u_gridSize, u_dotRadius) * u_showK;
|
|
238
|
+
|
|
239
|
+
// Subtractive mixing: start with white paper, subtract dye layers
|
|
240
|
+
vec3 paper = vec3(1.0);
|
|
241
|
+
paper -= warmDot * (vec3(1.0) - u_warmColor);
|
|
242
|
+
paper -= coolDot * (vec3(1.0) - u_coolColor);
|
|
243
|
+
paper *= (1.0 - kDot);
|
|
244
|
+
|
|
245
|
+
gl_FragColor = vec4(clamp(paper, 0.0, 1.0), color.a);
|
|
246
|
+
}
|
|
247
|
+
`,B={name:"technicolor-2strip",fragmentShader:Y,vertexShader:y,uniforms:[{name:"u_dotRadius",type:"float",default:4,attribute:"dot-radius"},{name:"u_gridSize",type:"float",default:8,attribute:"grid-size"},{name:"u_angleWarm",type:"float",default:15,attribute:"angle-warm"},{name:"u_angleCool",type:"float",default:75,attribute:"angle-cool"},{name:"u_angleK",type:"float",default:45,attribute:"angle-k"},{name:"u_showWarm",type:"float",default:1,attribute:"show-warm"},{name:"u_showCool",type:"float",default:1,attribute:"show-cool"},{name:"u_showK",type:"float",default:1,attribute:"show-k"},{name:"u_warmColor",type:"vec3",default:[.85,.25,.06],attribute:"warm-color"},{name:"u_coolColor",type:"vec3",default:[.05,.65,.6],attribute:"cool-color"}]},w=new Map;function p(e){w.set(e.name,e)}function U(e){return w.get(e)}function G(){return Array.from(w.keys())}p(I);p(V);p(N);p(B);var X=Object.defineProperty,u=(e,o,r,a)=>{for(var t=void 0,n=e.length-1,d;n>=0;n--)(d=e[n])&&(t=d(o,r,t)||t);return t&&X(o,r,t),t};const $=2;let T=Promise.resolve();function A(e){const o=T.then(e,e);return T=o,o}const q=new Set(["effect","dotRadius","gridSize","angleC","angleM","angleY","angleK","showC","showM","showY","showK","duotoneColor","angle","dotOffsetX","dotOffsetY","bgColor","angleWarm","angleCool","showWarm","showCool","warmColor","coolColor"]),R=class R extends _.LitElement{constructor(){super(...arguments),this.src="",this.effect="halftone-cmyk",this.dotRadius=4,this.gridSize=8,this.angleC=15,this.angleM=75,this.angleY=0,this.angleK=45,this.showC=1,this.showM=1,this.showY=1,this.showK=1,this.duotoneColor="#0099cc",this.angle=0,this.dotOffsetX=.5,this.dotOffsetY=.5,this.bgColor="#ffffff",this.angleWarm=15,this.angleCool=75,this.showWarm=1,this.showCool=1,this.warmColor="#d94010",this.coolColor="#0da699",this.gateWeave=0,this.loadingBlur=0,this._webglAvailable=!0,this._snapshotUrl="",this._snapshotLoaded=!1,this._image=null,this._observer=null,this._resizeObserver=null,this._lastClientWidth=0,this._visible=!1,this._needsRender=!1,this._gateWeaveRaf=0,this._gateWeaveTime=0}render(){if(!this._webglAvailable)return _.html`<img src=${this.src} alt="" />`;const o=this.loadingBlur>0?`filter: blur(${this.loadingBlur}px)`:"";return _.html`
|
|
248
|
+
<img src=${this.src} alt="" style=${o} />
|
|
249
|
+
${this._snapshotUrl?_.html`<img
|
|
217
250
|
class="snapshot${this._snapshotLoaded?" loaded":""}"
|
|
218
251
|
src=${this._snapshotUrl}
|
|
219
252
|
@load=${this._onSnapshotLoad}
|
|
220
253
|
alt="" />`:""}
|
|
221
|
-
|
|
254
|
+
`}connectedCallback(){super.connectedCallback(),this._observer=new IntersectionObserver(o=>{var a;const r=this._visible;this._visible=((a=o[0])==null?void 0:a.isIntersecting)??!1,this._visible&&!r&&this._needsRender&&(this._needsRender=!1,A(()=>this._renderEffect()))},{rootMargin:"200px"}),this._observer.observe(this),this._resizeObserver=new ResizeObserver(()=>{if(!this._image)return;const o=this.clientWidth;o>0&&o!==this._lastClientWidth&&(this._lastClientWidth=o,this._scheduleRender())}),this._resizeObserver.observe(this)}updated(o){if(o.has("src")&&this.src){this._loadImage(this.src);return}if(!this._image)return;[...o.keys()].some(a=>q.has(a))&&this._scheduleRender(),o.has("gateWeave")&&this._updateGateWeave()}disconnectedCallback(){var o,r;super.disconnectedCallback(),(o=this._observer)==null||o.disconnect(),(r=this._resizeObserver)==null||r.disconnect(),this._stopGateWeave(),this._revokeSnapshot()}_loadImage(o){const r=new Image;r.crossOrigin="anonymous",r.onload=()=>{this._image=r,this._scheduleRender()},r.onerror=()=>{console.warn(`[some-shade] Failed to load image: ${o}`)},r.src=o}_scheduleRender(){this._visible?A(()=>this._renderEffect()):this._needsRender=!0}async _renderEffect(){var g;if(!this._image)return;const o=U(this.effect);if(!o){console.warn(`[some-shade] Unknown effect: ${this.effect}`);return}const r=Math.min(window.devicePixelRatio||1,$),a=this._image.naturalWidth,t=this._image.naturalHeight,n=this.clientWidth||a,d=Math.max(1,a/n);this._lastClientWidth=this.clientWidth;const h=document.createElement("canvas");h.width=a*r,h.height=t*r;const s=L(h);if(!s){this._webglAvailable=!1;return}try{const c=k(s,o.vertexShader,o.fragmentShader);s.useProgram(c.program);const f=z(s,this._image),m=K(s,c);s.viewport(0,0,h.width,h.height),s.clearColor(0,0,0,0),s.clear(s.COLOR_BUFFER_BIT),s.activeTexture(s.TEXTURE0),s.bindTexture(s.TEXTURE_2D,f.texture);const C=c.uniformLocations.get("u_image");C&&s.uniform1i(C,0),D(s,c,this._getUniformValues(f,r,d)),s.bindBuffer(s.ARRAY_BUFFER,m);const S=4*Float32Array.BYTES_PER_ELEMENT,v=c.attribLocations.get("a_position");v!==void 0&&v!==-1&&(s.enableVertexAttribArray(v),s.vertexAttribPointer(v,2,s.FLOAT,!1,S,0));const b=c.attribLocations.get("a_texCoord");b!==void 0&&b!==-1&&(s.enableVertexAttribArray(b),s.vertexAttribPointer(b,2,s.FLOAT,!1,S,2*Float32Array.BYTES_PER_ELEMENT)),P(s);const x=await new Promise(W=>h.toBlob(W));s.deleteTexture(f.texture),s.deleteProgram(c.program),s.deleteBuffer(m),x&&(this._snapshotLoaded=!1,this._revokeSnapshot(),this._snapshotUrl=URL.createObjectURL(x))}finally{(g=s.getExtension("WEBGL_lose_context"))==null||g.loseContext()}}_onSnapshotLoad(){this._snapshotLoaded=!0}replayTransition(o=500){this._snapshotUrl&&(this._snapshotLoaded=!1,this.updateComplete.then(()=>{setTimeout(()=>{this._snapshotLoaded=!0},o)}))}_revokeSnapshot(){this._snapshotUrl&&(URL.revokeObjectURL(this._snapshotUrl),this._snapshotUrl="")}_getUniformValues(o,r,a){const t={};return t.u_resolution=[o.width*r,o.height*r],t.u_dotRadius=this.dotRadius*a,t.u_gridSize=this.gridSize*a,this.effect==="halftone-cmyk"?(t.u_angleC=this.angleC,t.u_angleM=this.angleM,t.u_angleY=this.angleY,t.u_angleK=this.angleK,t.u_showC=this.showC,t.u_showM=this.showM,t.u_showY=this.showY,t.u_showK=this.showK):this.effect==="halftone-duotone"?(t.u_duotoneColor=this._parseHexColor(this.duotoneColor),t.u_angle=this.angle):this.effect==="dot-grid"?(t.u_dotOffset=[this.dotOffsetX,this.dotOffsetY],t.u_bgColor=this._parseHexColor(this.bgColor),t.u_angle=this.angle):this.effect==="technicolor-2strip"&&(t.u_angleWarm=this.angleWarm,t.u_angleCool=this.angleCool,t.u_angleK=this.angleK,t.u_showWarm=this.showWarm,t.u_showCool=this.showCool,t.u_showK=this.showK,t.u_warmColor=this._parseHexColor(this.warmColor),t.u_coolColor=this._parseHexColor(this.coolColor)),t}_updateGateWeave(){if(this._stopGateWeave(),this.gateWeave<=0){const a=this.renderRoot.querySelector("img.snapshot");a&&(a.style.transform="");return}const o=1e3/12,r=a=>{if(a-this._gateWeaveTime>=o){this._gateWeaveTime=a;const t=this.renderRoot.querySelector("img.snapshot");if(t){const n=(Math.random()-.5)*2*this.gateWeave,d=(Math.random()-.5)*2*this.gateWeave,h=1+this.gateWeave*.003;t.style.transform=`translate(${n}px, ${d}px) scale(${h})`}}this._gateWeaveRaf=requestAnimationFrame(r)};this._gateWeaveRaf=requestAnimationFrame(r)}_stopGateWeave(){this._gateWeaveRaf&&(cancelAnimationFrame(this._gateWeaveRaf),this._gateWeaveRaf=0)}_parseHexColor(o){const r=o.replace("#",""),a=parseInt(r.substring(0,2),16)/255,t=parseInt(r.substring(2,4),16)/255,n=parseInt(r.substring(4,6),16)/255;return[a,t,n]}};R.styles=_.css`
|
|
222
255
|
:host {
|
|
223
256
|
display: block;
|
|
224
257
|
position: relative;
|
|
@@ -240,4 +273,4 @@ void main() {
|
|
|
240
273
|
img.snapshot.loaded {
|
|
241
274
|
opacity: 1;
|
|
242
275
|
}
|
|
243
|
-
`;let
|
|
276
|
+
`;let i=R;u([l.property()],i.prototype,"src");u([l.property()],i.prototype,"effect");u([l.property({type:Number,attribute:"dot-radius"})],i.prototype,"dotRadius");u([l.property({type:Number,attribute:"grid-size"})],i.prototype,"gridSize");u([l.property({type:Number,attribute:"angle-c"})],i.prototype,"angleC");u([l.property({type:Number,attribute:"angle-m"})],i.prototype,"angleM");u([l.property({type:Number,attribute:"angle-y"})],i.prototype,"angleY");u([l.property({type:Number,attribute:"angle-k"})],i.prototype,"angleK");u([l.property({type:Number,attribute:"show-c"})],i.prototype,"showC");u([l.property({type:Number,attribute:"show-m"})],i.prototype,"showM");u([l.property({type:Number,attribute:"show-y"})],i.prototype,"showY");u([l.property({type:Number,attribute:"show-k"})],i.prototype,"showK");u([l.property({attribute:"duotone-color"})],i.prototype,"duotoneColor");u([l.property({type:Number})],i.prototype,"angle");u([l.property({type:Number,attribute:"dot-offset-x"})],i.prototype,"dotOffsetX");u([l.property({type:Number,attribute:"dot-offset-y"})],i.prototype,"dotOffsetY");u([l.property({attribute:"bg-color"})],i.prototype,"bgColor");u([l.property({type:Number,attribute:"angle-warm"})],i.prototype,"angleWarm");u([l.property({type:Number,attribute:"angle-cool"})],i.prototype,"angleCool");u([l.property({type:Number,attribute:"show-warm"})],i.prototype,"showWarm");u([l.property({type:Number,attribute:"show-cool"})],i.prototype,"showCool");u([l.property({attribute:"warm-color"})],i.prototype,"warmColor");u([l.property({attribute:"cool-color"})],i.prototype,"coolColor");u([l.property({type:Number,attribute:"gate-weave"})],i.prototype,"gateWeave");u([l.property({type:Number,attribute:"loading-blur"})],i.prototype,"loadingBlur");u([l.state()],i.prototype,"_webglAvailable");u([l.state()],i.prototype,"_snapshotUrl");u([l.state()],i.prototype,"_snapshotLoaded");customElements.get("some-shade-image")||customElements.define("some-shade-image",i);exports.SomeShadeImage=i;exports.get=U;exports.list=G;exports.register=p;
|